diff --git a/.claude/skills/component/SKILL.md b/.claude/skills/component/SKILL.md new file mode 100644 index 0000000..4afd22c --- /dev/null +++ b/.claude/skills/component/SKILL.md @@ -0,0 +1,55 @@ +--- +name: component +description: Figma Stream 파일의 노드를 코드 컴포넌트로 옮긴다. WDS(원티드 디자인 시스템) 컴포넌트로 확인되면 @wanteddev/wds를 import해서 재사용하고, Stream 고유 UI만 새로 만든다. /component 로 호출한다. +--- + +# /component — Figma → 코드 컴포넌트 변환 워크플로우 + +Stream Figma 파일의 노드를 받아서, WDS로 확인된 부분은 `@wanteddev/wds`를 import해 재사용하고 Stream 고유 UI만 새로 컴포넌트로 만드는 방식으로 코드를 생성한다. 모든 컴포넌트가 같은 구조를 따르게 하는 게 목적이다. + +> **발동 조건**: `/component`로 호출했을 때. 뒤에 Figma URL(node-id 포함) 또는 node-id가 없으면 임의로 노드를 고르지 말고 사용자에게 요청한다. +> **`figma:figma-design-to-code` 스킬과의 관계**: 이 스킬을 대체하지 않는다. 그 스킬의 "기존 컴포넌트·토큰 재사용" 단계를 WDS 우선 규칙으로 구체화한 래퍼다. `get_design_context`를 호출하기 전 `figma-design-to-code` 스킬(또는 `skill://figma/figma-design-to-code/SKILL.md`)도 함께 로드한다. + +## 상수 + +``` +Stream fileKey: 3QkxTuGLZkB17pZILTog9L +WDS libraryKey: lk-01f447137a741b37c25896e9a4e109dcb719fa4e54177be9a39d24b8da507c109279c2d1d0533b9943595d7d6a5ea398977f43b5d84e163797965d810ba79b69 +``` + +Figma URL이 주어지면 거기서 fileKey/nodeId를 추출한다. node-id만 주어지고 fileKey가 없으면 위 Stream fileKey를 기본값으로 쓴다(이 스킬은 Stream 파일 전용). + +## Step 1 — 대상 노드 확인 + +`get_metadata(fileKey, nodeId)` 또는 `get_screenshot`으로 무엇을 컴포넌트화할지 확인한다. 노드가 화면 전체처럼 너무 크면, 실제로 컴포넌트화할 하위 노드를 좁혀달라고 사용자에게 요청한다 — 임의로 쪼개지 않는다. + +## Step 2 — WDS 대조 + +1. `docs/conventions/wds-component-usage.md`를 먼저 읽는다. 이미 확정된 매핑이 있으면 재조사 없이 바로 쓴다. +2. 대상 노드 안의 인스턴스 중 문서에 없는 이름이 있으면: + 1. `search_design_system`을 WDS libraryKey로 스코프 제한해서 정확한 이름으로 검색한다. + 2. 결과가 애매하면(이름만 비슷하거나 여러 개 매칭) `get_design_context`로 실제 인스턴스 노드를 열어, 응답의 "Component descriptions" 섹션에 나오는 메인 컴포넌트 Node ID·공식 문서 링크(`montage.wanted.co.kr`)로 확정한다. + 3. 새로 확정된 매핑은 `docs/conventions/wds-component-usage.md`의 표에 바로 추가한다 — 이 스킬을 쓸수록 문서가 쌓여서 다음 실행이 더 빨라진다. +3. 매칭도 안 되고 이름도 비슷한 게 없으면 Stream 고유 UI로 취급한다(Step 3-3). + +## Step 3 — 코드 생성 + +1. `get_design_context(fileKey, nodeId)`로 레퍼런스 코드를 받는다. +2. Step 2에서 WDS로 확정된 서브트리는 raw JSX 대신 실제 WDS export로 치환한다. + - export 이름은 반드시 `node_modules/@wanteddev/wds/dist/components/`에서 실존 여부를 확인한 후 쓴다 — 이름을 추측하지 않는다. + - 아이콘은 `@wanteddev/wds-icon`에서 가져온다. + - WDS 컴포넌트 내부를 오버라이드하지 않는다. 레이아웃 조정은 감싸는 wrapper에서 한다. +3. WDS로 확정되지 않은 나머지(Stream 고유 UI)는 `docs/conventions/component-convention.md`를 따라 새 컴포넌트로 작성한다 (파일 구조, variant→Props 유니온 타입 매핑, Figma 노드 추적 주석 등). +4. 이미지·아이콘 asset은 `download_assets`로 받아 `src/assets/`에 커밋한다 — Figma asset URL은 7일 후 만료되므로 그대로 참조하지 않는다. + +## Step 4 — 배치 & 검증 + +1. `docs/conventions/coding-style.md`·`component-convention.md`의 파일 위치 규칙(feature 전용 vs `components/ui/` 공용)에 따라 저장 위치를 정한다. 재사용 범위가 애매하면 사용자에게 확인한다. +2. `pnpm check`(Biome)로 lint/format을 통과시킨다. +3. `component-convention.md`의 "완료 기준 체크리스트"로 스스로 검증한다. + +## Step 5 — 결과 안내 + +- 생성·수정한 파일 경로 +- WDS로 대체한 컴포넌트 목록 / 새로 만든 Stream 고유 컴포넌트 목록 +- `wds-component-usage.md`에 새로 추가한 매핑이 있으면 그 사실을 짚어준다 diff --git a/docs/conventions/component-convention.md b/docs/conventions/component-convention.md new file mode 100644 index 0000000..7ff3bf1 --- /dev/null +++ b/docs/conventions/component-convention.md @@ -0,0 +1,61 @@ +# Component Convention + +> Figma 디자인을 코드 컴포넌트로 옮길 때 따르는 구조 규칙. +> `docs/conventions/coding-style.md`(네이밍·폴더·TS 규칙)를 보완하는 문서이며, `/component` 스킬이 만드는 모든 컴포넌트는 이 규칙을 따른다. + +## 0. 원칙 + +Figma에 있는 요소라고 전부 새로 코드를 짜지 않는다. + +- **WDS(원티드 디자인 시스템) 컴포넌트로 확인된 건 반드시 `@wanteddev/wds`/`@wanteddev/wds-icon`을 import해서 쓴다.** 직접 마크업을 새로 짜지 않는다. 판별 기준은 `docs/conventions/wds-component-usage.md`. +- **Stream 고유 UI만 새 컴포넌트로 만든다** — WDS에 없는, Stream 서비스에서만 쓰는 화면 조각(카드, 리스트 아이템 등). + +## 1. 파일 위치 + +`coding-style.md`의 기능 기반(feature-based) 구조를 따른다. + +- 지금 다루는 화면/기능 전용이면 `features/<기능>/components/.tsx` +- 이미 다른 화면에서도 쓰이는 게 Figma 상에서 확인되면 `components/ui/.tsx` +- **애매하면 먼저 `features/` 아래에 둔다.** 두 번째 화면에서 실제로 재사용될 때 `components/ui/`로 옮긴다 — 성급하게 공용 폴더부터 만들지 않는다(coding-style.md의 "빈 폴더 미리 만들지 않는다"와 같은 이유). + +## 2. 파일 구조 + +- **컴포넌트 하나 = 파일 하나** (`ComponentName.tsx`). 폴더로 쪼개지 않는다 — 실제로 서브컴포넌트가 분리될 필요가 생기면 그때 판단한다. +- Figma의 variant(예: `trailingControl: Button | Stepper`)는 **Props의 유니온 타입 하나로 매핑**한다. variant 조합마다 별도 컴포넌트를 만들지 않는다. +- Props는 `interface`로 선언한다(coding-style.md TypeScript 규칙). + +```tsx +interface RentalItemCardProps { + itemName: string + quantity: number + trailingControl?: 'button' | 'stepper' +} +``` + +## 3. WDS 컴포넌트 사용 + +- 매칭된 서브트리는 `get_design_context`가 준 raw JSX 대신 **실제 WDS export로 치환**한다. +- export 이름은 반드시 `node_modules/@wanteddev/wds/dist/components/`에서 확인 후 쓴다 — 이름을 추측하지 않는다. + - 예: Figma `Button/Button` → `import { Button } from '@wanteddev/wds'` + - 예: Figma `Chip/Chip` → `import { Chip } from '@wanteddev/wds'` +- 아이콘은 `@wanteddev/wds-icon`에서 가져온다. +- **WDS 컴포넌트 내부를 임의로 오버라이드하지 않는다.** 간격·배치 같은 레이아웃 조정은 감싸는 wrapper에서 한다. + +## 4. Stream 고유 UI (신규 컴포넌트) + +- `get_design_context`의 raw JSX/Tailwind는 **레퍼런스일 뿐, 그대로 커밋하지 않는다.** 프로젝트 Tailwind 클래스(추후 `@theme` 토큰이 채워지면 그것)로 다시 짠다. +- Stream 자체 이미지·아이콘(일러스트, 물품 아이콘 등)은 `download_assets`로 받아 `src/assets/`에 커밋한다. Figma asset URL은 **7일 후 만료**되므로 절대 코드에 그대로 참조하지 않는다. +- `data-node-id` 같은 Figma 추적용 속성은 컴포넌트 마크업에 남기지 않는다. 대신 파일 최상단에 원본 Figma 노드를 알 수 있는 주석 한 줄만 남긴다 — 나중에 디자인이 바뀌었을 때 다시 대조할 수 있도록: + +```tsx +// Figma: Rental Item Card (nodeId 1041:61407) +``` + +## 5. 완료 기준 체크리스트 + +- [ ] WDS로 확인된 요소는 전부 import로 대체했다 (raw JSX 없음) +- [ ] Stream 고유 요소만 새 컴포넌트로 작성했다 +- [ ] Props가 Figma variant를 유니온 타입으로 반영한다 +- [ ] 이미지/아이콘 asset을 다운로드해 커밋했다 (만료되는 Figma URL 미참조) +- [ ] 파일 위치가 재사용 범위(feature 전용 vs 공용)에 맞는다 +- [ ] `pnpm check`(Biome) 통과 diff --git a/docs/conventions/wds-component-usage.md b/docs/conventions/wds-component-usage.md new file mode 100644 index 0000000..e42924b --- /dev/null +++ b/docs/conventions/wds-component-usage.md @@ -0,0 +1,75 @@ +# Figma Stream 파일 — WDS(원티드 디자인 시스템) 사용 현황 + +> 작성일: 2026-09-07 +> 종류: 살아있는 참고 문서 — `/component` 스킬이 매번 이 문서부터 대조하고, 새로 확정되는 매핑을 여기에 추가한다 +> 대상 파일: [🌊 Stream](https://www.figma.com/design/3QkxTuGLZkB17pZILTog9L/%F0%9F%8C%8A--Stream) (`fileKey: 3QkxTuGLZkB17pZILTog9L`) + +## 배경 + +`4. 사용자 UI` 페이지(`nodeId: 971:29755`) 안에 있는 `component`라는 이름의 섹션(`nodeId: 985:36615`)은 Stream 팀이 자체적으로 만든 화면 조각(로컬 심볼) 모음이며, WDS 컴포넌트가 아니다. 실제 WDS 컴포넌트는 각 화면 인스턴스 안에 흩어져서 쓰이고 있어, 파일 전체 메타데이터에서 인스턴스 이름을 모아 [Wanted Design System (Community)](https://www.figma.com) 라이브러리(`libraryKey: lk-01f447137a741b37c25896e9a4e109dcb719fa4e54177be9a39d24b8da507c109279c2d1d0533b9943595d7d6a5ea398977f43b5d84e163797965d810ba79b69`)의 `search_design_system` 검색 결과와 이름을 대조해 정리했다. + +## 조사 방법 및 한계 + +1. `get_libraries`로 Stream 파일에 연결된 라이브러리 확인 → WDS(Community), iOS and iPadOS 26 2개가 연결됨. +2. `get_metadata(971:29755)`로 페이지 전체 노드 트리(약 43만자)를 받아 로컬 파일로 저장. +3. 저장된 트리에서 `` 태그만 추출해 이름별로 집계(총 178개 고유 이름, 인스턴스 총 개수 기준). +4. 각 이름을 `search_design_system`(WDS 라이브러리로 스코프 제한)에 검색해 **정확히 같은 이름의 컴포넌트가 WDS에 존재하는지** 대조. + +**한계**: `get_metadata`는 인스턴스의 `componentKey`(어느 라이브러리 컴포넌트를 참조하는지 나타내는 고유 키)를 주지 않는다. 그래서 아래 표는 "이름 일치"로 확인한 것이며, 100% 확정하려면 `get_design_context`로 각 인스턴스를 열어 componentKey를 대조해야 한다(아래 "완전 확정 방법" 참고). + +## WDS 컴포넌트로 확인됨 (이름 정확히 일치, componentKey까지 확보) + +| WDS 컴포넌트 | 파일 내 인스턴스 수 | WDS componentKey | +|---|---|---| +| `Top Navigation/Resource/Contents` | 81 | `fcafe72bb0d975a6a1e6486be86be899e7d93e4a` | +| `Content Badge/Content Badge` | 48 | `2118b972d3ea08f35fb551cc755e41b5adeeb312` | +| `Control/Checkbox` | 42 | `72378cce3669fd4c065f12d01c254c303209d700` | +| `Action Area/Action Area` | 34 | `56b315679e172ceeac7ad64851cb0059ec235ae7` | +| `Control/Radio` | 28 | `8267d231338418aa74450dcfbe1576791540fb2a` | +| `Textinput/Textarea` | 24 | 메인 컴포넌트 Node ID `567:14111` — [문서](https://montage.wanted.co.kr/docs/components/selection-and-input/text-area/design) | +| `Chip/Chip` | 24 | 메인 컴포넌트 Node ID `440:4251` — [문서](https://montage.wanted.co.kr/docs/components/actions/action-chip/design) | +| `Icon/Normal/Location` | 13 | 메인 컴포넌트 Node ID `567:16585` | +| `Icon/Normal/Clock` | 13 | `7b620e5b46b1a467c6f8662fabcde63ce4e73b01` | +| `Page Indicator/Counter` | 12 | `560362b1ebe2ebe05646e66f4eb54a1531f3073d` | +| `Toast/Toast` | 10 | `5e6b6b522ae500ca6cd893ee2c2ffaf378c5c808` | +| `Button/Button` | 9 | `d28f3e22ae96d34ce26fb02977f23fb8084b7f85` | +| `Menu/Resource/Action Area/Trailing Content/Button` | 7 | `b7088257913aa98cea946cc3dc7931ebd544b872` | +| `Divider/Divider` | 7 | `cdef3da5cdbdd1e6f5d5d9efe85280c509b9e614` | +| `Icon/Normal/Circle Info` | 6 | 메인 컴포넌트 Node ID `440:4076` | +| `Icon/Normal/Arrow Right` | 6 | `26812c6481c960486eebf2282d6e12f2d4a133cd` | +| `Tab/Tab` | 4 | `454aa29664579ce983621c8b1c078f3e2e7ddbb0` | +| `Pagination/Dots` | 4 | 메인 컴포넌트 Node ID `445:9563` — [문서](https://montage.wanted.co.kr/docs/components/navigations/pagination-dots/design) | +| `Avatar` (Avatar/Avatar 계열) | 4 | `5885add30e5c5f5048057425d06ee89f263e96dd` | +| `Icon/Normal/Circle Check` | 3 | `bd9f80c38233b8d368cbcb4b8b9dcfa4c14c10b5` | +| `Menu/Menu` | 3 | `b4044d0a6a54bc6f8b10a76b97bfdf46a3978098` | +| `Icon/Normal/Plus` | 2 | `c2b078d8027a89ac207d6f5a1fe4205f9e26242c` | +| `Icon/Normal/Pencil` | 2 | `ddc90ae1926c0277477f233629cd4c186e87426f` | +| `Category/Resource/Chip/Normal/Normal` | 2 | `7a1668f2266cd57a689731ded85b52aa58c39905` | +| `Category/Resource/Chip/Normal/XSmall` | 1 | `73e0f352cd9ac759377854340136cd1f1032b6cb` | +| `Category/Resource/Chip/Alternative/Small` | 1 | `72cee86994b3c3581887be5149c41a59c4b02d93` | +| `Icon/Normal/Calendar` | 1 | `e050124e28e6217eedf243295b6087728acd9edc` | + +WDS 컴포넌트만 합산하면 파일 안에서 **약 350회 이상**의 인스턴스가 확인된다(위 표 합계 기준). `Textinput/Textarea`, `Chip/Chip`, `Icon/Normal/Location`, `Icon/Normal/Circle Info`, `Pagination/Dots` 5개는 `get_design_context`로 실제 노드를 열어 WDS 메인 컴포넌트 Node ID(및 3개는 원티드 공식 디자인 시스템 문서 링크)까지 확인해 완전히 확정했다. + +## 제외됨 — Stream 자체 로컬 컴포넌트 (WDS 아님) + +이름은 비슷해 보여도 WDS 검색 결과에 없거나, `component` 섹션(985:36615)에서 로컬 심볼로 직접 정의된 것들: + +- `Rental Item Card`, `RentalHistory-card`, `ApplicationHistory-card`, `Item-card` 계열, `Event-card`, `Q&A Card`, `Notice-card` — Stream 도메인 전용 카드 +- `Bottom Nav`, `BottomNav/Icon`, `Locker-button`, `SearchField`, `Floating Button`, `Empty State`, `Modal`, `Modal/ButtonGroup`, `Section-header`, `Top Navigation`(WDS의 `Top Navigation/Resource/Contents`와 다른 별개 로컬 프레임), `divider(new)`, `ProgressBar`, `Native / Home Indicator`, `Native / Bottom Sheet Indicator` +- `Icon/Feedback`, `Icon/Camera`, `Icon/Link`, `Icon/Answer`, `Icon/Activity`, `Icon/Arrow` 및 고데기·알약·후시딘 등 물품 아이콘 — Stream 전용 아이콘 세트 (WDS의 `Icon/Normal/*` 네이밍과 다름) +- `Status Bar - iPhone`, `Home Bar` — WDS가 아니라 별도로 연결된 **iOS and iPadOS 26 (Community)** 라이브러리 소속으로 추정 + +### 재검증: `Bottom Nav` / `Modal` / `Section-header` + +`@wanteddev/wds` 코드 패키지에는 `bottom-navigation`, `modal`, `section-header` 컴포넌트가 실제로 존재해서 이 셋이 WDS일 가능성을 재검토했으나, Figma 쪽 이름으로 다시 검색한 결과 **원래 분류(Stream 로컬)가 맞다**: + +- `Bottom Nav` — WDS에는 `Bottom Navigation/Bottom Navigation`이라는 이름으로 존재(componentKey `e0bf6586448b2b496500a76ee51838f44eb562d6`). Stream 파일의 `component` 섹션에는 이와 별개로 `Bottom Nav`라는 **로컬 컴포넌트 셋**이 직접 정의돼 있고(`Selected=Home/Event/Board/Rental` variant), 화면에서 쓰이는 인스턴스 이름도 `Bottom Navigation/Bottom Navigation`이 아니라 `Bottom Nav`다. 즉 디자이너가 WDS 컴포넌트를 안 쓰고 로컬로 새로 만든 것. +- `Modal` — WDS 라이브러리에서 "Modal"로 검색해도 이름이 일치하는 컴포넌트가 없음(가장 가까운 결과가 무관한 `Icon/Normal/Medal`). Stream 파일의 `Modal`/`Modal/ButtonGroup`은 `Circle Exclamation=on/off`, `Style=Default/Negative` 같은 Stream 전용 variant를 가진 로컬 컴포넌트 셋. +- `Section-header` — WDS 라이브러리에서 "Section Header"로 검색해도 결과 0건. Stream 파일 안의 로컬 컴포넌트. + +**주의**: 이건 "Figma 디자인 파일이 어떤 컴포넌트를 참조하고 있는가"에 대한 결론이지, "코드에서 무엇을 써야 하는가"와는 다른 질문이다. `wds`의 `Modal`/`BottomNavigation`/`SectionHeader` 코드 컴포넌트는 여전히 존재하므로, 실제 구현 시에는 (디자인이 로컬로 그려졌더라도) WDS 코드 컴포넌트를 기반으로 만드는 게 나을 수 있다 — 이건 구현 단계에서 별도로 판단할 문제. + +## 완전 확정 방법 (필요 시) + +이름 대조가 아니라 100% 확정하려면, 확인하고 싶은 인스턴스의 `nodeId`를 알아낸 뒤 `get_design_context(fileKey, nodeId)`를 호출해 응답에 포함된 componentKey를 위 표의 WDS componentKey와 직접 비교하면 된다. 또는 Figma 앱에서 인스턴스 선택 → 우측 패널 "Instance of" → 라이브러리 아이콘 클릭으로도 즉시 확인 가능하다.