휠을 스크롤하여 오전·오후, 시, 분을 선택하는 12시간제 시간 선택 컴포넌트입니다.
import { Box, TimePicker } from "@seed-design/react" ;
export default function TimePickerPreview () {
return (
< Box width = "358px" maxWidth = "100%" >
< TimePicker defaultValue = {{ hour: 10 , minute: 30 }} />
</ Box >
);
}
npm install @seed-design/react @seed-design/css pnpm add @seed-design/react @seed-design/css yarn add @seed-design/react @seed-design/css bun add @seed-design/react @seed-design/css
TimePicker는 휠 형태의 시간 선택 UI만 제공합니다. Trigger, Bottom Sheet, 확인·취소 버튼과 폼 직렬화는 사용하는 화면에서 구성합니다.
import { TimePicker } from "@seed-design/react" ;
< TimePicker defaultValue = {{ hour: 10 , minute: 30 }} />
value와 onValueChange를 사용하여 선택한 시간을 제어할 수 있습니다. onValueChange는 스크롤이 선택 항목에 정착하여 값이 확정되었을 때 호출됩니다.
현재는 12시간제 UI만 지원합니다. 다만 value, defaultValue, onValueChange에서 사용하는 TimePickerValue는 오전·오후를 별도 필드로 나누지 않고 24시간 형식으로 표현합니다.
값을 지정하지 않으면 00:00에서 시작합니다. 선택된 시간이 없을 때 현재 시각에서 시작하려면 현재 시각의 hour와 minute을 defaultValue로 전달하세요.
유효한 값을 전달하세요 hour와 minute은 각각 유효한 범위의 정수여야 합니다. 유효하지 않은 value, defaultValue, minuteStep을 전달하면 RangeError가 발생합니다.
"use client" ;
import { Box, Text, TimePicker, VStack, type TimePickerValue } from "@seed-design/react" ;
import * as React from "react" ;
export default function TimePickerControlled () {
const [ value , setValue ] = React. useState < TimePickerValue >({ hour: 10 , minute: 30 });
return (
< VStack gap = "x3" align = "center" >
< Box width = "358px" maxWidth = "100%" >
< TimePicker value = {value} onValueChange = {setValue} />
</ Box >
< Text >
{ String (value.hour). padStart ( 2 , "0" )}:{ String (value.minute). padStart ( 2 , "0" )}
</ Text >
</ VStack >
);
}
Time Picker가 화면에서 차지하는 비중과 플랫폼에 따라 레이아웃을 정합니다.
시간 선택이 화면의 주된 작업이면 Inline 을 사용합니다.
모바일 폼의 시간 입력 필드에는 Bottom Sheet 를 우선 검토합니다.
Bottom Sheet는 surface를 여는 trigger가 필요합니다. FieldButton처럼 현재 값을 표시하는 입력형 버튼과 합성할 수 있습니다. Inline의 확정은 화면의 액션 영역이, Bottom Sheet의 확정은 surface 안의 확인 버튼이 담당합니다.
글쓰기나 설정 플로우에서 시간 선택이 하나의 단계라면 화면 흐름 안에 Time Picker를 직접 배치합니다.
"use client" ;
import { Box, HStack, Text, TimePicker, VStack, type TimePickerValue } from "@seed-design/react" ;
import { ActionButton } from "seed-design/ui/action-button" ;
import * as React from "react" ;
function formatTime ({ hour , minute } : TimePickerValue ) {
const period = hour < 12 ? "오전" : "오후" ;
const displayHour = hour % 12 || 12 ;
return `${ period } ${ displayHour }:${ String ( minute ). padStart ( 2 , "0" ) }` ;
}
export default function TimePickerInline () {
const [ value , setValue ] = React. useState < TimePickerValue >({ hour: 10 , minute: 30 });
const [ savedValue , setSavedValue ] = React. useState (value);
return (
< VStack width = "358px" maxWidth = "100%" gap = "x4" >
< VStack gap = "x1" >
< Text id = "business-open-time-label" textStyle = "t5Bold" >
영업 시작 시간
</ Text >
< Text color = "fg.neutralMuted" >현재 설정: { formatTime (savedValue)}</ Text >
</ VStack >
< Box width = "full" >
< TimePicker
aria-labelledby = "business-open-time-label"
value = {value}
onValueChange = {setValue}
/>
</ Box >
< HStack justify = "flex-end" >
< ActionButton variant = "neutralSolid" onClick = {() => setSavedValue (value)}>
완료
</ ActionButton >
</ HStack >
</ VStack >
);
}
모바일 폼에서는 입력 필드를 누르면 화면 하단에서 Time Picker가 나타나도록 구성합니다. 사용자가 완료하기 전까지는 임시 값을 관리하고, 확인 버튼을 누를 때 입력 필드의 값으로 반영합니다.
"use client" ;
import { Portal, TimePicker, type TimePickerValue } from "@seed-design/react" ;
import { ActionButton } from "seed-design/ui/action-button" ;
import {
BottomSheetBody,
BottomSheetContent,
BottomSheetFooter,
BottomSheetRoot,
} from "seed-design/ui/bottom-sheet" ;
import { FieldButton, FieldButtonValue } from "seed-design/ui/field-button" ;
import * as React from "react" ;
function formatTime ({ hour , minute } : TimePickerValue ) {
const period = hour < 12 ? "오전" : "오후" ;
const displayHour = hour % 12 || 12 ;
return `${ period } ${ displayHour }:${ String ( minute ). padStart ( 2 , "0" ) }` ;
}
export default function TimePickerBottomSheet () {
const [ open , setOpen ] = React. useState ( false );
const [ value , setValue ] = React. useState < TimePickerValue >({ hour: 10 , minute: 30 });
const [ draft , setDraft ] = React. useState (value);
const handleOpenChange = ( nextOpen : boolean ) => {
if (nextOpen) setDraft (value);
setOpen (nextOpen);
};
return (
< BottomSheetRoot open = {open} onOpenChange = {handleOpenChange}>
< FieldButton
label = "영업 시작 시간"
values = {[ formatTime (value)]}
style = {{ width: "240px" , maxWidth: "100%" }}
buttonProps = {{
"aria-label" : `영업 시작 시간, ${ formatTime ( value ) }` ,
"aria-haspopup" : "dialog" ,
"aria-expanded" : open,
onClick : () => handleOpenChange ( true ),
}}
>
< FieldButtonValue >{ formatTime (value)}</ FieldButtonValue >
</ FieldButton >
< Portal >
< BottomSheetContent title = "시간 선택" showCloseButton = { false } showHandle >
< BottomSheetBody paddingX = "x4" >
< TimePicker value = {draft} onValueChange = {setDraft} />
</ BottomSheetBody >
< BottomSheetFooter >
< ActionButton
variant = "neutralSolid"
onClick = {() => {
setValue (draft);
setOpen ( false );
}}
>
완료
</ ActionButton >
</ BottomSheetFooter >
</ BottomSheetContent >
</ Portal >
</ BottomSheetRoot >
);
}
TimePicker는 날짜나 시간대를 해석하지 않습니다. 애플리케이션에서 Date를 상태로 관리한다면 hour와 minute을 TimePickerValue로 변환하고, 변경된 시간을 기존 날짜에 반영하세요.
"use client" ;
import { Box, Text, TimePicker, VStack, type TimePickerValue } from "@seed-design/react" ;
import * as React from "react" ;
export default function TimePickerDateValue () {
const [ date , setDate ] = React. useState (() => new Date ( 2026 , 6 , 28 , 13 , 30 ));
const value : TimePickerValue = {
hour: date. getHours (),
minute: date. getMinutes (),
};
const handleValueChange = ({ hour , minute } : TimePickerValue ) => {
setDate (( currentDate ) => {
const nextDate = new Date (currentDate);
nextDate. setHours (hour, minute, 0 , 0 );
return nextDate;
});
};
return (
< VStack gap = "x3" align = "center" >
< Box width = "358px" maxWidth = "100%" >
< TimePicker value = {value} onValueChange = {handleValueChange} />
</ Box >
< Text >{date. toLocaleString ( "ko-KR" )}</ Text >
</ VStack >
);
}
minuteStep은 1, 5, 10, 15, 30 중 하나를 사용할 수 있으며 기본값은 5입니다.
선택된 분이 간격에 맞지 않으면 가장 가까운 값으로 반올림합니다. 예를 들어 9시 13분은 minuteStep={5}에서 9시 15분으로 표시됩니다.
반올림은 시각 전체를 기준으로 계산하므로 시가 함께 바뀔 수 있습니다. 예를 들어 23시 59분은 minuteStep={5}에서 0시 0분이 됩니다. value를 제어하는 경우 정규화 자체로 onValueChange가 호출되지는 않습니다.
분 컬럼은 순환하지만 경계를 넘어도 시는 유지됩니다. 예를 들어 10시 55분에서 다음 분 항목인 00을 선택하면 10시 00분이 됩니다.
import { Box, TimePicker } from "@seed-design/react" ;
export default function TimePickerMinuteStep () {
return (
< Box width = "358px" maxWidth = "100%" >
< TimePicker defaultValue = {{ hour: 9 , minute: 13 }} minuteStep = { 5 } />
</ Box >
);
}
disabled를 사용하면 모든 컬럼을 조작하거나 포커스할 수 없습니다.
import { Box, TimePicker } from "@seed-design/react" ;
export default function TimePickerDisabled () {
return (
< Box width = "358px" maxWidth = "100%" >
< TimePicker disabled defaultValue = {{ hour: 10 , minute: 30 }} />
</ Box >
);
}
locale에 따라 오전·오후와 숫자 표기, 컬럼 순서가 달라집니다.
전체와 각 컬럼의 기본 접근성 이름도 locale에 따라 한국어, 영어 또는 일본어로 제공됩니다. 기본값 외의 언어가 필요하거나 맥락을 더 구체적으로 설명하려면 aria-label, periodAriaLabel, hourAriaLabel, minuteAriaLabel을 직접 지정합니다.
import { Box, TimePicker } from "@seed-design/react" ;
export default function TimePickerLocalization () {
return (
< Box width = "358px" maxWidth = "100%" >
< TimePicker
locale = "en-US"
aria-label = "Select time"
periodAriaLabel = "AM/PM"
hourAriaLabel = "Hour"
minuteAriaLabel = "Minute"
defaultValue = {{ hour: 13 , minute: 30 }}
/>
</ Box >
);
}
Time Picker 전체는 group, 오전·오후·시·분 컬럼은 각각 spinbutton으로 제공됩니다. 키보드에서는 ArrowUp과 ArrowDown으로 이전·다음 항목을, Home과 End로 처음·마지막 항목을 선택할 수 있습니다.
화면에 Time Picker의 이름을 나타내는 요소가 있다면 aria-labelledby로 연결할 수 있습니다. aria-labelledby를 제공하지 않으면 전체 컴포넌트에는 기본값인 "시간 선택"이 사용됩니다.