@conscience-technology/ctds 0.3.4 → 0.4.0
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/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/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/list-item.md +182 -204
- package/assets/prompts/components/metric-card.md +2 -2
- package/assets/prompts/components/toast.md +1 -0
- package/assets/prompts/components/topbar.md +77 -59
- 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
|
@@ -527,6 +527,7 @@ export const componentSlugs: Record<string, string> = {
|
|
|
527
527
|
"Menu Item": "general/menu-item",
|
|
528
528
|
Badge: "general/badge",
|
|
529
529
|
Avatar: "general/avatar",
|
|
530
|
+
"Avatar Upload": "general/avatar-upload",
|
|
530
531
|
Tooltip: "general/tooltip",
|
|
531
532
|
Shortcut: "general/shortcut",
|
|
532
533
|
"Filter Button": "general/filter-button",
|
|
@@ -552,8 +553,11 @@ export const componentSlugs: Record<string, string> = {
|
|
|
552
553
|
"Identity Button": "nav/identity-button",
|
|
553
554
|
Pagination: "nav/pagination",
|
|
554
555
|
"App Frame": "layout/app-frame",
|
|
556
|
+
"Page Header": "nav/page-header",
|
|
555
557
|
Stepper: "nav/stepper",
|
|
556
558
|
"Context Menu": "overlay/context-menu",
|
|
559
|
+
"Dropdown Menu": "overlay/dropdown-menu",
|
|
560
|
+
Disclosure: "overlay/disclosure",
|
|
557
561
|
Callout: "feedback/callout",
|
|
558
562
|
Modal: "feedback/modal",
|
|
559
563
|
Drawer: "feedback/drawer",
|
|
@@ -562,9 +566,11 @@ export const componentSlugs: Record<string, string> = {
|
|
|
562
566
|
"Table Row": "data/table-row",
|
|
563
567
|
"Table Row Select": "data/table-row-select",
|
|
564
568
|
"Table Row Edit": "data/table-row-edit",
|
|
569
|
+
"Event List Item": "data/event-list-item",
|
|
565
570
|
"Event Chip": "data/event-chip",
|
|
566
571
|
"List Item": "data/list-item",
|
|
567
572
|
"Metric Card": "data/metric-card",
|
|
573
|
+
"Kanban": "data/kanban",
|
|
568
574
|
"Gallery Item": "data/gallery-item",
|
|
569
575
|
"Empty State": "data/empty-state",
|
|
570
576
|
"Progress Bar": "feedback/progress-bar",
|
|
@@ -596,7 +602,7 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
596
602
|
id: "general",
|
|
597
603
|
title: "General",
|
|
598
604
|
items: [
|
|
599
|
-
"Button", "Split Button", "Icon Button", "Menu Item", "Toolbar", "Badge", "Avatar",
|
|
605
|
+
"Button", "Split Button", "Icon Button", "Menu Item", "Toolbar", "Badge", "Avatar", "Avatar Upload",
|
|
600
606
|
"Tooltip", "Shortcut", "Filter Button", "Chip", "Separator", "Skeleton", "Aspect Ratio",
|
|
601
607
|
"Code Block",
|
|
602
608
|
],
|
|
@@ -616,7 +622,8 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
616
622
|
title: "Data Display",
|
|
617
623
|
items: [
|
|
618
624
|
"Table Header", "Table Row", "Table Row Select", "Table Row Edit",
|
|
619
|
-
"List Item", "Metric Card", "Gallery Item", "File Icon", "Attachment Item", "Event Chip",
|
|
625
|
+
"List Item", "Metric Card", "Gallery Item", "File Icon", "Attachment Item", "Event Chip", "Event List Item",
|
|
626
|
+
"Kanban",
|
|
620
627
|
"Empty State", "Description List", "Timeline",
|
|
621
628
|
],
|
|
622
629
|
},
|
|
@@ -624,7 +631,7 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
624
631
|
id: "nav",
|
|
625
632
|
title: "Navigation",
|
|
626
633
|
items: [
|
|
627
|
-
"App Frame", "Tabs", "Breadcrumb", "Sidebar", "Sidebar Split", "Topbar", "Identity Button", "Pagination",
|
|
634
|
+
"App Frame", "Page Header", "Tabs", "Breadcrumb", "Sidebar", "Sidebar Split", "Topbar", "Identity Button", "Pagination",
|
|
628
635
|
"Stepper",
|
|
629
636
|
],
|
|
630
637
|
},
|
|
@@ -640,7 +647,7 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
640
647
|
id: "overlay",
|
|
641
648
|
title: "Overlay / Menu",
|
|
642
649
|
items: [
|
|
643
|
-
"Context Menu", "Collapsible", "Toggle Group",
|
|
650
|
+
"Context Menu", "Dropdown Menu", "Disclosure", "Collapsible", "Toggle Group",
|
|
644
651
|
],
|
|
645
652
|
},
|
|
646
653
|
{
|
|
@@ -29,6 +29,7 @@ export * from "./Drawer";
|
|
|
29
29
|
export * from "./DropdownMenu";
|
|
30
30
|
export * from "./EmptyState";
|
|
31
31
|
export * from "./EventChip";
|
|
32
|
+
export * from "./EventListItem";
|
|
32
33
|
export * from "./FileIcon";
|
|
33
34
|
export * from "./FilterButton";
|
|
34
35
|
export * from "./GalleryItem";
|
|
@@ -47,6 +48,7 @@ export * from "./MessageTable";
|
|
|
47
48
|
export * from "./MetricCard";
|
|
48
49
|
export * from "./Modal";
|
|
49
50
|
export * from "./Onboarding";
|
|
51
|
+
export * from "./PageHeader";
|
|
50
52
|
export * from "./Pagination";
|
|
51
53
|
export * from "./ProgressBar";
|
|
52
54
|
export * from "./QuotationBar";
|
|
@@ -97,6 +97,16 @@
|
|
|
97
97
|
--input-w-md: calc(280px * var(--space-scale));
|
|
98
98
|
--input-w-lg: calc(400px * var(--space-scale));
|
|
99
99
|
|
|
100
|
+
/* Breakpoint — 레이아웃 전환 임계점.
|
|
101
|
+
CSS @media의 feature value는 var() 참조가 불가하므로 변수 자체는 참조용/문서용.
|
|
102
|
+
실제 미디어쿼리는 아래 값과 동일한 리터럴을 쓴다. 두 자리 뿐이라 흩어뜨리지 않는다.
|
|
103
|
+
sm = 560px — 모바일/태블릿 경계. "한 손 조작" vs "두 손 조작"
|
|
104
|
+
md = 900px — 태블릿 세로/데스크톱 경계. shell(topbar+sidebar) 축소 전환
|
|
105
|
+
신규 미디어쿼리를 작성할 땐 이 두 값 중 하나로 통일한다. 다른 값이 필요하면
|
|
106
|
+
여기 토큰부터 추가. */
|
|
107
|
+
--bp-sm: 560px;
|
|
108
|
+
--bp-md: 900px;
|
|
109
|
+
|
|
100
110
|
--radius-none: 0;
|
|
101
111
|
--radius-xs: 6px;
|
|
102
112
|
--radius-sm: 8px;
|
|
@@ -308,7 +318,7 @@
|
|
|
308
318
|
|
|
309
319
|
/* interactive / overlay
|
|
310
320
|
--hover: 컴포넌트 hover 시 표면·그림자에 얹는 공통 오버레이.
|
|
311
|
-
라이트=검정 3%, 다크=흰색
|
|
321
|
+
라이트=검정 3%, 다크=흰색 4%. 데이터 테이블·리스트 등 반복 행에서
|
|
312
322
|
"너무 두드러지지 않는" 톤. */
|
|
313
323
|
--hover: oklch(0 0 0 / 0.03);
|
|
314
324
|
/* tooltip: Mode 반전. 라이트에선 다크 bg + 밝은 fg, 다크에선 라이트 bg + 어두운 fg.
|
|
@@ -326,7 +336,7 @@
|
|
|
326
336
|
--shadow-color: 0 0 0;
|
|
327
337
|
|
|
328
338
|
--shadow-xs: 0 1px 2px oklch(var(--shadow-color) / 0.04);
|
|
329
|
-
--shadow-sm: 0
|
|
339
|
+
--shadow-sm: 0 2px 8px oklch(var(--shadow-color) / 0.03), 0 1px 2px oklch(var(--shadow-color) / 0.018);
|
|
330
340
|
--shadow-md: 0 6px 16px -4px oklch(var(--shadow-color) / 0.07),
|
|
331
341
|
0 2px 6px -2px oklch(var(--shadow-color) / 0.04);
|
|
332
342
|
--shadow-lg: 0 12px 20px -6px oklch(var(--shadow-color) / 0.1),
|
|
@@ -498,7 +508,7 @@
|
|
|
498
508
|
--radius-image-circle: var(--radius-2xl);
|
|
499
509
|
|
|
500
510
|
--shadow-xs: 0 1px 2px oklch(var(--shadow-color) / 0.04);
|
|
501
|
-
--shadow-sm: 0
|
|
511
|
+
--shadow-sm: 0 3px 10px -1px oklch(var(--shadow-color) / 0.032), 0 1px 3px oklch(var(--shadow-color) / 0.018);
|
|
502
512
|
--shadow-md: 0 8px 20px -4px oklch(var(--shadow-color) / 0.08), 0 3px 8px -3px oklch(var(--shadow-color) / 0.04);
|
|
503
513
|
--shadow-lg: 0 16px 32px -8px oklch(var(--shadow-color) / 0.14), 0 6px 12px -6px oklch(var(--shadow-color) / 0.06);
|
|
504
514
|
--shadow-xl: 0 32px 60px -14px oklch(var(--shadow-color) / 0.20);
|
|
@@ -515,7 +525,7 @@
|
|
|
515
525
|
--radius-3xl: 36px;
|
|
516
526
|
|
|
517
527
|
--shadow-xs: 0 1px 2px oklch(var(--shadow-color) / 0.04);
|
|
518
|
-
--shadow-sm: 0
|
|
528
|
+
--shadow-sm: 0 2px 8px oklch(var(--shadow-color) / 0.03), 0 1px 2px oklch(var(--shadow-color) / 0.018);
|
|
519
529
|
--shadow-md: 0 6px 16px -4px oklch(var(--shadow-color) / 0.07), 0 2px 6px -2px oklch(var(--shadow-color) / 0.04);
|
|
520
530
|
--shadow-lg: 0 12px 20px -6px oklch(var(--shadow-color) / 0.10), 0 4px 8px -4px oklch(var(--shadow-color) / 0.06);
|
|
521
531
|
--shadow-xl: 0 24px 40px -12px oklch(var(--shadow-color) / 0.16);
|
|
@@ -668,7 +678,7 @@
|
|
|
668
678
|
--radius-3xl: 22px;
|
|
669
679
|
|
|
670
680
|
--shadow-xs: 0 1px 0 oklch(var(--shadow-color) / 0.06);
|
|
671
|
-
--shadow-sm: 0 1px
|
|
681
|
+
--shadow-sm: 0 1px 3px oklch(var(--shadow-color) / 0.04), 0 0 0 1px oklch(var(--shadow-color) / 0.025);
|
|
672
682
|
--shadow-md: 0 2px 4px oklch(var(--shadow-color) / 0.10), 0 0 0 1px oklch(var(--shadow-color) / 0.06);
|
|
673
683
|
--shadow-lg: 0 4px 8px oklch(var(--shadow-color) / 0.12), 0 0 0 1px oklch(var(--shadow-color) / 0.06);
|
|
674
684
|
--shadow-xl: 0 8px 16px oklch(var(--shadow-color) / 0.16), 0 0 0 1px oklch(var(--shadow-color) / 0.06);
|
|
@@ -681,11 +691,14 @@
|
|
|
681
691
|
:root[data-mode="dark"],
|
|
682
692
|
[data-mode="dark"] {
|
|
683
693
|
--bg: oklch(0.20 var(--tone-chroma) var(--tone-hue));
|
|
684
|
-
--bg-subtle: oklch(0.
|
|
685
|
-
--bg-muted: oklch(0.
|
|
694
|
+
--bg-subtle: oklch(0.235 var(--tone-chroma) var(--tone-hue));
|
|
695
|
+
--bg-muted: oklch(0.27 var(--tone-chroma) var(--tone-hue));
|
|
686
696
|
--fg-strong: oklch(1.0 var(--tone-chroma) var(--tone-hue));
|
|
687
697
|
--fg: oklch(0.95 var(--tone-chroma) var(--tone-hue));
|
|
688
|
-
/* 다크: 밝은 잉크(≈0.95)를 낮은 알파로 얹음.
|
|
698
|
+
/* 다크: 밝은 잉크(≈0.95)를 낮은 알파로 얹음.
|
|
699
|
+
fg-muted 알파 0.60은 다크 bg(0.20) 위에서 effective L ≈ 0.65, 콘트라스트 ≈ 3.5:1.
|
|
700
|
+
WCAG AA "normal text" 4.5:1 임계 아래 — 보조 라벨·placeholder 전용.
|
|
701
|
+
중요한 보조 텍스트에는 --fg를 쓸 것. prefers-contrast: more에서 자동 상향됨. */
|
|
689
702
|
--fg-muted: oklch(0.95 var(--tone-chroma) var(--tone-hue) / 0.60);
|
|
690
703
|
--fg-subtle: oklch(0.95 var(--tone-chroma) var(--tone-hue) / 0.40);
|
|
691
704
|
--fg-disabled: oklch(0.95 var(--tone-chroma) var(--tone-hue) / 0.24);
|
|
@@ -695,7 +708,7 @@
|
|
|
695
708
|
|
|
696
709
|
/* primary / status는 :root의 정의를 그대로 사용 — --lightness가 아래에서 재정의되어 자동 반영됨 */
|
|
697
710
|
|
|
698
|
-
--hover: oklch(1 0 0 / 0.
|
|
711
|
+
--hover: oklch(1 0 0 / 0.04);
|
|
699
712
|
/* tooltip은 Mode 반전 — 다크 모드에선 밝은 bg + 어두운 fg. */
|
|
700
713
|
--tooltip-bg: oklch(0.95 var(--tone-chroma) var(--tone-hue));
|
|
701
714
|
--tooltip-fg: oklch(0.18 var(--tone-chroma) var(--tone-hue));
|
|
@@ -742,7 +755,7 @@
|
|
|
742
755
|
--tooltip-fg: oklch(1 0 0);
|
|
743
756
|
|
|
744
757
|
--shadow-xs: 0 1px 2px oklch(var(--shadow-color) / 0.04);
|
|
745
|
-
--shadow-sm: 0
|
|
758
|
+
--shadow-sm: 0 2px 8px oklch(var(--shadow-color) / 0.03), 0 1px 2px oklch(var(--shadow-color) / 0.018);
|
|
746
759
|
--shadow-md: 0 6px 16px -4px oklch(var(--shadow-color) / 0.07),
|
|
747
760
|
0 2px 6px -2px oklch(var(--shadow-color) / 0.04);
|
|
748
761
|
--shadow-lg: 0 12px 20px -6px oklch(var(--shadow-color) / 0.1),
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# CTDS 조립 가이드
|
|
2
|
+
|
|
3
|
+
**벤더에 없는 자리를 조립할 때** 쓰는 메타 알고리즘. 벤더 컴포넌트 문서(`components/*.md`)가 **"있는 컴포넌트를 어떻게 쓰는가"**를 다룬다면, 이 문서는 **"없는 컴포넌트를 어떻게 짜는가"**를 다룬다.
|
|
4
|
+
|
|
5
|
+
> 전제: [[PRINCIPLES]]·[[GUIDE]]를 이미 읽었다. 이 문서는 그 위의 조립 레이어.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0. 왜 이 문서가 필요한가
|
|
10
|
+
|
|
11
|
+
CTDS 벤더(`web/components/`)는 공통 자리(Button·Input·Modal·Badge 등)만 담는다. **페이지 헤더·섹션 헤더·배너·도메인 리스트 row** 같은 자리는 프로젝트마다 생김새가 다르다 — 벤더로 못 박기 어렵고, 유저·AI가 그때그때 조립할 수밖에 없다.
|
|
12
|
+
|
|
13
|
+
이 "조립" 자리가 CTDS의 **진짜 결과물**이다. 토큰은 토큰대로, 벤더는 벤더대로 지켜져도, **조립 알고리즘이 없으면 매 프로젝트마다 리듬·위계·여백이 흩어진다.**
|
|
14
|
+
|
|
15
|
+
이 문서는 그 흩어짐을 막는 **조립 순서**를 못 박는다.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. 조립 알고리즘 (5단계)
|
|
20
|
+
|
|
21
|
+
벤더에 없는 자리를 짤 때 **반드시 이 순서로** 생각한다. 건너뛰면 수제 하드코딩으로 샌다.
|
|
22
|
+
|
|
23
|
+
### 1단계 — "이게 정말 벤더에 없는가"를 먼저 의심한다
|
|
24
|
+
|
|
25
|
+
조립 전에 벤더를 재확인한다. [[PRINCIPLES]] §5a — 역할이 같은 벤더가 있으면 반드시 쓴다.
|
|
26
|
+
|
|
27
|
+
- `web/components/index.ts` 또는 `prompts/components/` 디렉토리 전수 스캔
|
|
28
|
+
- "비슷한 역할"이 아니라 **"같은 역할"**이면 벤더 사용. 생김새가 조금 다른 건 variant·size로 해결 가능한지 재검토
|
|
29
|
+
- 벤더 기존 variant로 안 되면 → **새 variant/size 신설**이 로컬 조립보다 우선 ([[PRINCIPLES]] §5a)
|
|
30
|
+
- 정말로 벤더에 없는 자리임을 확인한 뒤에만 2단계로
|
|
31
|
+
|
|
32
|
+
**[HARD] 리스트/반복 자리는 1단계에서 반드시 벤더로 확정** — "반복 아이템 N개"가 보이는 순간 Table/ListItem/EventListItem/MenuItem 중 하나를 **반드시** 쓴다. 레시피 조립은 리스트 자리를 커버하지 않는다. ([[PRINCIPLES]] §5a HARD, card-row 레시피 폐기 참고)
|
|
33
|
+
|
|
34
|
+
### 2단계 — "가장 가까운 벤더 anatomy"를 베낀다
|
|
35
|
+
|
|
36
|
+
새 자리의 뼈대를 **같은 성격의 벤더 컴포넌트에서 복제**한다. 새로 짜지 않는다.
|
|
37
|
+
|
|
38
|
+
- "액션+라벨 묶음"이면 Button/Toolbar의 anatomy
|
|
39
|
+
- "표시 라벨"이면 Badge/Chip의 anatomy
|
|
40
|
+
- "텍스트 블록"이면 Callout의 anatomy (단, [[PRINCIPLES]] §5b 준수)
|
|
41
|
+
- "아이콘+텍스트 row"이면 ListItem의 anatomy
|
|
42
|
+
|
|
43
|
+
anatomy = padding·gap·radius·border 전략. 이걸 복제하면 벤더가 축에 반응하는 방식이 자동으로 따라온다.
|
|
44
|
+
|
|
45
|
+
**벤더 사용 시 반드시 확인:**
|
|
46
|
+
- **Prop 이름·타입**은 `web/components/<Name>/<Name>.tsx`의 export type 선언에서 확인 (레시피 예시 그대로 믿지 말 것 — Badge는 `tone`이 아니라 `status`, Stepper는 `value`가 아니라 `activeIndex`)
|
|
47
|
+
- **Icon 이름**은 `web/components/Icon/Icon.tsx`의 phosphor 레지스트리에서 확인 — 레시피/다른 문서에 등장하는 이름이 실제 등재된 이름이라는 보장 없음
|
|
48
|
+
- **`"use client"` 전염**: 쓰는 벤더 중 하나라도 `"use client"`면 조립하는 **page 자체도 `"use client"`**. compound(`Stepper.Item`·`Card.Header` 등)를 Server Component에서 접근하면 **런타임 undefined**(Next 15/React 19 경계 이슈). 현재 Button/Icon/IconButton/Stepper 등 대다수 vendor가 client — 사실상 조립 page는 모두 client로 가는 쪽이 안전
|
|
49
|
+
|
|
50
|
+
### 3단계 — 레시피를 확인한다
|
|
51
|
+
|
|
52
|
+
`prompts/recipes/`에 **자리별 조립 레시피**가 있다. 맞는 레시피가 있으면 그 토큰·구조 그대로 쓴다.
|
|
53
|
+
|
|
54
|
+
- 레시피는 "이 자리에서 어떤 토큰 조합을 쓰는가"를 명시
|
|
55
|
+
- 레시피가 **없는 자리**면 4단계로. 레시피가 **있는데 맞지 않으면** 레시피를 수정하거나 신설(이 문서를 업데이트)
|
|
56
|
+
- 안티패턴 메모리(`antipattern_*`)에 해당하는 자리인지도 확인 — 긍정형 레시피가 없어도 "하지 마"는 있을 수 있다
|
|
57
|
+
|
|
58
|
+
### 4단계 — 토큰만 쓴다
|
|
59
|
+
|
|
60
|
+
조립 코드에 리터럴을 쓰지 않는다. [[PRINCIPLES]] §6.
|
|
61
|
+
|
|
62
|
+
- 색·크기·시간·그림자·폰트 — 전부 `var(--*)` 또는 vendor 컴포넌트 경유
|
|
63
|
+
- 간격은 [[tokens/spacing]]의 "앱 리듬 vs 랜딩 리듬"에서 먼저 리듬 선택 → 그 안에서 스케일
|
|
64
|
+
- `gap`과 `padding/margin`을 양쪽에서 동시에 잡지 않는다 (이중 여백 금지, [[tokens/spacing]] Don't)
|
|
65
|
+
- 20px 미만 `semibold`/`bold` 금지, 섹션 h2/h3 semibold 금지 ([[tokens/typography]])
|
|
66
|
+
|
|
67
|
+
### 5단계 — 조립 검증
|
|
68
|
+
|
|
69
|
+
조립이 끝나면 자가 검증. 항목:
|
|
70
|
+
|
|
71
|
+
**값·토큰**
|
|
72
|
+
- [ ] 토큰·vendor 외에 **리터럴 값**이 있는가 → 없어야 함
|
|
73
|
+
- [ ] `width/height/max-width`에 **1px/0.75em** 같은 리터럴 있는가 → 금지. hairline은 `border-left: 1px solid var(--border)` + `align-self: stretch`로
|
|
74
|
+
- [ ] `border: 1px solid transparent` 가드 패턴 있는가 → 금지 (check_bg_stroke_combo hook). border 안 쓸 땐 선언 자체를 생략
|
|
75
|
+
- [ ] 날짜·시간이 숫자 전용("10. 13", "2026-10-13")인가 → "10월 13일 / 12분 전" 또는 Tooltip에 ISO ([[feedback-date-writing]])
|
|
76
|
+
- [ ] **font-weight 토큰 이름이 정확한가** → `var(--font-weight-medium)` ✓ 아니라 `var(--font-medium)` ✗ 유령 토큰. 조용히 400으로 상속되어 **"얇음" 증상의 숨은 원인** ([[PRINCIPLES]] §6a)
|
|
77
|
+
|
|
78
|
+
**레이아웃·그룹**
|
|
79
|
+
- [ ] 간격을 `margin-top` 또는 `A + B` 선택자로 만들고 있는가 → **그룹 설계가 틀렸음**. wrapper를 추가해 flex column + gap으로 풀어라 (§2a)
|
|
80
|
+
- [ ] 섹션 header에 `margin-bottom`이 있는가 → section 자체를 flex column + gap으로. header는 margin 없음
|
|
81
|
+
- [ ] 섹션 상단에 큰 padding으로 "위 공간" 확보 중인가 → 섹션 사이 분리는 부모 그룹의 gap으로 (§2a)
|
|
82
|
+
- [ ] 조립 코드에 `<section>`/`<article>`/`<nav>` semantic 태그를 썼는가 → **금지**. AppShell의 `.content section`이 `padding-top: 80px` 꽂아 "섹션 머리에 거대한 여백" 함정. `<div>`로 짠다 ([[feedback-no-semantic-tags-in-components]])
|
|
83
|
+
|
|
84
|
+
**리스트·반복 자리 (HARD)**
|
|
85
|
+
- [ ] 반복 아이템 N개를 `<ul>`/`<li>` + 자체 className으로 짰는가 → **즉시 금지** ([[PRINCIPLES]] §5a HARD). 아래 중 하나로 교체:
|
|
86
|
+
- 표 역할 → [[components/table-row]]
|
|
87
|
+
- 단건 반복(알림·활동·작업·자원) → [[components/list-item]]
|
|
88
|
+
- 날짜 중심 이벤트 → [[components/event-list-item]]
|
|
89
|
+
- 메뉴 아이템 → [[components/menu-item]] / [[components/sidebar-item]]
|
|
90
|
+
- [ ] "카드처럼 보이는 리스트"가 필요하다는 이유로 수제 조립 중인가 → ListItem은 **chrome 없음이 공식**. border/bg 두르는 리스트는 (1) 블록 하나면 Callout, (2) 리스트 전체라면 **ListItem에 새 variant 신설**. 수제 조립 금지
|
|
91
|
+
|
|
92
|
+
**리듬·여백**
|
|
93
|
+
- [ ] 부모 gap + 자식 padding 이중 여백이 있는가 → 한쪽만
|
|
94
|
+
- [ ] 콘솔 리듬에서 **섹션 간 gap이 space-6 이상**인가 → `space-4` 고려. 섹션이 border/배경으로 분리돼 있으면 space-6는 "구조 끊어진" 느낌 ([[tokens/spacing]])
|
|
95
|
+
- [ ] 작은 컴포 묶음(버튼↔버튼) gap이 `space-1`인가 → 고정
|
|
96
|
+
- [ ] 아이콘·텍스트가 hairline에 padding 없이 붙었는가 → padding 확보
|
|
97
|
+
- [ ] **멀티라인 블록**(banner·card)에서 padding이 상하 `space-3` 이상인가 → single-line과 같은 padding 쓰면 타이트
|
|
98
|
+
- [ ] **title + sub** 2줄 묶음의 내부 gap이 `space-1` 이상인가 → `space-0.5`는 녹아보임
|
|
99
|
+
|
|
100
|
+
**정렬**
|
|
101
|
+
- [ ] 아이콘 + 여러 줄 텍스트 조합에서 `align-items: center`인가 → **첫 줄과 어긋남**. `flex-start` + 아이콘 `margin-top: space-0.5`로 보정
|
|
102
|
+
- [ ] "라벨 하나만" 블록 위에 뜨거나 혼자 떠서 여백이 깨지는가 → 라벨은 header 자리에 묶거나, 블록 밖으로 승격
|
|
103
|
+
|
|
104
|
+
**색·면**
|
|
105
|
+
- [ ] 배경 + 테두리 동시 적용이 있는가 → 둘 중 하나만 ([[PRINCIPLES]] §5b)
|
|
106
|
+
- [ ] 평상시 border만, hover에서 bg 추가 패턴 쓰는가 → OK (`cardRow` 전략 A). 역으로 평상시 bg + hover border 금지
|
|
107
|
+
|
|
108
|
+
**타이포·숫자**
|
|
109
|
+
- [ ] 숫자 셀을 우측정렬했는가 → 좌측정렬 유지
|
|
110
|
+
- [ ] 가운뎃점(·) 구분자가 있는가 → hairline 세로선으로 교체
|
|
111
|
+
- [ ] 20px 미만 글자에 `semibold` 썼는가 → medium 이하 ([[tokens/typography]])
|
|
112
|
+
- [ ] **섹션 타이틀(h2)이 text-xl 이상**인가 → 콘솔 리듬에서는 `text-lg` 기본. medium 가중치만으론 크면 "크고 얇은" 느낌으로 뜸
|
|
113
|
+
|
|
114
|
+
**아이콘**
|
|
115
|
+
- [ ] 아이콘을 인라인 `<svg>`로 쐈는가 → Icon 컴포넌트 경유
|
|
116
|
+
|
|
117
|
+
이 체크리스트는 메모리에 쌓인 피드백의 **긍정형 자동화** — 조립 시점에 매번 돌린다. 특히 1~3번은 **hook이 자동 차단**하므로 미리 검증.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 2. "조립 자리"로 흔히 등장하는 패턴
|
|
122
|
+
|
|
123
|
+
벤더에 없는 자리 중 반복적으로 등장하는 것들. **각 패턴은 `recipes/` 아래에 레시피가 있다** (없으면 신설).
|
|
124
|
+
|
|
125
|
+
- **페이지 헤더** — 큰 타이틀 + 서브텍스트 + (옵션) 우측 액션 → [[recipes/page-header]]
|
|
126
|
+
- **섹션 헤더** — 중간 타이틀 + (옵션) 설명 1줄 + (옵션) 우측 액션/카운트 → [[recipes/section-header]]
|
|
127
|
+
- **메타 라인** — 라벨 여러 개가 가로로 나열, 사이 구분자 필요 → [[recipes/meta-line]]
|
|
128
|
+
- **카드 리스트 row** → ~~레시피 아님~~. [[components/list-item]] 벤더 ([[PRINCIPLES]] §5a HARD)
|
|
129
|
+
- **폼 푸터** — Spacer + outline(취소) + primary(제출) → [[recipes/form-footer]]
|
|
130
|
+
- **페이지 상단 안내 배너** — 톤 있는 가로 바, 좌측 아이콘+텍스트 + 우측 액션 → [[recipes/page-banner]]
|
|
131
|
+
- **흐름 속 참고 각주** — 아이콘 + 1~2줄 muted 텍스트, 톤·액션 없음 → vendor [[components/hint]] (레시피 아님. "아이콘-텍스트 라인 맞추기" 함정 반복 때문에 벤더로 확정)
|
|
132
|
+
|
|
133
|
+
이 목록 자체가 "지금 CTDS가 자주 조립해야 하는 자리"의 정식 지도다. 새 패턴이 세 번 반복되면 이 목록에 추가하고 레시피를 쓴다.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 2a. 레이아웃 1원칙 — "관련있는 것끼리 묶는다"
|
|
138
|
+
|
|
139
|
+
**레이아웃 설계의 가장 중요한 결정은 "그룹"이다.** 어떤 요소가 어떤 요소와 한 묶음인지 먼저 정하고, 그 다음 각 그룹을 flex column으로 만들고, gap으로 간격을 준다. padding/margin으로 간격을 벌리는 접근은 **틀렸다**.
|
|
140
|
+
|
|
141
|
+
**원칙:**
|
|
142
|
+
1. 역할이 가까운 요소들을 **같은 flex 컨테이너**로 묶는다 (한 그룹 = 한 wrapper)
|
|
143
|
+
2. 그룹 안은 **좁은 gap** (`space-2` ~ `space-3`), 그룹끼리는 **넓은 gap** (`space-6` ~ `space-10`)
|
|
144
|
+
3. **모든 간격은 flex gap**. `margin-top`/`margin-bottom` 트릭 금지. `+` 선택자로 "다음 요소에만 추가 공간" 트릭도 금지 — gap으로 못 풀면 **그룹을 다시 짜라**
|
|
145
|
+
4. 섹션 header의 `margin-bottom` 금지. 섹션 자체를 flex column + gap으로 만들어 header와 body를 자연스럽게 묶는다
|
|
146
|
+
|
|
147
|
+
**전형 (3-tier 리듬):**
|
|
148
|
+
```
|
|
149
|
+
page (gap: space-10) ← 그룹 사이 넓게
|
|
150
|
+
├─ group A: 소개 (gap: space-3) ← 같은 그룹은 좁게
|
|
151
|
+
│ ├─ banner
|
|
152
|
+
│ ├─ page-header
|
|
153
|
+
│ └─ inline hint
|
|
154
|
+
├─ group B: Stepper (gap: space-2)
|
|
155
|
+
│ ├─ eyebrow
|
|
156
|
+
│ └─ Stepper
|
|
157
|
+
└─ group C: 섹션들 (gap: space-6) ← 중간 호흡
|
|
158
|
+
├─ section (gap: space-2) ← 섹션 안은 좁게
|
|
159
|
+
│ ├─ section-header
|
|
160
|
+
│ └─ card-list (gap: space-2)
|
|
161
|
+
└─ section (gap: space-2)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**금지:**
|
|
165
|
+
- [HARD] `.page { gap: X }` 하나로 모든 자식 블록 간격 담당 — 역할 다른 블록이 "통짜"로 뜸
|
|
166
|
+
- [HARD] `A + B { margin-top: ... }`로 특정 자리만 벌리기 — 그룹 설계가 틀렸다는 신호. wrapper를 추가해서 gap으로 풀어라
|
|
167
|
+
- [HARD] 섹션 header `margin-bottom` — section을 flex column + gap으로. 섹션 자체가 header↔body를 묶음
|
|
168
|
+
- [HARD] 섹션 head 쪽에 큰 padding으로 "위 공간 확보" — padding은 "안쪽" 공간, 섹션 사이 분리는 부모 그룹의 gap으로
|
|
169
|
+
|
|
170
|
+
**갭 선택 — 핵심 원칙: "가까운 관계일수록 더 좁게"**
|
|
171
|
+
|
|
172
|
+
갭의 크기는 **관계의 가까움에 반비례**한다. 두 요소가 "가장 가까운 관계"면 가장 좁은 gap, "성격이 다른 묶음"이면 가장 넓은 gap. **인접한 두 레벨은 반드시 한 단계 이상 차이**가 나야 위계가 생긴다 — 같으면 뭉쳐 보임.
|
|
173
|
+
|
|
174
|
+
**관계 → 갭 매핑 (앱/콘솔 리듬, 실측 기준):**
|
|
175
|
+
|
|
176
|
+
| 관계 | 갭 | 예시 |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| **리스트 안 반복 아이템** (가장 가까움) | `space-1` | cardList의 cardRow들 |
|
|
179
|
+
| **그룹 안 미니 묶음** | `space-2` | 타이틀+설명, eyebrow+컨트롤, Stepper eyebrow+Stepper |
|
|
180
|
+
| **같은 그룹 안 header ↔ body** | `space-3` | section header ↔ cardList, pageHeader ↔ inlineHint |
|
|
181
|
+
| **같은 그룹 안 성격 다른 요소** | `space-6` | 메타 banner ↔ 본문 headerBlock |
|
|
182
|
+
| **섹션 ↔ 섹션 (반복 작업 단위)** | `space-20` (80px) | "작업 계획" ↔ "코드 변경안 실행 요청" |
|
|
183
|
+
| **그룹 ↔ 그룹 (성격 다른 묶음)** | `space-20` (80px) | introGroup ↔ stepperGroup ↔ sectionsGroup |
|
|
184
|
+
|
|
185
|
+
**철칙: 인접 레벨 간 반드시 한 단계 이상 차이**
|
|
186
|
+
- 예: 리스트 아이템 gap `space-1` → 섹션 header↔body gap `space-3` (한 단계 뛰어넘음)
|
|
187
|
+
- 예: 섹션 header↔body `space-3` → 섹션 ↔ 섹션 `space-20` (큰 점프)
|
|
188
|
+
- **같은 값 금지**: 아이템 간 `space-2` + 섹션 header↔body `space-2` → "타이틀과 아이템이 같은 레벨"로 뭉쳐 보임
|
|
189
|
+
|
|
190
|
+
> ⚠ 처음엔 "섹션 반복은 space-6, 그룹끼리는 space-10"으로 잡았지만 실측에서 둘 다 **좁았음**. 콘솔 리듬이라도 **"독립된 작업 단위"끼리는 space-20 수준의 호흡**이 자연스러움. "space-6은 섹션 내부 블록 단위"로만 쓸 것.
|
|
191
|
+
|
|
192
|
+
## 2b. 카드로 감싸는 자리 — "균일 padding 지킬 수 있을 때만"
|
|
193
|
+
|
|
194
|
+
조립 자리에 **카드(border + radius + bg)**를 두르면 안쪽은 **상하좌우 균일 padding**이 공식 ([[tokens/spacing]] "카드·패널 내부 padding"). 지키지 못하면 카드 자체를 포기하는 게 낫다.
|
|
195
|
+
|
|
196
|
+
**카드 포기 시그널:**
|
|
197
|
+
- 카드 안에 **라벨/eyebrow가 혼자** 떠서 균일 padding을 깬다 → 라벨을 밖(PageHeader eyebrow)으로 승격 또는 생략, 카드 폐기
|
|
198
|
+
- 카드 안 요소들이 **각자 다른 수평 정렬**(가운데 정렬 Stepper + 오른쪽 정렬 라벨) → 균일 padding으론 못 담음. 풀어 놓거나 요소 하나만 남김
|
|
199
|
+
- 카드가 "그냥 공간 묶음용"일 뿐 **분리 신호가 없다** → border·bg 자체가 노이즈. 페이지 흐름에 그대로 두고 gap만
|
|
200
|
+
|
|
201
|
+
## 3. 조립 시 자주 틀리는 자리
|
|
202
|
+
|
|
203
|
+
조립할 때 사람·AI 모두 자주 미끄러지는 자리. 안티패턴 메모리와 쌍을 이룬다.
|
|
204
|
+
|
|
205
|
+
- **"페이지 맨 위에 Tabs만"** — Tabs 위에 PageHeader(h1/breadcrumb)가 먼저 와야 한다 ([antipattern-tabs-without-title])
|
|
206
|
+
- **"워크스페이스 스위처를 수제 Button+Dropdown으로 조립"** — 전용 IdentityButton + DropdownMenu 사용 ([antipattern-workspace-switcher])
|
|
207
|
+
- **"Topbar leading에만 요소 몰림"** — leading/center/trailing 3구역 균형. leading·trailing 짝 필수 ([[components/topbar]])
|
|
208
|
+
- **"Sidebar 2단 쉘을 SidebarSplit으로"** — 바깥 flex로 Rail+Sidebar 직접 조립 ([feedback-sidebar-scroll])
|
|
209
|
+
- **"안내 영역을 outline Callout으로"** — 색 테두리 + 색 배경 조합은 영구 금지 ([feedback-no-outlined-callout]). muted 인라인 텍스트 한 줄로 대체
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 관련
|
|
214
|
+
|
|
215
|
+
- [[PRINCIPLES]] — 전역 원칙. 특히 §1(값/판단 분리), §5a(컴포넌트 우선), §5b(배경 또는 테두리), §6(하드코딩 금지)
|
|
216
|
+
- [[GUIDE]] — 섹션 문서 작성 가이드. ASSEMBLY는 그 위의 **조립 레이어**
|
|
217
|
+
- [[components/_variants]] — 역할 × variant 매핑. 2단계(가장 가까운 벤더 anatomy)에서 참조
|
|
218
|
+
- [[tokens/spacing]] — 앱 리듬 vs 랜딩 리듬. 4단계(토큰) 참조
|
|
219
|
+
- `prompts/recipes/` — 자리별 조립 레시피 모음. 3단계 참조
|
|
@@ -93,10 +93,51 @@ CTDS는 9개 축(Mode, Palette, Hue, Shape, Icon, Density, Motion, Font, Voice)
|
|
|
93
93
|
- 컴포넌트의 variant/size/prop이 요구를 못 맞춘다고 생각되면: (1) 먼저 **기존 variant·size 안에서 맞출 수 있는지** 재검토, (2) 그래도 안 되면 **기존 사용처 영향을 주지 않는 선에서 새 variant/size 추가**(예: `size="xs"` 신설), (3) 로컬 하드코딩은 **마지막 수단**이고 그 자리에 반드시 "왜 로컬인지" 주석.
|
|
94
94
|
- 기존 size/variant의 수치·간격을 "이번 자리에서만 조금 다르게" 바꾸는 로컬 override 금지 — 다른 사용처가 다 영향 받는다. 수정이 필요하면 새 variant·size로.
|
|
95
95
|
|
|
96
|
+
**[HARD] 리스트 자리는 무조건 벤더 — 수제 조립 영구 금지:**
|
|
97
|
+
반복되는 아이템 N개를 수직/수평 나열하는 **모든 리스트 자리**는 다음 중 하나를 **반드시** 사용. `<ul>`/`<li>` + 자체 className으로 짜는 "수제 리스트" 전면 금지.
|
|
98
|
+
- **표 역할 (열 공통 축이 있는 격자)** → [[components/table-row]] + [[components/table-header]]
|
|
99
|
+
- **단건 반복 (알림·활동·작업·자원 목록 등)** → [[components/list-item]]
|
|
100
|
+
- **일정·이벤트 (날짜 중심)** → [[components/event-list-item]]
|
|
101
|
+
- **메뉴 아이템 (팝오버·사이드바 등)** → [[components/menu-item]] / [[components/sidebar-item]]
|
|
102
|
+
|
|
103
|
+
"카드처럼 보이게 하고 싶다"는 생김새 요구이지 자리 변경이 아니다 — ListItem은 chrome 없음 설계가 공식(블록 신호가 필요하면 Callout으로 승격). border/bg 두른 "카드 리스트"가 꼭 필요하면 (1) 레시피/수제 조립 금지, (2) ListItem 벤더에 `surface="card"` 등 **새 variant 신설 후 사용**. 수제 `<ul className="cardList"><li><a>...</a></li></ul>` 패턴은 즉시 리뷰 반려.
|
|
104
|
+
|
|
96
105
|
관련: §6(하드코딩 금지)은 "값"의 하드코딩, 이 조항은 "컴포넌트 역할"의 하드코딩.
|
|
97
106
|
|
|
98
107
|
---
|
|
99
108
|
|
|
109
|
+
## 5b. 배경 또는 테두리 — 둘 다는 금지 [HARD]
|
|
110
|
+
|
|
111
|
+
한 요소에 "배경색"과 "테두리"를 **동시에** 쓰지 않는다. 특히 **"안 연한 배경(= `--bg`보다 눈에 띄게 진한 톤) + 진한 스트로크"** 조합은 영구 금지.
|
|
112
|
+
|
|
113
|
+
**왜 중요한가:**
|
|
114
|
+
- "연한 배경 + 진한 스트로크"는 UI를 싸구려·시끄럽게 만든다 — CTDS의 밝은 중성 톤·hairline border 전제와 정면충돌
|
|
115
|
+
- Callout/Badge/Chip/Pill/Banner/Card 류에서 반복되는 전형적 함정 — 디자인이 "안전해 보이려고" 둘 다 집어넣는 자리
|
|
116
|
+
|
|
117
|
+
**OK:**
|
|
118
|
+
- hairline border만, 배경은 `transparent` 또는 `var(--bg)`
|
|
119
|
+
- 살짝 톤 다른 배경만(`--bg-subtle` 등), 테두리 없음
|
|
120
|
+
- `--primary-*` 같은 solid-fill variant도 배경만 — border는 `transparent` 처리 ([[feedback-solid-fill-no-matching-border]] 참조)
|
|
121
|
+
|
|
122
|
+
**NG:**
|
|
123
|
+
- `background: var(--bg-subtle|muted|...)` + `border: 1px solid var(--border|strong|...)` 공존
|
|
124
|
+
- 색 테두리 + 색 배경 조합의 outlined Callout류
|
|
125
|
+
- 평소엔 테두리 없다가 hover/active에서만 "급작스런 스트로크" 생기는 패턴
|
|
126
|
+
|
|
127
|
+
**적용 범위:**
|
|
128
|
+
- 신규 CSS 규칙 블록·TSX 인라인 style 둘 다
|
|
129
|
+
- 벤더 코드(`web/components/**`), 설치 템플릿(`packages/ctds-cli/assets/**`), 사용처 페이지 모두 동일 — "컴포넌트 안이라 괜찮다" 예외 없음
|
|
130
|
+
- hover/active 상태에서도 동일 — 상태 전환으로 테두리가 새로 생기는 것도 금지
|
|
131
|
+
|
|
132
|
+
**영역 분리가 필요하면:**
|
|
133
|
+
- 선으로 구분해야 할 자리 → 테두리만, 배경은 투명
|
|
134
|
+
- 톤 차이로 구분해야 할 자리 → 배경만, 테두리 없음
|
|
135
|
+
- 둘 다 필요해 보이면 레이아웃이 잘못됐을 가능성 → gap·padding으로 먼저 풀어본다
|
|
136
|
+
|
|
137
|
+
**자동화:** `.claude/hooks/check_bg_stroke_combo.py`가 Write/Edit 시점에 차단한다.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
100
141
|
## 6. 하드코딩 금지
|
|
101
142
|
|
|
102
143
|
컴포넌트/템플릿 코드에서 다음은 하드코딩 금지 (프롬프트에서 이런 코드를 생성하지 않는다):
|
|
@@ -155,3 +196,32 @@ CTDS는 9개 축(Mode, Palette, Hue, Shape, Icon, Density, Motion, Font, Voice)
|
|
|
155
196
|
- 프롬프트 문서는 한국어로 쓴다 (팀 언어).
|
|
156
197
|
- 코드 식별자·토큰명·축명은 영어 (`space-6`, `data-density="compact"`).
|
|
157
198
|
- 컴포넌트가 화면에 표시하는 문구는 프로젝트별 `data-lang`을 따른다 — CTDS 프롬프트에는 화면 문구를 하드코딩하지 않는다.
|
|
199
|
+
|
|
200
|
+
## 10. 날짜·시간 표기 [HARD]
|
|
201
|
+
|
|
202
|
+
사람이 읽는 UI 자리(ListItem.Meta, Table 셀, 알림 등)에서 숫자 패턴(`10. 13`, `2026-10-13`, `10/13`)은 금지. 기계 값은 `datetime` 속성이 담당하고, 사람 눈에 보이는 라벨은 아래 셋 중 하나.
|
|
203
|
+
|
|
204
|
+
### A. 사람 표기 (default)
|
|
205
|
+
- 한국어: **"10월 13일"**, 올해면 연도 생략. 연도가 필요하면 "2026년 10월 13일".
|
|
206
|
+
- 영어: **"Oct 13"**, 올해면 연도 생략. "Oct 13, 2026".
|
|
207
|
+
- 월명은 축약(Oct) 또는 전체(October), 리스트 안이면 축약을 쓴다.
|
|
208
|
+
|
|
209
|
+
### B. 상대 비교급 (최근 시간)
|
|
210
|
+
- 최근 48시간은 숫자 날짜 대신 **상대어**: "방금", "12분 전", "3시간 전", "어제", "내일", "오늘 오후 3시".
|
|
211
|
+
- 영어: "just now", "12 min ago", "3 h ago", "yesterday", "tomorrow", "today at 3 pm".
|
|
212
|
+
- 기준점은 "지금"이지 "오늘 00시"가 아니다 — 23시간 전은 "어제"가 아니라 "23시간 전".
|
|
213
|
+
|
|
214
|
+
### C. 기계 값 (Tooltip·`datetime`·스크린리더용)
|
|
215
|
+
- ISO 8601: `datetime="2026-10-13T22:25:00+09:00"`.
|
|
216
|
+
- 사용자에게 직접 보이는 자리 금지. `<time>` 태그의 속성이나 Tooltip 안으로.
|
|
217
|
+
|
|
218
|
+
### 선택 규칙
|
|
219
|
+
- 48시간 안 → **상대어 (B)**
|
|
220
|
+
- 48시간 밖 ~ 올해 안 → **"10월 13일" / "Oct 13" (A, 연도 생략)**
|
|
221
|
+
- 작년 이전 → **"2024년 10월 13일" / "Oct 13, 2024" (A, 연도 포함)**
|
|
222
|
+
- 정확한 시각이 필요하면 Tooltip에 ISO (C)
|
|
223
|
+
|
|
224
|
+
### 금지
|
|
225
|
+
- 숫자 전용 패턴: `10. 13`, `2026-10-13`, `10/13`, `10.13.`
|
|
226
|
+
- 혼합: "2026. 10. 13 오전 06:00" (한국어 서식 + 숫자 형태) — "2026년 10월 13일 오전 6시"로.
|
|
227
|
+
- 한 리스트 안에서 A/B 섞기 — 리스트 전체는 하나의 전략 (스트림 피드면 B, 정적 리스트면 A).
|
|
@@ -39,10 +39,16 @@ Family는 `phosphor`, Weight는 `regular`.
|
|
|
39
39
|
### 기본값
|
|
40
40
|
`family=phosphor`, `weight=regular`
|
|
41
41
|
|
|
42
|
+
### 축 간 상호작용
|
|
43
|
+
|
|
44
|
+
- **[[axes/palette]] = `mono`**: `--icon-color`가 자동으로 `--primary` 대신 `--fg-muted`로 override. 아이콘은 어떤 family든 중성 fg 톤. Family 선택과 무관하게 "mono 모드"의 색 규칙이 상속됨.
|
|
45
|
+
- **[[axes/palette]] 비mono**: `--icon-color = --primary`. 아이콘은 브랜드 액센트 톤.
|
|
46
|
+
- **[[axes/density]]**: `--icon-size-xs/sm/md/lg`가 `--space-scale`에 반응. 밀도 축을 바꾸면 아이콘 크기도 비례 스케일.
|
|
47
|
+
|
|
42
48
|
### 페어링
|
|
43
49
|
- `phosphor`·`solar` — regular/fill 모두 매칭. 활성 상태 표시 필요한 프로젝트 표준
|
|
44
50
|
- `lucide` — regular만 지원. 활성 상태를 색·배경으로만 표현하는 프로젝트에 적합
|
|
45
51
|
- 특정 [[axes/shape]] 값과의 강한 페어링은 없음. Family 선택은 브랜드 톤 기준
|
|
46
52
|
|
|
47
53
|
### 관련
|
|
48
|
-
|
|
54
|
+
- [[axes/palette]] — mono 선택 시 icon-color 자동 중성화
|
|
@@ -8,15 +8,18 @@ OKLCH의 chroma·lightness를 스왑.
|
|
|
8
8
|
- 프로젝트의 톤을 정하는 첫 축. Hue보다 Palette를 먼저 결정한다.
|
|
9
9
|
- 정보 밀도가 높은 화면은 `calm` 또는 `standard`.
|
|
10
10
|
- 마케팅·랜딩·온보딩은 `vivid` 또는 `pastel`.
|
|
11
|
+
- 완전 중성(브랜드 컬러 없는 흑백 톤)이 필요하면 `mono`.
|
|
11
12
|
|
|
12
13
|
### Don't
|
|
13
14
|
- Palette를 컴포넌트별로 다르게 쓰지 않는다. 프로젝트 전체가 한 Palette.
|
|
14
15
|
- `pastel` 컬러 위에 흰 텍스트를 얹지 않는다 (대비 부족).
|
|
16
|
+
- `mono`에서 "브랜드 색이 왜 안 보여?"는 설계 의도 — 상태색(status)만 유색 유지, 그 외 아이콘·포커스·nav-active·secondary는 전부 중성 fg 톤.
|
|
15
17
|
|
|
16
18
|
### Fallback
|
|
17
19
|
`standard`
|
|
18
20
|
|
|
19
21
|
### 값
|
|
22
|
+
- `mono` — chroma 0. 브랜드 컬러를 쓰지 않는 완전 중성 톤. 흑백·그레이 레벨만. 상태색(status)만 유색 유지(사용자 안전 신호). 에디터 UI·커맨드 중심 도구에 적합.
|
|
20
23
|
- `vivid` — 채도 높음. 강한 인상, 브랜드 강조.
|
|
21
24
|
- `standard` — 기본. 일반 웹 UI.
|
|
22
25
|
- `calm` — 채도 낮음. 차분, 신뢰. 금융·의료·공공.
|
|
@@ -25,11 +28,19 @@ OKLCH의 chroma·lightness를 스왑.
|
|
|
25
28
|
### 기본값
|
|
26
29
|
`standard`
|
|
27
30
|
|
|
31
|
+
### 축 간 상호작용
|
|
32
|
+
|
|
33
|
+
- **`mono` + [[axes/icon]]**: mono palette일 땐 `--icon-color`가 `--primary` 대신 `--fg-muted`로 override. [[components/icon]]은 자동 반응.
|
|
34
|
+
- **`mono` + 선택 상태**: Checkbox·Radio·Tab 등의 활성 색도 primary 아닌 fg 톤. 상태 자체는 다른 신호(배경·테두리·weight)로 전달됨.
|
|
35
|
+
- **`mono` + status**: warning/danger/success/info는 유색 유지. mono라도 안전 신호는 색으로 봐야 함.
|
|
36
|
+
|
|
28
37
|
### 페어링
|
|
29
38
|
- `calm` + 낮은 chroma hue(220~260°) — 신뢰·안정 인상 (금융·의료·공공)
|
|
30
39
|
- `vivid` + 따뜻한 hue(0~40°) — 강한 브랜드 인상 (스타트업·프로모션)
|
|
31
40
|
- `pastel` + 중간 hue — 부드러움 (교육·라이프스타일)
|
|
32
41
|
- `pastel` + [[axes/mode]] `dark` — 비권장. 파스텔은 밝은 배경에서 최적화됨
|
|
42
|
+
- `mono` + [[axes/hue]] — hue 값은 저장되지만 chroma가 0이라 실제로 안 보임. mono에선 hue 변경 무의미.
|
|
33
43
|
|
|
34
44
|
### 관련
|
|
35
45
|
- [[axes/hue]] — Palette와 조합해 최종 브랜드 컬러 생성
|
|
46
|
+
- [[axes/icon]] — mono에선 아이콘 색이 primary 아닌 fg-muted
|
|
@@ -42,12 +42,16 @@
|
|
|
42
42
|
| [Badge](badge.md) | `solid` | `soft` | — | — | `inverse` |
|
|
43
43
|
| [Chip](chip.md) *(필터 선택값 전용)* | — | `soft` | `outline` | — | — |
|
|
44
44
|
| [Callout](callout.md) *(알림 블록)* | — | — | `outline` | — | `fill` |
|
|
45
|
+
| [Hint](hint.md) *(참고 각주)* | — | — | — | — | — *(variant 축 없음, muted 평문 하나)* |
|
|
45
46
|
| [MetricCard](metric-card.md) | — | `soft` | — | `line` | `bare` |
|
|
46
47
|
|
|
47
48
|
**선택 규칙 (표시)**
|
|
48
49
|
- 상태·카테고리 라벨=`Badge solid`(기본) 또는 `soft`(순한 톤). 선택된 필터 값=`Chip`.
|
|
49
50
|
- Chip은 "선택된 값(X로 제거 가능)" 전용. 카테고리·태그·메타 라벨은 **반드시 Badge** (관련: feedback 메모리 [Chip filter-only](../../memory/feedback_chip_filter_only.md)).
|
|
50
|
-
-
|
|
51
|
+
- **안내 자리 3층 구분** — 성격에 따라 분리:
|
|
52
|
+
- **흐름 속 각주 (1~2줄, 톤 없음)** = `Hint`
|
|
53
|
+
- **액션 유도 있는 안내 블록 (사이드바·리스트 끝)** = `Callout fill` *(색 테두리+색 배경 금지 — 메모리 [No outlined Callout](../../memory/feedback_no_outlined_callout.md))*
|
|
54
|
+
- **페이지 폭 상태 바 (공유 모드·만료·권한)** = [[recipes/page-banner]]
|
|
51
55
|
|
|
52
56
|
### 입력 컴포넌트 — 매핑
|
|
53
57
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Callout
|
|
2
2
|
|
|
3
3
|
### 원칙
|
|
4
|
-
사이드바·리스트 끝·설정 패널의 여백에 상주하는 소형 안내 카드. 페이지 흐름을 끊지 않고 새 기능·이동 경로를 제시한다. hover가 없는 [[components/tooltip]], 휘발되는 Toast, 페이지 폭 전체를 덮는 [[
|
|
4
|
+
사이드바·리스트 끝·설정 패널의 여백에 상주하는 소형 안내 카드. 페이지 흐름을 끊지 않고 새 기능·이동 경로를 제시한다. hover가 없는 [[components/tooltip]], 휘발되는 Toast, 페이지 폭 전체를 덮는 [[recipes/page-banner]]와 구분된다.
|
|
5
5
|
|
|
6
6
|
### Do
|
|
7
7
|
- 표면 강도는 `variant`, 강조 색은 `tone`으로 독립 제어한다.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- `warning`/`destructive` tone은 `icon` 미지정 시 각각 `warning-circle`(주의) / `warning`(위험)이 자동. 다른 의미 아이콘이 필요할 때만 override한다.
|
|
13
13
|
|
|
14
14
|
### Don't
|
|
15
|
-
- 페이지 폭 전역 경고에 쓰지 않는다 — [[
|
|
15
|
+
- 페이지 폭 전역 경고에 쓰지 않는다 — [[recipes/page-banner]] 영역.
|
|
16
16
|
- 임시 피드백에 쓰지 않는다 — Toast를 쓴다.
|
|
17
17
|
- 본문을 4줄 넘게 채우지 않는다.
|
|
18
18
|
- `tone`을 임의 색상으로 확장하지 않는다.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
`variant` 애매하면 `fill`. `tone` 애매하면 `brand`. `actionsPlacement` 애매하면 `bottom`.
|
|
22
22
|
|
|
23
23
|
### 관련
|
|
24
|
-
- [[
|
|
24
|
+
- [[recipes/page-banner]] — 페이지 폭 전면 경고. Callout은 국소 카드.
|
|
25
25
|
- [[components/button]] — actions 슬롯의 실제 요소이며 variant 규칙 원본.
|
|
26
26
|
- [[components/badge]] — title 옆 상태 라벨.
|
|
27
27
|
- [[axes/hue]] · [[axes/palette]] — tone별 강조 색을 스왑.
|