@conscience-technology/ctds 0.3.4 → 0.4.1
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/Button/Button.module.css +2 -2
- package/assets/components/EventListItem/EventListItem.module.css +220 -0
- package/assets/components/EventListItem/EventListItem.tsx +205 -0
- package/assets/components/EventListItem/index.ts +8 -0
- package/assets/components/Icon/Icon.tsx +80 -41
- 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/ListItem/ListItem.module.css +91 -211
- package/assets/components/ListItem/ListItem.tsx +130 -154
- package/assets/components/ListItem/index.ts +2 -2
- package/assets/components/MetricCard/MetricCard.module.css +5 -5
- 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/Sidebar/Sidebar.module.css +81 -4
- package/assets/components/Sidebar/Sidebar.tsx +11 -2
- package/assets/components/SplitButton/SplitButton.module.css +3 -3
- package/assets/components/Stepper/Stepper.module.css +196 -52
- package/assets/components/Stepper/Stepper.tsx +68 -13
- package/assets/components/Switch/Switch.module.css +1 -9
- package/assets/components/Topbar/Topbar.module.css +33 -5
- package/assets/components/Topbar/Topbar.tsx +62 -7
- package/assets/components/_data.ts +11 -4
- package/assets/components/index.ts +2 -0
- package/assets/components/tokens.css +23 -10
- package/assets/prompts/ASSEMBLY.md +219 -0
- package/assets/prompts/PRINCIPLES.md +70 -0
- package/assets/prompts/axes/icon.md +7 -1
- package/assets/prompts/axes/palette.md +11 -0
- package/assets/prompts/components/_variants.md +5 -1
- package/assets/prompts/components/callout.md +3 -3
- package/assets/prompts/components/event-list-item.md +103 -0
- package/assets/prompts/components/generated-image.md +80 -0
- package/assets/prompts/components/hint.md +57 -0
- package/assets/prompts/components/kanban.md +167 -0
- package/assets/prompts/components/list-item.md +182 -204
- package/assets/prompts/components/metric-card.md +2 -2
- package/assets/prompts/components/page-header.md +155 -0
- package/assets/prompts/components/toast.md +1 -0
- package/assets/prompts/components/topbar.md +77 -59
- 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/assets/prompts/themes/focus.md +72 -0
- package/assets/skills/ctds/SKILL.md +1 -0
- package/assets/skills/ctds-page/SKILL.md +12 -2
- package/assets/skills/ctds-wireframe/SKILL.md +35 -1
- package/package.json +2 -4
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
### 원칙
|
|
4
4
|
앱 shell 최상단의 가로 bar — Breadcrumb·탐색·검색·알림·사용자 메뉴처럼 **"지금 어느 자리인지 + 공용 액션"**만 담는 chrome. 콘텐츠 자체(hero head, 큰 환영 메시지, 섹션 제목)는 Topbar의 자리가 아니라 **아래 body의 첫 블록 자리**다. Topbar는 세로 높이가 고정된 **얇은 축**이므로, 수직 리듬이 큰 요소를 끼우면 align이 깨지고 bar 자체가 "들뜬다".
|
|
5
5
|
|
|
6
|
+
구조는 **leading / center / trailing 3-zone 고정**. 루트가 부모 폭을 꽉 채우고 세 zone이 자리를 나눈다. "왼쪽에 몰림" 현상은 구조적으로 발생하지 않는다 — zone이 없으면 애초에 Topbar를 쓰지 않는다.
|
|
7
|
+
|
|
6
8
|
### Do
|
|
9
|
+
- `Topbar.Leading` + `Topbar.Trailing` **짝**을 기본으로. 한쪽만 있는 경우도 OK — 반드시 zone 안에 넣는다.
|
|
10
|
+
- 가운데에 pin하고 싶은 요소(검색 트리거 pill 등)는 `Topbar.Center`로. 비우면 L↔T 사이 공간이 자동으로 벌어진다.
|
|
7
11
|
- Breadcrumb·검색 Input·알림 IconButton·avatar menu·탐색 Tabs처럼 **반복적으로 쓰이는 공용 액션만** 담는다.
|
|
8
12
|
- 세로는 `size` prop(`sm`/`md`)이 결정하는 한 줄만. 수직으로 쌓지 않는다.
|
|
9
|
-
- 좌/우 정렬은 `Topbar.Spacer`로. 가운데 그룹이 필요하면 중앙에 또 하나의 Spacer를 끼운다.
|
|
10
13
|
- 자체 chrome(bg/border/shadow)은 두지 않는다 — 표현은 상위 [[components/app-frame]] variant가 담당.
|
|
11
14
|
- 페이지의 "지금 어디" 신호는 **Breadcrumb**로. 큰 타이틀로 쓰고 싶으면 Topbar가 아니라 body 첫 블록.
|
|
12
15
|
|
|
13
16
|
### Don't
|
|
17
|
+
- zone 밖에 **자식을 바로 넣지 않는다**. `<Topbar><Breadcrumb/>...</Topbar>` 같은 구조 금지 — 반드시 Leading/Center/Trailing 중 하나로 감싼다.
|
|
14
18
|
- `h1`·큰 hero head·환영 메시지("good morning, …")·페이지 섹션 타이틀을 Topbar 안에 넣지 않는다 — Topbar는 **얇은 축**이고 큰 타이포가 들어가는 순간 `align-items` 전쟁이 벌어진다. 이걸 로컬 CSS로 때우는 순간(`align-items: flex-start`, `padding-bottom` override 등) Topbar의 "축" 약속이 깨지고, 아래 body와의 수직 리듬도 어긋난다. hero는 body의 첫 섹션으로 꺼낸다.
|
|
15
19
|
- 수직 스택(eyebrow + title 세로 두 줄 같은 조합)을 Topbar 안에 넣지 않는다. 수직 리듬은 body의 섹션 head에서.
|
|
16
20
|
- 알림·검색·Breadcrumb 외의 **콘텐츠**(KPI·요약·공지 문구)를 Topbar에 끼우지 않는다. 그건 body hero 섹션 또는 Callout의 자리.
|
|
@@ -20,7 +24,7 @@
|
|
|
20
24
|
|
|
21
25
|
### Fallback
|
|
22
26
|
`size` 애매하면 `md`. 콘텐츠 밀도가 아주 낮은 랜딩/문서 shell이면 `sm`.
|
|
23
|
-
|
|
27
|
+
Leading·Trailing 둘 다 비면 Topbar를 두지 않는다 — 빈 bar는 수직 공간만 먹는다.
|
|
24
28
|
|
|
25
29
|
### 관련
|
|
26
30
|
- [[components/app-frame]] — Topbar를 품는 shell. bg/border/shadow 등 chrome 표현은 AppFrame variant가 쥔다.
|
|
@@ -33,12 +37,19 @@ Breadcrumb 자리·공용 액션 자리 둘 다 비면 Topbar를 두지 않는
|
|
|
33
37
|
### Props 시그니처
|
|
34
38
|
```tsx
|
|
35
39
|
<Topbar size="sm | md">
|
|
36
|
-
{
|
|
40
|
+
<Topbar.Leading>{/* brand·back·breadcrumb·title */}</Topbar.Leading>
|
|
41
|
+
<Topbar.Center>{/* 선택: 가운데 pin 요소 (검색 트리거 등) */}</Topbar.Center>
|
|
42
|
+
<Topbar.Trailing>{/* 공용 액션: 검색·알림·avatar·primary 버튼 */}</Topbar.Trailing>
|
|
37
43
|
</Topbar>
|
|
38
|
-
|
|
39
|
-
<Topbar.Spacer /> {/* 좌/우 정렬용 flex:1 빈 블록 */}
|
|
40
44
|
```
|
|
41
45
|
|
|
46
|
+
| 요소 | 역할 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `Topbar` | width:100%, 세로 flex 축. 높이만 `size`로 결정 |
|
|
49
|
+
| `Topbar.Leading` | 왼쪽 zone, 자연 폭. 자식 gap `--space-1` |
|
|
50
|
+
| `Topbar.Center` | 가운데 zone, 1fr + 중앙 정렬. 비우면 L↔T 자동 분리 |
|
|
51
|
+
| `Topbar.Trailing` | 오른쪽 zone, 자연 폭 + `margin-inline-start: auto`로 오른쪽 pin |
|
|
52
|
+
|
|
42
53
|
### Size 매트릭스
|
|
43
54
|
|
|
44
55
|
| size | 세로 높이 | 용도 |
|
|
@@ -47,6 +58,7 @@ Breadcrumb 자리·공용 액션 자리 둘 다 비면 Topbar를 두지 않는
|
|
|
47
58
|
| `md` | 기본 | 앱 콘솔. Breadcrumb + 공용 액션 2~3개 |
|
|
48
59
|
|
|
49
60
|
### 금지 조합
|
|
61
|
+
- zone 없이 바로 자식 삽입 — Leading/Center/Trailing 중 하나로 반드시 감싼다.
|
|
50
62
|
- Topbar 안에 `h1`·`h2`·hero head 삽입 — "얇은 축" 약속 깨짐. body hero 섹션으로 꺼낸다.
|
|
51
63
|
- Topbar 안에 수직 스택(eyebrow + title 2줄) — 단일 줄 원칙 깨짐.
|
|
52
64
|
- 로컬 CSS로 `align-items: flex-start` + `padding-bottom` 늘려서 큰 타이포 수용 — 수습이 필요한 순간 그 요소가 자리가 아니라는 신호.
|
|
@@ -56,77 +68,83 @@ Breadcrumb 자리·공용 액션 자리 둘 다 비면 Topbar를 두지 않는
|
|
|
56
68
|
|
|
57
69
|
### 조합 예시
|
|
58
70
|
|
|
59
|
-
Topbar는
|
|
71
|
+
Topbar는 **3-zone 자유 슬롯 컨테이너**다. "page / detail / panel" 같은 variant를 따로 두지 않고, **같은 Topbar에 다른 children**을 넣어 역할을 바꾼다. 뒤로가기·close도 전용 컴포넌트가 아니라 `IconButton` + `Icon`을 zone에 꽂는 조립.
|
|
60
72
|
|
|
61
73
|
```tsx
|
|
62
74
|
// 1) page — 운영 콘솔. Breadcrumb + 검색 + 알림
|
|
63
75
|
<Topbar size="md">
|
|
64
|
-
<
|
|
65
|
-
<Breadcrumb
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
<Topbar.Leading>
|
|
77
|
+
<Breadcrumb>
|
|
78
|
+
<Breadcrumb.Item href="/today">today</Breadcrumb.Item>
|
|
79
|
+
<Breadcrumb.Current>meetings</Breadcrumb.Current>
|
|
80
|
+
</Breadcrumb>
|
|
81
|
+
</Topbar.Leading>
|
|
82
|
+
<Topbar.Trailing>
|
|
83
|
+
<Input
|
|
84
|
+
size="md"
|
|
85
|
+
variant="filled"
|
|
86
|
+
placeholder="search anything"
|
|
87
|
+
leftIcon={<Icon name="search" />}
|
|
88
|
+
rightAction={<Shortcut size="sm">⌘K</Shortcut>}
|
|
89
|
+
/>
|
|
90
|
+
<IconButton icon={<Icon name="bell" />} size="md" variant="ghost" />
|
|
91
|
+
</Topbar.Trailing>
|
|
77
92
|
</Topbar>
|
|
78
93
|
|
|
79
94
|
// 2) detail — 뒤로가기 + Breadcrumb로 현재 위치 + overflow 액션
|
|
80
95
|
<Topbar size="md">
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<Breadcrumb
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
<Topbar.Leading>
|
|
97
|
+
<IconButton
|
|
98
|
+
icon={<Icon name="caret-left" />}
|
|
99
|
+
size="md"
|
|
100
|
+
variant="ghost"
|
|
101
|
+
aria-label="back"
|
|
102
|
+
/>
|
|
103
|
+
<Breadcrumb>
|
|
104
|
+
<Breadcrumb.Item href="/meetings">meetings</Breadcrumb.Item>
|
|
105
|
+
<Breadcrumb.Current>oct 5 standup</Breadcrumb.Current>
|
|
106
|
+
</Breadcrumb>
|
|
107
|
+
</Topbar.Leading>
|
|
108
|
+
<Topbar.Trailing>
|
|
109
|
+
<IconButton
|
|
110
|
+
icon={<Icon name="dots-three" />}
|
|
111
|
+
size="md"
|
|
112
|
+
variant="ghost"
|
|
113
|
+
aria-label="more"
|
|
114
|
+
/>
|
|
115
|
+
</Topbar.Trailing>
|
|
98
116
|
</Topbar>
|
|
99
117
|
|
|
100
118
|
// 3) panel — 사이드패널/드로어 내부. 더 얇게 + close
|
|
101
119
|
<Topbar size="sm">
|
|
102
|
-
<
|
|
103
|
-
<Breadcrumb
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
<Topbar.Leading>
|
|
121
|
+
<Breadcrumb>
|
|
122
|
+
<Breadcrumb.Current>filters</Breadcrumb.Current>
|
|
123
|
+
</Breadcrumb>
|
|
124
|
+
</Topbar.Leading>
|
|
125
|
+
<Topbar.Trailing>
|
|
126
|
+
<IconButton
|
|
127
|
+
icon={<Icon name="x" />}
|
|
128
|
+
size="sm"
|
|
129
|
+
variant="ghost"
|
|
130
|
+
aria-label="close"
|
|
131
|
+
/>
|
|
132
|
+
</Topbar.Trailing>
|
|
112
133
|
</Topbar>
|
|
113
134
|
|
|
114
|
-
//
|
|
115
|
-
<
|
|
116
|
-
<Topbar
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{ /* 이하 KPI, 섹션들 … */ }
|
|
124
|
-
</main>
|
|
135
|
+
// 4) center zone — 가운데에 pin해야 할 때
|
|
136
|
+
<Topbar size="md">
|
|
137
|
+
<Topbar.Leading>{/* brand */}</Topbar.Leading>
|
|
138
|
+
<Topbar.Center>
|
|
139
|
+
<SearchTrigger />
|
|
140
|
+
</Topbar.Center>
|
|
141
|
+
<Topbar.Trailing>{/* 알림·avatar */}</Topbar.Trailing>
|
|
142
|
+
</Topbar>
|
|
125
143
|
```
|
|
126
144
|
|
|
127
|
-
### 역할별
|
|
145
|
+
### 역할별 zone 공식
|
|
128
146
|
|
|
129
|
-
| 역할 |
|
|
147
|
+
| 역할 | Leading | Center | Trailing | size |
|
|
130
148
|
|---|---|---|---|---|
|
|
131
149
|
| page | Breadcrumb | — | 검색 Input, 알림/avatar IconButton | `md` |
|
|
132
150
|
| detail | `IconButton icon="caret-left" variant="ghost"` + Breadcrumb | — | overflow/share 등 IconButton | `md` |
|
|
@@ -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
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Page Banner
|
|
2
|
+
|
|
3
|
+
### 원칙
|
|
4
|
+
페이지 폭에 걸치는 **안내 바**. 공유 상태·만료 안내·시스템 공지·권한 경고 등 "이 페이지 전체에 적용되는 메타 상태"를 알린다. Callout과 다름 — Callout은 사이드바·리스트 끝의 **국소** 블록, Banner는 페이지 **상단/하단**의 전면 바.
|
|
5
|
+
|
|
6
|
+
관찰: 유저 페이지에서 ①상단 "공유 열람 10.20 만료" ②중간 "`</>` 자동 처리 안내" ③하단 "열람 권한 접속" 셋 다 Banner 성격인데 서로 다른 스타일로 조립됐다 — 이 레시피가 그 셋을 하나의 리듬으로 묶는다.
|
|
7
|
+
|
|
8
|
+
### Anatomy
|
|
9
|
+
한 줄 row.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
[leading icon] [text (1~2줄)] [...spacer...] [action/dismiss]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- leading icon: 20px, [[components/icon]]
|
|
16
|
+
- text: `flex: 1 1 auto`, 1~2줄, `line-height: 1.4`
|
|
17
|
+
- action: [[components/button]] `variant="ghost"` 또는 간단한 link. **banner에서 primary 금지** — primary 액션이 필요하면 banner가 아니라 modal/dialog
|
|
18
|
+
- dismiss: [[components/icon-button]] `ghost` + close 아이콘 (옵션)
|
|
19
|
+
|
|
20
|
+
**padding:**
|
|
21
|
+
- 단일라인 (title만, 또는 1줄 메시지): 상하 `space-2`, 좌우 `space-3`
|
|
22
|
+
- 멀티라인 (title + sub, 또는 메타 라인 포함): 상하 `space-3`, 좌우 `space-4`
|
|
23
|
+
- 하단 조용한 안내: `data-size="sm"`로 다운스케일 — 상하 `space-2`, 좌우 `space-3`
|
|
24
|
+
|
|
25
|
+
**radius:** `var(--radius-md)` (단, 페이지 full-bleed로 쓰면 radius 0).
|
|
26
|
+
|
|
27
|
+
**gap:**
|
|
28
|
+
- leading↔text: 단일라인 `space-2`, 멀티라인 `space-3`
|
|
29
|
+
- text↔action: `space-3`
|
|
30
|
+
- banner.text 내부 title↔sub: `space-1` (space-0.5는 너무 타이트 — 두 줄이 녹아보임)
|
|
31
|
+
|
|
32
|
+
**leading icon 수직 정렬:** 멀티라인일 때 `align-self: flex-start` + `margin-top: space-0.5` — 센터 정렬이면 title보다 아이콘이 내려가 보임.
|
|
33
|
+
|
|
34
|
+
### Tone 체계
|
|
35
|
+
Banner는 톤 3종만 지원 — Callout과 통일.
|
|
36
|
+
|
|
37
|
+
| Tone | 자리 | 색 조합 |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `info` | 상태 공지 (만료, 공유 모드) | `background: var(--bg-subtle)` + border 없음 |
|
|
40
|
+
| `warning` | 주의 (권한 제한, 임시 상태) | `background: var(--bg-warning-subtle)` + border 없음 |
|
|
41
|
+
| `critical` | 위험 (만료 임박, 오류) | `background: var(--bg-critical-subtle)` + border 없음 |
|
|
42
|
+
|
|
43
|
+
### Do
|
|
44
|
+
- leading icon은 tone에 맞춤 — info=`info`/`share-network`, warning=`warning-circle`, critical=`warning`. **아이콘 이름은 `web/components/Icon/Icon.tsx` 레지스트리에서 확인**
|
|
45
|
+
- 텍스트는 **1문장 명제 + (옵션) 부가 1문장** — 길어지면 banner가 아니라 Callout이나 dedicated page
|
|
46
|
+
- 액션 라벨은 **동사**로 짧게 ("공유 열람 종료", "자세히")
|
|
47
|
+
- 상단 banner면 Page Header 위에 둠. 블록 아래 gap: `space-4`
|
|
48
|
+
- 하단 banner면 섹션들 다 끝난 뒤, `space-6` 간격
|
|
49
|
+
|
|
50
|
+
### Don't
|
|
51
|
+
- [HARD] **배경 + 테두리 동시 사용 금지** ([[PRINCIPLES]] §5b). Banner는 **배경 톤만**, border는 transparent
|
|
52
|
+
- [HARD] primary 버튼 금지 — banner는 "알림" 자리, 주 액션은 다른 곳
|
|
53
|
+
- [HARD] banner 안에 chip·Badge 섞지 않는다 — 톤이 겹쳐 시끄러워짐. 상태 분류가 필요하면 Banner tone을 바꾼다
|
|
54
|
+
- [HARD] 아이콘 인라인 `<svg>` 금지 — [[components/icon]] 경유
|
|
55
|
+
- [SOFT] banner 3개 이상 동시에 쌓지 않는다. 2개 이상이 쌓이면 "뭐가 중요한지" 판독 불가. 정보가 많으면 하나의 banner + "자세히" 액션으로
|
|
56
|
+
|
|
57
|
+
### Fallback
|
|
58
|
+
- action 없으면 그 자리 생략, text가 끝까지 참
|
|
59
|
+
- dismiss만 있고 action 없으면 dismiss는 trailing 우측 끝
|
|
60
|
+
|
|
61
|
+
### 조립 예시
|
|
62
|
+
```tsx
|
|
63
|
+
{/* info: 공유 열람 상태 */}
|
|
64
|
+
<aside className="page-banner" data-tone="info">
|
|
65
|
+
<Icon name="share-network" size={20} className="leading" />
|
|
66
|
+
<div className="text">
|
|
67
|
+
<strong>Kailey Kim님 프로젝트 공유 열람</strong>
|
|
68
|
+
<span className="sub">
|
|
69
|
+
조회 전용
|
|
70
|
+
<span className="sep" aria-hidden="true" />
|
|
71
|
+
10월 20일 오후 9시 53분 만료
|
|
72
|
+
<span className="sep" aria-hidden="true" />
|
|
73
|
+
Kailey 프로젝트 전체 공유
|
|
74
|
+
</span>
|
|
75
|
+
</div>
|
|
76
|
+
<Button variant="ghost" size="sm">공유 열람 종료</Button>
|
|
77
|
+
</aside>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
/* 테두리 선언 없이 배경만 — bg+stroke 조합 금지 ([[PRINCIPLES]] §5b) */
|
|
82
|
+
.page-banner {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: var(--space-3);
|
|
86
|
+
padding: var(--space-3) var(--space-4);
|
|
87
|
+
border-radius: var(--radius-md);
|
|
88
|
+
}
|
|
89
|
+
.page-banner[data-size="sm"] {
|
|
90
|
+
padding: var(--space-2) var(--space-3);
|
|
91
|
+
gap: var(--space-2);
|
|
92
|
+
}
|
|
93
|
+
.page-banner[data-tone="info"] { background: var(--bg-subtle); }
|
|
94
|
+
.page-banner[data-tone="warning"] { background: var(--bg-warning-subtle); }
|
|
95
|
+
.page-banner[data-tone="critical"] { background: var(--bg-critical-subtle); }
|
|
96
|
+
|
|
97
|
+
.page-banner .leading {
|
|
98
|
+
color: var(--fg-muted);
|
|
99
|
+
flex: 0 0 auto;
|
|
100
|
+
align-self: flex-start;
|
|
101
|
+
margin-top: var(--space-0\.5);
|
|
102
|
+
}
|
|
103
|
+
.page-banner .text {
|
|
104
|
+
flex: 1 1 auto;
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
gap: var(--space-1);
|
|
108
|
+
line-height: 1.4;
|
|
109
|
+
}
|
|
110
|
+
.page-banner .text .sub {
|
|
111
|
+
font-size: var(--text-sm);
|
|
112
|
+
color: var(--fg-muted);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 관련
|
|
117
|
+
- [[components/callout]] — 국소 안내 블록 (사이드바·리스트 끝). Banner와 자리가 다름
|
|
118
|
+
- [[components/button]] — ghost 액션
|
|
119
|
+
- [[components/icon-button]] — dismiss
|
|
120
|
+
- [[tokens/color]] — bg-subtle, bg-warning-subtle, bg-critical-subtle
|
|
121
|
+
- [[PRINCIPLES]] §5b — 배경 또는 테두리
|