Files
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

135 lines
10 KiB
Markdown

# Textarea
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Textarea 고유의 API·Token·상태·결정만 기록한다.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | HM 스타일 적용 완료 (`hm-textarea` class 없음) |
| 기준일 | 2026-10-02 |
| 기반 | shadcn/ui Textarea (native `<textarea>`) |
| 대표 Story | `Components/Form/Textarea` → `All` (이름 "Textarea") |
| 보류 | 아래 §11 |
## 2. 적용 근거
원본: 외부 ui_viz Reference (ZIP 미포함)의 `docs/components/textarea.md` (+ `brand/hanmac/foundation/{color,border}.md`, `foundation/token-inventory.md`의 입력 계열 Semantic).
- **ui_viz Confirmed**: border 1px / radius 4px(§2, §2.2), 면·값·Placeholder 색(§2.2), 기본 테두리 `color.stroke.input.default`, Focus `color.stroke.input.focus`, Error `color.stroke.input.error`, Focus/Error 두께 1.5px(D-005 상속, §2.2), Hover = Default(§3.3 사용자 결정 TEXTAREA-05, Confirmed), Readonly 표현(§3.1), 값 typography 16/24/400(§2).
- **SCOPED_DESIGN_APPROVAL** (2026-09-17 owner, Textarea 한정): min-height `112px`, padding `8px 12px`(§8.1, TEXTAREA-01/02). D-093 디자인 확정값이 아니며 ui_viz Canonical은 `TBD`다. Textarea 범위에서만 사용하고 `size.*`/`space.*`로 승격하지 않는다.
- **Component-local**: `resize` 정책(vertical 기본, none/both 선택, Readonly는 none 고정).
- **shadcn 유지**: native textarea 동작, `transition-colors`, `outline-none`, Disabled opacity와 커서.
- **HM 사용자 결정(2026-10-06)**: Error border와 Keyboard Focus ring을 독립 Semantic으로 표시한다.
## 3. 기본 규격
| 항목 | 값 | 상태 |
|---|---|---|
| Width | 부모 폭 100% (`w-full`) | Canonical |
| Min-height | 112px | SCOPED_DESIGN_APPROVAL |
| Padding | 8px (block) / 12px (inline) | SCOPED_DESIGN_APPROVAL |
| Border / Radius | 1px `--hm-sem-stroke-input-default` / `--hm-radius-input`(4px) | Confirmed |
| Face / 값 색 | `--hm-sem-bg-surface` / `--hm-sem-fg-primary` | Confirmed |
| Typography | 16 / 24 / 400 | Existing role |
| Placeholder | `--hm-sem-fg-placeholder`, `opacity: 1` | Confirmed |
| Resize | `vertical` 기본 | Component-local |
Size 체계 없음. Input의 Small/Medium/Large와 공유하지 않는다(ui_viz Textarea에 size 정의 없음). `field.gap` 8px 등 Field 관계는 Textarea 밖(소비 조합)이다.
## 4. 상태 규격
| 상태 | 값 | 근거 |
|---|---|---|
| Default | 위 기본 규격 | Confirmed |
| Hover | Default와 동일 (hover rule 없음) | Confirmed (사용자 결정 TEXTAREA-05) |
| Focus (`:focus-visible`) | border 1.5px `--hm-sem-stroke-input-focus`, shadow/glow 없음, padding으로 0.5px 보정해 글자 위치 유지 | Confirmed (D-005 상속) |
| Error (`[aria-invalid="true"]`) | border 1.5px `--hm-sem-action-destructive`, ring 없음, 동일 padding 보정 | Confirmed (D-005 상속) |
| Error + Keyboard Focus | Destructive border 유지 + HM Focus Semantic 3px / 50% outer ring, `outline: none` | Input과 동일, 2026-10-06 사용자 결정 |
| Disabled | surface face 유지, `cursor: not-allowed`, `opacity: 0.6` | opacity: 사용자 결정(§10) |
| Readonly (`[readonly]`) | Default와 같은 면·테두리·typography, `opacity: 1`, `resize: none` 고정 | Component-local (§3.1) |
Chromium은 1.5px 테두리를 `getComputedStyle` 기준 1px로 계산한다(ui_viz도 같은 관측을 기록). padding 보정(7.5px/11.5px)은 선언 1.5px 기준으로 적용된다.
## 5. API
```
Textarea:
resize?: "vertical" | "none" | "both" 기본 "vertical"
```
- 나머지는 native `<textarea>` props 그대로(`rows`, `placeholder`, `disabled`, `readOnly`, `aria-invalid` 등).
- 출력 속성: `data-slot="textarea"`, `data-resize`.
- 상태는 새 prop이 아니라 native 속성으로 준다.
- Field(Label, Required 별표, Helper/Error 문구)는 Textarea에 포함하지 않는다. 소비 조합이다.
- auto-grow, 글자 수 카운터는 범위 밖이다. shadcn의 `field-sizing-content`(auto-grow)는 제거했다.
## 6. Token
Component Mapping 위치: `packages/ui/src/theme/textarea.css` (`--hm-textarea-*`).
- 재사용한 공통 Semantic: `--hm-radius-input`, `--hm-sem-bg-surface`, `--hm-sem-fg-primary`, `--hm-sem-fg-placeholder`, `--hm-sem-stroke-input-default`, `--hm-sem-stroke-input-focus`, `--hm-sem-action-destructive`(Semantic 참조).
- Textarea 전용 값: `--hm-textarea-min-height`, `--hm-textarea-padding-{block,inline}`, `--hm-textarea-font-{size,weight}`, `--hm-textarea-line-height`, `--hm-textarea-border-width`, `--hm-textarea-state-border-width`(1.5px).
- Primitive 신규 추가 없음.
- Input의 `--hm-input-*`와 값이 겹쳐도 alias하지 않는다(ui_viz도 Input·Textarea를 별도 owner로 두고 값을 각각 소비).
## 7. shadcn / Radix에서 유지한 항목
native `<textarea>` 동작 전체, `data-slot`, `cn(..., className)`, `transition-colors`, `outline-none`, `disabled:cursor-not-allowed`. Radix 사용 없음.
## 8. HM에서 변경한 항목
- `resize` prop, `data-resize` 추가.
- shadcn의 기본 min-height/padding/border/ring/shadow/`dark:`/`field-sizing-content` class 제거 → theme CSS가 담당.
- 일반 Focus는 border 1.5px로 표시, Error 단독은 ring 없음. Error + `:focus-visible`만 독립 Focus ring을 표시한다.
- Readonly 전용 표현(resize 고정) 추가.
## 9. Storybook
- 대표 Story `All`("Textarea"): 구역 `default`, `focus`, `error`, `error-message`, `readonly`, `disabled`, `multiline`, `resize`. Focus는 `play` 함수로 실제 focus를 준다. Hover는 Default와 같아(TEXTAREA-05) 2026-10-22 대표 화면의 중복 `hover` 예시를 뺐다. 실제 포인터 확인은 Default 예시에서 한다.
- verification(`tags: ["verification"]`): `Default`, `Focus`, `Error`, `InvalidFocus`, `ErrorMessage`, `Required`, `Readonly`, `Disabled`, `LongContent`, `Playground`(HM 전용).
- Error Message·Required 표시는 Story 안의 조합 예시이며 Textarea 컴포넌트의 일부가 아니다.
## 10. 사용자 확정 결정
**2026-10-02**: Textarea Disabled opacity `0.6` 확정. ui_viz에서 `Temporary`지만 현재 HM 구현에서는 이 결정이 우선한다. Textarea 한정이며 전역 Disabled opacity로 확대하지 않는다 (Input·Select와 같은 값). Token이 아니라 `ui/textarea.tsx`의 `disabled:opacity-60`으로 적용.
- ui_viz에 기록된 결정을 따른다: 2026-09-17 Hover = Default(TEXTAREA-05), min-height 112 / padding 8·12 Textarea 한정 승인(TEXTAREA-01/02). 2026-10-01 필수 별표 `#D32F2F`(TEXTAREA-07, Textarea에 별표 없음 — Story 조합 예시는 shadcn `text-destructive`).
## 11. 미확정 / 보류
- **min-height 112 / padding 8·12**: Textarea 한정 owner 승인이지만 ui_viz Canonical은 `TBD`, D-093 미확정(TEXTAREA-01/02/06, `NO_SOURCE_EVIDENCE`). 변경 가능성이 있다.
- **Label 14/20/500 · Value 16/24 · Error 11/18 vs D-093 원본 실행본 13/700 등**: `KNOWN_DEVIATION / FOLLOW_UP`(TEXTAREA-08). 확정 근거 없어 ui_viz 문서값을 사용.
- **Error + Focus**: HM 사용자 결정(2026-10-06)으로 Input과 같은 3px / 50% Focus Semantic ring을 적용한다.
- auto-grow, 글자 수 카운터(TEXTAREA-03): 범위 밖. max length 관련 표현 없음.
- 전역 focus ring 수치: TBD.
## 12. 관련 파일
```
packages/ui/src/components/ui/textarea.tsx
packages/ui/src/theme/textarea.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export Textarea, TextareaResize)
src/components/textarea.stories.tsx
src/components/story-board.tsx
```
## 13. 검증 상태
- `npm run typecheck`(root + ui + center-graphic), `npm run build`: 통과(2026-10-02).
- Storybook computed style(`components-form-textarea--all`): Default min-height 112px / padding 8px 12px / border 1px rgb(222,219,210) / radius 4px / bg white / 16·24·400 / resize vertical / Placeholder rgb(153,153,153), Error border rgb(211,47,47) + padding 7.5px 11.5px, Readonly resize none · opacity 1, `resize="none"` 적용, Disabled opacity 0.6.
- 실제 클릭 focus: `:focus-visible` 매칭, border rgb(18,51,40), padding 7.5px 11.5px 확인.
- Story ID: `All` + verification 8개 존재 확인(index.json). `@hm-design/ui`에서 `import { Textarea }`로 Story가 빌드됨.
- 2026-10-06 ui_viz Storybook 화면 비교(1280/768/390, Chromium): 컨트롤 computed style 일치(ui_viz Story의 114px은 `rows=4` 때문), Error+Focus outline 유지, 긴 내용은 내부 세로 scroll·가로 넘침 없음, Readonly 편집 불가·선택 가능, Disabled focus 불가. ui_viz는 Disabled에서 Field 전체(라벨 포함)를 흐리지만 HM은 컨트롤만 흐리고 라벨은 `Label`의 disabled 연동(소비 조합)이 맡는다 — 의도적 차이. 대표 Story는 ui_viz 5구역을 모두 포함한 상위 집합(Hover = Default, Error Message, Resize none 추가)이라 변경하지 않음.
- 미확인: 브라우저별 렌더(Chromium 외).
### Invalid + Focus 검증 (2026-10-07)
- Focus ring: `--ring` → `--hm-sem-stroke-input-focus` → `--hm-color-border-focus`. Invalid border: 기존 `--hm-sem-action-destructive`. 새 Runtime Role / Production Error prop / 메시지 컴포넌트 없음.
- Chromium computed style: Invalid border `rgb(211, 47, 47)`, Invalid + Keyboard Focus box-shadow `color(srgb 0.14902 0.4 0.317647 / 0.5) 0px 0px 0px 3px`, `outline-style: none`. Invalid 단독은 `box-shadow: none`.
- Theme Designer UI에서 Focus `#c75b00`, Destructive `#d41466`, Active `#007f73`, Accent `#ffd36d`로 분리. Invalid border `rgb(212, 20, 102)` + Focus shadow `color(srgb 0.780392 0.356863 0 / 0.5) 0px 0px 0px 3px` 확인.
- `npm run typecheck`, `npm run build`, `npm run build-storybook` 통과. 기존 문서 예시 CSS warning 및 Storybook chunk 크기 / plugin timing warning 유지.
- 검증 범위: Chromium 환경. 다른 브라우저의 렌더링은 별도 확인이 필요하다.
- 대표 `All`과 숨김 `InvalidFocus` 확인. 일반 Focus / Invalid의 padding 7.5px 11.5px, 기본 min-height 112px / 16px 글자 / Readonly resize none / Disabled opacity 0.6 유지. Border CSS 계약 1.5px, 이 브라우저 환경의 computed border-width는 1px으로 정규화. Input과 같은 Invalid + Focus ring.