@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
|
@@ -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 };
|