Stat | CoUI

Stat

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

Stat#

대시보드나 리포트에서 주요 수치를 레이블, 값, 보조 텍스트, 트렌드 방향과 함께 표시하는 통계 컴포넌트입니다.

Live Preview#

Web
Total Revenue
$45,231.89
+20.1%
Flutter
Loading Flutter...
class StatDefaultExample extends StatefulComponent {
  const StatDefaultExample({super.key});

  @override
  State<StatDefaultExample> createState() => _StatDefaultExampleState();
}

class _StatDefaultExampleState extends State<StatDefaultExample> {
  @override
  Component build(BuildContext context) {
    return CoStat(
      label: 'Total Revenue',
      value: '$45,231.89',
      change: '+20.1%',
      changeType: CoreStatChangeType.positive,
    );
  }
}
class StatDefaultExample extends StatefulWidget {
  const StatDefaultExample({super.key});

  @override
  State<StatDefaultExample> createState() => _StatDefaultExampleState();
}

class _StatDefaultExampleState extends State<StatDefaultExample> {
  @override
  Widget build(BuildContext context) {
    return const CoStat(
      label: 'Total Revenue',
      value: '$45,231.89',
      change: '+20.1%',
      changeType: CoreStatChangeType.positive,
    );
  }
}

사용 시기 (When to Use)#

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

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

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

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

기본 사용법 (Basic Usage)#

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

// 트렌드 포함
Stat(
  label: '이번 달 매출',
  value: '₩12,450,000',
  helpText: '지난 달 대비',
  trend: StatTrend.up,
  trendValue: '+12.5%',
)

// 하락 트렌드
Stat(
  label: '이탈률',
  value: '3.2%',
  helpText: '지난 주 대비',
  trend: StatTrend.down,
  trendValue: '-0.8%',
)

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

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

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

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

Props / Parameters#

속성타입기본값설명
labelString필수통계 항목 레이블
value dynamic 필수 표시할 수치 (String 또는 Widget)
helpText String? null 값 하단에 표시할 보조 설명
trend StatTrend? null 트렌드 방향 (up / down / neutral)
trendValue String? null 트렌드 수치 텍스트

변형 (Variants)#

상승 트렌드#

Stat(
  label: '신규 가입',
  value: '1,284',
  trend: StatTrend.up,
  trendValue: '+8.2%',
)

하락 트렌드#

Stat(
  label: '취소율',
  value: '1.4%',
  trend: StatTrend.down,
  trendValue: '-0.3%',
)

중립#

Stat(
  label: '활성 세션',
  value: '892',
  trend: StatTrend.neutral,
  trendValue: '±0%',
)

그리드 레이아웃#

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

GridView.count(
  crossAxisCount: 3,
  children: [
    Stat(label: '총 사용자', value: '128,450', trend: StatTrend.up, trendValue: '+5%'),
    Stat(label: '매출', value: '₩12.4M', trend: StatTrend.up, trendValue: '+12%'),
    Stat(label: '이탈률', value: '3.2%', trend: StatTrend.down, trendValue: '-0.8%'),
  ],
)

동작 스펙 (Behavior)#

인터랙션#

  • Stat은 기본적으로 표시 전용 컴포넌트입니다.
  • valueCouiNumberTicker를 전달하면 값 변경 시 애니메이션 적용 가능

상태 전환#

  • 값 변경: value가 Widget인 경우 해당 Widget의 애니메이션에 따름
  • 트렌드 변경: 아이콘 색상 즉시 변경 (up: 초록, down: 빨강, neutral: 회색)

애니메이션#

  • 기본: 없음
  • valueCouiNumberTicker 사용 시 숫자 롤링 애니메이션 적용

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

✅ Do#

NumberTicker와 조합하여 살아있는 대시보드 구현

CouiStat(
  label: '실시간 방문자',
  value: CouiNumberTicker(
    value: realtimeVisitors,
    separator: ',',
    duration: Duration(milliseconds: 400),
    style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
  ),
  helpText: '현재 접속 중',
  trend: realtimeVisitors > previousVisitors ? StatTrend.up : StatTrend.down,
  trendValue: '${(realtimeVisitors - previousVisitors).abs()} 명',
)

NumberTicker와 조합하면 실시간 데이터 변화를 생동감 있게 표현할 수 있습니다.


❌ Don't#

트렌드 방향과 trendValue의 의미 불일치 금지

// ❌ 트렌드 방향과 값이 모순됨
CouiStat(
  label: '이탈률',
  value: '3.2%',
  trend: StatTrend.down, // 하락
  trendValue: '+0.5%',   // 상승 값 — 혼란스러움
)

트렌드 방향과 trendValue가 일치하지 않으면 사용자가 혼란스러워합니다. 이탈률 감소는 긍정적이므로 StatTrend.down-0.5% 또는 맥락에 따라 StatTrend.up으로 표시하세요.

✅ Do#

비교 기간을 helpText로 명시

CouiStat(
  label: '월간 매출',
  value: '₩12,450,000',
  helpText: '지난 달(₩11,150,000) 대비',
  trend: StatTrend.up,
  trendValue: '+11.7%',
)

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


❌ Don't#

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

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

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

✅ Do#

지표 값의 변화량(delta)을 함께 표시하세요.

CouiStat(
  label: '월간 매출',
  value: '₩12,450,000',
  delta: '+15.3%',
  deltaType: DeltaType.positive,  // 초록색으로 표시
)

현재 값과 이전 기간 대비 변화율을 함께 보여주면 트렌드를 한눈에 파악할 수 있어 의사결정에 도움이 됩니다.


❌ Don't#

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

// ❌ 단위가 없어 의미를 알 수 없는 숫자
CouiStat(
  label: '방문자',
  value: '15234',  // 명/일/월 구분 불가
)

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

접근성 (Accessibility)#

키보드 인터랙션#

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

스크린 리더#

  • Flutter: 레이블, 값, 트렌드를 순서대로 읽는 Semantics 적용 권장 ('총 사용자, 128,450명, 5% 상승')
  • Web: dl/dt/dd 구조 또는 적절한 aria-label 적용

터치 타겟#

해당 없음. 표시 전용 요소. (클릭 시 상세 보기로 이동하는 경우 48x48dp 보장)

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

항목FlutterWeb
클래스명CouiStatStat
value 타입 dynamic (String 또는 Widget) dynamic (String 또는 Widget)
트렌드 아이콘Material IconsSVG 아이콘
  • NumberTicker: 애니메이션 숫자를 value로 사용할 때
  • Progress: 목표 대비 진행률을 바 형태로 표시할 때
  • Card: Stat을 카드로 감싸 배경과 그림자를 추가할 때

조합 예제#

// Stat + Card + NumberTicker 조합
GridView.count(
  crossAxisCount: 3,
  crossAxisSpacing: 16,
  mainAxisSpacing: 16,
  children: kpiData.map((kpi) => CouiCard(
    child: CouiStat(
      label: kpi.label,
      value: CouiNumberTicker(
        value: kpi.value,
        separator: ',',
        prefix: kpi.prefix,
        style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
      ),
      helpText: '지난 달 대비',
      trend: kpi.isUp ? StatTrend.up : StatTrend.down,
      trendValue: kpi.changePercent,
    ),
  )).toList(),
)