@conscience-technology/ctds 0.1.7 → 0.1.8
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/ChatInput/ChatInput.module.css +6 -1
- package/assets/components/ChatInput/ChatInput.tsx +9 -9
- package/assets/components/TableHeader/TableHeader.module.css +12 -0
- package/assets/components/TableHeader/TableHeader.tsx +22 -3
- package/assets/components/TableRow/TableRow.module.css +15 -0
- package/assets/components/TableRow/TableRow.tsx +24 -2
- package/assets/components/_data.ts +1 -1
- package/package.json +1 -1
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
align-items: center;
|
|
125
125
|
justify-content: space-between;
|
|
126
126
|
gap: var(--space-1);
|
|
127
|
-
padding: 0 var(--space-
|
|
127
|
+
padding: 0 var(--space-2) var(--space-2);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/* SingleLine */
|
|
@@ -141,6 +141,8 @@
|
|
|
141
141
|
background: transparent;
|
|
142
142
|
border: 0;
|
|
143
143
|
outline: none;
|
|
144
|
+
resize: none;
|
|
145
|
+
overflow: hidden;
|
|
144
146
|
color: var(--fg-strong);
|
|
145
147
|
font: inherit;
|
|
146
148
|
font-size: var(--text-sm);
|
|
@@ -149,6 +151,9 @@
|
|
|
149
151
|
.singleField::placeholder {
|
|
150
152
|
color: var(--fg-muted);
|
|
151
153
|
}
|
|
154
|
+
.singleField::placeholder {
|
|
155
|
+
color: var(--fg-muted);
|
|
156
|
+
}
|
|
152
157
|
|
|
153
158
|
/* Attach 버튼 */
|
|
154
159
|
.attachBtn {
|
|
@@ -108,11 +108,10 @@ export const ChatInput = forwardRef<HTMLDivElement, ChatInputProps>(
|
|
|
108
108
|
const canSend = !disabled && (value.trim().length > 0 || attachments.length > 0);
|
|
109
109
|
const showStop = isStreaming && !!onStop;
|
|
110
110
|
|
|
111
|
-
/* autoResize
|
|
112
|
-
외부 scroll wrapper가 max-height
|
|
113
|
-
|
|
111
|
+
/* autoResize — multi/single 둘 다. textarea는 content에 맞춰 자람(scroll 아님).
|
|
112
|
+
외부 scroll wrapper(multi)가 max-height 책임. single은 wrapper 없이 textarea가 직접 자람.
|
|
113
|
+
Shift+Enter로 줄바꿈 → textarea가 즉시 자라고, SingleLine도 자연스레 늘어남. */
|
|
114
114
|
useLayoutEffect(() => {
|
|
115
|
-
if (shape !== "multi") return;
|
|
116
115
|
const el = taRef.current;
|
|
117
116
|
if (!el) return;
|
|
118
117
|
el.style.height = "auto";
|
|
@@ -122,8 +121,8 @@ export const ChatInput = forwardRef<HTMLDivElement, ChatInputProps>(
|
|
|
122
121
|
const handleKeyDown = useCallback(
|
|
123
122
|
(e: KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
|
124
123
|
if (e.key !== "Enter") return;
|
|
125
|
-
if (e.shiftKey) return; //
|
|
126
|
-
if (
|
|
124
|
+
if (e.shiftKey) return; // Shift+Enter: multi/single 모두 줄바꿈 허용
|
|
125
|
+
if (e.nativeEvent.isComposing) return; // 한글 조합 중 전송 방지
|
|
127
126
|
e.preventDefault();
|
|
128
127
|
if (showStop) {
|
|
129
128
|
onStop?.();
|
|
@@ -131,7 +130,7 @@ export const ChatInput = forwardRef<HTMLDivElement, ChatInputProps>(
|
|
|
131
130
|
onSend();
|
|
132
131
|
}
|
|
133
132
|
},
|
|
134
|
-
[
|
|
133
|
+
[canSend, showStop, onSend, onStop]
|
|
135
134
|
);
|
|
136
135
|
|
|
137
136
|
const handleSendClick = useCallback(() => {
|
|
@@ -277,14 +276,15 @@ export const ChatInput = forwardRef<HTMLDivElement, ChatInputProps>(
|
|
|
277
276
|
{shape === "single" ? (
|
|
278
277
|
<>
|
|
279
278
|
{AttachButton}
|
|
280
|
-
<
|
|
281
|
-
|
|
279
|
+
<textarea
|
|
280
|
+
ref={taRef}
|
|
282
281
|
className={styles.singleField}
|
|
283
282
|
value={value}
|
|
284
283
|
onChange={(e) => onChange(e.target.value)}
|
|
285
284
|
onKeyDown={handleKeyDown}
|
|
286
285
|
placeholder={placeholder}
|
|
287
286
|
disabled={disabled}
|
|
287
|
+
rows={1}
|
|
288
288
|
aria-label={placeholder}
|
|
289
289
|
/>
|
|
290
290
|
{ModelAndSend}
|
|
@@ -92,6 +92,18 @@
|
|
|
92
92
|
background: var(--bg);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/* pinned 경계 — hairline으로 "고정 영역" 분리. TableRow와 짝. */
|
|
96
|
+
.cellSlot[data-pinned="left"] {
|
|
97
|
+
border-right: var(--hairline) solid var(--border);
|
|
98
|
+
}
|
|
99
|
+
.cellSlot[data-pinned="right"] {
|
|
100
|
+
border-left: var(--hairline) solid var(--border);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.selectCell {
|
|
104
|
+
border-right: var(--hairline) solid var(--border);
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
/* Cell (columnheader) ─────────────────────────────────────── */
|
|
96
108
|
/* width:100%로 슬롯 폭을 그대로 채워 라벨/tick/handle 정렬이 슬롯 경계에 맞춰 계산됨.
|
|
97
109
|
padding-inline은 hover/drag hit 영역이 콘텐츠에 바짝 붙지 않도록 필수.
|
|
@@ -394,6 +394,25 @@ const TableHeader = forwardRef<HTMLDivElement, TableHeaderProps>(function TableH
|
|
|
394
394
|
.map((k) => columnMap.get(k))
|
|
395
395
|
.filter((c): c is TableHeaderColumn => !!c);
|
|
396
396
|
|
|
397
|
+
/* selectCell이 렌더되면 그 실제 폭을 측정해 좌측 pinned columns의 left offset에
|
|
398
|
+
더해준다. CSS 토큰(--iconbtn-h-lg / --control-h-sm) + space-scale 반영이 자동으로
|
|
399
|
+
흡수되므로 하드코딩이나 CSS 변수 파싱 없이 처리 가능. */
|
|
400
|
+
const selectCellRef = useRef<HTMLDivElement>(null);
|
|
401
|
+
const [selectCellWidth, setSelectCellWidth] = useState(0);
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
if (!selectable) {
|
|
404
|
+
setSelectCellWidth(0);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const el = selectCellRef.current;
|
|
408
|
+
if (!el) return;
|
|
409
|
+
const update = () => setSelectCellWidth(el.getBoundingClientRect().width);
|
|
410
|
+
update();
|
|
411
|
+
const ro = new ResizeObserver(update);
|
|
412
|
+
ro.observe(el);
|
|
413
|
+
return () => ro.disconnect();
|
|
414
|
+
}, [selectable]);
|
|
415
|
+
|
|
397
416
|
/* Sort ─────────────────────────────────────────────────── */
|
|
398
417
|
const toggleSort = useCallback(
|
|
399
418
|
(key: string) => {
|
|
@@ -497,7 +516,7 @@ const TableHeader = forwardRef<HTMLDivElement, TableHeaderProps>(function TableH
|
|
|
497
516
|
const cells = (
|
|
498
517
|
<>
|
|
499
518
|
{selectable && (
|
|
500
|
-
<div className={styles.selectCell} role="columnheader">
|
|
519
|
+
<div ref={selectCellRef} className={styles.selectCell} role="columnheader">
|
|
501
520
|
<Checkbox
|
|
502
521
|
checked={selectAllChecked}
|
|
503
522
|
onChange={(e) => onSelectAllChange?.(e.currentTarget.checked)}
|
|
@@ -524,7 +543,7 @@ const TableHeader = forwardRef<HTMLDivElement, TableHeaderProps>(function TableH
|
|
|
524
543
|
className={styles.cellSlot}
|
|
525
544
|
style={{
|
|
526
545
|
...cellSlotStyle(col, widths[col.key]),
|
|
527
|
-
...pinnedStyle(col, order, columnMap, widths),
|
|
546
|
+
...pinnedStyle(col, order, columnMap, widths, selectCellWidth),
|
|
528
547
|
}}
|
|
529
548
|
>
|
|
530
549
|
<Cell
|
|
@@ -551,7 +570,7 @@ const TableHeader = forwardRef<HTMLDivElement, TableHeaderProps>(function TableH
|
|
|
551
570
|
className={styles.cellSlot}
|
|
552
571
|
style={{
|
|
553
572
|
...cellSlotStyle(col, widths[col.key]),
|
|
554
|
-
...pinnedStyle(col, order, columnMap, widths),
|
|
573
|
+
...pinnedStyle(col, order, columnMap, widths, selectCellWidth),
|
|
555
574
|
}}
|
|
556
575
|
>
|
|
557
576
|
<Cell
|
|
@@ -145,6 +145,21 @@ a.root:not(.disabled):focus-visible,
|
|
|
145
145
|
contain: none;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/* pinned 경계 — scroll shadow 대신 상시 hairline으로 "여기까지 고정 영역"을 분명히.
|
|
149
|
+
좌측 pinned → 우측 경계에 세로 hairline. 우측 pinned → 좌측 경계. */
|
|
150
|
+
.cellSlot[data-pinned="left"] {
|
|
151
|
+
border-right: var(--hairline) solid var(--border);
|
|
152
|
+
}
|
|
153
|
+
.cellSlot[data-pinned="right"] {
|
|
154
|
+
border-left: var(--hairline) solid var(--border);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* selectable + pinned 조합일 때 selectCell이 "실질적 좌측 pinned의 선두" 역할이라
|
|
158
|
+
selectCell에도 우측 hairline을 그어 경계 톤 일치. */
|
|
159
|
+
.selectCell {
|
|
160
|
+
border-right: var(--hairline) solid var(--border);
|
|
161
|
+
}
|
|
162
|
+
|
|
148
163
|
/* ── Cell ───────────────────────────────────────────────
|
|
149
164
|
Header의 .cell과 1:1 동일. slot 안쪽 width:100%로 가득 채우는 콘텐츠 그릇.
|
|
150
165
|
display:inline-flex 는 Header 그대로 따라감 — flex vs inline-flex 차이가
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
forwardRef,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
5
8
|
type AnchorHTMLAttributes,
|
|
6
9
|
type CSSProperties,
|
|
7
10
|
type HTMLAttributes,
|
|
@@ -143,6 +146,25 @@ function TableRowInner<T>(
|
|
|
143
146
|
const orderedKeys = resolveOrder(columns, order);
|
|
144
147
|
const columnMap = new Map(columns.map((c) => [c.key, c]));
|
|
145
148
|
|
|
149
|
+
/* selectCell 폭을 측정해 좌측 pinned column의 left offset에 반영.
|
|
150
|
+
Header와 동일한 로직 — selectCell이 sticky left:0이라 pinned column들이 그 뒤에
|
|
151
|
+
위치하려면 폭만큼 밀어야 함. */
|
|
152
|
+
const selectCellRef = useRef<HTMLDivElement>(null);
|
|
153
|
+
const [selectCellWidth, setSelectCellWidth] = useState(0);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!selectable) {
|
|
156
|
+
setSelectCellWidth(0);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const el = selectCellRef.current;
|
|
160
|
+
if (!el) return;
|
|
161
|
+
const update = () => setSelectCellWidth(el.getBoundingClientRect().width);
|
|
162
|
+
update();
|
|
163
|
+
const ro = new ResizeObserver(update);
|
|
164
|
+
ro.observe(el);
|
|
165
|
+
return () => ro.disconnect();
|
|
166
|
+
}, [selectable]);
|
|
167
|
+
|
|
146
168
|
const rootClass = [
|
|
147
169
|
styles.root,
|
|
148
170
|
variant === "plate" ? styles.plate : styles.line,
|
|
@@ -163,7 +185,7 @@ function TableRowInner<T>(
|
|
|
163
185
|
const cells = (
|
|
164
186
|
<>
|
|
165
187
|
{selectable && (
|
|
166
|
-
<div className={styles.selectCell} onClick={stop}>
|
|
188
|
+
<div ref={selectCellRef} className={styles.selectCell} onClick={stop}>
|
|
167
189
|
<Checkbox
|
|
168
190
|
checked={selected}
|
|
169
191
|
disabled={disabled}
|
|
@@ -184,7 +206,7 @@ function TableRowInner<T>(
|
|
|
184
206
|
minWidth: toCssSize(col.minWidth) ?? cssWidth,
|
|
185
207
|
flex: col.flex ? "1 1 0" : undefined,
|
|
186
208
|
justifyContent: alignToJustify(col.align),
|
|
187
|
-
...pinnedStyle(col, orderedKeys, columnMap, widths ?? {}),
|
|
209
|
+
...pinnedStyle(col, orderedKeys, columnMap, widths ?? {}, selectCellWidth),
|
|
188
210
|
};
|
|
189
211
|
const content = col.render ? col.render(data) : (data as Record<string, ReactNode>)?.[col.key];
|
|
190
212
|
return (
|