Files
HMDesign/docs/components/card.md

193 lines
19 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.
# Card
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Card 고유의 API·Token·상태·결정만 기록한다.
> 아래 외부 ui_viz 문서는 ZIP 미포함 초기 출처이며 실행·구현의 필수 의존성이 아니다. 현재 기준은 이 문서와 HM 구현·대표 Story다. ui_viz에는 `docs/components/card.md`가 없다. Card 규격은 `docs/patterns/content-container.md`(Type B **Content Card**)와 `docs/patterns/content-header.md`(`content` profile)가 소유한다. 두 문서는 "범용 Card System이 아니다"라고 명시하며, 상태·Footer·Media·elevation을 만들지 않았다(Out of Scope). HM Card는 이 Type B를 shadcn Card에 적용한 것이다. Type A(Section Panel)는 구현하지 않았다.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | HM 스타일 적용 완료 (`<Card />`만으로 적용, `hm-card` class 없음) |
| 기준일 | 2026-10-02 (2026-10-08 문서 정정, 2026-10-14 Font Weight 3단계 통합) |
| 기반 | shadcn/ui Card (div 기반, Radix 없음) |
| 대표 Story | `Components/Data & Display/Card` → `All` (이름 "Card") |
| 보류 | Eyebrow, Divider, Footer·Media, 반응형, 폭 (§11) |
## 2. 적용 근거
- **ui_viz Confirmed**: 컨테이너 radius 4px(`radius.container.default`), 표면 White(`color.bg.surface`)/Beige(`color.bg.content`), 제목 전경 `color.fg.primary`.
- **ui_viz 채택(SOURCE_ADOPTED, Canonical Pattern Q6 `YES`, 2026-09-28)**: border 1px, body-padding 24px 사방, header↔body 간격 24px(D-093 D-042/D-059), title↔action 간격 20px, 교차축 중앙 정렬, 상태 없음. 이 값들은 문서상 `Confirmed` 표기가 아니라 owner가 채택한 Canonical 값이다. HM Card는 이 채택 값을 적용한다.
- **ui_viz LOCAL_EXCEPTION(컴포넌트 범위 값)**: border 색 `#DEDBD2`(전역 token 아님, Tabs·Search Field와 값만 같고 alias 금지), Beige border `#ECD4A2`(Primitive `hm-brown-light-4`와 같은 값). **현재 HM 구현은 이 두 값을 컴포넌트 전용 값으로 두지 않고 공유 Semantic Token에 연결한다.** 차이는 §6.1에 기록한다.
- **ui_viz CANONICAL_OVERRIDE**: Title Source 19px/700 → `heading-md` 20/28/600.
- **HM 사용자 결정 (2026-10-14)**: Font Weight 400 / 500 / 700 통합. 카드 제목은 700이며 크기·행간·간격은 유지한다. 이전 ui_viz 600 근거와 과거 실측은 당시 기록이다.
- **shadcn 유지**: 아래 §7.
- **ui_viz 미정의**: Description, Footer, `size="sm"`, Media, Hover/Selected/Clickable, shadow.
## 3. 기본 규격
| 항목 | 값 | 근거 |
|---|---|---|
| Background | White (`--hm-sem-bg-surface`) / Beige (`--hm-sem-bg-content`) | Confirmed |
| Border | 1px solid `--hm-sem-stroke-input-default`(Default `#dedbd2`) / Beige는 `--hm-sem-palette-brown-light-4`(Default `#ecd4a2`) | 두께는 채택. 색 값은 LOCAL_EXCEPTION 값과 같고 Token 연결은 §6.1 |
| Radius | 4px | Confirmed |
| Shadow | 없음 | ui_viz: "Source의 컨테이너는 그림자를 쓰지 않는다" |
| Padding | 24px 사방 (Card 위/아래, Header·Content 좌우) | 채택 |
| Header ↔ Content 간격 | 24px | 채택 (D-093) |
| 자식 clipping | 하지 않음 (`overflow: visible`) | 채택 (Type B) |
| Title | 20px / 28px / 700, `--hm-sem-fg-primary` | HM 사용자 결정 (2026-10-14) |
| Header 안 Title ↔ Action 간격 | 20px (`column-gap`) | 채택 |
| Action 정렬 | Title 반대쪽 끝, 교차축 중앙 | 채택 |
| Description | shadcn 기본 (`text-sm text-muted-foreground`), HM Card CSS 미적용. 사용 범위는 §5.1 | ui_viz 미정의 |
| 폭/높이 | 내용과 레이아웃이 결정. 고정 token 없음 | ui_viz: "Card width token을 만들지 않는다" |
## 4. 상태 규격
- 존재하는 상태는 **Default 하나**다. Hover / Focus / Pressed / Selected / Disabled는 ui_viz가 `NOT_APPLICABLE`로 명시했고 추가하지 않았다. Clickable·Interactive 변형도 없다.
- Card 안의 Button·Input 등은 각자의 컴포넌트 문서에 따른 상태를 가진다.
## 5. API
```
Card:
size?: "default" | "sm" (shadcn 그대로)
→
size?: "default" | "sm" (유지)
surface?: "surface" | "content" (신규) 기본 "surface" — ui_viz surface 축 (White / Beige)
CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter: shadcn API 그대로
```
- 출력 속성: `data-slot="card"`, `data-size`, `data-surface`.
- 새 prop은 `surface` 하나다. ui_viz가 Content Card의 표면 축(`surface`/`content`)을 정의했기 때문이다. `variant`·`interactive`·`selected` 등은 근거가 없어 만들지 않았다.
- Eyebrow는 shadcn Card에 대응 요소가 없다. 필요하면 소비자가 조합한다(미구현, §11).
### 5.1 CardDescription 사용 범위
- 공개 API로 export되며(`packages/ui/src/index.ts`) shadcn 구현 그대로다. `packages/ui/src/theme/card.css`는 `card-description`을 선택하지 않으므로 HM 전용 Typography·색이 없다(`text-sm text-muted-foreground`).
- ui_viz Content Card Anatomy에 Description이 없다. 대표 Story `All`에는 넣지 않으며, HM Card 규격 요소가 아니라 **shadcn 호환 요소**로만 둔다.
- 현재 저장소의 실제 사용처:
| 위치 | 용도 | 비고 |
|---|---|---|
| `src/components/card.stories.tsx` `Compatibility` | shadcn 호환 확인 | verification 전용 |
| `src/patterns/dashboard-layout.stories.tsx` | 업무 목록 건수(`role="status"`), KPI 라벨(Title 없이 Description만), 가로 스크롤 안내 | Pattern Story 안의 소비 예시. 별도 스타일 없음 |
| 과거 UI Demo (`src/App.tsx`, 공식 이전 전 baseline) | Panel 부제 | `.hm-panel-header`가 11px·`--hm-muted`로 덮어씀. Demo 전용(§8.1) |
- Header에 Description이 있으면 shadcn의 `has-data-[slot=card-description]:grid-rows-[auto_auto]`로 2행 grid가 되고, `CardAction`은 `row-span-2`로 두 행에 걸친다. 이 동작은 shadcn 그대로다.
- Description의 HM Token·크기·색은 미정의다. 필요해지면 이 문서에서 결정한 뒤 Token으로 추가한다(§11).
### 5.2 Tabs를 Card 안에 조합할 때
기본 White Card 안의 탭은 `TabsList panelSurface="surface"`를 사용하고, CardContent 안의 각 `TabsContent`는 `px-0 py-6`로 조합한다. Card의 배경·border·radius·기본 여백은 유지하며 내부 탭 패널의 중복 `border bg-background rounded-b p-6`만 제거한다. [Tabs 조합 예제](tabs.md#16-publisher--developer-조합-안내-2026-10-09)와 Tabs 대표 Story의 Usage를 기준으로 한다.
- CardContent는 기본 24px 좌우 여백을 소유한다. 내부 TabsContent에 24px 좌우 여백을 다시 더하지 않는다. `size="sm"`의 12px은 기존 호환 규격이며 이번 조합 때문에 24px로 강제하지 않는다.
- `surface="content"` Card는 실제 배경에 맞춰 `panelSurface="content"`를 사용한다. `panelSurface`는 Card의 표면이나 패널 배경을 자동 변경하지 않는다.
- 탭 콘텐츠 상하 24px은 조합 예시 값이며, Card의 Header→Content 24px이나 전체 화면 섹션 간격을 대체하는 공통 Token이 아니다. §11의 Tabs Meta 예외(0px 간격)를 새로 구현한 것도 아니다.
- 내부 Input·Textarea의 border, Table의 행 배경·구분선, Alert 등의 역할별 스타일은 보존한다. CardDescription의 호환 범위는 §5.1을 유지하며 PageHeaderDescription의 8px/14·20 규격으로 바꾸지 않는다.
- 좁은 화면에서는 flex/grid 자식인 Card 자체와 CardContent의 `min-w-0` 등 소비 Layout 수축 조건을 확인한다. Table이 있는 grid 조합에서 Card 자체의 자동 최소 폭을 유지하면 내부 스크롤 전에 페이지가 넓어질 수 있다. Tabs/Table 내부 스크롤을 함께 확인하며 Card의 새로운 Responsive Variant나 폭 Token은 추가하지 않는다.
## 6. Token
Component Mapping 위치: `packages/ui/src/theme/card.css` (`--hm-card-*`), `packages/ui/src/theme.css`에서 import.
- 재사용한 공통 값: `--hm-radius-container`(radius), `--hm-sem-bg-surface`, `--hm-sem-bg-content`, `--hm-sem-fg-primary`, `--hm-sem-stroke-input-default`(White border), `--hm-sem-palette-brown-light-4`(Beige border). Border 연결 상세는 §6.1.
- Card 전용 값: `--hm-card-radius`, `--hm-card-border-width`(1px), `--hm-card-padding`(24px), `--hm-card-border-color`, `--hm-card-border-color-on-content`, `--hm-card-surface`, `--hm-card-surface-content`, `--hm-card-text`, `--hm-card-header-gap`(20px), `--hm-card-title-font-size|line-height|font-weight|color`.
- **Primitive 신규 추가 1건**: `--hm-brown-light-4: #ecd4a2` (`packages/ui/src/theme/primitives.css`). ui_viz가 Confirmed Primitive `hm-brown-light-4`로 명시한 값이다. Card CSS는 이 Primitive를 직접 참조하지 않고 Semantic alias `--hm-sem-palette-brown-light-4`를 참조한다.
- shadcn의 `--card-spacing` 변수를 24px(`--hm-card-padding`)로 채운다. `size="sm"`은 TSX class의 12px을 그대로 쓴다.
- Input·Select·Button과 값(4px, `#dedbd2`)이 겹쳐도 그쪽 Component Token을 alias하지 않는다. `--hm-card-radius`는 `--hm-radius-container`를 참조하고 `--hm-radius-input`과는 연결하지 않는다(ui_viz가 alias 금지를 명시).
- `--card-spacing`과 Title 규칙은 `:where()`로 감싸 specificity를 낮췄으므로 소비자 `className`과 Demo 스타일이 덮어쓸 수 있다. border·radius·surface 규칙(`[data-slot="card"]`, `[data-surface="content"]`)은 `:where()` 없는 attribute selector다.
### 6.1 Border Token 실제 연결 (LOCAL_EXCEPTION과의 차이)
| Card 변수 | 실제 참조 (`card.css`) | Runtime 계약 key (`contract.ts`) | HM Default 해석 경로 (`default.css` → `primitives.css`) | Default 값 |
|---|---|---|---|---|
| `--hm-card-border-color` | `var(--hm-sem-stroke-input-default)` | `color.stroke.input.default` | `--hm-color-border-control` → `--hm-sem-palette-warm-neutral-pale` → `--hm-warm-neutral-pale` | `#dedbd2` |
| `--hm-card-border-color-on-content` | `var(--hm-sem-palette-brown-light-4)` | `color.palette.brown-light-4` | `--hm-brown-light-4` | `#ecd4a2` |
- **ui_viz 기술**: 두 border 색은 LOCAL_EXCEPTION 컴포넌트 값이며, 전역 token이 아니고 다른 컴포넌트 값과 alias하지 않는다.
- **현재 구현**: Default 렌더 값은 ui_viz 값과 같다. 다만 White border는 Input·Select·Dialog·Separator 등이 함께 쓰는 공유 Semantic `--hm-sem-stroke-input-default`를, Beige border는 공유 Palette Semantic `--hm-sem-palette-brown-light-4`를 참조한다. 다른 Component Token(`--hm-input-*` 등)은 alias하지 않는다.
- **결과**: 외부 Theme JSON이 `color.stroke.input.default` 또는 `color.palette.brown-light-4`를 override하면 Card border도 함께 바뀐다. Card만 따로 조정하려면 Component 변수 `--hm-card-border-color` / `--hm-card-border-color-on-content`를 덮어써야 하며, 이 변수는 Runtime 계약 key가 아니다.
- 이 절은 문서 정정이다. Token 연결과 렌더 값은 바꾸지 않았다. Card 전용 border token으로 분리할지는 미결정이다(§11).
## 7. shadcn / Radix에서 유지한 항목
semantic HTML(`div`), React composition(Header/Title/Description/Action/Content/Footer), `data-slot`, `cn(..., className)`, header의 grid 구조와 `has-data-[slot=card-action]` 2열 전환, Footer(`border-t`, `bg-muted/50`, 전체 폭), 첫/마지막 `img` 처리, `has-data-[slot=card-footer]:pb-0`, `size="sm"`, Description 스타일. Radix 사용 없음.
## 8. HM에서 변경한 항목
- `rounded-xl`, `bg-card`, `ring-1 ring-foreground/10`, `overflow-hidden`, 기본 `--card-spacing: 16px`을 TSX에서 제거하고 theme CSS(border, radius 4px, surface, 24px)가 담당.
- ring 대신 실제 1px border 사용, shadow 없음.
- 기본 Title은 20/28/700으로 통합(2026-10-14). `size="sm"`도 굵기 700을 사용하며 기존 14px 크기와 행간은 유지한다. 이전 20/28/600 도입·실측은 §13의 당시 검증 기록으로 보존한다.
- Header 열 간격 20px, Action 교차축 중앙 정렬.
- Footer 아래 모서리를 컨테이너 radius(border 안쪽 3px)에 맞춤. Footer 표면은 shadcn 그대로다.
- `surface` prop 추가.
### 8.1 Demo `.hm-panel`과 운영 Card 구분
- **운영 Card**: `@hm-design/ui`의 `<Card />`와 `packages/ui/src/theme/card.css`. 이 문서 §3의 값(padding 24px, Title 20/28/700 등)과 대표 Story `All`이 기준이다. 소비 프로젝트는 `hm-panel*` class 없이 이 API만 쓴다.
- **과거 Demo Panel**: 공식 이전 전 UI Demo의 `src/App.tsx`에 있던 4개 Card에 붙은 `hm-panel`, `hm-panel-header`, `hm-panel-icon`, `hm-panel-content` class와 `src/hm-theme.css` 규칙. 패키지(`packages/ui`)에 포함되지 않으며 Card 규격 Reference가 아니다.
- `.hm-panel`은 Card 외형(border, radius, background, shadow)을 지정하지 않는다. 다만 Demo class 묶음은 운영 Card 값을 다음처럼 덮어쓴다.
| Demo class | 덮어쓰는 값 | 운영 Card 값 |
|---|---|---|
| `.hm-panel` | `gap: 0`, `--card-spacing: 16px` | gap·padding 24px |
| `.hm-panel-header` | 아이콘 34px 열 grid, `column-gap: 11px`, `border-bottom: 1px solid #ede9df`, `padding: 22px 23px 17px` | Header 좌우 24px, column-gap 20px, divider 없음 |
| `.hm-panel-header [data-slot="card-title"]` | 15px / 1.35 / 700, letter-spacing -.02em | 20 / 28 / 700 |
| `.hm-panel-header [data-slot="card-description"]` | 11px, `--hm-muted`, margin-top 2px | shadcn 기본 `text-sm text-muted-foreground` |
| `.hm-panel-icon` | 34px 아이콘 박스 | 해당 요소 없음 |
| `.hm-panel-content` | `padding: 23px` | 좌우 24px |
- Demo 값은 HM Token이 아닌 hex 값을 포함한다. Card Token·Story의 근거로 쓰지 않으며, Demo 화면을 Card 디자인 Reference로 캡처·인용하지 않는다.
- 현재 `src/App.tsx`는 `WorkDashboard`를 렌더한다. 현재 업무 앱에는 `hm-panel-header` 사용이 없지만 과거 CSS와 Git 이력은 보존한다. 기존 Demo override 기록을 현재 공개 Card 규격이나 신규 override 승인으로 해석하지 않는다. Guard의 Component 식별·진입점 범위 판정은 [STYLE-001 범위 계약](../guidelines/ai-generation-guard.md#style-001-component-식별과-진입점-범위-2026-10-11)을 따른다.
## 9. Storybook
- 대표 Story `All`("Card")이 기본 sidebar의 유일한 Card 항목이다 → 1뎁스. 구역: `surface`(White), `content`(Beige), `action`(Title + Action), `form`(본문에 Input).
- verification(`tags: ["verification"]`): `Surfaces`, `Action`, `Compatibility`(HM 전용: Description, Footer, `size="sm"`).
- 운영 규칙은 `프로젝트-지침.md` §6.3, §7 참조.
- 대표 Story에 Hover/Selected/Footer/Media는 넣지 않았다(ui_viz 미정의).
## 10. 사용자 확정 결정
2026-10-14: 카드 제목 기본 굵기를 600에서 700으로 통합한다. `--hm-card-title-font-weight` 이름은 유지하며 `--hm-font-weight-bold`를 참조한다. `size="sm"` 제목도 700을 사용하며 크기와 행간은 기존 값을 유지한다.
## 11. 미확정 / 보류
- **Eyebrow**: ui_viz `content` header는 Eyebrow(11→12px/700, letter-spacing .12em, `#3E3523`)를 필수 요소로 둔다. shadcn Card에 대응 요소가 없고 이번 범위에 추가하지 않았다. 현재 Title만 있는 Header는 ui_viz Anatomy의 부분 구현이다.
- **Divider(선택)**: ui_viz는 Header 뒤 24px + 1px + 18px의 선택 divider를 정의한다(`CONTENT_CARD_DIVIDER_OPTIONAL`). 기본은 없음이라 구현하지 않았다.
- **Tabs Meta**: Meta가 Tabs일 때 Header↔Body 간격이 0이 되는 예외는 구현하지 않았다.
- **ui_viz 미정의**: Description, Footer(`footer variant`는 Out of Scope), Media, `size="sm"`, 반응형, Card 폭.
- **자식 clipping**: ui_viz Type B는 하지 않음이다. `overflow-hidden`을 제거했다. 대신 Footer 모서리를 CSS로 맞췄고 이미지 모서리는 shadcn `rounded-*`가 남아 있다.
- **LOCAL_EXCEPTION**: ui_viz는 border 색 두 값을 전역 token이 아닌 컴포넌트 값으로 두고 tokenization을 follow-up으로 둔다. 현재 HM 구현은 공유 Semantic(`color.stroke.input.default`, `color.palette.brown-light-4`)에 연결되어 있다(§6.1). Card 전용 Semantic/Runtime key로 분리할지는 미결정이며, 이번 정정에서 Token 연결을 바꾸지 않았다.
- **CardDescription HM 규격**: Typography·색·간격 미정의. shadcn 호환 요소로만 둔다(§5.1).
- **Type A Section Panel**: 구현하지 않았고 미구현 상태를 유지한다. Demo `.hm-panel`은 Type A 구현이 아니며 대체 Reference로 쓰지 않는다(§8.1).
- **Confirmed 표기 해석**: border 1px·padding 24px 등은 ui_viz에서 `Confirmed`가 아니라 owner 채택 Canonical 값이다. Confirmed만 적용한다는 기준으로 보면 재확인이 필요한 항목이다.
## 12. 관련 파일
```
packages/ui/src/components/ui/card.tsx
packages/ui/src/theme/card.css
packages/ui/src/theme/primitives.css (--hm-brown-light-4, --hm-warm-neutral-pale)
packages/ui/src/theme/default.css (--hm-sem-stroke-input-default, --hm-sem-palette-brown-light-4 연결)
packages/ui/src/theme/contract.ts (Runtime key color.stroke.input.default, color.palette.brown-light-4)
themes/default.json (HM Default 컬러 원본)
packages/ui/src/theme.css (card.css import)
src/components/card.stories.tsx
src/components/story-board.tsx
src/patterns/dashboard-layout.stories.tsx (CardDescription 소비 예시)
src/App.tsx (현재 WorkDashboard; 과거 hm-panel* Demo 기록은 §8.1)
src/hm-theme.css (.hm-panel* Demo 전용 스타일, §8.1)
```
## 13. 검증 상태
- TypeScript, `npm run build`: 통과(2026-10-02).
- Storybook computed style(대표 Story 4개 구역): background White/Beige(`rgb(255,255,255)` / `rgb(245,240,229)`), border 1px `rgb(222,219,210)` / `rgb(236,212,162)`, radius 4px, shadow none, 위 padding 24px, Header·Content 좌우 24px, Header→Content 24px, overflow visible, Title 20px/28px/600 `rgb(10,34,29)`, column-gap 20px, Action 교차축 center, Action–Title 간격 20px — 문서 값과 일치.
- Compatibility: Footer 아래 모서리 3px, `size="sm"` 위 padding 12px·Title 14px 확인.
- Demo(App): 4개 Card가 border 1px/radius 4px/shadow none, 위 padding 16px, row-gap 0으로 렌더 확인. 이 값은 Demo `.hm-panel` 결과이며 운영 Card 값이 아니다(§8.1).
- 2026-10-08 문서 정정: Border Token 연결(`card.css` ↔ `default.css` ↔ `contract.ts` ↔ `themes/default.json`), CardDescription 사용처, Demo class 규칙을 코드와 대조했다. `card.tsx`·`card.css`는 주석만 고쳤고 CSS 값·Component 동작·공개 API·Story는 바꾸지 않았다.
- Story: `All` + verification 3개 ID 확인, 기본 sidebar는 `All` 하나만 보이는 1뎁스 구조(실제 사이드바 화면은 확인하지 못함).
- 미확인: 사이드바 실제 화면, 브라우저별 렌더, Demo 화면 시각 비교(그림자 제거로 달라짐), 이미지 포함 Card.