@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.
Files changed (53) hide show
  1. package/assets/components/Button/Button.module.css +2 -2
  2. package/assets/components/EventListItem/EventListItem.module.css +220 -0
  3. package/assets/components/EventListItem/EventListItem.tsx +205 -0
  4. package/assets/components/EventListItem/index.ts +8 -0
  5. package/assets/components/Icon/Icon.tsx +80 -41
  6. package/assets/components/Kanban/Kanban.module.css +339 -0
  7. package/assets/components/Kanban/Kanban.tsx +371 -0
  8. package/assets/components/Kanban/index.ts +10 -0
  9. package/assets/components/ListItem/ListItem.module.css +91 -211
  10. package/assets/components/ListItem/ListItem.tsx +130 -154
  11. package/assets/components/ListItem/index.ts +2 -2
  12. package/assets/components/MetricCard/MetricCard.module.css +5 -5
  13. package/assets/components/PageHeader/PageHeader.module.css +109 -0
  14. package/assets/components/PageHeader/PageHeader.tsx +112 -0
  15. package/assets/components/PageHeader/index.ts +2 -0
  16. package/assets/components/Sidebar/Sidebar.module.css +81 -4
  17. package/assets/components/Sidebar/Sidebar.tsx +11 -2
  18. package/assets/components/SplitButton/SplitButton.module.css +3 -3
  19. package/assets/components/Stepper/Stepper.module.css +196 -52
  20. package/assets/components/Stepper/Stepper.tsx +68 -13
  21. package/assets/components/Switch/Switch.module.css +1 -9
  22. package/assets/components/Topbar/Topbar.module.css +33 -5
  23. package/assets/components/Topbar/Topbar.tsx +62 -7
  24. package/assets/components/_data.ts +11 -4
  25. package/assets/components/index.ts +2 -0
  26. package/assets/components/tokens.css +23 -10
  27. package/assets/prompts/ASSEMBLY.md +219 -0
  28. package/assets/prompts/PRINCIPLES.md +70 -0
  29. package/assets/prompts/axes/icon.md +7 -1
  30. package/assets/prompts/axes/palette.md +11 -0
  31. package/assets/prompts/components/_variants.md +5 -1
  32. package/assets/prompts/components/callout.md +3 -3
  33. package/assets/prompts/components/event-list-item.md +103 -0
  34. package/assets/prompts/components/generated-image.md +80 -0
  35. package/assets/prompts/components/hint.md +57 -0
  36. package/assets/prompts/components/kanban.md +167 -0
  37. package/assets/prompts/components/list-item.md +182 -204
  38. package/assets/prompts/components/metric-card.md +2 -2
  39. package/assets/prompts/components/page-header.md +155 -0
  40. package/assets/prompts/components/toast.md +1 -0
  41. package/assets/prompts/components/topbar.md +77 -59
  42. package/assets/prompts/recipes/README.md +102 -0
  43. package/assets/prompts/recipes/card-row.md +58 -0
  44. package/assets/prompts/recipes/form-footer.md +76 -0
  45. package/assets/prompts/recipes/meta-line.md +61 -0
  46. package/assets/prompts/recipes/page-banner.md +121 -0
  47. package/assets/prompts/recipes/page-header.md +93 -0
  48. package/assets/prompts/recipes/section-header.md +90 -0
  49. package/assets/prompts/themes/focus.md +72 -0
  50. package/assets/skills/ctds/SKILL.md +1 -0
  51. package/assets/skills/ctds-page/SKILL.md +12 -2
  52. package/assets/skills/ctds-wireframe/SKILL.md +35 -1
  53. package/package.json +2 -4
@@ -0,0 +1,371 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Kanban — 가로 트랙(Board) + 컬럼(Column) + 카드(Card)의 3-레벨 compound.
5
+ *
6
+ * 레벨별 축:
7
+ * Kanban (board)
8
+ * columnWidth — 컬럼 1장 width. 기본 var(--kanban-column-width) = 320px
9
+ * gap — 컬럼 사이 가로 간격. 기본 space-3
10
+ * scroll — 가로 스크롤 on/off. 기본 true. false면 flex-wrap으로 반응형.
11
+ *
12
+ * Kanban.Column
13
+ * chrome — line (border) | filled (bg-muted) | plain (chrome 없음)
14
+ * accent — none | dot | bar. 상태 색 신호 위치.
15
+ * tone — neutral | primary | success | warning | danger | info
16
+ * * layout — stack (기본) | wrap
17
+ * · stack(권장): header만 자체 chrome을 가지고 카드 스택은
18
+ * chrome 없이 그 아래 자유롭게 쌓이는 "얹어진" 형태.
19
+ * · wrap(비권장): header와 카드가 한 쉘 안에 함께 들어있는
20
+ * "감싸는" 형태. 전통적 칸반이지만 카드가 그릇에 갇혀 답답함.
21
+ *
22
+ * Kanban.Card
23
+ * content은 Card.Cover · Card.Header · Card.Title · Card.Description
24
+ * · Card.Meta · Card.Footer slot으로 자유 조립.
25
+ * interactive — href/onClick 자동 hover (ListItem과 동일)
26
+ *
27
+ * DnD는 호출부가 바깥에서 dnd-kit로 감싸는 opt-in. 시각 먼저, 상호작용은 선택.
28
+ */
29
+
30
+ import {
31
+ forwardRef,
32
+ type AnchorHTMLAttributes,
33
+ type HTMLAttributes,
34
+ type ReactNode,
35
+ type Ref,
36
+ } from "react";
37
+
38
+ import styles from "./Kanban.module.css";
39
+
40
+ /* ──────────────────────────────────────────────────────
41
+ Kanban (board)
42
+ ──────────────────────────────────────────────────── */
43
+
44
+ export type KanbanProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
45
+ /** 컬럼 1장 width. CSS 길이 문자열(e.g. "280px", "320px"). */
46
+ columnWidth?: string;
47
+ /** 컬럼 사이 가로 간격. CSS 길이. 기본 var(--space-3). */
48
+ gap?: string;
49
+ /** 가로 스크롤. 기본 true. false면 flex-wrap으로 반응형. */
50
+ scroll?: boolean;
51
+ children?: ReactNode;
52
+ };
53
+
54
+ const KanbanRoot = forwardRef<HTMLDivElement, KanbanProps>(function Kanban(
55
+ { columnWidth, gap, scroll = true, className, style, children, ...rest },
56
+ ref,
57
+ ) {
58
+ const rootClass = [
59
+ styles.board,
60
+ scroll ? styles.scroll : styles.noscroll,
61
+ className ?? "",
62
+ ]
63
+ .filter(Boolean)
64
+ .join(" ");
65
+
66
+ const cssVars: Record<string, string> = {};
67
+ if (columnWidth) cssVars["--kanban-column-width"] = columnWidth;
68
+ if (gap) cssVars["--kanban-column-gap"] = gap;
69
+
70
+ return (
71
+ <div
72
+ ref={ref}
73
+ className={rootClass}
74
+ style={{ ...cssVars, ...style }}
75
+ {...rest}
76
+ >
77
+ {children}
78
+ </div>
79
+ );
80
+ });
81
+ KanbanRoot.displayName = "Kanban";
82
+
83
+ /* ──────────────────────────────────────────────────────
84
+ Column
85
+ ──────────────────────────────────────────────────── */
86
+
87
+ export type KanbanColumnChrome = "line" | "filled" | "plain";
88
+ export type KanbanColumnAccent = "none" | "dot";
89
+ export type KanbanColumnTone =
90
+ | "neutral"
91
+ | "primary"
92
+ | "success"
93
+ | "warning"
94
+ | "danger"
95
+ | "info";
96
+ export type KanbanColumnLayout = "wrap" | "stack";
97
+
98
+ export type KanbanColumnProps = Omit<HTMLAttributes<HTMLElement>, "title"> & {
99
+ title?: ReactNode;
100
+ count?: number | string;
101
+ action?: ReactNode;
102
+ footer?: ReactNode;
103
+ chrome?: KanbanColumnChrome;
104
+ accent?: KanbanColumnAccent;
105
+ tone?: KanbanColumnTone;
106
+ layout?: KanbanColumnLayout;
107
+ children?: ReactNode;
108
+ };
109
+
110
+ const Column = forwardRef<HTMLElement, KanbanColumnProps>(function KanbanColumn(
111
+ {
112
+ title,
113
+ count,
114
+ action,
115
+ footer,
116
+ chrome = "line",
117
+ accent = "none",
118
+ tone = "neutral",
119
+ layout = "stack",
120
+ className,
121
+ children,
122
+ ...rest
123
+ },
124
+ ref,
125
+ ) {
126
+ const cls = [
127
+ styles.column,
128
+ styles[`layout-${layout}`],
129
+ styles[`chrome-${chrome}`],
130
+ styles[`accent-${accent}`],
131
+ className ?? "",
132
+ ]
133
+ .filter(Boolean)
134
+ .join(" ");
135
+
136
+ const showHeader =
137
+ title !== undefined ||
138
+ count !== undefined ||
139
+ accent !== "none" ||
140
+ action !== undefined;
141
+
142
+ return (
143
+ <div
144
+ ref={ref as Ref<HTMLDivElement>}
145
+ className={cls}
146
+ data-tone={tone}
147
+ {...(rest as HTMLAttributes<HTMLDivElement>)}
148
+ >
149
+ {showHeader && (
150
+ <div className={styles.columnHeader}>
151
+ {accent !== "none" && (
152
+ <span className={styles.accentMark} aria-hidden data-accent={accent} />
153
+ )}
154
+ {title !== undefined && (
155
+ <span className={styles.columnTitle}>{title}</span>
156
+ )}
157
+ {count !== undefined && (
158
+ <span className={styles.columnCount}>{count}</span>
159
+ )}
160
+ {action !== undefined && (
161
+ <span className={styles.columnAction}>{action}</span>
162
+ )}
163
+ </div>
164
+ )}
165
+ <div className={styles.columnBody}>{children}</div>
166
+ {footer !== undefined && (
167
+ <div className={styles.columnFooter}>{footer}</div>
168
+ )}
169
+ </div>
170
+ );
171
+ });
172
+ Column.displayName = "Kanban.Column";
173
+
174
+ /* ──────────────────────────────────────────────────────
175
+ Card
176
+ ──────────────────────────────────────────────────── */
177
+
178
+ type CardCommon = {
179
+ children?: ReactNode;
180
+ };
181
+
182
+ type CardDivProps = CardCommon &
183
+ Omit<HTMLAttributes<HTMLDivElement>, keyof CardCommon> & { href?: undefined };
184
+
185
+ type CardAnchorProps = CardCommon &
186
+ Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof CardCommon> & {
187
+ href: string;
188
+ };
189
+
190
+ export type KanbanCardProps = CardDivProps | CardAnchorProps;
191
+
192
+ const Card = forwardRef<HTMLElement, KanbanCardProps>(function KanbanCard(
193
+ props,
194
+ ref,
195
+ ) {
196
+ const {
197
+ className,
198
+ children,
199
+ ...rest
200
+ } = props as CardCommon & { className?: string } & Record<string, unknown>;
201
+
202
+ const isLink = typeof (props as CardAnchorProps).href === "string";
203
+ const isInteractive =
204
+ isLink ||
205
+ typeof (props as HTMLAttributes<HTMLElement>).onClick === "function";
206
+
207
+ const cls = [
208
+ styles.card,
209
+ isInteractive ? styles.cardInteractive : "",
210
+ className ?? "",
211
+ ]
212
+ .filter(Boolean)
213
+ .join(" ");
214
+
215
+ if (isLink) {
216
+ return (
217
+ <a
218
+ ref={ref as Ref<HTMLAnchorElement>}
219
+ className={cls}
220
+ {...(rest as AnchorHTMLAttributes<HTMLAnchorElement>)}
221
+ href={(props as CardAnchorProps).href}
222
+ >
223
+ {children}
224
+ </a>
225
+ );
226
+ }
227
+
228
+ return (
229
+ <div
230
+ ref={ref as Ref<HTMLDivElement>}
231
+ className={cls}
232
+ {...(rest as HTMLAttributes<HTMLDivElement>)}
233
+ >
234
+ {children}
235
+ </div>
236
+ );
237
+ });
238
+ Card.displayName = "Kanban.Card";
239
+
240
+ /* ── Card slots ─────────────────────────────────────── */
241
+
242
+ type DivProps = HTMLAttributes<HTMLDivElement>;
243
+
244
+ const CardCover = forwardRef<HTMLDivElement, DivProps>(function KanbanCardCover(
245
+ { className, children, ...rest },
246
+ ref,
247
+ ) {
248
+ return (
249
+ <div
250
+ ref={ref}
251
+ className={[styles.cover, className ?? ""].filter(Boolean).join(" ")}
252
+ {...rest}
253
+ >
254
+ {children}
255
+ </div>
256
+ );
257
+ });
258
+ CardCover.displayName = "Kanban.Card.Cover";
259
+
260
+ const CardHeader = forwardRef<HTMLDivElement, DivProps>(function KanbanCardHeader(
261
+ { className, children, ...rest },
262
+ ref,
263
+ ) {
264
+ return (
265
+ <div
266
+ ref={ref}
267
+ className={[styles.cardHeader, className ?? ""].filter(Boolean).join(" ")}
268
+ {...rest}
269
+ >
270
+ {children}
271
+ </div>
272
+ );
273
+ });
274
+ CardHeader.displayName = "Kanban.Card.Header";
275
+
276
+ const CardTitle = forwardRef<HTMLDivElement, DivProps>(function KanbanCardTitle(
277
+ { className, children, ...rest },
278
+ ref,
279
+ ) {
280
+ return (
281
+ <div
282
+ ref={ref}
283
+ className={[styles.cardTitle, className ?? ""].filter(Boolean).join(" ")}
284
+ {...rest}
285
+ >
286
+ {children}
287
+ </div>
288
+ );
289
+ });
290
+ CardTitle.displayName = "Kanban.Card.Title";
291
+
292
+ const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
293
+ function KanbanCardDescription({ className, children, ...rest }, ref) {
294
+ return (
295
+ <p
296
+ ref={ref}
297
+ className={[styles.cardDescription, className ?? ""]
298
+ .filter(Boolean)
299
+ .join(" ")}
300
+ {...rest}
301
+ >
302
+ {children}
303
+ </p>
304
+ );
305
+ },
306
+ );
307
+ CardDescription.displayName = "Kanban.Card.Description";
308
+
309
+ const CardMeta = forwardRef<HTMLDivElement, DivProps>(function KanbanCardMeta(
310
+ { className, children, ...rest },
311
+ ref,
312
+ ) {
313
+ return (
314
+ <div
315
+ ref={ref}
316
+ className={[styles.cardMeta, className ?? ""].filter(Boolean).join(" ")}
317
+ {...rest}
318
+ >
319
+ {children}
320
+ </div>
321
+ );
322
+ });
323
+ CardMeta.displayName = "Kanban.Card.Meta";
324
+
325
+ const CardFooter = forwardRef<HTMLDivElement, DivProps>(function KanbanCardFooter(
326
+ { className, children, ...rest },
327
+ ref,
328
+ ) {
329
+ return (
330
+ <div
331
+ ref={ref}
332
+ className={[styles.cardFooter, className ?? ""].filter(Boolean).join(" ")}
333
+ {...rest}
334
+ >
335
+ {children}
336
+ </div>
337
+ );
338
+ });
339
+ CardFooter.displayName = "Kanban.Card.Footer";
340
+
341
+ /* ──────────────────────────────────────────────────────
342
+ compound export
343
+ ──────────────────────────────────────────────────── */
344
+
345
+ type CardCompound = typeof Card & {
346
+ Cover: typeof CardCover;
347
+ Header: typeof CardHeader;
348
+ Title: typeof CardTitle;
349
+ Description: typeof CardDescription;
350
+ Meta: typeof CardMeta;
351
+ Footer: typeof CardFooter;
352
+ };
353
+
354
+ const KanbanCard = Card as CardCompound;
355
+ KanbanCard.Cover = CardCover;
356
+ KanbanCard.Header = CardHeader;
357
+ KanbanCard.Title = CardTitle;
358
+ KanbanCard.Description = CardDescription;
359
+ KanbanCard.Meta = CardMeta;
360
+ KanbanCard.Footer = CardFooter;
361
+
362
+ type KanbanCompound = typeof KanbanRoot & {
363
+ Column: typeof Column;
364
+ Card: typeof KanbanCard;
365
+ };
366
+
367
+ const Kanban = KanbanRoot as KanbanCompound;
368
+ Kanban.Column = Column;
369
+ Kanban.Card = KanbanCard;
370
+
371
+ export { Kanban };
@@ -0,0 +1,10 @@
1
+ export { Kanban } from "./Kanban";
2
+ export type {
3
+ KanbanProps,
4
+ KanbanColumnProps,
5
+ KanbanColumnChrome,
6
+ KanbanColumnAccent,
7
+ KanbanColumnTone,
8
+ KanbanColumnLayout,
9
+ KanbanCardProps,
10
+ } from "./Kanban";