Files
HMDesign/docs/components/toggle-group.md
T

9.4 KiB
Raw Blame History

Toggle Group

현재 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)을 한 페이지에 둔다.
  • 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.