@conscience-technology/ctds 0.3.3 → 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.
Files changed (42) hide show
  1. package/assets/components/Button/Button.module.css +2 -2
  2. package/assets/components/Checkbox/Checkbox.module.css +1 -0
  3. package/assets/components/EventListItem/EventListItem.module.css +220 -0
  4. package/assets/components/EventListItem/EventListItem.tsx +205 -0
  5. package/assets/components/EventListItem/index.ts +8 -0
  6. package/assets/components/Icon/Icon.tsx +80 -41
  7. package/assets/components/ListItem/ListItem.module.css +91 -211
  8. package/assets/components/ListItem/ListItem.tsx +130 -154
  9. package/assets/components/ListItem/index.ts +2 -2
  10. package/assets/components/MenuItem/MenuItem.module.css +30 -5
  11. package/assets/components/MenuItem/MenuItem.tsx +86 -5
  12. package/assets/components/MetricCard/MetricCard.module.css +5 -5
  13. package/assets/components/Radio/Radio.module.css +1 -0
  14. package/assets/components/Sidebar/Sidebar.module.css +81 -4
  15. package/assets/components/Sidebar/Sidebar.tsx +15 -3
  16. package/assets/components/SplitButton/SplitButton.module.css +3 -3
  17. package/assets/components/Stepper/Stepper.module.css +196 -52
  18. package/assets/components/Stepper/Stepper.tsx +68 -13
  19. package/assets/components/Switch/Switch.module.css +1 -9
  20. package/assets/components/Topbar/Topbar.module.css +33 -5
  21. package/assets/components/Topbar/Topbar.tsx +62 -7
  22. package/assets/components/_data.ts +11 -4
  23. package/assets/components/index.ts +2 -0
  24. package/assets/components/tokens.css +23 -10
  25. package/assets/prompts/ASSEMBLY.md +219 -0
  26. package/assets/prompts/PRINCIPLES.md +70 -0
  27. package/assets/prompts/axes/icon.md +7 -1
  28. package/assets/prompts/axes/palette.md +11 -0
  29. package/assets/prompts/components/_variants.md +5 -1
  30. package/assets/prompts/components/callout.md +3 -3
  31. package/assets/prompts/components/event-list-item.md +103 -0
  32. package/assets/prompts/components/generated-image.md +80 -0
  33. package/assets/prompts/components/list-item.md +182 -204
  34. package/assets/prompts/components/menu-item.md +1 -0
  35. package/assets/prompts/components/metric-card.md +2 -2
  36. package/assets/prompts/components/toast.md +1 -0
  37. package/assets/prompts/components/topbar.md +77 -59
  38. package/assets/prompts/themes/focus.md +72 -0
  39. package/assets/skills/ctds/SKILL.md +1 -0
  40. package/assets/skills/ctds-page/SKILL.md +12 -2
  41. package/assets/skills/ctds-wireframe/SKILL.md +35 -1
  42. package/package.json +2 -4
@@ -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
- N/A
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
- - 알림·안내 박스는 `Callout fill` *(색 테두리+색 배경 금지 — 메모리 [No outlined Callout](../../memory/feedback_no_outlined_callout.md))*.
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, 페이지 폭 전체를 덮는 [[components/banner]]와 구분된다.
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
- - 페이지 폭 전역 경고에 쓰지 않는다 — [[components/banner]] 영역.
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
- - [[components/banner]] — 페이지 폭 전면 경고. Callout은 국소 카드.
24
+ - [[recipes/page-banner]] — 페이지 폭 전면 경고. Callout은 국소 카드.
25
25
  - [[components/button]] — actions 슬롯의 실제 요소이며 variant 규칙 원본.
26
26
  - [[components/badge]] — title 옆 상태 라벨.
27
27
  - [[axes/hue]] · [[axes/palette]] — tone별 강조 색을 스왑.
@@ -0,0 +1,103 @@
1
+ # EventListItem
2
+
3
+ ### 원칙
4
+ 리스트에 쌓이는 **하나의 일정** 아이템. 좌측 날짜 블록(큰 숫자 + 요일) · 세로 hairline · 우측 제목 + status Badge + meta 라인의 고정 anatomy. 캘린더 셀 안 색 바는 [[components/event-chip]]의 몫이고, 이 컴포넌트는 "다가오는 일정 리스트" "결재/행사 리스트"처럼 시간을 축으로 수직 나열되는 자리의 1 레코드.
5
+
6
+ 날짜 숫자는 2자리 기준으로 트랙이 고정되어 리스트가 세로로 쌓여도 제목 시작점이 흔들리지 않는다. status는 enum 4종(upcoming/pending/completed/cancelled)으로 받아 [[components/badge]] soft + 상태색으로 자동 매핑 — 호출부가 Badge를 직접 조립하지 않는다.
7
+
8
+ ### Do
9
+ - 한 아이템엔 **한 일정**. `date` 하나 + `title` 하나 + 선택적 `status` + `meta[]`.
10
+ - status는 enum 4종으로 넘긴다 — 자유 색/텍스트 Badge 조립 금지. 라벨만 override 필요하면 `statusLabel`.
11
+ - 메타 라인 아이템 사이는 **짧은 세로 hairline**으로 분리 (bullet·중점·|·하이픈 금지). `showMetaDivider={false}`로 끌 수 있지만 기본은 유지.
12
+ - 행 전체를 라우팅으로 쓰려면 `href` — hover bg + focus ring 자동.
13
+ - 리스트로 쌓을 때 세로 gap은 `--space-2` ~ `--space-3`. 아이템 자체가 chrome(border+radius+padding)을 들고 있어 부모는 수직 flex만. 단, `variant="line"`은 하단 hairline으로 분리되므로 부모 gap 없이 바로 쌓는다.
14
+ - chrome 축은 `variant`로 바꾼다 — `card`(기본, 떠있는 카드) · `line`(radius 없이 하단 hairline만) · `fill`(기본은 중립 soft 회색, status 지정 시 색상 톤).
15
+
16
+ ### Don't
17
+ - [HARD] 안에 다시 Card chrome(추가 border/shadow) 쌓지 않는다 — "리스트 안 카드 안 카드"가 되어 depth가 깨진다.
18
+ - [HARD] status를 자유 색으로 덮어쓰지 않는다. "좋음=green" 같은 가치판단 반전 금지. 색은 **상태 enum**이 결정.
19
+ - [HARD] 날짜 요일 라벨을 UPPERCASE로 강제하지 않는다 — 소문자 유지("fri", "sun"). [[feedback_no_uppercase]].
20
+ - [HARD] 메타 사이 bullet/중점/`|`/em-dash 삽입 금지. 짧은 세로 hairline만 (또는 `showMetaDivider={false}`로 완전 제거).
21
+ - 날짜 숫자 외의 자리에 큰 숫자(가격·카운트) 추가 금지 — 좌측 트랙은 "날짜" 자리 하나.
22
+ - 2줄 이상 서술/본문을 title에 넣지 않는다 — 1줄 ellipsis. 본문이 길면 상세 뷰로.
23
+ - 날짜 블록을 Avatar/Icon으로 대체하지 않는다 — 그러면 이건 [[components/list-item]].
24
+
25
+ ### Fallback
26
+ `status` 애매하면 생략(Badge 안 뜸, fill variant면 중립 회색). `statusLabel`은 enum 기본 라벨(Upcoming/Pending/Completed/Cancelled) 사용. 라우팅 자리 아니면 `href` 생략.
27
+
28
+ ### 관련
29
+ - [[components/event-chip]] — 혼동 금지. EventChip은 캘린더 셀 안 색 바, EventListItem은 리스트 아이템.
30
+ - [[components/list-item]] — 날짜 자리에 Avatar/Icon이 오는 "누가 뭘 했다" 피드는 ListItem.
31
+ - [[components/metric-card]] — 같은 Card chrome 규칙(그림자 금지, line 기본).
32
+ - [[components/badge]] — status 매핑은 Badge soft + status.
33
+ - [[components/icon]] — meta 아이콘.
34
+ - [[tokens/color]] — `--bg` · `--bg-subtle` · `--border` · `--fg-strong` · `--fg-muted` · `--hover` · `--info-subtle` · `--warning-subtle` · `--success-subtle` · `--destructive-subtle`.
35
+ - [[axes/density]] — compact에선 세로 padding 줄어 리스트가 조밀해짐.
36
+ - [[feedback_no_bullet_separator]] — meta 사이 분리 규칙.
37
+ - [[feedback_no_uppercase]] — 요일 소문자 유지.
38
+ - [[feedback_font_weight_cap]] — title은 sm이라 medium, day는 2xl이라 semibold.
39
+
40
+ ### Props 시그니처
41
+ ```tsx
42
+ <EventListItem
43
+ date={Date} // required — 날짜 블록으로 자동 포맷
44
+ title={ReactNode} // required — 1줄 ellipsis
45
+ variant="card | line | fill" // 기본 "card". chrome 스타일 축
46
+ size="md | sm" // 기본 "md". padding/날짜/제목/meta가 한 단계씩
47
+ status="upcoming | pending | completed | cancelled" // Badge soft + 상태색 자동 (fill에선 전면 색)
48
+ statusLabel={ReactNode} // 기본 라벨 override (ex: 한국어 "예정")
49
+ meta={{ icon?: IconName; label: ReactNode }[]} // 메타 라인. 사이는 세로 hairline으로 자동 분리
50
+ showDateDivider={boolean} // 기본 true. 날짜 블록 오른쪽 세로 hairline
51
+ showMetaDivider={boolean} // 기본 true. meta 사이 세로 hairline
52
+ href={string} // 있으면 <a> + hover/focus ring 활성
53
+ />
54
+ ```
55
+
56
+ ### Variant 매트릭스
57
+
58
+ | variant | chrome | 쓰임 |
59
+ |---|---|---|
60
+ | `card` (기본) | 1px border + radius-lg + bg | 단독 떠있는 아이템. 다른 섹션과 섞이는 리스트. |
61
+ | `line` | radius 없음, 하단 hairline만 | 리스트가 가로 폭 꽉 채워 흐르는 자리. 아이템이 쭉 쌓임. |
62
+ | `fill` | **기본 중립 soft 회색**. status 지정 시 info/warning/success/destructive 톤 | 상태가 아이템 전체의 시각 신호로 중요한 자리. |
63
+
64
+ size (variant와 독립 축, 기본 `md`)
65
+
66
+ | size | padding | day | weekday | title | meta | 쓰임 |
67
+ |---|---|---|---|---|---|---|
68
+ | `md` (기본) | space-4 / space-5 | text-2xl | text-2xs | text-sm | text-xs | 대시보드·상세 뷰 등 일반 자리 |
69
+ | `sm` | space-3 / space-4 | text-xl | text-3xs | text-xs | text-2xs | 사이드바·밀집 리스트 등 세로 공간 아끼는 자리 |
70
+
71
+ status enum (variant와 독립 축)
72
+
73
+ | status | Badge status | 기본 라벨 | 쓰임 |
74
+ |---|---|---|---|
75
+ | `upcoming` | `info` | Upcoming | 다가올 일정 (기본 info 톤) |
76
+ | `pending` | `warning` | Pending | 승인·확정 대기 |
77
+ | `completed` | `success` | Completed | 종료·완료 |
78
+ | `cancelled` | `destructive` | Cancelled | 취소·무산 |
79
+
80
+ 조합 결과
81
+
82
+ - `card` / `line` + `status` → Badge soft 표기, chrome은 variant 그대로
83
+ - `fill` + `status` 없음 → 중립 `--bg-subtle` 회색 bg (디폴트)
84
+ - `fill` + `status` 지정 → 아이템 전체가 status soft 톤 bg. 중복 방지로 **Badge는 자동 숨김**(색이 이미 상태를 표현)
85
+
86
+ ### 조합 예시
87
+ ```tsx
88
+ <div>
89
+ {events.map((e) => (
90
+ <EventListItem
91
+ key={e.id}
92
+ href={`/events/${e.id}`}
93
+ date={e.date}
94
+ title={e.title}
95
+ status={e.status}
96
+ meta={[
97
+ { icon: "calendar-dots", label: e.when },
98
+ { icon: "compass", label: e.where },
99
+ ]}
100
+ />
101
+ ))}
102
+ </div>
103
+ ```
@@ -0,0 +1,80 @@
1
+ # GeneratedImage
2
+
3
+ ### 원칙
4
+ AI가 생성한 **이미지 결과물의 큰 표시 자리**. 세 상태(loading / placeholder / image)가 같은 프레임 안에서 바뀐다. `aspect` 축으로 비율만 바꾸고 폭은 상위 그리드가 담당 — 자체적으로 크기를 들고 다니지 않는다. loading 상태는 image-gen 전용 lottie(sparkle breathe)로 "생성 중"을 전달하고, 플레이스홀더 배경이 이미 들어가 있어 깜빡임 없이 전환된다.
5
+
6
+ ### Do
7
+ - 폭은 상위 그리드/부모가 결정. GeneratedImage는 `aspect`로 비율만 선언.
8
+ - 생성 중이면 `loading` prop, 결과가 없을 뿐이면 `loading` 생략(= placeholder만 노출).
9
+ - 다운로드 버튼이 필요하면 `onDownload`를 넘긴다. hover·focus-visible일 때만 노출되고, loading·placeholder 상태에서는 자동으로 숨겨진다.
10
+ - 결과물 레퍼런스는 세로 비율이 많아 기본값 `portrait`(3/4) 그대로 쓰기를 권장.
11
+
12
+ ### Don't
13
+ - 폭을 CSS로 override 금지. 비율은 `aspect`가, 폭은 부모 레이아웃이 책임.
14
+ - `loading=true`일 때 `src`에 결과물을 미리 넣어두는 식의 선행 로딩 금지 — 상태가 꼬인다.
15
+ - 자체 border·bg 덮어쓰기 금지. `--bg-subtle`/`--bg-muted` 토큰이 상태를 신호한다.
16
+ - 다운로드 버튼 자리에 다른 액션(공유·삭제 등) 추가 금지. 이 자리는 "저장" 전용. 액션이 더 필요하면 외부 Toolbar/MessageActions로 뺀다.
17
+ - 결과물 위에 라벨·캡션 overlay 올리지 않는다. 캡션은 바깥 블록으로 분리.
18
+
19
+ ### Fallback
20
+ 비율 애매하면 `portrait`, 상태 애매하면 `loading` 생략(= placeholder). onDownload는 "저장이 UX의 핵심일 때만" 공급.
21
+
22
+ ### 관련
23
+ - [[components/spinner]] — loading 상태에서 `variant="lottie-image-gen"` size `lg`로 내장.
24
+ - [[components/tooltip]] — 다운로드 버튼의 "다운로드" 레이블.
25
+ - [[components/ai-message]] — 어시스턴트 메시지 안 결과물 자리에서 자주 쌍으로 조립.
26
+ - [[components/attachment-thumb]] — 사용자가 올린 참조 이미지 썸네일(작은 자리). 생성 결과 큰 자리는 GeneratedImage.
27
+ - [[components/gallery-item]] — 여러 결과를 그리드로 늘어놓을 때 셀 안쪽 미디어 자리.
28
+
29
+ ### Props 시그니처
30
+ ```tsx
31
+ <GeneratedImage
32
+ src={string} // 결과 이미지 URL (없거나 loading이면 placeholder)
33
+ alt={string} // 접근성 대체 텍스트
34
+ aspect="square | portrait | landscape" // default: portrait (3/4)
35
+ loading={boolean} // 생성 중 — placeholder + sparkle breathe
36
+ onDownload={(e) => void} // 지정 시만 우상단 다운로드 버튼 노출
37
+ />
38
+ ```
39
+
40
+ ### Aspect 가이드
41
+ | aspect | 비율 | 쓰임 |
42
+ |---|---|---|
43
+ | `portrait` | 3/4 | **기본**. 레퍼런스/캐릭터/인물 중심 결과물. |
44
+ | `square` | 1/1 | 아이콘·썸네일·SNS 포맷 결과물. |
45
+ | `landscape` | 4/3 | 배경·풍경·와이드 포스터 결과물. |
46
+
47
+ ### 상태 전환
48
+ 1. 초기: `src` 없음, `loading` 없음 → **placeholder** (배경 이미지 + 조용히 대기)
49
+ 2. 생성 시작: `loading=true` → **loading** (같은 배경 + image-gen lottie 중앙 중첩)
50
+ 3. 결과 수신: `loading=false`, `src=<url>` → **image** (cover로 가득 채움)
51
+ 4. 다운로드: `onDownload` 공급 시, image 상태에서만 우상단 버튼 hover 노출
52
+
53
+ 전환 사이에 레이아웃 shift가 없다 — 세 상태 모두 동일한 `aspect-ratio` 프레임 안에서 바뀌므로 상위 그리드가 흔들리지 않는다.
54
+
55
+ ### 조합 예시
56
+ ```tsx
57
+ // 기본 — AI 메시지 안 결과물
58
+ <GeneratedImage
59
+ src={result.url}
60
+ alt={result.prompt}
61
+ onDownload={() => downloadImage(result.url)}
62
+ />
63
+
64
+ // 생성 중
65
+ <GeneratedImage loading alt="생성 중" />
66
+
67
+ // 와이드 포맷 결과
68
+ <GeneratedImage
69
+ src={banner.url}
70
+ alt={banner.prompt}
71
+ aspect="landscape"
72
+ onDownload={() => downloadImage(banner.url)}
73
+ />
74
+
75
+ // AIMessage 안쪽에서 쌍으로
76
+ <AIMessage>
77
+ <AIMessage.Body>원하신 레퍼런스예요.</AIMessage.Body>
78
+ <GeneratedImage src={url} alt={prompt} onDownload={handleSave} />
79
+ </AIMessage>
80
+ ```