117 lines
9.8 KiB
Markdown
117 lines
9.8 KiB
Markdown
# 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 시각).
|