@conscience-technology/ctds 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/components/Hint/Hint.module.css +39 -0
- package/assets/components/Hint/Hint.tsx +48 -0
- package/assets/components/Hint/index.ts +2 -0
- package/assets/components/Kanban/Kanban.module.css +339 -0
- package/assets/components/Kanban/Kanban.tsx +371 -0
- package/assets/components/Kanban/index.ts +10 -0
- package/assets/components/PageHeader/PageHeader.module.css +109 -0
- package/assets/components/PageHeader/PageHeader.tsx +112 -0
- package/assets/components/PageHeader/index.ts +2 -0
- package/assets/components/index.ts +1 -0
- package/assets/prompts/components/hint.md +57 -0
- package/assets/prompts/components/kanban.md +167 -0
- package/assets/prompts/components/page-header.md +155 -0
- package/assets/prompts/recipes/README.md +102 -0
- package/assets/prompts/recipes/card-row.md +58 -0
- package/assets/prompts/recipes/form-footer.md +76 -0
- package/assets/prompts/recipes/meta-line.md +61 -0
- package/assets/prompts/recipes/page-banner.md +121 -0
- package/assets/prompts/recipes/page-header.md +93 -0
- package/assets/prompts/recipes/section-header.md +90 -0
- package/package.json +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Kanban
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
워크플로우를 **상태(컬럼)와 아이템(카드)의 2차원 격자**로 보여주는 자리. 가로축은 진행 단계(todo·in-progress·review·done 등), 세로축은 각 단계 안의 큐. 3-레벨 compound로 분리되어 보드 폭·컬럼 뼈대·카드 내용이 각자 축을 가진다.
|
|
5
|
+
|
|
6
|
+
- **Kanban (board)** — 가로 트랙. 컬럼 width·간격·스크롤 여부만 담당.
|
|
7
|
+
- **Kanban.Column** — 한 상태의 세로 스택. chrome(배경 vs 테두리) × layout(한 블록 vs 두 블록) × accent(상태색 위치) × tone(6종)으로 조립.
|
|
8
|
+
- **Kanban.Card** — 아이템 1장. slot(Cover/Header/Title/Description/Meta/Footer) 조합으로 자유롭게.
|
|
9
|
+
|
|
10
|
+
DnD는 **이 컴포넌트가 품지 않는다** — dnd-kit은 호출부가 바깥에서 감싸는 opt-in 패턴. 시각이 먼저, 상호작용은 선택.
|
|
11
|
+
|
|
12
|
+
### Do
|
|
13
|
+
- 컬럼은 **같은 chrome · 같은 layout으로 통일**한다. 한 보드 안에 line·filled·plain, wrap·stack을 섞지 않는다 — depth 혼란.
|
|
14
|
+
- 상태 색은 `tone` + `accent="dot"`(헤더 title 앞 점) 또는 `chrome="filled"`(bg·fg가 tone 색계열로 동시 변함)로 공급. 컬럼 전체를 진한 bg로 칠하지 않는다 ([[feedback_bg_or_stroke_only]]).
|
|
15
|
+
- `layout="wrap"`(기본)은 header와 카드 스택이 **한 쉘 안에** — 컬럼 하나가 하나의 "그릇". 가장 흔한 칸반 모습.
|
|
16
|
+
- `layout="stack"`은 **header만 chrome**을 가지고 그 아래 카드 스택은 chrome 없이 떨어져 쌓이는 "얹어진" 레이아웃. 카드가 그릇에 갇히지 않고 자유롭게 숨 쉬는 느낌. header에 action이 많거나 카드 영역을 더 넓게 쓰고 싶을 때.
|
|
17
|
+
- 카드는 Column 안에 **자유 조합**: 가장 작은 카드는 `Title`만, 가장 큰 카드는 `Cover` + `Header` + `Title` + `Description` + `Meta` + `Footer` 전부.
|
|
18
|
+
- 보드가 뷰포트보다 넓으면 `scroll` 기본 — 세로 wrap하지 않는다.
|
|
19
|
+
- Column의 count·action은 **헤더 우측**에 자동 배치됨. `action`은 ReactNode라 IconButton 여러 개(`+` · 설정 등)를 Fragment로 넘겨도 됨.
|
|
20
|
+
- `layout="stack"`에선 **컬럼별로 chrome을 섞어 쓰기** 자연스러움 — 특정 컬럼만 filled로 강조하고 나머지는 line으로 조용히 두는 식. `layout="wrap"`에선 chrome 통일.
|
|
21
|
+
|
|
22
|
+
### Don't
|
|
23
|
+
- [HARD] 카드 안에 다시 Card chrome(추가 border/shadow) 쌓지 않는다 — "컬럼 안 카드 안 카드"는 depth 깨짐.
|
|
24
|
+
- [HARD] 컬럼에 "안 연한 배경 + 테두리" 조합 금지 — chrome은 `line`(테두리만) · `filled`(배경만) · `plain`(둘 다 없음) 중 하나.
|
|
25
|
+
- [HARD] 컬럼 title을 UPPERCASE로 강제하지 않는다 — [[feedback_no_uppercase]].
|
|
26
|
+
- [HARD] 상태 색을 **진한 bg로 칠하지 않는다** — tone 색은 chrome=filled일 때 subtle bg(약 8% opacity) + 같은 색 fg 조합으로만. 신호가 더 조용해야 하면 chrome=line/plain + accent="dot".
|
|
27
|
+
- `layout="stack"`에서 body(카드 스택)에 chrome(배경·테두리)을 추가하지 않는다 — stack의 핵심은 "카드가 그릇에서 풀려남". chrome 축은 header에만 영향.
|
|
28
|
+
- 보드 전체를 `<section>`으로 감싸지 않는다 — [[feedback_no_semantic_tags_in_components]].
|
|
29
|
+
|
|
30
|
+
### Fallback
|
|
31
|
+
- 애매하면 `chrome="line"` + `layout="wrap"` + `accent="none"`. 가장 조용한 조합.
|
|
32
|
+
- 상태 신호를 강하게 주고 싶으면 `chrome="filled"` + `tone=…` (bg·fg 둘 다 그 색계열로).
|
|
33
|
+
- header에 action(검색·필터·추가 버튼 등)이 많아져 복잡해지면 `layout="stack"`로 분리.
|
|
34
|
+
|
|
35
|
+
### 관련
|
|
36
|
+
- [[components/list-item]] — 세로 1열 리스트. 칸반 카드는 리스트 아이템을 그리드로 재구성한 것.
|
|
37
|
+
- [[components/metric-card]] — 카드 chrome 규칙(그림자 금지, line 기본) 공유.
|
|
38
|
+
- [[components/badge]] — 카드 Header의 상태·우선순위 라벨.
|
|
39
|
+
- [[components/avatar]] — 카드 Footer의 담당자.
|
|
40
|
+
- [[components/icon-button]] — 컬럼 action, 카드 메뉴.
|
|
41
|
+
- [[tokens/color]] — `--bg` · `--bg-muted` · `--border` · `--primary` · `--success` · `--warning` · `--destructive` · `--info`.
|
|
42
|
+
- [[feedback_bg_or_stroke_only]] — 컬럼 chrome 설계의 핵심.
|
|
43
|
+
- [[feedback_no_hardcoded_values]] — columnWidth도 토큰/계산값 권장.
|
|
44
|
+
|
|
45
|
+
### Props 시그니처
|
|
46
|
+
```tsx
|
|
47
|
+
<Kanban
|
|
48
|
+
columnWidth={string} // CSS 길이. 기본 var(--space-80) = 320px
|
|
49
|
+
gap={string} // 컬럼 사이 간격. 기본 var(--space-3)
|
|
50
|
+
scroll={boolean} // 가로 스크롤 on/off. 기본 true
|
|
51
|
+
>
|
|
52
|
+
<Kanban.Column
|
|
53
|
+
title={ReactNode}
|
|
54
|
+
count={number | string}
|
|
55
|
+
action={ReactNode}
|
|
56
|
+
footer={ReactNode}
|
|
57
|
+
chrome="line | filled | plain" // 기본 line
|
|
58
|
+
layout="wrap | stack" // 기본 wrap
|
|
59
|
+
accent="none | dot" // 기본 none
|
|
60
|
+
tone="neutral | primary | success | warning | danger | info"
|
|
61
|
+
>
|
|
62
|
+
<Kanban.Card href={string}>
|
|
63
|
+
<Kanban.Card.Cover>…</Kanban.Card.Cover>
|
|
64
|
+
<Kanban.Card.Header>…</Kanban.Card.Header>
|
|
65
|
+
<Kanban.Card.Title>…</Kanban.Card.Title>
|
|
66
|
+
<Kanban.Card.Description>…</Kanban.Card.Description>
|
|
67
|
+
<Kanban.Card.Meta>…</Kanban.Card.Meta>
|
|
68
|
+
<Kanban.Card.Footer>…</Kanban.Card.Footer>
|
|
69
|
+
</Kanban.Card>
|
|
70
|
+
</Kanban.Column>
|
|
71
|
+
</Kanban>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### chrome 매트릭스
|
|
75
|
+
| chrome | 배경 | 테두리 | 쓰임 |
|
|
76
|
+
|---|---|---|---|
|
|
77
|
+
| `line` (기본) | 투명 | 1px border | 조용한 보드. 카드가 주연. |
|
|
78
|
+
| `filled` | tone-subtle(없으면 bg-muted) | 없음 | 컬럼 자체가 "그릇"임을 강조. tone 하나가 bg·header fg 둘 다 결정(같은 색을 opacity로만 조절). |
|
|
79
|
+
| `plain` | 투명 | 없음 | chrome 없이 scroll만. 미니 보드·임베드. |
|
|
80
|
+
|
|
81
|
+
### layout 매트릭스
|
|
82
|
+
| layout | 모양 | 쓰임 |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `wrap` (기본) | header와 카드 스택이 한 쉘 안에. 컬럼 하나 = 한 그릇. | 일반 칸반. 가장 흔함. |
|
|
85
|
+
| `stack` | header만 chrome. 그 아래 카드 스택은 chrome 없이 떨어져 쌓임. | 카드가 그릇에서 풀려 숨쉬는 느낌. header 중심 보드. |
|
|
86
|
+
|
|
87
|
+
### accent 매트릭스
|
|
88
|
+
| accent | 신호 위치 | 쓰임 |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `none` | 없음 | 상태 색 신호 불필요 |
|
|
91
|
+
| `dot` | 헤더 title 앞 | 조용한 색 신호. 다수 컬럼 구분. |
|
|
92
|
+
|
|
93
|
+
### 조합 예시
|
|
94
|
+
|
|
95
|
+
**가장 작은 카드 — 제목만**
|
|
96
|
+
```tsx
|
|
97
|
+
<Kanban>
|
|
98
|
+
<Kanban.Column title="To Do" count={3}>
|
|
99
|
+
<Kanban.Card>
|
|
100
|
+
<Kanban.Card.Title>디자인 토큰 정리</Kanban.Card.Title>
|
|
101
|
+
</Kanban.Card>
|
|
102
|
+
</Kanban.Column>
|
|
103
|
+
</Kanban>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**표준 — 제목 + 메타 + 담당자**
|
|
107
|
+
```tsx
|
|
108
|
+
<Kanban.Card href={`/tasks/${t.id}`}>
|
|
109
|
+
<Kanban.Card.Header>
|
|
110
|
+
<Badge variant="soft" status="warning">검토 필요</Badge>
|
|
111
|
+
</Kanban.Card.Header>
|
|
112
|
+
<Kanban.Card.Title>사이드바 리팩터 PR 리뷰</Kanban.Card.Title>
|
|
113
|
+
<Kanban.Card.Meta>
|
|
114
|
+
<Icon name="clock" size={14} /> 금요일 마감
|
|
115
|
+
</Kanban.Card.Meta>
|
|
116
|
+
<Kanban.Card.Footer>
|
|
117
|
+
<AvatarGroup size="xs"><Avatar name="박지훈"/><Avatar name="이수민"/></AvatarGroup>
|
|
118
|
+
<span>3 댓글</span>
|
|
119
|
+
</Kanban.Card.Footer>
|
|
120
|
+
</Kanban.Card>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**얹어진 header — layout=stack + chrome=filled**
|
|
124
|
+
```tsx
|
|
125
|
+
<Kanban>
|
|
126
|
+
<Kanban.Column
|
|
127
|
+
title="진행 중"
|
|
128
|
+
count={5}
|
|
129
|
+
action={<IconButton variant="ghost" size="md" icon={<Icon name="plus" />} aria-label="추가" />}
|
|
130
|
+
chrome="filled"
|
|
131
|
+
layout="stack"
|
|
132
|
+
tone="info"
|
|
133
|
+
>
|
|
134
|
+
{cards.map((c) => (…))}
|
|
135
|
+
</Kanban.Column>
|
|
136
|
+
</Kanban>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**색 신호 — chrome=filled + tone 하나로**
|
|
140
|
+
```tsx
|
|
141
|
+
<Kanban>
|
|
142
|
+
<Kanban.Column title="To Do" chrome="filled" tone="neutral" count={5} />
|
|
143
|
+
<Kanban.Column title="In Progress" chrome="filled" tone="info" count={3} />
|
|
144
|
+
<Kanban.Column title="Review" chrome="filled" tone="warning" count={2} />
|
|
145
|
+
<Kanban.Column title="Done" chrome="filled" tone="success" count={12} />
|
|
146
|
+
</Kanban>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**DnD opt-in — dnd-kit로 바깥 감싸기**
|
|
150
|
+
```tsx
|
|
151
|
+
import { DndContext } from "@dnd-kit/core";
|
|
152
|
+
import { SortableContext, useSortable } from "@dnd-kit/sortable";
|
|
153
|
+
|
|
154
|
+
function SortableCard({ id, children }) {
|
|
155
|
+
const { attributes, listeners, setNodeRef, transform } = useSortable({ id });
|
|
156
|
+
return (
|
|
157
|
+
<Kanban.Card
|
|
158
|
+
ref={setNodeRef}
|
|
159
|
+
style={{ transform: CSS.Transform.toString(transform) }}
|
|
160
|
+
{...attributes}
|
|
161
|
+
{...listeners}
|
|
162
|
+
>
|
|
163
|
+
{children}
|
|
164
|
+
</Kanban.Card>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Page Header
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
페이지 body의 **첫 블록**. "이 페이지가 뭔지"를 담는 자리. `h1`·eyebrow·lead·메타·페이지 액션이 여기 모인다. shell의 chrome인 [[components/topbar]]와는 역할이 다르다 — Topbar는 '지금 어디(breadcrumb)·공용 액션'을 담는 **얇은 축**이고, PageHeader는 '이 페이지의 content head'다.
|
|
5
|
+
|
|
6
|
+
레이아웃은 CSS Grid 2행 2열. Title과 Actions는 **같은 가로줄**, Eyebrow·Lead·Meta는 그 위아래로 수직. Actions가 없으면 Title이 혼자 그 줄을 차지한다. 자식 slot은 자유롭게 생략 가능 — 빈 slot은 자리를 먹지 않는다.
|
|
7
|
+
|
|
8
|
+
### Do
|
|
9
|
+
- 페이지당 **하나만** 둔다. `h1`은 PageHeader.Title이 담당하므로 중복 금지.
|
|
10
|
+
- body의 **첫 섹션 바로 앞**에 둔다. Topbar 바로 아래, 또는 shell 없는 페이지라면 콘텐츠 영역 최상단.
|
|
11
|
+
- Title + Lead 조합이 기본. 더 담고 싶을 때만 Eyebrow·Meta·Actions을 추가한다.
|
|
12
|
+
- Actions는 Title과 짝. 보통 primary + outline 두 개 ([[feedback_button_variant_defaults]]). overflow·share 같은 보조 액션은 ghost [[components/icon-button]]을 섞어 쓴다 — Button은 왼쪽, **IconButton은 더 오른쪽 끝**에 둔다(라벨이 있는 쪽이 선, 아이콘 전용이 뒤). IconButton은 **한 단계 크게**(Button이 md면 `size="lg"`) — Button 옆에 두면 정사각형이 좁아 보여서 균형을 맞춰야 한다.
|
|
13
|
+
- Meta 안의 [[components/badge]]는 `variant="soft"` 연한 톤이 기본 — 본문 리듬에 묻어가도록. solid는 상태를 강조해야 할 때만.
|
|
14
|
+
- size는 기본 `md`. 설정·서브페이지는 `sm`, 랜딩·docs 인덱스는 `lg`.
|
|
15
|
+
|
|
16
|
+
### Don't
|
|
17
|
+
- Breadcrumb slot을 두지 않는다 — "지금 어디"는 [[components/topbar]]의 몫. shell이 없는 페이지에서도 Topbar를 두고 그 안에 Breadcrumb을 넣는다.
|
|
18
|
+
- Topbar **안에** 넣지 않는다 — Topbar의 "얇은 축" 약속이 깨진다 ([[components/topbar]]).
|
|
19
|
+
- `<section>` **안에** 넣지 않는다 — 전역 `.content section`이 `padding-top: 80px`를 꽂는다. PageHeader는 section 바깥, body 최상단에 둔다 ([[feedback_no_semantic_tags_in_components]]).
|
|
20
|
+
- 색 배경 + 색 테두리 조합으로 꾸미지 않는다 — 그건 [[components/callout]]의 자리 ([[feedback_bg_or_stroke_only]]).
|
|
21
|
+
- Eyebrow에 uppercase + wide letter-spacing 조합 금지 ([[feedback_no_uppercase]]).
|
|
22
|
+
- Lead에 em dash·ellipsis 금지 ([[feedback_no_em_dash]]).
|
|
23
|
+
- 페이지에 두 개 쓰지 않는다. 하위 섹션 head는 `<h2>`로.
|
|
24
|
+
- 데이터 요약(KPI·숫자·차트)을 PageHeader에 끼우지 않는다 — body 첫 섹션의 [[components/metric-card]] 자리.
|
|
25
|
+
|
|
26
|
+
### Fallback
|
|
27
|
+
size 애매하면 `md`. 글로벌 유틸 `.page-title`(4xl)보다 두 단계 작은 `2xl` — PageHeader가 챙기는 수직 여백/eyebrow/meta와 어우러져 "과하지 않은 head"가 되도록 톤 다운한 값이다.
|
|
28
|
+
Title만으로 충분하면 Lead 생략 OK. Lead를 쓸 거면 1~2문장으로 짧게.
|
|
29
|
+
|
|
30
|
+
### 관련
|
|
31
|
+
- [[components/topbar]] — chrome 축. Breadcrumb·공용 액션은 Topbar의 자리, 페이지 head는 PageHeader의 자리.
|
|
32
|
+
- [[components/breadcrumb]] — Topbar 안에서만 쓴다. PageHeader slot에는 없다.
|
|
33
|
+
- [[components/callout]] — "지금 공지해야 할 큰 콘텐츠"는 PageHeader가 아니라 Callout.
|
|
34
|
+
- [[components/badge]] — PageHeader.Meta 안에서 상태 라벨로.
|
|
35
|
+
- [[components/button]] — PageHeader.Actions 안에서. primary + outline 짝이 기본.
|
|
36
|
+
- [[tokens/spacing]] — size별 아래 margin과 row-gap을 결정.
|
|
37
|
+
- [[tokens/typography]] — Title letter-spacing은 -0.03em, size별 font-size는 토큰 경유.
|
|
38
|
+
|
|
39
|
+
### Props 시그니처
|
|
40
|
+
```tsx
|
|
41
|
+
<PageHeader size="sm | md | lg">
|
|
42
|
+
<PageHeader.Eyebrow>Overview</PageHeader.Eyebrow>
|
|
43
|
+
<PageHeader.Title>Page title</PageHeader.Title>
|
|
44
|
+
<PageHeader.Lead>한두 문장 설명.</PageHeader.Lead>
|
|
45
|
+
<PageHeader.Meta>
|
|
46
|
+
<Badge>draft</Badge>
|
|
47
|
+
<span>updated oct 14</span>
|
|
48
|
+
</PageHeader.Meta>
|
|
49
|
+
<PageHeader.Actions>
|
|
50
|
+
<Button variant="outline">share</Button>
|
|
51
|
+
<Button variant="primary">publish</Button>
|
|
52
|
+
</PageHeader.Actions>
|
|
53
|
+
</PageHeader>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| 요소 | 역할 |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `PageHeader` | grid 컨테이너. slot 자리와 세로 리듬을 쥔다 |
|
|
59
|
+
| `PageHeader.Eyebrow` | 선택. 작은 상단 라벨. muted + small |
|
|
60
|
+
| `PageHeader.Title` | 필수. `<h1>` 자동, size별 font-size |
|
|
61
|
+
| `PageHeader.Lead` | 선택. muted 본문, max-width 72ch |
|
|
62
|
+
| `PageHeader.Meta` | 선택. Badge·날짜 등 가로 묶음 |
|
|
63
|
+
| `PageHeader.Actions` | 선택. Title과 같은 줄 오른쪽 pin |
|
|
64
|
+
|
|
65
|
+
### Size 매트릭스
|
|
66
|
+
|
|
67
|
+
| size | Title | Lead | 용도 |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `sm` | `--text-xl` | `--text-sm` | 설정·서브페이지 head |
|
|
70
|
+
| `md` (기본) | `--text-2xl` | `--text-base` | 일반 페이지 head |
|
|
71
|
+
| `lg` | `--text-3xl` | `--text-base` | 랜딩·docs 인덱스 |
|
|
72
|
+
|
|
73
|
+
**외부 간격**: PageHeader → 다음 섹션 간격은 **상위 wrapper gap이 담당**. 벤더가 margin-bottom으로 공급하지 않음 — margin trick 금지, `display: flex; flex-direction: column; gap: var(--space-6)` 식 group wrapper + gap만 사용 ([[feedback_page_differential_rhythm]], [[feedback_no_double_spacing]]).
|
|
74
|
+
|
|
75
|
+
### 금지 조합
|
|
76
|
+
- Breadcrumb을 PageHeader slot으로 노출 — Topbar만의 자리.
|
|
77
|
+
- `<section>` 안에 삽입 — 전역 padding 충돌.
|
|
78
|
+
- 색 배경 + 색 border — Callout 자리 침범.
|
|
79
|
+
- Title 없이 Lead/Meta만 두기 — head 역할이 성립 안 함.
|
|
80
|
+
- 페이지에 두 번 — `h1` 중복.
|
|
81
|
+
- Actions 안에 세 개 이상 Button — primary + outline 짝이 기본, 더 필요하면 overflow 메뉴로.
|
|
82
|
+
|
|
83
|
+
### 조합 예시
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// 1) 기본 — Title + Lead만
|
|
87
|
+
<PageHeader>
|
|
88
|
+
<PageHeader.Title>Billing</PageHeader.Title>
|
|
89
|
+
<PageHeader.Lead>구독 플랜과 결제 수단을 관리합니다.</PageHeader.Lead>
|
|
90
|
+
</PageHeader>
|
|
91
|
+
|
|
92
|
+
// 2) 액션 짝 — Title | Actions 같은 줄 자동 분리
|
|
93
|
+
<PageHeader>
|
|
94
|
+
<PageHeader.Title>Projects</PageHeader.Title>
|
|
95
|
+
<PageHeader.Lead>진행 중인 프로젝트와 아카이브를 모아 봅니다.</PageHeader.Lead>
|
|
96
|
+
<PageHeader.Actions>
|
|
97
|
+
<Button variant="outline">import</Button>
|
|
98
|
+
<Button variant="primary">new project</Button>
|
|
99
|
+
</PageHeader.Actions>
|
|
100
|
+
</PageHeader>
|
|
101
|
+
|
|
102
|
+
// 2-b) IconButton + primary — Button이 왼쪽, IconButton들이 더 오른쪽 끝.
|
|
103
|
+
// IconButton은 한 단계 크게(size="lg") — Button md와 시각적 균형.
|
|
104
|
+
<PageHeader>
|
|
105
|
+
<PageHeader.Title>Documents</PageHeader.Title>
|
|
106
|
+
<PageHeader.Lead>팀 문서와 공유 링크를 관리합니다.</PageHeader.Lead>
|
|
107
|
+
<PageHeader.Actions>
|
|
108
|
+
<Button variant="primary">new doc</Button>
|
|
109
|
+
<IconButton size="lg" icon={<Icon name="share-network" />} aria-label="share" />
|
|
110
|
+
<IconButton size="lg" icon={<Icon name="dots-three" />} aria-label="more" />
|
|
111
|
+
</PageHeader.Actions>
|
|
112
|
+
</PageHeader>
|
|
113
|
+
|
|
114
|
+
// 3) 메타 담긴 상세 head — Badge·날짜
|
|
115
|
+
<PageHeader>
|
|
116
|
+
<PageHeader.Eyebrow>Spec</PageHeader.Eyebrow>
|
|
117
|
+
<PageHeader.Title>Checkout flow v2</PageHeader.Title>
|
|
118
|
+
<PageHeader.Lead>결제 단계의 폼 밀도와 오류 복구를 개선합니다.</PageHeader.Lead>
|
|
119
|
+
<PageHeader.Meta>
|
|
120
|
+
<Badge variant="soft" status="warning">draft</Badge>
|
|
121
|
+
<span>updated oct 14</span>
|
|
122
|
+
<span>heeyoung</span>
|
|
123
|
+
</PageHeader.Meta>
|
|
124
|
+
<PageHeader.Actions>
|
|
125
|
+
<Button variant="outline">share</Button>
|
|
126
|
+
<Button variant="primary">publish</Button>
|
|
127
|
+
</PageHeader.Actions>
|
|
128
|
+
</PageHeader>
|
|
129
|
+
|
|
130
|
+
// 4) 설정 서브페이지 — size=sm
|
|
131
|
+
<PageHeader size="sm">
|
|
132
|
+
<PageHeader.Title>API keys</PageHeader.Title>
|
|
133
|
+
<PageHeader.Lead>개발자 토큰을 발급하고 폐기합니다.</PageHeader.Lead>
|
|
134
|
+
</PageHeader>
|
|
135
|
+
|
|
136
|
+
// 5) 랜딩·docs 인덱스 — size=lg
|
|
137
|
+
<PageHeader size="lg">
|
|
138
|
+
<PageHeader.Eyebrow>CTDS</PageHeader.Eyebrow>
|
|
139
|
+
<PageHeader.Title>디자인 시스템 하나로 끝</PageHeader.Title>
|
|
140
|
+
<PageHeader.Lead>8축 토큰 스왑으로 브랜드·밀도·형태를 즉시 바꿉니다.</PageHeader.Lead>
|
|
141
|
+
<PageHeader.Actions>
|
|
142
|
+
<Button variant="outline">문서 보기</Button>
|
|
143
|
+
<Button variant="primary">시작하기</Button>
|
|
144
|
+
</PageHeader.Actions>
|
|
145
|
+
</PageHeader>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 조립 — 상위 shell과의 공식
|
|
149
|
+
- shell 안 페이지: Topbar(size `md`) 바로 아래, body 콘텐츠 영역의 **첫 블록**.
|
|
150
|
+
- Topbar의 Breadcrumb과 PageHeader의 Title은 **다른 자리**다. "지금 어디(breadcrumb)" + "이 페이지(title)"는 쌍으로 들어가 되 **중복되지 않는다**.
|
|
151
|
+
- 아래 간격은 **상위 wrapper gap**이 쥔다. PageHeader는 margin 공급 안 함, 다음 섹션에서 `padding-top`을 또 걸지 않는다 ([[feedback_no_double_spacing]], [[feedback_page_differential_rhythm]]).
|
|
152
|
+
|
|
153
|
+
### Known Gaps
|
|
154
|
+
- 긴 Title의 반응형 축약 규칙은 없다. 자유롭게 줄바꿈. 필요하면 호출처가 `max-width`를 override.
|
|
155
|
+
- 다국어/RTL은 grid 자체가 flow 따라감 (`margin-inline`·`justify-items`로 대응).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Recipes
|
|
2
|
+
|
|
3
|
+
**자리별 조립 레시피 모음.** 벤더에 없는 자리를 조립할 때, 토큰·vendor 컴포넌트의 **정식 조합 규격**이다.
|
|
4
|
+
|
|
5
|
+
## 왜 레시피인가
|
|
6
|
+
|
|
7
|
+
벤더 컴포넌트 문서(`components/*.md`)는 "**있는 컴포넌트를 어떻게 쓰는가**"만 다룬다. 하지만 실제 페이지는 벤더가 없는 자리가 더 많다 — 페이지 헤더, 섹션 그룹, 메타 라인, 빈 상태 등. 이 자리들은 **여러 토큰과 vendor 컴포넌트의 조합**으로만 성립하고, 조합의 리듬·위계·여백이 매번 달라지면 CTDS의 결과물이 흩어진다.
|
|
8
|
+
|
|
9
|
+
레시피는 그 **조합을 못 박는다** — 벤더 컴포넌트를 추가하지 않고도, 조립 시점에 "이 자리는 이 토큰 조합"이라는 표준을 제공.
|
|
10
|
+
|
|
11
|
+
## 레시피 작성 규칙
|
|
12
|
+
|
|
13
|
+
레시피 파일은 [[GUIDE]]의 **공통 골격**을 따르되, 아래 추가 섹션을 포함한다.
|
|
14
|
+
|
|
15
|
+
```markdown
|
|
16
|
+
# <레시피 이름>
|
|
17
|
+
|
|
18
|
+
### 원칙
|
|
19
|
+
<1~2줄. 이 자리의 역할·언제 쓰는가>
|
|
20
|
+
|
|
21
|
+
### Anatomy
|
|
22
|
+
<이 자리를 구성하는 요소. 뼈대 구조. 수치가 아니라 역할 중심.>
|
|
23
|
+
|
|
24
|
+
### Do
|
|
25
|
+
- <토큰·vendor 조합 규칙 (명령형)>
|
|
26
|
+
|
|
27
|
+
### Don't
|
|
28
|
+
- <금지 조합. [HARD]/[SOFT] 명시 — PRINCIPLES §4a>
|
|
29
|
+
|
|
30
|
+
### Fallback
|
|
31
|
+
<규칙 밖 자리에서의 기본 선택>
|
|
32
|
+
|
|
33
|
+
### 조립 예시
|
|
34
|
+
<규칙을 종합한 최소 코드 예시 1개. 수치 하드코딩 금지, 토큰·vendor만.>
|
|
35
|
+
|
|
36
|
+
### 관련
|
|
37
|
+
- [[다른 레시피]] / [[components/X]] / [[tokens/Y]]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 등재된 레시피
|
|
41
|
+
|
|
42
|
+
- [[recipes/page-header]] — 페이지 상단: 큰 타이틀 + 서브텍스트 + (옵션) 액션
|
|
43
|
+
- [[recipes/section-header]] — 섹션 상단: 중간 타이틀 + (옵션) 설명/액션
|
|
44
|
+
- [[recipes/meta-line]] — 메타 라벨 가로 나열 (구분자 포함)
|
|
45
|
+
- ~~[[recipes/card-row]]~~ — **폐기**. 리스트 자리는 [[components/list-item]] 벤더 사용 ([[PRINCIPLES]] §5a HARD)
|
|
46
|
+
- [[recipes/form-footer]] — 폼·모달 하단 액션 묶음
|
|
47
|
+
- [[recipes/page-banner]] — 페이지 폭 안내 배너
|
|
48
|
+
|
|
49
|
+
> "빈 상태"는 vendor [[components/empty-state]]로 해결 — 조립 레시피 아님.
|
|
50
|
+
|
|
51
|
+
## 신설 기준
|
|
52
|
+
|
|
53
|
+
새 자리가 **3번 반복**되면 레시피로 올린다. 1~2번은 vendor anatomy 복제로 처리하되, 3번째가 되면 조합 규칙이 자리 잡은 것 — [[ASSEMBLY]] §2 목록에 추가하고 이 디렉토리에 파일 신설.
|
|
54
|
+
|
|
55
|
+
## 조립 테스트 결과 (레시피 품질 보증)
|
|
56
|
+
|
|
57
|
+
레시피는 **반드시 실제 조립으로 검증**된다. 테스트 페이지는 `web/app/assembly-test/`. 지금까지 검증 중 발견된 결함:
|
|
58
|
+
|
|
59
|
+
- **meta-line separator** — 초기 레시피의 `width:1px / height:0.75em`가 check_tokens hook에서 리터럴로 차단됨 → `border-left: 1px solid var(--border)` + `align-self: stretch`로 수정
|
|
60
|
+
- **투명 테두리 가드 금지** — `border: 1px solid transparent` 가드 패턴이 check_bg_stroke_combo hook에서 차단됨 → border 안 쓸 땐 선언 자체 생략. page-banner, card-row 레시피 수정
|
|
61
|
+
- **날짜 포맷** — 초기 예시들의 "10. 13." 숫자 날짜가 [[feedback-date-writing]] 룰 위반 → "10월 13일" 표기로 수정
|
|
62
|
+
- **Callout 유령 참조** — `[[components/banner]]`가 유령 → `[[recipes/page-banner]]`로 교체
|
|
63
|
+
- **아이콘 이름 misname** — 레시피 예시의 `name="chevron-right" / "refresh" / "share" / "code" / "shield" / "checklist" / "terminal"` 중 어느 것도 CTDS Icon 세트에 **등재되지 않음**. 각각 `caret-right / arrow-clockwise / share-network / brackets-square / shield-check / clipboard-text / brackets-square`로 교체. **레시피에 "아이콘 이름은 `web/components/Icon/Icon.tsx`의 phosphor 이름 레지스트리에서 찾을 것" 가이드 추가 필요**
|
|
64
|
+
- **Badge API prop 오류** — 레시피가 `tone="warning"`을 썼지만 실제 API는 `status="warning"` + `variant="soft"` 조합. `tone`은 Callout prop. 레시피 수정 필요
|
|
65
|
+
- **Vendor prop 선언 확인 공백** — ASSEMBLY §1 2단계 "가장 가까운 벤더 anatomy 베끼기"는 API까지 확인하지 않으면 복제가 깨짐. ASSEMBLY에 **"vendor prop·타입 선언을 반드시 확인"** 단계 추가 필요
|
|
66
|
+
- **Compound component의 Server Component 경계 — "use client" 전염** — Stepper처럼 `"use client"` 벤더를 Server Component page에서 compound property(`Stepper.Item`)로 접근하면 **런타임에 `Element type is invalid` + undefined**. Next 15/React 19 경계 이슈. 레시피 조립 코드는 vendor 하나라도 client면 **page 자체도 `"use client"`로 선언** — ASSEMBLY에 "조립 page가 client 벤더를 포함하면 'use client' 강제" 가이드 추가
|
|
67
|
+
|
|
68
|
+
**브라우저 렌더 1차 피드백 (유저 육안 검증, 2026-10-14)**:
|
|
69
|
+
- **섹션 간 gap `space-6` 과잉** — 콘솔 리듬에서 섹션이 border/배경으로 이미 분리되면 `space-4`가 적정. page 레벨 gap을 `space-4`로 조정 + ASSEMBLY 체크리스트에 "섹션 간 space-6 과잉" 항목 추가
|
|
70
|
+
- **섹션 타이틀 `text-xl` + medium → "크고 얇음"** — [[recipes/section-header]] 기본값을 `text-lg`로 다운. text-xl은 랜딩 리듬에만
|
|
71
|
+
- **멀티라인 banner padding 타이트** — `padding: space-2 space-4` + `gap: space-0.5`가 멀티라인에서 숨 안 쉼. 단일/멀티라인 2 모드로 분리 + 멀티라인은 상하 `space-3`
|
|
72
|
+
- **아이콘 + 다줄 텍스트 center 정렬 함정** — 긴 inline hint에서 `align-items: center`면 아이콘이 중앙쯤 뜸. `flex-start` + `margin-top: space-0.5`로 **첫 줄 baseline** 맞춤 필요
|
|
73
|
+
- **"라벨 혼자 위에 뜨는" 함정** — Stepper 카드에서 "작업 전 점검" 라벨을 Stepper 위에 flex-end로 두니 라벨 영역만 공백 → 라벨은 header 자리로 명시적 묶음
|
|
74
|
+
|
|
75
|
+
**브라우저 렌더 2차 피드백 (유저 육안 검증, 2026-10-14)**:
|
|
76
|
+
- **"page gap 하나로 다 담당" = 통짜 느낌의 근원** — 섹션 간 gap을 `space-4`로 줄여도 모든 블록 사이가 같은 공간이면 역할 분리가 안 됨
|
|
77
|
+
- **"Stepper 카드" 자체 폐기** — 카드로 감싸면 상하좌우 균일 padding이 공식인데 "라벨 혼자" 있는 조합은 균일 못 지킴. 카드를 포기하고 페이지 흐름에 그대로 놓는 쪽이 정답. → ASSEMBLY §2b "카드 포기 시그널" 섹션 신설
|
|
78
|
+
- **섹션 header가 "통짜 라벨"만 있으면 어색** — h2만 떠있고 count·설명·eyebrow 없으면 섹션끼리 구별 안 됨. count badge 또는 설명 1줄 **의미 있는 자리면** 넣기
|
|
79
|
+
|
|
80
|
+
**브라우저 렌더 3차 피드백 (유저 육안 검증, 2026-10-14)** — ★ 레이아웃 전면 교체:
|
|
81
|
+
- **"margin-top differential rhythm"은 틀린 결론이었음** — 2차에서 시도한 `A + B { margin-top: ... }`도 그룹 설계가 틀렸다는 신호였음. 정답은 **wrapper 추가 + 그룹별 flex column + gap**. 레이아웃 1원칙은 "**관련있는 것끼리 묶기**" — 이게 **padding/margin으로 간격 조정보다 선행**해야 함. → ASSEMBLY §2a 전면 재작성, [[feedback-group-first-layout]] 메모리 교체 (기존 differential rhythm 폐기)
|
|
82
|
+
- **유령 토큰 `--font-medium` → 모든 타이틀 얇음의 숨은 원인** — 실제 토큰은 `--font-weight-medium`. 레시피들이 전부 `var(--font-medium)` 썼고 **check_tokens hook이 유령 토큰은 못 잡음** (리터럴만 잡음). 조용히 상속된 400으로 렌더되어 h1·h2·cardTitle 전부 얇음. → ASSEMBLY §1 체크리스트 "font-weight 토큰 이름 정확성" 추가, 전수 교체
|
|
83
|
+
- **섹션 header의 `margin-bottom`은 틀림** — section 자체를 flex column + gap으로 묶으면 header와 body가 자연스럽게 간격을 가짐. margin-bottom은 그룹 설계 실패의 흔적
|
|
84
|
+
- **3-tier 리듬 확립**: 그룹 안 `space-2~3` / 섹션 반복 `space-6` / 그룹 사이 `space-10`. 콘솔 리듬 기준
|
|
85
|
+
- **`<section>` 태그 금지의 재확인** — AppShell의 `.content section`이 `padding-top: var(--space-20)` = **80px**을 모든 `<section>`에 꽂음. 메모리 [[feedback-no-semantic-tags-in-components]]가 이미 못 박은 함정인데 레시피 예시가 `<section>`을 그대로 썼음. 전수 `<div>`로 교체 + section-header 레시피 Don't에 명시 + ASSEMBLY §1 체크리스트 추가
|
|
86
|
+
- **그룹 안에서도 "미니 그룹" 필요** — introGroup 안 banner↔header↔hint 셋을 `space-3` 하나로 통일했더니 "같은 거리로 뭉쳐" 성격 구분 안 됨. **banner(메타)와 headerBlock(타이틀+설명)**을 분리하는 중간 wrapper(headerBlock) 추가 → introGroup gap을 `space-6`로 벌리고 headerBlock 안은 `space-2` 좁게. 그룹 설계는 **재귀적**이어야 함.
|
|
87
|
+
- **3-tier 리듬 수치 상향** — 초기 "섹션 반복 space-6 / 그룹 space-10"은 실측에서 좁았음. "섹션 ↔ 섹션 반복"과 "그룹 ↔ 그룹"은 둘 다 `space-20` (80px) 수준이 자연스러움. space-6은 "섹션 안 블록 단위" 또는 "같은 그룹 안 성격 다른 요소"에만.
|
|
88
|
+
- **"가까운 관계일수록 더 좁게" 원칙 확립 (ASSEMBLY §2a 재작성)** — 유저 지적: section header↔body `space-2` + cardList 아이템 간 `space-2`가 **같은 값**이라 "타이틀과 아이템이 같은 레벨"로 뭉쳐 보임. **인접한 두 레벨은 반드시 한 단계 이상 차이**가 나야 위계 성립. 수정: 아이템 간 `space-1`, 섹션 header↔body `space-3`. 같은 원칙으로 headerBlock gap도 `space-2`→`space-3` 상향 (pageHeader 안 title↔description이 `space-1`이므로)
|
|
89
|
+
- **"아이콘 + 다줄 muted 텍스트" 자리 → 벤더 `Hint`로 확정** — assembly-test의 inlineHint 수제 조립이 flex align/margin-top 보정으로도 반복 어긋남. 벤더 신설 결정, 이름 `Hint`. Layer 2 스펙은 [[components/hint]], Layer 1 코드는 유저 작성. 안내 자리 3층 구분 확립:
|
|
90
|
+
- 흐름 속 각주 (톤·액션 없음) = Hint
|
|
91
|
+
- 액션 유도 안내 블록 = Callout
|
|
92
|
+
- 페이지 폭 상태 바 = page-banner 레시피
|
|
93
|
+
- **벤더 신설이 레시피보다 나은 자리 식별 — 반복 함정 임계점** — 레시피 수정 2회 이상 반복해도 특정 조립 실패가 재발하면 "레시피로 담기 어려운 자리". 벤더로 승격 신호. "line-box 안 아이콘 정렬"처럼 **구현 디테일이 결과를 좌우**하는 자리는 특히.
|
|
94
|
+
- **card-row 레시피 폐기 — "리스트 자리는 벤더 필수" 룰 PRINCIPLES에 승격** — 유저 지적: 레시피에서 수제 조립 가이드를 올린 것 자체가 메모리 [[feedback-no-hand-rolled-components]]를 레시피가 어긴 셈. 메모리 수준이 아니라 **PRINCIPLES §5a HARD로 승격**해 "리스트는 무조건 Table/ListItem/EventListItem/MenuItem" 못 박음. "카드처럼 보이는 리스트"가 필요하면 레시피/수제 조립 금지, ListItem 벤더에 새 variant 신설. ASSEMBLY §1 1단계와 체크리스트에도 반영.
|
|
95
|
+
|
|
96
|
+
이 섹션은 **살아있는 체인지로그** — 새 결함이 나오면 추가하고 레시피를 수정한다.
|
|
97
|
+
|
|
98
|
+
## 관련
|
|
99
|
+
|
|
100
|
+
- [[ASSEMBLY]] — 조립 알고리즘. 레시피는 그 3단계에서 참조된다
|
|
101
|
+
- [[GUIDE]] — 공통 골격·N/A 규칙. 레시피 파일도 이를 따른다
|
|
102
|
+
- [[components/_variants]] — 역할 × variant 매핑. 레시피 내 vendor 선택 시 참조
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# ~~Card Row~~ (폐기 — ListItem 벤더로 대체)
|
|
2
|
+
|
|
3
|
+
> ⚠ **이 레시피는 폐기됨.** 반복 리스트 자리는 **반드시 벤더 컴포넌트**를 쓴다 ([[PRINCIPLES]] §5a HARD). 레시피 조립 금지.
|
|
4
|
+
|
|
5
|
+
## 왜 폐기됐나
|
|
6
|
+
|
|
7
|
+
이 레시피는 "border + bg + radius 있는 카드형 row"를 벤더 variant로 커버 못한다는 전제로 작성됐지만, 실제로는 **설계 판단 자체가 틀렸음**:
|
|
8
|
+
|
|
9
|
+
1. [[components/list-item]]은 **chrome 없음이 설계 공식** — 블록 신호가 필요하면 Callout으로 승격이 정답
|
|
10
|
+
2. 수제 조립 레시피는 메모리 [[feedback-no-hand-rolled-components]]와 PRINCIPLES §5a를 직접 위반
|
|
11
|
+
3. "카드처럼 보이는 리스트"가 꼭 필요하면 → ListItem 벤더에 `surface="card"` 등 **새 variant를 신설** 후 사용
|
|
12
|
+
|
|
13
|
+
## 지금 쓸 자리는 어디?
|
|
14
|
+
|
|
15
|
+
반복 아이템 N개를 수직 나열하는 자리는 전부 아래 중 하나:
|
|
16
|
+
|
|
17
|
+
| 자리 성격 | 쓸 벤더 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| **표 역할** (열 공통 축이 있는 격자) | [[components/table-row]] + [[components/table-header]] |
|
|
20
|
+
| **단건 반복** (알림·활동·작업·자원 목록) | [[components/list-item]] |
|
|
21
|
+
| **날짜 중심 이벤트** | [[components/event-list-item]] |
|
|
22
|
+
| **메뉴 아이템** (팝오버·사이드바) | [[components/menu-item]] / [[components/sidebar-item]] |
|
|
23
|
+
|
|
24
|
+
## ListItem 조립 예시 (card-row가 받으려던 자리)
|
|
25
|
+
|
|
26
|
+
작업 리스트 (상태 Badge + chevron):
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
<ListItem
|
|
30
|
+
href={item.href}
|
|
31
|
+
align="top"
|
|
32
|
+
leading={<Icon name="list-dashes" />}
|
|
33
|
+
trailing={
|
|
34
|
+
<>
|
|
35
|
+
<Badge variant="soft" status={item.status.kind}>{item.status.label}</Badge>
|
|
36
|
+
<IconButton size="sm" variant="ghost" icon={<Icon name="caret-right" />} aria-label="열기" />
|
|
37
|
+
</>
|
|
38
|
+
}
|
|
39
|
+
>
|
|
40
|
+
<ListItem.Title>{item.title}</ListItem.Title>
|
|
41
|
+
<ListItem.Meta>
|
|
42
|
+
<time>요청 {item.requestedAt}</time>
|
|
43
|
+
{item.note && <>
|
|
44
|
+
<ListItem.MetaDivider />
|
|
45
|
+
<span>{item.note}</span>
|
|
46
|
+
</>}
|
|
47
|
+
</ListItem.Meta>
|
|
48
|
+
</ListItem>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `size`·`align`·`leading`·`trailing` 선택은 [[components/list-item]] "결정 가이드" 참고
|
|
52
|
+
- chrome(border/bg)이 필요하면 ListItem 설계 재검토 — 블록 신호면 Callout, "카드형 리스트"면 ListItem 벤더에 새 variant 신설
|
|
53
|
+
|
|
54
|
+
## 관련
|
|
55
|
+
|
|
56
|
+
- [[PRINCIPLES]] §5a — 리스트 자리 수제 조립 영구 금지 (HARD)
|
|
57
|
+
- [[components/list-item]] — 범용 한 줄 anatomy
|
|
58
|
+
- [[ASSEMBLY]] §1 — 1단계에서 리스트 자리는 반드시 벤더로 확정
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Form Footer
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
폼·모달·drawer 하단의 **액션 묶음** 자리. 주 액션(primary)과 보조 액션(outline)이 **반드시 쌍으로** 등장하는 자리라 레시피로 못 박는다. 유저·AI가 자주 틀리는 지점: variant 선택, 좌/우 정렬, 액션 순서.
|
|
5
|
+
|
|
6
|
+
### Anatomy
|
|
7
|
+
한 줄 row, 액션은 **오른쪽 정렬**이 기본.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
[.............................spacer............................] [secondary] [primary]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- spacer: `flex: 1 1 auto` (액션들을 오른쪽으로 밀어냄)
|
|
14
|
+
- secondary (취소·닫기): [[components/button]] `variant="outline"`
|
|
15
|
+
- primary (저장·제출·확인): [[components/button]] `variant="primary"`
|
|
16
|
+
- 액션 간 gap: `space-1` 고정 (버튼↔버튼 묶음, [[tokens/spacing]])
|
|
17
|
+
- footer 자체 상단: `space-4` 또는 `space-6` 공간 (본문과의 분리) — 선 없이 공간으로
|
|
18
|
+
|
|
19
|
+
### Do
|
|
20
|
+
- 액션 순서: **왼쪽 secondary → 오른쪽 primary** ([[components/_variants]] "중요=primary, 서브=outline")
|
|
21
|
+
- destructive 액션이 primary 자리로 올 때: `variant="destructive"` 사용. destructive가 두 번째 시선이면 `destructive-ghost`
|
|
22
|
+
- 모달의 경우 primary는 항상 하나 — ESC/닫기 외에 두 개 이상의 "주 액션"이 있으면 모달 설계 재검토
|
|
23
|
+
- footer 상단에 **선이 필요하면** `border-top: 1px solid var(--border)`만, 배경 변화 없이 — [[PRINCIPLES]] §5b
|
|
24
|
+
- 좌측에 **보조 정보**(예: "변경사항 저장 안 됨")가 필요하면 spacer 자리에 muted 텍스트 1줄. 더는 금지
|
|
25
|
+
|
|
26
|
+
### Don't
|
|
27
|
+
- [HARD] primary 2개 금지 — [[PRINCIPLES]] §4a HARD 예시. 주 액션은 한 화면에 하나
|
|
28
|
+
- [HARD] 액션을 세로로 쌓지 않는다 (모바일에서도 가로 유지가 기본). 세로가 필요하면 drawer/sheet가 맞는지 재검토
|
|
29
|
+
- [HARD] secondary로 `variant="secondary"`(레거시) 쓰지 않는다 — `outline`을 쓴다 ([[components/_variants]] 액션 선택 규칙)
|
|
30
|
+
- [HARD] 액션 사이 gap을 `space-2` 이상으로 벌리지 않는다 — 버튼 묶음은 `space-1` 고정 ([[tokens/spacing]])
|
|
31
|
+
- [SOFT] 왼쪽 정렬·중앙 정렬 금지 — 우측 정렬이 디폴트. 좌측 정렬은 오직 "폼 흐름이 좌에서 우로 흐르지 않는" 특수 자리에서만
|
|
32
|
+
|
|
33
|
+
### Fallback
|
|
34
|
+
primary 하나만 있으면 그 단독으로 (여전히 오른쪽 정렬). destructive가 primary 자리면 secondary도 반드시 함께 — 확인 가능한 escape를 제공.
|
|
35
|
+
|
|
36
|
+
### 조립 예시
|
|
37
|
+
```tsx
|
|
38
|
+
<footer className="form-footer">
|
|
39
|
+
<Button variant="outline" size="md" onClick={onCancel}>
|
|
40
|
+
취소
|
|
41
|
+
</Button>
|
|
42
|
+
<Button variant="primary" size="md" type="submit">
|
|
43
|
+
저장
|
|
44
|
+
</Button>
|
|
45
|
+
</footer>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```css
|
|
49
|
+
.form-footer {
|
|
50
|
+
display: flex;
|
|
51
|
+
justify-content: flex-end;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: var(--space-1);
|
|
54
|
+
margin-top: var(--space-6);
|
|
55
|
+
}
|
|
56
|
+
/* 좌측 보조 정보가 필요한 경우 */
|
|
57
|
+
.form-footer .status {
|
|
58
|
+
margin-right: auto;
|
|
59
|
+
font-size: var(--text-sm);
|
|
60
|
+
color: var(--fg-muted);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
destructive 예시:
|
|
65
|
+
```tsx
|
|
66
|
+
<footer className="form-footer">
|
|
67
|
+
<Button variant="outline" size="md">취소</Button>
|
|
68
|
+
<Button variant="destructive" size="md">삭제</Button>
|
|
69
|
+
</footer>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 관련
|
|
73
|
+
- [[components/button]] — variant 선택
|
|
74
|
+
- [[components/_variants]] — 역할 × variant 매핑
|
|
75
|
+
- [[components/modal]] — footer 자리의 vendor host
|
|
76
|
+
- [[tokens/spacing]] — space-1 버튼 묶음 고정
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Meta Line
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
한 줄 안에 **여러 메타 라벨**(요청 시각, 작성자, 상태 주석, 카테고리 등)을 가로로 나열하는 자리. 카드 서브텍스트, 리스트 row의 두번째 줄, 테이블 셀 아래 보조 라인에 반복 등장. 구분자가 틀리면 UI가 즉시 싸구려로 뜬다.
|
|
5
|
+
|
|
6
|
+
### Anatomy
|
|
7
|
+
```
|
|
8
|
+
[label1] [separator] [label2] [separator] [label3]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- label은 모두 같은 톤·크기 (한 줄 안에서 위계 X)
|
|
12
|
+
- separator는 **짧고 연한 세로 hairline** 1개만 — 구간 사이에 들어감 (처음/끝 바깥 X)
|
|
13
|
+
- label과 separator 사이: `space-2` (양쪽 균등)
|
|
14
|
+
|
|
15
|
+
### Do
|
|
16
|
+
- 각 label: `text-xs` 또는 `text-sm`, `color: var(--fg-muted)`
|
|
17
|
+
- separator는 `<span>` + `border-left: 1px solid var(--border)` + `align-self: stretch` — 텍스트 하이트에 자동 맞춤
|
|
18
|
+
- separator는 `aria-hidden="true"` — 스크린리더에서 읽히지 않게
|
|
19
|
+
- label 중 하나가 **값**(사람 이름·시각)이고 나머지가 **고정 라벨**이면, 값만 `color: var(--fg)` (살짝 올림) — 단 톤 차이는 1단계 이내
|
|
20
|
+
- 전체 wrapper: `display: flex; align-items: center; gap: var(--space-2);`
|
|
21
|
+
- 날짜·시간은 [[feedback-date-writing]] 전략 중 하나로 (상대어 "12분 전" / "10월 13일") — 숫자 패턴("10. 13") 금지
|
|
22
|
+
|
|
23
|
+
### Don't
|
|
24
|
+
- [HARD] **가운뎃점(`·` `•` `∙`)·점(`.`)·pipe(`|`)·슬래시(`/`)** 등 **문자 구분자 금지** — 메모리 [no-bullet-separator]. hairline으로만 분리
|
|
25
|
+
- [HARD] separator를 label 앞뒤에도 넣지 않는다. **"사이에만"**. 끝에 또는 처음에 두면 끊긴 느낌
|
|
26
|
+
- [HARD] label 하나하나를 Badge로 쓰지 않는다 — 메타 라인은 톤이 조용해야 한다. Badge는 상태·카테고리 **표시**가 필요할 때만, 메타 라인에 섞이지 않게 분리
|
|
27
|
+
- [SOFT] 3개 넘으면 메타 라인이 아니라 **별도 블록**이 맞는지 재검토. 4개 이상은 사용자가 다 못 읽음
|
|
28
|
+
|
|
29
|
+
### Fallback
|
|
30
|
+
항목이 1개뿐이면 separator 없음. 2개 이상부터 hairline 사용.
|
|
31
|
+
|
|
32
|
+
### 조립 예시
|
|
33
|
+
```tsx
|
|
34
|
+
<div className="meta-line">
|
|
35
|
+
<span>요청 10월 13일 오후 10시 25분</span>
|
|
36
|
+
<span className="sep" aria-hidden="true" />
|
|
37
|
+
<span>근거 변경 확인 필요</span>
|
|
38
|
+
</div>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
.meta-line {
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: var(--space-2);
|
|
46
|
+
font-size: var(--text-sm);
|
|
47
|
+
color: var(--fg-muted);
|
|
48
|
+
}
|
|
49
|
+
/* separator는 border-left로 — width/height 리터럴 금지 (check_tokens hook) */
|
|
50
|
+
.meta-line .sep {
|
|
51
|
+
border-left: 1px solid var(--border);
|
|
52
|
+
align-self: stretch;
|
|
53
|
+
margin: var(--space-0\.5) 0;
|
|
54
|
+
flex: 0 0 auto;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 관련
|
|
59
|
+
- [[components/badge]] — 상태·카테고리 표시가 필요하면 (메타 라인 바깥에 분리)
|
|
60
|
+
- [[tokens/color]] — fg-muted, border
|
|
61
|
+
- [[tokens/typography]] — text-sm/text-xs
|