193 lines
18 KiB
Markdown
193 lines
18 KiB
Markdown
# 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, 디자인 값 변경 없음) |
|
||
| 기반 | 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 사용자 결정**: 없음.
|
||
- **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 / 600, `--hm-sem-fg-primary` | CANONICAL_OVERRIDE |
|
||
| 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/600으로 변경(`size="sm"` 제외).
|
||
- 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/600 등)과 대표 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 / 600 |
|
||
| `.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. 사용자 확정 결정
|
||
|
||
추가 사용자 결정 없음.
|
||
|
||
## 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.
|