Tracker | CoUI
본문으로 건너뛰기
LogoCoUI

Tracker

여러 세그먼트로 구성된 진행 상태를 시각적으로 추적하는 컴포넌트

목차

여러 세그먼트로 구성된 상태를 색상으로 시각화하는 컴포넌트입니다. 업타임 모니터링, 습관 추적, 스트릭 표시 등에 활용됩니다.

기본 사용법 (Basic Usage)#

Tracker(
  segments: [
    CoreTrackerSegment(status: CoreTrackerStatus.fine, tooltip: 'OK'),
    CoreTrackerSegment(status: CoreTrackerStatus.warning, tooltip: 'Slow'),
    CoreTrackerSegment(status: CoreTrackerStatus.critical, tooltip: 'Down'),
    CoreTrackerSegment(status: CoreTrackerStatus.unknown, tooltip: 'N/A'),
  ],
)
Tracker(
  segments: [
    CoreTrackerSegment(status: CoreTrackerStatus.fine, tooltip: 'OK'),
    CoreTrackerSegment(status: CoreTrackerStatus.warning, tooltip: 'Slow'),
    CoreTrackerSegment(status: CoreTrackerStatus.critical, tooltip: 'Down'),
    CoreTrackerSegment(status: CoreTrackerStatus.unknown, tooltip: 'N/A'),
  ],
)

빠른 오버라이드 (Chain)#

이미 만든 Tracker 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

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

  @override
  State<TrackerChainExample> createState() => _TrackerChainExampleState();
}

class _TrackerChainExampleState extends State<TrackerChainExample> {
  @override
  Widget build(BuildContext context) {
    return Tracker(
      segments: [
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.warning),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.critical),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.unknown),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.warning),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
      ],
    ).withStyle(
      const CoreTrackerStyle(
        itemHeight: CoreSpace.space48,
        itemSpacing: CoreSpace.space4,
        endRadius: CoreRadius.radius8,
      ),
    );
  }
}
class TrackerChainExample extends StatefulComponent {
  const TrackerChainExample({super.key});

  @override
  State<TrackerChainExample> createState() => _TrackerChainExampleState();
}

class _TrackerChainExampleState extends State<TrackerChainExample> {
  @override
  Component build(BuildContext context) {
    return Tracker(
      segments: [
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.warning),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.critical),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.unknown),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.warning),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
        CoreTrackerSegment(status: CoreTrackerStatus.fine),
      ],
    ).withStyle(
      const CoreTrackerStyle(
        itemHeight: CoreSpace.space48,
        itemSpacing: CoreSpace.space4,
        endRadius: CoreRadius.radius8,
      ),
    );
  }
}

Live Preview#

사용 시기 (When to Use)#

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

  • 서비스 업타임/다운타임 이력을 색상 바로 표시할 때
  • 습관 추적, 스트릭, 연속 기록을 시각화할 때

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

  • Progress: 단일 진행률 표시
  • Stat: 숫자 기반 통계 표시

Props / Parameters#

속성타입기본값설명
segments List<CoreTrackerSegment> 필수 세그먼트 데이터 목록
roundedbooltrue모서리 둥글기 적용
trackerStyle CoreTrackerStyle? null 인스턴스 chrome/dimensional override

CoreTrackerStyle 필드#

필드타입설명
itemHeight double? Height of each tracker segment (logical px).
itemSpacing double? Horizontal spacing (logical px, pre-scaling) between adjacent tracker segments. Flows directly into Row.spacing (Flutter native paint-API argument, 3.27+) on Flutter and the outer flex row's CSS gap inline rule on Web — both are native paint-API consumers that take a raw double, so this slot stays scalar ( double? ) rather than the nested CoreGapStyle shape used for Gap slots. Falls back to [defaultItemSpacing] when null.
endRadius double? Border-radius applied to the outer ends of the segment row when widget.rounded == true (logical px).
segmentColor CoreColor? Optional global override applied to every segment, regardless of the segment's [CoreTrackerStatus]. When null (the default) the resolver falls back to the per-status colour in [defaultsByStatus].

CoreTrackerSegment#

속성타입설명
status CoreTrackerStatus 상태 (fine, warning, critical, unknown)
tooltipString?호버 시 툴팁 텍스트

CoreTrackerStatus 색상#

상태색상
finesuccess (green)
warningwarning (orange)
criticalerror (red)
unknownoutlineVariant (gray)

동작 스펙 (Behavior)#

segments를 선언 순서대로 같은 폭의 구간으로 그립니다. 각 구간의 status가 색을 선택하며 trackerStyle.segmentColor가 있으면 모든 상태 색을 덮습니다. rounded는 바깥쪽 양 끝의 모서리만 정합니다. 각 구간은 tooltip을 표시하고 생략 시 status.name을 사용합니다. 높이·구간 간격·끝 반경은 논리 px 단위입니다. 자체 타이머·선택 상태·클릭 콜백은 없고 데이터가 바뀌면 호출자가 새 목록을 전달합니다.

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

기간·빌드별 상태를 여러 구간으로 표시하려면 Tracker를 사용합니다. 하나의 현재 상태 점은 Status, 작업 완료 비율은 Progress, 시간순 사건과 설명은 Timeline을 선택합니다.

✅ Do#

세그먼트마다 의미 있는 tooltip 지정

CoreTrackerSegment(status: CoreTrackerStatus.critical, tooltip: '다운타임 발생')

이유: tooltip을 생략하면 접근 가능한 이름이 로컬라이즈되지 않은 CoreTrackerStatus enum 이름(critical 등)으로 대체되어, 스크린 리더 사용자에게 상태 의미가 전달되지 않습니다.


❌ Don't#

요약 정보 없이 Tracker 단독 배치 금지

// ❌ "12개 중 3개 위험" 같은 요약 없이 Tracker 만 배치
Tracker(segments: uptimeSegments)

이유: 세그먼트는 hover 로만 열리는 툴팁을 가진 비인터랙티브 요소라 키보드 사용자는 개별 상태를 확인할 방법이 없습니다. 항목 수·위험 개수 같은 요약 정보는 호출자가 별도 텍스트로 제공해야 합니다.

접근성 (Accessibility)#

역할#

양 플랫폼 모두 그룹과 세그먼트 둘 다에 이름을 붙입니다. Flutter 는 트랙 전체를 Semantics(container: true, label: ...) 로 감싸고, Web 루트 <div> 는 같은 값을 aria-label 로 내보냅니다(CouiLocalizations.trackerLabel — 기본 Tracker, 한국어 트래커; 라벨일 뿐 role 은 아닙니다). 세그먼트마다도 aria-label 이 붙는데, 값은 tooltip ?? status.name — 호출자가 tooltip 을 지정하면 그 텍스트가, 지정하지 않으면 CoreTrackerStatus 의 enum 이름(fine / warning / critical / unknown)이 대신 쓰입니다(Flutter 의 Tooltip(message: tooltip ?? status.name) 과 같은 소스 · 같은 폴백). role 이 없는 <div> 위의 aria-label 이라 그룹/세그먼트 라벨 모두 일부 스크린 리더에서는 무시될 수 있습니다. 실제 role 을 가진 요소는 합성된 Tooltip 패널(role="tooltip") 하나뿐입니다.

키보드#

처리하는 키가 없습니다.

포커스 관리#

포커스를 받는 요소가 하나도 없습니다 — 세그먼트는 비인터랙티브 Container / <div> 입니다. Tab 으로 트래커에 진입할 수 없으므로 hover 로만 열리는 세그먼트 툴팁을 키보드로는 볼 수 없습니다(Web mouseenter / mouseleave, Flutter Hover → MouseRegion).

스크린 리더#

Flutter 는 로컬라이즈된 "트래커" 한 단어를 말하는 컨테이너 하나로 읽히고 개별 세그먼트 상태는 읽히지 않습니다 — 툴팁 메시지는 hover 시점에 마운트되는 오버레이 안에 있고, 합성된 Tooltip 역시 Semantics(container: true) 만 감쌉니다. Web 은 그룹 이름에 더해 세그먼트마다도 aria-label 이 있지만, role 이 없는 <div> 위의 aria-label 이라 스크린 리더가 실제로 읽어 줄지는 구현체마다 다릅니다.

알려진 제약#

  • tooltip 을 지정하지 않은 세그먼트의 접근 가능한 이름은 로컬라이즈되지 않은 CoreTrackerStatus enum 이름으로 대체됩니다(Flutter/Web 동일).
  • role="img" / list / meter 같은 역할이 없고, 트랙 전체를 대신할 텍스트 요약도 없습니다.
  • 세그먼트 툴팁에 키보드로 접근할 방법이 없습니다.

상태가 색과 툴팁으로만 구분되므로, 트래커가 전달하는 정보를 화면 밖에서도 알아야 한다면(예: "12개 중 3개 위험") 그 요약 문장을 소비자가 직접 제공해야 합니다.

감속 모션 · 고대비 · 강제 색상 · 최소 터치 타겟처럼 전 컴포넌트에 공통으로 걸리는 축은 전역 접근성 축에서 다룹니다.

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

항목FlutterWeb
클래스명TrackerTracker
렌더링 Row + Expanded + Container <div> flex + flex-1
둥글기 BorderRadius.horizontal + theme.radiusField rounded-l-${radius} / rounded-r-${radius} ( CoreTrackerTokens.endRadius )
툴팁Tooltip 위젯Tooltip 컴포넌트
상태 → 색상 CoreTrackerTokens → colorScheme.resolve(...) CoreTrackerTokens → bg-${token} Tailwind
  • Progress: 단일 진행률 바
  • Stat: 숫자 기반 통계 카드