Files
HMDesign/docs/components/button.md
T

192 lines
15 KiB
Markdown
Raw 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.
# Button
현재 HM 구현과 Runtime-neutral 사용 기준 문서. 공통 원칙은 `AGENTS.md`와 `docs/guidelines/runtime-portability.md`를 따른다. 이 문서는 Button API·Token·상태·사용 위치·조합 계약을 기록한다.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | HM 스타일 적용 완료 (`hm-*` class 없음) |
| 기준일 | 2026-10-08 (Role × Appearance 계약·현재 Story 대조) |
| 기반 | shadcn/ui Button (CVA, Radix `Slot`) |
| 대표 Story | `Components/Form/Button` → `All` (이름 "Button") |
| 보류 | Brand label Text Border 두께(EXPERIMENTAL, 미적용), Focus(ui_viz TBD) |
## 2. 적용 근거
- **ui_viz Confirmed**: size, padding-x, label size/line-height(button.md §26, D-087), Brand gradient·bevel(D-087), Standard Solid/Outline 색(§28, D-086, D-087), Hover/Pressed 동일 Black 20%(§12), Red Outline Hover/Pressed Red 8%.
- **HM 사용자 확정 규칙**: 실제 화면 Brand 최대 1개·팝업 Brand 금지, 일반 확인/작성 Solid 세트, 삭제 확인·행/반복 작업 Outline 세트, 같은 세트의 appearance와 size 통일(§5). Disabled `0.6`은 ui_viz 규격 값이며, Input·Select의 2026-10-02 결정과는 별개다.
- **shadcn 유지**: 구조(CVA, `data-slot`, `cn(..., className)`), focus/invalid ring class, `ghost`·`link` variant와 기존 size 이름.
- **미정의/TBD**: Focus 표현(ui_viz TBD → shadcn 기본 focus ring 유지), `aria-invalid` 표현(ui_viz에 없음 → shadcn class 유지), `ghost`·`link`의 규격(ui_viz 없음).
## 3. 기본 규격
| Size | Height(min) | Padding-x | Font / Line-height |
|---|---|---|---|
| `sm` | 28 | 10 | 12 / 18 |
| `default` | 34 | 14 | 13 / 20 |
| `lg` | 42 | 18 | 15 / 22 |
- 높이는 `min-height`다. 기본 라벨은 `nowrap`으로 한 줄을 유지한다. 여러 줄이 필요한 경우 기존 `className="whitespace-normal"`을 명시하며, 이때만 줄바꿈에 따라 높이가 늘어난다(가운데 정렬, `overflow-wrap: anywhere`).
- 세로 padding은 `(height − line-height) / 2 − border`로 계산한다.
- Radius `--hm-radius-button`, font-family `--hm-font-family-base`.
- Weight: Brand 700, Standard 500.
- 아이콘 전용 size(`icon*`)와 `xs`는 shadcn 기본이며 HM 규격이 없다(`data-appearance`가 있는 버튼에 한해 `default|sm|lg`만 HM 크기 적용).
## 4. 상태 규격
| 상태 | 값 | 근거 |
|---|---|---|
| Default | treatment별 face/border (아래 Token) | ui_viz Confirmed |
| Hover / Pressed (Brand, Solid) | face 위에 Black 20% overlay (`inset` shadow) | ui_viz §12 Confirmed |
| Hover (Outline) | role ink로 채움 + 흰 글자 | ui_viz Confirmed |
| Pressed (Outline) | 흰 face + Black 20% overlay | ui_viz Confirmed |
| Red Outline Hover/Pressed | Red 8% face, Black overlay 없음 | ui_viz D-087 Confirmed |
| Disabled | `opacity: 0.6`, `cursor: not-allowed`, Default paint 유지 | ui_viz Confirmed (HM roles) |
| Focus | shadcn 기본 `focus-visible` ring | ui_viz TBD → shadcn 유지 |
| Invalid | shadcn `aria-invalid` ring | ui_viz 미정의 → shadcn 유지 |
- Brand/Solid의 Hover·Pressed는 `:not(:disabled)`에만 적용된다.
- transition과 눌림 이동(`translate-y-px`)은 HM role에서 제거한다.
- **`ghost`·`link`의 Disabled는 `opacity-50`(shadcn 기본)** 이다. HM role(`0.6`)과 값이 다르다. 규격이 없는 호환 variant라 그대로 둔다(아래 §11).
## 5. 사용 위치와 버튼 세트
- `brand`: 페이지 대표 실행에 사용한다. 실제 서비스 화면당 최대 1개이며 팝업에서는 사용하지 않는다. Storybook 비교 갤러리는 예외다.
- `solid`: 일반 실행과 등록·저장·확인에 사용한다.
- `outline`: 반복·보조 작업과 표/목록 행 작업에 사용한다.
- 일반 작성·수정창: 취소 Solid + 등록/저장 Solid.
- 일반 확인창: 취소 Solid + 확인 Solid.
- 삭제 확인창: 취소 Outline + 삭제 Outline.
- 표/목록 행과 반복·보조 작업: 동일 `appearance`의 Outline 조합.
- FilterBar 대표 Story의 초기화·조회: `cancel` + `primary`, 둘 다 Outline·`size="default"`. 목록의 반복 조회·보조 작업 세트다.
- 하나의 세트는 `appearance`와 `size`를 통일한다. 역할 차이는 variant의 기존 역할색과 문구로 표현한다.
사용 위치를 기준으로 appearance를 명시하며 버튼 문구만으로 판단하지 않는다. 컴포넌트는 appearance 생략 시 기본 `brand`를 출력하므로, 팝업·버튼 세트에서는 기본값에 의존하지 않는다.
## 6. API
```
variant: shadcn "default | outline | secondary | ghost | destructive | link"
→
HM "primary | secondary | modify | destructive | cancel"
+ 별칭 "default"(= primary), "outline"(= primary + outline appearance)
+ 호환 "ghost", "link"
appearance: (신규) "brand | solid | outline" 기본 "brand"
size: "default | xs | sm | lg | icon | icon-xs | icon-sm | icon-lg" (shadcn 그대로)
asChild: shadcn 그대로
```
- 기본값: `variant="primary"`, `appearance="brand"`, `size="default"`.
- 출력 속성: `data-slot="button"`, `data-variant`(별칭은 role로 정규화), `data-appearance`(`ghost`/`link`은 없음), `data-size`.
- `secondary`의 의미가 바뀌었다: shadcn 회색 보조 → ui_viz Brown 역할. 기존 사용처 영향은 별도 확인하지 않았다.
### 6.1 허용 Role × Appearance (AI Reference)
현재 `button.css`의 역할별 Mapping과 대표 `All` Story의 `rolesFor`가 지원하는 조합은 다음과 같다.
- `primary` (Green): `brand`, `solid`, `outline`.
- `secondary` (Brown): `brand`, `solid`, `outline`.
- `modify` (Orange): `brand`, `solid`. **`outline` 미지원**.
- `cancel` (Gray): `solid`, `outline`. **`brand` 미지원**.
- `destructive` (Red): `outline`만 지원. **`brand`, `solid` 미지원**.
공개 Props는 `variant`와 `appearance`를 독립적으로 받는다. TypeScript 통과나 Storybook Controls에서 선택 가능하다는 사실은 해당 조합의 디자인 지원을 뜻하지 않는다. 미지원 조합에는 필요한 역할별 CSS Custom Property가 없으며 자동 보정·fallback도 없다. 기존 CSS 변수에 임의 fallback이나 새로운 역할색을 추가하지 않는다.
### 6.2 기본값·별칭·합성 주의
- `<Button>`은 Primary Brand다. `variant="destructive"`만 지정하면 미지원 Destructive Brand, `variant="cancel"`만 지정하면 미지원 Cancel Brand가 된다. 각각 허용 appearance를 명시한다.
- `variant="default"`는 Primary 별칭이며 appearance 생략 시 Brand다. `variant="outline"`는 Primary Outline 별칭이며 전달한 appearance보다 우선한다. Red Outline은 `variant="destructive" appearance="outline"`로 작성한다.
- `ghost`/`link`는 HM Role × Appearance 계약 밖의 shadcn 호환 variant다. `data-appearance`를 출력하지 않으며 appearance를 전달해도 HM treatment를 적용하지 않는다.
- `AlertDialogAction`과 `AlertDialogCancel`의 기본 appearance는 Button과 달리 `solid`다. `<AlertDialogAction variant="destructive">`만 지정하면 미지원 Destructive Solid다. 삭제 확인은 Action과 Cancel **모두** `appearance="outline"`을 명시한다. 자세한 합성 API는 [Alert Dialog](alert-dialog.md#5-api)를 따른다.
```tsx
// 삭제·취소: 같은 appearance와 size (DialogClose asChild로도 합성 가능)
<Button variant="cancel" appearance="outline" size="default">취소</Button>
<Button variant="destructive" appearance="outline" size="default">삭제</Button>
// Alert Dialog 삭제 확인: 두 요소 모두 Outline을 명시
<AlertDialogCancel appearance="outline" size="default">취소</AlertDialogCancel>
<AlertDialogAction variant="destructive" appearance="outline" size="default">삭제</AlertDialogAction>
```
미지원 조합 추가, role별 자동 appearance 선택, 기본 appearance 변경은 사용자 결정이 필요한 별도 변경이다. 현재 계약과 공개 API를 유지한다.
## 7. Token
Component Mapping 위치: `packages/ui/src/theme/button.css` (`--hm-button-*`). 공통 값은 `packages/ui/src/theme/default.css`, Primitive는 `packages/ui/src/theme/primitives.css`.
- 공통 Semantic 재사용: `--hm-radius-button`, `--hm-font-family-base`, `--hm-font-weight-*`, `--hm-color-text-inverse`, `--hm-color-surface-default`, `--hm-color-action-*`, `--hm-sem-surface/border-action-primary`.
- `--hm-button-height-md`는 `--hm-control-height-md`(34)를 참조한다. sm/lg 높이는 Button 전용 값이다.
- Brand/Solid의 색 Token은 role별(`green|brown|orange|gray`)로 Component 안에 둔다. Brown/Orange gradient의 일부 stop과 Gray 값은 `default.css`의 공개 Theme palette를 참조한다. `button.css`에 직접 HEX는 남아 있지 않다.
- Input·Select와 높이 값(28/34/42)이 겹쳐도 alias하지 않는다. 각 ui_viz 규격이 따로 확정한 값이기 때문이다.
## 8. Runtime-neutral DOM 계약
React가 아닌 Runtime도 배포된 `@hm-design/ui/theme.css` 또는 승인된 standalone Theme CSS를 먼저 로드하고, semantic `<button>`에 다음 속성을 사용한다.
- `data-slot="button"`, `data-variant="primary|secondary|modify|destructive|cancel"`, `data-appearance="brand|solid|outline"`, `data-size="default|sm|lg"`.
- Role × Appearance는 §6.1의 허용 조합만 사용한다. 위 속성 vocabulary가 모든 곱집합을 허용하는 것은 아니다.
- CSS는 `--hm-button-*` Component variable과 `--hm-sem-*`/기존 Theme variable을 소비한다. HEX와 임의 variable을 추가하지 않는다.
- native `disabled`, `:hover`, `:active`, `:focus-visible`로 Disabled/Hover/Pressed/Focus 계약을 유지한다. `aria-disabled`만으로 native disabled를 대체하지 않는다.
- 팝업의 취소/확인 조합은 같은 `size`를 사용하고, 삭제 확인은 취소·삭제 모두 Outline이어야 한다. Escape, Tab 순서, 초기 포커스와 닫힌 뒤 trigger 포커스 복귀는 modal primitive 또는 동등한 keyboard contract로 보존한다.
React의 `variant` prop을 그대로 복제할 필요는 없지만, 동일한 role/treatment attribute와 시각·상태·접근성 계약을 유지해야 한다.
## 9. shadcn / Radix에서 유지한 항목
CVA 구조, `data-slot`, `asChild`(Radix `Slot`), `className` 확장, focus-visible/invalid ring class, `[&_svg]` 아이콘 규칙, 기존 size 이름, `ghost`·`link` variant.
## 10. HM에서 변경한 항목
- `variant`를 ui_viz 역할로 재정의하고 `appearance` 추가.
- HM role의 transition·눌림 이동 제거, `disabled:cursor-not-allowed`.
- 높이를 `min-height` + 계산 padding으로 구성. 일반 라벨은 한 줄 유지하며, Long Label specimen은 명시적 `whitespace-normal`로 줄바꿈 지원.
- shadow에 shadcn ring shadow(`--tw-ring-*`)를 끝에 포함해 bevel과 ring이 함께 보이게 함(지침 §6.7).
- `hm-*` class 제거.
## 11. Storybook
- 대표 Story `All`("Button")이 기본 sidebar의 유일한 Button 항목이다 → 1뎁스.
- `All`의 구역: Overview → Sizes → Variants → States. 기본 버튼·크기·허용 조합·Brand/Standard 상태를 보여준다. Red Outline은 Variants와 Standard States에도 포함되며, 삭제·취소 세트와 Long Label은 verification Story에서 따로 확인한다.
- verification(`tags: ["verification"]`, 기본 sidebar 제외): `Roles`, `Sizes`, `States`("Brand States"), `StandardStates`, `LongLabel`, `KoreanLabels`, `IconTextNarrow`, `Destructive`, `Playground`(HM 전용), `Compatibility`(ghost/link, default/outline 별칭). Controls는 조합별 선택을 제한하지 않으므로 §6.1의 허용 조합을 직접 지킨다. Verification은 기술 점검용이며 운영 화면의 사용 위치·세트 규칙은 §5가 우선한다.
- 대표 경로/ID: `Components/Form/Button → All` (`components-form-button--all`), `Components/Overlay/Dialog → All` (`components-overlay-dialog--all`), `Components/Overlay/Alert Dialog → All` (`components-overlay-alert-dialog--all`), `Patterns/Data/Filter Bar → Filter Bar` (`patterns-data-filter-bar--all`). 삭제 세트 점검 ID는 `components-form-button--destructive`(verification)다.
- 운영 규칙은 `프로젝트-지침.md` §6.3, §7 참조.
## 12. 사용자 확정 결정
사용자가 확정한 사용 위치·세트 규칙은 §5에 기록한다. 2026-10-08 정합성 점검에서는 기존 허용 조합과 기본값을 유지하고 미지원 조합을 새로 지원하지 않는다.
## 13. 미확정 / 보류
- **EXPERIMENTAL**: Brand label Text Border 두께 — 적용하지 않음.
- **TBD(ui_viz)**: Focus 표현.
- **ui_viz 미정의**: `aria-invalid`, `ghost`/`link`, `xs`·`icon*` size, `ghost`/`link` Disabled(현재 0.5, shadcn 기본).
- Disabled `0.6`은 Input·Select의 사용자 결정과 별개이며, 전역 Disabled Token이 아니다.
## 14. 관련 파일
```
packages/ui/src/components/ui/button.tsx
packages/ui/src/theme/button.css
src/components/button.stories.tsx
src/components/story-board.tsx
```
## 15. 검증 상태
### 2026-10-08 Button 조합 정합성 점검
- 기존 6024 Storybook 재사용. 대표 Button의 허용 조합·고정 Hover/Pressed·Disabled를 computed style로 확인: 높이 28/34/42px, Disabled opacity `0.6`, Solid Hover/Pressed Black 20%, Red Outline Hover/Pressed Red 8%와 Red 전경 유지. 삭제 버튼의 실제 포인터 Hover도 같은 면을 확인했다.
- Playground에서 Destructive Brand/Solid와 Modify Outline을 직접 렌더했다. Destructive Brand/Solid는 역할 배경 변수가 비어 투명 배경·흰 글자, Modify Outline은 역할 ink 변수가 비어 기본 전경색으로 표시된다. 디자인 계약상 미지원이며 이 점검에서 지원을 추가하지 않았다.
- 대표 Dialog / Alert Dialog: 일반 확인 Solid 세트·삭제 확인 Outline 세트, 동일 `size="default"`·높이 34px 확인. AlertDialog Action/Cancel의 `data-slot="button"`과 역할·appearance 합성이 유지된다. Dialog의 Escape, Alert Dialog의 취소·Escape 닫기 후 trigger 포커스 복귀, Alert Dialog 초기 Cancel focus·Tab 순환 확인.
- 대표 Filter Bar: 초기화 Cancel Outline / 조회 Primary Outline·동일 크기 확인. 검색 조건 입력 후 초기화로 빈 검색어·조건 0개 복원. 조회는 기존 callback 없는 조합 예시다.
- 관련 브라우저 Console warning/error 없음. `npm run check:theme-defaults` 통과. Component·CSS·Theme 값·공개 API 변경 없음. 전체 회귀·build·브라우저별 비교는 요청 범위에 따라 생략했다.
### 과거 검증 기록
- TypeScript, `npm run build`: 통과(2026-10-02, 복구 후).
- 2026-10-06 ui_viz Storybook 화면 비교: 대표·verification Story의 버튼 51개(+대표 49개) computed style 일치(Red Outline 8% 면은 흰 바탕 합성값으로 같은 RGB, `cursor`는 ui_viz도 Web Mapping으로 분류해 shadcn 기본 유지). 대표 Story를 ui_viz 4구역으로 복원(Story만 변경).
- 과거 Storybook 기록: Long Label·Destructive 및 verification Story ID 렌더 확인. 현재 `All` 구역 구성은 §11을 따른다.
- 당시 미확인: 실제 사이드바 화면, Disabled computed opacity(Button), 브라우저별 렌더, `secondary` 의미 변경의 기존 사용처 영향. Disabled computed opacity는 위 2026-10-08 점검에서 확인했다.