docs(ai): add packaged AI guides for Web, SW and OrgChart
- Shared entry point docs/ai/AI_GUIDE.md (type selection Web / SW / OrgChart / mixed, steps 1-7, package style separation, report format, examples A-D) and AGENTS_SNIPPET.md, copied into every package ai/ folder - New ai/ docs for @hm-design/ui and @hm-design/org-chart (+ org-chart-react pointer): AI_USAGE, COMPONENT_CATALOG, DESIGN_RULES, COMPOSITION_PATTERNS - SW ai/ linked to the shared guide; catalog lists every Props type explicitly - "ai" added to package files of ui, org-chart, org-chart-react - scripts/ai-docs.mjs: sync:ai-docs / check:ai-docs (copies, files, catalog <-> exports, links) - Maintenance guide and verification record under docs/ai
This commit is contained in:
1 parent
e8409eff44
commit
fb74cd756f
34 files changed
+2396
-6
No files matched your search
@@ -4,6 +4,11 @@
|
||||
|
||||
이 파일을 먼저 읽고 필요한 항목만 탐색한다. 전체 문서·전체 소스·전체 Storybook을 반복해서 읽지 않는다.
|
||||
|
||||
## 패키지 소비용 AI 지침 — 2026-10-19
|
||||
|
||||
- 이 파일은 HMDesign 저장소를 개발하는 AI용이다. 패키지를 설치한 **다른 프로젝트**의 AI가 읽는 지침은 각 패키지의 `ai/`(`packages/ui/ai`, `packages/sw/ai`, `packages/org-chart/ai`, `packages/org-chart-react/ai`)에 있고 공통 진입점 원본은 [docs/ai/AI_GUIDE.md](docs/ai/AI_GUIDE.md)다.
|
||||
- 공개 Export·Props·토큰을 바꾸면 같은 변경에서 해당 `ai/` 문서를 갱신하고 `npm run check:ai-docs`를 실행한다. 공통 가이드는 원본만 고친 뒤 `npm run sync:ai-docs`로 사본을 갱신한다. 절차는 [docs/ai/MAINTAINING.md](docs/ai/MAINTAINING.md).
|
||||
|
||||
## Typography — 공통 정책
|
||||
|
||||
- HMDesign의 Font Weight는 400(Regular), 500(Medium), 700(Bold)의 3단계만 사용한다. Font Weight 600은 사용하지 않으며, 카드 제목은 700을 기본으로 한다.
|
||||
|
||||
@@ -14,6 +14,7 @@ shadcn/ui(Radix, Tailwind v4, CVA) 기반 HM 디자인 시스템 시제품. Reac
|
||||
- [AI 조직도 자동 생성 정책](docs/org-chart/ai-page-generation-rules.md) — 자연어 또는 JSON·CSV·Excel에서 새 조직도 컴포넌트 또는 전체 페이지를 생성할 때 먼저 읽는다. 생성 방식 미지정 시 코드 작성 전에 위 정책의 사전 선택 질문으로 Chart Only / Full Page를 확인하고 답변 후 구현한다. 이미 명시한 방식은 다시 묻지 않는다. Chart Only는 흰색 캔버스·노드·연결선의 삽입용 컴포넌트, 최초 Fit·휠 Zoom·Pan과 외부 UI 제외를 따르고, Full Page는 기존 HM UI로 요청 UI·전체 배치·반응형을 구성한다. 두 방식 모두 가변 계층·정확한 인원·제공 데이터 보존·기존 Core/React 재사용을 따른다. Fixed·Auto/Expanded는 생성 방식 선택을 대신하지 않는다. Chart Only의 Navigation 예외와 Full Page의 HM 화면 계약은 `AGENTS.md`를 따른다. 기존 Full Composition·Expanded OrgChart·Department Colors·Overview·P9.5·P-AI1에는 소급 적용하지 않는다.
|
||||
- 일반 업무 화면은 Storybook에 등록하지 않고 별도 React 화면·독립 미리보기로 제공한다. Storybook 수정은 Component/Pattern 또는 명시적 Storybook Reference 작업에 한정한다. 상세한 Storybook / 화면 생성 분리는 `AGENTS.md`와 `docs/guidelines/shadcn-기반-구현-원칙.md`를 따른다.
|
||||
- `docs/guidelines/프로젝트-지침.md` — §6 컴포넌트·Storybook·CSS 작업 규칙 (모든 컴포넌트 작업 전 필독)
|
||||
- `docs/ai/MAINTAINING.md` — 패키지에 포함되는 소비 프로젝트용 AI 지침(`packages/*/ai/`, 공통 원본 `docs/ai/AI_GUIDE.md`). 공개 API·토큰 변경 시 같은 변경에서 갱신하고 `npm run check:ai-docs`를 실행한다.
|
||||
- `docs/guidelines/shadcn-기반-구현-원칙.md` — Component / Pattern / Story의 재사용 우선순위와 Custom 구현 허용 조건 (관련 작업 전 필독)
|
||||
- `docs/guidelines/테마-토큰-가이드.md` — Token 구조, 이름 규칙, HM Default/External Runtime 계약, Light/Dark 없음
|
||||
- `docs/guidelines/개발-적용-가이드.md`, `운영-배포-가이드.md`, `theme-delivery.md` — JSON 원본/Runtime URL Loader/독립 Theme 배포와 향후 실제 CDN·Publish의 경계
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
## HMDesign 디자인 시스템
|
||||
|
||||
<!-- HMDesign 패키지의 ai/AGENTS_SNIPPET.md에서 복사. 아래 "사용 유형"을 프로젝트에 맞게 고치거나, 정하지 않았으면 그 줄을 지운다. -->
|
||||
|
||||
HMDesign 사용 유형: Web | SW | OrgChart | 혼합(영역별로 적기)
|
||||
|
||||
화면·컴포넌트를 구현하거나 수정하기 전에 다음을 지킨다.
|
||||
|
||||
1. 설치된 HMDesign 패키지의 공통 가이드를 먼저 읽는다: `node_modules/@hm-design/<설치된 패키지>/ai/AI_GUIDE.md` (`ui`, `sw`, `org-chart`, `org-chart-react` 중 아무 사본이나 내용이 같다).
|
||||
2. 위 "사용 유형"이 없고 요청으로도 정할 수 없으면 코드 작성 전에 "어떤 HMDesign 디자인 시스템을 사용하시겠습니까? 1. Web 2. SW 3. OrgChart 4. 혼합 사용"을 묻는다. 정해져 있으면 다시 묻지 않는다.
|
||||
3. 선택한 패키지의 `ai/AI_USAGE.md` → `COMPONENT_CATALOG.md` → `DESIGN_RULES.md` → `COMPOSITION_PATTERNS.md` 순으로 필요한 절을 읽는다.
|
||||
- Web: `node_modules/@hm-design/ui/ai/`
|
||||
- SW: `node_modules/@hm-design/sw/ai/`
|
||||
- OrgChart: `node_modules/@hm-design/org-chart/ai/` (React는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`도)
|
||||
4. 기존 컴포넌트 → Props → 조합 → (없는 부분만) 패키지 토큰으로 신규 구현 순서를 지킨다. 패키지 파일 수정·내부 경로 import·구현 복사·새 디자인 값 생성은 하지 않는다.
|
||||
5. Web / SW / OrgChart 스타일을 영역별로 분리하고, 완료 후 AI_GUIDE의 결과 보고 형식으로 재사용·신규·토큰·검증·NOT VERIFIED를 보고한다.
|
||||
@@ -0,0 +1,213 @@
|
||||
# HMDesign AI 작업 가이드 — 공통 진입점
|
||||
|
||||
> 이 파일은 HMDesign 패키지(`@hm-design/ui`, `@hm-design/sw`, `@hm-design/org-chart`, `@hm-design/org-chart-react`)의 `ai/AI_GUIDE.md`에 **같은 내용으로** 들어 있습니다. 원본은 HMDesign 저장소 `docs/ai/AI_GUIDE.md`이며 패키지 사본은 `npm run sync:ai-docs`로 갱신합니다. 사본을 직접 고치지 않습니다.
|
||||
|
||||
사용자가 "HMDesign 패키지를 기준으로 화면을 구현해 주세요"처럼 요청하면 AI 코딩 도구는 이 파일의 1~6단계를 순서대로 따릅니다. 이 문서와 각 패키지의 `ai/` 문서는 실제 공개 API·CSS에서 확인한 내용만 적습니다. 여기에 없는 컴포넌트·Props·토큰은 없는 것으로 취급합니다.
|
||||
|
||||
## 0. 읽는 순서
|
||||
|
||||
1. **이 파일**(어느 패키지의 사본이든 같음)
|
||||
2. 선택한 패키지의 `ai/AI_USAGE.md` → `ai/COMPONENT_CATALOG.md` → `ai/DESIGN_RULES.md` → `ai/COMPOSITION_PATTERNS.md`
|
||||
3. API가 의심스러우면 그 패키지의 `dist/*.d.ts`(공개 진입점에서 export되는 것만)
|
||||
|
||||
| 유형 | 패키지 | 지침 위치(설치 후) | JS 진입점 | CSS |
|
||||
|---|---|---|---|---|
|
||||
| ① Web UI | `@hm-design/ui` | `node_modules/@hm-design/ui/ai/` | `@hm-design/ui`, `@hm-design/ui/theme` | `@hm-design/ui/tailwind.css` + `@hm-design/ui/theme.css`(Tailwind CSS 4 필요), 선택 `@hm-design/ui/dashboard-status.css` |
|
||||
| ② SW UI | `@hm-design/sw` | `node_modules/@hm-design/sw/ai/` | `@hm-design/sw` | `@hm-design/sw/styles.css` |
|
||||
| ③ OrgChart | `@hm-design/org-chart`(Core) + React면 `@hm-design/org-chart-react` | `node_modules/@hm-design/org-chart/ai/` (React 어댑터 안내는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`) | `@hm-design/org-chart`, `@hm-design/org-chart-react`, 선택 `@hm-design/org-chart-react/hm-dialog` | `@hm-design/org-chart/engine.css`, `@hm-design/org-chart/style.css`(URL로 전달 — Shadow DOM 안에 로드) |
|
||||
| 기타 | `@hm-design/center-graphic` | AI 지침 없음. 패키지 `README.md`만 | `@hm-design/center-graphic` | 자동 import |
|
||||
|
||||
모든 패키지는 서로 독립입니다(`@hm-design/org-chart-react`만 Core에 의존, `@hm-design/ui`는 `/hm-dialog`를 쓸 때만 선택 peer). React·React DOM `^19`는 peer입니다.
|
||||
|
||||
## 1단계. 사용 목적 확인
|
||||
|
||||
다음 순서로 판단하고, **판단되면 다시 묻지 않습니다.**
|
||||
|
||||
1. 소비 프로젝트의 `AGENTS.md`·`CLAUDE.md` 등에 `HMDesign 사용 유형:` 설정이 있으면 그대로 따릅니다(§9).
|
||||
2. 사용자 요청이 유형을 분명히 말하면 그대로 따릅니다.
|
||||
- Web: 웹 화면, 관리자·업무 화면, 목록·검색·Form, 대시보드, GNB/LNB, Dialog, Table …
|
||||
- SW: 데스크톱 SW, 뷰어, 도면·CAD, SW Header, 툴바, 레이어/색상/선스타일, 속성창·SW Popup …
|
||||
- OrgChart: 조직도, 조직 계층·직원 카드, 조직 JSON/CSV/Excel 시각화 …
|
||||
- 둘 이상이 함께 나오면 ④ 혼합(예: "업무 화면 안에 조직도" = Web + OrgChart).
|
||||
3. 위로 정할 수 없으면 코드 작성 전에 **그대로** 묻습니다.
|
||||
|
||||
> 어떤 HMDesign 디자인 시스템을 사용하시겠습니까?
|
||||
>
|
||||
> 1. Web
|
||||
> 2. SW
|
||||
> 3. OrgChart
|
||||
> 4. 혼합 사용
|
||||
|
||||
- `package.json`에 설치된 패키지가 하나뿐이어도 그것만으로 유형을 확정하지 않습니다(설치가 덜 된 상태일 수 있음). 요청 내용과 맞을 때만 근거로 씁니다.
|
||||
- 혼합을 고르면 영역별 패키지(예: "페이지 = Web, 가운데 영역 = OrgChart")를 확인합니다.
|
||||
- **OrgChart가 포함되고 생성 방식이 없으면** 같은 메시지에서 다음도 묻습니다(이미 말했으면 묻지 않음): "조직도만 기존 페이지에 삽입할까요(Chart Only), 아니면 레이아웃까지 구성한 새 페이지 전체를 생성할까요(Full Page)?" 상세는 `@hm-design/org-chart/ai/AI_USAGE.md` §1.
|
||||
- 선택한 패키지가 설치되어 있지 않으면 설치가 필요하다고 보고합니다. 다른 HMDesign 패키지나 임의 라이브러리로 대신하지 않습니다.
|
||||
|
||||
## 2단계. 패키지 분석
|
||||
|
||||
1. `node_modules/@hm-design/<패키지>/package.json`의 `version`·`exports`·`peerDependencies`를 확인합니다. `exports`에 없는 경로(`src/...`, `dist/components/...`)는 import하지 않습니다.
|
||||
2. 그 패키지의 `ai/` 문서를 §0 순서로 읽습니다. 필요한 절만 읽고 전체 `dist`를 훑지 않습니다.
|
||||
3. 카탈로그와 실제 `dist/index.d.ts`가 다르면 **`.d.ts`가 기준**입니다. 차이를 결과 보고에 적습니다.
|
||||
4. 소비 프로젝트의 기존 CSS 진입점·폰트·빌드 도구(Vite 등)·이미 쓰는 디자인 시스템을 확인합니다. 기존 프로젝트의 디자인 시스템을 바꾸지 않습니다.
|
||||
|
||||
## 3단계. 화면 분석
|
||||
|
||||
요청 화면을 항목으로 나누고 항목마다 판단을 기록합니다.
|
||||
|
||||
| 요청 항목 | 판단 | 근거(카탈로그의 실제 이름) |
|
||||
|---|---|---|
|
||||
| 예: 상단 메뉴 | 재사용 | Web `GNB` + `GNBMobileMenu` |
|
||||
| 예: 레이어 선택 | 재사용 | SW `SWHeaderLayerMenu` |
|
||||
| 예: 검색 상자(SW) | 신규 | SW 카탈로그에 없음 → `SWHeaderInput` 재사용 + SW 토큰 |
|
||||
|
||||
판단 값은 **재사용**(그대로) / **Props 설정**(기존 Variant·Props) / **조합**(기존 컴포넌트 배치) / **신규**(소비 프로젝트에 최소 구현) 중 하나입니다.
|
||||
|
||||
## 4단계. UI 구현
|
||||
|
||||
우선순위(위가 먼저):
|
||||
|
||||
1. 선택 패키지의 기존 컴포넌트·Pattern을 그대로 사용
|
||||
2. 기존 Variant / Props / Callback으로 해결
|
||||
3. 기존 컴포넌트 조합(배치용 CSS만 소비 프로젝트에 작성)
|
||||
4. 조합으로 안 되는 부분만 **소비 프로젝트에** 새로 구현 — 패키지 토큰만 읽고, 안쪽에 기존 컴포넌트를 재사용
|
||||
|
||||
규칙:
|
||||
|
||||
- `node_modules` 안 패키지 파일을 수정하지 않습니다. 패키지 `dist` 구현이나 내부 CSS 블록을 복사해 같은 컴포넌트를 다시 만들지 않습니다.
|
||||
- 이미 있는 컴포넌트와 같은 역할의 컴포넌트를 새로 만들지 않습니다(예: Web에 `Dialog`가 있는데 자체 Modal 작성 금지).
|
||||
- 새 디자인 값을 만들지 않습니다. 색·크기·간격·radius·그림자는 각 패키지 `DESIGN_RULES.md`의 토큰·확인값만 씁니다. 값이 없으면 추측하지 말고 사용자에게 확인합니다.
|
||||
- 공통: Font Weight는 400 / 500 / 700만 씁니다(600 없음). Light/Dark 분기를 만들지 않습니다. 새 컴포넌트 CSS에 `html`·`body`·`*` 전역 규칙을 넣지 않습니다. 새 class는 소비 프로젝트 접두사(예: `app-`)를 씁니다.
|
||||
- 요청에 없는 메뉴·통계·설명·다운로드·외부 API·AI 입력 UI를 추가하지 않습니다. 필요해 보이면 제안으로만 보고합니다.
|
||||
|
||||
### 없는 UI를 같은 스타일로 만들 때 확인할 항목
|
||||
|
||||
신규 항목마다 아래를 선택 패키지 `DESIGN_RULES.md`에서 찾아 그대로 적용합니다. 찾을 수 없는 항목은 "값 없음 — 사용자 확인 필요"로 보고합니다.
|
||||
|
||||
| 항목 | Web(`@hm-design/ui`) | SW(`@hm-design/sw`) | OrgChart |
|
||||
|---|---|---|---|
|
||||
| 토큰·CSS 변수 | `--hm-sem-*`, `--hm-color-*`, `--hm-radius-*`(`:root`) | `--hm-sw-header-*`, `--hm-sw-property-*`(`SWTheme` 범위) | `--hm-orgchart-*` 공개 입력(Shadow DOM 상속) |
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
| 동작·키보드 | Radix 동작 유지 | 카탈로그 "공통 키보드" | Core 키보드(Pan, Alt+M 이동 등) |
|
||||
|
||||
## 5단계. 패키지별 디자인 분리 (혼합 사용 포함)
|
||||
|
||||
각 패키지의 스타일 범위는 다음과 같습니다(실제 CSS 기준).
|
||||
|
||||
| 패키지 | 스타일이 적용되는 범위 | 주의 |
|
||||
|---|---|---|
|
||||
| Web | `theme.css`의 토큰은 `:root, [data-hm-theme]`. `tailwind.css`는 Tailwind base에서 `*`(border·outline), `body`(배경·글자), `html`(글꼴)을 **문서 전체에** 적용. 앱이 import하는 `tailwindcss`의 Preflight도 전역 | Web CSS를 넣은 문서에서는 다른 영역에도 전역 초기화가 적용됨 |
|
||||
| SW | 토큰은 `.hm-sw-header`와 `.hm-sw-theme`(`<SWTheme>`)에만 정의. 전역 선택자 없음 | Header 밖 SW 부품은 반드시 `<SWTheme>` 안 |
|
||||
| OrgChart | Core가 자기 open Shadow DOM에만 CSS 로드. 호스트 CSS selector는 안쪽에 닿지 않고 **Custom Property 상속만** 통과 | 호스트 class로 카드 모양을 바꾸려 하지 않음 |
|
||||
|
||||
혼합 규칙:
|
||||
|
||||
1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다.
|
||||
2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다.
|
||||
- SW `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`)
|
||||
- OrgChart Full Page의 바깥 레이아웃 = Web(또는 요청된 패키지)
|
||||
- OrgChart React의 직원 팝업 = 선택 `@hm-design/org-chart-react/hm-dialog`(Web `Dialog`, Web Theme CSS 필요)
|
||||
3. 패키지 class(`hm-sw-*`, `hm-orgchart-*`, Web `data-slot`)를 소비 코드에서 덮어쓰지 않습니다. 배치는 `className`·`style`과 소비 프로젝트 class로 합니다.
|
||||
4. **Web + SW를 한 문서에 함께 쓰면** Web의 전역 base(위 표)가 SW 화면에도 적용됩니다. SW는 자기 범위에서 버튼 등을 초기화하지만 이 조합의 시각 결과는 패키지에서 검증된 적이 없습니다(NOT VERIFIED). 이 조합이면 브라우저에서 SW 부품의 글꼴·테두리·배경을 직접 확인하고 차이를 보고합니다. 가능하면 Web CSS가 없는 SW 전용 화면(별도 entry)으로 분리합니다.
|
||||
5. Web Scoped Theme(`[data-hm-theme]`)는 body Portal(Select·Popover·DatePicker 팝업)에 전달되지 않습니다. Web 영역만 다른 테마로 바꾸려면 Global Theme를 쓰거나 차이를 보고합니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
|
||||
1. Typecheck(`tsc --noEmit` 등)와 Production Build
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW는 대상 창 폭(Header는 반응형 More 포함), OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지(§5)
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
```md
|
||||
## HMDesign 적용 결과
|
||||
- 사용 유형: Web | SW | OrgChart(Chart Only|Full Page) | 혼합(영역별: …)
|
||||
- 패키지·버전: @hm-design/ui 0.1.0, …
|
||||
- 재사용한 컴포넌트: …(Props 설정 포함)
|
||||
- 조합한 부분: …
|
||||
- AI가 새로 구현한 부분: 파일 경로 · 이유(카탈로그에 없음) · 안쪽에서 재사용한 컴포넌트
|
||||
- 적용한 디자인 토큰: --hm-… (신규 UI가 읽은 것)
|
||||
- 카탈로그와 .d.ts 차이: 없음 | …
|
||||
- 검증: Typecheck PASS · Build PASS · 브라우저(폭/상태) … · NOT VERIFIED: …
|
||||
- 제안(구현하지 않음): …
|
||||
```
|
||||
|
||||
## 8. 상황별 예시
|
||||
|
||||
### 예시 A — Web 화면 (목록 + 검색)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/ui/ai/` → COMPOSITION_PATTERNS "목록 화면"
|
||||
- 재사용: `LNB`(또는 `GNB`) 중 **하나**, `PageHeader`, `Card`, `FilterBar` + `FilterBarSearch` + `FilterBarActions`, `DataTable` + `useDataTable`, `Button`
|
||||
- 신규(필요 시만): 카탈로그에 없는 콘텐츠 블록 → 소비 프로젝트 컴포넌트, `--hm-sem-*` 토큰과 `Card` 재사용
|
||||
|
||||
```css
|
||||
/* src/index.css */
|
||||
@import "tailwindcss";
|
||||
@import "@hm-design/ui/tailwind.css";
|
||||
@import "@hm-design/ui/theme.css";
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { Button, Card, CardContent, FilterBar, FilterBarActions, FilterBarSearch, PageHeader, PageHeaderContent, PageHeaderTitle } from "@hm-design/ui"
|
||||
```
|
||||
|
||||
### 예시 B — SW 뷰어
|
||||
|
||||
- 읽기: `node_modules/@hm-design/sw/ai/` → COMPOSITION_PATTERNS "6. 파일 열기 + 도구 막대", 예제 `ai/examples/ExampleF-ViewerToolbar.tsx`
|
||||
- 재사용: `SWHeader`(`documentOpenPlacement="header"`, `controls`, `tools`), `SWToolGroup` + `SWToolButton`(Model/Layout 탭, Fit 등), `SWPopup` + `SWPopupSection` + `SWPopupRow` + `SWHeaderInput`(설정 Popup)
|
||||
- 신규: SW에 없는 기능(예: 검색) → 소비 프로젝트 컴포넌트, `<SWTheme>` 안, `--hm-sw-header-*` 토큰, 안쪽 입력은 `SWHeaderInput`(예제 `ExampleD-SearchExtension.tsx`)
|
||||
|
||||
```tsx
|
||||
import { SWHeader, SWPopup, SWTheme, SWToolButton, SWToolGroup } from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
```
|
||||
|
||||
### 예시 C — 조직도만 (Chart Only)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/org-chart/ai/` → COMPOSITION_PATTERNS "1. Chart Only"
|
||||
- 재사용: React `OrgChart`(`@hm-design/org-chart-react`) 또는 Vanilla `createOrgChart`, 데이터 `JsonOrgChartDataSource` + `validateOrgChartDataset`
|
||||
- 필수 옵션: `layoutMode="auto"` + `presentationMode="chart-only"`(초기 Fit·직접 휠 Zoom이 기본)
|
||||
- **추가하지 않음:** 페이지 제목·통계·설명·Zoom 버튼 바·외부 회색 배경·Navigation. Fixed Chart Only는 미지원(`TypeError`)
|
||||
|
||||
```tsx
|
||||
import { OrgChart } from "@hm-design/org-chart-react"
|
||||
import { JsonOrgChartDataSource } from "@hm-design/org-chart"
|
||||
import engineUrl from "@hm-design/org-chart/engine.css?url" // Vite. 다른 도구는 실제 배포 URL
|
||||
import coreUrl from "@hm-design/org-chart/style.css?url"
|
||||
```
|
||||
|
||||
### 예시 D — Web + OrgChart (Full Page)
|
||||
|
||||
- 영역: 페이지 레이아웃·제목·필터 = Web, 조직도 영역 = OrgChart
|
||||
- 읽기: Web `ai/` + OrgChart `ai/COMPOSITION_PATTERNS.md` "2. Full Page"
|
||||
- 재사용: Web `LNB`/`GNB` 중 하나, `PageHeader`, `Card`, `Select`·`Label`(회사 선택 등 요청된 컨트롤만) + OrgChart `OrgChart`(`selectedCompanyIds` 등 Props로 연결)
|
||||
- 직원 팝업을 Web Dialog로: `import { OrgChart } from "@hm-design/org-chart-react/hm-dialog"` + `employeePopup="hm-dialog"`(Web CSS 진입점 필요)
|
||||
- 분리: 조직도 카드·색은 OrgChart 기본 유지. Web 토큰을 조직도에 넣지 않음(바꿔야 하면 `--hm-orgchart-*` 공개 입력만)
|
||||
|
||||
## 9. 소비 프로젝트 설정 — AI가 이 지침을 찾게 하기
|
||||
|
||||
AI 코딩 도구는 보통 **프로젝트 루트의** `AGENTS.md`(Codex 등)·`CLAUDE.md`(Claude Code)·도구별 규칙 파일만 자동으로 읽고 `node_modules`는 읽지 않습니다. 패키지 설치만으로 지침이 자동 적용되지는 않으므로 소비 프로젝트에서 한 번 연결합니다.
|
||||
|
||||
1. 패키지에 들어 있는 `ai/AGENTS_SNIPPET.md`를 루트 `AGENTS.md`(없으면 새로 만듦)에 붙여 넣고 `HMDesign 사용 유형:` 줄을 프로젝트에 맞게 고칩니다. 정하지 않았으면 그 줄을 지워 AI가 1단계에서 묻게 합니다.
|
||||
```bash
|
||||
cat node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md >> AGENTS.md
|
||||
```
|
||||
(PowerShell: `Get-Content node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md | Add-Content AGENTS.md`. 설치한 아무 HMDesign 패키지의 사본이나 같습니다.)
|
||||
2. Claude Code는 `CLAUDE.md`에 `@AGENTS.md` 한 줄을 두면 같은 내용을 읽습니다. 다른 도구(Cursor 규칙 등)는 그 도구의 규칙 파일에 같은 내용을 넣습니다.
|
||||
3. 패키지를 업데이트한 뒤 스니펫이 바뀌었는지 확인합니다(스니펫은 경로 안내만 하므로 보통 그대로 둡니다).
|
||||
|
||||
자동으로 되지 않는 것: 패키지 설치 시 루트 파일 생성(`postinstall` 없음), 도구가 `node_modules`를 스스로 탐색하는 것. 위 연결이 없으면 사용자가 "node_modules/@hm-design/…/ai/AI_GUIDE.md를 먼저 읽어"라고 요청해야 합니다.
|
||||
|
||||
## 10. 문서 갱신 (HMDesign 저장소 유지보수자)
|
||||
|
||||
컴포넌트를 추가·수정하면 같은 변경에서 해당 패키지 `ai/COMPONENT_CATALOG.md`와 필요한 `DESIGN_RULES.md`·`COMPOSITION_PATTERNS.md`를 갱신하고 `npm run check:ai-docs`를 통과시킵니다. 절차는 저장소 `docs/ai/MAINTAINING.md`(패키지 미포함)에 있습니다.
|
||||
@@ -0,0 +1,52 @@
|
||||
# HMDesign 패키지 AI 지침 유지보수
|
||||
|
||||
이 문서는 HMDesign 저장소 유지보수자용이며 패키지에 포함되지 않습니다. 패키지에 포함되는 AI 지침은 소비 프로젝트의 AI 코딩 도구가 읽는 **실행 규칙**이므로, 코드와 다른 내용이 남지 않게 아래 절차로 함께 갱신합니다.
|
||||
|
||||
## 1. 구조
|
||||
|
||||
| 위치 | 역할 | 패키지 포함 |
|
||||
|---|---|---|
|
||||
| `docs/ai/AI_GUIDE.md` | 공통 진입점 원본(사용 유형 선택, 1~7단계, 패키지 분리, 예시 A~D, 소비 프로젝트 설정) | 사본으로 포함 |
|
||||
| `docs/ai/AGENTS_SNIPPET.md` | 소비 프로젝트 루트 `AGENTS.md`에 붙일 연결 문구 원본 | 사본으로 포함 |
|
||||
| `docs/ai/MAINTAINING.md` | 이 문서 | 미포함 |
|
||||
| `packages/ui/ai/` | Web: `AI_USAGE`, `COMPONENT_CATALOG`, `DESIGN_RULES`, `COMPOSITION_PATTERNS` | 포함(`files`) |
|
||||
| `packages/sw/ai/` | SW: 위 4종 + `examples/` | 포함 |
|
||||
| `packages/org-chart/ai/` | OrgChart(Core + React 어댑터 API): 위 4종 | 포함 |
|
||||
| `packages/org-chart-react/ai/` | `AI_USAGE`(Core `ai/` 안내) | 포함 |
|
||||
| `scripts/ai-docs.mjs` | 동기화(`--sync`)·검사(`--check`) | — |
|
||||
|
||||
`@hm-design/center-graphic`에는 AI 지침이 없습니다(공통 가이드에 "README만"으로 안내). 추가하려면 같은 4종 구조로 만들고 `scripts/ai-docs.mjs`의 `packages`에 등록합니다.
|
||||
|
||||
저장소 루트 `AGENTS.md`·`CLAUDE.md`는 **이 저장소를 개발하는 AI**용 규칙이고, `packages/*/ai/`는 **패키지를 설치해 쓰는 소비 프로젝트의 AI**용 규칙입니다. 내용이 겹쳐도 서로를 대신하지 않습니다. 패키지 `ai/` 문서는 패키지 밖(`../../docs/…`)을 링크하지 않습니다(설치 후 존재하지 않음).
|
||||
|
||||
## 2. 컴포넌트를 추가·수정할 때
|
||||
|
||||
같은 변경(같은 Commit)에서 처리합니다.
|
||||
|
||||
1. **공개 Export가 바뀌면** 해당 패키지 `ai/COMPONENT_CATALOG.md`에 이름·용도·HM Props(기본값)를 추가·수정·삭제합니다. 내부 전용 이름은 "공개하지 않은 것" 절에만 적습니다.
|
||||
2. **Props·Variant 기본값·허용 조합이 바뀌면** 카탈로그 표와 `COMPOSITION_PATTERNS.md`의 예시 코드를 함께 고칩니다.
|
||||
3. **토큰·CSS 값이 바뀌면** `DESIGN_RULES.md`의 표를 실제 CSS 선언과 맞춥니다(값은 CSS에서 복사, 추측 금지).
|
||||
4. **새 화면 유형·조합 규칙이 생기면** `COMPOSITION_PATTERNS.md` §0 표에 행을 추가합니다.
|
||||
5. 공통 규칙(유형 선택·혼합·보고 형식)이 바뀌면 `docs/ai/AI_GUIDE.md`만 고치고 `npm run sync:ai-docs`로 사본을 갱신합니다. 패키지 사본을 직접 고치지 않습니다.
|
||||
6. `npm run check:ai-docs`를 실행해 PASS를 확인합니다.
|
||||
7. 버전을 올리면 카탈로그 첫 줄의 기준 버전을 같이 바꿉니다.
|
||||
|
||||
## 3. `npm run check:ai-docs`가 검사하는 것
|
||||
|
||||
1. `docs/ai`의 공유 파일과 각 패키지 사본이 같은지
|
||||
2. 각 패키지 `package.json`의 `files`에 `ai`가 있는지
|
||||
3. 공개 진입점(`src/index.ts` 등, `export *` 재귀 포함)의 **모든 Export 이름이 카탈로그에 나오는지**
|
||||
4. 카탈로그에 Export처럼 보이지만(기존 Export 이름으로 시작) 실제로는 없는 이름이 없는지 — 예: `DialogFancyPanel`. "공개하지 않은 것"·"없는 것"·"지원하지 않는" 절은 제외
|
||||
5. `ai/` 문서의 상대 링크가 존재하고 패키지 밖(저장소 전용 문서)을 가리키지 않는지
|
||||
|
||||
검사하지 않는 것: Props 이름·기본값·토큰 값의 정확성, 예시 코드의 컴파일. 이 부분은 리뷰와 소비 프로젝트 검증(§4)으로 확인합니다.
|
||||
|
||||
## 4. 소비 프로젝트 검증 (배포 전 권장)
|
||||
|
||||
1. `npm run build:packages` 후 `npm pack -w <패키지> --ignore-scripts --pack-destination <임시 폴더>`
|
||||
2. 임시 소비 프로젝트(React 19 + Vite + Tailwind CSS 4)에 tarball 설치
|
||||
3. `node_modules/@hm-design/*/ai/AI_GUIDE.md`가 있는지, 링크가 설치 위치에서 열리는지 확인
|
||||
4. `COMPOSITION_PATTERNS.md`의 예시를 옮겨 `tsc --noEmit`·`vite build`·브라우저 렌더링 확인
|
||||
5. 결과와 NOT VERIFIED를 기록
|
||||
|
||||
`npm pack`의 `prepack`은 build를 다시 실행합니다. 공유 `dist`를 쓰는 build를 동시에 돌리지 않습니다.
|
||||
@@ -0,0 +1,28 @@
|
||||
# 패키지 AI 지침 구축 검증 기록 (2026-10-19)
|
||||
|
||||
범위: 공통 진입점(`docs/ai/AI_GUIDE.md` → 각 패키지 `ai/AI_GUIDE.md`), Web(`packages/ui/ai/`)·OrgChart(`packages/org-chart/ai/`, `packages/org-chart-react/ai/`) 신규 문서, SW 기존 `ai/` 연결, 패키지 `files`에 `ai` 추가, `scripts/ai-docs.mjs`. 런타임 코드·CSS·Storybook·공개 API 변경 없음.
|
||||
|
||||
## 실행한 검증
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| `npm run check:ai-docs` (사본 일치, `files`, 카탈로그 ↔ 공개 Export 양방향, `ai/` 링크) | PASS |
|
||||
| 검사기 음성 테스트(Export 누락, 가짜 이름 `DialogFancyPanel`, 사본 불일치, 패키지 밖 링크를 임시 주입) | 4건 모두 FAIL로 검출 후 원복, 재검사 PASS |
|
||||
| `npm run build:packages` | PASS |
|
||||
| `npm pack --ignore-scripts` 4종(ui 0.1.0, sw 0.2.0, org-chart 0.1.0, org-chart-react 0.1.0) | 모든 tarball에 `package/ai/AI_GUIDE.md`·`AGENTS_SNIPPET.md`와 패키지별 문서 포함 |
|
||||
| 독립 소비 프로젝트(React 19.3.0, Vite 8.3.0, Tailwind CSS 4.3.3, TypeScript 7.0.2)에 tarball 설치 | `node_modules/@hm-design/*/ai/` 4곳 존재, 설치 위치 기준 상대 링크 42개 모두 해석, `AI_GUIDE.md` 4사본 동일 |
|
||||
| `AI_GUIDE.md` §9의 스니펫 연결 명령(`cat … >> AGENTS.md`) | 동작 |
|
||||
| 문서만 근거로 구현: Web+OrgChart 페이지(LNB ONLY, PageHeader, 신규 StatCard = Card + 토큰, FilterBar + DataTable, AlertDialog, OrgChart Full Page + `/hm-dialog`), Chart Only 미리보기, SW 뷰어(SWHeader + SWToolGroup + SWPopup, 별도 entry) | `tsc --noEmit`(strict, skipLibCheck false) PASS, `vite build` PASS |
|
||||
| 브라우저(1440px) | HM 외형 렌더링(글꼴 Noto Sans KR, Card·Button 토큰 값), 조회 1건 → 초기화 3건, 삭제 확인창 열림·Escape 닫힘·Trigger Focus 복귀, 조직도 직원 카드 → Web Dialog 표시·Escape 닫힘, 회사 Select → `selectedCompanyIds` 반영, 콘솔 오류 0, 가로 넘침 없음 |
|
||||
| 브라우저(390px) | LNB Desktop 숨김·LNB Mobile Header 표시, 문서 가로 넘침 없음 |
|
||||
| Chart Only(1280px) | 흰 캔버스 + 노드 + 연결선만, 외부 제목·버튼·요약 없음, 중앙 Fit |
|
||||
| SW 뷰어 | Popup 열림·내부 Focus·Escape 닫힘·Trigger 복귀, `--hm-sw-header-*`가 `body`에는 없고 `.hm-sw-theme` 범위에만 존재 |
|
||||
|
||||
검증 프로젝트는 세션 임시 폴더에서 수행했고 저장소에 추가하지 않았다.
|
||||
|
||||
## NOT VERIFIED
|
||||
|
||||
- 다른 AI 코딩 도구(Codex·Cursor 등)가 소비 프로젝트의 `AGENTS.md`만 보고 스스로 `ai/` 문서를 찾아가는지. 이번 시나리오는 이 문서를 작성한 같은 AI가 수행했으므로 독립 Cold-start 검증이 아니다.
|
||||
- Web CSS와 SW를 **한 문서**에서 함께 쓰는 경우의 시각 결과(문서에 NOT VERIFIED로 명시, 별도 entry 권장).
|
||||
- 768px 화면, 실제 터치·스크린리더, Registry publish 후 설치.
|
||||
- 문서의 Props 기본값·토큰 값 전부를 기계적으로 대조하지는 않았다(검사기는 Export 이름·링크·사본만 검사). 이번 작성은 소스를 직접 확인했다.
|
||||
@@ -15,6 +15,8 @@
|
||||
"build:publisher-example": "vite build --config examples/publisher-list/vite.config.ts",
|
||||
"test:components": "node --test scripts/publisher-components.test.mjs",
|
||||
"test:guard": "node --test scripts/ai-generation-guard.test.mjs",
|
||||
"sync:ai-docs": "node scripts/ai-docs.mjs --sync",
|
||||
"check:ai-docs": "node scripts/ai-docs.mjs --check",
|
||||
"build": "tsc -b && vite build",
|
||||
"build:dashboard-export": "npm run check:theme-defaults && npm run build:packages && vite build --config vite.dashboard-export.config.ts && node scripts/finalize-dashboard-export.mjs",
|
||||
"h2:preview": "node scripts/h2-preview.mjs",
|
||||
|
||||
@@ -144,3 +144,7 @@ npm run build --workspace @hm-design/org-chart-react
|
||||
```
|
||||
|
||||
Storybook is one React host, not proof of every consumer deployment. The adapter delegates Auto Pan/Zoom to Core; it adds no alternate layout, additional relation overlays or API client. SSR/hydration, full screen reader behavior, physical touch, complete heap/GC recovery, HM Dialog without its Theme, Workdesk reference visual approval and every deployment environment require their own evidence. Default/Core rendering requires no HM Theme. Public ESM import without a DOM does not establish SSR/hydration support. Consult the minimal-install verification report for actual packed-package consumer results.
|
||||
|
||||
## AI coding tools
|
||||
|
||||
AI coding tools should read [`ai/AI_GUIDE.md`](./ai/AI_GUIDE.md) first (shared HMDesign entry point). [`ai/AI_USAGE.md`](./ai/AI_USAGE.md) points to the OrgChart rules, API catalog and patterns shipped in `@hm-design/org-chart/ai/`. `ai/` is included in the packed package.
|
||||
@@ -0,0 +1,16 @@
|
||||
## HMDesign 디자인 시스템
|
||||
|
||||
<!-- HMDesign 패키지의 ai/AGENTS_SNIPPET.md에서 복사. 아래 "사용 유형"을 프로젝트에 맞게 고치거나, 정하지 않았으면 그 줄을 지운다. -->
|
||||
|
||||
HMDesign 사용 유형: Web | SW | OrgChart | 혼합(영역별로 적기)
|
||||
|
||||
화면·컴포넌트를 구현하거나 수정하기 전에 다음을 지킨다.
|
||||
|
||||
1. 설치된 HMDesign 패키지의 공통 가이드를 먼저 읽는다: `node_modules/@hm-design/<설치된 패키지>/ai/AI_GUIDE.md` (`ui`, `sw`, `org-chart`, `org-chart-react` 중 아무 사본이나 내용이 같다).
|
||||
2. 위 "사용 유형"이 없고 요청으로도 정할 수 없으면 코드 작성 전에 "어떤 HMDesign 디자인 시스템을 사용하시겠습니까? 1. Web 2. SW 3. OrgChart 4. 혼합 사용"을 묻는다. 정해져 있으면 다시 묻지 않는다.
|
||||
3. 선택한 패키지의 `ai/AI_USAGE.md` → `COMPONENT_CATALOG.md` → `DESIGN_RULES.md` → `COMPOSITION_PATTERNS.md` 순으로 필요한 절을 읽는다.
|
||||
- Web: `node_modules/@hm-design/ui/ai/`
|
||||
- SW: `node_modules/@hm-design/sw/ai/`
|
||||
- OrgChart: `node_modules/@hm-design/org-chart/ai/` (React는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`도)
|
||||
4. 기존 컴포넌트 → Props → 조합 → (없는 부분만) 패키지 토큰으로 신규 구현 순서를 지킨다. 패키지 파일 수정·내부 경로 import·구현 복사·새 디자인 값 생성은 하지 않는다.
|
||||
5. Web / SW / OrgChart 스타일을 영역별로 분리하고, 완료 후 AI_GUIDE의 결과 보고 형식으로 재사용·신규·토큰·검증·NOT VERIFIED를 보고한다.
|
||||
@@ -0,0 +1,213 @@
|
||||
# HMDesign AI 작업 가이드 — 공통 진입점
|
||||
|
||||
> 이 파일은 HMDesign 패키지(`@hm-design/ui`, `@hm-design/sw`, `@hm-design/org-chart`, `@hm-design/org-chart-react`)의 `ai/AI_GUIDE.md`에 **같은 내용으로** 들어 있습니다. 원본은 HMDesign 저장소 `docs/ai/AI_GUIDE.md`이며 패키지 사본은 `npm run sync:ai-docs`로 갱신합니다. 사본을 직접 고치지 않습니다.
|
||||
|
||||
사용자가 "HMDesign 패키지를 기준으로 화면을 구현해 주세요"처럼 요청하면 AI 코딩 도구는 이 파일의 1~6단계를 순서대로 따릅니다. 이 문서와 각 패키지의 `ai/` 문서는 실제 공개 API·CSS에서 확인한 내용만 적습니다. 여기에 없는 컴포넌트·Props·토큰은 없는 것으로 취급합니다.
|
||||
|
||||
## 0. 읽는 순서
|
||||
|
||||
1. **이 파일**(어느 패키지의 사본이든 같음)
|
||||
2. 선택한 패키지의 `ai/AI_USAGE.md` → `ai/COMPONENT_CATALOG.md` → `ai/DESIGN_RULES.md` → `ai/COMPOSITION_PATTERNS.md`
|
||||
3. API가 의심스러우면 그 패키지의 `dist/*.d.ts`(공개 진입점에서 export되는 것만)
|
||||
|
||||
| 유형 | 패키지 | 지침 위치(설치 후) | JS 진입점 | CSS |
|
||||
|---|---|---|---|---|
|
||||
| ① Web UI | `@hm-design/ui` | `node_modules/@hm-design/ui/ai/` | `@hm-design/ui`, `@hm-design/ui/theme` | `@hm-design/ui/tailwind.css` + `@hm-design/ui/theme.css`(Tailwind CSS 4 필요), 선택 `@hm-design/ui/dashboard-status.css` |
|
||||
| ② SW UI | `@hm-design/sw` | `node_modules/@hm-design/sw/ai/` | `@hm-design/sw` | `@hm-design/sw/styles.css` |
|
||||
| ③ OrgChart | `@hm-design/org-chart`(Core) + React면 `@hm-design/org-chart-react` | `node_modules/@hm-design/org-chart/ai/` (React 어댑터 안내는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`) | `@hm-design/org-chart`, `@hm-design/org-chart-react`, 선택 `@hm-design/org-chart-react/hm-dialog` | `@hm-design/org-chart/engine.css`, `@hm-design/org-chart/style.css`(URL로 전달 — Shadow DOM 안에 로드) |
|
||||
| 기타 | `@hm-design/center-graphic` | AI 지침 없음. 패키지 `README.md`만 | `@hm-design/center-graphic` | 자동 import |
|
||||
|
||||
모든 패키지는 서로 독립입니다(`@hm-design/org-chart-react`만 Core에 의존, `@hm-design/ui`는 `/hm-dialog`를 쓸 때만 선택 peer). React·React DOM `^19`는 peer입니다.
|
||||
|
||||
## 1단계. 사용 목적 확인
|
||||
|
||||
다음 순서로 판단하고, **판단되면 다시 묻지 않습니다.**
|
||||
|
||||
1. 소비 프로젝트의 `AGENTS.md`·`CLAUDE.md` 등에 `HMDesign 사용 유형:` 설정이 있으면 그대로 따릅니다(§9).
|
||||
2. 사용자 요청이 유형을 분명히 말하면 그대로 따릅니다.
|
||||
- Web: 웹 화면, 관리자·업무 화면, 목록·검색·Form, 대시보드, GNB/LNB, Dialog, Table …
|
||||
- SW: 데스크톱 SW, 뷰어, 도면·CAD, SW Header, 툴바, 레이어/색상/선스타일, 속성창·SW Popup …
|
||||
- OrgChart: 조직도, 조직 계층·직원 카드, 조직 JSON/CSV/Excel 시각화 …
|
||||
- 둘 이상이 함께 나오면 ④ 혼합(예: "업무 화면 안에 조직도" = Web + OrgChart).
|
||||
3. 위로 정할 수 없으면 코드 작성 전에 **그대로** 묻습니다.
|
||||
|
||||
> 어떤 HMDesign 디자인 시스템을 사용하시겠습니까?
|
||||
>
|
||||
> 1. Web
|
||||
> 2. SW
|
||||
> 3. OrgChart
|
||||
> 4. 혼합 사용
|
||||
|
||||
- `package.json`에 설치된 패키지가 하나뿐이어도 그것만으로 유형을 확정하지 않습니다(설치가 덜 된 상태일 수 있음). 요청 내용과 맞을 때만 근거로 씁니다.
|
||||
- 혼합을 고르면 영역별 패키지(예: "페이지 = Web, 가운데 영역 = OrgChart")를 확인합니다.
|
||||
- **OrgChart가 포함되고 생성 방식이 없으면** 같은 메시지에서 다음도 묻습니다(이미 말했으면 묻지 않음): "조직도만 기존 페이지에 삽입할까요(Chart Only), 아니면 레이아웃까지 구성한 새 페이지 전체를 생성할까요(Full Page)?" 상세는 `@hm-design/org-chart/ai/AI_USAGE.md` §1.
|
||||
- 선택한 패키지가 설치되어 있지 않으면 설치가 필요하다고 보고합니다. 다른 HMDesign 패키지나 임의 라이브러리로 대신하지 않습니다.
|
||||
|
||||
## 2단계. 패키지 분석
|
||||
|
||||
1. `node_modules/@hm-design/<패키지>/package.json`의 `version`·`exports`·`peerDependencies`를 확인합니다. `exports`에 없는 경로(`src/...`, `dist/components/...`)는 import하지 않습니다.
|
||||
2. 그 패키지의 `ai/` 문서를 §0 순서로 읽습니다. 필요한 절만 읽고 전체 `dist`를 훑지 않습니다.
|
||||
3. 카탈로그와 실제 `dist/index.d.ts`가 다르면 **`.d.ts`가 기준**입니다. 차이를 결과 보고에 적습니다.
|
||||
4. 소비 프로젝트의 기존 CSS 진입점·폰트·빌드 도구(Vite 등)·이미 쓰는 디자인 시스템을 확인합니다. 기존 프로젝트의 디자인 시스템을 바꾸지 않습니다.
|
||||
|
||||
## 3단계. 화면 분석
|
||||
|
||||
요청 화면을 항목으로 나누고 항목마다 판단을 기록합니다.
|
||||
|
||||
| 요청 항목 | 판단 | 근거(카탈로그의 실제 이름) |
|
||||
|---|---|---|
|
||||
| 예: 상단 메뉴 | 재사용 | Web `GNB` + `GNBMobileMenu` |
|
||||
| 예: 레이어 선택 | 재사용 | SW `SWHeaderLayerMenu` |
|
||||
| 예: 검색 상자(SW) | 신규 | SW 카탈로그에 없음 → `SWHeaderInput` 재사용 + SW 토큰 |
|
||||
|
||||
판단 값은 **재사용**(그대로) / **Props 설정**(기존 Variant·Props) / **조합**(기존 컴포넌트 배치) / **신규**(소비 프로젝트에 최소 구현) 중 하나입니다.
|
||||
|
||||
## 4단계. UI 구현
|
||||
|
||||
우선순위(위가 먼저):
|
||||
|
||||
1. 선택 패키지의 기존 컴포넌트·Pattern을 그대로 사용
|
||||
2. 기존 Variant / Props / Callback으로 해결
|
||||
3. 기존 컴포넌트 조합(배치용 CSS만 소비 프로젝트에 작성)
|
||||
4. 조합으로 안 되는 부분만 **소비 프로젝트에** 새로 구현 — 패키지 토큰만 읽고, 안쪽에 기존 컴포넌트를 재사용
|
||||
|
||||
규칙:
|
||||
|
||||
- `node_modules` 안 패키지 파일을 수정하지 않습니다. 패키지 `dist` 구현이나 내부 CSS 블록을 복사해 같은 컴포넌트를 다시 만들지 않습니다.
|
||||
- 이미 있는 컴포넌트와 같은 역할의 컴포넌트를 새로 만들지 않습니다(예: Web에 `Dialog`가 있는데 자체 Modal 작성 금지).
|
||||
- 새 디자인 값을 만들지 않습니다. 색·크기·간격·radius·그림자는 각 패키지 `DESIGN_RULES.md`의 토큰·확인값만 씁니다. 값이 없으면 추측하지 말고 사용자에게 확인합니다.
|
||||
- 공통: Font Weight는 400 / 500 / 700만 씁니다(600 없음). Light/Dark 분기를 만들지 않습니다. 새 컴포넌트 CSS에 `html`·`body`·`*` 전역 규칙을 넣지 않습니다. 새 class는 소비 프로젝트 접두사(예: `app-`)를 씁니다.
|
||||
- 요청에 없는 메뉴·통계·설명·다운로드·외부 API·AI 입력 UI를 추가하지 않습니다. 필요해 보이면 제안으로만 보고합니다.
|
||||
|
||||
### 없는 UI를 같은 스타일로 만들 때 확인할 항목
|
||||
|
||||
신규 항목마다 아래를 선택 패키지 `DESIGN_RULES.md`에서 찾아 그대로 적용합니다. 찾을 수 없는 항목은 "값 없음 — 사용자 확인 필요"로 보고합니다.
|
||||
|
||||
| 항목 | Web(`@hm-design/ui`) | SW(`@hm-design/sw`) | OrgChart |
|
||||
|---|---|---|---|
|
||||
| 토큰·CSS 변수 | `--hm-sem-*`, `--hm-color-*`, `--hm-radius-*`(`:root`) | `--hm-sw-header-*`, `--hm-sw-property-*`(`SWTheme` 범위) | `--hm-orgchart-*` 공개 입력(Shadow DOM 상속) |
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
| 동작·키보드 | Radix 동작 유지 | 카탈로그 "공통 키보드" | Core 키보드(Pan, Alt+M 이동 등) |
|
||||
|
||||
## 5단계. 패키지별 디자인 분리 (혼합 사용 포함)
|
||||
|
||||
각 패키지의 스타일 범위는 다음과 같습니다(실제 CSS 기준).
|
||||
|
||||
| 패키지 | 스타일이 적용되는 범위 | 주의 |
|
||||
|---|---|---|
|
||||
| Web | `theme.css`의 토큰은 `:root, [data-hm-theme]`. `tailwind.css`는 Tailwind base에서 `*`(border·outline), `body`(배경·글자), `html`(글꼴)을 **문서 전체에** 적용. 앱이 import하는 `tailwindcss`의 Preflight도 전역 | Web CSS를 넣은 문서에서는 다른 영역에도 전역 초기화가 적용됨 |
|
||||
| SW | 토큰은 `.hm-sw-header`와 `.hm-sw-theme`(`<SWTheme>`)에만 정의. 전역 선택자 없음 | Header 밖 SW 부품은 반드시 `<SWTheme>` 안 |
|
||||
| OrgChart | Core가 자기 open Shadow DOM에만 CSS 로드. 호스트 CSS selector는 안쪽에 닿지 않고 **Custom Property 상속만** 통과 | 호스트 class로 카드 모양을 바꾸려 하지 않음 |
|
||||
|
||||
혼합 규칙:
|
||||
|
||||
1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다.
|
||||
2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다.
|
||||
- SW `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`)
|
||||
- OrgChart Full Page의 바깥 레이아웃 = Web(또는 요청된 패키지)
|
||||
- OrgChart React의 직원 팝업 = 선택 `@hm-design/org-chart-react/hm-dialog`(Web `Dialog`, Web Theme CSS 필요)
|
||||
3. 패키지 class(`hm-sw-*`, `hm-orgchart-*`, Web `data-slot`)를 소비 코드에서 덮어쓰지 않습니다. 배치는 `className`·`style`과 소비 프로젝트 class로 합니다.
|
||||
4. **Web + SW를 한 문서에 함께 쓰면** Web의 전역 base(위 표)가 SW 화면에도 적용됩니다. SW는 자기 범위에서 버튼 등을 초기화하지만 이 조합의 시각 결과는 패키지에서 검증된 적이 없습니다(NOT VERIFIED). 이 조합이면 브라우저에서 SW 부품의 글꼴·테두리·배경을 직접 확인하고 차이를 보고합니다. 가능하면 Web CSS가 없는 SW 전용 화면(별도 entry)으로 분리합니다.
|
||||
5. Web Scoped Theme(`[data-hm-theme]`)는 body Portal(Select·Popover·DatePicker 팝업)에 전달되지 않습니다. Web 영역만 다른 테마로 바꾸려면 Global Theme를 쓰거나 차이를 보고합니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
|
||||
1. Typecheck(`tsc --noEmit` 등)와 Production Build
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW는 대상 창 폭(Header는 반응형 More 포함), OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지(§5)
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
```md
|
||||
## HMDesign 적용 결과
|
||||
- 사용 유형: Web | SW | OrgChart(Chart Only|Full Page) | 혼합(영역별: …)
|
||||
- 패키지·버전: @hm-design/ui 0.1.0, …
|
||||
- 재사용한 컴포넌트: …(Props 설정 포함)
|
||||
- 조합한 부분: …
|
||||
- AI가 새로 구현한 부분: 파일 경로 · 이유(카탈로그에 없음) · 안쪽에서 재사용한 컴포넌트
|
||||
- 적용한 디자인 토큰: --hm-… (신규 UI가 읽은 것)
|
||||
- 카탈로그와 .d.ts 차이: 없음 | …
|
||||
- 검증: Typecheck PASS · Build PASS · 브라우저(폭/상태) … · NOT VERIFIED: …
|
||||
- 제안(구현하지 않음): …
|
||||
```
|
||||
|
||||
## 8. 상황별 예시
|
||||
|
||||
### 예시 A — Web 화면 (목록 + 검색)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/ui/ai/` → COMPOSITION_PATTERNS "목록 화면"
|
||||
- 재사용: `LNB`(또는 `GNB`) 중 **하나**, `PageHeader`, `Card`, `FilterBar` + `FilterBarSearch` + `FilterBarActions`, `DataTable` + `useDataTable`, `Button`
|
||||
- 신규(필요 시만): 카탈로그에 없는 콘텐츠 블록 → 소비 프로젝트 컴포넌트, `--hm-sem-*` 토큰과 `Card` 재사용
|
||||
|
||||
```css
|
||||
/* src/index.css */
|
||||
@import "tailwindcss";
|
||||
@import "@hm-design/ui/tailwind.css";
|
||||
@import "@hm-design/ui/theme.css";
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { Button, Card, CardContent, FilterBar, FilterBarActions, FilterBarSearch, PageHeader, PageHeaderContent, PageHeaderTitle } from "@hm-design/ui"
|
||||
```
|
||||
|
||||
### 예시 B — SW 뷰어
|
||||
|
||||
- 읽기: `node_modules/@hm-design/sw/ai/` → COMPOSITION_PATTERNS "6. 파일 열기 + 도구 막대", 예제 `ai/examples/ExampleF-ViewerToolbar.tsx`
|
||||
- 재사용: `SWHeader`(`documentOpenPlacement="header"`, `controls`, `tools`), `SWToolGroup` + `SWToolButton`(Model/Layout 탭, Fit 등), `SWPopup` + `SWPopupSection` + `SWPopupRow` + `SWHeaderInput`(설정 Popup)
|
||||
- 신규: SW에 없는 기능(예: 검색) → 소비 프로젝트 컴포넌트, `<SWTheme>` 안, `--hm-sw-header-*` 토큰, 안쪽 입력은 `SWHeaderInput`(예제 `ExampleD-SearchExtension.tsx`)
|
||||
|
||||
```tsx
|
||||
import { SWHeader, SWPopup, SWTheme, SWToolButton, SWToolGroup } from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
```
|
||||
|
||||
### 예시 C — 조직도만 (Chart Only)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/org-chart/ai/` → COMPOSITION_PATTERNS "1. Chart Only"
|
||||
- 재사용: React `OrgChart`(`@hm-design/org-chart-react`) 또는 Vanilla `createOrgChart`, 데이터 `JsonOrgChartDataSource` + `validateOrgChartDataset`
|
||||
- 필수 옵션: `layoutMode="auto"` + `presentationMode="chart-only"`(초기 Fit·직접 휠 Zoom이 기본)
|
||||
- **추가하지 않음:** 페이지 제목·통계·설명·Zoom 버튼 바·외부 회색 배경·Navigation. Fixed Chart Only는 미지원(`TypeError`)
|
||||
|
||||
```tsx
|
||||
import { OrgChart } from "@hm-design/org-chart-react"
|
||||
import { JsonOrgChartDataSource } from "@hm-design/org-chart"
|
||||
import engineUrl from "@hm-design/org-chart/engine.css?url" // Vite. 다른 도구는 실제 배포 URL
|
||||
import coreUrl from "@hm-design/org-chart/style.css?url"
|
||||
```
|
||||
|
||||
### 예시 D — Web + OrgChart (Full Page)
|
||||
|
||||
- 영역: 페이지 레이아웃·제목·필터 = Web, 조직도 영역 = OrgChart
|
||||
- 읽기: Web `ai/` + OrgChart `ai/COMPOSITION_PATTERNS.md` "2. Full Page"
|
||||
- 재사용: Web `LNB`/`GNB` 중 하나, `PageHeader`, `Card`, `Select`·`Label`(회사 선택 등 요청된 컨트롤만) + OrgChart `OrgChart`(`selectedCompanyIds` 등 Props로 연결)
|
||||
- 직원 팝업을 Web Dialog로: `import { OrgChart } from "@hm-design/org-chart-react/hm-dialog"` + `employeePopup="hm-dialog"`(Web CSS 진입점 필요)
|
||||
- 분리: 조직도 카드·색은 OrgChart 기본 유지. Web 토큰을 조직도에 넣지 않음(바꿔야 하면 `--hm-orgchart-*` 공개 입력만)
|
||||
|
||||
## 9. 소비 프로젝트 설정 — AI가 이 지침을 찾게 하기
|
||||
|
||||
AI 코딩 도구는 보통 **프로젝트 루트의** `AGENTS.md`(Codex 등)·`CLAUDE.md`(Claude Code)·도구별 규칙 파일만 자동으로 읽고 `node_modules`는 읽지 않습니다. 패키지 설치만으로 지침이 자동 적용되지는 않으므로 소비 프로젝트에서 한 번 연결합니다.
|
||||
|
||||
1. 패키지에 들어 있는 `ai/AGENTS_SNIPPET.md`를 루트 `AGENTS.md`(없으면 새로 만듦)에 붙여 넣고 `HMDesign 사용 유형:` 줄을 프로젝트에 맞게 고칩니다. 정하지 않았으면 그 줄을 지워 AI가 1단계에서 묻게 합니다.
|
||||
```bash
|
||||
cat node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md >> AGENTS.md
|
||||
```
|
||||
(PowerShell: `Get-Content node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md | Add-Content AGENTS.md`. 설치한 아무 HMDesign 패키지의 사본이나 같습니다.)
|
||||
2. Claude Code는 `CLAUDE.md`에 `@AGENTS.md` 한 줄을 두면 같은 내용을 읽습니다. 다른 도구(Cursor 규칙 등)는 그 도구의 규칙 파일에 같은 내용을 넣습니다.
|
||||
3. 패키지를 업데이트한 뒤 스니펫이 바뀌었는지 확인합니다(스니펫은 경로 안내만 하므로 보통 그대로 둡니다).
|
||||
|
||||
자동으로 되지 않는 것: 패키지 설치 시 루트 파일 생성(`postinstall` 없음), 도구가 `node_modules`를 스스로 탐색하는 것. 위 연결이 없으면 사용자가 "node_modules/@hm-design/…/ai/AI_GUIDE.md를 먼저 읽어"라고 요청해야 합니다.
|
||||
|
||||
## 10. 문서 갱신 (HMDesign 저장소 유지보수자)
|
||||
|
||||
컴포넌트를 추가·수정하면 같은 변경에서 해당 패키지 `ai/COMPONENT_CATALOG.md`와 필요한 `DESIGN_RULES.md`·`COMPOSITION_PATTERNS.md`를 갱신하고 `npm run check:ai-docs`를 통과시킵니다. 절차는 저장소 `docs/ai/MAINTAINING.md`(패키지 미포함)에 있습니다.
|
||||
@@ -0,0 +1,20 @@
|
||||
# @hm-design/org-chart-react — AI 코딩 도구 사용 안내
|
||||
|
||||
먼저 [AI_GUIDE.md](./AI_GUIDE.md)(공통 1~7단계)를 읽습니다. 이 패키지는 OrgChart Core의 **React 수명주기 어댑터**일 뿐이며 자체 디자인·레이아웃이 없습니다. OrgChart 작업 규칙·API·디자인은 Core 패키지의 `ai/` 폴더가 기준입니다.
|
||||
|
||||
| 읽을 문서 | 설치 후 위치 |
|
||||
|---|---|
|
||||
| OrgChart 작업 순서·생성 방식(Chart Only / Full Page)·데이터 규칙 | `node_modules/@hm-design/org-chart/ai/AI_USAGE.md` |
|
||||
| React `OrgChart` Props 전체 + Core API | `node_modules/@hm-design/org-chart/ai/COMPONENT_CATALOG.md` §1 |
|
||||
| 디자인·CSS 입력 | `node_modules/@hm-design/org-chart/ai/DESIGN_RULES.md` |
|
||||
| React·Vanilla 조합 예시 | `node_modules/@hm-design/org-chart/ai/COMPOSITION_PATTERNS.md` |
|
||||
|
||||
Core는 이 패키지의 runtime dependency이므로 함께 설치됩니다. 패키지 관리자가 중첩 설치한 경우 `node_modules/@hm-design/org-chart-react/node_modules/@hm-design/org-chart/ai/`에 있습니다.
|
||||
|
||||
## 이 패키지에서 확인할 것
|
||||
|
||||
- Entry: `@hm-design/org-chart-react`(runtime export `OrgChart` 하나, HM UI 불필요) / `@hm-design/org-chart-react/hm-dialog`(`OrgChart` + `HMEmployeeDialog`, `@hm-design/ui` 0.1.0 선택 peer와 Web CSS 필요).
|
||||
- 타입: `OrgChartProps`, `OrgChartEmployeeSelection`, `OrgChartEmployeePopupProps`와 Core 공개 타입(type-only 재export). `JsonOrgChartDataSource` 같은 runtime helper는 `@hm-design/org-chart`에서 import합니다.
|
||||
- Peer: `react` `^19.0.0`. `react-dom`은 앱이 제공합니다.
|
||||
- `"use client"` 경계가 유지됩니다. SSR/hydration은 검증 범위 밖입니다.
|
||||
- 공개 `ref`·imperative handle·`children`·`data`·`title` Props는 없습니다.
|
||||
@@ -17,7 +17,7 @@
|
||||
"default": "./dist/hm-dialog.js"
|
||||
}
|
||||
},
|
||||
"files": ["dist", "README.md"],
|
||||
"files": ["dist", "ai", "README.md"],
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"typecheck": "tsc -p tsconfig.json",
|
||||
|
||||
@@ -271,3 +271,7 @@ Enter로도 이동하며 Esc로 취소한다. Fixed에는 편집을 적용하지
|
||||
|
||||
데이터는 브라우저 인스턴스에만 유지한다. 회사 표시 선택은 이동 내용을 보존하고,
|
||||
새 source의 reload/update는 새 데이터로 대체한다. JSON 파일·서버 저장은 수행하지 않는다.
|
||||
|
||||
## AI coding tools
|
||||
|
||||
AI coding tools should read [`ai/AI_GUIDE.md`](./ai/AI_GUIDE.md) first (shared HMDesign entry point), then the OrgChart rules in [`ai/AI_USAGE.md`](./ai/AI_USAGE.md) (Chart Only / Full Page pre-selection, data rules), [`ai/COMPONENT_CATALOG.md`](./ai/COMPONENT_CATALOG.md) (Core and React API), [`ai/DESIGN_RULES.md`](./ai/DESIGN_RULES.md) and [`ai/COMPOSITION_PATTERNS.md`](./ai/COMPOSITION_PATTERNS.md). `ai/` is included in the packed package. To make a consumer project pick them up automatically, copy [`ai/AGENTS_SNIPPET.md`](./ai/AGENTS_SNIPPET.md) into its root `AGENTS.md`.
|
||||
@@ -0,0 +1,16 @@
|
||||
## HMDesign 디자인 시스템
|
||||
|
||||
<!-- HMDesign 패키지의 ai/AGENTS_SNIPPET.md에서 복사. 아래 "사용 유형"을 프로젝트에 맞게 고치거나, 정하지 않았으면 그 줄을 지운다. -->
|
||||
|
||||
HMDesign 사용 유형: Web | SW | OrgChart | 혼합(영역별로 적기)
|
||||
|
||||
화면·컴포넌트를 구현하거나 수정하기 전에 다음을 지킨다.
|
||||
|
||||
1. 설치된 HMDesign 패키지의 공통 가이드를 먼저 읽는다: `node_modules/@hm-design/<설치된 패키지>/ai/AI_GUIDE.md` (`ui`, `sw`, `org-chart`, `org-chart-react` 중 아무 사본이나 내용이 같다).
|
||||
2. 위 "사용 유형"이 없고 요청으로도 정할 수 없으면 코드 작성 전에 "어떤 HMDesign 디자인 시스템을 사용하시겠습니까? 1. Web 2. SW 3. OrgChart 4. 혼합 사용"을 묻는다. 정해져 있으면 다시 묻지 않는다.
|
||||
3. 선택한 패키지의 `ai/AI_USAGE.md` → `COMPONENT_CATALOG.md` → `DESIGN_RULES.md` → `COMPOSITION_PATTERNS.md` 순으로 필요한 절을 읽는다.
|
||||
- Web: `node_modules/@hm-design/ui/ai/`
|
||||
- SW: `node_modules/@hm-design/sw/ai/`
|
||||
- OrgChart: `node_modules/@hm-design/org-chart/ai/` (React는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`도)
|
||||
4. 기존 컴포넌트 → Props → 조합 → (없는 부분만) 패키지 토큰으로 신규 구현 순서를 지킨다. 패키지 파일 수정·내부 경로 import·구현 복사·새 디자인 값 생성은 하지 않는다.
|
||||
5. Web / SW / OrgChart 스타일을 영역별로 분리하고, 완료 후 AI_GUIDE의 결과 보고 형식으로 재사용·신규·토큰·검증·NOT VERIFIED를 보고한다.
|
||||
@@ -0,0 +1,213 @@
|
||||
# HMDesign AI 작업 가이드 — 공통 진입점
|
||||
|
||||
> 이 파일은 HMDesign 패키지(`@hm-design/ui`, `@hm-design/sw`, `@hm-design/org-chart`, `@hm-design/org-chart-react`)의 `ai/AI_GUIDE.md`에 **같은 내용으로** 들어 있습니다. 원본은 HMDesign 저장소 `docs/ai/AI_GUIDE.md`이며 패키지 사본은 `npm run sync:ai-docs`로 갱신합니다. 사본을 직접 고치지 않습니다.
|
||||
|
||||
사용자가 "HMDesign 패키지를 기준으로 화면을 구현해 주세요"처럼 요청하면 AI 코딩 도구는 이 파일의 1~6단계를 순서대로 따릅니다. 이 문서와 각 패키지의 `ai/` 문서는 실제 공개 API·CSS에서 확인한 내용만 적습니다. 여기에 없는 컴포넌트·Props·토큰은 없는 것으로 취급합니다.
|
||||
|
||||
## 0. 읽는 순서
|
||||
|
||||
1. **이 파일**(어느 패키지의 사본이든 같음)
|
||||
2. 선택한 패키지의 `ai/AI_USAGE.md` → `ai/COMPONENT_CATALOG.md` → `ai/DESIGN_RULES.md` → `ai/COMPOSITION_PATTERNS.md`
|
||||
3. API가 의심스러우면 그 패키지의 `dist/*.d.ts`(공개 진입점에서 export되는 것만)
|
||||
|
||||
| 유형 | 패키지 | 지침 위치(설치 후) | JS 진입점 | CSS |
|
||||
|---|---|---|---|---|
|
||||
| ① Web UI | `@hm-design/ui` | `node_modules/@hm-design/ui/ai/` | `@hm-design/ui`, `@hm-design/ui/theme` | `@hm-design/ui/tailwind.css` + `@hm-design/ui/theme.css`(Tailwind CSS 4 필요), 선택 `@hm-design/ui/dashboard-status.css` |
|
||||
| ② SW UI | `@hm-design/sw` | `node_modules/@hm-design/sw/ai/` | `@hm-design/sw` | `@hm-design/sw/styles.css` |
|
||||
| ③ OrgChart | `@hm-design/org-chart`(Core) + React면 `@hm-design/org-chart-react` | `node_modules/@hm-design/org-chart/ai/` (React 어댑터 안내는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`) | `@hm-design/org-chart`, `@hm-design/org-chart-react`, 선택 `@hm-design/org-chart-react/hm-dialog` | `@hm-design/org-chart/engine.css`, `@hm-design/org-chart/style.css`(URL로 전달 — Shadow DOM 안에 로드) |
|
||||
| 기타 | `@hm-design/center-graphic` | AI 지침 없음. 패키지 `README.md`만 | `@hm-design/center-graphic` | 자동 import |
|
||||
|
||||
모든 패키지는 서로 독립입니다(`@hm-design/org-chart-react`만 Core에 의존, `@hm-design/ui`는 `/hm-dialog`를 쓸 때만 선택 peer). React·React DOM `^19`는 peer입니다.
|
||||
|
||||
## 1단계. 사용 목적 확인
|
||||
|
||||
다음 순서로 판단하고, **판단되면 다시 묻지 않습니다.**
|
||||
|
||||
1. 소비 프로젝트의 `AGENTS.md`·`CLAUDE.md` 등에 `HMDesign 사용 유형:` 설정이 있으면 그대로 따릅니다(§9).
|
||||
2. 사용자 요청이 유형을 분명히 말하면 그대로 따릅니다.
|
||||
- Web: 웹 화면, 관리자·업무 화면, 목록·검색·Form, 대시보드, GNB/LNB, Dialog, Table …
|
||||
- SW: 데스크톱 SW, 뷰어, 도면·CAD, SW Header, 툴바, 레이어/색상/선스타일, 속성창·SW Popup …
|
||||
- OrgChart: 조직도, 조직 계층·직원 카드, 조직 JSON/CSV/Excel 시각화 …
|
||||
- 둘 이상이 함께 나오면 ④ 혼합(예: "업무 화면 안에 조직도" = Web + OrgChart).
|
||||
3. 위로 정할 수 없으면 코드 작성 전에 **그대로** 묻습니다.
|
||||
|
||||
> 어떤 HMDesign 디자인 시스템을 사용하시겠습니까?
|
||||
>
|
||||
> 1. Web
|
||||
> 2. SW
|
||||
> 3. OrgChart
|
||||
> 4. 혼합 사용
|
||||
|
||||
- `package.json`에 설치된 패키지가 하나뿐이어도 그것만으로 유형을 확정하지 않습니다(설치가 덜 된 상태일 수 있음). 요청 내용과 맞을 때만 근거로 씁니다.
|
||||
- 혼합을 고르면 영역별 패키지(예: "페이지 = Web, 가운데 영역 = OrgChart")를 확인합니다.
|
||||
- **OrgChart가 포함되고 생성 방식이 없으면** 같은 메시지에서 다음도 묻습니다(이미 말했으면 묻지 않음): "조직도만 기존 페이지에 삽입할까요(Chart Only), 아니면 레이아웃까지 구성한 새 페이지 전체를 생성할까요(Full Page)?" 상세는 `@hm-design/org-chart/ai/AI_USAGE.md` §1.
|
||||
- 선택한 패키지가 설치되어 있지 않으면 설치가 필요하다고 보고합니다. 다른 HMDesign 패키지나 임의 라이브러리로 대신하지 않습니다.
|
||||
|
||||
## 2단계. 패키지 분석
|
||||
|
||||
1. `node_modules/@hm-design/<패키지>/package.json`의 `version`·`exports`·`peerDependencies`를 확인합니다. `exports`에 없는 경로(`src/...`, `dist/components/...`)는 import하지 않습니다.
|
||||
2. 그 패키지의 `ai/` 문서를 §0 순서로 읽습니다. 필요한 절만 읽고 전체 `dist`를 훑지 않습니다.
|
||||
3. 카탈로그와 실제 `dist/index.d.ts`가 다르면 **`.d.ts`가 기준**입니다. 차이를 결과 보고에 적습니다.
|
||||
4. 소비 프로젝트의 기존 CSS 진입점·폰트·빌드 도구(Vite 등)·이미 쓰는 디자인 시스템을 확인합니다. 기존 프로젝트의 디자인 시스템을 바꾸지 않습니다.
|
||||
|
||||
## 3단계. 화면 분석
|
||||
|
||||
요청 화면을 항목으로 나누고 항목마다 판단을 기록합니다.
|
||||
|
||||
| 요청 항목 | 판단 | 근거(카탈로그의 실제 이름) |
|
||||
|---|---|---|
|
||||
| 예: 상단 메뉴 | 재사용 | Web `GNB` + `GNBMobileMenu` |
|
||||
| 예: 레이어 선택 | 재사용 | SW `SWHeaderLayerMenu` |
|
||||
| 예: 검색 상자(SW) | 신규 | SW 카탈로그에 없음 → `SWHeaderInput` 재사용 + SW 토큰 |
|
||||
|
||||
판단 값은 **재사용**(그대로) / **Props 설정**(기존 Variant·Props) / **조합**(기존 컴포넌트 배치) / **신규**(소비 프로젝트에 최소 구현) 중 하나입니다.
|
||||
|
||||
## 4단계. UI 구현
|
||||
|
||||
우선순위(위가 먼저):
|
||||
|
||||
1. 선택 패키지의 기존 컴포넌트·Pattern을 그대로 사용
|
||||
2. 기존 Variant / Props / Callback으로 해결
|
||||
3. 기존 컴포넌트 조합(배치용 CSS만 소비 프로젝트에 작성)
|
||||
4. 조합으로 안 되는 부분만 **소비 프로젝트에** 새로 구현 — 패키지 토큰만 읽고, 안쪽에 기존 컴포넌트를 재사용
|
||||
|
||||
규칙:
|
||||
|
||||
- `node_modules` 안 패키지 파일을 수정하지 않습니다. 패키지 `dist` 구현이나 내부 CSS 블록을 복사해 같은 컴포넌트를 다시 만들지 않습니다.
|
||||
- 이미 있는 컴포넌트와 같은 역할의 컴포넌트를 새로 만들지 않습니다(예: Web에 `Dialog`가 있는데 자체 Modal 작성 금지).
|
||||
- 새 디자인 값을 만들지 않습니다. 색·크기·간격·radius·그림자는 각 패키지 `DESIGN_RULES.md`의 토큰·확인값만 씁니다. 값이 없으면 추측하지 말고 사용자에게 확인합니다.
|
||||
- 공통: Font Weight는 400 / 500 / 700만 씁니다(600 없음). Light/Dark 분기를 만들지 않습니다. 새 컴포넌트 CSS에 `html`·`body`·`*` 전역 규칙을 넣지 않습니다. 새 class는 소비 프로젝트 접두사(예: `app-`)를 씁니다.
|
||||
- 요청에 없는 메뉴·통계·설명·다운로드·외부 API·AI 입력 UI를 추가하지 않습니다. 필요해 보이면 제안으로만 보고합니다.
|
||||
|
||||
### 없는 UI를 같은 스타일로 만들 때 확인할 항목
|
||||
|
||||
신규 항목마다 아래를 선택 패키지 `DESIGN_RULES.md`에서 찾아 그대로 적용합니다. 찾을 수 없는 항목은 "값 없음 — 사용자 확인 필요"로 보고합니다.
|
||||
|
||||
| 항목 | Web(`@hm-design/ui`) | SW(`@hm-design/sw`) | OrgChart |
|
||||
|---|---|---|---|
|
||||
| 토큰·CSS 변수 | `--hm-sem-*`, `--hm-color-*`, `--hm-radius-*`(`:root`) | `--hm-sw-header-*`, `--hm-sw-property-*`(`SWTheme` 범위) | `--hm-orgchart-*` 공개 입력(Shadow DOM 상속) |
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
| 동작·키보드 | Radix 동작 유지 | 카탈로그 "공통 키보드" | Core 키보드(Pan, Alt+M 이동 등) |
|
||||
|
||||
## 5단계. 패키지별 디자인 분리 (혼합 사용 포함)
|
||||
|
||||
각 패키지의 스타일 범위는 다음과 같습니다(실제 CSS 기준).
|
||||
|
||||
| 패키지 | 스타일이 적용되는 범위 | 주의 |
|
||||
|---|---|---|
|
||||
| Web | `theme.css`의 토큰은 `:root, [data-hm-theme]`. `tailwind.css`는 Tailwind base에서 `*`(border·outline), `body`(배경·글자), `html`(글꼴)을 **문서 전체에** 적용. 앱이 import하는 `tailwindcss`의 Preflight도 전역 | Web CSS를 넣은 문서에서는 다른 영역에도 전역 초기화가 적용됨 |
|
||||
| SW | 토큰은 `.hm-sw-header`와 `.hm-sw-theme`(`<SWTheme>`)에만 정의. 전역 선택자 없음 | Header 밖 SW 부품은 반드시 `<SWTheme>` 안 |
|
||||
| OrgChart | Core가 자기 open Shadow DOM에만 CSS 로드. 호스트 CSS selector는 안쪽에 닿지 않고 **Custom Property 상속만** 통과 | 호스트 class로 카드 모양을 바꾸려 하지 않음 |
|
||||
|
||||
혼합 규칙:
|
||||
|
||||
1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다.
|
||||
2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다.
|
||||
- SW `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`)
|
||||
- OrgChart Full Page의 바깥 레이아웃 = Web(또는 요청된 패키지)
|
||||
- OrgChart React의 직원 팝업 = 선택 `@hm-design/org-chart-react/hm-dialog`(Web `Dialog`, Web Theme CSS 필요)
|
||||
3. 패키지 class(`hm-sw-*`, `hm-orgchart-*`, Web `data-slot`)를 소비 코드에서 덮어쓰지 않습니다. 배치는 `className`·`style`과 소비 프로젝트 class로 합니다.
|
||||
4. **Web + SW를 한 문서에 함께 쓰면** Web의 전역 base(위 표)가 SW 화면에도 적용됩니다. SW는 자기 범위에서 버튼 등을 초기화하지만 이 조합의 시각 결과는 패키지에서 검증된 적이 없습니다(NOT VERIFIED). 이 조합이면 브라우저에서 SW 부품의 글꼴·테두리·배경을 직접 확인하고 차이를 보고합니다. 가능하면 Web CSS가 없는 SW 전용 화면(별도 entry)으로 분리합니다.
|
||||
5. Web Scoped Theme(`[data-hm-theme]`)는 body Portal(Select·Popover·DatePicker 팝업)에 전달되지 않습니다. Web 영역만 다른 테마로 바꾸려면 Global Theme를 쓰거나 차이를 보고합니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
|
||||
1. Typecheck(`tsc --noEmit` 등)와 Production Build
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW는 대상 창 폭(Header는 반응형 More 포함), OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지(§5)
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
```md
|
||||
## HMDesign 적용 결과
|
||||
- 사용 유형: Web | SW | OrgChart(Chart Only|Full Page) | 혼합(영역별: …)
|
||||
- 패키지·버전: @hm-design/ui 0.1.0, …
|
||||
- 재사용한 컴포넌트: …(Props 설정 포함)
|
||||
- 조합한 부분: …
|
||||
- AI가 새로 구현한 부분: 파일 경로 · 이유(카탈로그에 없음) · 안쪽에서 재사용한 컴포넌트
|
||||
- 적용한 디자인 토큰: --hm-… (신규 UI가 읽은 것)
|
||||
- 카탈로그와 .d.ts 차이: 없음 | …
|
||||
- 검증: Typecheck PASS · Build PASS · 브라우저(폭/상태) … · NOT VERIFIED: …
|
||||
- 제안(구현하지 않음): …
|
||||
```
|
||||
|
||||
## 8. 상황별 예시
|
||||
|
||||
### 예시 A — Web 화면 (목록 + 검색)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/ui/ai/` → COMPOSITION_PATTERNS "목록 화면"
|
||||
- 재사용: `LNB`(또는 `GNB`) 중 **하나**, `PageHeader`, `Card`, `FilterBar` + `FilterBarSearch` + `FilterBarActions`, `DataTable` + `useDataTable`, `Button`
|
||||
- 신규(필요 시만): 카탈로그에 없는 콘텐츠 블록 → 소비 프로젝트 컴포넌트, `--hm-sem-*` 토큰과 `Card` 재사용
|
||||
|
||||
```css
|
||||
/* src/index.css */
|
||||
@import "tailwindcss";
|
||||
@import "@hm-design/ui/tailwind.css";
|
||||
@import "@hm-design/ui/theme.css";
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { Button, Card, CardContent, FilterBar, FilterBarActions, FilterBarSearch, PageHeader, PageHeaderContent, PageHeaderTitle } from "@hm-design/ui"
|
||||
```
|
||||
|
||||
### 예시 B — SW 뷰어
|
||||
|
||||
- 읽기: `node_modules/@hm-design/sw/ai/` → COMPOSITION_PATTERNS "6. 파일 열기 + 도구 막대", 예제 `ai/examples/ExampleF-ViewerToolbar.tsx`
|
||||
- 재사용: `SWHeader`(`documentOpenPlacement="header"`, `controls`, `tools`), `SWToolGroup` + `SWToolButton`(Model/Layout 탭, Fit 등), `SWPopup` + `SWPopupSection` + `SWPopupRow` + `SWHeaderInput`(설정 Popup)
|
||||
- 신규: SW에 없는 기능(예: 검색) → 소비 프로젝트 컴포넌트, `<SWTheme>` 안, `--hm-sw-header-*` 토큰, 안쪽 입력은 `SWHeaderInput`(예제 `ExampleD-SearchExtension.tsx`)
|
||||
|
||||
```tsx
|
||||
import { SWHeader, SWPopup, SWTheme, SWToolButton, SWToolGroup } from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
```
|
||||
|
||||
### 예시 C — 조직도만 (Chart Only)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/org-chart/ai/` → COMPOSITION_PATTERNS "1. Chart Only"
|
||||
- 재사용: React `OrgChart`(`@hm-design/org-chart-react`) 또는 Vanilla `createOrgChart`, 데이터 `JsonOrgChartDataSource` + `validateOrgChartDataset`
|
||||
- 필수 옵션: `layoutMode="auto"` + `presentationMode="chart-only"`(초기 Fit·직접 휠 Zoom이 기본)
|
||||
- **추가하지 않음:** 페이지 제목·통계·설명·Zoom 버튼 바·외부 회색 배경·Navigation. Fixed Chart Only는 미지원(`TypeError`)
|
||||
|
||||
```tsx
|
||||
import { OrgChart } from "@hm-design/org-chart-react"
|
||||
import { JsonOrgChartDataSource } from "@hm-design/org-chart"
|
||||
import engineUrl from "@hm-design/org-chart/engine.css?url" // Vite. 다른 도구는 실제 배포 URL
|
||||
import coreUrl from "@hm-design/org-chart/style.css?url"
|
||||
```
|
||||
|
||||
### 예시 D — Web + OrgChart (Full Page)
|
||||
|
||||
- 영역: 페이지 레이아웃·제목·필터 = Web, 조직도 영역 = OrgChart
|
||||
- 읽기: Web `ai/` + OrgChart `ai/COMPOSITION_PATTERNS.md` "2. Full Page"
|
||||
- 재사용: Web `LNB`/`GNB` 중 하나, `PageHeader`, `Card`, `Select`·`Label`(회사 선택 등 요청된 컨트롤만) + OrgChart `OrgChart`(`selectedCompanyIds` 등 Props로 연결)
|
||||
- 직원 팝업을 Web Dialog로: `import { OrgChart } from "@hm-design/org-chart-react/hm-dialog"` + `employeePopup="hm-dialog"`(Web CSS 진입점 필요)
|
||||
- 분리: 조직도 카드·색은 OrgChart 기본 유지. Web 토큰을 조직도에 넣지 않음(바꿔야 하면 `--hm-orgchart-*` 공개 입력만)
|
||||
|
||||
## 9. 소비 프로젝트 설정 — AI가 이 지침을 찾게 하기
|
||||
|
||||
AI 코딩 도구는 보통 **프로젝트 루트의** `AGENTS.md`(Codex 등)·`CLAUDE.md`(Claude Code)·도구별 규칙 파일만 자동으로 읽고 `node_modules`는 읽지 않습니다. 패키지 설치만으로 지침이 자동 적용되지는 않으므로 소비 프로젝트에서 한 번 연결합니다.
|
||||
|
||||
1. 패키지에 들어 있는 `ai/AGENTS_SNIPPET.md`를 루트 `AGENTS.md`(없으면 새로 만듦)에 붙여 넣고 `HMDesign 사용 유형:` 줄을 프로젝트에 맞게 고칩니다. 정하지 않았으면 그 줄을 지워 AI가 1단계에서 묻게 합니다.
|
||||
```bash
|
||||
cat node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md >> AGENTS.md
|
||||
```
|
||||
(PowerShell: `Get-Content node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md | Add-Content AGENTS.md`. 설치한 아무 HMDesign 패키지의 사본이나 같습니다.)
|
||||
2. Claude Code는 `CLAUDE.md`에 `@AGENTS.md` 한 줄을 두면 같은 내용을 읽습니다. 다른 도구(Cursor 규칙 등)는 그 도구의 규칙 파일에 같은 내용을 넣습니다.
|
||||
3. 패키지를 업데이트한 뒤 스니펫이 바뀌었는지 확인합니다(스니펫은 경로 안내만 하므로 보통 그대로 둡니다).
|
||||
|
||||
자동으로 되지 않는 것: 패키지 설치 시 루트 파일 생성(`postinstall` 없음), 도구가 `node_modules`를 스스로 탐색하는 것. 위 연결이 없으면 사용자가 "node_modules/@hm-design/…/ai/AI_GUIDE.md를 먼저 읽어"라고 요청해야 합니다.
|
||||
|
||||
## 10. 문서 갱신 (HMDesign 저장소 유지보수자)
|
||||
|
||||
컴포넌트를 추가·수정하면 같은 변경에서 해당 패키지 `ai/COMPONENT_CATALOG.md`와 필요한 `DESIGN_RULES.md`·`COMPOSITION_PATTERNS.md`를 갱신하고 `npm run check:ai-docs`를 통과시킵니다. 절차는 저장소 `docs/ai/MAINTAINING.md`(패키지 미포함)에 있습니다.
|
||||
@@ -0,0 +1,71 @@
|
||||
# @hm-design/org-chart — AI 코딩 도구 사용 안내 (OrgChart)
|
||||
|
||||
먼저 [AI_GUIDE.md](./AI_GUIDE.md)(공통 1~7단계)를 읽고 사용 유형이 **OrgChart** 또는 OrgChart가 포함된 혼합일 때 이 폴더를 씁니다. 이 폴더(`node_modules/@hm-design/org-chart/ai/`)는 Core `@hm-design/org-chart` 0.1.0과 React 어댑터 `@hm-design/org-chart-react` 0.1.0의 공개 Export·타입에서 확인한 내용만 적습니다.
|
||||
|
||||
| 문서 | 내용 |
|
||||
|---|---|
|
||||
| [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) | Core 함수·Handle·옵션, 데이터 계약·Validator, React `OrgChart` Props, 선택 Entry `/hm-dialog` |
|
||||
| [DESIGN_RULES.md](./DESIGN_RULES.md) | 승인된 기본 디자인, 회사 컬러, 공개 CSS 입력 `--hm-orgchart-*`, 변경 가능/불가 경계 |
|
||||
| [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) | Chart Only(React·Vanilla), Full Page(Web + OrgChart), 데이터 생성·변환, 팝업 선택 |
|
||||
|
||||
## 1. 생성 방식 사전 선택 (필수)
|
||||
|
||||
새 조직도 컴포넌트나 조직도 페이지를 만들 때 **생성 방식이 요청에 없으면 코드 작성 전에** 대화에서 묻습니다(웹페이지에 선택 UI를 만들지 않음). 이미 말했으면 다시 묻지 않습니다.
|
||||
|
||||
> 조직도만 기존 페이지에 삽입할까요, 아니면 레이아웃까지 구성한 새 페이지 전체를 생성할까요?
|
||||
>
|
||||
> ① 조직도만 생성 (Chart Only) ② 새 페이지 전체 생성 (Full Page)
|
||||
|
||||
| 방식 | 만드는 것 | 만들지 않는 것 |
|
||||
|---|---|---|
|
||||
| **Chart Only** | 기존 페이지에 넣을 독립 조직도 컴포넌트: 흰색 캔버스 + 노드 + 연결선, 최초 Fit to View, 일반 휠 Zoom, Pan. Core 내부 기능(그룹 접기, 직원 팝업)은 유지 | 페이지 제목·기준일·회사 수/인원 요약·설명·Zoom/Fit 버튼 바·배율 안내·Drag & Drop 문구·JSON/디버그 패널·외부 회색 배경·Page Header·Navigation·AI 입력 UI |
|
||||
| **Full Page** | 기존 HM UI(보통 Web `@hm-design/ui`)로 요청된 제목·정보·필터·컨트롤과 전체 배치·반응형 + 조직도 영역 | 요청에 없는 통계·설명·다운로드·외부 API·추가 메뉴 |
|
||||
|
||||
- `layoutMode`(Fixed / Auto)는 배치 모드이며 생성 방식 선택을 대신하지 않습니다.
|
||||
- **Chart Only는 Auto 전용**입니다: `layoutMode: 'auto'` + `presentationMode: 'chart-only'`. Fixed와 함께 쓰면 `TypeError`. Fixed Chart Only 요청은 Auto로 임의 변경하지 말고 "미지원"으로 보고합니다.
|
||||
- Chart Only에는 일반 Web 화면의 Navigation(GNB/LNB/DASHBOARD) 의무를 적용하지 않습니다. Full Page에는 Web 화면 규칙([Web COMPOSITION_PATTERNS §1](../../ui/ai/COMPOSITION_PATTERNS.md) — 설치 경로 `node_modules/@hm-design/ui/ai/`)을 적용합니다.
|
||||
|
||||
## 2. 작업 순서
|
||||
|
||||
1. 요청을 가변 구조로 정리합니다: 회사 수·ID, 대표이사 유무, 조직 유형과 명칭, 부모·자식 관계, 조직별 하위 조직 수·직원 수, 전체 직원 수, Depth(회사·직원 카드 포함 여부), 집계 기준. 의미가 불분명해 구조·총원이 달라지는 항목만 질문합니다.
|
||||
2. 데이터 만들기·변환 → `validateOrgChartDataset`로 검증(§3). 실패 항목을 지워서 통과시키지 않습니다.
|
||||
3. 기존 Core·React `OrgChart`를 그대로 씁니다. 새 조직도 엔진·카드·연결선을 만들지 않고 Shadow DOM 내부를 조작하지 않습니다.
|
||||
4. 공개 옵션으로 표현할 수 없는 요구(예: Fixed Chart Only, 임의 회사 프리셋 등록)는 지원 한계와 대안을 보고합니다.
|
||||
5. 검증: 요청 대비 회사·조직·Depth·직원·조직장 수가 실제 데이터(`organizations`, 고유 `employees`)와 일치하는지, 렌더링·초기 Fit·Zoom/Pan·접기·팝업·키보드를 브라우저에서 확인합니다.
|
||||
|
||||
## 3. 데이터 규칙
|
||||
|
||||
- **요청을 정확히 반영:** 명시된 회사·조직명·관계·유형·조직 수·직원 수·직위·대표이사 유무를 그대로 만듭니다. 이전 예시의 회사·Depth·인원을 재사용하지 않고 특정 Depth를 강제하지 않습니다.
|
||||
- **제공 JSON·CSV·Excel 보존:** 직원·조직을 임의로 추가·삭제·병합하지 않고 이름·직위·원본 ID·상하 관계를 바꾸지 않습니다(trim·철자 교정 포함). 원본에 없는 근무 상태·관계사·유형을 실제 정보처럼 채우지 않습니다. Core는 CSV/XLSX parser를 제공하지 않습니다 — 변환 결과는 `unknown`으로 보고 Validator를 통과시킵니다.
|
||||
- **직원 1회 생성:** 대표이사·조직장은 `employees`에 한 번만 만들고 `leaderId`로 참조합니다. 별도 지시가 없으면 총원에 포함합니다.
|
||||
- **복수 회사:** 회사마다 `parentId: null`인 별도 Root. 회사들을 잇는 가짜 상위 조직을 만들지 않습니다. Root에 `companyId`(하위 조직이 상속).
|
||||
- **조직 유형:** `type`은 `root | division | office | div | cell | team`뿐입니다. 업무 명칭(본부·실·부서 …)은 `name`에 그대로 두고 `type`은 가장 가까운 값으로 대응한 뒤 그 대응을 보고합니다. 새 enum을 만들지 않습니다.
|
||||
- **팀·셀 위계(확장형 데이터 생성 규칙):** 팀(`team`)을 셀(`cell`)의 직접 자식이나 셀의 어떤 하위 계층에도 두지 않습니다. 팀 → 셀은 허용합니다. 제공 데이터가 이 규칙과 충돌하면 원본 경로·ID를 보고하고 조용히 고치지 않습니다(이 규칙은 데이터 생성 규칙이며 Validator가 자동 거절하지는 않음).
|
||||
- **회사 컬러:** Auto 회사 ID는 `hanmac`, `saman`, `jangheon-industry`, `jangheon`, `ptc`, `halla`, `baron` 7개뿐입니다. 미등록 회사를 기존 ID에 임의로 연결하지 않고 제약으로 보고합니다. 알 수 없는 ID는 Auto에서 `TypeError`.
|
||||
- `createOrgChartExampleDataset`은 기존 fixture template에서 20 / 100 / 300명 예시를 만드는 helper입니다. 임의 인원 생성기나 실제 데이터 변환 API가 아닙니다.
|
||||
|
||||
## 4. 설치·Import
|
||||
|
||||
```tsx
|
||||
// React
|
||||
import { OrgChart } from "@hm-design/org-chart-react" // 기본 Entry, HM UI 불필요
|
||||
import { JsonOrgChartDataSource, validateOrgChartDataset } from "@hm-design/org-chart"
|
||||
import engineUrl from "@hm-design/org-chart/engine.css?url" // Vite 전용 문법
|
||||
import coreUrl from "@hm-design/org-chart/style.css?url"
|
||||
```
|
||||
|
||||
- 최소 설치: Core + Vanilla = `@hm-design/org-chart`. React = `@hm-design/org-chart` + `@hm-design/org-chart-react` + React 19 + `react-dom`. HM Dialog 팝업을 쓸 때만 `@hm-design/ui`와 그 CSS 진입점 추가.
|
||||
- Core exports: `.`, `./style.css`, `./engine.css`, `./browser`(IIFE 전역 `window.HMOrgChart`, ESM named import용 아님). React exports: `.`, `./hm-dialog`. 내부 `src`/`dist` 경로 import 금지.
|
||||
- `stylesheetUrls: { engine, core }`는 **필수**이며 CSS의 **URL**입니다. Core가 자기 Shadow DOM에 engine → core 순으로 link를 넣습니다. 문서에 CSS를 import하는 것만으로는 카드가 스타일링되지 않습니다. `?url`은 Vite 문법이며 다른 도구는 공개 CSS를 실제 배포 URL로 바꿔 전달합니다.
|
||||
- 글꼴: 기본 Noto Sans KR. 호스트가 font-face를 한 번 등록합니다(Web 패키지 `theme.css`를 쓰면 이미 포함).
|
||||
- URL JSON은 HTTP(S)/상대 경로만. `file://` fetch는 지원하지 않습니다.
|
||||
|
||||
## 5. 디자인 보호
|
||||
|
||||
- 승인된 기본 디자인(카드·회사색·그룹 제목·연결선·Typography·간격)을 바꾸지 않습니다. 의도적 커스터마이징은 공개 CSS 입력 `--hm-orgchart-*`(외부 테마)로만 하고, 기본값 자체 변경은 사용자 확인이 필요합니다([DESIGN_RULES.md](./DESIGN_RULES.md)).
|
||||
- 접근성 미해결: 삼안(`#F08300`, 약 2.64:1)·장헌산업(`#4580A6`, 약 4.29:1) 그룹 제목의 흰 글자 대비가 WCAG AA 4.5:1 미달입니다. 숨기거나 PASS로 보고하지 않습니다.
|
||||
- Font Weight 400 / 500 / 700만 사용합니다.
|
||||
|
||||
## 6. 공개하지 않은 것
|
||||
|
||||
`chartOnly`, `showToolbar`, `showHeader`, React `ref`/imperative handle, `data`·`title`·`children` Props, 회사 프리셋 등록 setter, 카드 교체 API, Compact 모드는 **없습니다**. Chart Only의 정확한 API는 `presentationMode`입니다. `displayMode: 'readOnly'`나 `createOrgChartTree`는 Chart Only를 대신하지 않습니다.
|
||||
@@ -0,0 +1,109 @@
|
||||
# OrgChart 공개 API 카탈로그
|
||||
|
||||
기준: `@hm-design/org-chart` 0.1.0(`src/index.ts` → `dist/index.d.ts`)과 `@hm-design/org-chart-react` 0.1.0(`.`, `./hm-dialog`). **이 문서에 없는 이름은 공개 API가 아닙니다.** 저장소의 `npm run check:ai-docs`가 이 문서와 실제 Export의 일치를 검사합니다.
|
||||
|
||||
- 런타임 helper(`JsonOrgChartDataSource` 등)는 항상 Core에서 import합니다. React 패키지는 Core 타입을 type-only로 다시 export합니다.
|
||||
- 옵션은 생성 시점에 고정되는 것이 많습니다. 아래 "변경 방법"을 확인하고, React에서는 객체 Props를 `useMemo`로 고정합니다.
|
||||
|
||||
## 1. React — `@hm-design/org-chart-react`
|
||||
|
||||
| Export | 내용 |
|
||||
|---|---|
|
||||
| `OrgChart` | 유일한 기본 runtime export. React가 전용 container를 소유하고 Core handle의 mount·subscribe·destroy를 관리. Props는 아래 표 |
|
||||
| 타입 `OrgChartProps`, `OrgChartEmployeeSelection`, `OrgChartEmployeePopupProps` | `OrgChartEmployeePopupProps = { selection: { employee, context } \| null; onClose }` |
|
||||
| `/hm-dialog`의 `OrgChart`, `HMEmployeeDialog` | 같은 Props. 직원 팝업을 Web `Dialog`로(`employeePopup="hm-dialog"` 명시). `@hm-design/ui` 0.1.0 peer와 Web CSS 진입점 필요. `HMEmployeeDialog`는 이 Entry에서만 export |
|
||||
|
||||
`OrgChartProps` = Core `OrgChartOptions`에서 `container`·`onStateChange`를 뺀 것 + 아래 React 전용 Props.
|
||||
|
||||
| Prop | 값 | 변경 시 |
|
||||
|---|---|---|
|
||||
| `dataSource` (필수) | `OrgChartDataSource` (`new JsonOrgChartDataSource(data)`) | 새 객체 → 같은 handle `reload` (useMemo로 고정) |
|
||||
| `stylesheetUrls` (필수) | `{ engine: string; core: string }` CSS URL | 문자열 값이 바뀌면 재생성 |
|
||||
| `layoutMode` | `'fixed'`(기본) · `'auto'` | 재생성 |
|
||||
| `displayMode` | `'interactive'`(기본) · `'readOnly'` | 재생성 |
|
||||
| `presentationMode` | `'default'`(기본) · `'chart-only'` — **Auto 전용**, Fixed와 쓰면 `TypeError` | — |
|
||||
| `initialView` | `'center'` · `'fit'` (chart-only 기본 `fit`) | — |
|
||||
| `wheelZoom` | `'modifier'`(Ctrl/⌘ + 휠) · `'direct'` (chart-only 기본 `direct`) | — |
|
||||
| `companyId` | Auto 기본 회사 프리셋(기본 `hanmac`) | 재생성 |
|
||||
| `selectedCompanyIds` | `undefined` = 전체, `[]` = 없음 (Auto) | 같은 handle에서 선택 변경 |
|
||||
| `autoLayout` | `{ type?: 'tb' \| 'lr' \| 'hybrid'; hybridStartDepth?: 2 \| 3 \| 4; initialVisibleDepth?: 1 \| 2 \| 3 \| 'all'; hybridOrganizationIds?: string[] }` | 같은 handle |
|
||||
| `interactions` | `{ collapse?, zoom?, pan? }` 모두 기본 true (Auto) | 같은 handle |
|
||||
| `dragDropEnabled` | 기본 `false`. true면 Auto 직원·그룹 이동(포인터, `Alt+M` → 방향키 → Enter) | 같은 handle |
|
||||
| `companyTopNodes` | `{ [companyId]: { employeeId?, title?, name?, profileImage?, organizationName?, display?: 'card' \| 'titleOnly' \| 'hidden' } }` (표시만 변경) | 재생성 |
|
||||
| `employeeConnections` | `{ [organizationId]: 'top' \| 'left' \| 'right' }` 직원 연결선 방향 | 재생성 |
|
||||
| `zoom` | `{ min?, max?, step? }` (기본 5–300%) | 재생성 |
|
||||
| `organizationLinks` | `{ [organizationId]: url }` 1Depth 제목만 링크. HTTP(S)·상대 URL | 재생성 |
|
||||
| `employeePopup` | `'core'`(기본) · `'hm-dialog'` · `'adapter'` · `'none'` (Auto. Fixed는 항상 Core 팝업) | 같은 handle |
|
||||
| `employeePopupAdapter` | `ComponentType<OrgChartEmployeePopupProps>` (`employeePopup="adapter"`와 함께) | — |
|
||||
| `onEmployeeClick(employee, context)` | 호스트 소유 정보 UI. 지정하면 기본 팝업 대신 호출(최우선) | — |
|
||||
| `onMove(result)` | Auto 이동 결과(`moved` 성공/거절). 서버 저장 없음 | — |
|
||||
| `aggregationPolicy` | `ApprovedAggregationPolicy` (업무 집계 정책) | 재생성 |
|
||||
| `onStateChange(state)` | `loading` → `ready`(summary 포함) / `error`(issues) | — |
|
||||
| `onLifecycleError(error)` | 생성·mount·reload·cleanup의 예기치 않은 오류 | — |
|
||||
| `className`, `style` | 바깥 container에만 적용. 캔버스 높이는 container 높이(미지정 시 최소 320px) | — |
|
||||
|
||||
없는 것: `ref`/imperative handle, `children`, `data`, `title`, `chartOnly`, `showToolbar`. React에서 `fitToView` 등을 직접 호출하는 공개 경로는 없습니다(Chart Only의 `initialView="fit"`이 초기 Fit 담당).
|
||||
|
||||
## 2. Core — `@hm-design/org-chart`
|
||||
|
||||
### 2.1 렌더링
|
||||
|
||||
| Export | 내용 |
|
||||
|---|---|
|
||||
| `createOrgChart(options)` → `OrgChartHandle` | 전체 조직도. 옵션 = §1 표의 Core 항목 + `container?: HTMLElement`, `onStateChange?`. DOM 없이 생성 가능, `mount`에서 연결된 HTMLElement 필요 |
|
||||
| `createOrgChartTree(options)` | 계층만 렌더링(독립 Tree). Chart Only를 대신하지 않음 |
|
||||
| `createPositionSummary`, `createAffiliationSummary`, `createStatusSummary` (옵션 타입 `OrgChartSummaryComponentOptions`) | 직위별·관계사별·근무 상태별 요약 독립 모듈 |
|
||||
|
||||
`OrgChartHandle`:
|
||||
|
||||
| 메서드 | 내용 |
|
||||
|---|---|
|
||||
| `mount(container?)`, `update(input)`, `reload(source?)`, `destroy()` | 수명주기. `destroy`는 최종·idempotent — 다시 쓰려면 새 handle |
|
||||
| `subscribe(listener)` → unsubscribe, `getState()` | 상태 구독 |
|
||||
| `setSelectedCompanies(ids?)`, `getSelectedCompanies()` | Auto 회사 선택 |
|
||||
| `setAutoLayout(options)`, `getAutoLayout()` | Auto 배치 변경(접기 상태 유지) |
|
||||
| `setInteractions(options)`, `getInteractions()` | Auto 접기·Zoom·Pan on/off |
|
||||
| `setEmployeeClickHandler(handler?)` | Auto 팝업 소유자 변경 |
|
||||
| `setDragDropEnabled(enabled)`, `getDragDropEnabled()`, `move(request)`, `getData()` | Auto 이동 편집(브라우저 메모리만) |
|
||||
| `zoomTo(scale)`, `panBy(x, y)`, `fitToView()`, `getViewportState()` | 보기 조작 |
|
||||
|
||||
### 2.2 데이터 계약·검증
|
||||
|
||||
| Export | 내용 |
|
||||
|---|---|
|
||||
| 타입 `OrgChartDataset` | `{ schemaVersion: '0.1-draft'; organizations; employees; affiliations; relations?; presentation?; metadata: { asOfDate; dataVersion; schemaVersion?; updatedAt?; updateNote? } }` (`SCHEMA_VERSION` 상수) |
|
||||
| 타입 `Organization` | `{ id; companyId?; parentId: string \| null; name; type: OrganizationType; leaderId: string \| null; displayOrder: number }` |
|
||||
| 타입 `Employee` | `{ id; organizationId; affiliationId; name; position; jobTitle: string \| null; profileImage: string \| null; workStatus: WorkStatus }` |
|
||||
| 타입 `OrganizationType`, `WorkStatus`, `Affiliation`, `ID` | `'root' \| 'division' \| 'office' \| 'div' \| 'cell' \| 'team'` / `'active' \| 'dispatched' \| 'onLeave'` / `{ id; name }` / `string` |
|
||||
| 타입 `OrganizationRelation`, `RelationType`, `PresentationConfig`, `Metadata` | 관계(`cooperation` · `support`, 검증만 하고 그리지 않음), 표시 설정, 메타데이터 |
|
||||
| `validateOrgChartDataset(input)` → `ValidationResult` | `{ valid: true, data }`(깊게 freeze) 또는 `{ valid: false, issues: ValidationIssue[] }`. 알 수 없는 필드·중복 ID·참조 오류·순환·안전하지 않은 URL 등 거절. `organizations: []`는 오류, `employees: []`는 유효 |
|
||||
| `isSafeImageUrl(url)` | 이미지 URL 안전성 검사 |
|
||||
| `normalizeOrgChartDataset(data)` → `NormalizedOrgChartDataset` (`OrganizationTreeNode`) | 트리 정규화 |
|
||||
| `calculateSummary(data, policy?)` → `OrgChartSummary` (`SummaryCounts`, `AggregationResult`) | `raw` = 원본 고유 직원 집계(업무 승인 인원 아님). 정책 없으면 `aggregation.status='unavailable'` |
|
||||
| 타입 `ApprovedAggregationPolicy`, `AggregationPolicy` | `{ policyVersion; includedStatuses; activeStatuses; includeLeaders; membershipMode }` |
|
||||
| `snapshotOrgChartDataset(data)` | 불변 snapshot |
|
||||
| `JsonOrgChartDataSource(input, options?)` (`JsonOrgChartDataSourceOptions` = `{ fetch? }`) | inline dataset 또는 HTTP(S)/상대 URL. 생성 시 inline 입력 고정 |
|
||||
| `OrgChartSourceError` (`OrgChartSourceErrorCode` = `SOURCE_LOAD` · `SOURCE_PARSE` · `SOURCE_ABORT` · `SOURCE_URL` · `SOURCE_SNAPSHOT`) | Source 오류 |
|
||||
| 타입 `OrgChartDataSource`, `ValidationIssue`, `ValidationResult` | 사용자 정의 Source는 `load(options?: { signal?: AbortSignal }): Promise<unknown>` 구현 |
|
||||
|
||||
### 2.3 회사 컬러·예시·이동
|
||||
|
||||
| Export | 내용 |
|
||||
|---|---|
|
||||
| `companyColorPresets`, `getCompanyColorPreset(id)` / 타입 `CompanyId`, `CompanyColorPreset` | 7개 회사 `{ companyId, companyName, primary, secondary }`. 등록 setter 없음 |
|
||||
| `createOrgChartExampleDataset(template, selection)` / 타입 `OrgChartExampleScope`, `OrgChartExampleEmployeeCount`, `OrgChartExampleSelection` | 기존 large fixture template에서 `employeeCount: 20 \| 100 \| 300`, `scope: 'total' \| 'per-company'`, `companyIds?` 예시 생성 |
|
||||
| `createOrgChartMoveValidator`, `validateOrgChartMove`, `applyOrgChartMove` / 타입 `OrgChartMoveRequest`, `OrgChartMoveFailureCode`, `OrgChartMoveFailure`, `OrgChartMoveValidation`, `OrgChartMoveResult` | 순수 이동 검증·적용(`{ kind: 'employee' \| 'organization', sourceId, targetOrganizationId }`) |
|
||||
|
||||
### 2.4 옵션·상태 타입
|
||||
|
||||
`OrgChartOptions`, `OrgChartHandle`, `OrgChartState`, `OrgChartDisplayMode`, `OrgChartLayoutMode`, `OrgChartPresentationMode`, `OrgChartInitialView`, `OrgChartWheelZoom`, `OrgChartAutoLayoutType`, `OrgChartAutoLayoutOptions`, `OrgChartInteractionOptions`, `OrgChartConnectionDirection`, `OrgChartTopNode`, `OrgChartViewportState`, `OrgChartOrganizationLinks`, `OrgChartEmployeeClickContext`(`{ organization, affiliation, trigger, originalEvent }`).
|
||||
|
||||
## 3. 상태·오류
|
||||
|
||||
- `OrgChartState.status`: `loading` → `ready`(`dataVersion`, `summary`, `aggregation`) 또는 `error`(`issues: { code, path, message }[]`). 타입에 `empty`·`idle`·`destroyed`가 있으나 지원되는 Empty UI 증거는 아닙니다.
|
||||
- Auto는 회사 프리셋·회사 경계·상단 직원 설정을 추가 검사합니다(`COMPANY_PRESET`, `COMPANY_HIERARCHY`, `TOP_NODE_CONFIG`, `TOP_NODE_REFERENCE`). Validator 통과만으로 Auto 표시가 보장되지 않습니다.
|
||||
- 데이터·Source·스타일 오류 = `state.error`, 예기치 않은 생성·수명주기 오류 = React `onLifecycleError`.
|
||||
|
||||
## 4. 지원하지 않는 것
|
||||
|
||||
자연어 → 데이터 자동 생성 UI, CSV/XLSX 직접 import, 서버 저장·외부 API·이미지/PDF export, 임의 회사 프리셋 등록, 카드 교체, Compact 모드, 공개 collapse setter, React imperative ref, Fixed Chart Only, Fixed의 휠 Zoom/Pan, SSR/hydration(검증 범위 밖).
|
||||
@@ -0,0 +1,185 @@
|
||||
# OrgChart 조합 패턴
|
||||
|
||||
모든 패턴은 공개 Export만 씁니다([COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md)). 예시 데이터는 가상이며 회사·분기·인원을 기본값으로 쓰지 않습니다.
|
||||
|
||||
## 0. 어떤 패턴을 고를까
|
||||
|
||||
| 요청 | 패턴 |
|
||||
|---|---|
|
||||
| 조직도만 기존 페이지에 삽입 | 1. Chart Only (Auto) |
|
||||
| 조직도가 들어간 새 페이지 전체 | 2. Full Page (Web + OrgChart) |
|
||||
| 직원 정보 팝업을 Web 디자인으로 | 3. HM Dialog 팝업 |
|
||||
| 다른 디자인 시스템의 팝업 | 4. 팝업 어댑터 |
|
||||
| 자연어·CSV·Excel → 데이터 | 5. 데이터 만들기 |
|
||||
| 기존 Workdesk(고정) 구성 | `layoutMode="fixed"`(기본), `displayMode="readOnly"` 등 — Chart Only 불가 |
|
||||
|
||||
## 1. Chart Only (Auto) — 조직도 영역만
|
||||
|
||||
**React**
|
||||
|
||||
```tsx
|
||||
import { useMemo } from "react"
|
||||
import { OrgChart } from "@hm-design/org-chart-react"
|
||||
import { JsonOrgChartDataSource, type OrgChartDataset } from "@hm-design/org-chart"
|
||||
import engineUrl from "@hm-design/org-chart/engine.css?url"
|
||||
import coreUrl from "@hm-design/org-chart/style.css?url"
|
||||
|
||||
const stylesheetUrls = { engine: engineUrl, core: coreUrl }
|
||||
|
||||
export function OrganizationChart({ data }: { data: OrgChartDataset }) {
|
||||
const dataSource = useMemo(() => new JsonOrgChartDataSource(data), [data])
|
||||
return (
|
||||
<OrgChart
|
||||
dataSource={dataSource}
|
||||
stylesheetUrls={stylesheetUrls}
|
||||
layoutMode="auto"
|
||||
presentationMode="chart-only" // 초기 Fit·일반 휠 Zoom 기본
|
||||
dragDropEnabled={false}
|
||||
style={{ height: "100%" }} // 높이는 호스트 영역이 정함
|
||||
onLifecycleError={error => console.error(error)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Vanilla (React 없음)**
|
||||
|
||||
```ts
|
||||
import { createOrgChart, JsonOrgChartDataSource, type OrgChartDataset } from "@hm-design/org-chart"
|
||||
import engineUrl from "@hm-design/org-chart/engine.css?url"
|
||||
import coreUrl from "@hm-design/org-chart/style.css?url"
|
||||
|
||||
export async function mountOrganizationChart(container: HTMLElement, data: OrgChartDataset): Promise<() => void> {
|
||||
const chart = createOrgChart({
|
||||
container, dataSource: new JsonOrgChartDataSource(data),
|
||||
stylesheetUrls: { engine: engineUrl, core: coreUrl },
|
||||
layoutMode: "auto", presentationMode: "chart-only", dragDropEnabled: false,
|
||||
})
|
||||
try {
|
||||
await chart.mount()
|
||||
if (chart.getState().status !== "ready") throw new Error("조직도를 표시할 수 없습니다.")
|
||||
return () => chart.destroy()
|
||||
} catch (error) {
|
||||
chart.destroy()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 이 컴포넌트에 제목·통계·버튼 바·설명·바깥 회색 영역을 **추가하지 않습니다.** 호스트 페이지의 기존 UI도 바꾸지 않습니다.
|
||||
- 독립 미리보기가 필요하면 흰색 조직도 영역을 최상위로 두는 별도 entry만 만듭니다(`html, body, #root { height: 100%; margin: 0 }` 같은 페이지 설정은 **미리보기 entry의 index.html/CSS**에만).
|
||||
- 레이아웃 방향 등 요청된 조직도 내부 옵션만 추가: `autoLayout={{ type: "lr" }}`, `selectedCompanyIds`, `employeePopup` …
|
||||
|
||||
## 2. Full Page (Web + OrgChart)
|
||||
|
||||
영역: 페이지·Navigation·제목·필터 = Web(`@hm-design/ui`, `node_modules/@hm-design/ui/ai/` 규칙), 조직도 영역 = OrgChart.
|
||||
|
||||
```tsx
|
||||
import { useId, useMemo, useState } from "react"
|
||||
import {
|
||||
Card, CardContent, Label, PageHeader, PageHeaderContent, PageHeaderTitle,
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
} from "@hm-design/ui"
|
||||
import { OrgChart } from "@hm-design/org-chart-react"
|
||||
import { JsonOrgChartDataSource, companyColorPresets, type CompanyId, type OrgChartDataset } from "@hm-design/org-chart"
|
||||
import engineUrl from "@hm-design/org-chart/engine.css?url"
|
||||
import coreUrl from "@hm-design/org-chart/style.css?url"
|
||||
|
||||
const stylesheetUrls = { engine: engineUrl, core: coreUrl }
|
||||
|
||||
export function OrganizationPage({ data, companyIds }: { data: OrgChartDataset; companyIds: CompanyId[] }) {
|
||||
const selectId = useId()
|
||||
const [company, setCompany] = useState<"all" | CompanyId>("all")
|
||||
const dataSource = useMemo(() => new JsonOrgChartDataSource(data), [data])
|
||||
const selected = useMemo(() => (company === "all" ? undefined : [company]), [company])
|
||||
return (
|
||||
<main className="app-org-page">
|
||||
<PageHeader><PageHeaderContent><PageHeaderTitle>조직도</PageHeaderTitle></PageHeaderContent></PageHeader>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Label htmlFor={selectId}>회사</Label>
|
||||
<Select value={company} onValueChange={value => setCompany(value as "all" | CompanyId)}>
|
||||
<SelectTrigger id={selectId} size="medium"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">전체</SelectItem>
|
||||
{companyIds.map(id => <SelectItem key={id} value={id}>{companyColorPresets[id].companyName}</SelectItem>)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="app-org-canvas">
|
||||
<OrgChart dataSource={dataSource} stylesheetUrls={stylesheetUrls} layoutMode="auto"
|
||||
selectedCompanyIds={selected} style={{ height: "100%" }} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* 소비 레이아웃만. 조직도 안쪽 디자인은 건드리지 않음 */
|
||||
.app-org-page { display: flex; flex-direction: column; gap: 16px; }
|
||||
.app-org-canvas { height: 640px; margin-top: 16px; }
|
||||
```
|
||||
|
||||
- 위 회사 선택 Select는 **요청된 경우의 예**입니다. 요청에 없는 필터·통계·설명을 넣지 않습니다.
|
||||
- Full Page의 Navigation(GNB/LNB/DASHBOARD 중 하나)과 Web 규칙은 Web 문서를 따릅니다.
|
||||
- Web 토큰(`--hm-sem-*`)을 조직도에 넣지 않습니다. 조직도 색을 맞춰야 하면 [DESIGN_RULES.md](./DESIGN_RULES.md) §3의 `--hm-orgchart-*` 입력으로 따로 연결합니다(자동 동기화 없음).
|
||||
- `presentationMode`를 생략하면 Auto 기본 화면(조직도 내부 제목·요약·툴바 포함)입니다. Full Page에서 그 내장 UI가 요청 UI와 겹치면 `presentationMode="chart-only"`로 조직도 영역만 쓰고 바깥 UI는 Web으로 만듭니다.
|
||||
|
||||
## 3. HM Dialog 팝업 (Web Dialog)
|
||||
|
||||
```tsx
|
||||
import { OrgChart } from "@hm-design/org-chart-react/hm-dialog"
|
||||
<OrgChart dataSource={dataSource} stylesheetUrls={stylesheetUrls} layoutMode="auto" employeePopup="hm-dialog" />
|
||||
```
|
||||
|
||||
- `@hm-design/ui` 0.1.0 설치 + 앱 CSS 진입점에 Web `tailwind.css`·`theme.css` 필요(`/hm-dialog`가 CSS를 자동 import하지 않음).
|
||||
- `@hm-design/ui` 설치만으로 팝업이 바뀌지 않습니다. Entry와 `employeePopup`을 모두 명시합니다. Fixed는 항상 Core 팝업입니다.
|
||||
|
||||
## 4. 팝업 어댑터 (다른 디자인 시스템)
|
||||
|
||||
```tsx
|
||||
import { OrgChart, type OrgChartEmployeePopupProps } from "@hm-design/org-chart-react"
|
||||
|
||||
function MyEmployeePopup({ selection, onClose }: OrgChartEmployeePopupProps) {
|
||||
if (!selection) return null
|
||||
// selection.employee / selection.context를 읽기만 한다. 모달 접근성·Escape·context.trigger로 Focus 복귀는 어댑터 책임.
|
||||
return <div role="dialog" aria-modal="true" aria-label={selection.employee.name}>…<button onClick={onClose}>닫기</button></div>
|
||||
}
|
||||
|
||||
<OrgChart dataSource={dataSource} stylesheetUrls={stylesheetUrls} layoutMode="auto"
|
||||
employeePopup="adapter" employeePopupAdapter={MyEmployeePopup} />
|
||||
```
|
||||
|
||||
`onEmployeeClick`을 주면 어떤 팝업 설정보다 우선합니다.
|
||||
|
||||
## 5. 데이터 만들기
|
||||
|
||||
```ts
|
||||
import { validateOrgChartDataset, type OrgChartDataset } from "@hm-design/org-chart"
|
||||
|
||||
const data: OrgChartDataset = {
|
||||
schemaVersion: "0.1-draft",
|
||||
organizations: [
|
||||
{ id: "hanmac-root", companyId: "hanmac", parentId: null, name: "한맥기술", type: "root", leaderId: "e-ceo", displayOrder: 0 },
|
||||
{ id: "hanmac-office-1", parentId: "hanmac-root", name: "1본부", type: "office", leaderId: "e-1", displayOrder: 0 },
|
||||
{ id: "hanmac-team-1", parentId: "hanmac-office-1", name: "1팀", type: "team", leaderId: null, displayOrder: 0 },
|
||||
],
|
||||
employees: [
|
||||
{ id: "e-ceo", organizationId: "hanmac-root", affiliationId: "hanmac", name: "가상 대표", position: "대표이사", jobTitle: null, profileImage: null, workStatus: "active" },
|
||||
{ id: "e-1", organizationId: "hanmac-office-1", affiliationId: "hanmac", name: "가상 본부장", position: "본부장", jobTitle: null, profileImage: null, workStatus: "active" },
|
||||
{ id: "e-2", organizationId: "hanmac-team-1", affiliationId: "hanmac", name: "가상 사원", position: "사원", jobTitle: null, profileImage: null, workStatus: "active" },
|
||||
],
|
||||
affiliations: [{ id: "hanmac", name: "한맥기술" }],
|
||||
metadata: { asOfDate: "2026-10-19", dataVersion: "example-1" },
|
||||
}
|
||||
|
||||
const checked = validateOrgChartDataset(data)
|
||||
if (!checked.valid) throw new Error(JSON.stringify(checked.issues))
|
||||
```
|
||||
|
||||
- 필드는 위가 전부입니다(알 수 없는 필드는 Validator가 거절). 이름 규칙: `displayOrder`, `jobTitle`, `profileImage`, `workStatus`.
|
||||
- 업무 명칭 "본부"는 `name`에 두고 `type: "office"`로 대응했습니다 — 이런 대응은 결과 보고에 적습니다.
|
||||
- 회사 추가 = `parentId: null`인 Root를 하나 더(+ 그 회사 `affiliations` 항목). 가짜 상위 조직 금지.
|
||||
- 검증 후 보고: 요청 대비 회사 수, 조직 수, Depth, 고유 직원 수(대표·조직장 포함 여부), 조직별 직속/하위 포함 인원.
|
||||
@@ -0,0 +1,81 @@
|
||||
# OrgChart 디자인 규칙
|
||||
|
||||
출처: Core `style.css`(원본 `src/styles/design-tokens.css`)·`src/core/style-presets.ts`·`company-color-presets.ts`와 사용자 승인 기록(2026-10-13 APPROVED). OrgChart는 **독립 디자인**입니다. Web(`--hm-sem-*`)·SW(`--hm-sw-*`) 토큰의 영향을 받지 않고, 그 토큰을 조직도 안에 넣지 않습니다.
|
||||
|
||||
## 1. 원칙
|
||||
|
||||
1. **승인된 기본 디자인을 그대로 씁니다.** 카드·회사색·그룹 제목·연결선·Typography·간격·배치를 바꾸지 않습니다. 기본값 자체를 바꾸려면 사용자 확인이 먼저입니다.
|
||||
2. **커스터마이징은 공개 CSS 입력(`--hm-orgchart-*`)만**으로 합니다(외부 테마). 이는 기본값 변경과 구분해 보고합니다.
|
||||
3. 새 카드·연결선·노드 디자인을 만들지 않습니다. Shadow DOM 내부 DOM을 스크립트로 바꾸지 않습니다.
|
||||
4. `resolved-*`, `current-*`, `expanded-primary`, `expanded-card-primary`는 내부 계산 변수입니다. 쓰지 않습니다.
|
||||
|
||||
## 2. 승인된 기본값
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| Auto(Expanded)·Chart Only 캔버스 | 흰색 `#FFFFFF` |
|
||||
| Fixed readOnly / Full Composition 배경 | `#F5F8FB`(`bg-content` 기본) |
|
||||
| 회사 메인 컬러(Auto 그룹 제목·카드 강조) | `hanmac` 한맥기술 `#CB2E28`, `saman` 삼안 `#F08300`, `jangheon-industry` 장헌산업 `#4580A6`, `jangheon` 장헌 `#1D418D`, `ptc` 피티씨 `#84338E`, `halla` 한라산업개발 `#0068B7`, `baron` 바론컨설턴트 `#0C367F` |
|
||||
| 그룹 제목 글자 | 흰색 |
|
||||
| Auto 연결선 | `#738399`(`expanded-line-color` 기본) |
|
||||
| Fixed 부서 Gradient | 6종(`business-management`, `business-planning`, `talent-growth`, `integrated-system`, `design-planning`, `solution-sales`). 회사색과 별개 — 서로 대체하지 않음 |
|
||||
| 글꼴 | Noto Sans KR(`--hm-orgchart-font-family`로 override 가능). 호스트가 font-face 등록 |
|
||||
| Weight | 400 / 500 / 700만 |
|
||||
|
||||
**접근성 미해결(숨기지 않음):** 삼안 `#F08300`(흰 글자 약 2.64:1), 장헌산업 `#4580A6`(약 4.29:1)은 WCAG AA 4.5:1 미달입니다. 디자인은 승인됐지만 접근성 준수 판정은 NOT VERIFIED입니다.
|
||||
|
||||
## 3. 공개 CSS 입력 (`--hm-orgchart-` 접두사)
|
||||
|
||||
| 입력(접두사 생략) | 범위 |
|
||||
|---|---|
|
||||
| `fixed-canvas-background`, `expanded-canvas-background` | Fixed composition·stage / Auto 내부 viewport 배경(서로 독립) |
|
||||
| `bg-surface`, `bg-content`, `bg-subtle`, `bg-brand` | 캔버스·내용·보조·브랜드 배경 |
|
||||
| `fg-primary`, `fg-secondary` | 글자 |
|
||||
| `stroke-default`, `stroke-input-default`, `stroke-input-focus` | 테두리·Focus |
|
||||
| `accent-color`, `on-accent`, `action-primary`, `on-action-primary` | 강조·액션 |
|
||||
| `company-{id}`, `company-{id}-secondary-{n}` | Auto 회사 제목·소속 카드 강조선(7개 ID) |
|
||||
| `group-title-background`, `group-title-color`, `group-border` | 그룹 제목·Fixed 조직 카드 상단 Border |
|
||||
| `card-background`, `card-color`, `card-position-color`, `card-border`, `card-accent` | 카드 |
|
||||
| `leader-card-background`, `top-leader-card-background` | Auto 그룹장·대표자 카드 |
|
||||
| `card-hover-background`, `card-focus-color` | Hover·Focus |
|
||||
| `expanded-line-color`, `fixed-line-color` | 연결선 |
|
||||
| `gradient`, `border`, `department-color`, `{department}-gradient` / `-border` / `-department-color` / `-card-background` / `-card-color` | Fixed 부서 |
|
||||
| `status-title-*`, `status-card-*`, `dispatched-*`, `on-leave-*` | 파견·휴직 |
|
||||
| `dialog-background`, `dialog-color`, `dialog-border`, `dialog-close-color`, `dialog-close-hover-background`, `dialog-focus-color` | Core 기본 팝업 |
|
||||
| `font-family` | 글꼴 |
|
||||
|
||||
### 적용 방법 (둘 중 하나)
|
||||
|
||||
```css
|
||||
/* A. 호스트 상속: 기본 stylesheetUrls.core 유지 + 컨테이너 class (변수만 Shadow DOM을 통과) */
|
||||
.app-orgchart-theme {
|
||||
--hm-orgchart-company-hanmac: #2457a7;
|
||||
--hm-orgchart-expanded-line-color: #249a79;
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* B. Shadow DOM 안 외부 테마 파일: 기본 CSS를 먼저 import하고 :host에 override → 이 파일 URL을 stylesheetUrls.core로 */
|
||||
@import "@hm-design/org-chart/style.css"; /* Vite. 일반 서버는 복사한 style.css URL */
|
||||
:host { --hm-orgchart-card-background: #f4f8ff; }
|
||||
```
|
||||
|
||||
- 호스트의 `.hm-orgchart-…` 같은 selector는 Shadow DOM 안에 닿지 않습니다. 내부 selector 수정이 꼭 필요하면 B 파일 안에 작성합니다.
|
||||
- `stylesheetUrls.core`를 변수만 있는 파일로 바꾸면 기본 레이아웃이 사라집니다(B는 반드시 기본 CSS import 포함).
|
||||
- Fixed의 PNG·SVG 카드·로고 이미지 내부 색은 CSS로 바뀌지 않습니다.
|
||||
- Web Dialog(`/hm-dialog`) 팝업은 Shadow DOM 밖 Web 컴포넌트라 `dialog-*` 입력이 아니라 Web Theme를 따릅니다.
|
||||
|
||||
## 4. 레이아웃·동작 기준
|
||||
|
||||
| 항목 | Fixed | Auto(Expanded) |
|
||||
|---|---|---|
|
||||
| 배치 | 승인된 Workdesk 구성, 비례 축소 | 데이터 기반 가변 계층(TB / LR / Hybrid) |
|
||||
| Zoom / Pan | 없음 | 5–300%(`zoom`), Ctrl/⌘+휠(기본) 또는 일반 휠(`wheelZoom='direct'`), 빈 공간 Drag Pan, 방향키 Pan |
|
||||
| Chart Only | **미지원** | `presentationMode='chart-only'` |
|
||||
| 접기 | — | 그룹 제목 클릭(native toggle, `aria-expanded`) |
|
||||
| 직원 팝업 | Core 팝업 | Core / HM Dialog / 어댑터 / 없음 |
|
||||
| 이동 편집 | 없음 | `dragDropEnabled` (기본 OFF) |
|
||||
|
||||
- Chart Only 초기 Fit: 전체 Bounds를 16px 여백 안에 가로·세로 중앙 배치, 100% 이상 확대하지 않음. 사용자가 조작한 뒤에는 크기가 바뀌어도 다시 Fit하지 않음.
|
||||
- 캔버스 높이는 container 높이(정해지지 않으면 최소 320px). Chart Only 컴포넌트의 높이는 호스트가 정합니다.
|
||||
- 조직도 바깥(제목·필터·패널)은 OrgChart 디자인이 아닙니다. Full Page에서는 Web 등 선택한 패키지의 규칙을 따릅니다.
|
||||
@@ -11,7 +11,7 @@
|
||||
"./engine.css": "./dist/engine.css",
|
||||
"./browser": "./dist/orgchart.browser.js"
|
||||
},
|
||||
"files": ["dist", "README.md"],
|
||||
"files": ["dist", "ai", "README.md"],
|
||||
"sideEffects": ["**/*.css"],
|
||||
"scripts": {
|
||||
"typecheck": "tsc -p tsconfig.json",
|
||||
|
||||
@@ -92,4 +92,4 @@ npm run typecheck -w @hm-design/sw
|
||||
|
||||
## AI 코딩 도구용 가이드 (2026-10-19)
|
||||
|
||||
다른 프로젝트에서 AI 코딩 도구가 이 패키지를 재사용·조합·확장할 때는 설치된 패키지의 `ai/` 폴더를 먼저 읽습니다: [AI_USAGE.md](./ai/AI_USAGE.md)(작업 순서·Import 규칙) → [COMPONENT_CATALOG.md](./ai/COMPONENT_CATALOG.md)(공개 API) → [DESIGN_RULES.md](./ai/DESIGN_RULES.md)(토큰·상태·Motion) → [COMPOSITION_PATTERNS.md](./ai/COMPOSITION_PATTERNS.md)(조합 방법) → [examples/](./ai/examples)(공개 API만 쓰는 예제 A~D). `ai/`는 npm 패키지(`files`)에 포함됩니다. 런타임 API는 바뀌지 않았습니다.
|
||||
다른 프로젝트에서 AI 코딩 도구가 이 패키지를 재사용·조합·확장할 때는 설치된 패키지의 `ai/` 폴더를 먼저 읽습니다: [AI_GUIDE.md](./ai/AI_GUIDE.md)(HMDesign 공통 진입점: Web / SW / OrgChart / 혼합 선택, 패키지 분리, 결과 보고. 모든 HMDesign 패키지에 같은 사본, 2026-10-19 추가) → [AI_USAGE.md](./ai/AI_USAGE.md)(작업 순서·Import 규칙) → [COMPONENT_CATALOG.md](./ai/COMPONENT_CATALOG.md)(공개 API) → [DESIGN_RULES.md](./ai/DESIGN_RULES.md)(토큰·상태·Motion) → [COMPOSITION_PATTERNS.md](./ai/COMPOSITION_PATTERNS.md)(조합 방법) → [examples/](./ai/examples)(공개 API만 쓰는 예제 A~D). `ai/`는 npm 패키지(`files`)에 포함됩니다. 런타임 API는 바뀌지 않았습니다.
|
||||
@@ -0,0 +1,16 @@
|
||||
## HMDesign 디자인 시스템
|
||||
|
||||
<!-- HMDesign 패키지의 ai/AGENTS_SNIPPET.md에서 복사. 아래 "사용 유형"을 프로젝트에 맞게 고치거나, 정하지 않았으면 그 줄을 지운다. -->
|
||||
|
||||
HMDesign 사용 유형: Web | SW | OrgChart | 혼합(영역별로 적기)
|
||||
|
||||
화면·컴포넌트를 구현하거나 수정하기 전에 다음을 지킨다.
|
||||
|
||||
1. 설치된 HMDesign 패키지의 공통 가이드를 먼저 읽는다: `node_modules/@hm-design/<설치된 패키지>/ai/AI_GUIDE.md` (`ui`, `sw`, `org-chart`, `org-chart-react` 중 아무 사본이나 내용이 같다).
|
||||
2. 위 "사용 유형"이 없고 요청으로도 정할 수 없으면 코드 작성 전에 "어떤 HMDesign 디자인 시스템을 사용하시겠습니까? 1. Web 2. SW 3. OrgChart 4. 혼합 사용"을 묻는다. 정해져 있으면 다시 묻지 않는다.
|
||||
3. 선택한 패키지의 `ai/AI_USAGE.md` → `COMPONENT_CATALOG.md` → `DESIGN_RULES.md` → `COMPOSITION_PATTERNS.md` 순으로 필요한 절을 읽는다.
|
||||
- Web: `node_modules/@hm-design/ui/ai/`
|
||||
- SW: `node_modules/@hm-design/sw/ai/`
|
||||
- OrgChart: `node_modules/@hm-design/org-chart/ai/` (React는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`도)
|
||||
4. 기존 컴포넌트 → Props → 조합 → (없는 부분만) 패키지 토큰으로 신규 구현 순서를 지킨다. 패키지 파일 수정·내부 경로 import·구현 복사·새 디자인 값 생성은 하지 않는다.
|
||||
5. Web / SW / OrgChart 스타일을 영역별로 분리하고, 완료 후 AI_GUIDE의 결과 보고 형식으로 재사용·신규·토큰·검증·NOT VERIFIED를 보고한다.
|
||||
@@ -0,0 +1,213 @@
|
||||
# HMDesign AI 작업 가이드 — 공통 진입점
|
||||
|
||||
> 이 파일은 HMDesign 패키지(`@hm-design/ui`, `@hm-design/sw`, `@hm-design/org-chart`, `@hm-design/org-chart-react`)의 `ai/AI_GUIDE.md`에 **같은 내용으로** 들어 있습니다. 원본은 HMDesign 저장소 `docs/ai/AI_GUIDE.md`이며 패키지 사본은 `npm run sync:ai-docs`로 갱신합니다. 사본을 직접 고치지 않습니다.
|
||||
|
||||
사용자가 "HMDesign 패키지를 기준으로 화면을 구현해 주세요"처럼 요청하면 AI 코딩 도구는 이 파일의 1~6단계를 순서대로 따릅니다. 이 문서와 각 패키지의 `ai/` 문서는 실제 공개 API·CSS에서 확인한 내용만 적습니다. 여기에 없는 컴포넌트·Props·토큰은 없는 것으로 취급합니다.
|
||||
|
||||
## 0. 읽는 순서
|
||||
|
||||
1. **이 파일**(어느 패키지의 사본이든 같음)
|
||||
2. 선택한 패키지의 `ai/AI_USAGE.md` → `ai/COMPONENT_CATALOG.md` → `ai/DESIGN_RULES.md` → `ai/COMPOSITION_PATTERNS.md`
|
||||
3. API가 의심스러우면 그 패키지의 `dist/*.d.ts`(공개 진입점에서 export되는 것만)
|
||||
|
||||
| 유형 | 패키지 | 지침 위치(설치 후) | JS 진입점 | CSS |
|
||||
|---|---|---|---|---|
|
||||
| ① Web UI | `@hm-design/ui` | `node_modules/@hm-design/ui/ai/` | `@hm-design/ui`, `@hm-design/ui/theme` | `@hm-design/ui/tailwind.css` + `@hm-design/ui/theme.css`(Tailwind CSS 4 필요), 선택 `@hm-design/ui/dashboard-status.css` |
|
||||
| ② SW UI | `@hm-design/sw` | `node_modules/@hm-design/sw/ai/` | `@hm-design/sw` | `@hm-design/sw/styles.css` |
|
||||
| ③ OrgChart | `@hm-design/org-chart`(Core) + React면 `@hm-design/org-chart-react` | `node_modules/@hm-design/org-chart/ai/` (React 어댑터 안내는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`) | `@hm-design/org-chart`, `@hm-design/org-chart-react`, 선택 `@hm-design/org-chart-react/hm-dialog` | `@hm-design/org-chart/engine.css`, `@hm-design/org-chart/style.css`(URL로 전달 — Shadow DOM 안에 로드) |
|
||||
| 기타 | `@hm-design/center-graphic` | AI 지침 없음. 패키지 `README.md`만 | `@hm-design/center-graphic` | 자동 import |
|
||||
|
||||
모든 패키지는 서로 독립입니다(`@hm-design/org-chart-react`만 Core에 의존, `@hm-design/ui`는 `/hm-dialog`를 쓸 때만 선택 peer). React·React DOM `^19`는 peer입니다.
|
||||
|
||||
## 1단계. 사용 목적 확인
|
||||
|
||||
다음 순서로 판단하고, **판단되면 다시 묻지 않습니다.**
|
||||
|
||||
1. 소비 프로젝트의 `AGENTS.md`·`CLAUDE.md` 등에 `HMDesign 사용 유형:` 설정이 있으면 그대로 따릅니다(§9).
|
||||
2. 사용자 요청이 유형을 분명히 말하면 그대로 따릅니다.
|
||||
- Web: 웹 화면, 관리자·업무 화면, 목록·검색·Form, 대시보드, GNB/LNB, Dialog, Table …
|
||||
- SW: 데스크톱 SW, 뷰어, 도면·CAD, SW Header, 툴바, 레이어/색상/선스타일, 속성창·SW Popup …
|
||||
- OrgChart: 조직도, 조직 계층·직원 카드, 조직 JSON/CSV/Excel 시각화 …
|
||||
- 둘 이상이 함께 나오면 ④ 혼합(예: "업무 화면 안에 조직도" = Web + OrgChart).
|
||||
3. 위로 정할 수 없으면 코드 작성 전에 **그대로** 묻습니다.
|
||||
|
||||
> 어떤 HMDesign 디자인 시스템을 사용하시겠습니까?
|
||||
>
|
||||
> 1. Web
|
||||
> 2. SW
|
||||
> 3. OrgChart
|
||||
> 4. 혼합 사용
|
||||
|
||||
- `package.json`에 설치된 패키지가 하나뿐이어도 그것만으로 유형을 확정하지 않습니다(설치가 덜 된 상태일 수 있음). 요청 내용과 맞을 때만 근거로 씁니다.
|
||||
- 혼합을 고르면 영역별 패키지(예: "페이지 = Web, 가운데 영역 = OrgChart")를 확인합니다.
|
||||
- **OrgChart가 포함되고 생성 방식이 없으면** 같은 메시지에서 다음도 묻습니다(이미 말했으면 묻지 않음): "조직도만 기존 페이지에 삽입할까요(Chart Only), 아니면 레이아웃까지 구성한 새 페이지 전체를 생성할까요(Full Page)?" 상세는 `@hm-design/org-chart/ai/AI_USAGE.md` §1.
|
||||
- 선택한 패키지가 설치되어 있지 않으면 설치가 필요하다고 보고합니다. 다른 HMDesign 패키지나 임의 라이브러리로 대신하지 않습니다.
|
||||
|
||||
## 2단계. 패키지 분석
|
||||
|
||||
1. `node_modules/@hm-design/<패키지>/package.json`의 `version`·`exports`·`peerDependencies`를 확인합니다. `exports`에 없는 경로(`src/...`, `dist/components/...`)는 import하지 않습니다.
|
||||
2. 그 패키지의 `ai/` 문서를 §0 순서로 읽습니다. 필요한 절만 읽고 전체 `dist`를 훑지 않습니다.
|
||||
3. 카탈로그와 실제 `dist/index.d.ts`가 다르면 **`.d.ts`가 기준**입니다. 차이를 결과 보고에 적습니다.
|
||||
4. 소비 프로젝트의 기존 CSS 진입점·폰트·빌드 도구(Vite 등)·이미 쓰는 디자인 시스템을 확인합니다. 기존 프로젝트의 디자인 시스템을 바꾸지 않습니다.
|
||||
|
||||
## 3단계. 화면 분석
|
||||
|
||||
요청 화면을 항목으로 나누고 항목마다 판단을 기록합니다.
|
||||
|
||||
| 요청 항목 | 판단 | 근거(카탈로그의 실제 이름) |
|
||||
|---|---|---|
|
||||
| 예: 상단 메뉴 | 재사용 | Web `GNB` + `GNBMobileMenu` |
|
||||
| 예: 레이어 선택 | 재사용 | SW `SWHeaderLayerMenu` |
|
||||
| 예: 검색 상자(SW) | 신규 | SW 카탈로그에 없음 → `SWHeaderInput` 재사용 + SW 토큰 |
|
||||
|
||||
판단 값은 **재사용**(그대로) / **Props 설정**(기존 Variant·Props) / **조합**(기존 컴포넌트 배치) / **신규**(소비 프로젝트에 최소 구현) 중 하나입니다.
|
||||
|
||||
## 4단계. UI 구현
|
||||
|
||||
우선순위(위가 먼저):
|
||||
|
||||
1. 선택 패키지의 기존 컴포넌트·Pattern을 그대로 사용
|
||||
2. 기존 Variant / Props / Callback으로 해결
|
||||
3. 기존 컴포넌트 조합(배치용 CSS만 소비 프로젝트에 작성)
|
||||
4. 조합으로 안 되는 부분만 **소비 프로젝트에** 새로 구현 — 패키지 토큰만 읽고, 안쪽에 기존 컴포넌트를 재사용
|
||||
|
||||
규칙:
|
||||
|
||||
- `node_modules` 안 패키지 파일을 수정하지 않습니다. 패키지 `dist` 구현이나 내부 CSS 블록을 복사해 같은 컴포넌트를 다시 만들지 않습니다.
|
||||
- 이미 있는 컴포넌트와 같은 역할의 컴포넌트를 새로 만들지 않습니다(예: Web에 `Dialog`가 있는데 자체 Modal 작성 금지).
|
||||
- 새 디자인 값을 만들지 않습니다. 색·크기·간격·radius·그림자는 각 패키지 `DESIGN_RULES.md`의 토큰·확인값만 씁니다. 값이 없으면 추측하지 말고 사용자에게 확인합니다.
|
||||
- 공통: Font Weight는 400 / 500 / 700만 씁니다(600 없음). Light/Dark 분기를 만들지 않습니다. 새 컴포넌트 CSS에 `html`·`body`·`*` 전역 규칙을 넣지 않습니다. 새 class는 소비 프로젝트 접두사(예: `app-`)를 씁니다.
|
||||
- 요청에 없는 메뉴·통계·설명·다운로드·외부 API·AI 입력 UI를 추가하지 않습니다. 필요해 보이면 제안으로만 보고합니다.
|
||||
|
||||
### 없는 UI를 같은 스타일로 만들 때 확인할 항목
|
||||
|
||||
신규 항목마다 아래를 선택 패키지 `DESIGN_RULES.md`에서 찾아 그대로 적용합니다. 찾을 수 없는 항목은 "값 없음 — 사용자 확인 필요"로 보고합니다.
|
||||
|
||||
| 항목 | Web(`@hm-design/ui`) | SW(`@hm-design/sw`) | OrgChart |
|
||||
|---|---|---|---|
|
||||
| 토큰·CSS 변수 | `--hm-sem-*`, `--hm-color-*`, `--hm-radius-*`(`:root`) | `--hm-sw-header-*`, `--hm-sw-property-*`(`SWTheme` 범위) | `--hm-orgchart-*` 공개 입력(Shadow DOM 상속) |
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
| 동작·키보드 | Radix 동작 유지 | 카탈로그 "공통 키보드" | Core 키보드(Pan, Alt+M 이동 등) |
|
||||
|
||||
## 5단계. 패키지별 디자인 분리 (혼합 사용 포함)
|
||||
|
||||
각 패키지의 스타일 범위는 다음과 같습니다(실제 CSS 기준).
|
||||
|
||||
| 패키지 | 스타일이 적용되는 범위 | 주의 |
|
||||
|---|---|---|
|
||||
| Web | `theme.css`의 토큰은 `:root, [data-hm-theme]`. `tailwind.css`는 Tailwind base에서 `*`(border·outline), `body`(배경·글자), `html`(글꼴)을 **문서 전체에** 적용. 앱이 import하는 `tailwindcss`의 Preflight도 전역 | Web CSS를 넣은 문서에서는 다른 영역에도 전역 초기화가 적용됨 |
|
||||
| SW | 토큰은 `.hm-sw-header`와 `.hm-sw-theme`(`<SWTheme>`)에만 정의. 전역 선택자 없음 | Header 밖 SW 부품은 반드시 `<SWTheme>` 안 |
|
||||
| OrgChart | Core가 자기 open Shadow DOM에만 CSS 로드. 호스트 CSS selector는 안쪽에 닿지 않고 **Custom Property 상속만** 통과 | 호스트 class로 카드 모양을 바꾸려 하지 않음 |
|
||||
|
||||
혼합 규칙:
|
||||
|
||||
1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다.
|
||||
2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다.
|
||||
- SW `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`)
|
||||
- OrgChart Full Page의 바깥 레이아웃 = Web(또는 요청된 패키지)
|
||||
- OrgChart React의 직원 팝업 = 선택 `@hm-design/org-chart-react/hm-dialog`(Web `Dialog`, Web Theme CSS 필요)
|
||||
3. 패키지 class(`hm-sw-*`, `hm-orgchart-*`, Web `data-slot`)를 소비 코드에서 덮어쓰지 않습니다. 배치는 `className`·`style`과 소비 프로젝트 class로 합니다.
|
||||
4. **Web + SW를 한 문서에 함께 쓰면** Web의 전역 base(위 표)가 SW 화면에도 적용됩니다. SW는 자기 범위에서 버튼 등을 초기화하지만 이 조합의 시각 결과는 패키지에서 검증된 적이 없습니다(NOT VERIFIED). 이 조합이면 브라우저에서 SW 부품의 글꼴·테두리·배경을 직접 확인하고 차이를 보고합니다. 가능하면 Web CSS가 없는 SW 전용 화면(별도 entry)으로 분리합니다.
|
||||
5. Web Scoped Theme(`[data-hm-theme]`)는 body Portal(Select·Popover·DatePicker 팝업)에 전달되지 않습니다. Web 영역만 다른 테마로 바꾸려면 Global Theme를 쓰거나 차이를 보고합니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
|
||||
1. Typecheck(`tsc --noEmit` 등)와 Production Build
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW는 대상 창 폭(Header는 반응형 More 포함), OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지(§5)
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
```md
|
||||
## HMDesign 적용 결과
|
||||
- 사용 유형: Web | SW | OrgChart(Chart Only|Full Page) | 혼합(영역별: …)
|
||||
- 패키지·버전: @hm-design/ui 0.1.0, …
|
||||
- 재사용한 컴포넌트: …(Props 설정 포함)
|
||||
- 조합한 부분: …
|
||||
- AI가 새로 구현한 부분: 파일 경로 · 이유(카탈로그에 없음) · 안쪽에서 재사용한 컴포넌트
|
||||
- 적용한 디자인 토큰: --hm-… (신규 UI가 읽은 것)
|
||||
- 카탈로그와 .d.ts 차이: 없음 | …
|
||||
- 검증: Typecheck PASS · Build PASS · 브라우저(폭/상태) … · NOT VERIFIED: …
|
||||
- 제안(구현하지 않음): …
|
||||
```
|
||||
|
||||
## 8. 상황별 예시
|
||||
|
||||
### 예시 A — Web 화면 (목록 + 검색)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/ui/ai/` → COMPOSITION_PATTERNS "목록 화면"
|
||||
- 재사용: `LNB`(또는 `GNB`) 중 **하나**, `PageHeader`, `Card`, `FilterBar` + `FilterBarSearch` + `FilterBarActions`, `DataTable` + `useDataTable`, `Button`
|
||||
- 신규(필요 시만): 카탈로그에 없는 콘텐츠 블록 → 소비 프로젝트 컴포넌트, `--hm-sem-*` 토큰과 `Card` 재사용
|
||||
|
||||
```css
|
||||
/* src/index.css */
|
||||
@import "tailwindcss";
|
||||
@import "@hm-design/ui/tailwind.css";
|
||||
@import "@hm-design/ui/theme.css";
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { Button, Card, CardContent, FilterBar, FilterBarActions, FilterBarSearch, PageHeader, PageHeaderContent, PageHeaderTitle } from "@hm-design/ui"
|
||||
```
|
||||
|
||||
### 예시 B — SW 뷰어
|
||||
|
||||
- 읽기: `node_modules/@hm-design/sw/ai/` → COMPOSITION_PATTERNS "6. 파일 열기 + 도구 막대", 예제 `ai/examples/ExampleF-ViewerToolbar.tsx`
|
||||
- 재사용: `SWHeader`(`documentOpenPlacement="header"`, `controls`, `tools`), `SWToolGroup` + `SWToolButton`(Model/Layout 탭, Fit 등), `SWPopup` + `SWPopupSection` + `SWPopupRow` + `SWHeaderInput`(설정 Popup)
|
||||
- 신규: SW에 없는 기능(예: 검색) → 소비 프로젝트 컴포넌트, `<SWTheme>` 안, `--hm-sw-header-*` 토큰, 안쪽 입력은 `SWHeaderInput`(예제 `ExampleD-SearchExtension.tsx`)
|
||||
|
||||
```tsx
|
||||
import { SWHeader, SWPopup, SWTheme, SWToolButton, SWToolGroup } from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
```
|
||||
|
||||
### 예시 C — 조직도만 (Chart Only)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/org-chart/ai/` → COMPOSITION_PATTERNS "1. Chart Only"
|
||||
- 재사용: React `OrgChart`(`@hm-design/org-chart-react`) 또는 Vanilla `createOrgChart`, 데이터 `JsonOrgChartDataSource` + `validateOrgChartDataset`
|
||||
- 필수 옵션: `layoutMode="auto"` + `presentationMode="chart-only"`(초기 Fit·직접 휠 Zoom이 기본)
|
||||
- **추가하지 않음:** 페이지 제목·통계·설명·Zoom 버튼 바·외부 회색 배경·Navigation. Fixed Chart Only는 미지원(`TypeError`)
|
||||
|
||||
```tsx
|
||||
import { OrgChart } from "@hm-design/org-chart-react"
|
||||
import { JsonOrgChartDataSource } from "@hm-design/org-chart"
|
||||
import engineUrl from "@hm-design/org-chart/engine.css?url" // Vite. 다른 도구는 실제 배포 URL
|
||||
import coreUrl from "@hm-design/org-chart/style.css?url"
|
||||
```
|
||||
|
||||
### 예시 D — Web + OrgChart (Full Page)
|
||||
|
||||
- 영역: 페이지 레이아웃·제목·필터 = Web, 조직도 영역 = OrgChart
|
||||
- 읽기: Web `ai/` + OrgChart `ai/COMPOSITION_PATTERNS.md` "2. Full Page"
|
||||
- 재사용: Web `LNB`/`GNB` 중 하나, `PageHeader`, `Card`, `Select`·`Label`(회사 선택 등 요청된 컨트롤만) + OrgChart `OrgChart`(`selectedCompanyIds` 등 Props로 연결)
|
||||
- 직원 팝업을 Web Dialog로: `import { OrgChart } from "@hm-design/org-chart-react/hm-dialog"` + `employeePopup="hm-dialog"`(Web CSS 진입점 필요)
|
||||
- 분리: 조직도 카드·색은 OrgChart 기본 유지. Web 토큰을 조직도에 넣지 않음(바꿔야 하면 `--hm-orgchart-*` 공개 입력만)
|
||||
|
||||
## 9. 소비 프로젝트 설정 — AI가 이 지침을 찾게 하기
|
||||
|
||||
AI 코딩 도구는 보통 **프로젝트 루트의** `AGENTS.md`(Codex 등)·`CLAUDE.md`(Claude Code)·도구별 규칙 파일만 자동으로 읽고 `node_modules`는 읽지 않습니다. 패키지 설치만으로 지침이 자동 적용되지는 않으므로 소비 프로젝트에서 한 번 연결합니다.
|
||||
|
||||
1. 패키지에 들어 있는 `ai/AGENTS_SNIPPET.md`를 루트 `AGENTS.md`(없으면 새로 만듦)에 붙여 넣고 `HMDesign 사용 유형:` 줄을 프로젝트에 맞게 고칩니다. 정하지 않았으면 그 줄을 지워 AI가 1단계에서 묻게 합니다.
|
||||
```bash
|
||||
cat node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md >> AGENTS.md
|
||||
```
|
||||
(PowerShell: `Get-Content node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md | Add-Content AGENTS.md`. 설치한 아무 HMDesign 패키지의 사본이나 같습니다.)
|
||||
2. Claude Code는 `CLAUDE.md`에 `@AGENTS.md` 한 줄을 두면 같은 내용을 읽습니다. 다른 도구(Cursor 규칙 등)는 그 도구의 규칙 파일에 같은 내용을 넣습니다.
|
||||
3. 패키지를 업데이트한 뒤 스니펫이 바뀌었는지 확인합니다(스니펫은 경로 안내만 하므로 보통 그대로 둡니다).
|
||||
|
||||
자동으로 되지 않는 것: 패키지 설치 시 루트 파일 생성(`postinstall` 없음), 도구가 `node_modules`를 스스로 탐색하는 것. 위 연결이 없으면 사용자가 "node_modules/@hm-design/…/ai/AI_GUIDE.md를 먼저 읽어"라고 요청해야 합니다.
|
||||
|
||||
## 10. 문서 갱신 (HMDesign 저장소 유지보수자)
|
||||
|
||||
컴포넌트를 추가·수정하면 같은 변경에서 해당 패키지 `ai/COMPONENT_CATALOG.md`와 필요한 `DESIGN_RULES.md`·`COMPOSITION_PATTERNS.md`를 갱신하고 `npm run check:ai-docs`를 통과시킵니다. 절차는 저장소 `docs/ai/MAINTAINING.md`(패키지 미포함)에 있습니다.
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
이 폴더(`node_modules/@hm-design/sw/ai/`)는 다른 React 프로젝트에서 AI 코딩 도구가 SW 패키지를 **재사용·조합·확장**할 때 먼저 읽는 안내서입니다. 패키지 자체에는 AI 기능이 없습니다. 아래 문서는 실제 공개 API와 CSS에서 확인한 값만 적습니다.
|
||||
|
||||
먼저 [AI_GUIDE.md](./AI_GUIDE.md)(HMDesign 공통: 사용 유형 Web / SW / OrgChart / 혼합 확인, 작업 1~7단계, 패키지 분리, 결과 보고)를 읽고 사용 유형이 **SW** 또는 SW가 포함된 혼합일 때 이 폴더를 씁니다.
|
||||
|
||||
| 문서 | 내용 |
|
||||
|---|---|
|
||||
| [AI_GUIDE.md](./AI_GUIDE.md) | HMDesign 공통 진입점(모든 HMDesign 패키지에 같은 사본) |
|
||||
| [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) | 공식 공개 컴포넌트(Header·개별 컨트롤·Popup·도구 막대)·Props·Callback·크기·CSS 의존성 |
|
||||
| [DESIGN_RULES.md](./DESIGN_RULES.md) | SW 전용 토큰·Typography·간격·Hover/Focus/Selected·Overlay·Motion·반응형·Popup 외형·도구 버튼 |
|
||||
| [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) | 완성형·간소화·기능 중심·확장 Header, Popup(자유 콘텐츠), 파일 열기 + 도구 막대 |
|
||||
@@ -34,6 +37,7 @@ import "@hm-design/sw/styles.css" // 앱 진입점에서 한 번
|
||||
- SVG·PNG 자산은 JS에 data URI로 포함되어 별도 설정이 없다.
|
||||
- 글꼴 파일은 포함하지 않는다. 앱에서 Noto Sans KR(400/500/700)을 로드한다(없으면 Malgun Gothic → Apple SD Gothic Neo).
|
||||
- CSS는 전역(`html`, `body`, `*`)을 건드리지 않는다. `body` margin 등 페이지 기본값은 소비 앱이 정한다.
|
||||
- 스크롤바: SW 전용 스크롤바 디자인은 없다. 기존 SW 컴포넌트는 브라우저 기본 스크롤바를 쓰며(속성Bar만 `scrollbar-width: thin`), Popup 본문 스크롤은 `SWPopup`이 처리한다. 새 스크롤 영역에 임의 스크롤바 색·두께를 만들지 않는다.
|
||||
- TypeScript에서 CSS side-effect import 오류가 나면 Vite의 `vite/client` 타입을 포함한다.
|
||||
|
||||
## 3. 스타일 규칙
|
||||
|
||||
@@ -88,7 +88,7 @@ SW 공식 기준 Header(Figma 26:8944, 1920 × 49). 로고 · 파일 · 객체
|
||||
|
||||
## 5. 공개 타입
|
||||
|
||||
`SWHeaderProps`, 각 컴포넌트 `*Props`, `SWThemeProps`, `SWPopupProps`, `SWPopupSectionProps`, `SWPopupRowProps`, `SWToolButtonProps`, `SWToolButtonAppearance`, `SWToolGroupProps`, `SWHeaderMenuId`, `SWHeaderControlId`, `SWHeaderControlConfig`, `SWHeaderFrameControlId`, `SWHeaderSubControlId`, `SWHeaderHideableControlId`, `SWHeaderDocument`, `SWHeaderLayer`, `SWHeaderLayerValue`, `SWHeaderColorValue`, `SWHeaderColorOption`, `SWHeaderColorMode`, `SWHeaderColorInputValue`, `SWHeaderLineStyleValue`, `SWHeaderLineType`, `SWHeaderLineWeight`, `SWHeaderTextStyleValue`, `SWHeaderTextStyleOption`, `SWHeaderDimensionStyleValue`, `SWHeaderDimensionStyleOption`, `SWHeaderInputValueType`, `SWHeaderProperty`, `SWHeaderPropertyType`, `SWHeaderPropertyPicker`, `SWHeaderPropertyMenus`, `SWHeaderPropertyGroup`, `SWHeaderPropertySelection`, `SWHeaderPropertyShapeContent`, `SWHeaderPropertyShapeContext`.
|
||||
`SWHeaderProps`, 각 컴포넌트 Props(`SWHeaderHomeProps`, `SWHeaderDocumentTitleProps`, `SWHeaderLayerControlProps`, `SWHeaderLayerMenuProps`, `SWHeaderColorMenuProps`, `SWHeaderLineStyleMenuProps`, `SWHeaderTextStyleMenuProps`, `SWHeaderDimensionStyleMenuProps`, `SWHeaderInputProps`, `SWHeaderPropertiesProps`, `SWHeaderPropertyPanelProps`, `SWHeaderPropertyBarProps`, `SWHeaderActionsProps`, `SWHeaderWindowControlsProps`), `SWThemeProps`, `SWPopupProps`, `SWPopupSectionProps`, `SWPopupRowProps`, `SWToolButtonProps`, `SWToolButtonAppearance`, `SWToolGroupProps`, `SWHeaderMenuId`, `SWHeaderControlId`, `SWHeaderControlConfig`, `SWHeaderFrameControlId`, `SWHeaderSubControlId`, `SWHeaderHideableControlId`, `SWHeaderDocument`, `SWHeaderLayer`, `SWHeaderLayerValue`, `SWHeaderColorValue`, `SWHeaderColorOption`, `SWHeaderColorMode`, `SWHeaderColorInputValue`, `SWHeaderLineStyleValue`, `SWHeaderLineType`, `SWHeaderLineWeight`, `SWHeaderTextStyleValue`, `SWHeaderTextStyleOption`, `SWHeaderDimensionStyleValue`, `SWHeaderDimensionStyleOption`, `SWHeaderInputValueType`, `SWHeaderProperty`, `SWHeaderPropertyType`, `SWHeaderPropertyPicker`, `SWHeaderPropertyMenus`, `SWHeaderPropertyGroup`, `SWHeaderPropertySelection`, `SWHeaderPropertyShapeContent`, `SWHeaderPropertyShapeContext`.
|
||||
|
||||
## 6. 공개하지 않은 것 (사용 금지)
|
||||
|
||||
|
||||
@@ -30,4 +30,8 @@ For Dashboard work status colors, explicitly load the optional status stylesheet
|
||||
|
||||
The status stylesheet defines only `--hm-dashboard-status-active`, `--hm-dashboard-status-hold`, `--hm-dashboard-status-done` and `--hm-dashboard-status-issue`. It does not automatically style `Badge`, buttons or `data-status` attributes. Consumers must map these variables to their existing component styles; the repository's `docs/patterns/dashboard-layout.md` shows the Badge mapping. `done` resolves the existing white palette token in each `:root` / `[data-hm-theme]` scope. The other colors preserve the approved Dashboard values and are not additional Runtime Theme tokens.
|
||||
|
||||
From the HMDesignSystem repository, run `npm run build -w @hm-design/ui`. `npm pack` runs this build automatically. Only `dist/`, this README and the package manifest are packed; source and Storybook files are excluded.
|
||||
From the HMDesignSystem repository, run `npm run build -w @hm-design/ui`. `npm pack` runs this build automatically. Only `dist/`, `ai/`, this README and the package manifest are packed; source and Storybook files are excluded.
|
||||
|
||||
## AI coding tools
|
||||
|
||||
AI coding tools should read [`ai/AI_GUIDE.md`](./ai/AI_GUIDE.md) first (shared HMDesign entry point: choose Web / SW / OrgChart / mixed, then reuse → props → composition → minimal custom). Web-specific rules: [`ai/AI_USAGE.md`](./ai/AI_USAGE.md), [`ai/COMPONENT_CATALOG.md`](./ai/COMPONENT_CATALOG.md), [`ai/DESIGN_RULES.md`](./ai/DESIGN_RULES.md), [`ai/COMPOSITION_PATTERNS.md`](./ai/COMPOSITION_PATTERNS.md). To make a consumer project pick them up automatically, copy [`ai/AGENTS_SNIPPET.md`](./ai/AGENTS_SNIPPET.md) into its root `AGENTS.md` (see `AI_GUIDE.md` §9).
|
||||
@@ -0,0 +1,16 @@
|
||||
## HMDesign 디자인 시스템
|
||||
|
||||
<!-- HMDesign 패키지의 ai/AGENTS_SNIPPET.md에서 복사. 아래 "사용 유형"을 프로젝트에 맞게 고치거나, 정하지 않았으면 그 줄을 지운다. -->
|
||||
|
||||
HMDesign 사용 유형: Web | SW | OrgChart | 혼합(영역별로 적기)
|
||||
|
||||
화면·컴포넌트를 구현하거나 수정하기 전에 다음을 지킨다.
|
||||
|
||||
1. 설치된 HMDesign 패키지의 공통 가이드를 먼저 읽는다: `node_modules/@hm-design/<설치된 패키지>/ai/AI_GUIDE.md` (`ui`, `sw`, `org-chart`, `org-chart-react` 중 아무 사본이나 내용이 같다).
|
||||
2. 위 "사용 유형"이 없고 요청으로도 정할 수 없으면 코드 작성 전에 "어떤 HMDesign 디자인 시스템을 사용하시겠습니까? 1. Web 2. SW 3. OrgChart 4. 혼합 사용"을 묻는다. 정해져 있으면 다시 묻지 않는다.
|
||||
3. 선택한 패키지의 `ai/AI_USAGE.md` → `COMPONENT_CATALOG.md` → `DESIGN_RULES.md` → `COMPOSITION_PATTERNS.md` 순으로 필요한 절을 읽는다.
|
||||
- Web: `node_modules/@hm-design/ui/ai/`
|
||||
- SW: `node_modules/@hm-design/sw/ai/`
|
||||
- OrgChart: `node_modules/@hm-design/org-chart/ai/` (React는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`도)
|
||||
4. 기존 컴포넌트 → Props → 조합 → (없는 부분만) 패키지 토큰으로 신규 구현 순서를 지킨다. 패키지 파일 수정·내부 경로 import·구현 복사·새 디자인 값 생성은 하지 않는다.
|
||||
5. Web / SW / OrgChart 스타일을 영역별로 분리하고, 완료 후 AI_GUIDE의 결과 보고 형식으로 재사용·신규·토큰·검증·NOT VERIFIED를 보고한다.
|
||||
@@ -0,0 +1,213 @@
|
||||
# HMDesign AI 작업 가이드 — 공통 진입점
|
||||
|
||||
> 이 파일은 HMDesign 패키지(`@hm-design/ui`, `@hm-design/sw`, `@hm-design/org-chart`, `@hm-design/org-chart-react`)의 `ai/AI_GUIDE.md`에 **같은 내용으로** 들어 있습니다. 원본은 HMDesign 저장소 `docs/ai/AI_GUIDE.md`이며 패키지 사본은 `npm run sync:ai-docs`로 갱신합니다. 사본을 직접 고치지 않습니다.
|
||||
|
||||
사용자가 "HMDesign 패키지를 기준으로 화면을 구현해 주세요"처럼 요청하면 AI 코딩 도구는 이 파일의 1~6단계를 순서대로 따릅니다. 이 문서와 각 패키지의 `ai/` 문서는 실제 공개 API·CSS에서 확인한 내용만 적습니다. 여기에 없는 컴포넌트·Props·토큰은 없는 것으로 취급합니다.
|
||||
|
||||
## 0. 읽는 순서
|
||||
|
||||
1. **이 파일**(어느 패키지의 사본이든 같음)
|
||||
2. 선택한 패키지의 `ai/AI_USAGE.md` → `ai/COMPONENT_CATALOG.md` → `ai/DESIGN_RULES.md` → `ai/COMPOSITION_PATTERNS.md`
|
||||
3. API가 의심스러우면 그 패키지의 `dist/*.d.ts`(공개 진입점에서 export되는 것만)
|
||||
|
||||
| 유형 | 패키지 | 지침 위치(설치 후) | JS 진입점 | CSS |
|
||||
|---|---|---|---|---|
|
||||
| ① Web UI | `@hm-design/ui` | `node_modules/@hm-design/ui/ai/` | `@hm-design/ui`, `@hm-design/ui/theme` | `@hm-design/ui/tailwind.css` + `@hm-design/ui/theme.css`(Tailwind CSS 4 필요), 선택 `@hm-design/ui/dashboard-status.css` |
|
||||
| ② SW UI | `@hm-design/sw` | `node_modules/@hm-design/sw/ai/` | `@hm-design/sw` | `@hm-design/sw/styles.css` |
|
||||
| ③ OrgChart | `@hm-design/org-chart`(Core) + React면 `@hm-design/org-chart-react` | `node_modules/@hm-design/org-chart/ai/` (React 어댑터 안내는 `node_modules/@hm-design/org-chart-react/ai/AI_USAGE.md`) | `@hm-design/org-chart`, `@hm-design/org-chart-react`, 선택 `@hm-design/org-chart-react/hm-dialog` | `@hm-design/org-chart/engine.css`, `@hm-design/org-chart/style.css`(URL로 전달 — Shadow DOM 안에 로드) |
|
||||
| 기타 | `@hm-design/center-graphic` | AI 지침 없음. 패키지 `README.md`만 | `@hm-design/center-graphic` | 자동 import |
|
||||
|
||||
모든 패키지는 서로 독립입니다(`@hm-design/org-chart-react`만 Core에 의존, `@hm-design/ui`는 `/hm-dialog`를 쓸 때만 선택 peer). React·React DOM `^19`는 peer입니다.
|
||||
|
||||
## 1단계. 사용 목적 확인
|
||||
|
||||
다음 순서로 판단하고, **판단되면 다시 묻지 않습니다.**
|
||||
|
||||
1. 소비 프로젝트의 `AGENTS.md`·`CLAUDE.md` 등에 `HMDesign 사용 유형:` 설정이 있으면 그대로 따릅니다(§9).
|
||||
2. 사용자 요청이 유형을 분명히 말하면 그대로 따릅니다.
|
||||
- Web: 웹 화면, 관리자·업무 화면, 목록·검색·Form, 대시보드, GNB/LNB, Dialog, Table …
|
||||
- SW: 데스크톱 SW, 뷰어, 도면·CAD, SW Header, 툴바, 레이어/색상/선스타일, 속성창·SW Popup …
|
||||
- OrgChart: 조직도, 조직 계층·직원 카드, 조직 JSON/CSV/Excel 시각화 …
|
||||
- 둘 이상이 함께 나오면 ④ 혼합(예: "업무 화면 안에 조직도" = Web + OrgChart).
|
||||
3. 위로 정할 수 없으면 코드 작성 전에 **그대로** 묻습니다.
|
||||
|
||||
> 어떤 HMDesign 디자인 시스템을 사용하시겠습니까?
|
||||
>
|
||||
> 1. Web
|
||||
> 2. SW
|
||||
> 3. OrgChart
|
||||
> 4. 혼합 사용
|
||||
|
||||
- `package.json`에 설치된 패키지가 하나뿐이어도 그것만으로 유형을 확정하지 않습니다(설치가 덜 된 상태일 수 있음). 요청 내용과 맞을 때만 근거로 씁니다.
|
||||
- 혼합을 고르면 영역별 패키지(예: "페이지 = Web, 가운데 영역 = OrgChart")를 확인합니다.
|
||||
- **OrgChart가 포함되고 생성 방식이 없으면** 같은 메시지에서 다음도 묻습니다(이미 말했으면 묻지 않음): "조직도만 기존 페이지에 삽입할까요(Chart Only), 아니면 레이아웃까지 구성한 새 페이지 전체를 생성할까요(Full Page)?" 상세는 `@hm-design/org-chart/ai/AI_USAGE.md` §1.
|
||||
- 선택한 패키지가 설치되어 있지 않으면 설치가 필요하다고 보고합니다. 다른 HMDesign 패키지나 임의 라이브러리로 대신하지 않습니다.
|
||||
|
||||
## 2단계. 패키지 분석
|
||||
|
||||
1. `node_modules/@hm-design/<패키지>/package.json`의 `version`·`exports`·`peerDependencies`를 확인합니다. `exports`에 없는 경로(`src/...`, `dist/components/...`)는 import하지 않습니다.
|
||||
2. 그 패키지의 `ai/` 문서를 §0 순서로 읽습니다. 필요한 절만 읽고 전체 `dist`를 훑지 않습니다.
|
||||
3. 카탈로그와 실제 `dist/index.d.ts`가 다르면 **`.d.ts`가 기준**입니다. 차이를 결과 보고에 적습니다.
|
||||
4. 소비 프로젝트의 기존 CSS 진입점·폰트·빌드 도구(Vite 등)·이미 쓰는 디자인 시스템을 확인합니다. 기존 프로젝트의 디자인 시스템을 바꾸지 않습니다.
|
||||
|
||||
## 3단계. 화면 분석
|
||||
|
||||
요청 화면을 항목으로 나누고 항목마다 판단을 기록합니다.
|
||||
|
||||
| 요청 항목 | 판단 | 근거(카탈로그의 실제 이름) |
|
||||
|---|---|---|
|
||||
| 예: 상단 메뉴 | 재사용 | Web `GNB` + `GNBMobileMenu` |
|
||||
| 예: 레이어 선택 | 재사용 | SW `SWHeaderLayerMenu` |
|
||||
| 예: 검색 상자(SW) | 신규 | SW 카탈로그에 없음 → `SWHeaderInput` 재사용 + SW 토큰 |
|
||||
|
||||
판단 값은 **재사용**(그대로) / **Props 설정**(기존 Variant·Props) / **조합**(기존 컴포넌트 배치) / **신규**(소비 프로젝트에 최소 구현) 중 하나입니다.
|
||||
|
||||
## 4단계. UI 구현
|
||||
|
||||
우선순위(위가 먼저):
|
||||
|
||||
1. 선택 패키지의 기존 컴포넌트·Pattern을 그대로 사용
|
||||
2. 기존 Variant / Props / Callback으로 해결
|
||||
3. 기존 컴포넌트 조합(배치용 CSS만 소비 프로젝트에 작성)
|
||||
4. 조합으로 안 되는 부분만 **소비 프로젝트에** 새로 구현 — 패키지 토큰만 읽고, 안쪽에 기존 컴포넌트를 재사용
|
||||
|
||||
규칙:
|
||||
|
||||
- `node_modules` 안 패키지 파일을 수정하지 않습니다. 패키지 `dist` 구현이나 내부 CSS 블록을 복사해 같은 컴포넌트를 다시 만들지 않습니다.
|
||||
- 이미 있는 컴포넌트와 같은 역할의 컴포넌트를 새로 만들지 않습니다(예: Web에 `Dialog`가 있는데 자체 Modal 작성 금지).
|
||||
- 새 디자인 값을 만들지 않습니다. 색·크기·간격·radius·그림자는 각 패키지 `DESIGN_RULES.md`의 토큰·확인값만 씁니다. 값이 없으면 추측하지 말고 사용자에게 확인합니다.
|
||||
- 공통: Font Weight는 400 / 500 / 700만 씁니다(600 없음). Light/Dark 분기를 만들지 않습니다. 새 컴포넌트 CSS에 `html`·`body`·`*` 전역 규칙을 넣지 않습니다. 새 class는 소비 프로젝트 접두사(예: `app-`)를 씁니다.
|
||||
- 요청에 없는 메뉴·통계·설명·다운로드·외부 API·AI 입력 UI를 추가하지 않습니다. 필요해 보이면 제안으로만 보고합니다.
|
||||
|
||||
### 없는 UI를 같은 스타일로 만들 때 확인할 항목
|
||||
|
||||
신규 항목마다 아래를 선택 패키지 `DESIGN_RULES.md`에서 찾아 그대로 적용합니다. 찾을 수 없는 항목은 "값 없음 — 사용자 확인 필요"로 보고합니다.
|
||||
|
||||
| 항목 | Web(`@hm-design/ui`) | SW(`@hm-design/sw`) | OrgChart |
|
||||
|---|---|---|---|
|
||||
| 토큰·CSS 변수 | `--hm-sem-*`, `--hm-color-*`, `--hm-radius-*`(`:root`) | `--hm-sw-header-*`, `--hm-sw-property-*`(`SWTheme` 범위) | `--hm-orgchart-*` 공개 입력(Shadow DOM 상속) |
|
||||
| 색·Typography | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 | DESIGN_RULES §2·§3 |
|
||||
| 크기·여백 | DESIGN_RULES §4 | DESIGN_RULES §1 | 카드·간격 변경 금지(승인 디자인) |
|
||||
| Border·Radius·배경·그림자 | DESIGN_RULES §2·§4 | DESIGN_RULES §2·§8 | 공개 입력만 |
|
||||
| 아이콘 | `lucide-react`(패키지 의존성) | 패키지 내장 SVG만, 새 아이콘은 사용자 제공 | Core 내장 |
|
||||
| 스크롤바 | `ScrollArea`(Radix), DESIGN_RULES §6 | 전용 디자인 없음 — 브라우저 기본(AI_USAGE §2) | Core 내부 Zoom/Pan |
|
||||
| Hover / Active / Focus / Disabled | DESIGN_RULES §5 | DESIGN_RULES §4 | Core 내장 |
|
||||
| Popup·Panel | `Dialog`·`AlertDialog`·`Sheet`·`Drawer`·`Popover` | `SWPopup` + `SWPopupSection`/`SWPopupRow` | Core 팝업 / HM Dialog / 어댑터 |
|
||||
| 동작·키보드 | Radix 동작 유지 | 카탈로그 "공통 키보드" | Core 키보드(Pan, Alt+M 이동 등) |
|
||||
|
||||
## 5단계. 패키지별 디자인 분리 (혼합 사용 포함)
|
||||
|
||||
각 패키지의 스타일 범위는 다음과 같습니다(실제 CSS 기준).
|
||||
|
||||
| 패키지 | 스타일이 적용되는 범위 | 주의 |
|
||||
|---|---|---|
|
||||
| Web | `theme.css`의 토큰은 `:root, [data-hm-theme]`. `tailwind.css`는 Tailwind base에서 `*`(border·outline), `body`(배경·글자), `html`(글꼴)을 **문서 전체에** 적용. 앱이 import하는 `tailwindcss`의 Preflight도 전역 | Web CSS를 넣은 문서에서는 다른 영역에도 전역 초기화가 적용됨 |
|
||||
| SW | 토큰은 `.hm-sw-header`와 `.hm-sw-theme`(`<SWTheme>`)에만 정의. 전역 선택자 없음 | Header 밖 SW 부품은 반드시 `<SWTheme>` 안 |
|
||||
| OrgChart | Core가 자기 open Shadow DOM에만 CSS 로드. 호스트 CSS selector는 안쪽에 닿지 않고 **Custom Property 상속만** 통과 | 호스트 class로 카드 모양을 바꾸려 하지 않음 |
|
||||
|
||||
혼합 규칙:
|
||||
|
||||
1. 영역마다 하나의 패키지를 정하고 그 영역의 신규 UI는 그 패키지의 토큰만 읽습니다. Web 토큰(`--hm-sem-*`)을 SW 영역에, SW 토큰(`--hm-sw-*`)을 Web 영역에 쓰지 않습니다. OrgChart 내부는 `--hm-orgchart-*`만 씁니다.
|
||||
2. 다른 패키지 컴포넌트는 **문서에 허용된 지점에서만** 씁니다.
|
||||
- SW `SWPopup`의 `children` 안 Web 컨트롤(SW 카탈로그가 허용, 예: Popup 하단 `Button`)
|
||||
- OrgChart Full Page의 바깥 레이아웃 = Web(또는 요청된 패키지)
|
||||
- OrgChart React의 직원 팝업 = 선택 `@hm-design/org-chart-react/hm-dialog`(Web `Dialog`, Web Theme CSS 필요)
|
||||
3. 패키지 class(`hm-sw-*`, `hm-orgchart-*`, Web `data-slot`)를 소비 코드에서 덮어쓰지 않습니다. 배치는 `className`·`style`과 소비 프로젝트 class로 합니다.
|
||||
4. **Web + SW를 한 문서에 함께 쓰면** Web의 전역 base(위 표)가 SW 화면에도 적용됩니다. SW는 자기 범위에서 버튼 등을 초기화하지만 이 조합의 시각 결과는 패키지에서 검증된 적이 없습니다(NOT VERIFIED). 이 조합이면 브라우저에서 SW 부품의 글꼴·테두리·배경을 직접 확인하고 차이를 보고합니다. 가능하면 Web CSS가 없는 SW 전용 화면(별도 entry)으로 분리합니다.
|
||||
5. Web Scoped Theme(`[data-hm-theme]`)는 body Portal(Select·Popover·DatePicker 팝업)에 전달되지 않습니다. Web 영역만 다른 테마로 바꾸려면 Global Theme를 쓰거나 차이를 보고합니다.
|
||||
|
||||
## 6단계. 디자인 검증
|
||||
|
||||
소비 프로젝트에서 실제로 실행한 것만 PASS로 적습니다.
|
||||
|
||||
1. Typecheck(`tsc --noEmit` 등)와 Production Build
|
||||
2. 브라우저 렌더링: 재사용한 컴포넌트가 HM 외형으로 보이는지(CSS 진입점 누락 확인), 신규 UI가 같은 토큰을 쓰는지
|
||||
3. 상태·동작: Hover / Focus(키보드) / Disabled, Popup 열기·닫기·Escape·Focus 복귀, 선택 패키지의 핵심 동작
|
||||
4. 폭: Web 390 / 768 / 1280 / 1440px, SW는 대상 창 폭(Header는 반응형 More 포함), OrgChart는 캔버스 크기·초기 Fit
|
||||
5. 혼합이면 영역 경계에서 스타일 침범이 없는지(§5)
|
||||
6. 실행하지 못한 항목은 `NOT VERIFIED`로 남깁니다.
|
||||
|
||||
## 7단계. 결과 보고 형식
|
||||
|
||||
```md
|
||||
## HMDesign 적용 결과
|
||||
- 사용 유형: Web | SW | OrgChart(Chart Only|Full Page) | 혼합(영역별: …)
|
||||
- 패키지·버전: @hm-design/ui 0.1.0, …
|
||||
- 재사용한 컴포넌트: …(Props 설정 포함)
|
||||
- 조합한 부분: …
|
||||
- AI가 새로 구현한 부분: 파일 경로 · 이유(카탈로그에 없음) · 안쪽에서 재사용한 컴포넌트
|
||||
- 적용한 디자인 토큰: --hm-… (신규 UI가 읽은 것)
|
||||
- 카탈로그와 .d.ts 차이: 없음 | …
|
||||
- 검증: Typecheck PASS · Build PASS · 브라우저(폭/상태) … · NOT VERIFIED: …
|
||||
- 제안(구현하지 않음): …
|
||||
```
|
||||
|
||||
## 8. 상황별 예시
|
||||
|
||||
### 예시 A — Web 화면 (목록 + 검색)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/ui/ai/` → COMPOSITION_PATTERNS "목록 화면"
|
||||
- 재사용: `LNB`(또는 `GNB`) 중 **하나**, `PageHeader`, `Card`, `FilterBar` + `FilterBarSearch` + `FilterBarActions`, `DataTable` + `useDataTable`, `Button`
|
||||
- 신규(필요 시만): 카탈로그에 없는 콘텐츠 블록 → 소비 프로젝트 컴포넌트, `--hm-sem-*` 토큰과 `Card` 재사용
|
||||
|
||||
```css
|
||||
/* src/index.css */
|
||||
@import "tailwindcss";
|
||||
@import "@hm-design/ui/tailwind.css";
|
||||
@import "@hm-design/ui/theme.css";
|
||||
```
|
||||
|
||||
```tsx
|
||||
import { Button, Card, CardContent, FilterBar, FilterBarActions, FilterBarSearch, PageHeader, PageHeaderContent, PageHeaderTitle } from "@hm-design/ui"
|
||||
```
|
||||
|
||||
### 예시 B — SW 뷰어
|
||||
|
||||
- 읽기: `node_modules/@hm-design/sw/ai/` → COMPOSITION_PATTERNS "6. 파일 열기 + 도구 막대", 예제 `ai/examples/ExampleF-ViewerToolbar.tsx`
|
||||
- 재사용: `SWHeader`(`documentOpenPlacement="header"`, `controls`, `tools`), `SWToolGroup` + `SWToolButton`(Model/Layout 탭, Fit 등), `SWPopup` + `SWPopupSection` + `SWPopupRow` + `SWHeaderInput`(설정 Popup)
|
||||
- 신규: SW에 없는 기능(예: 검색) → 소비 프로젝트 컴포넌트, `<SWTheme>` 안, `--hm-sw-header-*` 토큰, 안쪽 입력은 `SWHeaderInput`(예제 `ExampleD-SearchExtension.tsx`)
|
||||
|
||||
```tsx
|
||||
import { SWHeader, SWPopup, SWTheme, SWToolButton, SWToolGroup } from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
```
|
||||
|
||||
### 예시 C — 조직도만 (Chart Only)
|
||||
|
||||
- 읽기: `node_modules/@hm-design/org-chart/ai/` → COMPOSITION_PATTERNS "1. Chart Only"
|
||||
- 재사용: React `OrgChart`(`@hm-design/org-chart-react`) 또는 Vanilla `createOrgChart`, 데이터 `JsonOrgChartDataSource` + `validateOrgChartDataset`
|
||||
- 필수 옵션: `layoutMode="auto"` + `presentationMode="chart-only"`(초기 Fit·직접 휠 Zoom이 기본)
|
||||
- **추가하지 않음:** 페이지 제목·통계·설명·Zoom 버튼 바·외부 회색 배경·Navigation. Fixed Chart Only는 미지원(`TypeError`)
|
||||
|
||||
```tsx
|
||||
import { OrgChart } from "@hm-design/org-chart-react"
|
||||
import { JsonOrgChartDataSource } from "@hm-design/org-chart"
|
||||
import engineUrl from "@hm-design/org-chart/engine.css?url" // Vite. 다른 도구는 실제 배포 URL
|
||||
import coreUrl from "@hm-design/org-chart/style.css?url"
|
||||
```
|
||||
|
||||
### 예시 D — Web + OrgChart (Full Page)
|
||||
|
||||
- 영역: 페이지 레이아웃·제목·필터 = Web, 조직도 영역 = OrgChart
|
||||
- 읽기: Web `ai/` + OrgChart `ai/COMPOSITION_PATTERNS.md` "2. Full Page"
|
||||
- 재사용: Web `LNB`/`GNB` 중 하나, `PageHeader`, `Card`, `Select`·`Label`(회사 선택 등 요청된 컨트롤만) + OrgChart `OrgChart`(`selectedCompanyIds` 등 Props로 연결)
|
||||
- 직원 팝업을 Web Dialog로: `import { OrgChart } from "@hm-design/org-chart-react/hm-dialog"` + `employeePopup="hm-dialog"`(Web CSS 진입점 필요)
|
||||
- 분리: 조직도 카드·색은 OrgChart 기본 유지. Web 토큰을 조직도에 넣지 않음(바꿔야 하면 `--hm-orgchart-*` 공개 입력만)
|
||||
|
||||
## 9. 소비 프로젝트 설정 — AI가 이 지침을 찾게 하기
|
||||
|
||||
AI 코딩 도구는 보통 **프로젝트 루트의** `AGENTS.md`(Codex 등)·`CLAUDE.md`(Claude Code)·도구별 규칙 파일만 자동으로 읽고 `node_modules`는 읽지 않습니다. 패키지 설치만으로 지침이 자동 적용되지는 않으므로 소비 프로젝트에서 한 번 연결합니다.
|
||||
|
||||
1. 패키지에 들어 있는 `ai/AGENTS_SNIPPET.md`를 루트 `AGENTS.md`(없으면 새로 만듦)에 붙여 넣고 `HMDesign 사용 유형:` 줄을 프로젝트에 맞게 고칩니다. 정하지 않았으면 그 줄을 지워 AI가 1단계에서 묻게 합니다.
|
||||
```bash
|
||||
cat node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md >> AGENTS.md
|
||||
```
|
||||
(PowerShell: `Get-Content node_modules/@hm-design/ui/ai/AGENTS_SNIPPET.md | Add-Content AGENTS.md`. 설치한 아무 HMDesign 패키지의 사본이나 같습니다.)
|
||||
2. Claude Code는 `CLAUDE.md`에 `@AGENTS.md` 한 줄을 두면 같은 내용을 읽습니다. 다른 도구(Cursor 규칙 등)는 그 도구의 규칙 파일에 같은 내용을 넣습니다.
|
||||
3. 패키지를 업데이트한 뒤 스니펫이 바뀌었는지 확인합니다(스니펫은 경로 안내만 하므로 보통 그대로 둡니다).
|
||||
|
||||
자동으로 되지 않는 것: 패키지 설치 시 루트 파일 생성(`postinstall` 없음), 도구가 `node_modules`를 스스로 탐색하는 것. 위 연결이 없으면 사용자가 "node_modules/@hm-design/…/ai/AI_GUIDE.md를 먼저 읽어"라고 요청해야 합니다.
|
||||
|
||||
## 10. 문서 갱신 (HMDesign 저장소 유지보수자)
|
||||
|
||||
컴포넌트를 추가·수정하면 같은 변경에서 해당 패키지 `ai/COMPONENT_CATALOG.md`와 필요한 `DESIGN_RULES.md`·`COMPOSITION_PATTERNS.md`를 갱신하고 `npm run check:ai-docs`를 통과시킵니다. 절차는 저장소 `docs/ai/MAINTAINING.md`(패키지 미포함)에 있습니다.
|
||||
@@ -0,0 +1,62 @@
|
||||
# @hm-design/ui — AI 코딩 도구 사용 안내 (Web UI)
|
||||
|
||||
먼저 [AI_GUIDE.md](./AI_GUIDE.md)(공통 1~7단계)를 읽고 사용 유형이 **Web** 또는 Web이 포함된 혼합일 때 이 폴더를 씁니다. 이 폴더(`node_modules/@hm-design/ui/ai/`)의 내용은 `@hm-design/ui` 0.1.0의 공개 Export(`src/index.ts` → `dist/index.d.ts`)와 `theme.css`에서 확인한 값만 적습니다.
|
||||
|
||||
| 문서 | 내용 |
|
||||
|---|---|
|
||||
| [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md) | 공개 컴포넌트 44종·Pattern 8종·Theme API, HM 전용 Props·Variant |
|
||||
| [DESIGN_RULES.md](./DESIGN_RULES.md) | 실제 토큰(`--hm-sem-*` 등), 색·Typography·크기·상태·스크롤바·Overlay 규칙 |
|
||||
| [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) | 화면 유형별 조합(목록·Form·Dashboard·확인창·측면 상세)과 Navigation 단일 모드 |
|
||||
|
||||
## 1. 작업 순서 (필수)
|
||||
|
||||
1. **Pattern을 먼저 본다.** 화면 단위 요구(목록 검색, 상단/좌측 메뉴, Dashboard, 페이지 제목, 로딩)는 [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) §0 표에서 Pattern을 고른다.
|
||||
2. **컴포넌트를 찾는다.** [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md)에서 요청 항목과 같은 역할을 찾는다. 같은 역할이 있으면 새로 만들지 않는다.
|
||||
3. **Variant·Props로 맞춘다.** `Button`의 `variant` × `appearance`, `Input`/`Select`/`DatePicker`/`Combobox`의 `size`, `Card`·`Tabs`의 `surface` 등 기존 Props로 해결한다. `<Button>`처럼 기본 API만으로 HM 외형이 적용된다. `hm-*` class를 붙이거나 Story 전용 class로 외형을 보정하지 않는다.
|
||||
4. **조합한다.** 여러 컴포넌트를 배치하고 배치용 레이아웃(flex/grid, 간격)만 소비 프로젝트에 작성한다.
|
||||
5. **없을 때만 새로 만든다.** 소비 프로젝트에 작성하고, 외형은 [DESIGN_RULES.md](./DESIGN_RULES.md)의 토큰만 `var(--…)`로 읽으며, 안쪽에는 기존 컴포넌트(`Card`, `Button`, `Label` …)를 재사용한다.
|
||||
6. **추측하지 않는다.** 카탈로그·`dist/index.d.ts`에 없는 Export·Props·토큰은 없다. 기본 HTML·임의 CSS·`data-slot` 흉내로 HM 컴포넌트를 모방하지 않는다(레이아웃·콘텐츠용 HTML은 허용).
|
||||
7. **실제로 검증한다.** [AI_GUIDE.md](./AI_GUIDE.md) 6단계.
|
||||
|
||||
## 2. 설치·Import
|
||||
|
||||
```tsx
|
||||
import { Button, Card, Dialog, DataTable, useDataTable } from "@hm-design/ui"
|
||||
import { applyRuntimeTheme, resetRuntimeTheme, loadRuntimeTheme } from "@hm-design/ui/theme"
|
||||
```
|
||||
|
||||
앱 CSS 진입점(Tailwind CSS 4 통합이 처리하는 파일)에 이 순서로 넣는다.
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "@hm-design/ui/tailwind.css";
|
||||
@import "@hm-design/ui/theme.css";
|
||||
/* Dashboard 업무 상태 색이 필요할 때만 */
|
||||
@import "@hm-design/ui/dashboard-status.css";
|
||||
```
|
||||
|
||||
- 공개 진입점은 `.`, `./theme`, `./theme.css`, `./tailwind.css`, `./dashboard-status.css`뿐이다. `@hm-design/ui/dist/components/...` 같은 내부 경로를 import하지 않는다.
|
||||
- Peer: `react`, `react-dom` `^19.0.0`, `tailwindcss` `^4.0.0`. Tailwind CSS 4가 없는 프로젝트에서는 HM 외형이 적용되지 않는다(설치가 필요하다고 보고).
|
||||
- `tailwind.css`가 패키지의 compiled class를 `@source`로 등록하므로 shadcn CLI 설치나 `components.json`이 필요 없다.
|
||||
- Noto Sans KR 글꼴과 OFL 라이선스는 `theme.css`에 포함된다(`dist/fonts/`).
|
||||
- 아이콘은 패키지 의존성인 `lucide-react`를 쓴다.
|
||||
|
||||
## 3. 스타일 규칙
|
||||
|
||||
- HM이 값을 정한 속성은 Theme Token으로 적용되어 있고, 정하지 않은 속성은 shadcn 기본 그대로다. 새 화면에서도 같은 구분을 지킨다.
|
||||
- `className`은 배치 확장용이다. HM 컴포넌트에 HEX·RGB·임의 px Spacing/Radius를 덮어쓰지 않는다.
|
||||
- Light/Dark 분기 없음. `dark:` 분기·Dark Token을 추가하지 않는다.
|
||||
- 테마 변경은 Runtime Theme(`applyRuntimeTheme(root, theme)`, `loadRuntimeTheme(root, url)`)만 쓴다. `theme`은 `{ schemaVersion: 1, tokens: { "color.bg.content": "#…" } }` 형식이며 키는 `runtimeThemeContract`에 있는 것만 유효하다.
|
||||
- 한 페이지의 Navigation은 GNB / LNB / DASHBOARD 중 하나다([COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md) §1).
|
||||
|
||||
## 4. 접근성 기본
|
||||
|
||||
- 입력 항목명은 `Label htmlFor`와 control `id`를 연결한다(`useId`). placeholder는 항목명이 아니다. `SelectTrigger`·`Combobox`도 `id` 또는 `aria-labelledby`를 연결한다.
|
||||
- Dialog·AlertDialog·Sheet·Drawer는 Title(필요 시 Description)을 넣는다. Escape와 Trigger Focus 복귀는 Radix 동작을 그대로 쓴다.
|
||||
- 기존 컴포넌트의 `aria-*` 연결 속성을 임의로 덮어쓰지 않는다.
|
||||
|
||||
## 5. 이 패키지에 없는 것
|
||||
|
||||
- 공개 Form Pattern, Empty·Error 전용 컴포넌트, Stepper, Tree, Chart(그래프) 컴포넌트는 없다. Form은 `Label` + `Input`/`Textarea`/`Select`/`Checkbox`/`RadioGroup`/`Switch`를 `Card` 안에 조합하고, 빈 목록은 `DataTable`의 `emptyMessage`, 오류 안내는 `Alert`로 만든다.
|
||||
- Badge에 success / warning / information variant는 없다(HM Theme에 상태 팔레트가 아직 없음). 새 상태색을 만들지 않는다.
|
||||
- 상세 컴포넌트 문서·Storybook은 HMDesign 저장소(`docs/components/<name>.md`, `docs/patterns/`)에 있고 패키지에는 포함되지 않는다. 패키지만 있을 때는 이 폴더와 `.d.ts`가 기준이다.
|
||||
@@ -0,0 +1,125 @@
|
||||
# @hm-design/ui 컴포넌트 카탈로그 (Web UI)
|
||||
|
||||
기준: `@hm-design/ui` 0.1.0, 공개 Export(`src/index.ts` → `dist/index.d.ts`, `dist/theme/index.d.ts`). 모든 컴포넌트 import는 `from "@hm-design/ui"`, Theme API는 `from "@hm-design/ui/theme"`입니다. **이 표에 없는 이름은 공개 API가 아닙니다.** 저장소의 `npm run check:ai-docs`가 이 문서와 실제 Export의 일치를 검사합니다.
|
||||
|
||||
- **구조:** shadcn/ui(Radix) 구조와 공개 API를 유지하고 외형만 HM Theme로 바꿨습니다. 아래 "HM Props"에 없는 Props는 shadcn·Radix 원래 Props입니다.
|
||||
- **상태 소유:** 대부분 Radix처럼 `open`/`onOpenChange`, `value`/`onValueChange`(Controlled)와 `default*`(Uncontrolled)를 지원합니다.
|
||||
- **외형 보정 금지:** HM 외형은 기본 API만으로 적용됩니다. `className`은 배치용이며 색·간격·radius를 덮어쓰지 않습니다.
|
||||
|
||||
## 1. Form
|
||||
|
||||
| 컴포넌트 (Export) | 용도 | HM Props·Variant (기본값) |
|
||||
|---|---|---|
|
||||
| `Button`, `buttonVariants` / 타입 `ButtonAppearance` | 작업 실행 | `variant`: `primary`(기본) · `secondary`(Brown) · `modify`(Orange) · `destructive`(Red) · `cancel`(Gray), 별칭 `default`(=primary) · `outline`(=primary+outline), 호환 `ghost` · `link`(HM 외형 없음). `appearance`: `brand`(기본) · `solid` · `outline`. `size`: HM 규격은 `sm`(28) · `default`(34) · `lg`(42), `xs`·`icon*`는 shadcn 기본. `asChild` |
|
||||
| `Input` / 타입 `InputSize` | 한 줄 입력 | `size`: `small`(28) · `medium`(34) · `large`(42, 기본). 상태는 native `disabled`·`readOnly`·`aria-invalid` |
|
||||
| `Textarea` / 타입 `TextareaResize` | 여러 줄 입력 | `resize`: `vertical`(기본) · `none` · `both` |
|
||||
| `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectScrollUpButton`, `SelectScrollDownButton` / 타입 `SelectSize` | 목록 선택 | `SelectTrigger size`: `medium`(34) · `large`(44, 기본). Content·Item은 Trigger 크기를 따름 |
|
||||
| `Label` | 입력 라벨 | 없음. `htmlFor` ↔ control `id` 연결 |
|
||||
| `Checkbox` | 복수 선택 | 없음(Radix) |
|
||||
| `RadioGroup`, `RadioGroupItem` | 단일 선택 | 없음(Radix) |
|
||||
| `Switch` | 켜기·끄기 | 없음(Radix) |
|
||||
| `Slider` | 범위 값 | 없음(Radix) |
|
||||
| `Toggle`, `toggleVariants` | 선택 상태 버튼 | `variant`: `default` · `outline`, `size`: `default` · `sm` · `lg` |
|
||||
| `ToggleGroup`, `ToggleGroupItem` | 선택 버튼 묶음 | `variant`, `size`(Toggle과 같음), `spacing` |
|
||||
|
||||
**Button 조합 규칙(필수).** 디자인이 있는 조합만 씁니다. TypeScript가 통과해도 아래에 없는 조합은 디자인이 없습니다.
|
||||
|
||||
| variant | 허용 appearance |
|
||||
|---|---|
|
||||
| `primary` | `brand`, `solid`, `outline` |
|
||||
| `secondary` | `brand`, `solid`, `outline` |
|
||||
| `modify` | `brand`, `solid` (outline 없음) |
|
||||
| `cancel` | `solid`, `outline` (brand 없음) |
|
||||
| `destructive` | `solid`, `outline` (brand 없음) |
|
||||
|
||||
- `brand`는 페이지 대표 실행 1개에만 씁니다(팝업 안 금지). 일반 실행·저장·확인은 `solid`, 반복·보조·표 행 작업은 `outline`.
|
||||
- 결정 세트(취소 + 저장/확인/삭제)는 같은 `appearance`·`size`: 예) `<Button variant="cancel" appearance="solid">취소</Button>` + `<Button appearance="solid">저장</Button>`.
|
||||
- 기본값이 `brand`이므로 `variant="cancel"`·`"destructive"`만 쓰면 미지원 조합이 됩니다. 항상 `appearance`를 명시합니다.
|
||||
- 검색 영역: 조회 `primary`/`solid` + 초기화 `cancel`/`outline`, 같은 size.
|
||||
|
||||
## 2. Overlay
|
||||
|
||||
| 컴포넌트 (Export) | 용도 | HM Props·주의 |
|
||||
|---|---|---|
|
||||
| `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogBody`, `DialogFooter`, `DialogClose`, `DialogOverlay`, `DialogPortal` | 모달 | `DialogBody`(HM 추가: 본문 여백·스크롤, Header·Footer 고정). `DialogContent showCloseButton`(기본 true), `DialogFooter showCloseButton`(기본 false) |
|
||||
| `AlertDialog`, `AlertDialogTrigger`, `AlertDialogContent`, `AlertDialogHeader`, `AlertDialogTitle`, `AlertDialogDescription`, `AlertDialogFooter`, `AlertDialogAction`, `AlertDialogCancel`, `AlertDialogOverlay`, `AlertDialogPortal` | 작업 확인 | `AlertDialogAction`: `variant`(기본 `primary`)·`appearance`(기본 `solid`)·`size`. `AlertDialogCancel`: `variant`(기본 `cancel`)·`appearance`(기본 `solid`). 삭제 확인 = Action `variant="destructive"` |
|
||||
| `Sheet`, `SheetTrigger`, `SheetContent`, `SheetHeader`, `SheetTitle`, `SheetDescription`, `SheetBody`, `SheetFooter`, `SheetClose`, `SheetOverlay`, `SheetPortal` | 측면 패널 | `SheetContent side`: `right`(기본) · `left` · `top` · `bottom`, `showCloseButton`(기본 true) |
|
||||
| `Drawer`, `DrawerTrigger`, `DrawerContent`, `DrawerHeader`, `DrawerTitle`, `DrawerDescription`, `DrawerBody`, `DrawerFooter`, `DrawerClose`, `DrawerOverlay`, `DrawerPortal` | 하단 슬라이드(vaul) | 드래그 동작은 vaul 기본 |
|
||||
| `Popover`, `PopoverTrigger`, `PopoverContent`, `PopoverAnchor`, `PopoverClose` | 보조 팝업 | 없음 |
|
||||
| `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider` | 보조 설명 | 없음 |
|
||||
| `HoverCard`, `HoverCardTrigger`, `HoverCardContent` | Hover 카드 | 없음 |
|
||||
| `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, `DropdownMenuCheckboxItem`, `DropdownMenuRadioGroup`, `DropdownMenuRadioItem`, `DropdownMenuLabel`, `DropdownMenuSeparator`, `DropdownMenuShortcut`, `DropdownMenuGroup`, `DropdownMenuPortal`, `DropdownMenuSub`, `DropdownMenuSubTrigger`, `DropdownMenuSubContent` | 작업 메뉴 | `DropdownMenuItem variant`: `default` · `destructive`, `inset` |
|
||||
| `ContextMenu`, `ContextMenuTrigger`, `ContextMenuContent`, `ContextMenuItem`, `ContextMenuCheckboxItem`, `ContextMenuRadioGroup`, `ContextMenuRadioItem`, `ContextMenuLabel`, `ContextMenuSeparator`, `ContextMenuShortcut`, `ContextMenuGroup`, `ContextMenuPortal`, `ContextMenuSub`, `ContextMenuSubTrigger`, `ContextMenuSubContent` | 우클릭 메뉴 | DropdownMenu와 같음 |
|
||||
| `Toaster`, `toast` / 타입 `ExternalToast`, `ToasterProps` | 토스트(sonner) | 앱에 `<Toaster />` 한 번, 호출은 `toast("…")` |
|
||||
|
||||
## 3. Navigation (컴포넌트)
|
||||
|
||||
| 컴포넌트 (Export) | 용도 | HM Props |
|
||||
|---|---|---|
|
||||
| `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` / 타입 `TabsPanelSurface` | 탭 | `TabsList fill`(기본 true = 균등 폭 Primary Fill, false = Legacy), `TabsList panelSurface`: `content`(기본) · `surface` — 실제 배경(Card `surface`)과 맞춤 |
|
||||
| `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage`, `BreadcrumbSeparator`, `BreadcrumbEllipsis` | 현재 경로 | `BreadcrumbLink asChild` |
|
||||
| `Pagination`, `PaginationContent`, `PaginationItem`, `PaginationLink`, `PaginationPrevious`, `PaginationNext`, `PaginationEllipsis` | 페이지 선택 | `PaginationLink isActive`, `size`(기본 `icon`) |
|
||||
| `NavigationMenu`, `NavigationMenuList`, `NavigationMenuItem`, `NavigationMenuTrigger`, `NavigationMenuContent`, `NavigationMenuLink`, `NavigationMenuIndicator`, `NavigationMenuViewport` | 링크 메뉴 | `NavigationMenu viewport` |
|
||||
| `Menubar`, `MenubarMenu`, `MenubarTrigger`, `MenubarContent`, `MenubarItem`, `MenubarCheckboxItem`, `MenubarRadioGroup`, `MenubarRadioItem`, `MenubarLabel`, `MenubarSeparator`, `MenubarShortcut`, `MenubarGroup`, `MenubarPortal`, `MenubarSub`, `MenubarSubTrigger`, `MenubarSubContent` | 메뉴 막대 | DropdownMenu와 같음 |
|
||||
|
||||
페이지 Navigation(상단·좌측 메뉴)은 컴포넌트가 아니라 Pattern `GNB` / `LNB` / `DashboardLayout` 중 하나를 씁니다(§6).
|
||||
|
||||
## 4. Data & Display
|
||||
|
||||
| 컴포넌트 (Export) | 용도 | HM Props |
|
||||
|---|---|---|
|
||||
| `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, `CardFooter` / 타입 `CardSurface` | 콘텐츠 묶음 | `surface`: `surface`(White, 기본) · `content`(Beige), `size`: `default` · `sm` |
|
||||
| `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead`, `TableCell`, `TableCaption` | 표(표시만) | 없음. 정렬·선택·페이지가 필요하면 Pattern `DataTable` |
|
||||
| `Badge`, `badgeVariants` | 상태·라벨 | `variant`: `default` · `secondary` · `destructive` · `outline`(success/warning/info 없음), `asChild` |
|
||||
| `Alert`, `AlertTitle`, `AlertDescription` | 안내·경고 | `variant`: `default` · `destructive` |
|
||||
| `Avatar`, `AvatarImage`, `AvatarFallback` | 사용자 이미지 | `size`: `default` · `sm` · `lg` |
|
||||
| `Separator` | 구분선 | 없음 |
|
||||
| `Progress` | 진행률 | 없음 |
|
||||
| `Skeleton` | 로딩 자리 | 없음 |
|
||||
| `ScrollArea`, `ScrollBar` | 스크롤 영역(HM 스크롤바) | 없음. 스크롤바 외형은 DESIGN_RULES §6 |
|
||||
| `AspectRatio` | 비율 유지 | 없음 |
|
||||
| `Carousel`, `CarouselContent`, `CarouselItem`, `CarouselPrevious`, `CarouselNext`, `useCarousel` / 타입 `CarouselApi` | 슬라이드(embla) | `opts`, `plugins`, `orientation`, `setApi` |
|
||||
| `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` | 접고 펼치기 | 없음 |
|
||||
| `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` | 영역 접기 | 없음 |
|
||||
| `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle` | 크기 조절 | `ResizableHandle withHandle` |
|
||||
|
||||
## 5. Search & Date
|
||||
|
||||
| 컴포넌트 (Export) | 용도 | HM Props |
|
||||
|---|---|---|
|
||||
| `Calendar` | 달력(react-day-picker) | react-day-picker Props |
|
||||
| `DatePicker` / 타입 `DatePickerSize` | 날짜 입력 | `value`·`defaultValue`(Date), `onValueChange`, `controlled`, `placeholder`, `size`: `medium` · `large`(기본), `format`(기본 `YYYY.MM.DD`), `calendarProps`(disabled·startMonth·endMonth·locale·captionLayout·weekStartsOn·today) |
|
||||
| `Combobox` / 타입 `ComboboxOption`, `ComboboxSize` | 검색 가능한 선택 | `options: { value, label, disabled? }[]`(필수), `value`·`defaultValue`, `onValueChange`, `controlled`, `placeholder`, `searchPlaceholder`, `emptyText`, `size`: `medium` · `large`(기본) |
|
||||
| `Command`, `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem`, `CommandSeparator`, `CommandShortcut` | 명령 검색(cmdk) | 없음 |
|
||||
|
||||
## 6. Pattern (업무 화면 단위)
|
||||
|
||||
Pattern은 위 컴포넌트를 조합한 것이며 자체 primitive 외형을 추가하지 않습니다. 조합 예시는 [COMPOSITION_PATTERNS.md](./COMPOSITION_PATTERNS.md).
|
||||
|
||||
| Pattern (Export) | 용도 | 주요 Props |
|
||||
|---|---|---|
|
||||
| `DataTable`, `useDataTable`, `DataTableColumnHeader`, `DataTablePagination`, `DataTableViewOptions`, `dataTableSelectColumn` / 타입 `DataTableColumnDef`, `DataTableInstance`, `DataTableOptions`, `DataTableProps` | 데이터 표(TanStack Table v9: 정렬·필터·선택·페이지·열 표시) | `useDataTable({ columns, data, … })`(columns·data는 참조 안정), `DataTable table loading emptyMessage pagination(기본 true) pageSizeOptions`, `DataTableColumnHeader column title`, `DataTableViewOptions table label labels`, `dataTableSelectColumn()` |
|
||||
| `FilterBar`, `FilterBarSearch`, `FilterBarActions` / 타입 `FilterBarProps`, `FilterBarSearchProps` | 조회 조건 행(레이아웃) | `FilterBar layout`: `default` · `list-basic`(가운데) · `list-extended`(추가 조건 왼쪽). `FilterBarSearch value onValueChange size`(기본 `medium`) `placeholder`(기본 "검색") |
|
||||
| `GNB`, `GNBBrand`, `GNBNav`, `GNBUtilities`, `GNBUser`, `GNBMobileMenu`, `GNBMobileLink`, `GNBMobileGroup` / 타입 `GNBUserProps`, `GNBMobileMenuProps` | 상단 Navigation | `GNB sticky`, `GNBBrand asChild`, `GNBUser name description`, `GNBMobileMenu title side open defaultOpen onOpenChange`, `GNBMobileLink active asChild`, `GNBMobileGroup label` |
|
||||
| `LNB`, `LNBBrand`, `LNBNav`, `LNBItem`, `LNBGroup`, `LNBProfile` / 타입 `LNBGroupProps`, `LNBProfileProps` | 좌측 Navigation(3뎁스까지 `LNBGroup` 중첩) | `LNBItem active asChild`, `LNBGroup label current`, `LNBProfile name description` |
|
||||
| `LNBMobileHeader`, `LNBMobileBrand`, `LNBMobileMenu`, `LNBMobileLink`, `LNBMobileGroup` / 타입 `LNBMobileMenuProps` | LNB 모바일 Header + Sheet | `LNBMobileMenu title side open defaultOpen onOpenChange`, `LNBMobileLink active` |
|
||||
| `PageHeader`, `PageHeaderContent`, `PageHeaderTitle`, `PageHeaderDescription`, `PageHeaderActions` | 페이지 제목·설명·주요 작업 | HTML 속성만 |
|
||||
| `DashboardLayout`, `DashboardHeader`, `DashboardMain`, `DashboardContent`, `DashboardSidebar` / 타입 `DashboardLayoutProps` | Dashboard 전용 레이아웃(Header 48px, 최소 폭 960px·내부 스크롤) | `DashboardLayout header sidebar children`. **신규 화면은 `sidebar`·`DashboardSidebar`를 쓰지 않음**(호환성 전용) |
|
||||
| `dashboardStatusDefinitions`, `dashboardStatusAttributes`, `dashboardFilterDefinitions`, `dashboardFilterIds`, `countDashboardFilter`, `matchesDashboardFilter` / 타입 `DashboardStatusId`, `DashboardStatusDefinition`, `DashboardStatusRecord`, `DashboardStatusTone`, `DashboardFilterId`, `DashboardFilterDefinition` | Dashboard 업무 상태·필터 계약(데이터 helper) | 색은 `@hm-design/ui/dashboard-status.css`의 `--hm-dashboard-status-active` · `-hold` · `-done` · `-issue` 변수만 제공. Badge 등 자동 스타일 없음 — 소비자가 연결 |
|
||||
| `LoadingScreen` | 전체·부분 로딩 | `fullscreen`(기본 false), `children`(기본 "Loading") |
|
||||
|
||||
## 7. Theme API (`@hm-design/ui/theme`)
|
||||
|
||||
| Export | 용도 |
|
||||
|---|---|
|
||||
| `applyRuntimeTheme(root, theme)` → `boolean` | `root`(예: `document.documentElement` 또는 `[data-hm-theme]` 요소)에 Runtime Theme 적용. 잘못된 입력은 전체 거부(false) |
|
||||
| `resetRuntimeTheme(root)` | 적용한 값 제거, HM Default로 복원 |
|
||||
| `isRuntimeTheme(value)` | 형식 검사 |
|
||||
| `fetchRuntimeTheme(url, request?)`, `resolveRuntimeTheme(theme, defaults?)`, `loadRuntimeTheme(root, url, options?)` / 타입 `RuntimeThemeLoadOptions` | URL의 Theme JSON 불러오기·병합·적용 |
|
||||
| `runtimeThemeContract`, `defaultRuntimeTheme` / 타입 `RuntimeTheme`, `RuntimeThemeToken` | 계약(토큰 키 → CSS 변수)과 HM Default 값. `RuntimeTheme = { schemaVersion: 1, tokens: Partial<Record<RuntimeThemeToken, string>> }` |
|
||||
|
||||
Theme JSON 원본·CDN은 패키지에 포함되지 않습니다(URL은 소비 앱이 정함).
|
||||
|
||||
## 8. 공개하지 않은 것
|
||||
|
||||
`cn`, 내부 `lib/utils`, 각 컴포넌트 파일의 비공개 helper, `components/ui/*` 직접 경로, `theme/*.css` 개별 파일은 공개 API가 아닙니다. shadcn CLI로 같은 컴포넌트를 소비 프로젝트에 다시 추가하지 않습니다(같은 이름의 중복 컴포넌트가 됨).
|
||||
@@ -0,0 +1,236 @@
|
||||
# Web(@hm-design/ui) 조합 패턴
|
||||
|
||||
모든 패턴은 공개 Export만 씁니다([COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md)). 코드는 형태를 보여 주는 예이며, 실제 화면에서는 **요청된 메뉴·조건·버튼만** 넣습니다(예시의 항목을 요구사항으로 복사하지 않음).
|
||||
|
||||
## 0. 어떤 패턴을 고를까
|
||||
|
||||
| 요청 | 먼저 고를 것 | 조합 |
|
||||
|---|---|---|
|
||||
| 목록 검색·필터 | `FilterBar`(표가 없어도 먼저 검토), 표가 필요하면 `DataTable` | `PageHeader` 아래 `Card` 하나 안에 FilterBar + DataTable. 조건 → 조회 → 초기화, draft/applied 분리(§3) |
|
||||
| 업무 Dashboard | `DashboardLayout` + `DashboardHeader` + `DashboardMain` + `DashboardContent` | Header 48px, 최소 폭 960px·내부 가로 스크롤, Sidebar 없음. 상태는 Dashboard Status 계약(§6) |
|
||||
| Form(등록·수정) | 공개 Form Pattern 없음 | `Card` 안 `Label` + `Input`/`Textarea`/`Select`/`Checkbox`/`RadioGroup`/`Switch`, `useId`·`htmlFor`, 오류는 `aria-invalid` + 메시지, 첫 오류 Focus(§4) |
|
||||
| 서비스 상단 메뉴 | `GNB` + `GNBMobileMenu` | 요청된 경로만 Desktop·Mobile에 같게. Sidebar 없음(§1) |
|
||||
| 계층 좌측 메뉴 | `LNB` + `LNBMobileHeader`/`LNBMobileMenu` | 3뎁스까지 `LNBGroup` 중첩(§1) |
|
||||
| 작업 확인 | `AlertDialog`(또는 `Dialog`) | Title/Description, 취소/진행, Escape·Trigger Focus 복귀. 취소·삭제 모두 Solid(§5) |
|
||||
| 측면 상세 | `Sheet` | 메뉴 대체로 쓰지 않음 |
|
||||
| 하단 작업 | `Drawer` | desktop 취소→실행, mobile 실행 위·취소 아래 |
|
||||
| 초기·부분 로딩 | `LoadingScreen` 또는 `Skeleton` / `DataTable loading` | 기존 콘텐츠 유지 여부는 요구사항으로 결정 |
|
||||
| 빈 목록·오류 | `DataTable emptyMessage` / 콘텐츠 HTML / `Alert` | 별도 Empty 컴포넌트 만들지 않음 |
|
||||
| 패키지에 없는 블록 | 소비 프로젝트 신규 | `Card`·`Button` 등 재사용 + [DESIGN_RULES.md](./DESIGN_RULES.md) 토큰(§7) |
|
||||
|
||||
## 1. Navigation 단일 모드 (필수)
|
||||
|
||||
한 페이지 = 하나의 Navigation: **GNB ONLY** · **LNB ONLY** · **DASHBOARD ONLY** 중 하나.
|
||||
|
||||
| 모드 | 구조 | 금지 |
|
||||
|---|---|---|
|
||||
| GNB | `GNB`(+ `GNBMobileMenu`) + Content | Sidebar·LNB·Dashboard와 혼합 |
|
||||
| LNB | `LNB`(Desktop) + `LNBMobileHeader`/`LNBMobileMenu`(모바일 <1024px) + Content | GNB·Dashboard와 혼합 |
|
||||
| DASHBOARD | `DashboardLayout header={<DashboardHeader/>}` + Content | Sidebar·LNB·GNB(`sidebar` prop·`DashboardSidebar`는 호환성 전용) |
|
||||
|
||||
- 요청이 모드를 말하지 않으면 화면 목적(서비스 상단 메뉴 = GNB, 계층 업무 메뉴 = LNB, 운영 현황 = DASHBOARD)으로 고르고 **선택 근거를 보고**합니다. 임의로 Sidebar를 추가하지 않습니다.
|
||||
- 혼합 요청(GNB + LNB 등)은 정책 충돌로 안내하고 하나를 고르게 합니다. Drawer·Hamburger 등 대체 메뉴를 추측해 만들지 않습니다.
|
||||
- Breadcrumb·FilterBar·사용자 Dropdown은 Navigation 모드가 아닙니다.
|
||||
|
||||
```tsx
|
||||
// LNB ONLY 골격
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[var(--hm-lnb-width)_minmax(0,1fr)]">
|
||||
<LNB>
|
||||
<LNBBrand><span><small>업무 시스템</small><strong>서비스명</strong></span></LNBBrand>
|
||||
<LNBNav aria-label="서비스 메뉴">
|
||||
<LNBItem href="/list" active>목록</LNBItem>
|
||||
<LNBGroup label="관리" current>
|
||||
<LNBItem href="/admin/users">사용자</LNBItem>
|
||||
</LNBGroup>
|
||||
</LNBNav>
|
||||
</LNB>
|
||||
<div className="app-content-stack">
|
||||
<LNBMobileHeader>
|
||||
<LNBMobileMenu title="서비스 메뉴">
|
||||
<LNBMobileLink href="/list" active>목록</LNBMobileLink>
|
||||
<LNBMobileGroup label="관리"><LNBMobileLink href="/admin/users">사용자</LNBMobileLink></LNBMobileGroup>
|
||||
</LNBMobileMenu>
|
||||
<LNBMobileBrand href="/">서비스명</LNBMobileBrand>
|
||||
</LNBMobileHeader>
|
||||
<main className="app-page-content">…</main>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
/* 소비 레이아웃(높이·스크롤)만. HM 면·간격은 그대로. --hm-lnb-width는 패키지 LNB 변수 */
|
||||
.app-content-stack { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
|
||||
.app-page-content { position: relative; flex: 1; min-height: 0; min-width: 0; overflow: auto; }
|
||||
```
|
||||
|
||||
```tsx
|
||||
// GNB ONLY 골격 — NavigationMenu는 viewport={false} 필수
|
||||
<GNB sticky>
|
||||
<GNBMobileMenu title="메뉴"><GNBMobileLink href="/" active>대시보드</GNBMobileLink></GNBMobileMenu>
|
||||
<GNBBrand href="/"><span><small>업무 시스템</small><strong>서비스명</strong></span></GNBBrand>
|
||||
<GNBNav>
|
||||
<NavigationMenu viewport={false} aria-label="주 메뉴">
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem><NavigationMenuLink href="/" active>대시보드</NavigationMenuLink></NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</GNBNav>
|
||||
<GNBUtilities>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild><GNBUser name="홍길동" description="관리자"><Avatar><AvatarFallback>홍</AvatarFallback></Avatar></GNBUser></DropdownMenuTrigger>
|
||||
<DropdownMenuContent><DropdownMenuItem variant="destructive">로그아웃</DropdownMenuItem></DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</GNBUtilities>
|
||||
</GNB>
|
||||
```
|
||||
|
||||
## 2. 페이지 제목
|
||||
|
||||
```tsx
|
||||
<PageHeader>
|
||||
<PageHeaderContent>
|
||||
<PageHeaderTitle>사용자 목록</PageHeaderTitle>
|
||||
<PageHeaderDescription>등록된 사용자를 조회합니다.</PageHeaderDescription>
|
||||
</PageHeaderContent>
|
||||
<PageHeaderActions>
|
||||
<Button appearance="brand">사용자 등록</Button>{/* 페이지 대표 실행 1개만 brand */}
|
||||
</PageHeaderActions>
|
||||
</PageHeader>
|
||||
```
|
||||
|
||||
## 3. 목록 화면 — FilterBar + DataTable (조회 버튼 적용)
|
||||
|
||||
`draft` = 입력 중 조건, `applied` = 목록에 적용한 조건. 조회 클릭에만 복사하고, 초기화는 두 상태와 페이지를 함께 비웁니다.
|
||||
|
||||
```tsx
|
||||
import { useMemo, useState } from "react"
|
||||
import {
|
||||
Button, Card, CardContent, DataTable, FilterBar, FilterBarActions, FilterBarSearch,
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue, useDataTable,
|
||||
type DataTableColumnDef,
|
||||
} from "@hm-design/ui"
|
||||
|
||||
type User = { id: string; name: string; status: "active" | "inactive" }
|
||||
type Conditions = { query: string; status: string }
|
||||
const empty: Conditions = { query: "", status: "all" }
|
||||
const rows: User[] = [
|
||||
{ id: "U-1", name: "김한맥", status: "active" },
|
||||
{ id: "U-2", name: "이한맥", status: "inactive" },
|
||||
]
|
||||
const columns: DataTableColumnDef<User>[] = [ // 모듈 상수(참조 안정)
|
||||
{ accessorKey: "name", header: "이름" },
|
||||
{ accessorKey: "status", header: "상태", cell: ({ row }) => (row.original.status === "active" ? "활성" : "비활성") },
|
||||
]
|
||||
|
||||
export function UserList() {
|
||||
const [draft, setDraft] = useState<Conditions>({ ...empty })
|
||||
const [applied, setApplied] = useState<Conditions>({ ...empty })
|
||||
const data = useMemo(() => rows.filter(row =>
|
||||
row.name.toLocaleLowerCase().includes(applied.query.trim().toLocaleLowerCase()) &&
|
||||
(applied.status === "all" || row.status === applied.status)
|
||||
), [applied])
|
||||
const table = useDataTable({ columns, data, getRowId: row => row.id })
|
||||
const apply = () => { setApplied({ ...draft }); table.setPageIndex(0) }
|
||||
const reset = () => { setDraft({ ...empty }); setApplied({ ...empty }); table.setPageIndex(0) }
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<FilterBar layout="list-basic">
|
||||
<Select value={draft.status} onValueChange={status => setDraft(s => ({ ...s, status }))}>
|
||||
<SelectTrigger size="medium" aria-label="사용자 상태"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">전체 상태</SelectItem>
|
||||
<SelectItem value="active">활성</SelectItem>
|
||||
<SelectItem value="inactive">비활성</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FilterBarSearch value={draft.query} onValueChange={query => setDraft(s => ({ ...s, query }))} placeholder="이름 검색" />
|
||||
<FilterBarActions>
|
||||
<Button type="button" variant="primary" appearance="solid" onClick={apply}>조회</Button>
|
||||
<Button type="button" variant="cancel" appearance="outline" onClick={reset}>초기화</Button>
|
||||
</FilterBarActions>
|
||||
</FilterBar>
|
||||
<p role="status">조회 결과 {data.length}건</p>
|
||||
<DataTable table={table} emptyMessage="조건에 맞는 사용자가 없습니다." />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- 정렬 Header: `header: ({ column }) => <DataTableColumnHeader column={column} title="이름" />`. 선택 열: `dataTableSelectColumn<User>()`를 columns 맨 앞에.
|
||||
- TanStack Table **v9** API입니다(v8의 `useReactTable`·`getCoreRowModel` 예제를 쓰지 않음). 필터: `table.setGlobalFilter`, `table.getColumn(id)?.setFilterValue`, 초기화 `resetGlobalFilter(true)`·`resetColumnFilters(true)`.
|
||||
- `ColumnDef.size`는 th/td 폭으로 연결되지 않습니다. 긴 ID는 셀 콘텐츠에 `whitespace-nowrap` 등을 주고 실제 폭을 확인합니다.
|
||||
|
||||
## 4. Form
|
||||
|
||||
```tsx
|
||||
const nameId = useId()
|
||||
<Card>
|
||||
<CardHeader><CardTitle>사용자 등록</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<Label htmlFor={nameId}>이름</Label>
|
||||
<Input id={nameId} size="large" aria-invalid={error ? true : undefined} aria-describedby={error ? `${nameId}-error` : undefined} />
|
||||
{error && <p id={`${nameId}-error`} role="alert">{error}</p>}
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button variant="cancel" appearance="solid">취소</Button>
|
||||
<Button appearance="solid">저장</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
```
|
||||
|
||||
- 결정 세트는 같은 `appearance`·`size`. 오류 메시지 색이 필요하면 `--hm-sem-status-error`(잠정 토큰) 또는 `Alert variant="destructive"`.
|
||||
|
||||
## 5. 확인창 (삭제)
|
||||
|
||||
```tsx
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild><Button variant="destructive" appearance="outline">삭제</Button></AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>사용자를 삭제할까요?</AlertDialogTitle>
|
||||
<AlertDialogDescription>삭제한 사용자는 복구할 수 없습니다.</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>취소</AlertDialogCancel>{/* 기본 cancel + solid */}
|
||||
<AlertDialogAction variant="destructive">삭제</AlertDialogAction>{/* 기본 solid */}
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
```
|
||||
|
||||
일반 작성·수정 모달은 `Dialog` + `DialogHeader` + `DialogBody`(스크롤) + `DialogFooter`, 버튼은 취소 Solid + 저장 Solid(팝업에 `brand` 금지).
|
||||
|
||||
## 6. Dashboard
|
||||
|
||||
```tsx
|
||||
<DashboardLayout header={<DashboardHeader>…요청된 제목·상태 필터만…</DashboardHeader>}>
|
||||
<DashboardMain>
|
||||
<DashboardContent>…</DashboardContent>
|
||||
</DashboardMain>
|
||||
</DashboardLayout>
|
||||
```
|
||||
|
||||
- 업무 상태: `dashboardStatusDefinitions`·`dashboardFilterDefinitions`·`matchesDashboardFilter`·`countDashboardFilter`로 필터·집계를 하고, 색은 `@hm-design/ui/dashboard-status.css`를 import한 뒤 `--hm-dashboard-status-active|hold|done|issue`를 소비 코드에서 Badge 등에 연결합니다(자동 스타일 없음). 새 상태색을 만들지 않습니다.
|
||||
|
||||
## 7. 패키지에 없는 블록 만들기
|
||||
|
||||
예: "통계 카드 4개".
|
||||
|
||||
1. 카탈로그 확인 → 통계 전용 컴포넌트 없음 → **조합**: `Card` + `CardHeader`/`CardTitle` + `CardContent`.
|
||||
2. 숫자 강조 등 Card 밖 스타일이 필요하면 소비 프로젝트 class(`app-stat-value`)를 만들고 토큰만 읽습니다.
|
||||
|
||||
```css
|
||||
.app-stat-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
|
||||
.app-stat-value { color: var(--hm-sem-fg-primary); font-weight: var(--hm-font-weight-bold); }
|
||||
.app-stat-label { color: var(--hm-sem-fg-secondary); }
|
||||
```
|
||||
|
||||
3. 간격 값(16px 등)은 레이아웃용입니다. 컴포넌트 내부 padding·radius는 바꾸지 않습니다.
|
||||
4. 결과 보고에 "신규: `StatCard`(소비 프로젝트) — Card 조합, 토큰 `--hm-sem-fg-primary`, `--hm-sem-fg-secondary`, `--hm-font-weight-bold`"처럼 남깁니다.
|
||||
|
||||
## 8. 혼합 사용과 함께 쓸 때
|
||||
|
||||
- **Web + OrgChart:** 페이지·제목·필터는 이 문서대로, 조직도 영역은 `@hm-design/org-chart/ai/COMPOSITION_PATTERNS.md` "Full Page". 조직도 안쪽에 Web 토큰을 넣지 않습니다.
|
||||
- **Web + SW:** SW Popup 안 하단 버튼 등 SW 문서가 허용한 지점에서만 Web 컴포넌트를 씁니다. Web CSS(Tailwind base 포함)는 문서 전체에 적용되므로 SW 영역을 브라우저에서 확인합니다([AI_GUIDE.md](./AI_GUIDE.md) §5).
|
||||
@@ -0,0 +1,113 @@
|
||||
# Web(@hm-design/ui) 디자인 규칙
|
||||
|
||||
출처: `@hm-design/ui/theme.css`(원본 `src/theme/primitives.css`·`default.css`·컴포넌트별 `theme/*.css`)에 실제 선언된 값. 이 문서와 그 CSS에 없는 값은 확정값이 아닙니다. 새 UI는 아래 변수를 `var(--…)`로 읽고 HEX·px 값을 새로 만들지 않습니다.
|
||||
|
||||
- 토큰 범위: `:root, [data-hm-theme]`. Runtime Theme(`@hm-design/ui/theme`)가 같은 변수를 바꿉니다.
|
||||
- Light/Dark 분기 없음. 컴포넌트 CSS에 `html`·`body`·`*` 규칙을 추가하지 않습니다.
|
||||
- 이름을 추측하지 않습니다. 특히 `--hm-sem-text-*`, `--hm-sem-border-*`, `--hm-sem-stroke-divider`는 **선언되어 있지 않습니다**(아래 표의 이름을 씀). 없는 토큰을 fallback 색으로 감추지 않습니다.
|
||||
- Tailwind utility로는 shadcn 매핑 utility(`bg-card`, `text-primary`, `text-muted-foreground` 등)를 쓸 수 있습니다. HM 값과 다를 수 있는 임의 utility 색(`bg-green-700` 등)은 쓰지 않습니다.
|
||||
|
||||
## 1. 기본 팔레트 (primitives → `--hm-sem-palette-*`)
|
||||
|
||||
| 역할 | 값 | 변수 |
|
||||
|---|---|---|
|
||||
| Green(주색) | `#123328` | `--hm-sem-palette-green-default` |
|
||||
| Green 진한 글자 | `#0A221D` | `--hm-sem-palette-green-dark-1` |
|
||||
| Green Focus·Active | `#266651` | `--hm-sem-palette-green-light-2` |
|
||||
| Green Tint(강조 면) | `#BDDFD4` | `--hm-sem-palette-green-tint` |
|
||||
| Beige(콘텐츠 면) | `#F5F0E5` | `--hm-sem-palette-beige-light` |
|
||||
| Warm neutral | dark `#6E6759`, mid `#9A9284`, pale `#DEDBD2`, muted `#F4F1EA` | `--hm-sem-palette-warm-neutral-dark` / `-mid` / `-pale` / `-muted` |
|
||||
| Red | `#D32F2F` | `--hm-sem-palette-red-danger` |
|
||||
| Gray placeholder | `#999999` | `--hm-sem-palette-neutral-gray-500` |
|
||||
| White / Black | `#FFFFFF` / `#000000` | `--hm-sem-palette-white` / `--hm-sem-palette-black` |
|
||||
|
||||
Brown·Orange 계열 팔레트(`--hm-sem-palette-brown-*`, `--hm-sem-palette-orange-*`)도 선언되어 있으며 Button `secondary`(Brown)·`modify`(Orange) 역할에 쓰입니다. 새 UI는 팔레트보다 §2의 Semantic 변수를 우선합니다.
|
||||
|
||||
## 2. Semantic 색·면 (새 UI가 읽는 변수)
|
||||
|
||||
| 역할 | 변수 | 기본 |
|
||||
|---|---|---|
|
||||
| 페이지·콘텐츠 배경 | `--hm-sem-bg-content` | Beige |
|
||||
| 카드·팝업 표면 | `--hm-sem-bg-surface` | White |
|
||||
| 보조 면 | `--hm-sem-bg-subtle` | warm-neutral-muted |
|
||||
| 브랜드 배경(gradient) | `--hm-sem-bg-brand`, `--hm-sem-bg-brown`, `--hm-sem-bg-orange` | 세로 gradient(GNB·Brand 버튼 면) |
|
||||
| 기본 글자 | `--hm-sem-fg-primary` | green-dark-1 |
|
||||
| 보조 글자 | `--hm-sem-fg-secondary` | warm-neutral-dark |
|
||||
| placeholder | `--hm-sem-fg-placeholder` | gray-500 |
|
||||
| 일반 테두리 | `--hm-sem-stroke-default` | warm-neutral-mid |
|
||||
| 입력 테두리 | `--hm-sem-stroke-input-default` | warm-neutral-pale |
|
||||
| Focus 테두리 | `--hm-sem-stroke-input-focus` | green-light-2 |
|
||||
| 주 액션 / 위 글자 | `--hm-sem-action-primary` / `--hm-sem-on-action-primary` | Green / White |
|
||||
| 취소 액션 | `--hm-sem-action-cancel` | warm-neutral-dark |
|
||||
| 위험 액션 | `--hm-sem-action-destructive` | Red |
|
||||
| 오류 상태(잠정) | `--hm-sem-status-error` | `#B3261E` |
|
||||
| 강조 면 / 위 글자 | `--hm-sem-accent` / `--hm-sem-on-accent` | green-tint / `#123328` |
|
||||
| 선택(Active) / 위 글자 | `--hm-sem-state-active` / `--hm-sem-on-state-active` | green-light-2 / White |
|
||||
| 하이라이트 | `--hm-sem-surface-highlight` | accent 45% |
|
||||
| Floating(Popover·Menu·Tooltip 류) | `--hm-sem-floating-background`, `-border-width`(1px), `-border-color`, `-radius`, `-shadow` | White, 입력 테두리, 4px, `0 0 12px` 검정 20% |
|
||||
| Overlay(Dim) | `--hm-sem-overlay-background` | 검정 10% |
|
||||
|
||||
## 3. Typography
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 글꼴 | `--hm-font-family-base`: "Noto Sans KR", "Malgun Gothic", sans-serif (글꼴 파일 포함) |
|
||||
| Weight | **400 / 500 / 700만**(600 없음). `--hm-font-weight-medium`(500), `--hm-font-weight-bold`(700). 카드·Dialog 제목 700 |
|
||||
| Card 제목 | 20 / 28px / 700 (`--hm-card-title-*`) |
|
||||
| Dialog | 제목 20 / 28 / 700, 설명 14 / 20, 본문 16 / 24 (`--hm-dialog-*`) |
|
||||
| Button | sm 12/18, default 13/20, lg 15/22. Brand 700, Standard(solid·outline) 500 |
|
||||
| Input | small 12, medium 14, large 16 (weight 400) |
|
||||
|
||||
## 4. 크기·간격·Radius
|
||||
|
||||
| 항목 | 값 | 변수 |
|
||||
|---|---|---|
|
||||
| 기본 control 높이 | 34px | `--hm-control-height-md` |
|
||||
| Button 높이(min) | sm 28 · default 34 · lg 42, padding-x 10 · 14 · 18 | — |
|
||||
| Input 높이 | small 28 · medium 34 · large 42(기본), padding-x 10 · 12 · 12 | `--hm-input-*` |
|
||||
| Select Trigger 높이 | medium 34 · large 44(기본) | — |
|
||||
| Control radius | 4px | `--hm-radius-control-md` → `--hm-radius-button`, `--hm-radius-input`, `--hm-radius-select`, `--hm-radius-container` |
|
||||
| Card | border 1px, padding 24px, radius 4px, 그림자 없음 | `--hm-card-*` |
|
||||
| Dialog | radius 8px, padding 24px, Header 아래 16px, Footer 위 16px, 버튼 간격 8px, 그림자 `0 0 12px` 검정 20% | `--hm-dialog-*` |
|
||||
|
||||
## 5. 상태
|
||||
|
||||
| 상태 | 규칙(실제 CSS) |
|
||||
|---|---|
|
||||
| Button Hover·Pressed(brand·solid) | 면 위 검정 20% inset overlay. transition·눌림 이동 없음 |
|
||||
| Button Hover(outline) | 역할 색으로 채우고 흰 글자. Red outline은 Red 8% 면 |
|
||||
| Disabled | Button·Input·Select `opacity: 0.6` + `cursor: not-allowed`, 기본 면 유지 (전역 Disabled 토큰 아님) |
|
||||
| Input Focus | 테두리만 1.5px `--hm-sem-stroke-input-focus`(그림자·glow 없음) |
|
||||
| Invalid | `aria-invalid="true"` → 오류 테두리, 키보드 Focus 시 3px 50% ring |
|
||||
| ReadOnly(Input) | 상자 없이 글자만, 흐리지 않음 |
|
||||
| Focus(그 밖) | shadcn 기본 `focus-visible` ring 유지 |
|
||||
| 선택 | `--hm-sem-state-active` / `--hm-sem-on-state-active` |
|
||||
|
||||
새 상호작용 UI는 이 표의 같은 상태 규칙을 따르고, 가능하면 상태를 가진 기존 컴포넌트(`Button`, `Toggle`, `Input`)를 안쪽에 써서 상태를 다시 만들지 않습니다.
|
||||
|
||||
## 6. 스크롤바
|
||||
|
||||
- 스크롤 영역이 필요하면 `ScrollArea`(Radix)를 씁니다. 막대는 Hover·스크롤 중에 표시됩니다.
|
||||
- 값: 두께 10px(`--hm-scroll-area-scrollbar-size`), thumb `--hm-sem-stroke-default`, Hover `--hm-sem-fg-secondary`, radius 9999px.
|
||||
- 브라우저 기본 스크롤바를 CSS(`::-webkit-scrollbar`)로 새로 꾸미지 않습니다.
|
||||
|
||||
## 7. Overlay·Panel 레이아웃
|
||||
|
||||
- 모달 = `Dialog`(Header · Body(스크롤) · Footer). 확인 = `AlertDialog`. 측면 상세 = `Sheet`. 하단 작업 = `Drawer`. 비모달 보조 = `Popover`.
|
||||
- 새 Panel 모양이 필요하면 `Card`(White `surface` / Beige `content`)로 만듭니다. Floating 모양이 꼭 필요하면 `--hm-sem-floating-*`만 읽습니다.
|
||||
- Dim은 `--hm-sem-overlay-background`.
|
||||
- Scoped Theme(`[data-hm-theme]`)는 body Portal(SelectContent·PopoverContent·DatePicker 팝업)에 자동 전달되지 않습니다.
|
||||
|
||||
## 8. 아이콘
|
||||
|
||||
- `lucide-react`(패키지 의존성)의 아이콘을 씁니다. Button 안 아이콘 기본 크기는 16px(`size-4`, shadcn 기본).
|
||||
- 아이콘 색은 글자색을 상속합니다. 아이콘 전용 색 토큰은 없습니다.
|
||||
|
||||
## 9. 새 컴포넌트 체크리스트
|
||||
|
||||
1. 같은 역할의 기존 컴포넌트·Pattern이 없는지 [COMPONENT_CATALOG.md](./COMPONENT_CATALOG.md)에서 확인했다.
|
||||
2. 소비 프로젝트에 작성했고 패키지 파일·`hm-*`/`data-slot` 규칙을 덮어쓰지 않았다.
|
||||
3. 면·글자·테두리·radius는 §2·§4 변수만 읽는다. HEX·임의 px 없음.
|
||||
4. 버튼·입력·선택은 기존 컴포넌트를 안쪽에 썼다(상태 재구현 없음).
|
||||
5. Font Weight 400/500/700, 글꼴은 상속(`--hm-font-family-base`).
|
||||
6. Label·키보드·Focus 복귀·Disabled를 확인했다.
|
||||
@@ -25,7 +25,8 @@
|
||||
"./tailwind.css": "./dist/tailwind.css"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"ai"
|
||||
],
|
||||
"scripts": {
|
||||
"typecheck": "tsc -p tsconfig.json",
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
// AI guide maintenance for the HMDesign packages.
|
||||
// node scripts/ai-docs.mjs --sync copy the shared docs/ai files into every package's ai/ folder
|
||||
// node scripts/ai-docs.mjs --check verify copies, package "files", catalog <-> public export parity and ai/ links
|
||||
// Package sources are read, never built; the check needs no dist.
|
||||
import { copyFile, readFile } from "node:fs/promises"
|
||||
import { existsSync } from "node:fs"
|
||||
import path from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
const root = fileURLToPath(new URL("../", import.meta.url))
|
||||
const shared = ["AI_GUIDE.md", "AGENTS_SNIPPET.md"]
|
||||
// entries: public JS entrypoints (package.json "exports"). catalog: the document that must list every export.
|
||||
const packages = {
|
||||
ui: { entries: ["src/index.ts", "src/theme/index.ts"], catalog: "ai/COMPONENT_CATALOG.md" },
|
||||
sw: { entries: ["src/index.ts"], catalog: "ai/COMPONENT_CATALOG.md" },
|
||||
"org-chart": { entries: ["src/index.ts"], catalog: "ai/COMPONENT_CATALOG.md" },
|
||||
"org-chart-react": { entries: ["src/index.ts", "src/hm-dialog.tsx"], catalog: "../org-chart/ai/COMPONENT_CATALOG.md" },
|
||||
}
|
||||
// Sections whose names are intentionally not public API (excluded from the reverse check).
|
||||
const privateHeading = /공개하지 않은|없는 것|지원하지 않는|사용 금지/
|
||||
|
||||
const mode = process.argv[2]
|
||||
if (mode !== "--sync" && mode !== "--check") {
|
||||
console.error("Usage: node scripts/ai-docs.mjs --sync | --check")
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const packageDir = name => path.join(root, "packages", name)
|
||||
const failures = []
|
||||
|
||||
if (mode === "--sync") {
|
||||
for (const name of Object.keys(packages)) {
|
||||
for (const file of shared) await copyFile(path.join(root, "docs/ai", file), path.join(packageDir(name), "ai", file))
|
||||
}
|
||||
console.log(`Synced ${shared.join(", ")} into ${Object.keys(packages).length} packages.`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// 1. Shared copies are identical to docs/ai.
|
||||
for (const file of shared) {
|
||||
const source = await readFile(path.join(root, "docs/ai", file), "utf8")
|
||||
for (const name of Object.keys(packages)) {
|
||||
const copy = path.join(packageDir(name), "ai", file)
|
||||
if (!existsSync(copy)) failures.push(`${name}: ai/${file} missing (run npm run sync:ai-docs)`)
|
||||
else if ((await readFile(copy, "utf8")) !== source) failures.push(`${name}: ai/${file} differs from docs/ai/${file} (run npm run sync:ai-docs)`)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. The ai/ folder is packed.
|
||||
for (const name of Object.keys(packages)) {
|
||||
const manifest = JSON.parse(await readFile(path.join(packageDir(name), "package.json"), "utf8"))
|
||||
if (!manifest.files?.includes("ai")) failures.push(`${name}: package.json "files" does not include "ai"`)
|
||||
}
|
||||
|
||||
// 3. Catalog <-> public exports.
|
||||
const resolveModule = (from, specifier) => {
|
||||
const base = path.resolve(path.dirname(from), specifier.replace(/\.js$/, ""))
|
||||
return [".ts", ".tsx", "/index.ts", "/index.tsx"].map(ext => base + ext).find(existsSync)
|
||||
}
|
||||
async function collectExports(file, seen = new Set()) {
|
||||
if (seen.has(file)) return new Set()
|
||||
seen.add(file)
|
||||
const text = (await readFile(file, "utf8")).replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, "")
|
||||
const names = new Set()
|
||||
for (const [, body] of text.matchAll(/export\s+(?:type\s+)?\{([^}]*)\}/g)) {
|
||||
for (const part of body.split(",")) {
|
||||
const item = part.trim().replace(/^type\s+/, "")
|
||||
if (!item) continue
|
||||
const alias = item.split(/\s+as\s+/).pop().trim()
|
||||
if (alias !== "default") names.add(alias)
|
||||
}
|
||||
}
|
||||
for (const [, name] of text.matchAll(/export\s+(?:declare\s+)?(?:async\s+)?(?:const|let|function\*?|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/g)) names.add(name)
|
||||
for (const [, specifier] of text.matchAll(/export\s+(?:type\s+)?\*\s+from\s+["']([^"']+)["']/g)) {
|
||||
if (!specifier.startsWith(".")) continue // re-export of another package (documented by that package)
|
||||
const target = resolveModule(file, specifier)
|
||||
if (!target) { failures.push(`cannot resolve ${specifier} from ${path.relative(root, file)}`); continue }
|
||||
for (const name of await collectExports(target, seen)) names.add(name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
function codeTokens(markdown, { publicOnly }) {
|
||||
const tokens = new Set()
|
||||
let isPrivate = false
|
||||
for (const line of markdown.split("\n")) {
|
||||
const heading = line.match(/^#{1,6}\s+(.*)/)
|
||||
if (heading) isPrivate = privateHeading.test(heading[1])
|
||||
if (publicOnly && (isPrivate || /^\s*(없는 것|공개하지 않은)/.test(line))) continue
|
||||
for (const [, span] of line.matchAll(/`([^`]+)`/g)) for (const word of span.match(/[A-Za-z_$][\w$]*/g) ?? []) tokens.add(word)
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
const catalogExports = new Map()
|
||||
for (const [name, config] of Object.entries(packages)) {
|
||||
const catalogPath = path.resolve(packageDir(name), config.catalog)
|
||||
const exports = new Set()
|
||||
for (const entry of config.entries) for (const item of await collectExports(path.join(packageDir(name), entry))) exports.add(item)
|
||||
if (!catalogExports.has(catalogPath)) catalogExports.set(catalogPath, new Set())
|
||||
for (const item of exports) catalogExports.get(catalogPath).add(item)
|
||||
const markdown = await readFile(catalogPath, "utf8")
|
||||
const mentioned = codeTokens(markdown, { publicOnly: true })
|
||||
const missing = [...exports].filter(item => !mentioned.has(item))
|
||||
if (missing.length) failures.push(`${name}: exports missing from ${path.relative(root, catalogPath)}: ${missing.join(", ")}`)
|
||||
}
|
||||
// Reverse: a code name that extends an exported name (e.g. DialogFoo, SWHeaderBar) must itself be exported,
|
||||
// unless it sits in a "not public" section. Catches invented components that look like real ones.
|
||||
for (const [catalogPath, exports] of catalogExports) {
|
||||
const markdown = await readFile(catalogPath, "utf8")
|
||||
const sorted = [...exports].filter(item => /^[A-Z]/.test(item) && item.length >= 4)
|
||||
const invented = [...codeTokens(markdown, { publicOnly: true })].filter(token =>
|
||||
/^[A-Z]/.test(token) && !exports.has(token) && sorted.some(item => token.startsWith(item) && token !== item))
|
||||
if (invented.length) failures.push(`${path.relative(root, catalogPath)}: names that look like exports but are not: ${invented.join(", ")}`)
|
||||
}
|
||||
|
||||
// 4. Links inside every ai/ document resolve, and never point at repository-only docs (they are not packed).
|
||||
for (const name of Object.keys(packages)) {
|
||||
const aiDir = path.join(packageDir(name), "ai")
|
||||
const { readdir } = await import("node:fs/promises")
|
||||
for (const file of (await readdir(aiDir)).filter(item => item.endsWith(".md"))) {
|
||||
const markdown = await readFile(path.join(aiDir, file), "utf8")
|
||||
for (const [, target] of markdown.matchAll(/\]\(([^)\s]+)\)/g)) {
|
||||
if (/^(https?:|mailto:|#)/.test(target)) continue
|
||||
const resolved = path.resolve(aiDir, target.split("#")[0])
|
||||
const relativeToPackages = path.relative(path.join(root, "packages"), resolved)
|
||||
const [targetPackage, folder] = relativeToPackages.split(path.sep)
|
||||
if (relativeToPackages.startsWith("..") || (targetPackage !== name && folder !== "ai")) failures.push(`${name}/ai/${file}: link leaves the packed ai/ docs: ${target}`)
|
||||
else if (!existsSync(resolved)) failures.push(`${name}/ai/${file}: broken link ${target}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length) {
|
||||
console.error(`AI docs check FAILED (${failures.length})`)
|
||||
for (const failure of failures) console.error(`- ${failure}`)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`AI docs check PASS: ${shared.length} shared files x ${Object.keys(packages).length} packages, catalogs match public exports, ai/ links resolve.`)
|
||||
Reference in new issue
Block a user