Files
HMDesign/docs/components/calendar.md
T

8.7 KiB
Raw Blame History

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, Selected rgb(18,51,40)·흰 글자, Today rgb(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) 동작.