135 lines
10 KiB
Markdown
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`, `hover`, `focus`, `error`, `error-message`, `readonly`, `disabled`, `multiline`, `resize`. Focus는 `play` 함수로 실제 focus를 준다. Hover는 별도 pseudo 복제 없이 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.
|