@conscience-technology/ctds 0.4.0 → 0.4.2

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.
@@ -0,0 +1,39 @@
1
+ /* Hint ──────────────────────────────────────────────────────
2
+ 흐름 속 참고 한 줄~두 줄. 배경·테두리 없음, muted 텍스트 하나.
3
+ 아이콘은 line-box 안에 흘려넣어 첫 줄 캡 하이트 중앙에 자동 정렬 —
4
+ line-height 바뀌어도 어긋나지 않도록 flex align 트릭 쓰지 않음. */
5
+ .root {
6
+ display: inline;
7
+ color: var(--fg-muted);
8
+ font-family: var(--font-sans);
9
+ line-height: 1.6;
10
+ }
11
+
12
+ /* 아이콘은 inline-flex 작은 박스로 띄우되,
13
+ vertical-align: text-bottom으로 line-box 하단에 맞춰 캡 하이트 가운데에 걸치게 함.
14
+ margin-inline-end는 공백 역할. */
15
+ .icon {
16
+ display: inline-flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ vertical-align: text-bottom;
20
+ color: var(--fg-muted);
21
+ margin-inline-end: var(--space-1_5);
22
+ }
23
+
24
+ /* Size ─────────────────────────────────────────────────────── */
25
+ .sm {
26
+ font-size: var(--text-sm);
27
+ }
28
+ .sm .icon > svg {
29
+ width: var(--icon-size-sm);
30
+ height: var(--icon-size-sm);
31
+ }
32
+
33
+ .md {
34
+ font-size: var(--text-base);
35
+ }
36
+ .md .icon > svg {
37
+ width: var(--icon-size-md);
38
+ height: var(--icon-size-md);
39
+ }
@@ -0,0 +1,48 @@
1
+ "use client";
2
+
3
+ import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
4
+
5
+ import { Icon, type IconName } from "../Icon";
6
+
7
+ import styles from "./Hint.module.css";
8
+
9
+ /**
10
+ * Hint — 흐름 속에 스며드는 참고 한 줄~두 줄.
11
+ * 아이콘 하나 + muted 텍스트. tone·variant 축 없음. 액션·배경·테두리 전부 없음.
12
+ *
13
+ * 아이콘 수직 정렬은 flex가 아니라 line-box에 흘려넣는다
14
+ * (display: inline + vertical-align) — hint.md "구현 노트" 참고.
15
+ */
16
+ export type HintSize = "sm" | "md";
17
+
18
+ export interface HintProps extends HTMLAttributes<HTMLSpanElement> {
19
+ icon?: IconName;
20
+ size?: HintSize;
21
+ children?: ReactNode;
22
+ }
23
+
24
+ const sizeClass: Record<HintSize, string> = {
25
+ sm: styles.sm,
26
+ md: styles.md,
27
+ };
28
+
29
+ const Hint = forwardRef<HTMLSpanElement, HintProps>(
30
+ ({ className, icon = "info", size = "sm", children, ...rest }, ref) => {
31
+ const rootClass = [styles.root, sizeClass[size], className ?? ""]
32
+ .filter(Boolean)
33
+ .join(" ");
34
+
35
+ return (
36
+ <span ref={ref} className={rootClass} {...rest}>
37
+ <span className={styles.icon} aria-hidden>
38
+ <Icon name={icon} />
39
+ </span>
40
+ {children}
41
+ </span>
42
+ );
43
+ },
44
+ );
45
+
46
+ Hint.displayName = "Hint";
47
+
48
+ export { Hint };
@@ -0,0 +1,2 @@
1
+ export { Hint } from "./Hint";
2
+ export type { HintProps, HintSize } from "./Hint";
@@ -0,0 +1,339 @@
1
+ /*
2
+ * Kanban — 보드 / 컬럼 / 카드 3-레벨.
3
+ *
4
+ * CSS 변수 조절점:
5
+ * --kanban-column-width 컬럼 1장 width
6
+ * --kanban-column-gap 컬럼 사이 가로 간격
7
+ * --kanban-column-pad 컬럼 내부 padding
8
+ * --kanban-card-pad 카드 내부 padding
9
+ * --kanban-card-gap 카드 안 slot 세로 gap
10
+ * --kanban-stack-gap 한 컬럼 안 카드 세로 gap
11
+ *
12
+ * layout 축:
13
+ * wrap — 컬럼 자체가 chrome(배경/테두리)을 가지고 header·body가 안에 함께 들어감.
14
+ * stack — 컬럼 자체엔 chrome 없고 header와 body가 각자 자체 chrome을 가진
15
+ * 두 블록으로 세로 분리. header가 chrome 바깥 위에 "얹어진" 모습.
16
+ */
17
+
18
+ /* ──────────────────────────────────────────────────────
19
+ Board
20
+ ──────────────────────────────────────────────────── */
21
+
22
+ .board {
23
+ /* Kanban 컬럼 전용 폭 — 공용 space 스케일에 등가 토큰 없음, 로컬 상수로 선언. */
24
+ --kanban-column-width: 320px; /* lint-hardcoded: ignore */
25
+ --kanban-column-gap: var(--space-3);
26
+ --kanban-column-pad: var(--space-3);
27
+ --kanban-stack-gap: var(--space-2);
28
+
29
+ display: flex;
30
+ gap: var(--kanban-column-gap);
31
+ align-items: stretch;
32
+ min-width: 0;
33
+ }
34
+
35
+ .board.scroll {
36
+ overflow-x: auto;
37
+ overflow-y: hidden;
38
+ padding-bottom: var(--space-1);
39
+ }
40
+
41
+ /* scroll=false: 가로 스크롤 끄되 wrap하지 않음. 칸반은 무조건 로우. */
42
+ .board.noscroll {
43
+ overflow: visible;
44
+ }
45
+
46
+ /* ──────────────────────────────────────────────────────
47
+ Column — 공통 뼈대
48
+ ──────────────────────────────────────────────────── */
49
+
50
+ .column {
51
+ position: relative;
52
+ flex: 0 0 var(--kanban-column-width);
53
+ width: var(--kanban-column-width);
54
+ max-width: 100%;
55
+ min-width: 0;
56
+ display: flex;
57
+ flex-direction: column;
58
+ color: var(--fg);
59
+ }
60
+
61
+ /* scroll=false면 각 컬럼이 남는 공간을 균등 분할. 로우 유지. */
62
+ .board.noscroll .column {
63
+ flex: 1 1 var(--kanban-column-width);
64
+ }
65
+
66
+ /* ──────────────────────────────────────────────────────
67
+ layout=wrap — 컬럼 자체가 chrome 가짐. header/body 공통 쉘.
68
+ ──────────────────────────────────────────────────── */
69
+
70
+ .layout-wrap {
71
+ gap: var(--space-2);
72
+ padding: var(--kanban-column-pad);
73
+ border-radius: var(--radius-lg);
74
+ transition: var(--transition-colors);
75
+ }
76
+
77
+ /* chrome 축 — layout=wrap. [[feedback_bg_or_stroke_only]]: 배경만 or 테두리만. */
78
+ .layout-wrap.chrome-line {
79
+ background: transparent;
80
+ border: 1px solid var(--border);
81
+ }
82
+ .layout-wrap.chrome-filled {
83
+ background: var(--column-fill, var(--bg-muted));
84
+ }
85
+ .layout-wrap.chrome-plain {
86
+ background: transparent;
87
+ padding: 0;
88
+ }
89
+
90
+ /* tone별 fill 색 — chrome=filled일 때 적용됨.
91
+ neutral은 중립 bg-muted(기본값), 나머지는 base color 4% opacity.
92
+ 글로벌 *-subtle(8%)보다 더 연하게 — 컬럼은 "면"이라 subtle보다 조용해야 함. */
93
+ .column[data-tone="primary"] { --column-fill: color-mix(in oklab, var(--primary) 4%, transparent); }
94
+ .column[data-tone="success"] { --column-fill: color-mix(in oklab, var(--success) 4%, transparent); }
95
+ .column[data-tone="warning"] { --column-fill: color-mix(in oklab, var(--warning) 4%, transparent); }
96
+ .column[data-tone="danger"] { --column-fill: color-mix(in oklab, var(--destructive) 4%, transparent); }
97
+ .column[data-tone="info"] { --column-fill: color-mix(in oklab, var(--info) 4%, transparent); }
98
+
99
+ /* chrome=filled일 때 header 안 텍스트·아이콘도 tone 색계열로 (fill 색 full opacity).
100
+ neutral은 기본 fg-strong 유지.
101
+ action 안 IconButton까지 색이 번지도록 자손 svg·button까지 상속. */
102
+ .chrome-filled[data-tone="primary"] .columnHeader,
103
+ .chrome-filled[data-tone="primary"] .columnHeader * { color: var(--primary); }
104
+ .chrome-filled[data-tone="success"] .columnHeader,
105
+ .chrome-filled[data-tone="success"] .columnHeader * { color: var(--success); }
106
+ .chrome-filled[data-tone="warning"] .columnHeader,
107
+ .chrome-filled[data-tone="warning"] .columnHeader * { color: var(--warning); }
108
+ .chrome-filled[data-tone="danger"] .columnHeader,
109
+ .chrome-filled[data-tone="danger"] .columnHeader * { color: var(--destructive); }
110
+ .chrome-filled[data-tone="info"] .columnHeader,
111
+ .chrome-filled[data-tone="info"] .columnHeader * { color: var(--info); }
112
+
113
+ /* ──────────────────────────────────────────────────────
114
+ layout=stack — header만 chrome. body(카드 스택)는 chrome 없음.
115
+ header가 그릇 바깥 위에 "얹어진" 모습 — 카드들은 그 아래 분리된 공간.
116
+ ──────────────────────────────────────────────────── */
117
+
118
+ .layout-stack {
119
+ gap: var(--space-2);
120
+ background: transparent;
121
+ padding: 0;
122
+ }
123
+
124
+ /* stack: header만 chrome 보유 */
125
+ .layout-stack .columnHeader {
126
+ padding: var(--space-2) var(--space-3);
127
+ border-radius: var(--radius-md);
128
+ transition: var(--transition-colors);
129
+ position: relative;
130
+ }
131
+
132
+ /* stack: body는 chrome 없음. 카드 스택만. */
133
+ .layout-stack .columnBody {
134
+ padding: 0;
135
+ }
136
+
137
+ /* chrome 축 — layout=stack: header에만 적용 */
138
+ .layout-stack.chrome-line .columnHeader {
139
+ background: transparent;
140
+ border: 1px solid var(--border);
141
+ }
142
+ .layout-stack.chrome-filled .columnHeader {
143
+ background: var(--column-fill, var(--bg-muted));
144
+ }
145
+ .layout-stack.chrome-plain .columnHeader {
146
+ padding: 0 var(--space-1);
147
+ }
148
+
149
+ /* ──────────────────────────────────────────────────────
150
+ Column header (공통)
151
+ ──────────────────────────────────────────────────── */
152
+
153
+ .columnHeader {
154
+ display: flex;
155
+ align-items: center;
156
+ gap: var(--space-2);
157
+ min-width: 0;
158
+ padding-inline: var(--space-1);
159
+ }
160
+
161
+ .columnTitle {
162
+ font-size: var(--text-base);
163
+ font-weight: var(--font-weight-medium);
164
+ color: var(--fg-strong, var(--fg));
165
+ line-height: var(--lh-base);
166
+ min-width: 0;
167
+ overflow: hidden;
168
+ text-overflow: ellipsis;
169
+ white-space: nowrap;
170
+ }
171
+
172
+ .columnCount {
173
+ font-size: var(--text-xs);
174
+ color: var(--fg-muted);
175
+ font-variant-numeric: tabular-nums;
176
+ flex-shrink: 0;
177
+ margin-left: calc(var(--space-2) * -1 + var(--space-1));
178
+ margin-right: auto;
179
+ }
180
+
181
+ .columnAction {
182
+ display: inline-flex;
183
+ align-items: center;
184
+ gap: var(--space-1);
185
+ flex-shrink: 0;
186
+ color: var(--fg-muted);
187
+ }
188
+
189
+ /* ──────────────────────────────────────────────────────
190
+ Accent (tone 색 지점)
191
+ ──────────────────────────────────────────────────── */
192
+
193
+ .accentMark {
194
+ flex-shrink: 0;
195
+ background: var(--column-accent, var(--fg-muted));
196
+ }
197
+
198
+ .accentMark[data-accent="dot"] {
199
+ width: var(--space-2);
200
+ height: var(--space-2);
201
+ border-radius: var(--radius-full);
202
+ }
203
+
204
+ .column[data-tone="neutral"] { --column-accent: var(--fg-muted); }
205
+ .column[data-tone="primary"] { --column-accent: var(--primary); }
206
+ .column[data-tone="success"] { --column-accent: var(--success); }
207
+ .column[data-tone="warning"] { --column-accent: var(--warning); }
208
+ .column[data-tone="danger"] { --column-accent: var(--destructive); }
209
+ .column[data-tone="info"] { --column-accent: var(--info); }
210
+
211
+ /* ──────────────────────────────────────────────────────
212
+ Column body (카드 스택)
213
+ ──────────────────────────────────────────────────── */
214
+
215
+ .columnBody {
216
+ display: flex;
217
+ flex-direction: column;
218
+ gap: var(--kanban-stack-gap);
219
+ min-height: var(--space-8);
220
+ border-radius: var(--radius-md);
221
+ transition: var(--transition-colors);
222
+ }
223
+
224
+
225
+ .columnFooter {
226
+ padding-top: var(--space-1);
227
+ color: var(--fg-muted);
228
+ }
229
+
230
+ /* ──────────────────────────────────────────────────────
231
+ Card
232
+ ──────────────────────────────────────────────────── */
233
+
234
+ .card {
235
+ --kanban-card-pad: var(--space-3);
236
+ --kanban-card-gap: var(--space-2);
237
+
238
+ display: flex;
239
+ flex-direction: column;
240
+ gap: var(--kanban-card-gap);
241
+ padding: var(--kanban-card-pad);
242
+ background: color-mix(in oklab, var(--bg) 80%, transparent);
243
+ border-radius: var(--radius-md);
244
+ color: var(--fg);
245
+ text-decoration: none;
246
+ min-width: 0;
247
+ transition: var(--transition-colors), transform var(--duration-base) var(--ease-out),
248
+ box-shadow var(--duration-base) var(--ease-out);
249
+ }
250
+
251
+ .card:hover {
252
+ background: var(--bg);
253
+ border-color: var(--border-strong);
254
+ transform: translateY(calc(var(--space-0_5) * -1));
255
+ box-shadow: var(--shadow-md);
256
+ }
257
+
258
+ .cardInteractive {
259
+ cursor: pointer;
260
+ }
261
+ .cardInteractive:focus-visible {
262
+ outline: none;
263
+ box-shadow: var(--ring-shadow);
264
+ }
265
+
266
+ /* ── Card slots ────────────────────────────────────── */
267
+
268
+ .cover {
269
+ margin: calc(var(--kanban-card-pad) * -1) calc(var(--kanban-card-pad) * -1) 0;
270
+ border-top-left-radius: inherit;
271
+ border-top-right-radius: inherit;
272
+ overflow: hidden;
273
+ display: block;
274
+ }
275
+ .cover > img,
276
+ .cover > video {
277
+ display: block;
278
+ width: 100%;
279
+ height: auto;
280
+ }
281
+
282
+ .cardHeader {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: var(--space-2);
286
+ min-width: 0;
287
+ color: var(--fg-muted);
288
+ }
289
+
290
+ .cardTitle {
291
+ font-size: var(--text-sm);
292
+ line-height: var(--lh-sm);
293
+ color: var(--fg-strong, var(--fg));
294
+ font-weight: var(--font-weight-medium);
295
+ min-width: 0;
296
+ display: flex;
297
+ flex-wrap: wrap;
298
+ align-items: center;
299
+ gap: var(--space-2);
300
+ }
301
+
302
+ .cardDescription {
303
+ margin: 0;
304
+ font-size: var(--text-sm);
305
+ line-height: var(--lh-sm);
306
+ color: var(--fg-muted);
307
+ font-weight: var(--font-weight-regular);
308
+ display: -webkit-box;
309
+ -webkit-line-clamp: 3;
310
+ -webkit-box-orient: vertical;
311
+ overflow: hidden;
312
+ }
313
+
314
+ /* title 바로 뒤의 description — 둘은 한 묶음으로 간주해 gap 좁힘.
315
+ card의 기본 gap(space-2)에서 space-1만큼 당겨 space-1(=4px)로 유지. */
316
+ .cardTitle + .cardDescription {
317
+ margin-top: calc(var(--space-1) * -1);
318
+ }
319
+
320
+ .cardMeta {
321
+ display: flex;
322
+ align-items: center;
323
+ flex-wrap: wrap;
324
+ gap: var(--space-2);
325
+ font-size: var(--text-xs);
326
+ line-height: var(--lh-sm);
327
+ color: var(--fg-subtle);
328
+ font-variant-numeric: tabular-nums;
329
+ }
330
+
331
+ .cardFooter {
332
+ display: flex;
333
+ align-items: center;
334
+ justify-content: space-between;
335
+ gap: var(--space-2);
336
+ color: var(--fg-muted);
337
+ font-size: var(--text-xs);
338
+ line-height: var(--lh-sm);
339
+ }