feat(skill): route fixes, delivery report, composition diversity, platform refresh
- route table: extension route for products with an existing visual language, feedback row, derived state tokens stay local - completion: blocking 0 plus reported unverified; hard-gate surface is the touched scope; delivery report starts with what and how to open - design.md created at step 0 with a skeleton, fingerprint line and change log; product-surface rubric reading; direction delegation states the chosen card and reason - layout: hero families, nav forms, footer forms; art-direction page convergence 6 axes; genre sections; image treatments; placeholder conventions; multi-screen order; Tailwind theme mapping - platform: scroll-driven examples, anchor positioning, canvas OT, three/R3F pins, shadcn style prefixes, Safari 26 notes, subgrid - design-gate: four result states, exit codes, cwd module resolution, --url/--out-dir/--strict, oklch contrast, dead selector word match - Korean font budget split from the Latin baseline
This commit is contained in:
parent
9bcb45cbd3
commit
88c11682b1
51 changed files with 5834 additions and 2259 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# component-systems — 기존 컴포넌트 라이브러리 위에서 일하기
|
||||
# component-systems — 컴포넌트 라이브러리 위에서 일하기, 폼·데이터 테이블
|
||||
|
||||
기존 컴포넌트 라이브러리 위에서 일할 때 4단계 전에 읽는다. `components.json`, `@radix-ui/*`, Base UI 계열 패키지, `cva`·`tailwind-merge` 같은 컴포넌트 기반이 감지되면 여는 조건부 문서다. 감지되지 않으면 이 문서는 읽지 않는다 — designpaca의 기본 파이프라인(SVG 필터·three.js·순수 CSS)은 이 문서 없이도 완결된다.
|
||||
기존 컴포넌트 라이브러리 위에서 일할 때 4단계 전에 읽는다. `components.json`, `radix-ui`·`@radix-ui/*`, Base UI 계열 패키지, `react-aria-components`, `cva`·`tailwind-merge` 같은 컴포넌트 기반이 감지되면 여는 조건부 문서다. 감지되지 않으면 §0~§9는 읽지 않는다 — designpaca의 기본 파이프라인(SVG 필터·three.js·순수 CSS)은 그 절들 없이도 완결된다. **단, §10 폼 레이아웃과 §11 데이터 테이블은 컴포넌트 기반 유무와 상관없이 폼·표를 만들 때 읽는다.**
|
||||
|
||||
## 이 문서를 읽는 법
|
||||
|
||||
|
|
@ -11,12 +11,15 @@
|
|||
| "이건 브랜드 디자인 시스템 작업인가?"가 헷갈린다 | §2 |
|
||||
| 토큰 이름을 라이브러리 변수로 옮겨야 한다 | §3 |
|
||||
| Dialog·Avatar·Group 같은 합성 실수를 피해야 한다 | §4 |
|
||||
| Base UI와 Radix 중 뭘 쓰는 프로젝트인지 API가 다르다 | §5 |
|
||||
| Radix·Base UI·React Aria 중 뭘 쓰는 프로젝트인지 API가 다르다 | §5 |
|
||||
| CLI로 컴포넌트를 추가·갱신해야 한다 | §6 |
|
||||
| Tailwind 프로젝트다 | §7(조건부) |
|
||||
| Tailwind 프로젝트다 | §7(조건부). 토큰을 테마에 넣는 법은 §7-1 |
|
||||
| 설정·대시보드 같은 화면 유형의 조합을 참고하고 싶다 | §8(관찰 후보) |
|
||||
| 채팅·스트리밍 UI를 만든다 | §9(조건부) |
|
||||
| 작업 직전 마지막 확인 | §10 |
|
||||
| **폼**을 만든다(배치·필수 표시·오류 위치·한국 폼 관례·본인인증·제출 뒤) — 컴포넌트 기반이 없어도 | §10 |
|
||||
| **데이터 테이블**을 만든다(마크업·정렬·숫자·밀도·고정 헤더·모바일) — 컴포넌트 기반이 없어도 | §11 |
|
||||
| 커머스·SaaS·에디토리얼 같은 장르의 필수 블록 | [genre-conventions.md](genre-conventions.md) |
|
||||
| 작업 직전 마지막 확인 | §12 |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -27,10 +30,10 @@
|
|||
다음 중 하나라도 있으면 컴포넌트 기반이 있는 프로젝트다.
|
||||
|
||||
- 저장소 루트 또는 앱 루트에 `components.json`이 있다(shadcn/ui 계열의 표준 설정 파일)
|
||||
- `package.json` 의존성에 `@radix-ui/*`, Base UI 계열 패키지(`@base-ui/react`, 구 `@base-ui-components/react` 등), `cva`(class-variance-authority), `tailwind-merge`/`clsx` 중 하나 이상이 있다
|
||||
- `package.json` 의존성에 `radix-ui`(통합 패키지) 또는 `@radix-ui/*`, Base UI 계열(`@base-ui/react`, 구 `@base-ui-components/react`), `react-aria-components`, `cva`, `tailwind-merge`/`clsx` 중 하나 이상이 있다
|
||||
- `src/components/ui/`(또는 동등한 디렉터리)에 `button.tsx`, `dialog.tsx` 같은 생성된 프리미티브 파일이 이미 있다
|
||||
|
||||
`components.json`이 있으면 그 안의 `aliases`, `style`, `iconLibrary`, `tailwind` 필드를 먼저 읽는다. CLI가 있으면(`npx shadcn@latest info` 등) 실행해 프로젝트가 실제로 어떤 `base`(`radix` 또는 `base`)·경로 별칭·아이콘 라이브러리를 쓰는지 확정하고, 이후 모든 결정에서 그 값을 그대로 따른다. 설정을 읽지 못했는데 라이브러리 종류·경로를 추측해서 설치·수정을 진행하지 않는다 — 불확실하면 사용자에게 묻는다([brief-interview.md](brief-interview.md)) [SKILL-SHADCN].
|
||||
`components.json`이 있으면 그 안의 `aliases`, `style`, `iconLibrary`, `tailwind` 필드를 먼저 읽는다. CLI가 있으면(`npx shadcn@latest info` 등) 실행해 프로젝트가 실제로 어떤 프리미티브 계열(`style` 접두어 `radix-`/`base-`/`aria-`, §5)·경로 별칭·아이콘 라이브러리를 쓰는지 확정하고, 이후 모든 결정에서 그 값을 그대로 따른다. 설정을 읽지 못했는데 라이브러리 종류·경로를 추측해서 설치·수정을 진행하지 않는다 — 불확실하면 사용자에게 묻는다([brief-interview.md](brief-interview.md)) [SKILL-SHADCN].
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -78,11 +81,11 @@
|
|||
| `--line` | `--border` | 경계선. 폼 입력 테두리는 `--input`으로 분리되어 있을 수 있다 |
|
||||
| `--accent` | `--primary` | 강조 역할의 기본 토큰 |
|
||||
| `--accent-ink` | `--primary-foreground` | 강조 위 글자색 |
|
||||
| (역할 확장) | `--destructive` / `--destructive-foreground` | 위험·삭제 상태. designpaca 쪽 상태색 확장 자리(`tokens.md` §2)에 대응 |
|
||||
| (역할 확장) | `--destructive`(구 테마에는 `--destructive-foreground`도 있다) | 위험·삭제 상태. designpaca 쪽 상태색 확장 자리(`tokens.md` §2)에 대응 |
|
||||
| (역할 확장) | `--ring` | 포커스 링. `accessibility.md`의 포커스 하드 게이트와 연결 |
|
||||
| `--radius-*` | `--radius` | 라이브러리는 보통 단일 `--radius`에서 `calc()`로 나머지 반경을 파생시킨다. designpaca가 3종 미만으로 제한한 radius 어휘와 정신이 같다(`tokens.md` §3-b) |
|
||||
| `--radius-*` | `--radius` | 라이브러리는 단일 `--radius`에서 `calc()`로 `--radius-sm`~`--radius-4xl`을 파생시킨다. designpaca의 "역할 토큰으로만, 역할당 하나" radius 어휘와 정신이 같다(`tokens.md` §3-b가 정본) |
|
||||
|
||||
이 표는 라이브러리 변수명을 designpaca가 강제하는 것이 아니라, 이미 있는 값을 designpaca 쪽 역할 토큰과 대응시켜 이중 정의를 막는 목적이다. 색상 값 자체(라이트/다크 쌍)를 어떻게 정하는지는 `tokens.md` §2와 [color.md](color.md)를 따른다. `--dur-*`·`--ease-*` 모션 토큰은 라이브러리가 별도로 정의하지 않는 한 `tokens.md` §4와 [motion.md](motion.md)의 값을 그대로 쓴다 — 라이브러리 컴포넌트의 트랜지션 duration이 프로젝트 모션 토큰과 다르면 그 불일치를 기록하고 맞출지 결정한다 [SKILL-SHADCN].
|
||||
이 표는 라이브러리 변수명을 designpaca가 강제하는 것이 아니라, 이미 있는 값을 designpaca 쪽 역할 토큰과 대응시켜 이중 정의를 막는 목적이다. 표에 없는 변수(`--sidebar-*`·`--chart-*` 등)는 설치된 CSS에서 확인해 같은 방식으로 대응시킨다. 색상 값 자체(라이트/다크 쌍)를 어떻게 정하는지는 `tokens.md` §2와 [color.md](color.md)를 따른다. `--dur-*`·`--ease-*` 모션 토큰은 라이브러리가 별도로 정의하지 않는 한 `tokens.md` §4와 [motion.md](motion.md)의 값을 그대로 쓴다 — 라이브러리 컴포넌트의 트랜지션 duration이 프로젝트 모션 토큰과 다르면 그 불일치를 기록하고 맞출지 결정한다 [SKILL-SHADCN].
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -121,9 +124,9 @@
|
|||
</Avatar>
|
||||
```
|
||||
|
||||
### 4-3. Group 안에 Item — 프로젝트 계약
|
||||
### 4-3. 라벨이 있는 묶음은 Group으로 — 프로젝트 계약
|
||||
|
||||
리스트형 컴포넌트는 콘텐츠 컨테이너에 Item을 직접 렌더링하지 않고 Group으로 감싼다. 감싸지 않으면 스크린리더가 그룹 경계를 읽지 못하거나 레이아웃이 깨진다.
|
||||
리스트형 컴포넌트에서 **라벨(`SelectLabel` 등)로 이름 붙인 묶음**은 Group으로 감싼다. Group이 있어야 라벨이 그 묶음의 접근 가능한 이름으로 연결되고, 라벨 컴포넌트는 Group 안에서 쓰도록 설계돼 있다. 묶음 없이 항목만 나열하는 단일 목록에서 Group은 선택 사항이다(Radix 기준 — 라이브러리 문서로 확인한다). 아래는 묶음이 있는 경우다.
|
||||
|
||||
| Item | Group |
|
||||
|---|---|
|
||||
|
|
@ -133,14 +136,16 @@
|
|||
| `MenubarItem` | `MenubarGroup` |
|
||||
|
||||
```tsx
|
||||
// 틀림 — Item이 콘텐츠 컨테이너에 직접
|
||||
// 틀림 — 라벨이 묶음 밖에 떠 있어 어느 항목들의 이름인지 연결되지 않는다
|
||||
<SelectContent>
|
||||
<SelectLabel>과일</SelectLabel>
|
||||
<SelectItem value="apple">사과</SelectItem>
|
||||
</SelectContent>
|
||||
|
||||
// 맞음
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>과일</SelectLabel>
|
||||
<SelectItem value="apple">사과</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
|
|
@ -158,9 +163,11 @@ Dialog, Sheet, Popover, DropdownMenu, Tooltip, HoverCard 같은 오버레이 컴
|
|||
|
||||
---
|
||||
|
||||
## 5. Base UI vs Radix — API 차이
|
||||
## 5. Radix · Base UI · React Aria — API 차이
|
||||
|
||||
**프로젝트 계약.** 같은 컴포넌트 이름이라도 밑바탕 프리미티브가 Base UI인지 Radix인지에 따라 prop 시그니처가 다르다. `components.json`의 `base` 필드나 `info` 출력에서 어느 쪽인지 먼저 확인하고, 프로젝트가 쓰는 쪽의 문법만 쓴다. 섞어 쓰면 조용히 동작하지 않는 코드가 나온다 [SKILL-SHADCN].
|
||||
**프로젝트 계약.** 같은 컴포넌트 이름이라도 밑바탕 프리미티브가 Radix인지 Base UI인지 React Aria인지에 따라 prop 시그니처가 다르다. `components.json`에 `base` 필드는 없다. **`style` 값의 접두어**가 계열이다 — `radix-*`·`new-york`·`default`는 Radix, `base-*`는 Base UI, `aria-*`는 React Aria(공식 스키마, 확인 2026-10-06). `style`과 `info` 출력으로 확인하고 그 문법만 쓴다. 섞어 쓰면 조용히 동작하지 않는 코드가 나온다 [SKILL-SHADCN].
|
||||
|
||||
`aria-*`는 아래 표의 어느 열도 아니다. 표를 추측으로 확장하지 말고 설치된 컴포넌트 파일과 React Aria 문서에서 문법을 확인한다.
|
||||
|
||||
| 상황 | Radix | Base UI |
|
||||
|---|---|---|
|
||||
|
|
@ -208,7 +215,7 @@ non-button 렌더 대상 예시(Base UI):
|
|||
|
||||
**프로젝트 계약.** 컴포넌트 추가·갱신에 CLI(예: `shadcn` CLI)를 쓸 수 있으면 다음 순서를 지킨다.
|
||||
|
||||
1. **먼저 `info`로 프로젝트 설정을 확인한다.** 별칭 경로(`aliases`), Tailwind 버전, `base`(radix/base), `iconLibrary`를 읽고 그 값을 그대로 따른다. 값을 추측하거나 프로젝트 대신 기본값을 고르지 않는다.
|
||||
1. **먼저 `info`로 프로젝트 설정을 확인한다.** 별칭 경로(`aliases`), Tailwind 버전, 프리미티브 계열(`style` 접두어), `iconLibrary`를 읽고 그 값을 그대로 따른다. 값을 추측하거나 프로젝트 대신 기본값을 고르지 않는다.
|
||||
2. **`--dry-run`·`--diff`로 먼저 미리 본다.** 실제 적용 전에 영향받는 파일과 upstream 대비 diff를 확인한다. 로컬 변경이 없는 파일은 덮어써도 안전하지만, 로컬 변경이 있는 파일은 diff를 읽고 upstream 갱신을 로컬 수정과 함께 병합한다.
|
||||
3. **`--overwrite`는 사용자의 명시적 승인 없이 쓰지 않는다.** "그냥 다 업데이트해" 같은 포괄 지시가 와도 실행 전에 한 번 더 확인한다.
|
||||
4. **레지스트리를 추측하지 말고 묻는다.** 사용자가 "로그인 블록 추가해줘"처럼 레지스트리를 명시하지 않고 요청하면, 기본 레지스트리를 임의로 고르지 않고 어느 레지스트리를 쓸지 먼저 묻는다.
|
||||
|
|
@ -236,13 +243,23 @@ non-button 렌더 대상 예시(Base UI):
|
|||
|
||||
레지스트리·CLI 플래그·`registry.json` 스키마 자체는 designpaca 범위 밖이므로 다루지 않는다 [SKILL-SHADCN].
|
||||
|
||||
### 7-1. designpaca 토큰을 Tailwind 테마에 넣는다
|
||||
|
||||
**프로젝트 계약.** 토큰은 유틸리티 클래스가 읽는 자리에 둔다. 따로 만든 `:root` 변수를 임의값(`text-[…]`, `bg-[#…]`)으로 꺼내 쓰면 죽은 토큰이 된다. `package.json`의 `tailwindcss` 버전과 전역 CSS를 읽어 방식을 확정하고 두 방식을 섞지 않는다.
|
||||
|
||||
| 프로젝트 | 토큰을 두는 곳과 방법 |
|
||||
|---|---|
|
||||
| v4 (CSS에 `@import "tailwindcss"`) | 전역 CSS의 `@theme`. `tailwind.config.js`를 새로 만들지 않는다. `--color-*`·`--font-*`·`--text-*`·`--spacing-*`·`--radius-*`·`--ease-*` 네임스페이스로 넣으면 같은 이름의 유틸리티가 생긴다. 다른 변수를 참조하면 `@theme inline { --color-surface: var(--surface); }` |
|
||||
| v4 + shadcn/ui | §3 대응표대로 `:root`·`.dark`의 **값만** 바꾸고 `@theme inline`의 연결은 그대로 둔다. shadcn에 없는 토큰(타입 스케일·모션)은 `@theme`에 더한다 |
|
||||
| v3 (`tailwind.config.*`) | `theme.extend`에서 CSS 변수를 `var(--…)`로 참조한다 |
|
||||
|
||||
네임스페이스 이름은 v4.3 문서 기준이다(확인 2026-10-06) — 다른 버전이면 그 문서로 확인하고, 넣은 뒤 빌드 결과 CSS에 유틸리티가 생겼는지 본다. v4의 브라우저 하한(Chrome 111·Safari 16.4·Firefox 128)과 브리프의 지원 범위도 대조한다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 화면 유형별 조합 참고
|
||||
|
||||
> 언제: 컴포넌트 기반 위에서 익숙한 화면 유형(설정, 대시보드 등)을 조립할 때. 아래는 **관찰 후보**이며 정답이 아니다.
|
||||
|
||||
**관찰 후보.** 특정 화면 유형에서 자주 관찰되는 컴포넌트 조합 예시다. 그대로 베끼면 템플릿화된 결과가 나온다는 경고와 함께 둔다.
|
||||
> 언제: 컴포넌트 기반 위에서 익숙한 화면 유형(설정, 대시보드 등)을 조립할 때. 아래는 **관찰 후보**이며 정답이 아니다 — 브리프·업종·목표 행동에 따라 완전히 다른 조합이 맞을 수 있다.
|
||||
|
||||
| 화면 유형 | 자주 보이는 조합(예시일 뿐) |
|
||||
|---|---|
|
||||
|
|
@ -252,9 +269,8 @@ non-button 렌더 대상 예시(Base UI):
|
|||
|
||||
**주의**:
|
||||
|
||||
1. 이 표를 정답으로 채택하지 않는다. 브리프·업종·목표 행동에 따라 완전히 다른 조합이 맞을 수 있다
|
||||
2. `reference-method.md`의 레퍼런스 우선 원칙이 이 표보다 앞선다 — 실제 레퍼런스 조사 없이 이 표만으로 화면을 확정하지 않는다
|
||||
3. 같은 조합을 모든 설정·대시보드 화면에 기계적으로 반복하면 그 자체가 제네릭 신호다(`antipatterns.md` 참고)
|
||||
1. `reference-method.md`의 레퍼런스 우선 원칙이 이 표보다 앞선다 — 실제 레퍼런스 조사 없이 이 표만으로 화면을 확정하지 않는다
|
||||
2. 같은 조합을 모든 설정·대시보드 화면에 기계적으로 반복하면 그 자체가 제네릭 신호다(`antipatterns.md` 참고)
|
||||
|
||||
[SKILL-SHADCN]
|
||||
|
||||
|
|
@ -266,17 +282,126 @@ non-button 렌더 대상 예시(Base UI):
|
|||
|
||||
**프로젝트 계약.** 스트리밍 채팅 화면에서 자동 스크롤 추적·앵커링·"최신으로 이동" 로직은 흔히 재발명되는 영역이다. 라이브러리가 이 동작을 소유하는 컴포넌트(메시지 스크롤 컨테이너류)를 이미 제공하면, `useStickToBottom`이나 수동 `ResizeObserver` 훅으로 직접 재구현하지 않는다. 합성으로 표현할 수 없는 동작만 라이브러리가 제공하는 훅으로 보강하고, 그것도 없을 때만 직접 구현한다. 메시지 행·표면(bubble)·첨부·시스템 구분선도 마찬가지로 라이브러리 프리미티브가 있으면 그것을 합성하고, flex `div`로 직접 재구성하지 않는다 [SKILL-SHADCN].
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 10. 체크리스트
|
||||
## 10. 폼 레이아웃 — 컴포넌트 기반이 없어도 이 절을 따른다
|
||||
|
||||
- [ ] `components.json`(또는 동등 설정)과 `info` 명령 출력을 프로젝트에서 실제로 확인했다. 값을 추측하지 않았다
|
||||
- [ ] 새 UI를 손으로 짜기 전에 기존 컴포넌트·반복 패턴을 먼저 검색했다(§1)
|
||||
- [ ] 이 작업이 "라이브러리 조립"과 "designpaca 토큰·브랜드 표면·검증" 중 어디에 속하는지 구분했다(§2)
|
||||
- [ ] 새 CSS 변수를 중복 정의하지 않고 §3 매핑표로 기존 라이브러리 변수에 연결했다
|
||||
- [ ] Dialog·Sheet·Drawer에 접근 가능한 제목이 있다(§4-1, 하드 게이트)
|
||||
- [ ] Avatar·Group/Item·폼 필드 묶음·오버레이 z-index를 §4의 합성 규칙대로 처리했다
|
||||
- [ ] 프로젝트가 Base UI인지 Radix인지 확인하고 그 문법만 썼다(§5)
|
||||
- [ ] CLI로 추가·갱신했다면 `info` → `--dry-run`/`--diff` → 검증 순서를 지켰고, `--overwrite`는 승인 없이 쓰지 않았다(§6)
|
||||
- [ ] Tailwind 프로젝트라면 §7 클래스 위생을 지켰다. 아니라면 이 절은 적용하지 않았다
|
||||
- [ ] §8의 화면 유형 조합을 정답이 아니라 참고로만 썼고, 실제 레퍼런스 조사로 확인했다
|
||||
> 언제: 가입·주문·문의·설정처럼 입력 필드가 셋 이상인 폼을 만들 때. 라벨·`autocomplete`·오류 배선·인증 같은 접근성 계약은 [accessibility.md](accessibility.md) §6이 정본이고, 이 절은 **배치와 한국어 폼 관례**만 다룬다. 문구는 [product-copy.md](product-copy.md)를 따른다.
|
||||
|
||||
### 10-1. 배치
|
||||
|
||||
- 기본은 **단일 열, 라벨은 입력 위**다. 두 열은 의미상 한 쌍(시·분, 카드 유효기간)일 때만 쓴다. 라벨을 입력 왼쪽에 두는 배치는 넓은 설정 화면에서만, 라벨 폭을 고정해 쓴다.
|
||||
- **입력 폭은 예상 값 길이에 맞춘다.** 우편번호·인증번호·수량은 짧게, 주소·이메일은 길게. 전부 같은 폭이면 무엇을 얼마나 써야 하는지 단서가 사라진다.
|
||||
- 플레이스홀더를 라벨 대신 쓰지 않는다. 형식 예시(`예: 010-1234-5678`)는 라벨 아래 도움말로 둔다.
|
||||
- 관련 필드는 `fieldset` + `legend`로 묶는다(배송지, 결제 수단, 동의 항목).
|
||||
- 선택지가 5개 이하면 라디오나 세그먼트로 전부 보여 준다. 6개 이상이거나 값이 익숙한 목록(시·도)이면 셀렉트, 아주 길면 검색 가능한 콤보박스다.
|
||||
- 자라는 `textarea`는 스크립트 대신 `field-sizing: content` + `max-height`를 쓴다(Baseline Newly 2026-06) [WEB-BASELINE].
|
||||
- 긴 폼은 단계로 나누고 진행 표시(`1/3 배송 정보`)를 둔다. 단계 이동 때 입력값을 잃지 않고, 이전 단계로 돌아갈 수 있다.
|
||||
- 주 행동 버튼은 폼 끝, 마지막 필드 바로 아래에 둔다. 모바일에서 긴 폼이면 하단 고정 바도 후보다. 제출 버튼을 조기에 비활성화하지 않는다([accessibility.md](accessibility.md) §6.6).
|
||||
|
||||
### 10-2. 필수·선택 표시
|
||||
|
||||
- **소수 쪽만 표시한다.** 대부분 필수면 선택 항목에 `(선택)`을, 대부분 선택이면 필수 항목에 `*` 또는 `(필수)`를 단다. `*`를 쓰면 폼 상단에 "* 필수 입력"을 한 번 적는다.
|
||||
- 색만으로 필수를 나타내지 않는다. 필수 입력은 `required`(또는 `aria-required="true"`)로도 표시한다.
|
||||
|
||||
### 10-3. 오류 메시지 위치와 시점
|
||||
|
||||
- 오류 문구는 **해당 필드 바로 아래**(라벨 → 입력 → 오류 순서)에 아이콘과 텍스트로 둔다. 토스트·알림창만으로 오류를 알리지 않는다.
|
||||
- 검증 시점은 "늦게 꾸짖고 일찍 풀어 준다": 형식 오류는 입력을 마친 뒤(blur) 또는 제출 시 보이고, 이미 오류인 필드는 고치는 즉시 해제한다. 타이핑 도중 오류를 띄우지 않는다.
|
||||
- 제출 시 오류가 여러 개거나 필드가 5개 이상이면 폼 상단에 **오류 요약**(각 항목이 해당 필드로 가는 링크)을 두고 요약 또는 첫 오류 필드로 포커스를 옮긴다. 배선은 [accessibility.md](accessibility.md) §6.8을 따른다.
|
||||
- 오류 문구는 무엇이 틀렸고 어떻게 고치는지 말한다(`휴대폰 번호 11자리를 입력해 주세요`). "잘못된 입력입니다"는 쓰지 않는다.
|
||||
|
||||
### 10-4. 한국어 폼 관례
|
||||
|
||||
| 항목 | 기본 | 이유·주의 |
|
||||
|---|---|---|
|
||||
| 이름 | **한 칸**(`autocomplete="name"`). 성/이름 분리는 해외 결제·여권 정보처럼 분리가 요구될 때만 | 한국 이름은 성·이름을 붙여 쓰고 복성·외국인 이름도 있다. 두 칸은 입력 순서를 헷갈리게 한다 |
|
||||
| 휴대폰 번호 | **한 칸**, `type="tel"` `inputmode="numeric"` `autocomplete="tel"`. 입력 중 `010-1234-5678` 형태로 하이픈을 자동 표시하되 저장은 숫자만 | 세 칸 분리 입력은 붙여넣기·자동완성을 깨뜨린다. 하이픈을 직접 넣은 붙여넣기도 받아들인다 |
|
||||
| 주소 | **우편번호 찾기 버튼** → 검색 결과 선택 → 우편번호·기본주소 자동 입력(읽기 전용 표시) → **상세주소 필드로 포커스 이동** | 도로명·지번 검색 서비스를 쓴다. 검색 창이 막히거나 실패하면 직접 입력 경로를 둔다. 상세주소를 빈칸으로 둔 채 넘어가는 실수가 잦으니 자동 포커스가 중요하다 |
|
||||
| 생년월일 | 8자리 숫자 한 칸(`19900101`, `inputmode="numeric"` `autocomplete="bday"`) 또는 연·월·일 세 칸 숫자 입력 | 연도 셀렉트로 100개 항목을 스크롤하게 하지 않는다 |
|
||||
| 이메일 | 한 칸 + `type="email"` `autocomplete="email"` | 아이디·도메인 셀렉트 분리(`@` + 도메인 선택)는 흔한 관례지만 자동완성·붙여넣기를 깨뜨린다. 쓰더라도 "직접 입력"을 기본에 둔다 |
|
||||
| 본인인증 | 아래 10-5 | |
|
||||
| 약관 동의 | "전체 동의" + 개별 항목. 항목마다 `(필수)`/`(선택)` 표기와 "보기" 링크. **선택 항목은 미리 체크하지 않는다** | 필수와 선택(마케팅 수신 등)을 한 체크박스로 묶으면 선택 동의를 강제하는 셈이 된다. 정확한 고지 문구는 법무 확인 대상이다 |
|
||||
|
||||
### 10-5. 본인인증 흐름
|
||||
|
||||
- 인증을 요구하기 **전에** 왜 필요한지 한 줄로 말한다(`만 14세 이상 확인을 위해 휴대폰 본인인증이 필요합니다`).
|
||||
- 인증 수단(휴대폰·간편인증 앱 등)은 실제 연동된 것만 보여 준다. 인증 창은 새 창·팝업·앱 전환으로 열리므로 팝업 차단과 모바일 앱 전환 뒤 복귀를 시험한다.
|
||||
- 인증을 마치면 **원래 단계로 돌아오고, 그때까지 입력한 값이 남아 있어야 한다.** 인증으로 받은 이름·휴대폰 번호는 자동으로 채우고 "인증됨" 상태와 함께 수정 불가로 표시한다(바꾸려면 재인증 버튼).
|
||||
- 실패·취소·시간 초과 각각에 다음 행동을 안내한다. 인증 없이 진행할 수 있는 경로가 있으면 함께 보여 준다.
|
||||
- 실제 인증 연동이 없는 시안·쇼케이스에서는 인증이 된 것처럼 보이게 만들지 않는다 — 버튼과 흐름만 두고 "연동 필요"를 `design.md`에 적는다.
|
||||
|
||||
### 10-6. 제출 뒤
|
||||
|
||||
- 성공하면 같은 자리에서 무엇이 접수됐고 다음에 무슨 일이 언제 일어나는지 말한다(`예약 요청을 받았습니다. 영업일 하루 안에 문자로 확정해 드립니다`). 상태 메시지로 알린다([accessibility.md](accessibility.md) §7).
|
||||
- 전송 중 중복 제출을 막고, 실패하면 입력을 지우지 않는다.
|
||||
- **받을 곳이 없으면 성공을 흉내 내지 않는다.** 전송처(이메일·폼 서비스·API)는 사용자가 정한다. 임의의 폼 서비스를 붙이지 말고 제출을 막은 채 '전송처 미정'을 `design.md`와 납품 보고에 적는다.
|
||||
|
||||
---
|
||||
|
||||
## 11. 데이터 테이블
|
||||
|
||||
> 언제: 관리자 목록·주문 내역·요금 비교·사양표처럼 행과 열로 비교하는 화면. 차트·지표 설계는 데이터 시각화 스킬로 넘기고, 이 절은 표 자체만 다룬다. 정렬 기준선의 기본(숫자는 끝, 텍스트는 시작)은 [layout.md](layout.md)를 따른다.
|
||||
|
||||
### 11-1. 마크업
|
||||
|
||||
- 진짜 `<table>`을 쓴다. 표 제목은 `<caption>`(시각적으로 숨겨도 된다), 열 머리글은 `<th scope="col">`, 행 머리글이 있으면 `<th scope="row">`.
|
||||
- 정렬 가능한 열은 머리글 안에 `<button>`을 두고, 현재 정렬 열의 `<th>`에만 `aria-sort="ascending|descending"`을 단다. 정렬 방향은 아이콘과 함께 보이게 한다.
|
||||
- 행 전체를 클릭 영역으로 만들면 행 안의 대표 셀(이름)에 진짜 링크를 두고 키보드로도 같은 곳에 간다.
|
||||
|
||||
### 11-2. 정렬·숫자·빈 값
|
||||
|
||||
- 숫자 열은 끝 정렬 + `font-variant-numeric: tabular-nums`. 같은 열의 소수 자릿수를 통일한다.
|
||||
- **단위는 머리글에 한 번**(`금액(원)`, `용량(GB)`), 셀마다 반복하지 않는다.
|
||||
- 빈 값은 빈칸이 아니라 `—`로 표시하고, 0과 구분한다.
|
||||
- 음수·하락은 색만이 아니라 부호(`−`)나 아이콘으로도 표시한다.
|
||||
- 기본 정렬 기준을 정하고 화면에 보인다(`최근 주문순`).
|
||||
|
||||
### 11-3. 밀도
|
||||
|
||||
- 행 높이 3단을 시작값으로 둔다: 촘촘 32px · 기본 40px · 여유 48px. 데이터가 많고 숙련 사용자가 쓰면 촘촘, 처음 쓰는 사람이 많거나 터치가 주 입력이면 여유. 터치 대상 크기는 [accessibility.md](accessibility.md) §8 정책을 따른다.
|
||||
- 행 구분은 옅은 가로 구분선이 기본이다. 열이 많아 시선이 행을 잃는 넓은 표에서만 줄무늬를 쓴다. 세로 구분선은 대부분 필요 없다.
|
||||
- 긴 텍스트 셀은 말줄임하되 전체 값에 닿는 수단(펼치기·상세 링크)을 둔다.
|
||||
|
||||
### 11-4. 고정 헤더·고정 열
|
||||
|
||||
- 세로로 긴 표는 머리글을 고정한다(`th { position: sticky; top: 0; }` + 불투명 배경). 페이지 상단 고정 헤더가 있으면 그 높이만큼 `top`을 내린다.
|
||||
- 가로로 넓은 표는 첫 열(식별자)을 고정한다(`position: sticky; inset-inline-start: 0`). 고정 열 경계에는 스크롤 중에만 보이는 그림자 단서를 둔다.
|
||||
- 스크롤 컨테이너에는 `tabindex="0"`, `role="region"`, `aria-label`(표 제목)을 줘 키보드로도 스크롤할 수 있게 한다.
|
||||
|
||||
### 11-5. 모바일
|
||||
|
||||
전략을 과업으로 고른다.
|
||||
|
||||
| 과업 | 전략 |
|
||||
|---|---|
|
||||
| 여러 행을 열끼리 **비교**한다(요금제, 사양, 성적) | 가로 스크롤 + 첫 열 고정. **카드 변환 금지** — 비교가 불가능해진다 |
|
||||
| 행 하나씩 훑고 들어간다(주문 내역, 게시물 목록) | 카드 변환. 카드 안에서도 라벨-값 쌍을 유지한다 |
|
||||
| 열 일부만 자주 본다 | 열 우선순위를 정해 좁은 화면에서 낮은 열을 숨기고, 행 상세에서 나머지를 보여 준다 |
|
||||
|
||||
### 11-6. 선택·일괄 작업·페이지
|
||||
|
||||
- 행 선택 체크박스가 있으면 선택 개수와 일괄 작업 바를 표 위(또는 하단 고정)에 띄운다. 파괴적 일괄 작업은 되돌리기를 우선한다([interaction-feel.md](interaction-feel.md) §7).
|
||||
- 페이지 이동 방식: 특정 위치로 돌아와야 하는 업무 목록은 **페이지네이션**, 훑어보는 피드는 **더 보기** 버튼. 무한 스크롤은 푸터에 닿을 수 없고 뒤로 가기 위치 복원이 어려우므로 표에는 쓰지 않는 것이 기본이다.
|
||||
- 빈 상태(데이터 없음)와 필터 결과 없음은 다른 문구·다른 행동으로 구분한다([product-copy.md](product-copy.md)).
|
||||
|
||||
---
|
||||
|
||||
## 12. 체크리스트 (구현 중 자기 점검 — 납품 판정은 preflight.md X9)
|
||||
|
||||
납품 판정은 [preflight.md](preflight.md) X9가 한다. Dialog·Sheet·Drawer의 접근 가능한 제목(§4-1)은 X9의 하드 게이트 행으로 판정한다.
|
||||
|
||||
- `components.json`(또는 동등 설정)과 `info` 명령 출력을 프로젝트에서 실제로 확인했다. 값을 추측하지 않았다
|
||||
- 새 UI를 손으로 짜기 전에 기존 컴포넌트·반복 패턴을 먼저 검색했다(§1)
|
||||
- 이 작업이 "라이브러리 조립"과 "designpaca 토큰·브랜드 표면·검증" 중 어디에 속하는지 구분했다(§2)
|
||||
- 새 CSS 변수를 중복 정의하지 않고 §3 매핑표로 기존 라이브러리 변수에 연결했다
|
||||
- Avatar·Group/Item·폼 필드 묶음·오버레이 z-index를 §4의 합성 규칙대로 처리했다
|
||||
- 프로젝트가 Base UI인지 Radix인지 확인하고 그 문법만 썼다(§5)
|
||||
- 폼이 단일 열·라벨 위·값 길이에 맞는 폭이고, 필수/선택은 소수 쪽만 표시했고, 오류는 필드 바로 아래에 있다(§10)
|
||||
- 한국어 폼이면 이름 한 칸·휴대폰 한 칸·우편번호 검색 후 상세주소 포커스·선택 동의 미체크를 지켰고, 본인인증 뒤 입력값이 남는다(§10-4·§10-5)
|
||||
- 표에 `caption`·`th scope`·정렬 열 `aria-sort`가 있고, 숫자 열은 끝 정렬·tabular, 단위는 머리글에 있다(§11)
|
||||
- 모바일 표 전략이 과업에 맞다 — 비교 과업을 카드로 바꾸지 않았다(§11-5)
|
||||
- CLI로 추가·갱신했다면 `info` → `--dry-run`/`--diff` → 검증 순서를 지켰고, `--overwrite`는 승인 없이 쓰지 않았다(§6)
|
||||
- Tailwind 프로젝트라면 §7 클래스 위생을 지켰다. 아니라면 이 절은 적용하지 않았다
|
||||
- §8의 화면 유형 조합을 정답이 아니라 참고로만 썼고, 실제 레퍼런스 조사로 확인했다
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue