TimePicker | CoUI

TimePicker

시간을 선택하는 시간 선택기 컴포넌트

TimePicker#

시간(시·분·초)을 선택할 수 있는 시간 선택기 컴포넌트입니다. 12시간/24시간 형식과 선택적인 초 표시를 지원합니다.

Live Preview#

기본 (popover)#

Web
Flutter
Loading Flutter...
class TimePickerDefaultExample extends StatefulComponent {
  const TimePickerDefaultExample({super.key});

  @override
  State<TimePickerDefaultExample> createState() =>
      _TimePickerDefaultExampleState();
}

class _TimePickerDefaultExampleState extends State<TimePickerDefaultExample> {
  String? _value;

  @override
  Component build(BuildContext context) {
    return div(
      [
        CoTimePicker(
          value: _value,
          placeholder: 'Select time',
          onChanged: (value) => setState(() => _value = value),
        ),
      ],
      classes: 'w-${CoreSpace.scale.space256}',
    );
  }
}
class TimePickerDefaultExample extends StatefulWidget {
  const TimePickerDefaultExample({super.key});

  @override
  State<TimePickerDefaultExample> createState() =>
      _TimePickerDefaultExampleState();
}

class _TimePickerDefaultExampleState extends State<TimePickerDefaultExample> {
  String? _value;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: CoreSpace.space256,
      child: CoTimePicker(
        value: _value,
        placeholder: 'Select time',
        onChanged: (value) => setState(() => _value = value),
      ),
    );
  }
}

다이얼로그 모드#

Web
Flutter
Loading Flutter...
class TimePickerDialogExample extends StatefulComponent {
  const TimePickerDialogExample({super.key});

  @override
  State<TimePickerDialogExample> createState() =>
      _TimePickerDialogExampleState();
}

class _TimePickerDialogExampleState extends State<TimePickerDialogExample> {
  String? _value;

  @override
  Component build(BuildContext context) {
    return div(
      [
        CoTimePicker(
          value: _value,
          placeholder: 'Select time',
          mode: CorePromptMode.dialog,
          dialogTitle: Text('Pick a time'),
          onChanged: (value) => setState(() => _value = value),
        ),
      ],
      classes: 'w-${CoreSpace.scale.space256}',
    );
  }
}
class TimePickerDialogExample extends StatefulWidget {
  const TimePickerDialogExample({super.key});

  @override
  State<TimePickerDialogExample> createState() =>
      _TimePickerDialogExampleState();
}

class _TimePickerDialogExampleState extends State<TimePickerDialogExample> {
  String? _value;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: CoreSpace.space256,
      child: CoTimePicker(
        value: _value,
        placeholder: 'Select time',
        mode: CorePromptMode.dialog,
        dialogTitle: const Text('Pick a time'),
        onChanged: (value) => setState(() => _value = value),
      ),
    );
  }
}

초 단위 (HH:MM:SS)#

Web
Flutter
Loading Flutter...
class TimePickerSecondsExample extends StatefulComponent {
  const TimePickerSecondsExample({super.key});

  @override
  State<TimePickerSecondsExample> createState() =>
      _TimePickerSecondsExampleState();
}

class _TimePickerSecondsExampleState extends State<TimePickerSecondsExample> {
  String? _value;

  @override
  Component build(BuildContext context) {
    return div(
      [
        CoTimePicker(
          value: _value,
          placeholder: 'Select time',
          showSeconds: true,
          onChanged: (value) => setState(() => _value = value),
        ),
      ],
      classes: 'w-${CoreSpace.scale.space256}',
    );
  }
}
class TimePickerSecondsExample extends StatefulWidget {
  const TimePickerSecondsExample({super.key});

  @override
  State<TimePickerSecondsExample> createState() =>
      _TimePickerSecondsExampleState();
}

class _TimePickerSecondsExampleState extends State<TimePickerSecondsExample> {
  String? _value;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: CoreSpace.space256,
      child: CoTimePicker(
        value: _value,
        placeholder: 'Select time',
        showSeconds: true,
        onChanged: (value) => setState(() => _value = value),
      ),
    );
  }
}

에러 상태#

Web
Flutter
Loading Flutter...
class TimePickerErrorExample extends StatefulComponent {
  const TimePickerErrorExample({super.key});

  @override
  State<TimePickerErrorExample> createState() =>
      _TimePickerErrorExampleState();
}

class _TimePickerErrorExampleState extends State<TimePickerErrorExample> {
  String? _value;

  @override
  Component build(BuildContext context) {
    return div(
      [
        CoTimePicker(
          value: _value,
          placeholder: 'Select time',
          errorText: 'Time is required',
          onChanged: (value) => setState(() => _value = value),
        ),
      ],
      classes: 'w-${CoreSpace.scale.space256}',
    );
  }
}
class TimePickerErrorExample extends StatefulWidget {
  const TimePickerErrorExample({super.key});

  @override
  State<TimePickerErrorExample> createState() =>
      _TimePickerErrorExampleState();
}

class _TimePickerErrorExampleState extends State<TimePickerErrorExample> {
  String? _value;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: CoreSpace.space256,
      child: CoTimePicker(
        value: _value,
        placeholder: 'Select time',
        errorText: 'Time is required',
        onChanged: (value) => setState(() => _value = value),
      ),
    );
  }
}

사용 시기 (When to Use)#

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

  • 예약, 일정, 알림 등 시간을 입력받아야 하는 경우
  • 12시간(AM/PM) 또는 24시간 형식의 시간 선택이 필요한 경우
  • 영업 시간, 배송 시간처럼 선택 가능한 시간 범위를 제한해야 하는 경우

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

  • DatePicker: 날짜와 시간을 함께 선택해야 하는 경우
  • Input: 시간을 자유 형식 텍스트로 직접 입력받는 경우
  • Select: 고정된 시간 슬롯 목록에서 선택해야 하는 경우

기본 사용법 (Basic Usage)#

// 기본 시간 선택기
CoTimePicker(
  value: selectedTime,
  placeholder: 'Select time',
  onChanged: handleTimeChanged,
)

// 24시간 형식 강제
CoTimePicker(
  value: meetingTime,
  use24HourFormat: true,
  onChanged: handleMeetingTimeChanged,
)

// 초 표시 포함
CoTimePicker(
  value: preciseTime,
  showSeconds: true,
  onChanged: handlePreciseTimeChanged,
)

// 선택 가능 범위 제한 (ISO time string)
CoTimePicker(
  value: businessHour,
  min: '09:00',
  max: '18:00',
  onChanged: handleBusinessHourChanged,
)
// 기본 시간 선택기
CoTimePicker(
  value: selectedTime,
  placeholder: 'Select time',
  onChanged: handleTimeChanged,
)

// 24시간 형식 강제
CoTimePicker(
  value: meetingTime,
  use24HourFormat: true,
  onChanged: handleMeetingTimeChanged,
)

// 초 표시 포함
CoTimePicker(
  value: preciseTime,
  showSeconds: true,
  onChanged: handlePreciseTimeChanged,
)

// 선택 가능 범위 제한
CoTimePicker(
  value: businessHour,
  min: '09:00',
  max: '18:00',
  onChanged: handleBusinessHourChanged,
)

Props / Parameters#

공통 Contract 필드 (CoreTimePickerContract):

속성FlutterWeb기본값설명
value String? String? null 현재 선택된 시간 (ISO 형식: HH:MM 또는 HH:MM:SS)
placeholder String? String? 자동 (--:--) / 'Select time' 미선택 시 표시될 텍스트
enabled bool bool true 활성화 여부
showSeconds bool bool false 초 선택 포함 여부
use24HourFormat bool? bool? null (플랫폼 기본값) 24시간 형식 사용 여부
min String? String? null 최소 선택 가능 시간 (ISO 문자열)
max String? String? null 최대 선택 가능 시간 (ISO 문자열)
onChanged ValueChanged<String>? CoreValueChanged<String>? null 시간 변경 콜백 (ISO 문자열)
mode CorePromptMode? CorePromptMode? CorePromptMode.popover 트리거 클릭 시 popover/dialog 중 어떤 surface로 열릴지
dialogTitle Widget? Component? null mode = dialog일 때 dialog 헤더에 표시되는 타이틀
errorText String? String? null 에러 메시지(있으면 보더가 error 색으로 표시됨, CoTextField와 패리티)
timePickerStyle CoreTimePickerStyle<Color, List<BoxShadow>>? CoreTimePickerStyle<String, String>? null 인스턴스 스타일 (Style 시스템 참조)

Web 확장 필드:

속성타입기본값설명
idString?nullDOM 요소 id
classes String? null 트리거에 적용할 추가 CSS 클래스
css Styles? null 루트에 적용할 인라인 스타일

스타일 시스템 (Style System)#

TimePicker의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreTimePickerStyle<Clr, Shadow> 단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 평면 chrome 필드(borderRadius / padding / height / gap / iconSize / popoverPadding / popoverGap)는 모두 제거되었습니다.

시맨틱 vs 스타일#

  • 시맨틱 enum / behaviour: 위젯 파라미터로 직접 (mode, placeholder, enabled, showSeconds, use24HourFormat, onChanged, min, max, errorText, dialogTitle)
  • chrome / dimensional / 슬롯 스타일: CoreTimePickerStyle 한곳으로 (popoverStyle / dialogStyle / segmentInputStyle / amPmButtonStyle / labelStyle / descriptionStyle / errorStyle)
  • 변형(variant) 교체: asChild — dialogTitle 슬롯 등에 직접 위젯 주입

Resolve chain#

design system default for time picker
  → CoreTimePickerTheme.style                  // 프로젝트 공통
  → parent component slot override
  → widget.timePickerStyle                     // 인스턴스별

각 nested 슬롯 스타일 (popoverStyle / dialogStyle / segmentInputStyle / amPmButtonStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다. 예: popoverStyle.triggerStyle 의 chrome 은 → CoreButtonTheme → variant 룩업 → 부모 슬롯 → widget.timePickerStyle.popoverStyle.triggerStyle 순.

옛 평면 필드 → 새 위치 매핑#

옛 chrome 필드새 위치
heightpopoverStyle.triggerStyle.height
paddingpopoverStyle.triggerStyle.paddingH
borderRadiuspopoverStyle.triggerStyle.borderRadius
gap (트리거 내부)popoverStyle.triggerStyle.gap
iconSizepopoverStyle.triggerStyle.trailingIconStyle.size
popoverPaddingpopoverStyle.panelPadding
popoverGappopoverStyle.gap

사용 예 (Flutter)#

CoTimePicker(
  value: '14:30',
  placeholder: 'Select time',
  onChanged: handleTimeChanged,
  timePickerStyle: CoreTimePickerStyle(
    popoverStyle: CorePopoverStyle(
      panelBorderRadius: 12,
      panelPadding: 16,
      gap: 8,
      triggerStyle: CoreButtonStyle(
        height: 44,
        paddingH: 16,
      ),
    ),
    segmentInputStyle: CoreTextFieldStyle(
      borderColor: Color(0xFF3B82F6),
    ),
    amPmButtonStyle: CoreButtonStyle(
      height: 36,
    ),
    labelStyle: CoreTextStyle(fontWeight: 600),
  ),
)

동작 스펙 (Behavior)#

인터랙션#

  • 트리거 클릭: 양쪽 모두 CoPopover 패널이 트리거 아래로 열립니다
  • 직접 입력: 시/분/초 필드에 숫자를 직접 입력 가능 (양쪽 동일)
  • AM/PM 토글: 12시간 형식에서 AM/PM 전환 — CoButton 기반 (양쪽 동일)
  • 저장/취소: 패널 하단의 CoButton (Save/Cancel)

값 형식#

  • 값은 ISO 시간 문자열로 주고받습니다: "14:30" 또는 "14:30:45" (showSeconds 시)
  • 12시간 포맷에서도 값은 24시간 ISO로 저장되며, 디스플레이만 hh:mm AM/PM로 포맷팅됩니다

Theme 오버라이드#

CoreComponentTheme.timePicker를 통해 프로젝트 레벨의 기본 스타일(CoreTimePickerStyle)을 지정할 수 있습니다:

CoreComponentTheme(
  timePicker: CoreTimePickerTheme(
    style: CoreTimePickerStyle(
      popoverStyle: CorePopoverStyle(
        panelBorderRadius: 8,
        panelPadding: 16,
        triggerStyle: CoreButtonStyle(height: 40, paddingH: 12),
      ),
    ),
  ),
)

접근성 (Accessibility)#

  • 트리거에 role="button" + aria-haspopup="dialog" 적용
  • 패널에 role="dialog" 적용
  • 키보드로 시/분/초 입력 가능 (양쪽 동일)
  • 터치 타겟 최소 크기: 40dp (field 기본 높이)

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

양쪽 모두 CoPopover + CoButton + CoIcon을 사용해 동일한 UX를 제공합니다. 값 형식, 토큰, 동작 모두 1:1 통일되어 있습니다.

  • DatePicker: 날짜와 시간을 함께 선택해야 하는 경우
  • Input: 시간을 텍스트로 직접 입력받는 경우
  • Select: 고정된 시간 슬롯 목록을 드롭다운으로 제공하는 경우