Stat | CoUI
LogoCoUI

Stat

수치 통계를 레이블, 값, 트렌드와 함께 표시하는 컴포넌트

Stat#

대시보드나 리포트에서 주요 수치를 레이블, 값, 변동 지표와 함께 표시하는 통계 컴포넌트입니다. Flutter / Web 양쪽에서 동일한 Stat API 를 사용합니다.

Live Preview#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 대시보드의 KPI(핵심 성과 지표)를 카드 형태로 표시할 때
  • 매출, 사용자 수, 방문자 수 등 주요 지표를 강조하여 표시할 때
  • 이전 기간 대비 증감 트렌드를 함께 보여줄 때
  • 분석 리포트의 요약 수치를 그리드로 나열할 때

대신 다른 컴포넌트를 사용하세요:

  • Progress: 완료율이나 목표 달성률을 시각적으로 표현할 때
  • NumberTicker: 단순히 애니메이션 숫자만 필요할 때 (레이블, 트렌드 불필요)
  • Text: 레이블 없이 수치만 간단히 표시할 때

기본 사용법 (Basic Usage)#

// 기본 통계
Stat(
  label: '총 사용자',
  value: '128,450',
)

// 상승 트렌드
Stat(
  label: '이번 달 매출',
  value: '₩12,450,000',
  change: '+12.5%',
  changeType: CoreStatChangeType.positive,
)

// 하락 트렌드
Stat(
  label: '이탈률',
  value: '3.2%',
  change: '-0.8%',
  changeType: CoreStatChangeType.negative,
)

// 중립 트렌드
Stat(
  label: '평균 응답 시간',
  value: '245ms',
  change: '변동 없음',
  changeType: CoreStatChangeType.neutral,
)
// 기본 통계
Stat(
  label: '총 사용자',
  value: '128,450',
)

// 상승 트렌드
Stat(
  label: '이번 달 매출',
  value: '₩12,450,000',
  change: '+12.5%',
  changeType: CoreStatChangeType.positive,
)

// 하락 트렌드
Stat(
  label: '이탈률',
  value: '3.2%',
  change: '-0.8%',
  changeType: CoreStatChangeType.negative,
)

// 중립 상태
Stat(
  label: '평균 응답 시간',
  value: '245ms',
  change: '변동 없음',
  changeType: CoreStatChangeType.neutral,
)

Props / Parameters#

속성타입기본값설명
labelString필수통계 항목 레이블
valueString필수표시할 수치 텍스트
variant CoreStatVariant outlined 카드 변형
changeString?null변동 텍스트
changeType CoreStatChangeType? null 변동 종류 (positive / negative / neutral) — 색상과 화살표를 결정
icon Widget? / Component? null 우상단 trailing 아이콘
statStyle CoreStatStyle? null per-instance chrome 오버라이드 (padding / borderRadius / colours / typography 등)

빠른 오버라이드 (Chain)#

이미 만든 Stat 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다. .radius4처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius4 == CoreRadius.radius4) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.

class StatChainExample extends StatefulWidget {
  const StatChainExample({super.key});

  @override
  State<StatChainExample> createState() => _StatChainExampleState();
}

class _StatChainExampleState extends State<StatChainExample> {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
        const Stat(
          label: 'Total Revenue',
          value: '\$45,231.89',
          change: '+20.1%',
          changeType: CoreStatChangeType.positive,
        ).radius16.primary,
        const Gap.space12(),
        // withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
        const Stat(
          label: 'Total Revenue',
          value: '\$45,231.89',
          change: '+20.1%',
          changeType: CoreStatChangeType.positive,
        ).withStyle(
          const CoreStatStyle(
            backgroundColor: CoreColor.token(CoreColors.tertiary),
            borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
            padding: CoreEdgeInsets.symmetric(
              horizontal: CoreSpace.space24,
              vertical: CoreSpace.space8,
            ),
          ),
        ),
      ],
    );
  }
}
class StatChainExample extends StatefulComponent {
  const StatChainExample({super.key});

  @override
  State<StatChainExample> createState() => _StatChainExampleState();
}

class _StatChainExampleState extends State<StatChainExample> {
  @override
  Component build(BuildContext context) {
    return div(
      [
        // 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
        Stat(
          label: 'Total Revenue',
          value: '\$45,231.89',
          change: '+20.1%',
          changeType: CoreStatChangeType.positive,
        ).radius16.primary,
        const Gap.space12(),
        // withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
        Stat(
          label: 'Total Revenue',
          value: '\$45,231.89',
          change: '+20.1%',
          changeType: CoreStatChangeType.positive,
        ).withStyle(
          const CoreStatStyle(
            backgroundColor: CoreColor.token(CoreColors.tertiary),
            borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
            padding: CoreEdgeInsets.symmetric(
              horizontal: CoreSpace.space24,
              vertical: CoreSpace.space8,
            ),
          ),
        ),
      ],
      classes: 'flex flex-col items-start',
    );
  }
}

스타일 시스템 (Style System)#

CoreStatStyle 필드#

필드타입설명
padding CoreEdgeInsets? Inner padding around the stat content.
borderRadius CoreBorderRadius? Outer border radius of the stat card.
borderColorCoreColor?Outer border colour.
borderWidth double? Outer border width (logical px).
backgroundColor CoreColor? Card background colour.
contentGapStyle CoreGapStyle? Nested [CoreGapStyle] slot for the 1-off gap between the 3 content sections of the card (label-row ↔ value ↔ change-row). Forwarded straight to the Gap widget that separates the sections. null defers to [defaultContentGapStyle].
changeIconGapStyle CoreGapStyle? Nested [CoreGapStyle] slot for the 1-off inner gap between the change arrow glyph and the change text inside the change row. Forwarded straight to the Gap widget. null defers to [defaultChangeIconGapStyle].
labelColorCoreColor?Label text colour.
valueColorCoreColor?Value text colour.
positiveColor CoreColor? Colour of the change indicator when changeType == positive.
negativeColor CoreColor? Colour of the change indicator when changeType == negative.
neutralColor CoreColor? Colour of the change indicator when changeType == neutral.
labelTextStyle CoreTextStyle? Label text style override.
valueTextStyle CoreTextStyle? Value text style override.
changeTextStyle CoreTextStyle? Change indicator text style override.

CoreStatStyle 변형별 기본값 (CoreStatVariantStyle)#

필드outlined
backgroundColorsurface
borderColoroutline
borderWidthstroke1

변형 (Variants)#

상승 트렌드#

Stat(
  label: '신규 가입',
  value: '1,284',
  change: '+8.2%',
  changeType: CoreStatChangeType.positive,
)

하락 트렌드#

Stat(
  label: '취소율',
  value: '1.4%',
  change: '-0.3%',
  changeType: CoreStatChangeType.negative,
)

중립#

Stat(
  label: '활성 세션',
  value: '892',
  change: '±0%',
  changeType: CoreStatChangeType.neutral,
)

그리드 레이아웃#

여러 Stat 을 그리드로 배치하여 대시보드를 구성합니다.

GridView.count(
  crossAxisCount: 3,
  children: [
    Stat(
      label: '총 사용자',
      value: '128,450',
      change: '+5%',
      changeType: CoreStatChangeType.positive,
    ),
    Stat(
      label: '매출',
      value: '₩12.4M',
      change: '+12%',
      changeType: CoreStatChangeType.positive,
    ),
    Stat(
      label: '이탈률',
      value: '3.2%',
      change: '-0.8%',
      changeType: CoreStatChangeType.negative,
    ),
  ],
)

동작 스펙 (Behavior)#

인터랙션#

  • Stat 은 기본적으로 표시 전용 컴포넌트입니다.

상태 전환#

  • change / changeType 변경 시 아이콘과 색이 즉시 갱신됩니다.
  • changeType.positive → 색 = success, 화살표 =
  • changeType.negative → 색 = error, 화살표 =
  • changeType.neutral → 색 = onSurfaceVariant, 화살표 =

애니메이션#

기본 동작 없음. value 슬롯에 다른 컴포넌트 (예: 숫자 ticker) 를 합성해 표현하려면 호출처에서 별도 위젯과 조합합니다.

사용 가이드라인 (Usage Guidelines)#

✅ Do#

의미 있는 비교 기간을 함께 표시

Stat(
  label: '월간 매출',
  value: '₩12,450,000',
  change: '+11.7% (지난 달 대비)',
  changeType: CoreStatChangeType.positive,
)

어느 기간과 비교한 변화율인지 명시하면 수치의 의미를 정확히 전달할 수 있습니다.


❌ Don't#

changeTypechange 의미가 어긋나지 않게 하세요

// ❌ change 는 양수 표기인데 changeType 은 negative
Stat(
  label: '이탈률',
  value: '3.2%',
  change: '+0.5%',
  changeType: CoreStatChangeType.negative,
)

방향이 일치하지 않으면 사용자가 혼란스러워합니다. 이탈률이 증가했다면 보통 부정적이므로 change: '+0.5%' + changeType.negative 로 일치시키거나, 맥락에 따라 change: '-0.5%' + changeType.negative 로 명확히 합니다.

❌ Don't#

너무 많은 Stat 을 한 화면에 나열 금지

// ❌ 15 개 KPI 한 화면에 나열
GridView.count(
  crossAxisCount: 5,
  children: List.generate(15, (i) => Stat(...)),
)

너무 많은 통계는 인지 과부하를 유발합니다. 한 화면에는 가장 중요한 4 ~ 8 개의 KPI 만 표시하세요.

❌ Don't#

단위 없이 숫자만 표시하지 마세요

// ❌ 단위가 없어 의미를 알 수 없는 숫자
Stat(
  label: '방문자',
  value: '15234',
)

숫자만 있으면 무엇을 기준으로 한 값인지 알 수 없습니다. 단위(명·원·% 등)를 반드시 함께 표시하세요.

접근성 (Accessibility)#

키보드 인터랙션#

해당 없음. Stat 은 기본적으로 인터랙티브 요소가 아닙니다.

스크린 리더#

  • Flutter: 레이블·값·변동을 순서대로 읽도록 Semantics 적용 권장 (예: '총 사용자, 128,450명, 5% 상승').
  • Web: dl/dt/dd 구조 또는 적절한 aria-label 적용 권장.

터치 타겟#

해당 없음. 표시 전용 요소. (클릭 가능하게 만들 경우 최소 터치 타겟 24×24, CoreTouchTarget.minimum 을 보장합니다.)

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명StatStat
icon 타입Widget?Component?
변동 화살표 / / 글리프 / / 글리프 (동일)
  • Card: Stat 의 외곽 chrome 을 더 다양하게 표현하고 싶을 때.
  • Progress: 목표 대비 진행률을 바 형태로 표시할 때.
  • NumberTicker: 단순히 애니메이션 숫자만 필요할 때.