Files
HMDesign/docs/components/card.md
T

18 KiB
Raw Blame History

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 대표 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 범위 계약을 따른다.

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.