@conscience-technology/ctds 0.1.14 → 0.2.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/AppFrame/AppFrame.module.css +6 -0
- package/assets/components/{AttachmentChip/AttachmentChip.module.css → AttachmentItem/AttachmentItem.module.css} +5 -8
- package/assets/components/{AttachmentChip/AttachmentChip.tsx → AttachmentItem/AttachmentItem.tsx} +21 -15
- package/assets/components/AttachmentItem/index.ts +6 -0
- package/assets/components/Button/Button.module.css +11 -2
- package/assets/components/ChatInput/ChatInput.tsx +2 -2
- package/assets/components/DatePicker/Calendar.tsx +19 -1
- package/assets/components/Drawer/Drawer.tsx +120 -39
- package/assets/components/Drawer/index.ts +9 -1
- package/assets/components/{MessageAttachment/MessageAttachment.module.css → GeneratedImage/GeneratedImage.module.css} +3 -2
- package/assets/components/{MessageAttachment/MessageAttachment.tsx → GeneratedImage/GeneratedImage.tsx} +8 -8
- package/assets/components/GeneratedImage/index.ts +5 -0
- package/assets/components/Icon/Icon.tsx +4 -1
- package/assets/components/Modal/Modal.tsx +114 -33
- package/assets/components/Modal/index.ts +12 -2
- package/assets/components/Spinner/lottieManifest.ts +8 -8
- package/assets/components/Switch/Switch.tsx +6 -0
- package/assets/components/TableHeader/TableHeader.module.css +29 -20
- package/assets/components/TableRow/TableRow.module.css +7 -5
- package/assets/components/TableRowEdit/TableRowEdit.module.css +10 -3
- package/assets/components/TableRowEdit/TableRowEdit.tsx +7 -2
- package/assets/components/Toast/Toast.tsx +19 -0
- package/assets/components/_data.ts +39 -16
- package/assets/components/app-shell.css +17 -0
- package/assets/components/index.ts +77 -0
- package/assets/components/reset.css +63 -0
- package/assets/components/themeBootScript.ts +40 -0
- package/assets/components/tokens.css +68 -57
- package/assets/prompts/GUIDE.md +1 -1
- package/assets/prompts/PRINCIPLES.md +9 -0
- package/assets/prompts/axes/cascade.md +49 -0
- package/assets/prompts/axes/mode.md +3 -1
- package/assets/prompts/components/_variants.md +84 -0
- package/assets/prompts/components/{attachment-chip.md → attachment-item.md} +12 -10
- package/assets/prompts/components/attachment-thumb.md +2 -2
- package/assets/prompts/components/avatar-upload.md +53 -0
- package/assets/prompts/components/bubble.md +2 -2
- package/assets/prompts/components/chat-input.md +7 -8
- package/assets/prompts/components/code-block.md +2 -2
- package/assets/prompts/components/disclosure.md +47 -0
- package/assets/prompts/components/file-icon.md +3 -3
- package/assets/prompts/components/generated-image.md +38 -0
- package/assets/prompts/components/message-quote.md +2 -2
- package/assets/prompts/components/message-sources.md +5 -4
- package/assets/prompts/components/message-table.md +9 -6
- package/assets/prompts/components/onboarding.md +43 -0
- package/assets/prompts/components/quotation-bar.md +1 -1
- package/assets/prompts/components/sidebar-split.md +70 -0
- package/assets/prompts/components/sidebar.md +2 -0
- package/assets/prompts/components/time-picker.md +69 -0
- package/assets/prompts/components/toolbar.md +69 -0
- package/assets/prompts/components/user-message.md +2 -2
- package/package.json +80 -3
- package/src/commands/init.js +53 -23
- package/src/utils/deps.js +23 -17
- package/src/utils/patch.js +155 -0
- package/assets/components/AttachmentChip/index.ts +0 -6
- package/assets/components/Calendar/Calendar.module.css +0 -163
- package/assets/components/Calendar/Calendar.tsx +0 -323
- package/assets/components/Calendar/index.ts +0 -2
- package/assets/components/MessageAttachment/index.ts +0 -5
- package/assets/prompts/components/calendar.md +0 -116
- package/assets/prompts/components/form-field.md +0 -74
- package/assets/prompts/components/message-attachment.md +0 -40
|
@@ -29,7 +29,16 @@ export interface ModalProps {
|
|
|
29
29
|
disableEscape?: boolean;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* 간편 모드 — 가장 흔한 "title + body + footer" 레이아웃을 flat prop으로 넘긴다.
|
|
34
|
+
* 커스터마이즈가 필요하면 compound 모드:
|
|
35
|
+
* <Modal.Root open onOpenChange={set}>
|
|
36
|
+
* <Modal.Header title="…" />
|
|
37
|
+
* <Modal.Body>…</Modal.Body>
|
|
38
|
+
* <Modal.Footer>…</Modal.Footer>
|
|
39
|
+
* </Modal.Root>
|
|
40
|
+
*/
|
|
41
|
+
const ModalComponent = forwardRef<HTMLDivElement, ModalProps>(function Modal(
|
|
33
42
|
{
|
|
34
43
|
open,
|
|
35
44
|
onOpenChange,
|
|
@@ -43,6 +52,38 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(function Modal(
|
|
|
43
52
|
disableEscape,
|
|
44
53
|
},
|
|
45
54
|
ref,
|
|
55
|
+
) {
|
|
56
|
+
return (
|
|
57
|
+
<ModalRoot
|
|
58
|
+
open={open}
|
|
59
|
+
onOpenChange={onOpenChange}
|
|
60
|
+
disableEscape={disableEscape}
|
|
61
|
+
ref={ref}
|
|
62
|
+
>
|
|
63
|
+
<ModalHeader
|
|
64
|
+
title={title}
|
|
65
|
+
description={description}
|
|
66
|
+
titleSrOnly={titleSrOnly}
|
|
67
|
+
dismissible={dismissible}
|
|
68
|
+
dismissLabel={dismissLabel}
|
|
69
|
+
/>
|
|
70
|
+
{children !== undefined && children !== null && <ModalBody>{children}</ModalBody>}
|
|
71
|
+
{footer && <ModalFooter>{footer}</ModalFooter>}
|
|
72
|
+
</ModalRoot>
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export interface ModalRootProps {
|
|
77
|
+
open: boolean;
|
|
78
|
+
onOpenChange: (open: boolean) => void;
|
|
79
|
+
children: ReactNode;
|
|
80
|
+
/** ESC 키 닫힘 차단. 중요한 확인/저장 중엔 true. */
|
|
81
|
+
disableEscape?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const ModalRoot = forwardRef<HTMLDivElement, ModalRootProps>(function ModalRoot(
|
|
85
|
+
{ open, onOpenChange, children, disableEscape },
|
|
86
|
+
ref,
|
|
46
87
|
) {
|
|
47
88
|
return (
|
|
48
89
|
<RadixDialog.Root open={open} onOpenChange={onOpenChange}>
|
|
@@ -59,40 +100,80 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(function Modal(
|
|
|
59
100
|
e.preventDefault();
|
|
60
101
|
}}
|
|
61
102
|
>
|
|
62
|
-
|
|
63
|
-
<div className={styles.headerText}>
|
|
64
|
-
<RadixDialog.Title
|
|
65
|
-
className={[styles.title, titleSrOnly && "visually-hidden"]
|
|
66
|
-
.filter(Boolean)
|
|
67
|
-
.join(" ")}
|
|
68
|
-
>
|
|
69
|
-
{title}
|
|
70
|
-
</RadixDialog.Title>
|
|
71
|
-
{description && (
|
|
72
|
-
<RadixDialog.Description className={styles.description}>
|
|
73
|
-
{description}
|
|
74
|
-
</RadixDialog.Description>
|
|
75
|
-
)}
|
|
76
|
-
</div>
|
|
77
|
-
{dismissible && (
|
|
78
|
-
<RadixDialog.Close asChild>
|
|
79
|
-
<IconButton
|
|
80
|
-
className={styles.close}
|
|
81
|
-
variant="ghost"
|
|
82
|
-
icon={<Icon name="x" />}
|
|
83
|
-
aria-label={dismissLabel}
|
|
84
|
-
/>
|
|
85
|
-
</RadixDialog.Close>
|
|
86
|
-
)}
|
|
87
|
-
</div>
|
|
88
|
-
{children !== undefined && children !== null && (
|
|
89
|
-
<div className={styles.body}>
|
|
90
|
-
<div className={styles.bodyInner}>{children}</div>
|
|
91
|
-
</div>
|
|
92
|
-
)}
|
|
93
|
-
{footer && <div className={styles.footer}>{footer}</div>}
|
|
103
|
+
{children}
|
|
94
104
|
</RadixDialog.Content>
|
|
95
105
|
</RadixDialog.Portal>
|
|
96
106
|
</RadixDialog.Root>
|
|
97
107
|
);
|
|
98
108
|
});
|
|
109
|
+
|
|
110
|
+
export interface ModalHeaderProps {
|
|
111
|
+
title: ReactNode;
|
|
112
|
+
description?: ReactNode;
|
|
113
|
+
titleSrOnly?: boolean;
|
|
114
|
+
dismissible?: boolean;
|
|
115
|
+
dismissLabel?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function ModalHeader({
|
|
119
|
+
title,
|
|
120
|
+
description,
|
|
121
|
+
titleSrOnly,
|
|
122
|
+
dismissible = true,
|
|
123
|
+
dismissLabel,
|
|
124
|
+
}: ModalHeaderProps) {
|
|
125
|
+
return (
|
|
126
|
+
<div className={styles.header}>
|
|
127
|
+
<div className={styles.headerText}>
|
|
128
|
+
<RadixDialog.Title
|
|
129
|
+
className={[styles.title, titleSrOnly && "visually-hidden"]
|
|
130
|
+
.filter(Boolean)
|
|
131
|
+
.join(" ")}
|
|
132
|
+
>
|
|
133
|
+
{title}
|
|
134
|
+
</RadixDialog.Title>
|
|
135
|
+
{description && (
|
|
136
|
+
<RadixDialog.Description className={styles.description}>
|
|
137
|
+
{description}
|
|
138
|
+
</RadixDialog.Description>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
{dismissible && (
|
|
142
|
+
<RadixDialog.Close asChild>
|
|
143
|
+
<IconButton
|
|
144
|
+
className={styles.close}
|
|
145
|
+
variant="ghost"
|
|
146
|
+
icon={<Icon name="x" />}
|
|
147
|
+
aria-label={dismissLabel}
|
|
148
|
+
/>
|
|
149
|
+
</RadixDialog.Close>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function ModalBody({ children }: { children: ReactNode }) {
|
|
156
|
+
return (
|
|
157
|
+
<div className={styles.body}>
|
|
158
|
+
<div className={styles.bodyInner}>{children}</div>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function ModalFooter({ children }: { children: ReactNode }) {
|
|
164
|
+
return <div className={styles.footer}>{children}</div>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
type ModalCompound = typeof ModalComponent & {
|
|
168
|
+
Root: typeof ModalRoot;
|
|
169
|
+
Header: typeof ModalHeader;
|
|
170
|
+
Body: typeof ModalBody;
|
|
171
|
+
Footer: typeof ModalFooter;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const Modal = Object.assign(ModalComponent, {
|
|
175
|
+
Root: ModalRoot,
|
|
176
|
+
Header: ModalHeader,
|
|
177
|
+
Body: ModalBody,
|
|
178
|
+
Footer: ModalFooter,
|
|
179
|
+
}) as ModalCompound;
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
1
|
+
export {
|
|
2
|
+
Modal,
|
|
3
|
+
ModalRoot,
|
|
4
|
+
ModalHeader,
|
|
5
|
+
ModalBody,
|
|
6
|
+
ModalFooter,
|
|
7
|
+
} from "./Modal";
|
|
8
|
+
export type {
|
|
9
|
+
ModalProps,
|
|
10
|
+
ModalRootProps,
|
|
11
|
+
ModalHeaderProps,
|
|
12
|
+
} from "./Modal";
|
|
@@ -42,21 +42,21 @@ export const LOTTIE_MANIFEST: Record<LottieName, LottieManifestEntry> = {
|
|
|
42
42
|
name: "rabbit",
|
|
43
43
|
path: "/lottie/rabbit.json",
|
|
44
44
|
colorBind: true,
|
|
45
|
-
visualScale: 1.
|
|
45
|
+
visualScale: 1.4,
|
|
46
46
|
description: "뛰어가는 토끼. 긴 작업·AI 생성 대기",
|
|
47
47
|
},
|
|
48
48
|
infiniti: {
|
|
49
49
|
name: "infiniti",
|
|
50
50
|
path: "/lottie/infiniti.json",
|
|
51
51
|
colorBind: true,
|
|
52
|
-
visualScale: 3.
|
|
52
|
+
visualScale: 3.2,
|
|
53
53
|
description: "무한 루프 선. 네트워크·동기화",
|
|
54
54
|
},
|
|
55
55
|
dots: {
|
|
56
56
|
name: "dots",
|
|
57
57
|
path: "/lottie/dots.json",
|
|
58
58
|
colorBind: true,
|
|
59
|
-
visualScale:
|
|
59
|
+
visualScale: 0.95,
|
|
60
60
|
offsetY: 0.025,
|
|
61
61
|
description: "그라데이션 dot 흐름. 포인트 로더",
|
|
62
62
|
},
|
|
@@ -64,35 +64,35 @@ export const LOTTIE_MANIFEST: Record<LottieName, LottieManifestEntry> = {
|
|
|
64
64
|
name: "spinner",
|
|
65
65
|
path: "/lottie/spinner.json",
|
|
66
66
|
colorBind: true,
|
|
67
|
-
visualScale: 2
|
|
67
|
+
visualScale: 2,
|
|
68
68
|
description: "표준 라인 스피너",
|
|
69
69
|
},
|
|
70
70
|
clock: {
|
|
71
71
|
name: "clock",
|
|
72
72
|
path: "/lottie/clock.json",
|
|
73
73
|
colorBind: true,
|
|
74
|
-
visualScale:
|
|
74
|
+
visualScale: 3.8,
|
|
75
75
|
description: "회전 시계 바늘. 처리 대기·타임아웃",
|
|
76
76
|
},
|
|
77
77
|
cosmos: {
|
|
78
78
|
name: "cosmos",
|
|
79
79
|
path: "/lottie/cosmos.json",
|
|
80
80
|
colorBind: true,
|
|
81
|
-
visualScale: 1.
|
|
81
|
+
visualScale: 1.2,
|
|
82
82
|
description: "그라데이션 궤도. 몽환적·온보딩",
|
|
83
83
|
},
|
|
84
84
|
"image-gen": {
|
|
85
85
|
name: "image-gen",
|
|
86
86
|
path: "/lottie/image-gen.json",
|
|
87
87
|
colorBind: true,
|
|
88
|
-
visualScale: 1.
|
|
88
|
+
visualScale: 1.1,
|
|
89
89
|
description: "이미지 생성 대기. AI 이미지",
|
|
90
90
|
},
|
|
91
91
|
"loading-square": {
|
|
92
92
|
name: "loading-square",
|
|
93
93
|
path: "/lottie/loading-square.json",
|
|
94
94
|
colorBind: true,
|
|
95
|
-
visualScale: 3.
|
|
95
|
+
visualScale: 3.5,
|
|
96
96
|
description: "회전하는 사각형. 미니멀 처리 대기",
|
|
97
97
|
},
|
|
98
98
|
};
|
|
@@ -16,6 +16,12 @@ export type SwitchProps = Omit<
|
|
|
16
16
|
"type" | "size"
|
|
17
17
|
> & {
|
|
18
18
|
size?: SwitchSize;
|
|
19
|
+
/**
|
|
20
|
+
* 토글 변경 콜백. 공식 API — 대부분의 경우 이걸 쓴다.
|
|
21
|
+
* 네이티브 `onChange`도 받아낼 수 있는데, 둘 다 쓰면 호출 순서는:
|
|
22
|
+
* 1) onChange(e) — 호출자가 e.preventDefault() 할 수 있는 escape hatch.
|
|
23
|
+
* 2) onChange가 preventDefault 하지 않으면 onCheckedChange(checked).
|
|
24
|
+
*/
|
|
19
25
|
onCheckedChange?: (checked: boolean) => void;
|
|
20
26
|
};
|
|
21
27
|
|
|
@@ -92,16 +92,18 @@
|
|
|
92
92
|
background: var(--bg);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
/* pinned 경계 — hairline으로 "고정 영역" 분리. TableRow와 짝.
|
|
95
|
+
/* pinned 경계 — hairline으로 "고정 영역" 분리. TableRow와 짝.
|
|
96
|
+
border 대신 inset box-shadow로 그어 sticky 레이어 subpixel 겹침(두 줄처럼
|
|
97
|
+
보이는 Chrome 렌더 현상) 회피 + 셀 폭에 영향 안 줌. */
|
|
96
98
|
.cellSlot[data-pinned="left"] {
|
|
97
|
-
|
|
99
|
+
box-shadow: inset calc(var(--hairline) * -1) 0 0 var(--border);
|
|
98
100
|
}
|
|
99
101
|
.cellSlot[data-pinned="right"] {
|
|
100
|
-
|
|
102
|
+
box-shadow: inset var(--hairline) 0 0 var(--border);
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
.selectCell {
|
|
104
|
-
|
|
106
|
+
box-shadow: inset calc(var(--hairline) * -1) 0 0 var(--border);
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
/* Cell (columnheader) ─────────────────────────────────────── */
|
|
@@ -210,6 +212,15 @@
|
|
|
210
212
|
display: none;
|
|
211
213
|
}
|
|
212
214
|
|
|
215
|
+
/* pinned 경계 hairline과 tick(cellSlot 좌측 세로선) 겹침 방지.
|
|
216
|
+
- pinned-left 셀의 바로 다음 cellSlot: pinned의 border-right와 다음 셀의
|
|
217
|
+
tick이 바로 붙어 두 줄처럼 보이는 현상 제거.
|
|
218
|
+
- pinned-right 셀 자신: border-left와 자기 tick이 같은 지점에 겹침. */
|
|
219
|
+
.cellSlot[data-pinned="left"] + .cellSlot .tick,
|
|
220
|
+
.cellSlot[data-pinned="right"] .tick {
|
|
221
|
+
display: none;
|
|
222
|
+
}
|
|
223
|
+
|
|
213
224
|
/* Resize handle ──────────────────────────────────────────────
|
|
214
225
|
히트 영역은 셀 우측 경계에 걸쳐 있는 투명 버튼. 시각 세로선은 안쪽 .resizeLine이
|
|
215
226
|
담당하고, 라인은 handle 폭 안에서 "우측 끝"이 아니라 "중앙"에 위치 →
|
|
@@ -217,11 +228,10 @@
|
|
|
217
228
|
.resizeHandle {
|
|
218
229
|
position: absolute;
|
|
219
230
|
top: 0;
|
|
220
|
-
/* handle 중심을
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
right: calc((var(--space-1) * -1) - (var(--space-3) * 0.5));
|
|
231
|
+
/* handle 중심을 셀 우측 경계(= 데이터 row의 세로 구분선)에 정확히 맞춤.
|
|
232
|
+
TableRow는 cellSlot 사이 column-gap을 쓰지 않으므로 추가 보정 없음.
|
|
233
|
+
right = -(handleWidth/2). */
|
|
234
|
+
right: calc(var(--space-3) * -0.5);
|
|
225
235
|
bottom: 0;
|
|
226
236
|
width: var(--space-3);
|
|
227
237
|
padding: 0;
|
|
@@ -241,36 +251,35 @@
|
|
|
241
251
|
border-radius: var(--radius-xs);
|
|
242
252
|
}
|
|
243
253
|
|
|
244
|
-
/* 시각 세로선 — plate에서만 hover 시 표시. line은 자체 tick으로 대체하므로 숨김.
|
|
245
|
-
색은 border-strong(살짝 밝은 hairline 톤) — fg-subtle은 너무 진해서 데이터 색과 경쟁함. */
|
|
246
254
|
/* 둥근 pill 핸들 — 선이 아니라 떠있는 그립 느낌.
|
|
247
|
-
|
|
255
|
+
흰(bg) 캡슐에 연한 hairline + 연한 그림자로 "집어들 수 있는" 어포던스.
|
|
256
|
+
hover 전엔 숨겨두고, 셀 hover 때 opacity로 올려 보여줌. */
|
|
248
257
|
.resizeLine {
|
|
249
258
|
display: block;
|
|
250
|
-
width: var(--space-1);
|
|
251
|
-
height:
|
|
252
|
-
background: var(--
|
|
259
|
+
width: calc(var(--space-1) * 1.25);
|
|
260
|
+
height: 48%;
|
|
261
|
+
background: var(--bg);
|
|
262
|
+
border: var(--hairline) solid var(--border);
|
|
253
263
|
border-radius: var(--radius-full);
|
|
254
264
|
opacity: 0;
|
|
255
265
|
box-shadow: var(--shadow-xs);
|
|
256
266
|
transition:
|
|
257
267
|
opacity var(--duration-fast) var(--ease-out),
|
|
258
|
-
background var(--duration-fast) var(--ease-out),
|
|
259
268
|
height var(--duration-fast) var(--ease-out),
|
|
260
269
|
box-shadow var(--duration-fast) var(--ease-out);
|
|
261
270
|
}
|
|
262
271
|
|
|
263
272
|
/* 셀 전체 hover: pill 연하게 노출. */
|
|
264
273
|
.cell:hover .resizeLine {
|
|
265
|
-
opacity: 0.
|
|
274
|
+
opacity: 0.7;
|
|
266
275
|
}
|
|
267
276
|
|
|
268
|
-
/* handle hover/active:
|
|
277
|
+
/* handle hover/active: 또렷하게 + 길어지고 그림자 깊어짐 — "집은 상태".
|
|
278
|
+
색/스트로크는 유지, 변하는 건 opacity·height·shadow 뿐. */
|
|
269
279
|
.resizeHandle:hover .resizeLine,
|
|
270
280
|
.resizeHandle:active .resizeLine {
|
|
271
281
|
opacity: 1;
|
|
272
|
-
|
|
273
|
-
height: 80%;
|
|
282
|
+
height: 64%;
|
|
274
283
|
box-shadow: var(--shadow-sm);
|
|
275
284
|
}
|
|
276
285
|
|
|
@@ -146,18 +146,20 @@ a.root:not(.disabled):focus-visible,
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/* pinned 경계 — scroll shadow 대신 상시 hairline으로 "여기까지 고정 영역"을 분명히.
|
|
149
|
-
좌측 pinned → 우측
|
|
149
|
+
좌측 pinned → 우측 경계, 우측 pinned → 좌측 경계.
|
|
150
|
+
border 대신 inset box-shadow로 그어 sticky 레이어 subpixel 겹침(두 줄처럼
|
|
151
|
+
보이는 Chrome 렌더 현상) 회피 + 셀 폭에 영향 안 줌. */
|
|
150
152
|
.cellSlot[data-pinned="left"] {
|
|
151
|
-
|
|
153
|
+
box-shadow: inset calc(var(--hairline) * -1) 0 0 var(--border);
|
|
152
154
|
}
|
|
153
155
|
.cellSlot[data-pinned="right"] {
|
|
154
|
-
|
|
156
|
+
box-shadow: inset var(--hairline) 0 0 var(--border);
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
/* selectable + pinned 조합일 때 selectCell이 "실질적 좌측 pinned의 선두" 역할이라
|
|
158
|
-
selectCell에도 우측
|
|
160
|
+
selectCell에도 우측 경계를 그어 톤 일치. */
|
|
159
161
|
.selectCell {
|
|
160
|
-
|
|
162
|
+
box-shadow: inset calc(var(--hairline) * -1) 0 0 var(--border);
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
/* ── Cell ───────────────────────────────────────────────
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
pointer-events: none;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
|
|
66
67
|
/* ── Edit cell ────────────────────────────────────────────
|
|
67
68
|
클릭/포커스 타겟. 활성 상태엔 input/select가 셀을 꽉 채움. */
|
|
68
69
|
.editCell {
|
|
@@ -102,11 +103,17 @@
|
|
|
102
103
|
left: 0;
|
|
103
104
|
right: 0;
|
|
104
105
|
/* 1줄 상태엔 셀 높이에 딱 맞도록 min-height: 100%. textarea autosize가
|
|
105
|
-
여러 줄로 늘어나면 host
|
|
106
|
+
여러 줄로 늘어나면 host 자체가 scroll 컨테이너가 되고(max-height 도달 시),
|
|
107
|
+
textarea 전체(자기 padding 포함)가 host 안에서 위로 올라감 → 상·하 padding이
|
|
108
|
+
scroll content의 일부라 host border에서 자연스레 clip, 안쪽 여백 항상 보존.
|
|
109
|
+
scroll.md "textarea 특수 패턴" 규약. padding은 textarea가 책임. */
|
|
106
110
|
min-height: 100%;
|
|
111
|
+
/* 3.5줄 × line-height + 상·하 padding(space-2 각각) — 그 이상은 host가 scroll. */
|
|
112
|
+
max-height: calc(var(--lh-sm) * 3.5 + var(--space-2) * 2);
|
|
107
113
|
display: flex;
|
|
108
|
-
align-items:
|
|
109
|
-
padding
|
|
114
|
+
align-items: stretch;
|
|
115
|
+
padding: 0;
|
|
116
|
+
overflow-y: auto;
|
|
110
117
|
background: var(--bg);
|
|
111
118
|
border: var(--hairline) solid var(--border-strong);
|
|
112
119
|
border-radius: var(--radius-sm);
|
|
@@ -179,8 +179,13 @@ function TableRowEditInner<T>(
|
|
|
179
179
|
/* 세로 구분선은 editable 여부와 무관하게 모든 셀 사이에 균일하게.
|
|
180
180
|
엑셀 톤 "셀 그리드" 신호는 일관성이 핵심 — read-only 셀만 선이 빠지면
|
|
181
181
|
리듬이 깨져 보임. 첫 데이터 셀은 왼쪽에 선 없음(테이블 외곽).
|
|
182
|
-
selectable이면 체크박스 ↔ 첫 데이터 셀 사이에도 선.
|
|
183
|
-
|
|
182
|
+
selectable이면 체크박스 ↔ 첫 데이터 셀 사이에도 선.
|
|
183
|
+
pinned 경계는 cellSlot box-shadow가 담당 — pinned-right 셀 자신과
|
|
184
|
+
pinned-left 바로 다음 셀의 왼쪽 divider는 그으면 두 줄로 겹쳐 보임. */
|
|
185
|
+
const prevCol = idx > 0 ? columnMap.get(orderedKeys[idx - 1]) : undefined;
|
|
186
|
+
const skipDivider =
|
|
187
|
+
col.pinned === "right" || prevCol?.pinned === "left";
|
|
188
|
+
const showLeftDivider = (idx > 0 || selectable) && !skipDivider;
|
|
184
189
|
return (
|
|
185
190
|
<div
|
|
186
191
|
key={col.key}
|
|
@@ -27,7 +27,12 @@ export interface ToastOptions {
|
|
|
27
27
|
tone?: ToastTone;
|
|
28
28
|
surface?: ToastSurface;
|
|
29
29
|
icon?: IconName;
|
|
30
|
+
/** 액션 요소(Button 등). `actionAltText`도 함께 넘겨야 렌더링된다 — 스크린리더용 라벨이 없으면 Radix가 action을 숨긴다. */
|
|
30
31
|
action?: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* action을 쓸 때 **필수** — 스크린리더가 action 버튼을 어떻게 안내할지. Toast 사라져도
|
|
34
|
+
* 사용자가 수행할 수 있는 대안 설명(예: "실행 취소", "설정 열기"). 생략 시 action 미렌더.
|
|
35
|
+
*/
|
|
31
36
|
actionAltText?: string;
|
|
32
37
|
dismissible?: boolean;
|
|
33
38
|
dismissLabel?: string;
|
|
@@ -227,6 +232,9 @@ export const Toast = forwardRef<HTMLLIElement, ToastProps>(function Toast(
|
|
|
227
232
|
{action}
|
|
228
233
|
</RadixToast.Action>
|
|
229
234
|
)}
|
|
235
|
+
{action && !actionAltText && typeof window !== "undefined" && process.env.NODE_ENV !== "production" && (
|
|
236
|
+
<ToastActionWarning />
|
|
237
|
+
)}
|
|
230
238
|
{dismissible && (
|
|
231
239
|
<RadixToast.Close asChild>
|
|
232
240
|
<IconButton
|
|
@@ -241,3 +249,14 @@ export const Toast = forwardRef<HTMLLIElement, ToastProps>(function Toast(
|
|
|
241
249
|
</RadixToast.Root>
|
|
242
250
|
);
|
|
243
251
|
});
|
|
252
|
+
|
|
253
|
+
/** dev-only. action은 주고 actionAltText를 빠뜨리면 Radix가 조용히 숨기므로 console로 경고. */
|
|
254
|
+
function ToastActionWarning() {
|
|
255
|
+
if (typeof window !== "undefined") {
|
|
256
|
+
// eslint-disable-next-line no-console
|
|
257
|
+
console.warn(
|
|
258
|
+
"[CTDS Toast] `action` prop을 넘겼는데 `actionAltText`가 비어있어요. 스크린리더용 라벨이 없으면 action이 렌더되지 않습니다.",
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
@@ -585,7 +585,6 @@ export const componentSlugs: Record<string, string> = {
|
|
|
585
585
|
"Table Row": "data/table-row",
|
|
586
586
|
"Table Row Select": "data/table-row-select",
|
|
587
587
|
"Table Row Edit": "data/table-row-edit",
|
|
588
|
-
Calendar: "data/calendar",
|
|
589
588
|
"Event Chip": "data/event-chip",
|
|
590
589
|
"List Item": "data/list-item",
|
|
591
590
|
"Metric Card": "data/metric-card",
|
|
@@ -595,7 +594,7 @@ export const componentSlugs: Record<string, string> = {
|
|
|
595
594
|
Spinner: "feedback/spinner",
|
|
596
595
|
"File Icon": "chat/file-icon",
|
|
597
596
|
"Attachment Thumb": "chat/attachment-thumb",
|
|
598
|
-
"Attachment
|
|
597
|
+
"Attachment Item": "data/attachment-item",
|
|
599
598
|
"Quotation Bar": "chat/quotation-bar",
|
|
600
599
|
"Chat Input": "chat/chat-input",
|
|
601
600
|
"Bubble": "chat/bubble",
|
|
@@ -607,14 +606,13 @@ export const componentSlugs: Record<string, string> = {
|
|
|
607
606
|
"Message Sources": "chat/message-sources",
|
|
608
607
|
"Message Table": "chat/message-table",
|
|
609
608
|
"Message State": "chat/message-state",
|
|
610
|
-
"
|
|
609
|
+
"Generated Image": "chat/generated-image",
|
|
611
610
|
};
|
|
612
611
|
|
|
613
|
-
/** 작업중 — 사이드바에서
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
]);
|
|
612
|
+
/** 작업중 — 사이드바에서 "작업중" 뱃지로 표시되고,
|
|
613
|
+
* 외부에서 import하면 pre-commit 훅(scripts/lint-wip.mjs)이 차단한다.
|
|
614
|
+
* 문서 라우트(web/app/components/**)와 컴포넌트 자신의 폴더는 허용. */
|
|
615
|
+
export const componentsInProgress: Set<string> = new Set([]);
|
|
618
616
|
|
|
619
617
|
export const components: { id: string; title: string; items: string[] }[] = [
|
|
620
618
|
{
|
|
@@ -623,6 +621,7 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
623
621
|
items: [
|
|
624
622
|
"Button", "Split Button", "Icon Button", "Menu Item", "Toolbar", "Badge", "Avatar",
|
|
625
623
|
"Tooltip", "Shortcut", "Filter Button", "Chip", "Separator", "Skeleton", "Aspect Ratio",
|
|
624
|
+
"Code Block",
|
|
626
625
|
],
|
|
627
626
|
},
|
|
628
627
|
{
|
|
@@ -632,7 +631,7 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
632
631
|
"Input", "Input OTP", "Textarea", "Select", "Combobox", "Multi-Select",
|
|
633
632
|
"Checkbox", "Radio Group", "Selectable Card", "Choice Card", "Switch", "Slider",
|
|
634
633
|
"Date Picker", "Time Picker",
|
|
635
|
-
"File Upload",
|
|
634
|
+
"File Upload",
|
|
636
635
|
],
|
|
637
636
|
},
|
|
638
637
|
{
|
|
@@ -640,7 +639,7 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
640
639
|
title: "Data Display",
|
|
641
640
|
items: [
|
|
642
641
|
"Table Header", "Table Row", "Table Row Select", "Table Row Edit",
|
|
643
|
-
"List Item", "Metric Card", "Gallery Item", "
|
|
642
|
+
"List Item", "Metric Card", "Gallery Item", "Attachment Item", "Event Chip",
|
|
644
643
|
"Empty State", "Description List", "Timeline",
|
|
645
644
|
],
|
|
646
645
|
},
|
|
@@ -669,13 +668,37 @@ export const components: { id: string; title: string; items: string[] }[] = [
|
|
|
669
668
|
],
|
|
670
669
|
},
|
|
671
670
|
{
|
|
672
|
-
id: "chat",
|
|
673
|
-
title: "AI Chat",
|
|
671
|
+
id: "chat-input",
|
|
672
|
+
title: "AI Chat / 입력",
|
|
673
|
+
items: [
|
|
674
|
+
"Chat Input", "Quotation Bar",
|
|
675
|
+
],
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
id: "chat-attachment",
|
|
679
|
+
title: "AI Chat / 첨부",
|
|
680
|
+
items: [
|
|
681
|
+
"File Icon", "Attachment Thumb", "Generated Image",
|
|
682
|
+
],
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
id: "chat-message",
|
|
686
|
+
title: "AI Chat / 메시지",
|
|
687
|
+
items: [
|
|
688
|
+
"Bubble", "User Message", "AI Message", "Message State", "Message Actions",
|
|
689
|
+
],
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
id: "chat-content",
|
|
693
|
+
title: "AI Chat / 메시지 콘텐츠",
|
|
694
|
+
items: [
|
|
695
|
+
"Message Quote", "Message Sources", "Message Table",
|
|
696
|
+
],
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
id: "chat-shell",
|
|
700
|
+
title: "AI Chat / 쉘",
|
|
674
701
|
items: [
|
|
675
|
-
"File Icon", "Attachment Thumb", "Attachment Chip", "Quotation Bar", "Chat Input",
|
|
676
|
-
"Bubble", "Message Actions", "User Message", "AI Message",
|
|
677
|
-
"Code Block", "Message Quote", "Message Sources", "Message Table", "Message State",
|
|
678
|
-
"Message Attachment",
|
|
679
702
|
"Chat Sidebar",
|
|
680
703
|
],
|
|
681
704
|
},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
CTDS app-shell — opt-in. AppFrame(100dvh overflow:hidden)을 쓰는
|
|
3
|
+
"앱 셸" 레이아웃 전제 스타일. 소비자 프로젝트가 일반 body-scroll
|
|
4
|
+
페이지라면 이 파일을 import하지 않는다. 데모 앱(web/app/globals.css)
|
|
5
|
+
과 ctds-cli init이 복사하는 앱 템플릿만 사용.
|
|
6
|
+
|
|
7
|
+
app-scroll 패러다임: AppFrame이 뷰포트 100dvh를 꽉 채우고 자기 overflow:hidden,
|
|
8
|
+
스크롤은 AppFrame 안의 .contentSlot만 담당 — html/body는 스크롤할 필요가 없다.
|
|
9
|
+
overflow:hidden으로 바깥 러버밴드/스크롤 체이닝 자체를 차단.
|
|
10
|
+
============================================================ */
|
|
11
|
+
|
|
12
|
+
html,
|
|
13
|
+
body {
|
|
14
|
+
height: 100%;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
overscroll-behavior: none;
|
|
17
|
+
}
|