# Slider 현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Slider 고유의 API·Token·상태·결정만 기록한다. ## 1. 상태 | 항목 | 내용 | |---|---| | 구현 상태 | 구현 완료 (현재 HM 규격) | | 기준일 | 2026-10-06 | | 기반 | shadcn/ui Slider (Radix `Slider`) | | 대표 Story | `Components/Form/Slider` → `All` (이름 "Slider") | | 보류 | 아래 §11 | ## 2. 적용 근거 - **참고 근거**: 외부 ui_viz Reference (ZIP 미포함)의 기존 테스트 규격에는 Slider가 없다. 참고할 수치가 없었다. - **현재 HM 규격**: 아래 §3~§4. 현재 HM Semantic/Primitive로 새로 정했다. - **구현 방식**: shadcn / Radix Slider 구조(Root, Track, Range, Thumb)를 유지하고 theme CSS로 연결. Slider는 값 입력 컨트롤이며 Toggle/Button 계열과 역할이 다르다. - **보류**: §11. ## 3. 기본 규격 (현재 HM 규격) | 항목 | 값 | |---|---| | Track | 두께 6px, radius 9999px, 배경 `--hm-sem-palette-warm-neutral-light` | | Range | `--hm-sem-state-active` | | Thumb | 18px 원, 배경 `--hm-sem-bg-surface`, 2px `--hm-sem-state-active` border | | Thumb halo | Hover 4px / Drag(`:active`) 6px Primary 20% 후광. 키보드 Focus는 같은 크기의 Focus Semantic 20% 후광, 커서 grab/grabbing 유지 | | 폭/높이 | Root에 `className`. 기본은 부모 폭 100%, vertical은 높이 100%·최소 176px(shadcn 기본) | ## 4. 상태 규격 | 상태 | 값 | |---|---| | Default | 후광 없음, 커서 grab | | Hover | 4px `--hm-sem-action-primary` 20% 후광 | | Focus (`:focus-visible`) | 4px `--hm-sem-stroke-input-focus` 20% 후광 | | Drag (`:active`) | 6px Primary 20% 후광, 커서 grabbing | | Focus + Hover | Focus 색 우선, 4px Focus 20% 후광 | | Focus + Drag | Focus 색 우선, Drag의 6px 크기와 grabbing 커서 유지 | | Disabled (`data-disabled`) | Root `opacity: 0.6`(이 컴포넌트 값, 전역 Disabled 규칙으로 승격 안 함), 후광 없음, 커서 not-allowed, thumb이 focus를 받지 않음 | | Orientation | `data-orientation` horizontal / vertical | 우선순위: Disabled > Focus 색 > Hover/Drag interaction 색. Drag는 halo 크기만 6px로 확대한다. Radix의 pointer focus 정책은 유지한다(pointer로 thumb을 잡으면 `focusVisible: false`로 focus하므로 일반 pointer drag는 Primary halo). Focus + Drag 규칙은 실제로 `:focus-visible`과 `:active`가 겹칠 때 적용하며, interaction 규칙이 Focus 표시를 덮지 않는다. ## 5. API Radix Slider props 그대로. export: `Slider`. - `value`/`defaultValue`/`onValueChange`/`onValueCommit`, `min`/`max`/`step`, `minStepsBetweenThumbs`, `orientation`, `dir`, `inverted`, `disabled`, `name`. - 값이 배열이면 항목 수만큼 thumb을 렌더한다(두 개면 range). Range용 별도 컴포넌트는 없다. - `value`와 `defaultValue`를 모두 생략하면 초기값은 `[min]`이고 thumb은 하나다(`min` 기본값 0). 숨겨진 추가 thumb이나 빈 FormData 항목을 만들지 않는다. - Uncontrolled의 `defaultValue`는 마운트 시 초기값이다. 이후 `defaultValue` 배열이나 항목 수를 바꿔도 내부 값과 thumb 수는 초기 배열을 유지한다. 초기화가 필요하면 새 `key`로 재마운트하고, 동적 값 변경에는 Controlled `value`를 사용한다. Controlled/Uncontrolled 모드는 컴포넌트 수명 동안 유지한다. - Form 안에서 `name`을 지정하면 Enabled Slider는 현재 값을 제출한다(단일값은 `name`, 범위값은 `name[]`으로 여러 항목). `form="id"`로 외부 Form을 연결할 수도 있다. - Disabled Slider는 FormData와 native Form Submit에서 제외한다. 설치된 Radix Slider 1.4.7의 내부 input에 `disabled`가 전달되지 않아 HM은 disabled일 때 Radix에 넘기는 `name`을 생략한다. 다시 Enabled로 바꾸면 기존 `name`으로 현재 값 제출을 재개한다. Controlled/Uncontrolled에 동일하게 적용하며 값·callback·키보드·ARIA 계약은 유지한다. - **`aria-label`**: `role="slider"`는 Root가 아니라 Thumb이므로 Slider가 `aria-label`을 모든 thumb에 전달한다(thumb이 둘 이상이면 `"label 1/2"`, `"label 2/2"`). `aria-label`이 없으면 Radix 기본 thumb 이름(Minimum/Maximum)이 남는다. - `variant`, `status`, `appearance` 같은 prop은 만들지 않았다. ## 6. Token Component Mapping 위치: `packages/ui/src/theme/slider.css` (`--hm-slider-*`). `track-size`, `track-background`, `range-background`, `thumb-size`, `thumb-background`, `thumb-border-width|color`, `thumb-halo-color|size|size-active`, `thumb-focus-halo-color`, `disabled-opacity`. Interaction halo는 Primary, Focus halo는 기존 Focus Semantic을 참조한다. 다른 컴포넌트 Token을 쓰지 않는다. 신규 Primitive 없음. Track/Range/Thumb은 내부 부품이라 theme CSS(레이어 밖)로 정하고, Slider Root의 폭·높이는 `className` 영역이다. ## 7. shadcn / Radix에서 유지한 항목 Radix 구조·동작(pointer drag, track 클릭 이동, 키보드, thumb 정렬/교차 방지), ARIA(`role=slider`, `aria-valuemin|max|now`, `aria-orientation`), 배열 value에서 thumb 수를 계산하는 shadcn 로직, `touch-none select-none`, `data-slot`, `cn(..., className)`. ## 8. HM에서 변경한 항목 - shadcn `bg-muted`, `h-1`, `bg-primary`, thumb `size-3 border-ring ring-*` class를 제거하고 Token으로 이전. thumb을 12px에서 18px로 키웠다. - `aria-label`을 thumb으로 전달(shadcn은 Root에만 붙어 thumb에 이름이 없었다). ## 9. Storybook - 대표 Story `All`("Slider"): Single(값 label), Range(두 thumb, `minStepsBetweenThumbs` 5), Step(25), Disabled, Vertical을 한 페이지에 둔다. 값 label은 Story 조합(local state)이다. - verification: `Single`, `Range`, `Disabled`, `Focus`(실제 thumb focus). 기본 Sidebar에서 숨긴다. ## 10. 사용자 확정 결정 - 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). 개별 수치에 대한 별도 결정은 없다. ## 11. 미확정 / 보류 - 수치(6px track, 18px thumb, halo 4/6px)는 현재 HM 규격이며 디자인 확정 시 재검토. - 값 tooltip/label 표시, tick mark, 범위 밖 구간 표현은 정의하지 않았다. - 상태색 range(예: 경고)는 HM 상태색 확정 후. - hit area: thumb 18px이 터치 타깃(44px 권장)보다 작다. 터치 기기 정책은 미정. - 대표 Story의 form 예시는 만들지 않았다. 기본값 및 Enabled/Disabled 제출 계약은 §5를 따른다. ## 12. 관련 파일 ``` packages/ui/src/components/ui/slider.tsx packages/ui/src/theme/slider.css packages/ui/src/theme.css (import) packages/ui/src/index.ts (export) src/components/slider.stories.tsx ``` ## 13. 검증 상태 - 2026-10-08 기능 개선: 프로젝트 외부 UI 복사본에서 타입 검사와 패키지 빌드 통과. 실제 프로젝트 Slider 소스와 복사본의 SHA-256이 일치하며, 브라우저는 해당 패키지 빌드 결과를 사용했다. - 브라우저 Form 검증: 기본값 생략/`min=10`은 thumb 하나·정상 `aria-valuenow`·빈 제출 항목 없음. 명시적 단일/범위, Controlled 값·배열 길이 변경, Uncontrolled `defaultValue` 배열 길이 변경 시 초기 thumb 수 유지 확인. Controlled/Uncontrolled 단일·범위의 Disabled → Enabled → Disabled 전환에서 FormData와 실제 native POST 제출 결과 정상. 키보드·callback·ARIA와 외부 `form` 연결의 FormData 확인. - 디자인 보존: Slider CSS·Theme JSON·대표 Story 변경 없음. 수정 전후 computed style에서 thumb 18px/2px border, track 6px, Disabled opacity 0.6, Hover/Focus 4px 후광 일치. 검증 예제와 POST 수집 서버는 외부 임시 환경에서만 실행했다. - 2026-10-06 Focus 정합: `typecheck`, `build`, `build-storybook`, `git diff --check` 통과. 기존 문서 예시 CSS(`bg-[var(...)]`)와 Storybook chunk 크기 경고 유지. - Theme Designer에서 Primary `#6837a8`, Active `#007f73`, Focus `#c75b00`, Accent `#ffd36d`를 분리 적용한 뒤 **일반 verification Story**의 computed style로 확인: Range·Thumb border `rgb(0,127,115)`, Hover/Drag는 Primary 20% / 4·6px, Focus/Focus + Hover는 Focus 20% / 4px, Focus + Drag는 Focus 20% / 6px, Default/Disabled는 shadow 없음. Hover/Drag 중첩은 기존 Storybook pseudo-state mirror로 고정하고 Focus는 실제 DOM focus다. - 각 역할을 단독 override해도 Primary는 interaction halo, Active는 Range·Thumb border, Focus는 Focus halo만 변경하며 Accent는 Slider에 영향 없음. 실제 ArrowRight로 값·ARIA·label 40 → 41 확인. Focus Semantic 이름·값·Runtime/JSON 계약 변경 없음. - `npm run typecheck`, `npm run build`: 통과(2026-10-02). - 실제 DOM(Browser pane): thumb `role="slider"`, `aria-valuemin=0`, `aria-valuemax=100`, `aria-valuenow`가 실제 값과 일치, vertical은 `aria-orientation=vertical`, disabled thumb은 focus 불가. `aria-label`이 단일 thumb에 `볼륨`, range에 `가격 범위 1/2`·`2/2`로 반영됨. - keyboard: ArrowRight 40 → 41, ArrowLeft → 40, End 100, Home 0, step 25에서 ArrowRight 50 → 75, vertical ArrowUp 30 → 31. 값 label과 ARIA가 함께 변함. - pointer: thumb을 오른쪽으로 drag해 40 → 79(range 폭 382/484), track 클릭으로 23, 왼쪽 경계 밖으로 drag해 0에 고정. Range에서 첫 thumb을 두 번째 thumb 너머로 drag해도 값이 정렬 상태(75, 100)로 유지됨. - computed style: track 6px `rgb(224,218,206)`, range `rgb(18,51,40)`, thumb 18px·2px border·흰 면, disabled Root opacity 0.6. - Responsive(390×844): Slider 폭 358px(좌우 16px), thumb이 잘리지 않고 페이지 가로 overflow 없음. - 미확인: PageUp/PageDown, 터치 drag, `inverted`/RTL, thumb halo의 시각(hover/active 상태 pointer 시각).