Files
HMDesign/docs/components/label.md

122 lines
7.3 KiB
Markdown

# Label
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Label 고유의 API·Token·상태·결정만 기록한다.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | HM 스타일 적용 완료 (typography·color만, `hm-label` class 없음) |
| 기준일 | 2026-10-02 |
| 기반 | shadcn/ui Label (Radix `Label`) |
| 대표 Story | `Components/Form/Label` → `All` (이름 "Label") |
| 보류 | 아래 §11 |
## 2. 적용 근거
- ui_viz에는 **독립 Label 문서가 없다.** Field Label 역할은 Input·Textarea가 소유한다: `docs/components/input.md` §9(14/20/500, `label-md`), §10·§16(`color.fg.secondary` `#6E6759`, Confirmed), `textarea.md` §2.2·§2.
- **ui_viz Confirmed (Input·Textarea Field Label 범위)**: 14 / 20 / 500, 색 `color.fg.secondary`. (Source 13/700은 `CANONICAL_OVERRIDE`로 14/20/500이 확정. D-093 원본 실행본 13/700은 `KNOWN_DEVIATION / FOLLOW_UP`.)
- **shadcn/Radix 유지**: 라벨 연결(`htmlFor`/감싸기), 클릭 시 컨트롤 focus, `select-none`, Disabled 연동 class.
- Required 표시, Checkbox/Radio option label의 별도 규칙은 ui_viz에 없다 → §11.
## 3. 기본 규격
| 항목 | 값 |
|---|---|
| Typography | 14 / 20 / 500 |
| 색 | `--hm-sem-fg-secondary` (`#6E6759`) |
| 배치 | `flex w-fit items-center gap-2` (shadcn 기본, 부모 grid에서 시작 정렬) |
Label ↔ Control 간격 8px(`input.field.gap`)은 Field 조합의 값이며 Label이 소유하지 않는다.
## 4. 상태 규격
| 상태 | 값 |
|---|---|
| Default | 위 기본 규격 |
| Disabled | shadcn 기본: 연결된 컨트롤이 `peer`이고 disabled면 `opacity-50`, `cursor-not-allowed` (또는 상위 `group[data-disabled=true]`) |
Hover/Focus/Active 스타일 없음.
## 5. API
Radix `Label` props 그대로 (`htmlFor`, `asChild` 등). 출력 속성: `data-slot="label"`.
- Required 표시(`*`)를 위한 prop은 만들지 않았다(ui_viz `field-semantics.md` §1: Select/Checkbox/Radio 등에 required API·표시를 새로 추가하지 않는다). 소비자가 `aria-hidden` span으로 조합한다.
### 신규 업무 화면의 연결 예제
`Label`은 `@hm-design/ui`에 공개 export되어 있다. 지원하는 Field Label은 직접 스타일링한 native `<label>` 대신 실제 `Label`을 렌더한다. Input/Textarea는 `id`, Select는 **Root가 아닌 SelectTrigger**의 `id`, Combobox는 trigger로 전달되는 공개 `id`에 연결한다. `aria-labelledby`로 화면에 보이는 항목명을 접근성 이름으로 함께 사용한다.
```tsx
import { useId } from "react"
import { Label, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Combobox } from "@hm-design/ui"
export function Fields() {
const id = useId()
return <>
<Label htmlFor={`${id}-name`}>이름</Label>
<Input id={`${id}-name`} size="medium" />
<Label id={`${id}-type-label`} htmlFor={`${id}-type`}>유형</Label>
<Select>
<SelectTrigger id={`${id}-type`} aria-labelledby={`${id}-type-label`} size="medium"><SelectValue placeholder="선택" /></SelectTrigger>
<SelectContent><SelectItem value="work">업무</SelectItem></SelectContent>
</Select>
<Label id={`${id}-owner-label`} htmlFor={`${id}-owner`}>담당자</Label>
<Combobox id={`${id}-owner`} aria-labelledby={`${id}-owner-label`} options={[{ value: "kim", label: "김한맥" }]} size="medium" />
</>
}
```
Label/control 간격은 부모 조합이 소유한다. 반복 Field는 인스턴스별 ID를 사용하며, Label 클릭 focus와 팝업 키보드·접근성 이름은 실제 브라우저에서 확인한다. 위 예제는 기존 공개 props를 사용하며 새 `label`/`required` prop을 만들지 않는다. native Label 사용을 무조건 금지하지 않는다. 명시적인 비 React adapter 또는 기존 HM Label로 연결할 수 없는 사용처는 지원 제약·의미 있는 HTML 선택 이유·접근성 확인 결과를 기록한다. Story에 남은 native Label fixture만으로 신규 화면의 예외를 인정하지 않는다.
## 6. Token
Component Mapping 위치: `packages/ui/src/theme/label.css` (`--hm-label-*`).
- `--hm-label-font-size`(14px), `--hm-label-line-height`(20px), `--hm-label-font-weight`(500), `--hm-label-text`(→ `--hm-sem-fg-secondary`).
- Primitive 신규 추가 없음.
## 7. shadcn / Radix에서 유지한 항목
Radix Label 동작·접근성, `data-slot`, `cn(..., className)`, `select-none`, `group-data-[disabled=true]`·`peer-disabled` 연동.
## 8. HM에서 변경한 항목
- shadcn `leading-none font-medium text-sm` 대신 theme CSS가 typography·색을 정한다.
- `flex` 라벨이 한 줄 전체를 차지하지 않도록 `w-fit`을 추가(ui_viz Label `justify-self: start`).
## 9. Storybook
- 대표 Story `All`("Label"): 구역 `default`(htmlFor), `required`(소비 조합), `disabled`, `long`.
- 별도 verification Story 없음(구역이 단순).
## 10. 사용자 확정 결정
- **2026-10-11 최종 사용자 결정**: [Input Story 소비 조합의 Disabled Label](input.md#10-사용자-확정-결정)은 원본 색 `#6E6759` / opacity `0.6`을 적용한다. 공통 Label Component의 Disabled `0.5`는 그대로 유지한다. Input 기본·readOnly·Error Label은 Disabled가 아니면 `1`, Message / Helper Text는 기존 구현 유지다. 이 결정은 공통 Label API·Theme·Checkbox/Switch/Radio Label 규칙으로 확대하지 않는다.
## 11. 미확정 / 보류
- 독립 Label 규격 없음. Input·Textarea Field Label 값을 Label 컴포넌트에 적용한 것이다. Checkbox/Radio option label의 전경 role은 ui_viz `TBD`(`field-semantics.md` §3)이므로 해당 컴포넌트 작업에서 따로 확인한다.
- Disabled opacity의 과거 기록: Input의 "Label·Message 포함 .6"(2026-10-02 Input 한정 결정)은 공통 Label로 확대하지 않고 shadcn 기본 `0.5`를 유지했다. 2026-10-11 최종 결정은 Input Story 소비 Label만 `0.6`으로 적용하며, Message는 변경하지 않는다. 이전 검토의 “Label 100% 유지”와 과거 검증은 당시 기록이고 현재 정책은 §10을 따른다.
- Required 표시 색(`#D32F2F`, TEXTAREA-07)과 표시 방식은 소비 조합이다. 컴포넌트 API 없음.
- D-093 원본 실행본 13/700: `KNOWN_DEVIATION / FOLLOW_UP`.
## 12. 관련 파일
```
packages/ui/src/components/ui/label.tsx
packages/ui/src/theme/label.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export Label)
src/components/label.stories.tsx
```
## 13. 검증 상태
- `npm run typecheck`, `npm run build`: 통과(2026-10-02).
- Storybook computed style(`components-form-label--all`): 14px / 20px / 500, color `rgb(110,103,89)`, Disabled 연결 시 opacity 0.5.
- 라벨 클릭 시 연결된 input으로 실제 focus 이동 확인.
- 2026-10-02 당시 미확인: 사이드바 화면, 390px 폭, Checkbox/Radio와의 조합. 현재 Checkbox/Radio는 구현되어 있으나 이 과거 검증 기록이 조합 검증 완료를 뜻하지는 않는다.
- 2026-10-11 선택 회귀: 실제 공통 Label 대표 Story Disabled의 색 `rgb(110, 103, 89)` / opacity `0.5`, 부모 `1`, disabled Input과의 `htmlFor` 연결 확인. 공통 Label 구현·Theme는 변경하지 않았다. [Input Story Label 60% 별도 결정 및 검증](../reports/disabled-input-label-2026-10-11.md).