8.7 KiB
Calendar
현재 HM 구현의 기준 문서. 공통 원칙은 docs/guidelines/프로젝트-지침.md(§6, §7), Token 구조 원칙은 테마-토큰-가이드.md를 따른다. 이 문서는 Calendar 고유의 API·Token·상태·결정만 기록한다.
1. 상태와 성격
| 항목 | 내용 |
|---|---|
| 성격 | Primitive component (날짜 선택 UI). Date Picker 자체가 아니다 |
| 구현 상태 | 구현 완료 (현재 HM 규격) |
| 기준일 | 2026-10-02 |
| 기반 | shadcn/ui Calendar = react-day-picker v10 |
| 대표 Story | Components/Search & Date/Calendar → All (이름 "Calendar") |
| 보류 | 아래 §11 |
2. 적용 근거
- 참고 근거: 외부 ui_viz Reference (ZIP 미포함)의 기존 테스트 규격에는 Calendar가 없다(인벤토리에 Date Picker 이름만). 참고할 수치가 없었다.
- 현재 HM 규격: 아래 §3~§4. 현재 HM Semantic/Primitive로 새로 정했다. Button/Input/Popover Token은 참조하지 않는다(Navigation 버튼이 Button처럼 보여도 Calendar Token으로 관리).
- 구현 방식: react-day-picker가 날짜 계산, 월 이동, 키보드, 선택 모드, ARIA grid를 소유한다. HM은 시각만 입힌다. 라이브러리의 기본 class(
rdp-*)와 날짜 셀의data-*상태 속성을 그대로 두고[data-slot="calendar"]아래에서 CSS로 스타일한다. - 보류: §11.
3. 기본 규격 (현재 HM 규격)
| 항목 | 값 |
|---|---|
| Surface | 없음(투명). 감싸는 컨테이너(Popover, Card 등)가 면과 윤곽을 가진다 |
| Padding / 폭 | 12px / 내용 폭(fit-content, 한 달 276px) |
| Caption | 가운데, 높이 32px, 14 / 20 / 700 |
| Navigation 버튼 | 28 × 28px, radius 4px, 아이콘 16px(lucide chevron), Caption 줄 양끝에 절대 배치 |
| Weekday | 12 / 18 / 500, --hm-sem-fg-secondary, 폭 36px |
| Day | 36 × 36px 셀과 버튼, radius 4px, 14 / 20 |
| 여러 달 | numberOfMonths에 따라 flex로 나란히, 좁으면 줄바꿈(gap 16px) |
| Locale | 기본 한국어(react-day-picker/locale의 ko, 추가 dependency 없음). locale prop으로 교체 |
4. 상태 규격
| 상태 | 값 |
|---|---|
| Default | 투명 |
| Hover | 배경 --accent 45% |
Focus (:focus-visible) |
ring 모양 0 0 0 3px ring 50% |
Today (data-today) |
배경 --hm-sem-bg-subtle, weight 700 |
Selected (data-selected) |
배경 --hm-sem-state-active(Light 2 #266651), 글자 --hm-sem-on-state-active(흰색) (hover해도 유지, focus 시 fill + Focus ring) |
Outside (data-outside) |
글자 --hm-sem-fg-secondary, opacity 0.6 |
Disabled (data-disabled) |
opacity 0.4(이 컴포넌트 값, 전역 Disabled 규칙으로 승격 안 함), cursor not-allowed, 선택 불가 |
Range start / middle / end (rdp-range_*) |
start·end는 Active 면(--hm-calendar-range-edge-* → --hm-sem-state-active, hover해도 유지, 안쪽 모서리 직각), middle은 --accent 45% 면 + 직각 |
| Nav 버튼 disabled | opacity 0.4 (startMonth/endMonth 경계) |
기반 라이브러리가 실제로 제공하는 상태만 쓴다. HM 전용 상태 속성은 없다.
5. API
react-day-picker DayPicker props 그대로. export: Calendar.
mode"single" | "multiple" | "range",selected,onSelect,required,defaultMonth,month/onMonthChange,disabled(Matcher),startMonth/endMonth,numberOfMonths,captionLayout,weekStartsOn,today,locale등.- Calendar가 바꾸는 기본값:
showOutsideDays=true,locale=ko, Chevron 아이콘을 lucide로 교체(components.Chevron). - 출력
data-slot="calendar". 날짜 셀은td.rdp-day(data-selected,data-today,data-outside,data-disabled,data-focused,aria-selected). variant,size는 만들지 않았다.
6. Dependency
react-day-picker^10.0.2 (packages/ui에 추가, shadcn Calendar의 기반). 날짜 계산용으로date-fns와@date-fns/tz가 이 패키지의 dependency로 따라 들어온다. HM이 직접 date 라이브러리를 추가하지 않았다.- locale은 같은 패키지의
react-day-picker/locale(ko)을 사용(추가 설치 없음). - CENTER 패키지에는 추가하지 않았다.
7. Token
Component Mapping 위치: packages/ui/src/theme/calendar.css (--hm-calendar-*).
padding, text, month-gap, caption-*, nav-size|radius|icon-size|hover-background, weekday-*, day-size|radius|font-size|line-height|hover-background, today-background|font-weight, selected-background|text, range-edge-background|text, range-middle-background|text, outside-color|opacity, disabled-opacity. Semantic/Theme palette를 참조한다. Selected·Range edge는 --hm-sem-state-active / --hm-sem-on-state-active(2026-10-06 Active Semantic)를 참조한다. 신규 Primitive 없음.
8. react-day-picker에서 유지한 항목
날짜 계산, 월 이동, 키보드 모델(방향키, Home/End, PageUp/PageDown), 선택 모드와 required, disabled/outside/hidden 날짜, 숫자·날짜 접근성 label(locale 기반), role="grid"/gridcell, aria-selected, roving tabindex.
9. Storybook
- 대표 Story
All("Calendar"): Single(오늘 10/2, 선택 10/15), Disabled dates(5일 이전·일요일), Range(10/8–10/14), Two months를 한 페이지에 둔다.today는 라이브러리 prop으로 2026-10-02에 고정(어느 날 열어도 같게 보이기 위함). - 2026-10-06 Visual QA: Two months 구역만
StateBoard의wide를 사용한다. Desktop에서 252px month 두 개 + 16px gap + Calendar padding 24px + frame border 2px가 들어가며 좁은 폭에서는 기본 wrap을 유지한다. Production Calendar API/CSS는 변경하지 않았다. - verification:
Single,Disabled Dates,Range. - meta에
component를 지정하지 않는다(props가 선택 모드 union이라 args 없는 Story가 타입을 통과하지 못함).
10. 사용자 확정 결정
- 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). 개별 수치에 대한 별도 결정은 없다.
11. 미확정 / 보류
- 수치(36px 셀, 12px padding, Today 면, Selected 면)는 현재 HM 규격이며 디자인 확정 시 재검토.
- 기본 locale을 한국어로 둔 것은 현재 HM 서비스 기준이며, 다국어는 소비자가
locale을 넘긴다. i18n 시스템은 만들지 않았다. - 주 시작 요일은 locale 기본(한국어는 일요일).
captionLayout="dropdown"(월/연도 select)의 시각은 스타일링하지 않았다(.rdp-dropdown*). 사용이 정해지면 추가.- 주 번호(
showWeekNumber), footer, 달력 애니메이션(animate)은 스타일링하지 않았다. - 모바일: 한 달은 276px로 390px 폭에 들어간다.
numberOfMonths > 1은 좁은 화면에서 줄바꿈으로 세로로 쌓이며 별도 responsive calendar는 만들지 않았다. - Range의 hover 미리보기, 최소/최대 길이 표현은 없음.
12. 관련 파일
packages/ui/src/components/ui/calendar.tsx
packages/ui/src/theme/calendar.css
packages/ui/package.json (dependency: react-day-picker)
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export)
src/components/calendar.stories.tsx
13. 검증 상태
-
2026-10-06 Visual QA: Two months는 1280/1024/768px 한 행, 390px wrap, 모든 month 폭 252px 및 페이지 가로 overflow 없음. Production CSS 변경 없음.
typecheck,build,build-storybook통과. -
npm run typecheck,npm run build: 통과(2026-10-02). -
실제 렌더/DOM(Browser pane): 한국어 caption "2026년 10월", 요일 "일월화수목금토",
role="grid", 날짜 폭 36px, Selectedrgb(18,51,40)·흰 글자, Todayrgb(244,241,234)·700, 이전/다음 달 버튼 label이 한국어("이전 달로 이동"/"다음 달로 이동"). -
pointer: 날짜 클릭으로 선택(
aria-selected=true), 다음/이전 달 버튼으로 월 이동(10월 → 11월, 10월 → 9월). -
keyboard: 포커스 후 ArrowRight 10 → 11, ArrowDown 11 → 18(다음 주), Enter와 Space로 선택, PageDown으로 다음 달(11월), Home으로 주의 첫 날. roving tabindex가 선택된 날짜에 있음.
-
disabled: 5일 이전과 일요일은
data-disabled, 버튼disabled, opacity 0.4, 클릭해도 선택 안 됨. 가능한 날짜는 선택됨. -
range: 시작/끝은 Active 면(안쪽 직각), 중간은 accent 45% 면(모서리 0)으로 구분됨(처음에는
data-selected규칙이 중간 면을 덮는 버그가 있어 specificity를 올려 수정). -
Responsive(390×844): Calendar 폭 276px(두 달은 줄바꿈되어 356px), 페이지 가로 overflow 없음.
-
미확인: 실제 pointer hover 면 시각, focus ring 시각,
multiple모드,captionLayout="dropdown", RTL, 타임존(@date-fns/tz) 동작.