Files
HMDesign/docs/guidelines/hm-custom-implementation.md
T

271 lines
24 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.
# HM Custom Implementation Inventory
> 조사 기준일: 2026-10-06. 실제 코드(`packages/ui/src`) 기준의 현황 기록이며 확정 규격이 아니다. 구현은 바꾸지 않았다.
> 신규 구현은 [AI/Component 구현 원칙](shadcn-기반-구현-원칙.md)과 [Navigation Single Mode Policy](navigation-single-mode.md)의 2026-10-07 확정 정책을 따른다. 아래 조사 이력은 운영 예제나 정책 예외를 허용하는 근거가 아니다.
> Select의 `UNDER_REVIEW`는 2026-10-06 조사 당시 설계 평가이며 현재 작업 진행 상태를 뜻하지 않는다. 현재 API·동작은 `docs/components/select.md`와 실제 구현·대표 Story를 따른다.
> **현재 탐색 기준 (2026-10-08 개발 저장소)**: UI 구현 TSX 44개, 공개 Component 모듈 44개(222 named export), Component Story 파일/그룹 44개, Component 문서 45개(`field.md`는 독립 구현이 아닌 소비 가이드)다. Pattern은 소스 디렉터리/공개 모듈 8개(62 named export), Story 파일/그룹 6개, Pattern 문서 7개다. 확정 배포 기준 `HMDesignTest.zip`에는 Component 구성은 동일하고 Pattern 모듈 7개(50 named export)가 포함되며 Dashboard Status 모듈은 없다. 파일·심볼·그룹은 서로 다른 집계 단위다. 현재 경로 연결은 [Component / Pattern Reference Index](component-pattern-reference.md)를 따른다. 아래 표와 분석은 2026-10-06 당시의 분류 기록이다.
## 0. 한 줄 요약
shadcn/Radix를 그대로 쓰지 않고 **실제로 API·State·Behavior·Structure가 추가된 Component는 44개 중 15개**다. 나머지 29개는 shadcn 구조를 유지하고 CSS/Token만 얹었다. Pattern 7종은 모두 기존 Component의 조합이다.
| 질문 | 답 |
|---|---|
| 꼭 필요한가? | Component 기준 15개 중 `REQUIRED` 13, `CAN_SIMPLIFY` 1(Textarea), `UNDER_REVIEW` 1(Select). 항목 단위로는 `CAN_SIMPLIFY` 3건, `LEGACY` 1건(Button alias) (§4, §11, §12) |
| Public API에 영향을 주는가? | 12건이 consumer에게 노출된다 (§8). 내부 전용은 6건 (§7) |
| shadcn 업데이트 시 관리해야 하는가? | **Tabs(HIGH)** 우선, 이어서 Button·Select(MEDIUM). 나머지는 LOW |
> 위 숫자는 아래 표의 합이다. 표를 고치면 이 요약도 함께 고친다.
## 1. 목적
- shadcn 업데이트·재생성 시 "무엇을 손으로 지켜야 하는가"를 한 문서에서 확인한다.
- HM 스타일이 들어갔다는 이유만으로 Custom으로 부르지 않는다. Behavior / Structure를 유지하고 CSS/Token만 바꾼 것은 `HM_THEME`다.
- 이 문서는 가시화·분류·유지보수 기준만 다룬다. 제거·단순화 판단은 별도 작업이다.
- Visual 값(height, radius, color 등)은 각 `docs/components/<name>.md`, `docs/patterns/<name>.md`가 담당하므로 여기에 나열하지 않는다.
## 2. 분류 기준
| 분류 | 정의 |
|---|---|
| `SHADCN_BASE` | shadcn/Radix 구조·API 그대로. `.tsx`에 HM 전용 코드가 없고 Theme CSS도 얇은 Token 매핑 수준 |
| `HM_THEME` | Behavior / Structure는 shadcn/Radix 그대로, Visual만 HM CSS / Token. 독립 Custom 아님 |
| `HM_CUSTOM` | shadcn에 없는 Public API, 상태/behavior, Radix 동작 확장, 추가 Context/state, helper logic, 구조 확장 중 하나 이상 |
| `HM_COMPOSITION` | 기존 HM/shadcn Component를 조합한 Pattern |
| `STORY_ONLY` | 검증용 Story, StateBoard 등. Production Custom 아님 |
조사 한계(반드시 읽을 것):
- 현재 저장소는 Git으로 관리한다. 2026-10-06 조사는 shadcn 원본 사본과의 Git diff를 수행하지 않았다. 따라서 당시 분류는 "shadcn 원본과의 diff"가 아니라 **현재 코드와 코드 주석을 읽고 shadcn v4(Radix) 구조에 대한 지식과 대조**해 판정했다. 경계 항목은 `shadcn add`로 원본을 받아 diff하면 확정할 수 있다.
- `SHADCN_BASE`와 `HM_THEME`의 경계는 휴리스틱이다: `.tsx`가 shadcn 그대로이면서 `packages/ui/src/theme/<name>.css`가 약 70줄 이하이면 `SHADCN_BASE`, 그보다 크면 `HM_THEME`. 둘 다 "Custom 아님"이므로 유지보수 판단에는 영향이 없다.
- Story는 전수 검토하지 않고 공용 helper와 파일 구성만 확인했다.
## 3. 전체 요약
| Category | Count | 비고 |
|---|---:|---|
| Component 총계 | 44 | `packages/ui/src/components/ui/` |
| `SHADCN_BASE` | 16 | §5 |
| `HM_THEME` | 13 | §5 |
| `HM_CUSTOM` | 15 | §4 (Public 12건 + Internal 6건이 이 15개에 걸쳐 있음) |
| `HM_COMPOSITION` | 7 | Pattern (§6, Component 44개와 별도) |
| `STORY_ONLY` | 4 묶음 | §9 |
## 4. Components — HM_CUSTOM
한 Component 안에 Visual(HM_THEME)과 구조 Custom이 함께 있을 수 있다. 아래는 구조·API·상태 부분만 적었다. 평가: `REQUIRED` 현재 필요 / `CAN_SIMPLIFY` shadcn 방식으로 단순화 가능 / `LEGACY` 필요성 불명확 / `UNDER_REVIEW` 작업 중·미결정.
| Component | Base | Custom 종류 | Scope | 평가 | Risk |
|---|---|---|---|---|---|
| Button | shadcn + Radix Slot + CVA | API, Structure | Public | REQUIRED (+ alias는 LEGACY) | MEDIUM |
| Input | shadcn | API | Public | REQUIRED | LOW |
| Textarea | shadcn | API | Public | CAN_SIMPLIFY | LOW |
| Card | shadcn | API | Public | REQUIRED | LOW |
| Select | Radix Select | API, State, Structure | Public + Internal | UNDER_REVIEW | MEDIUM |
| Tabs | Radix Tabs | API, State, Behavior, Structure | Public + Internal | REQUIRED | HIGH |
| Dialog | Radix Dialog | API(Structure), Behavior | Public + Internal | REQUIRED | LOW |
| Sheet | Radix Dialog | API(Structure), Behavior | Public + Internal | REQUIRED | LOW |
| Drawer | vaul | API(Structure), Behavior | Public + Internal | REQUIRED | LOW |
| Alert Dialog | Radix Alert Dialog | API | Public | REQUIRED | LOW |
| Combobox | Popover + Command (cmdk) | API, State, Composition | Public | REQUIRED | LOW |
| Date Picker | Popover + Calendar (react-day-picker) | API, State, Behavior | Public + Internal | REQUIRED | LOW |
| Popover | Radix Popover | Behavior | Internal | REQUIRED | LOW |
| Collapsible | Radix Collapsible | Behavior | Internal | REQUIRED | LOW |
| Slider | Radix Slider | Behavior | Internal | REQUIRED | LOW |
### 4.1 항목별 기록
**Button** — `button.tsx`, `theme/button.css`
- 추가: `variant`를 역할(`primary|secondary|modify|destructive|cancel`)로 교체, `appearance`(`brand|solid|outline`) 추가, `data-variant/data-appearance/data-size`. `default`·`outline`은 shadcn 호환 alias로 `primary`/`primary+outline`에 매핑, `ghost`·`link`는 shadcn 기본 유지.
- 이유: ui_viz 역할/처리(treatment) 2축 규격. `<Button />`만으로 HM 디자인 적용.
- Public API 영향: 있음(`variant` 값 집합, `appearance`). 기존 shadcn `variant="default|outline"` 코드는 alias로 동작.
- 구조 주의: `data-slot="button"`을 `{...props}` **뒤에** 둔다. 앞에 두면 `<XTrigger asChild><Button/></XTrigger>`가 trigger의 `data-slot`으로 덮어써 HM 스타일이 사라진다(§7 I3).
- 대체 가능한 shadcn 방식: shadcn `variant` 값에 HM 역할을 추가하는 방식은 가능하나 `appearance` 2축은 표현 불가.
- 리스크 사유: `buttonVariants` CVA 정의를 통째로 다시 썼다. shadcn이 `buttonVariants`를 바꾸면 수동 병합이 필요하다. 코드에 `dark:` 잔재 2곳(§11).
- 유지 필요성: REQUIRED. 단, `default`/`outline` alias는 LEGACY(호환 목적이며 소비처가 정리되면 제거 후보).
**Input** — `input.tsx`
- 추가: `size="small|medium|large"`(기본 large). 네이티브 `size`(문자 폭) 속성은 `Omit`으로 가려져 **사용 불가**.
- Public API 영향: 있음, 네이티브 속성 가림. 대체: `className` 높이 지정(2축 상태 규칙은 불가). 유지 REQUIRED. Risk LOW.
**Textarea** — `textarea.tsx`
- 추가: `resize="vertical|none|both"`(기본 vertical)를 `data-resize`로 노출. readonly→none은 CSS에서 처리.
- 대체: `className="resize-none"` 등. 기본 정책을 컴포넌트가 갖게 하려는 목적이라 단순화 가능. CAN_SIMPLIFY, Risk LOW.
**Card** — `card.tsx`
- 추가: `surface="surface|content"`(`data-surface`). `size="sm"`은 shadcn 기존 API.
- 유지 REQUIRED(ui_viz Content Card 면 구분). Risk LOW.
**Select** — `select.tsx`, `theme/select.css` (size 계약 구현됨; 구조 선택은 아래 UNDER_REVIEW)
- 추가(조사 시점 코드):
- `SelectTrigger`의 `size="medium|large"`(shadcn `sm|default` 대체), `data-size`.
- `SelectSizeContext` + `Select` Root를 Provider로 감싸 `useState`로 size 보관, Trigger가 `useLayoutEffect`로 등록. Portal 안의 `SelectContent`가 `data-size`를 읽는다. 즉 **Root가 shadcn처럼 순수 `SelectPrimitive.Root`가 아니라 Provider 래퍼**다.
- 아이콘을 Radix 기본 대신 18×18 SVG로 교체, `SelectItem`의 check indicator 제거(선택 표시는 배경 면), `SelectContent` 기본값 `position="popper"`, `sideOffset=-1`(border 1px 공유 seam).
- long text ellipsis와 seam(`--hm-select-seam-color`)은 CSS 처리(`select.css`).
- Public API 영향: `size` prop(Public). Context·`setSize`는 노출되지 않는 Internal.
- 대체 가능한 shadcn 방식: shadcn은 Content가 `size`를 몰라도 되게 Trigger 쪽만 `data-size`를 둔다. Content까지 size 연동이 필요한 이유가 Portal 때문이므로 대안은 (a) Content에도 `size` prop을 직접 전달, (b) CSS만으로 처리, (c) 현 Context 유지. **어느 쪽이 맞는지 결정되지 않았다.**
- 유지 필요성: UNDER_REVIEW. Risk MEDIUM(Root 래퍼 + 렌더 후 `useLayoutEffect`로 state를 갱신하는 구조라, 첫 렌더에서 Content가 기본 `large`로 보일 수 있는 타이밍 의존이 있음).
- 이 항목은 Final Spec이 아니다.
**Tabs** — `tabs.tsx`, `theme/tabs.css`
- 추가:
- `TabsList`에 `fill`(동일 폭·초록 선택 border·곡선 연결)과 `panelSurface="surface|content"` prop.
- `TabsList`가 Radix List를 `<div data-slot="tabs-frame">`로 **한 겹 감싼다**(DOM 구조 변경).
- 넘침 감지(`ResizeObserver`, `MutationObserver`, scroll 리스너, `document.fonts.ready`), 이전/다음 이동 버튼(`aria-label` 한국어 고정), 선택 탭 가시 유지(`keepSelectedVisible`), `prefers-reduced-motion` 분기, `useId`로 List id 생성, 외부 `ref` 병합.
- 연결 곡선은 `tabs.css`의 `::before/::after` + `radial-gradient`(HM 고유 구조 CSS).
- Public API 영향: `fill`, `panelSurface`(Public). 나머지는 Internal.
- 대체: shadcn Tabs는 위 기능이 전혀 없다. 넘침 버튼 없이 가로 스크롤만 허용하는 단순화는 가능하나 ui_viz D-090 규격을 포기해야 한다.
- 유지 REQUIRED. **Risk HIGH**: 원본 `TabsList` 구조를 크게 바꿨고 Observer 기반 로직과 한국어 문구가 컴포넌트 안에 있다.
- 2026-10-06: 대표 형태를 Primary(`fill` + `panelSurface="content"`)·Secondary(`fill` + `panelSurface="surface"`) 두 가지로 정리했다. Story 노출만 줄였고 코드·API·CSS branch는 그대로라 **Custom complexity 변화 없음**. non-fill branch는 REMOVE_CANDIDATE(사용자 확정 대기).
- 2026-10-08 추가 개선 E 승인: 당시 `fill=false`, `panelSurface="surface"` 기본값을 현재 `fill=true`, `panelSurface="content"`(Primary)로 변경했다. Props 이름·타입·기존 CSS 디자인 값·선택자·Observer 동작은 유지하므로 **Custom complexity 변화 없음**. Legacy non-fill은 `fill={false}`로 유지하고 OLD_TEST_ONLY / REMOVE_CANDIDATE 삭제 대기 이력도 보존한다. 외부 기본 렌더링·명시적 Props 조합·CSS Override 영향은 [Tabs 문서 §15](../components/tabs.md#15-외부-소비자-영향--호환성-2026-10-08)를 따른다. 외부 소비자 전체 호환성은 미검증이다.
**Dialog / Sheet / Drawer** — `dialog.tsx`, `sheet.tsx`, `drawer.tsx`
- 추가(Public): `DialogBody`, `SheetBody`, `DrawerBody` — 스크롤 소유 영역(Header/Footer 고정, Body만 스크롤). shadcn에는 없는 구조 API.
- 추가(Internal): `*Close`가 `asChild`일 때 `data-slot`을 넘기지 않는 규칙(§7 I3).
- Drawer 추가: `autoFocus` 기본값을 `true`로(vaul 기본은 `false`라 modal에서 포커스가 trigger에 남음). 소비자가 `autoFocus={false}`로 되돌릴 수 있다 → Public 동작 차이.
- `showCloseButton`, `side`는 shadcn 기존 API(Custom 아님). Radius·shadow 등은 HM_THEME.
- 대체: Body는 `className`이 붙은 `div`로 대체 가능하나 `data-slot` 기반 CSS 선택이 사라진다. REQUIRED, Risk LOW.
**Alert Dialog** — `alert-dialog.tsx`
- 추가: `AlertDialogAction`/`AlertDialogCancel`이 `variant|appearance|size`를 받고 `Button asChild`로 합성. 기본값 Action=`primary/solid`, Cancel=`cancel/solid`. shadcn도 Button 스타일을 입히지만 `appearance`는 HM 전용.
- REQUIRED, Risk LOW.
**Combobox** — `combobox.tsx`
- shadcn에는 파일이 아닌 recipe(Popover+Command)만 있고 HM은 이를 **`options` 기반 컴포넌트**로 만들었다. Public: `options`(value/label/disabled), `value/defaultValue/onValueChange`, `placeholder/searchPlaceholder/emptyText`, `size="medium|large"`. State: `open`, 비제어용 `inner`(제어/비제어 분기 직접 구현). 한국어 기본 문구 포함.
- 대체: Popover+Command를 소비자가 직접 조합(shadcn 방식). REQUIRED(서비스 공통 필요), Risk LOW(원본 파일이 없어 업스트림 충돌 없음).
- 내부: `PopoverContent`에 인라인 `--hm-popover-padding/width` 주입 → CAN_SIMPLIFY(§11).
**Date Picker** — `date-picker.tsx`
- 같은 성격. Public: `value/defaultValue/onValueChange`, `placeholder`, `size`, `format`, `calendarProps`(Calendar 옵션 일부). 제어/비제어 상태 직접 구현. Behavior: `onOpenAutoFocus`에서 Calendar의 tab stop 날짜로 포커스 이동(Internal). 한국어 기본 포맷.
- 대체: Popover+Calendar 직접 조합. REQUIRED, Risk LOW.
**Popover / Collapsible** — `popover.tsx`, `collapsible.tsx`
- `PopoverClose`, `CollapsibleTrigger`의 `asChild` data-slot 규칙만 추가(§7 I3). Risk LOW.
**Slider** — `slider.tsx`
- 추가: `aria-label`을 Root가 아니라 **각 Thumb**에 전달(Range는 `"라벨 1/2"`). `role="slider"`가 Thumb에 있기 때문. Internal. shadcn 원본에는 없음. REQUIRED(접근성), Risk LOW.
## 5. Components — HM_THEME / SHADCN_BASE
`.tsx`에 HM 구조·API·State 추가가 없는 Component. 시각 값은 각 Component 문서가 관리한다.
**SHADCN_BASE (16)**: Accordion, Alert, Aspect Ratio, Avatar, Badge, Breadcrumb, Checkbox, Hover Card, Label, Progress, Radio Group, Scroll Area, Separator, Skeleton, Switch, Tooltip
**HM_THEME (13)**: Calendar, Carousel, Command, Context Menu, Dropdown Menu, Menubar, Navigation Menu, Pagination, Resizable, Sonner, Table, Toggle, Toggle Group
참고(Custom으로 보지 않은 이유):
- Alert·Badge: 사용 가능한 `variant`를 HM Theme에 있는 의미로 **줄였다**(Alert=default/destructive, Badge=4종). API 축소이며 추가가 아니다.
- Calendar: 기본 `locale`을 한국어(`ko`)로 두었다. shadcn도 `Chevron` 컴포넌트를 교체한다. 기본값 하나의 차이라 THEME로 둔다.
- Command: shadcn의 `CommandDialog`가 **없다**(의도적 제외, 코드 주석 명시). 기능 제거이며 추가 Custom 아님.
- Toggle Group: shadcn 그대로 Context를 쓰며 `spacing`을 CSS 변수(`--hm-toggle-group-gap`)로 넘기는 한 줄이 추가됐다.
- Carousel: shadcn 원본 로직(Context, Embla, 키보드)이 그대로다.
- Tooltip: 원본처럼 `Tooltip`이 자체 `TooltipProvider`를 가진다(shadcn v4).
## 6. Patterns — HM_COMPOSITION
`packages/ui/src/patterns/`. 모두 기존 HM Component의 조합이며 별도 primitive가 아니다. 전부 shadcn에 없는 **Public API**다(Pattern 이름 자체).
| Pattern | Composition | Custom Logic | Public API |
|---|---|---|---|
| GNB | Navigation Menu, Dropdown Menu, Avatar, Sheet, Button | 있음: `GNBMobileMenu`(open state, Context, Sheet 합성), `GNBMobileLink`가 `preventDefault`된 클릭에도 Sheet를 닫음, `GNBUser` 기본 `aria-label` | `GNB`(`sticky`), `GNBBrand`, `GNBNav`, `GNBUtilities`, `GNBUser`, `GNBMobileMenu`, `GNBMobileLink`, `GNBMobileGroup` |
| LNB | Collapsible, Slot | 없음(`LNBGroup`은 Collapsible 합성) | `LNB`, `LNBBrand`, `LNBNav`, `LNBItem`, `LNBGroup`, `LNBProfile` |
| Dashboard Layout | 현재는 Dashboard 내부 Header/Menu/Sidebar 조합만 허용 | 없음(레이아웃) | `DashboardLayout`(`header`, `sidebar`), `DashboardMain`, `DashboardContent` |
| Page Header | Breadcrumb·Button을 child로 받음 | 없음 | `PageHeader`, `PageHeaderContent/Title/Description/Actions` |
| Loading Screen | 없음(자체 요소) | 24개 막대 렌더, CSS 애니메이션·reduced-motion, `role="status"` | `LoadingScreen`(`fullscreen`) |
| Filter Bar | Input, Select·Combobox·Date Picker·Button을 child로 받음 | 있음: `FilterBarSearch`(검색 아이콘 + Input + 지우기 버튼, `aria-label` 기본값) | `FilterBar`, `FilterBarSearch`, `FilterBarActions` |
| Data Table | Table, Checkbox, Dropdown Menu, Pagination, Select, Skeleton, Button + TanStack Table v9 | 있음: 기능(feature) 등록, `useDataTable`, `dataTableSelectColumn`, `DataTableColumnHeader`(정렬 순환·`aria-sort`), `DataTableViewOptions`, `DataTablePagination`(`pageRange`), loading/empty 상태 | `DataTable`, `useDataTable`, `dataTableSelectColumn`, `DataTableColumnHeader`, `DataTableViewOptions`, `DataTablePagination` |
CSS 자체가 구조를 구현하는 항목(HM 고유): GNB 아래쪽 concave join과 current 항목 연결 곡선(`gnb.css`), LNB와 콘텐츠 join(`lnb.css`, `--hm-lnb-join`), Loading Screen 막대 애니메이션(`loading-screen.css`). 각 Navigation Mode는 독립 레이아웃을 소유하며 Dashboard에 외부 GNB/LNB를 넣지 않는다.
과거 조사에서는 GNB+LNB 동시 조립과 Brand ownership을 기록했으나 현재 단일 Navigation 정책으로 대체되었다. 혼합 Story는 기술 검증 목적의 `verification` 태그와 **VERIFICATION ONLY** 표시가 있을 때만 유지할 수 있으며 운영 Reference로 사용하지 않는다(`docs/patterns/gnb.md`, `lnb.md`).
## 7. Internal Custom
consumer에게 노출되지 않는 내부 구현. 6건.
| # | 항목 | 위치 | 평가 | Risk |
|---|---|---|---|---|
| I1 | Select size Context + `useLayoutEffect` 등록 (**작업 중일 수 있음**) | `select.tsx` | UNDER_REVIEW | MEDIUM |
| I2 | Tabs 넘침 측정(Observer·scroll·state) 과 이동 버튼 | `tabs.tsx` | REQUIRED | HIGH |
| I3 | `asChild` 시 `data-slot`을 child에 남기는 규칙(Dialog/Sheet/Drawer/Popover/Collapsible의 Close·Trigger, Button의 `data-slot` 후순위 배치) | 여러 파일 | REQUIRED | LOW |
| I4 | Slider thumb별 `aria-label` 전달 | `slider.tsx` | REQUIRED | LOW |
| I5 | Date Picker `onOpenAutoFocus` 날짜 포커스 | `date-picker.tsx` | REQUIRED | LOW |
| I6 | Combobox·Date Picker의 `PopoverContent` 인라인 `--hm-popover-*` 변수 | `combobox.tsx`, `date-picker.tsx` | CAN_SIMPLIFY | LOW |
## 8. Public API Custom
shadcn 원본에 없는 consumer-facing 확장. Component 12건 + Pattern 전체(§6).
| # | Component | HM API | shadcn 원본 | Risk |
|---|---|---|---|---|
| P1 | Button | `variant`(역할 5종+alias), `appearance` | `variant` 값이 다름 | MEDIUM |
| P2 | Input | `size="small|medium|large"` | 네이티브 `size` 가림 | LOW |
| P3 | Textarea | `resize` | 없음 | LOW |
| P4 | Card | `surface` | 없음(`size`는 있음) | LOW |
| P5 | SelectTrigger | `size="medium|large"` (**작업 중일 수 있음**) | `sm|default` | MEDIUM |
| P6 | TabsList | `fill`, `panelSurface` (+ 프레임 DOM) | 없음 | HIGH |
| P7 | Dialog | `DialogBody` | 없음 | LOW |
| P8 | Sheet | `SheetBody` | 없음 | LOW |
| P9 | Drawer | `DrawerBody`, `autoFocus` 기본 `true` | 없음 / vaul 기본 `false` | LOW |
| P10 | AlertDialogAction / Cancel | `appearance` (`variant`·`size` 포함) | `appearance` 없음 | LOW |
| P11 | Combobox | `options` 등 전체 API | 파일 없음(recipe) | LOW |
| P12 | Date Picker | `format`, `calendarProps` 등 전체 API | 파일 없음(recipe) | LOW |
## 9. Storybook-only Custom (`STORY_ONLY`)
Production Custom 목록에 포함하지 않는다. 4묶음.
| 묶음 | 위치 | 내용 |
|---|---|---|
| StateBoard helper | `src/components/story-board.tsx` | `BoardState`(`note`, `noteAfter`, `wide`), `StateBoard`. 39개 Story 파일이 사용 |
| 고정 상태·검증 Story | `src/components/*.stories.tsx`, `src/patterns/*.stories.tsx` | `States`, `StandardStates`, `ListSizeComparison` 등 비교·검증용 export. 전수 검토하지 않았음 |
| 샘플 데이터 | `src/patterns/sample-users.ts` | Data Table Story용 |
| Demo / CENTER Story | `src/hm-theme.css`, `src/theme/preview.ts`, `src/center/*` | Demo 스타일, preview 설정, `center-graphic` 전용 Story(UI 규격과 분리) |
## 10. External Library Base
| Library | 사용처 | 구분 |
|---|---|---|
| `radix-ui` | 대부분의 primitive | Base. Custom은 §4 |
| `class-variance-authority` | Button, Badge, Alert, Toggle | shadcn 구조. Button만 CVA 정의를 HM 값으로 교체 |
| `cmdk` | Command, Combobox | Library Base + HM Theme (Combobox는 HM_CUSTOM 합성) |
| `vaul` | Drawer | Library Base + HM Custom(`DrawerBody`, `autoFocus` 기본값) |
| `embla-carousel-react` | Carousel | Library Base + HM Theme (shadcn 로직 그대로) |
| `react-resizable-panels` | Resizable | Library Base + HM Theme |
| `react-day-picker` | Calendar, Date Picker | Library Base + HM Theme (Date Picker는 HM_CUSTOM 합성) |
| `sonner` | Sonner(Toast) | Library Base + HM Theme |
| `@tanstack/react-table` v9 | Data Table | Library Base + HM Composition (v9 feature 등록 방식) |
| `lucide-react` | 아이콘 전반 | shadcn 기본 |
## 11. CAN_SIMPLIFY 후보
평가만 한다. 변경은 하지 않았다.
| 항목 | 단순화 방향 | 비고 |
|---|---|---|
| Textarea `resize` (P3) | `className`(`resize-none` 등)로 대체 가능 | 기본 정책을 컴포넌트에 두려는 의도라면 유지 |
| Combobox·Date Picker 인라인 `--hm-popover-*` (I6) | CSS 선택자·`data-slot`으로 이동 | |
| `dark:` 잔재 | 현재 `button.tsx` 2개 행에만 남아 있고 `select.tsx`에는 없다. 기존 upstream `dark:` 분기는 보존할 수 있으며 새 HM Dark 분기는 추가하지 않는다 | HM_CUSTOM이 아니라 shadcn 원본 잔재. 정리 후보 |
## 12. UNDER_REVIEW / LEGACY
| 항목 | 상태 | 비고 |
|---|---|---|
| Select `size` Context (P5 + I1) | UNDER_REVIEW | 조사 당시 미결 설계 평가. 대안은 §4.1 Select 참조. 현재 작업 중이라는 뜻이 아니며 현재 API는 Select 문서·코드를 따른다 |
| Button `default`/`outline` alias (P1) | LEGACY | shadcn 호환용. 소비처 사용 여부 미확인 |
## 13. 유지 원칙
1. HM_THEME·SHADCN_BASE는 CSS/Token만 바꾼다. `.tsx`에 구조·상태를 추가하려면 `shadcn-기반-구현-원칙.md` §6의 Custom 허용 조건과 기록 요건을 확인하고 이 문서에 행을 추가한다.
2. shadcn 재생성·업데이트 시 우선 확인 순서: **Tabs → Button → Select → 나머지 Body/Close 규칙(I3)**. 이 파일들은 덮어쓰지 않고 수동 병합한다.
3. `data-slot`을 child에 남기는 규칙(I3)은 새 `asChild` 지원 컴포넌트에도 같은 방식으로 적용한다.
4. 새 Pattern은 §6 표에 "Composition / Custom Logic / Public API"를 한 행으로 기록한다.
5. 이 문서의 수치는 조사 시점(2026-10-06) 기준이다. Select 설계 평가가 변경되면 §4·§7·§8·§12를 함께 갱신한다.
## 2026-10-06 Theme infrastructure
Default/External 동일 Runtime 계약, 패키지 theme API, Semantic/팔레트 연결과 Accent derived 공통화는 HM_THEME / Theme infrastructure다. Component DOM/props/state/Portal/breakpoint를 바꾸지 않았으므로 위 HM_CUSTOM Inventory 수를 늘리지 않는다. 상세: [Runtime Theme](runtime-theme.md).