시작하기
기여 방법
컴포넌트를 추가하는 6단계, meta 계약과 tsc가 잡는 것, 파일 소유, 등록 요청, 통과해야 하는 검사.
컴포넌트 추가 6단계
문서에만 있는 prop, 이름만 다른 중복 컴포넌트, “나중에 정리하자”로 남은 분류는 받지 않는다.
- src/design/components/<Name>/에 <Name>.tsx, <Name>.module.css, <Name>.meta.ts를 만든다(PascalCase 폴더).
- <Name>.tsx에서 문서화할 API를 export type <Name>OwnProps로 분리한다. 네이티브 속성은 Omit<…HTMLAttributes, keyof OwnProps | "className" | "style">로 합친다.
- <Name>.meta.ts를 ComponentMeta<<Name>OwnProps>로 쓴다.
- registry.ts에 register(xMeta, X) 한 줄, index.ts에 export 한 줄 — 직접 넣지 말고 등록 요청으로(아래).
- CSS는 @layer components { … } 안에서 --cb-* 토큰만.
- npm run typecheck && npm run design:lint && npm run design:check
export type BadgeOwnProps = {
kind?: 'label' | 'count';
tone?: 'neutral' | 'attention';
children?: ReactNode;
count?: number;
};
type NativeProps = Omit<HTMLAttributes<HTMLSpanElement>, keyof BadgeOwnProps | 'className' | 'style'>;
export type BadgeProps = BadgeOwnProps & NativeProps;meta 계약
| 필드 | 필수 | 내용 |
|---|---|---|
name · slug · category | ✓ | PascalCase 이름, URL 조각(소문자, 유일, “tokens” 금지), 분류 id |
description | ✓ | 4개 언어 한 줄 설명(catalog-copy의 설명 줄) |
launch | ✓ | 출시 화면이 쓰면 true(inventory.md 기준) |
props | ✓ | OwnProps의 모든 키. 컨트롤 종류는 타입에서 나온다 |
states · examples | ✓ | URL 하나로 재현할 수 있는 프리셋. 프리셋에 없는 상태는 없는 상태다 |
rules | ✓ | 디자인 기준 do/dont. 계약의 “가드” 줄은 dont로 |
related · a11y · glyph | ✓ | 관련 slug(design-check가 확인), 키보드 표와 메모, 3자 이하 글리프 |
demos · keyOptions · guidelineLinks · snippets · preview · source | — | 추가 데모, prop 하나로 설명되지 않는 API, 외부 기준 링크, 조합 코드, 미리보기 코드, 21st 출처 |
tsc가 잡는 것
- OwnProps에 prop을 더하고 meta를 그대로 두면 TS2741 (Property … is missing)
- meta에 없는 prop을 쓰면 TS2353 (does not exist)
- select 옵션이 실제 유니온과 다르면 TS2322
- 4개 언어 중 하나라도 빠지면 Localized 오류
파일 소유와 등록 요청
- 이렇게자기 분류의 컴포넌트 폴더만 만든다. 다른 사람이 만든 폴더는 고치지 않고, 고칠 점은 로그에 적는다.
- 이렇게 하지 않기registry.ts, index.ts, meta.ts, catalog/**, tokens/**, package.json은 통합 담당 외에는 수정하지 않는다.
- 이렇게등록이 필요한 줄은 docs/design-system-v4/round-4/registry-requests/<담당자>.md에 적는다. 통합 담당(registry·index: Minh Anh, 카탈로그·meta: Daniel)이 한 번에 반영한다.
- 이렇게다른 분류의 컴포넌트가 필요하면 @/design에 있는 것을 import한다. 없으면 자기 폴더 안에서 최소한으로 만들고 로그에 적는다.
- 이렇게 하지 않기런타임 의존성을 추가하지 않는다(아이콘은 lucide-react). 개발 의존성은 @playwright/test, @axe-core/playwright 둘뿐.
검사
| 명령 | 무엇을 |
|---|---|
npm run typecheck | meta 계약 포함 전체 타입. 자기 파일 오류 0 |
npm run design:lint | 아래 R1–R9 |
npm run design:check | registry · components 폴더 · index.ts 일치, 다크 토큰 블록 쌍, related slug |
npm run design:contrast | 토큰 대비 계산 |
npm run test:design | Playwright + axe 카탈로그 게이트(DB 없음) |
npm run test:design-logic | 근거·데이터 상태·초점 복귀·가져오기 로직 단위 테스트 |
| 규칙 | 막는 것 |
|---|---|
| R1 | tokens/ 밖의 hex·rgb·hsl 색 리터럴 |
| R2 | CSS의 important 우선순위 플래그 |
| R3 | 대체 표시 없는 outline: none |
| R4 | tokens/ 밖의 px 글자 크기·반경 |
| R5 | tokens/ 밖에서 --p-* primitive 읽기 |
| R6 | legacy 코드가 @/design을 import |
| R7 | v4 코드가 @/ui나 legacy 라우트를 import |
| R8 | tokens/ 밖의 하드코딩된 duration·delay |
| R9 | 컴포넌트가 aria-live를 직접 만듦(공유 announcer·Toast 제외) |
예외가 꼭 필요하면 그 줄에 design-lint-disable-line R<n> <이유>를 적는다. 이유 없는 예외는 리뷰에서 거절한다.
검토와 완료 조건
| 대상 | 검토자 |
|---|---|
| 타이포·기초·액션·입력 (Minh Anh) | Kenji, Harin |
| 탐색·데이터 표시 (Adaeze) | Kenji, Camila |
| 근거·기록 (Kenji) | Wei-Ting, Priya |
| 차트 (Yusuf) | Rafael, Priya |
| 날짜·시간 (Priya) | Kenji, Wei-Ting |
| 상태·피드백 (Noor) | Kenji, Rafael |
| 미디어·편집·위젯 부품 (Theo) | Camila, Priya |
| 카탈로그 사이트 전체 | Adaeze, Léa, Wei-Ting |
- 완료 조건: typecheck · design:lint · design:check 통과, 모든 페이지 200, 접근성 게이트(라이트·다크 × ko·es × 320·1280) 통과 기록, 짝 검토 지적 0 또는 이유 있는 보류.
- 위 목록 밖의 컴포넌트는 오너 결정 없이 PR에서 거절한다(DIRECTION §6).
- 폐기 경로: 이름을 바꾸거나 흡수할 때는 “만들지 않는 이유” 규칙 페이지(alias)를 남겨 옛 이름이 새 컴포넌트로 이어지게 한다(예: AreaChart → LineChart fill).
가이드 페이지 추가
- src/design/docs/page-meta.ts에 slug·group·4개 언어 title·summary·keywords를 더한다. slug는 컴포넌트 slug와 겹치면 안 된다.
- src/design/docs/pages/<Name>.tsx를 kit의 DocPage · Section · Example로 만든다. 값은 live-tokens로 실행 중인 CSS에서 읽고, 예시는 실제 @/design 컴포넌트로.
- src/design/docs/pages.ts의 components 맵에 한 줄을 더한다. 라우트·사이드바·검색은 docPages를 읽으므로 따로 고칠 곳이 없다.