Files
HMDesign/docs/components/toggle-group.md
T
shkim4 4c29894e56 refactor(storybook): simplify component and preview examples
Drop page / section descriptions, state notes and repeated captions from
the representative Stories of 44 components, the Patterns, Center
Graphic and OrgChart pages. Value readouts, spec lists and captions hide
only on the representative page; hidden verification Stories keep them.
Tabs Default API and the Filter Bar draft / applied counts move to the
hidden DefaultApi and DraftApplied Stories; Center Overview is hidden
with its direct URL kept. Story IDs are unchanged.

Align the component, pattern and OrgChart docs with the pages, point the
quality reference check at the DraftApplied Story, and make the P8
styles check expect no section header on the preview-only Expanded page.
2026-10-23 00:28:05 +09:00

114 lines
9.9 KiB
Markdown
Raw 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.
# Toggle Group
> 2026-10-10 추가 수정 요청 기준: 소비자 style과 내부 --hm-toggle-group-gap을 병합. spacing은 4px 단위이며 명시적 동일 CSS 변수 override는 기존 확장 계약으로 유지. 과거 검증 기록은 당시 결과이며 현재 검증은 [추가 수정 보고서](../guidelines/publisher-additional-review-2026-10-10.md)를 따릅니다.
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Toggle Group 고유의 API·Token·상태·결정만 기록한다. Toggle(`toggle.md`)과 역할 비교는 거기 §14.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | 구현 완료 (현재 HM 규격) |
| 기준일 | 2026-10-02 |
| 기반 | shadcn/ui Toggle Group (Radix `ToggleGroup`) |
| 대표 Story | `Components/Form/Toggle Group` → `All` (이름 "Toggle Group") |
| 보류 | 아래 §11 |
## 2. 적용 근거
- **참고 근거**: 외부 ui_viz Reference (ZIP 미포함)의 기존 테스트 규격에는 Toggle Group이 없다. 참고할 수치가 없었다.
- **현재 HM 규격**: 아래 §3~§4. 현재 HM Semantic/Primitive로 새로 정했다. Toggle과 같은 선택 언어(On = accent 면)를 공유하지만 Token은 `--hm-toggle-group-*`로 따로 두고 Toggle의 CSS에 의존하지 않는다.
- **구현 방식**: shadcn / Radix ToggleGroup 구조(Root + Item)와 `variant`·`size`·`spacing` API(context로 Item에 전달)를 유지한다. Item은 Toggle 컴포넌트가 아니라 독립 요소다.
- **Tabs와의 구분**: Tabs는 content 전환(navigation), Toggle Group은 상태 선택이다.
- **보류**: §11.
## 3. 기본 규격 (현재 HM 규격)
| 항목 | 값 |
|---|---|
| Item | 높이 `sm` 28 / `default` 34 / `lg` 42px, padding-x 8 / 12 / 16px, radius 4px, 14 / 20 / 500, icon + text gap 8px |
| Icon-only | 단일 React element + 직계 `svg` 하나인 경우 `data-icon-only`와 CSS로 padding 0, 높이와 같은 정사각형. SVG + text node는 size별 padding 8/12/16px 유지 |
| Off / On | Off는 투명 배경과 `--hm-sem-fg-primary`, On은 `--accent` 배경과 `--accent-foreground` |
| `outline` variant | 1px `--hm-sem-stroke-input-default` border |
| Connected (`spacing={0}`, 기본) | 안쪽 모서리 직각, 바깥 모서리 4px, outline border는 1px씩 겹침(`-1px`), On Item은 위로 올림(`z-index`) |
| Separated (`spacing > 0`) | 각 Item이 독립 radius, 간격 = `spacing × 4px` |
| 방향 | horizontal(기본), vertical(`orientation="vertical"`, 세로 연결 모서리) |
## 4. 상태 규격
| 상태 | 값 |
|---|---|
| Off / On | Radix `data-state`. single은 Item `role="radio"` + `aria-checked`(Root `role="radiogroup"`), multiple은 `aria-pressed`(Root `role="toolbar"`) |
| Hover (off) | 배경 `--accent` 45% |
| Selected + Hover (on) | 배경 `--accent` 100%, 글자·아이콘 `--accent-foreground` 유지 |
| Focus (`:focus-visible`) | shadcn ring, focus된 Item이 인접 Item 위로(`z-10`) |
| Disabled (그룹 또는 Item) | `opacity: 0.6`(이 컴포넌트 값, 전역 Disabled 규칙으로 승격 안 함), 클릭·focus 불가, 방향키 이동에서 제외 |
## 5. API
Radix ToggleGroup props 그대로. export: `ToggleGroup`, `ToggleGroupItem`.
- `ToggleGroup`: `type="single" | "multiple"`, `value`/`defaultValue`/`onValueChange`, `orientation`, `disabled`, `rovingFocus`, `loop`, `dir`. shadcn: `variant`, `size`, `spacing`(기본 0). 출력 `data-variant`, `data-size`, `data-spacing`, `data-orientation`(`orientation`을 줄 때만).
- `ToggleGroupItem`: `value`(필수), `disabled`. `variant`/`size`는 그룹에서 내려온다.
- single에서 선택된 Item을 다시 누르면 선택이 해제되어 `onValueChange("")`가 호출된다(Radix 동작). 항상 하나를 유지하려면 소비자가 빈 값을 무시한다(Story 참고).
- icon-only Item은 `aria-label`을 준다.
- 새 selection state 관리는 만들지 않았다.
## 6. Token
Component Mapping 위치: `packages/ui/src/theme/toggle-group.css` (`--hm-toggle-group-*`).
`height-sm|md|lg`, `padding-x-sm|md|lg`, `radius`, `gap`, `font-size|line-height|font-weight`, `off-background|off-text`, `hover-background`, `on-background|on-text`, `border-width|color`, `disabled-opacity`. Semantic과 Theme palette(`--hm-sem-stroke-input-default`)만 참조한다. `--hm-toggle-*`, `--hm-button-*`는 참조하지 않는다. 신규 Primitive와 Semantic 없음. `--hm-toggle-group-gap`은 `spacing` 값으로 인라인 style이 덮어쓴다.
## 7. shadcn / Radix에서 유지한 항목
Radix 구조·ARIA·roving focus(방향키, Home/End, disabled 건너뜀, `loop`)·Enter/Space·controlled/uncontrolled, shadcn context 방식의 `variant`/`size`/`spacing`, focus ring class, `data-slot`, `cn(..., className)`.
## 8. HM에서 변경한 항목
- shadcn의 `data-[spacing=0]:*` 계열 class와 Toggle 색 class를 제거하고 theme CSS로 이전. 연결형 모서리·border 겹침을 CSS 선택자로 처리.
- Radix가 `orientation`을 주지 않으면 `data-orientation`을 출력하지 않으므로 가로 연결형은 `:not([data-orientation="vertical"])`로 선택한다.
- 기본 모양을 `@layer components`에 두어 소비자의 `className`이 이긴다.
## 9. Storybook
- 대표 Story `All`("Toggle Group"): Single(local state), Multiple(icon-only, local state), Disabled group / item, Vertical(icon-only), `spacing={2}`, Many items(`spacing={1}` + `flex-wrap`)을 한 페이지에 둔다. 대표 화면은 예시 제목·선택값 표시(`value = …`)를 생략하고 구역 제목으로 구분한다. 값 표시는 verification에 남긴다.
- verification: `Single`, `Multiple`, `Vertical`.
- meta에 `component`를 지정하지 않는다(props가 `single | multiple` union이라 args 없는 Story가 타입을 통과하지 못함).
## 10. 사용자 확정 결정
- 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). Toggle Group은 Button과 독립 Component Token을 쓰며 Tabs와 같은 모양으로 만들지 않는다(작업 지시).
- 2026-10-06 Selected 원복: Git baseline `d101530`의 On 매핑 `--accent` / `--accent-foreground`를 복원했다. Selected + Hover도 Accent 100%와 동일 전경을 유지하고 아이콘은 `currentColor`를 따른다. Border는 기존 default 투명 / outline `--hm-sem-stroke-input-default`를 유지한다. Active Semantic은 참조하지 않고 Focus는 `--ring` → `--hm-sem-stroke-input-focus`(Light 2), `ring-ring/50`을 유지한다. Hover·Disabled·Icon-only / Icon+Text padding·Theme JSON·Runtime 계약 변경 없음.
## 11. 미확정 / 보류
- 수치(28/34/42px, 연결형 기본, On 면 accent, Disabled opacity 0.6)는 현재 HM 규격이며 디자인 확정 시 재검토.
- **줄바꿈**: 연결형(`spacing={0}`) 그룹이 `flex-wrap`으로 여러 줄이 되면 두 번째 줄 첫 Item의 border 겹침(`-1px`)과 모서리가 어색하다. 항목이 많거나 폭이 좁으면 `spacing > 0`을 쓰고 `className="flex-wrap"`을 소비자가 선택한다. 자동 responsive 배치는 만들지 않았다.
- vertical 그룹의 폭: Item 폭이 서로 다르면 가장 넓은 Item에 맞춰 늘어난다(`items-stretch`).
- 선택 해제 가능 정책(single에서 재클릭)은 Radix 기본.
- 스크린리더 안내 문구(그룹 이름)는 소비자의 `aria-label`.
## 12. 관련 파일
```
packages/ui/src/components/ui/toggle-group.tsx
packages/ui/src/theme/toggle-group.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export)
src/components/toggle-group.stories.tsx
```
## 13. 검증 상태
- 2026-10-06 Selected 원복 검증: Storybook `All` / `ContentNodes`에서 Default·실제 Hover·Selected·Selected + Hover·키보드 Focus·Disabled·Icon-only·Icon+Text 확인. Selected 배경 `rgb(189, 223, 212)` / 전경·아이콘 `rgb(18, 51, 40)`, outline border `rgb(222, 219, 210)`, Focus는 Light 2 `#266651` 50%의 3px ring. Runtime API의 Active override는 Selected에 영향 없음, Accent/on Accent override는 면·전경만 변경, Focus override는 ring만 변경. 1280/390px에서 sm/default/lg Icon-only 28/34/42px square·padding 0, Icon+Text/Text-only padding-x 8/12/16px 및 Array/Fragment 조합 유지. `typecheck`, `build`, `build-storybook`, `git diff --check` 통과(기존 CSS 예시·Storybook chunk 경고 유지).
- 2026-10-06 Visual QA: Icon+Text item도 기존 `:has(> svg:only-child)`에 실제 매칭됨을 확인하고 Toggle과 함께 수정. 1280/1024/768/390px, sm/default/lg에서 Icon-only PL/PR 0/0px·28/34/42px square, Icon+Text와 Text-only PL/PR 각각 8/12/16px, pressed 상태 정상. Array/Fragment 조합 확인. `typecheck`, `build`, `build-storybook` 통과.
- `npm run typecheck`, `npm run build`: 통과(2026-10-02).
- 실제 DOM(Browser pane): single은 `radiogroup`/`radio`(`aria-checked`), multiple은 `toolbar`와 `aria-pressed`.
- single keyboard: 포커스 후 ArrowRight로 다음 Item, Enter로 선택(`value = center`), End로 마지막, Space로 선택(`value = right`), Home으로 처음. multiple: Enter/Space로 각각 토글되어 `[bold, italic, underline]`. vertical: ArrowDown으로 이동, ArrowRight는 이동하지 않음. disabled: ArrowRight가 비활성 Item을 건너뛰어 A → C, 비활성 그룹은 클릭해도 상태 불변. (ArrowLeft는 첫 입력이 한 번 무시되는 경우가 있었고 두 번째 입력에서 A로 이동. 입력 도구의 타이밍으로 보이며 ArrowRight 건너뜀은 확인됨.)
- computed style: 연결형에서 첫/끝 Item만 바깥 모서리 4px, 가운데 0, outline Item margin `-1px`, vertical은 위/아래 모서리, `spacing={2}`는 각 Item 4px radius.
- Responsive(390×844): 모든 그룹이 부모 폭 안에서 페이지 가로 overflow 없음, `spacing={1}` + `flex-wrap` 그룹은 358px 안에서 두 줄로 줄바꿈.
- 미확인: 실제 pointer hover 면 시각, focus ring 시각, `loop={false}`·`rovingFocus={false}`, RTL.