Files
HMDesign/docs/components/checkbox.md
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

140 lines
9.5 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.
# Checkbox
> 2026-10-10 추가 수정 요청 기준: `asChild`는 단일 React DOM 호환 요소를 받으며 소비자 자식·ID·이벤트·ref를 보존하고 내부 Indicator를 추가한다. 커스텀 요소는 props/ref를 최종 DOM으로 전달해야 한다. 기존 기본 button·외부 form 유지. 과거 검증 기록은 당시 결과이며 현재 검증은 [추가 수정 보고서](../guidelines/publisher-additional-review-2026-10-10.md)를 따릅니다.
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Checkbox 고유의 API·Token·상태·결정만 기록한다.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | 구현 완료 (현재 HM 규격) |
| 기준일 | 2026-10-02 |
| 기반 | shadcn/ui Checkbox (Radix `Checkbox`) |
| 대표 Story | `Components/Form/Checkbox` → `All` (이름 "Checkbox") |
| 보류 | 아래 §11 |
## 2. 적용 근거
- **참고 근거**: ui-viz 기존 테스트 규격에는 Checkbox 문서가 없다(border·radius·indicator는 TBD, mark는 native UA 소유, HOLD). 구현 값으로 쓸 수 있는 확정 규격이 없어 참고만 했다.
- **현재 HM 규격**: 아래 §3~§4. 현재 HM Semantic 체계(surface, stroke, action-primary, state-active, on-state-active)로 새로 정했다.
- **구현 방식**: shadcn / Radix Checkbox 구조(Root/Indicator, `data-state`, indeterminate, 키보드, Label 연결, 폼 동작)를 유지하고 theme CSS로 HM 스타일을 연결.
- **보류**: §11.
## 3. 기본 규격 (현재 HM 규격)
| 항목 | 값 | 비고 |
|---|---|---|
| Box | 18 × 18px | 현재 HM |
| Border | 1px `--hm-sem-stroke-default` | Semantic |
| Radius | `--hm-radius-control-md` (4px) | 공통 control radius |
| Face | `--hm-sem-bg-surface` | Semantic |
| Indicator | shadcn `CheckIcon` / `MinusIcon`, 14px, `--hm-sem-on-state-active` | shadcn 아이콘 유지 |
| Hit area | 18px box + 연결된 `Label` 클릭 | 별도 확장 없음 |
shadcn(lucide) 아이콘을 유지했다. 별도 HM glyph는 만들지 않았다.
## 4. 상태 규격
| 상태 | 값 |
|---|---|
| Unchecked | 위 기본 규격 |
| Checked (`data-state="checked"`) | border·face `--hm-sem-state-active`, 체크 아이콘 |
| Indeterminate (`data-state="indeterminate"`) | Checked와 같은 면, 마이너스 아이콘 |
| Hover (미체크) | border `--hm-sem-action-primary` |
| Hover (체크·indeterminate) | Black 20% overlay (`--hm-color-action-overlay-hover`) |
| Focus (`:focus-visible`) | shadcn ring (`ring-3 ring-ring/50`) 유지 |
| Error (`aria-invalid="true"`) | border `--hm-sem-action-destructive` |
| Disabled | `opacity: 0.6` (이 컴포넌트 값, 전역 Disabled 규칙으로 승격 안 함), `cursor: not-allowed` |
## 5. API
Radix Checkbox props 그대로 (`checked` / `defaultChecked` / `onCheckedChange`, `"indeterminate"`, `disabled`, `required`, `name`, `value`, `form`). 추가 prop 없음. 출력 속성: `data-slot="checkbox"`, Radix `data-state`·`data-disabled`.
- Label과의 연결은 `<Label htmlFor>` 또는 감싸기. Label·Helper·Error 조합은 소비 조합(`field.md`).
### Form 연결 및 Reset
- Form 내부에서는 기존 native Form 연결을 사용한다. 외부에서는 `form="target"`과 `<form id="target">`으로 연결한다.
- 기본 버튼 렌더링에 `form`을 지정하면 HM은 Radix의 공개 `asChild` 경로로 같은 Checkbox 버튼에도 `form`을 전달한다. 추가 DOM wrapper, 별도 상태, Reset listener 또는 숨김 input 강제 조작은 없다. 호출자가 명시한 `asChild` 경로는 기존대로 유지한다.
- Uncontrolled는 `defaultChecked`로 초기 상태를 지정한다. Native Reset 버튼과 `form.reset()`은 Radix가 보관한 최초 상태를 복원한다. 이후 `defaultChecked` 변경을 현재 선택값 변경으로 사용하지 않는다.
- Controlled는 부모의 `checked`가 상태의 기준이다. 부모 `onReset`에서 초기값을 복원하고 `onCheckedChange`로 변경 요청을 반영한다. 외부 Form에도 내부 Form과 같은 Radix Reset callback 계약이 적용된다.
- Checked는 `name` / `value`로 제출된다(기본 `value="on"`). Unchecked와 indeterminate는 제출되지 않는다. Indeterminate는 `aria-checked="mixed"`이며 native input의 `checked`는 false다. Disabled는 버튼과 숨김 input 모두 비활성화되어 제출되지 않는다.
```tsx
const initialChecked = true
const [checked, setChecked] = React.useState(initialChecked)
return (
<>
<form id="target" onReset={() => setChecked(initialChecked)}>
<button type="reset">초기화</button>
</form>
<Checkbox
form="target"
name="agreement"
checked={checked}
onCheckedChange={setChecked}
/>
</>
)
```
Uncontrolled 예제는 `checked` / `onCheckedChange` 대신 `defaultChecked={true}`를 사용하며 부모의 Reset 상태 관리는 필요하지 않다.
## 6. Token
Component Mapping 위치: `packages/ui/src/theme/checkbox.css` (`--hm-checkbox-*`).
`size`, `radius`, `border-width`, `icon-size`, `background`, `border-color`, `hover-border-color`, `checked-background`, `checked-border-color`, `checked-hover-overlay`, `indicator-color`, `error-border-color`. Semantic(`--hm-sem-*`)을 참조하고 Input 등 다른 컴포넌트 Token을 쓰지 않는다. Primitive 신규 추가 없음(`--hm-sem-action-destructive`는 기존 Primitive를 직접 참조).
## 7. shadcn / Radix에서 유지한 항목
Radix 구조·ARIA(`role=checkbox`, `aria-checked`)·키보드, `data-slot`, `cn(..., className)`, `transition-colors`, `outline-none` + focus ring, disabled 커서, lucide `CheckIcon`.
## 8. HM에서 변경한 항목
- shadcn `size-4 rounded-[4px] border shadow-xs`, `dark:` 및 `data-[state=checked]:bg-primary` 계열 class 제거 → theme CSS.
- `MinusIcon` 추가(shadcn 기본은 indeterminate 아이콘이 없다). 두 아이콘은 `data-state`에 따라 CSS로 전환.
- Disabled opacity 50 → 60.
## 9. Storybook
- 대표 Story `All`("Checkbox"): `unchecked`, `checked`, `indeterminate`, `hover`, `focus`, `error`, `disabled`, `long`. Focus는 `play`로 실제 focus. `indeterminate`는 controlled 예시를 겸해 클릭하면 checked가 된다(Radix, 2026-10-22 별도 `interactive` 구역 통합). `hover`는 Unchecked·Checked를 Storybook 전용 `data-sb-hover`(`.storybook/pseudo-states.ts`)로 고정해 보여 준다. Checkbox API가 아니며 소비 코드에 붙이지 않는다.
- verification: `Unchecked`, `Checked`, `Indeterminate`, `Disabled`, `Error`, `Playground`.
## 10. 사용자 확정 결정
- 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). 개별 수치에 대한 별도 결정은 없다.
## 11. 미확정 / 보류
- Checkbox Group 간격, group Label·Error 슬롯, option label 전경 role: 미결정.
- 수치(18px, 14px 아이콘 등)는 현재 HM 규격이며 디자인 확정 시 재검토.
- Checkbox Group, Error 문구 슬롯: 컴포넌트 없음(소비 조합).
- Hit area 확장(24px 타깃 등) 미적용.
- Disabled opacity `0.6`: 이 컴포넌트 값이며 전역 Disabled 규칙으로 승격하지 않는다.
## 12. 관련 파일
```
packages/ui/src/components/ui/checkbox.tsx
packages/ui/src/theme/checkbox.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export Checkbox)
src/components/checkbox.stories.tsx
```
## 13. 검증 상태
- `npm run typecheck`, `npm run build`: 통과(2026-10-02).
- Storybook computed style: 18×18, border 1px `rgb(154,146,132)`, radius 4px; Checked/Indeterminate bg·border `rgb(18,51,40)`; Error border `rgb(211,47,47)`; Disabled opacity 0.6; 체크/마이너스 아이콘이 상태에 따라 하나만 표시.
- 실제 interaction: controlled indeterminate → click → checked → click → unchecked, Label 클릭으로 토글 확인.
- 2026-10-08, React 19.3 / Radix Checkbox 1.3.11: 프로젝트 외부 격리 환경의 수정된 HM 패키지로 실제 브라우저 검증. 수정 전 외부 Form의 true·false·indeterminate Reset 불일치를 재현했고, 수정 후 모두 PASS.
- Form 내부·외부에서 Space로 상태 변경 후 Native Reset 버튼 및 `form.reset()`을 실행: `defaultChecked` true·false·indeterminate와 Controlled 부모 `onReset`의 상태·ARIA·숨김 input·FormData 복원 확인. Indeterminate 복원 시 숨김 input은 `checked=false`, `indeterminate=true`, 제출값 없음.
- 기존 Radix 동작도 구분해 확인: 최초 indeterminate 렌더에서는 숨김 input의 `indeterminate` 속성이 false다(내부·외부 및 수정 전·후 동일). UI/ARIA는 mixed, input의 `checked=false`, FormData 제외는 정상이며 이번 외부 Form Reset 수정에서 이 초기화 동작은 변경하지 않는다.
- Native Form HTTP POST 8건: 내부·외부·Controlled의 Checked 값은 `choice=accepted`, Unchecked·indeterminate·Disabled 값은 제외. `required` native 유효성 및 ARIA, Disabled Tab 제외, BUTTON ref 유지 확인.
- 수정 전·후 같은 Theme에서 Checked·Unchecked·Indeterminate·Disabled computed style과 아이콘 일치: 18×18px, border 1px, radius 4px, icon 14px, Disabled opacity 0.6. 키보드 focus-visible 3px ring도 일치. CSS·Theme·대표 Story 변경 없음.
- UI 전체 타입 검사 및 공식 UI 패키지 빌드를 격리 경로에서 통과. 테스트에 사용한 Checkbox 소스와 실제 수정본의 SHA-256 일치. 공용 dist는 이 검증에서 빌드하지 않음.
- 미확인: 실제 pointer hover, 사이드바 화면, 390px 폭. Hover 관련 CSS와 class는 변경하지 않음.