# Date Picker URL: /react/components/date-picker Source: https://github.com/daangn/seed-design/blob/dev/docs/content/react/components/date-picker.mdx 달력에서 하나의 날짜, 날짜 범위 또는 여러 날짜를 선택하는 컴포넌트입니다. 사용 가능 버전: @seed-design/react@2.2.0, @seed-design/css@2.4.0 ## Preview ```tsx import { Box, DatePicker } from "@seed-design/react"; export default function DatePickerPreview() { return ( ); } ``` ## Installation - npm: npm install @seed-design/react @seed-design/css - pnpm: pnpm add @seed-design/react @seed-design/css - yarn: yarn add @seed-design/react @seed-design/css - bun: bun add @seed-design/react @seed-design/css ## Props ### DatePicker ### TwoMonthDatePicker ### WeekDatePicker ### ContinuousDatePicker ## Examples ### 레이아웃 Date Picker는 레이아웃에 따라 다음 공개 컴포넌트로 나뉩니다. - `DatePicker`: 현재 월의 실제 주 수만 렌더링합니다. - `TwoMonthDatePicker`: 연속한 두 달을 가로로 표시합니다. 이전·다음 버튼은 한 달씩 이동합니다. - `WeekDatePicker`: 한 주만 표시하며 이전·다음 버튼은 한 주씩 이동합니다. - `ContinuousDatePicker`: `monthRange` 안의 월을 세로로 가상화합니다. `monthRange`를 생략하면 `yearRange`를 월 범위로 변환합니다. `height`, `minHeight`, `maxHeight` 중 하나를 반드시 지정합니다. 한 달과 두 달 레이아웃은 항상 6주 높이를 확보하지 않고 각 월에 필요한 4~6주만 렌더링합니다. Date Picker는 자체적으로 고정 폭이나 최대 폭을 제한하지 않고 부모 컨테이너의 너비를 채웁니다. 한 달과 두 달 레이아웃에 필요한 가로 크기는 사용하는 화면에서 적절한 너비의 컨테이너로 감싸서 결정하세요. ```tsx "use client"; import { Box, ContinuousDatePicker, DatePicker, TwoMonthDatePicker, VStack, WeekDatePicker, } from "@seed-design/react"; import { SegmentedControl, SegmentedControlItem } from "seed-design/ui/segmented-control"; import * as React from "react"; const today = { year: 2026, month: 7, day: 30 }; const layoutOptions = [ { value: "month", label: "한 달" }, { value: "twoMonths", label: "두 달" }, { value: "week", label: "한 주" }, { value: "continuous", label: "연속" }, ] as const; type DatePickerLayout = (typeof layoutOptions)[number]["value"]; export default function DatePickerVisibleRanges() { const [layout, setLayout] = React.useState("month"); const picker = (() => { switch (layout) { case "twoMonths": return ; case "week": return ; case "continuous": return ( ); default: return ; } })(); return ( setLayout(value as DatePickerLayout)} > {layoutOptions.map((option) => ( {option.label} ))} {picker} ); } ``` ### 연속 달력의 노출 월 제한 `ContinuousDatePicker`의 `monthRange`로 스크롤해서 볼 수 있는 첫 월과 마지막 월을 제한할 수 있습니다. 양끝 월을 모두 포함합니다. `yearRange`만 전달하면 시작 연도의 1월부터 종료 연도의 12월까지로 변환합니다. 두 prop을 함께 사용하면 `monthRange`는 `yearRange` 안에 있어야 합니다. `monthRange`는 노출되는 월을 정하고 `constraints`는 날짜별 선택 가능 여부를 정합니다. 아래 예시는 12월 15일부터 30일 뒤인 1월 14일까지만 선택할 수 있게 하면서, 스크롤 범위도 12월과 1월로 제한합니다. ```tsx "use client"; import { Box, ContinuousDatePicker, dateOnOrAfter, dateOnOrBefore } from "@seed-design/react"; const today = { year: 2026, month: 12, day: 15 }; const thirtyDaysLater = { year: 2027, month: 1, day: 14 }; export default function ContinuousDatePickerMonthRange() { return ( ); } ``` ### 선택 방식 `selectionMode`에 따라 하나의 날짜(`single`), 날짜 범위(`range`), 여러 날짜(`multiple`)를 선택합니다. 기본값은 `single`입니다. - `single`의 값은 `DatePickerDate`입니다. - `range`는 시작일을 선택한 직후 `{ start }`, 종료일까지 선택하면 `{ start, end }`를 반환합니다. - `multiple`은 중복 없는 날짜 배열을 항상 오름차순으로 반환합니다. ```tsx "use client"; import { Box, DatePicker, Text, VStack, type DatePickerDate, type DatePickerRangeValue, } from "@seed-design/react"; import * as React from "react"; const today = { year: 2026, month: 7, day: 30 }; export default function DatePickerSelectionModes() { const [single, setSingle] = React.useState(today); const [range, setRange] = React.useState({ start: { year: 2026, month: 7, day: 10 }, end: { year: 2026, month: 7, day: 13 }, }); const [multiple, setMultiple] = React.useState([ { year: 2026, month: 7, day: 7 }, { year: 2026, month: 7, day: 14 }, ]); return ( Single Range Multiple ); } ``` ### 날짜 선택 제약 조건 `constraints`는 후보 날짜를 선택할 수 있는지 판단하는 함수 배열입니다. 모든 함수가 `true`를 반환해야 선택할 수 있습니다. 직접 함수를 작성하거나 다음 helper를 조합할 수 있습니다. - `dateOnOrAfter`, `dateOnOrBefore`: 선택 가능한 날짜의 양끝을 포함해 제한합니다. - `excludeDates`: 예약 완료일, 휴무일처럼 선택할 수 없는 날짜를 제외합니다. Range에서는 기본적으로 시작일부터 후보 종료일까지의 전체 구간을 검사합니다. - `rangeDayCountAtLeast`, `rangeDayCountAtMost`: 시작일과 종료일을 모두 포함한 날짜 수를 제한합니다. - `maxSelectionCount`: Multiple에서 선택 가능한 최대 개수를 제한합니다. 이미 선택한 날짜의 해제는 허용합니다. 조건을 만족하지 않는 날짜는 키보드로 포커스할 수 있지만 `aria-disabled` 상태이며 선택할 수 없습니다. 아래 예시는 체크인·체크아웃을 포함한 선택 구간을 1~14일로 제한해 당일치기를 허용하고, 예약 완료일이 포함된 구간은 선택할 수 없도록 구성합니다. ```tsx "use client"; import { Box, Text, TwoMonthDatePicker, VStack, excludeDates, rangeDayCountAtLeast, rangeDayCountAtMost, type DatePickerDate, type DatePickerRangeValue, } from "@seed-design/react"; import * as React from "react"; const bookedDateKeys = new Set(["2026-07-18", "2026-07-19", "2026-07-25"]); function toDateKey(date: DatePickerDate) { return `${date.year}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`; } const constraints = [ rangeDayCountAtLeast(1), rangeDayCountAtMost(14), excludeDates((date) => bookedDateKeys.has(toDateKey(date))), ]; export default function DatePickerReservation() { const [value, setValue] = React.useState({ start: { year: 2026, month: 7, day: 10 }, }); return ( 체크인 {toDateKey(value.start)} {value.end ? ` · 체크아웃 ${toDateKey(value.end)}` : " · 체크아웃을 선택하세요"} 선택 구간은 체크인·체크아웃을 포함해 1~14일이어야 합니다. 예약 완료일(7월 18일·19일·25일)이 포함된 구간은 선택할 수 없습니다. ); } ``` ### 시작일을 유지하고 종료일만 변경 이미 시작된 기간을 수정할 때는 `selectionMode="range"`와 `rangeStartReadOnly`를 함께 사용합니다. 기존 시작일은 선택 상태로 표시되지만 변경할 수 없고, 시작일보다 늦은 날짜만 새 종료일로 선택할 수 있습니다. `rangeStartReadOnly`에는 시작일을 포함한 `value` 또는 `defaultValue`가 필요합니다. 제어 방식으로 사용할 때 외부에서 전달하는 시작일이 바뀌면 Date Picker에도 새 값이 반영됩니다. 아래 광고 기간 예시는 시작일이 8월 7일이고 오늘이 8월 10일인 상황입니다. `dateOnOrAfter`를 함께 사용해 이미 지난 8일과 9일은 새 종료일로 선택할 수 없도록 제한합니다. ```tsx "use client"; import { Box, DatePicker, Text, VStack, dateOnOrAfter, type DatePickerRangeValue, } from "@seed-design/react"; import * as React from "react"; const today = { year: 2026, month: 8, day: 10 } as const; const initialValue: DatePickerRangeValue = { start: { year: 2026, month: 8, day: 7 }, end: { year: 2026, month: 8, day: 14 }, }; function formatDate(date: DatePickerRangeValue["start"]) { return `${date.year}.${date.month}.${date.day}`; } export default function DatePickerReadOnlyRangeStart() { const [value, setValue] = React.useState(initialValue); return ( 광고 기간 {formatDate(value.start)} {value.end ? ` ~ ${formatDate(value.end)}` : ""} ); } ``` ### 날짜 셀에 추가 정보 표시 기본 날짜 숫자를 유지하면서 가격, 예약 가능 여부, 혼잡도 같은 정보를 추가할 때는 `renderDateCellSupplement`를 우선 사용하세요. 함수에는 날짜와 포맷된 날짜 숫자, 선택·오늘·범위·사용 불가·포커스 상태가 전달됩니다. `renderDateCellSupplement`로 표현할 수 없어 날짜 숫자를 포함한 내부 콘텐츠 전체를 직접 구성해야 할 때만 `renderDateCellContent`를 사용합니다. 이 prop은 일반적인 확장 지점이 아니라 저수준 이스케이프 해치이며, 두 prop은 동시에 사용할 수 없습니다. 컴포넌트는 날짜 셀의 DOM, ARIA, 이벤트, 상태 배경을 계속 소유합니다. 따라서 두 함수에서는 버튼이나 `gridcell`을 만들지 말고 콘텐츠만 반환합니다. 각 주의 셀 높이는 가장 높은 콘텐츠에 맞춰 늘어납니다. 좁은 화면에서 콘텐츠가 가로로 넘치는 경우에는 렌더 함수에서 `maxLines` 같은 말줄임 정책을 직접 적용합니다. ```tsx "use client"; import { Box, DatePicker, Text } from "@seed-design/react"; const prices = new Map([ [10, "12만원"], [11, "9만원"], [12, "11만원"], [14, "8만원"], ]); export default function DatePickerCustomCell() { return ( ( {isUnavailable ? "선택 불가" : (prices.get(date.day) ?? "예약 가능")} )} constraints={[(date) => date.day !== 13]} /> ); } ``` ### Time Picker와 조합 Date Picker는 시간과 시간대를 해석하지 않는 달력 날짜인 `DatePickerDate`를 사용합니다. Time Picker의 `TimePickerValue`와 별도로 상태를 관리한 뒤, 서버 요청이나 도메인 모델 경계에서 두 값을 합칩니다. 시간대가 필요한 서비스는 이 시점에 `CalendarDateTime`, `ZonedDateTime` 또는 서비스의 날짜·시간 DTO로 변환하세요. Date Picker 자체에 `CalendarDateTime`을 전달하면 날짜 선택 UI가 불필요하게 시간대 정책에 결합되므로 지원하지 않습니다. ```tsx "use client"; import { Box, DatePicker, Text, TimePicker, VStack, type DatePickerDate, type TimePickerValue, } from "@seed-design/react"; import * as React from "react"; const TODAY: DatePickerDate = { year: 2026, month: 7, day: 30 }; export default function DatePickerDateAndTime() { const [date, setDate] = React.useState(TODAY); const [time, setTime] = React.useState({ hour: 14, minute: 30 }); return ( {date.year}.{date.month}.{date.day}. {String(time.hour).padStart(2, "0")}: {String(time.minute).padStart(2, "0")} ); } ``` ### 날짜로 이동하고 포커스하기 `viewDate`, `defaultViewDate`, `onViewDateChange`는 현재 표시하는 월·주·스크롤 기준점을 제어합니다. Month에서는 월의 첫날로 정규화되므로 특정 날짜 셀을 이동 대상으로 지정할 때는 `actionsRef`를 사용합니다. - `navigateToDate(date)`: 날짜가 보이도록 이동하고 다음 Tab 진입점을 갱신하지만 현재 DOM 포커스는 유지합니다. 외부의 “오늘” 버튼에는 이 action을 권장합니다. - `focusDate(date)`: 날짜가 보이도록 이동한 뒤 날짜 셀에 실제 DOM 포커스를 둡니다. 키보드 단축키나 사용자를 명시적으로 그리드 안으로 이동시키는 동작에 사용합니다. ```tsx "use client"; import { ActionButton, Box, DatePicker, HStack, type DatePickerActions, type DatePickerDate, } from "@seed-design/react"; import * as React from "react"; const today: DatePickerDate = { year: 2026, month: 7, day: 30 }; export default function DatePickerNavigationActions() { const actionsRef = React.useRef(null); return ( actionsRef.current?.navigateToDate(today)} > 오늘로 이동 actionsRef.current?.focusDate(today)}> 오늘에 포커스 ); } ``` ### 월·연도 이동 제목을 누르면 Time Picker와 같은 Wheel Picker로 전환합니다. 연도 휠은 반복하지 않고 `yearRange` 안에서 이동하며, 월 휠은 반복합니다. `yearRange`의 기본값은 `today`를 기준으로 앞뒤 100년입니다. Month 계열의 표시 기준 날짜는 월의 첫날, Week는 locale의 주 시작일로 정규화합니다. ### Locale 기본 locale은 `ko-KR`입니다. 월·요일·날짜·숫자 표기와 주 시작일은 locale을 따릅니다. `weekStartsOn`을 전달하면 주 시작일만 재정의합니다. RTL locale에서는 좌우 방향키와 이전·다음 아이콘 방향도 반전됩니다. 루트, 이전·다음 버튼과 연도·월 휠의 기본 접근성 이름은 한국어와 영어로 제공됩니다. 제품 맥락에 맞는 이름이 필요하면 `ariaLabels`로 재정의합니다. ### Accessibility Date Picker는 WAI-ARIA Grid 패턴을 사용합니다. 하나의 날짜만 `tab` 순서에 들어가며 방향키로 날짜를 이동합니다. - `ArrowLeft`, `ArrowRight`: 하루 전·후 - `ArrowUp`, `ArrowDown`: 일주일 전·후 - `Home`, `End`: 현재 주의 시작·끝 - `PageUp`, `PageDown`: 현재 표시 단위의 이전·다음 - `Shift + PageUp`, `Shift + PageDown`: 일 년 전·후 - `Enter`, `Space`: 포커스한 날짜 선택 화면에 Date Picker의 이름을 나타내는 요소가 있다면 `aria-labelledby`로 연결할 수 있습니다. 그렇지 않으면 locale에 따라 `"날짜 선택"` 또는 `"Select date"`가 사용됩니다.