# Toggle 현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Toggle 고유의 API·Token·상태·결정만 기록한다. ## 1. 상태 | 항목 | 내용 | |---|---| | 구현 상태 | 구현 완료 (현재 HM 규격) | | 기준일 | 2026-10-02 | | 기반 | shadcn/ui Toggle (Radix `Toggle`) | | 대표 Story | `Components/Form/Toggle` → `All` (이름 "Toggle") | | 보류 | 아래 §11 | ## 2. 적용 근거 - **참고 근거**: 외부 ui_viz Reference (ZIP 미포함)의 기존 테스트 규격에는 Toggle이 없다(인벤토리에 Switch 계열 기능 이름으로만 언급). 참고할 수치가 없었다. - **현재 HM 규격**: 아래 §3~§4. 현재 HM Semantic/Primitive로 새로 정했다. - **구현 방식**: shadcn / Radix Toggle 구조와 `variant`(default | outline), `size`(default | sm | lg) API를 유지하고, 모양은 HM 독립 Token(`--hm-toggle-*`)으로 정한다. - **Button과의 관계**: 이 문서 §14. - **보류**: §11. ## 3. 기본 규격 (현재 HM 규격) | 항목 | 값 | |---|---| | Size | `sm` 28px, `default` 34px, `lg` 42px (높이). padding-x 8 / 12 / 16px | | Icon-only | 단일 React element + 직계 `svg` 하나인 경우 `data-icon-only`와 CSS로 padding 0. SVG + text node는 size별 padding 8/12/16px 유지 | | Radius | `--hm-radius-control-md` (4px) | | Typography | 14 / 20 / 500, icon + text는 gap 8px | | Off | 배경 투명, 글자 `--hm-sem-fg-primary` | | On | 배경 `--accent`, 글자 `--accent-foreground` (full Accent; Select 선택 항목의 Accent 45% 면과는 별도 정책) | | `outline` variant | 1px `--hm-sem-stroke-input-default` border(`default`는 border 없음) | | 커서 | pointer | ## 4. 상태 규격 | 상태 | 값 | |---|---| | Off / On | Radix `data-state="off" | "on"`, `aria-pressed` | | Hover (off) | 배경 `--accent` 45% (on 상태는 그대로 On 면) | | Selected + Hover (on) | 배경 `--accent` 100%, 글자·아이콘 `--accent-foreground` 유지. Hover 45% 면으로 내려가지 않음 | | Focus (`:focus-visible`) | shadcn ring (`ring-3 ring-ring/50`) 유지 | | Disabled | `opacity: 0.6`(이 컴포넌트 값, 전역 Disabled 규칙으로 승격 안 함), `cursor: not-allowed`, 클릭·hover 무반응 | ## 5. API Radix Toggle props 그대로. export: `Toggle`, `toggleVariants`. - `pressed`/`defaultPressed`/`onPressedChange`, `disabled`, `asChild`. - `variant` `"default" | "outline"`, `size` `"default" | "sm" | "lg"`(shadcn 호환). 출력 `data-variant`, `data-size`, `data-slot="toggle"`. - icon-only Toggle은 소비자가 `aria-label`을 준다(Story 참고). - 새 variant는 만들지 않았다(`default`, `outline` 두 개만 유지). ## 6. Token Component Mapping 위치: `packages/ui/src/theme/toggle.css` (`--hm-toggle-*`). `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`, `outline-border-width|color`, `disabled-opacity`. Semantic과 Theme palette(`--hm-sem-stroke-input-default`)만 참조한다. `--hm-button-*`와 Button의 role 체계는 참조하지 않는다. 신규 Primitive와 Semantic 없음. ## 7. shadcn / Radix에서 유지한 항목 Radix 구조·`aria-pressed`·`data-state`·Enter/Space·controlled/uncontrolled·disabled·`asChild`, shadcn `variant`/`size` API와 `toggleVariants`(cva), focus ring class, svg 정렬 class, `data-slot`, `cn(..., className)`. ## 8. HM에서 변경한 항목 - shadcn `toggleVariants`의 색·크기 class(`h-8 min-w-8 px-2.5`, `hover:bg-muted`, `data-[state=on]:bg-muted`, outline `border-input shadow-xs`)를 제거하고 theme CSS로 이전. On 면은 `muted`가 아닌 accent. - 기본 모양을 `@layer components`에 두어 소비자의 `className`(폭, radius, 레이아웃 utility)이 이긴다. 다른 theme CSS(레이어 밖)와 달리 Toggle은 Story에서 `className="w-40 rounded-full"`이 적용되는 것을 확인했다. ## 9. Storybook - 대표 Story `All`("Toggle"): Off/On(default·outline), Icon-only와 icon + text(`BoldIcon`, `ItalicIcon`, `StarIcon`), Sizes, Disabled(off/on), Controlled(`pressed` 상태 표시), className 확장을 한 페이지에 둔다. - verification: `States`, `Icon Only`, `Disabled`. ## 10. 사용자 확정 결정 - 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). Toggle은 Button과 독립 Component Token을 쓴다(작업 지시). - 2026-10-06 Selected 원복: Git baseline `d101530`의 `--accent` / `--accent-foreground` 매핑을 복원했다. 아이콘은 `currentColor`로 전경을 따른다. Border는 baseline대로 default 투명 / outline `--hm-sem-stroke-input-default`이며 Selected 전용 border는 없다. 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, padding, On 면 accent, Disabled opacity 0.6)는 현재 HM 규격이며 디자인 확정 시 재검토. - On 상태 hover는 On 면 그대로이다(별도 hover 면을 정하지 않음). - On 상태를 Primary(진한 녹색 면)로 강하게 표시하는 variant는 만들지 않았다. - 아이콘 크기는 shadcn 기본(16px) 유지. ## 12. 관련 파일 ``` packages/ui/src/components/ui/toggle.tsx packages/ui/src/theme/toggle.css packages/ui/src/theme.css (import) packages/ui/src/index.ts (export) src/components/toggle.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)`, 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: 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 icon과 Fragment/element label 조합도 확인. `typecheck`, `build`, `build-storybook` 통과. - `npm run typecheck`, `npm run build`: 통과(2026-10-02). - 실제 interaction(Browser pane): 클릭으로 off → on → off(`aria-pressed`, `data-state` 일치), 키보드 focus 후 Enter로 on, Space로 off, disabled는 클릭과 focus 불가, controlled는 클릭으로 `pressed = true`와 `data-state="on"` 동기화. - computed style: default 34px / On 배경 `rgb(189,223,212)`·글자 `rgb(18,51,40)`, outline border `rgb(222,219,210)`, `sm` 28px, `lg` 42px, icon-only 34×34, disabled opacity 0.6, `className="w-40 rounded-full"` 적용(폭 160px, radius 최대). - 미확인: 실제 pointer hover 면 시각, focus ring 시각, 390px(단일 버튼이라 폭 문제 없음). ## 14. 역할 구분 (Button · Checkbox · Radio Group · Switch와의 차이) | 컴포넌트 | 역할 | |---|---| | Button | **Action 실행**(일회성). `variant`가 역할(primary/secondary/destructive…), `appearance`가 treatment | | Toggle | **pressed 상태 유지**(툴바식 on/off). `aria-pressed` | | Toggle Group | **선택 그룹**(단일/다중 선택, roving focus) | | Checkbox | 폼의 boolean / 다중 선택 | | Radio Group | 폼의 배타적 선택 | | Switch | 설정 on/off(즉시 적용되는 설정 값) | Toggle은 Button과 모양이 비슷해 보여도 Button의 action 중심 variant 체계(role/appearance)와 `--hm-button-*` Token을 상속하지 않는다. 역할이 다르고, Button 규격이 바뀔 때 Toggle이 함께 바뀌면 안 되기 때문이다. 공통 의미(선택 면의 accent, control radius)만 Semantic으로 공유한다.