Files
HMDesign/docs/components/slider.md

117 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 시각).