@ncds/ui-admin 1.8.11 → 1.8.12
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/dist/cjs/src/components/data-display/data-grid/DataGrid.js +3 -1
- package/dist/cjs/src/components/data-display/table/Table.js +326 -75
- package/dist/cjs/src/components/data-display/table/dnd-context.js +15 -0
- package/dist/cjs/src/components/data-display/table/dnd-preview.js +127 -0
- package/dist/esm/src/components/data-display/data-grid/DataGrid.js +3 -1
- package/dist/esm/src/components/data-display/table/Table.js +328 -77
- package/dist/esm/src/components/data-display/table/dnd-context.js +10 -0
- package/dist/esm/src/components/data-display/table/dnd-preview.js +121 -0
- package/dist/temp/src/components/data-display/data-grid/DataGrid.js +1 -1
- package/dist/temp/src/components/data-display/data-grid/DataGrid.types.d.ts +5 -0
- package/dist/temp/src/components/data-display/table/Table.d.ts +14 -4
- package/dist/temp/src/components/data-display/table/Table.js +172 -14
- package/dist/temp/src/components/data-display/table/dnd-context.d.ts +12 -0
- package/dist/temp/src/components/data-display/table/dnd-context.js +10 -0
- package/dist/temp/src/components/data-display/table/dnd-preview.d.ts +6 -0
- package/dist/temp/src/components/data-display/table/dnd-preview.js +120 -0
- package/dist/temp/src/components/data-display/table/types.d.ts +24 -1
- package/dist/types/src/components/data-display/data-grid/DataGrid.types.d.ts +5 -0
- package/dist/types/src/components/data-display/table/Table.d.ts +14 -4
- package/dist/types/src/components/data-display/table/dnd-context.d.ts +12 -0
- package/dist/types/src/components/data-display/table/dnd-preview.d.ts +6 -0
- package/dist/types/src/components/data-display/table/types.d.ts +24 -1
- package/dist/ui-admin/assets/styles/style.css +166 -2
- package/package.json +2 -2
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { dropTargetForElements, draggable as makeDraggable, monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
3
|
+
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
|
|
4
|
+
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
|
|
5
|
+
import { attachClosestEdge, extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
6
|
+
import { ChevronDown, ChevronSelectorVertical, ChevronUp, DotsGrid02 } from '@ncds/ui-admin-icon';
|
|
3
7
|
import classNames from 'classnames';
|
|
4
|
-
import { Children, forwardRef, useRef } from 'react';
|
|
8
|
+
import { Children, forwardRef, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
5
9
|
import { FloatingProvider } from '../../../contexts/FloatingContext';
|
|
10
|
+
import { RowDndContext, TableDndContext } from './dnd-context';
|
|
11
|
+
import { buildDragPreview, PREVIEW_BADGE_SIZE, TABLE_DND_ID_RADIX, TABLE_DND_ID_SLICE_END, TABLE_DND_ID_SLICE_START } from './dnd-preview';
|
|
6
12
|
import { TABLE_HEADER_HEIGHT, useTableHorizontalScrollbar, useTableVerticalScrollbar } from './useTableScrollbars';
|
|
7
13
|
// 가로 스크롤 디자인 기준 폭 — 14인치 모니터 + LNB 고려한 디자인 권장 너비
|
|
8
14
|
const DEFAULT_HORIZONTAL_SCROLL_MIN_WIDTH = 1140;
|
|
@@ -43,35 +49,254 @@ Header.displayName = 'Table.Header';
|
|
|
43
49
|
const Body = _ref2 => {
|
|
44
50
|
let {
|
|
45
51
|
children,
|
|
46
|
-
className
|
|
52
|
+
className,
|
|
53
|
+
onRowDrop
|
|
47
54
|
} = _ref2;
|
|
55
|
+
const {
|
|
56
|
+
tableId
|
|
57
|
+
} = useContext(TableDndContext);
|
|
58
|
+
// onRowDrop 참조를 ref로 유지 — 콜백이 매 렌더마다 재생성되어도 effect가 재구독하지 않도록
|
|
59
|
+
const onRowDropRef = useRef(onRowDrop);
|
|
60
|
+
onRowDropRef.current = onRowDrop;
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
return monitorForElements({
|
|
63
|
+
canMonitor: _ref3 => {
|
|
64
|
+
let {
|
|
65
|
+
source
|
|
66
|
+
} = _ref3;
|
|
67
|
+
return source.data.tableId === tableId;
|
|
68
|
+
},
|
|
69
|
+
onDrop: _ref4 => {
|
|
70
|
+
let {
|
|
71
|
+
source,
|
|
72
|
+
location
|
|
73
|
+
} = _ref4;
|
|
74
|
+
if (!onRowDropRef.current) return;
|
|
75
|
+
const target = location.current.dropTargets[0];
|
|
76
|
+
if (!target) return;
|
|
77
|
+
const fromId = source.data.id;
|
|
78
|
+
const toId = target.data.id;
|
|
79
|
+
if (typeof fromId !== 'string' || typeof toId !== 'string' || fromId === toId) return;
|
|
80
|
+
const edge = extractClosestEdge(target.data);
|
|
81
|
+
onRowDropRef.current(fromId, toId, edge === 'bottom' ? 'bottom' : 'top');
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}, [tableId]);
|
|
48
85
|
return _jsx("tbody", {
|
|
49
86
|
className: classNames('ncua-table__body', className),
|
|
50
87
|
children: children
|
|
51
88
|
});
|
|
52
89
|
};
|
|
53
90
|
Body.displayName = 'Table.Body';
|
|
54
|
-
const Row = /*#__PURE__*/forwardRef((
|
|
91
|
+
const Row = /*#__PURE__*/forwardRef((_ref5, forwardedRef) => {
|
|
55
92
|
let {
|
|
56
93
|
children,
|
|
57
94
|
className,
|
|
58
95
|
selected,
|
|
59
96
|
status,
|
|
97
|
+
dragId,
|
|
60
98
|
...rest
|
|
61
|
-
} =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
} = _ref5;
|
|
100
|
+
const {
|
|
101
|
+
isDraggable,
|
|
102
|
+
tableId
|
|
103
|
+
} = useContext(TableDndContext);
|
|
104
|
+
const internalRef = useRef(null);
|
|
105
|
+
const [closestEdge, setClosestEdge] = useState(null);
|
|
106
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
107
|
+
const setRef = useCallback(el => {
|
|
108
|
+
internalRef.current = el;
|
|
109
|
+
if (typeof forwardedRef === 'function') {
|
|
110
|
+
forwardedRef(el);
|
|
111
|
+
} else if (forwardedRef) {
|
|
112
|
+
forwardedRef.current = el;
|
|
113
|
+
}
|
|
114
|
+
}, [forwardedRef]);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!isDraggable || !dragId || !internalRef.current) return;
|
|
117
|
+
return dropTargetForElements({
|
|
118
|
+
element: internalRef.current,
|
|
119
|
+
getData: _ref6 => {
|
|
120
|
+
let {
|
|
121
|
+
input,
|
|
122
|
+
element
|
|
123
|
+
} = _ref6;
|
|
124
|
+
return attachClosestEdge({
|
|
125
|
+
id: dragId,
|
|
126
|
+
tableId
|
|
127
|
+
}, {
|
|
128
|
+
input,
|
|
129
|
+
element,
|
|
130
|
+
allowedEdges: ['top', 'bottom']
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
onDragEnter: _ref7 => {
|
|
134
|
+
let {
|
|
135
|
+
self,
|
|
136
|
+
source
|
|
137
|
+
} = _ref7;
|
|
138
|
+
if (source.data.id === dragId) return;
|
|
139
|
+
const edge = extractClosestEdge(self.data);
|
|
140
|
+
setClosestEdge(edge === 'bottom' && internalRef.current?.nextElementSibling ? null : edge);
|
|
141
|
+
},
|
|
142
|
+
onDrag: _ref8 => {
|
|
143
|
+
let {
|
|
144
|
+
self,
|
|
145
|
+
source
|
|
146
|
+
} = _ref8;
|
|
147
|
+
if (source.data.id === dragId) return;
|
|
148
|
+
const edge = extractClosestEdge(self.data);
|
|
149
|
+
setClosestEdge(edge === 'bottom' && internalRef.current?.nextElementSibling ? null : edge);
|
|
150
|
+
},
|
|
151
|
+
onDragLeave: () => setClosestEdge(null),
|
|
152
|
+
onDrop: () => setClosestEdge(null)
|
|
153
|
+
});
|
|
154
|
+
}, [isDraggable, dragId, tableId]);
|
|
155
|
+
return _jsx(RowDndContext.Provider, {
|
|
156
|
+
value: {
|
|
157
|
+
dragId,
|
|
158
|
+
setIsDragging
|
|
159
|
+
},
|
|
160
|
+
children: _jsx("tr", {
|
|
161
|
+
ref: setRef,
|
|
162
|
+
"data-draggable-id": dragId,
|
|
163
|
+
className: classNames('ncua-table__row', className, {
|
|
164
|
+
'ncua-table__row--selected': selected,
|
|
165
|
+
'ncua-table__row--warning': status === 'warning',
|
|
166
|
+
'ncua-table__row--error': status === 'error',
|
|
167
|
+
'ncua-table__row--drag-over-top': closestEdge === 'top',
|
|
168
|
+
'ncua-table__row--drag-over-bottom': closestEdge === 'bottom',
|
|
169
|
+
'is-dragging': isDragging
|
|
170
|
+
}),
|
|
171
|
+
...rest,
|
|
172
|
+
children: children
|
|
173
|
+
})
|
|
71
174
|
});
|
|
72
175
|
});
|
|
73
176
|
Row.displayName = 'Table.Row';
|
|
74
|
-
const
|
|
177
|
+
const DragHeaderCell = _ref9 => {
|
|
178
|
+
let {
|
|
179
|
+
className,
|
|
180
|
+
children
|
|
181
|
+
} = _ref9;
|
|
182
|
+
return _jsx("th", {
|
|
183
|
+
className: classNames('ncua-table__header-cell', 'ncua-table__drag-header-cell', className),
|
|
184
|
+
children: _jsxs("span", {
|
|
185
|
+
className: "ncua-table__drag-cell-inner",
|
|
186
|
+
children: [_jsx("span", {
|
|
187
|
+
className: "ncua-table__drag-header-icon",
|
|
188
|
+
"aria-hidden": "true",
|
|
189
|
+
children: _jsx(DotsGrid02, {
|
|
190
|
+
width: 16,
|
|
191
|
+
height: 16
|
|
192
|
+
})
|
|
193
|
+
}), children]
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
DragHeaderCell.displayName = 'Table.DragHeaderCell';
|
|
198
|
+
const DragCell = _ref0 => {
|
|
199
|
+
let {
|
|
200
|
+
disabled,
|
|
201
|
+
className,
|
|
202
|
+
children
|
|
203
|
+
} = _ref0;
|
|
204
|
+
const {
|
|
205
|
+
isDraggable,
|
|
206
|
+
tableId
|
|
207
|
+
} = useContext(TableDndContext);
|
|
208
|
+
const {
|
|
209
|
+
dragId,
|
|
210
|
+
setIsDragging
|
|
211
|
+
} = useContext(RowDndContext);
|
|
212
|
+
const handleRef = useRef(null);
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (!isDraggable || !dragId || !handleRef.current || disabled) return;
|
|
215
|
+
let overlay = null;
|
|
216
|
+
let removePointerMove = null;
|
|
217
|
+
let pendingPreview = null;
|
|
218
|
+
return makeDraggable({
|
|
219
|
+
element: handleRef.current,
|
|
220
|
+
getInitialData: () => ({
|
|
221
|
+
id: dragId,
|
|
222
|
+
tableId
|
|
223
|
+
}),
|
|
224
|
+
onGenerateDragPreview: _ref1 => {
|
|
225
|
+
let {
|
|
226
|
+
nativeSetDragImage,
|
|
227
|
+
location
|
|
228
|
+
} = _ref1;
|
|
229
|
+
if (!handleRef.current) return;
|
|
230
|
+
const rowEl = handleRef.current.closest('tr');
|
|
231
|
+
if (!rowEl) return;
|
|
232
|
+
disableNativeDragPreview({
|
|
233
|
+
nativeSetDragImage
|
|
234
|
+
});
|
|
235
|
+
const rowRect = rowEl.getBoundingClientRect();
|
|
236
|
+
const badgeHalf = PREVIEW_BADGE_SIZE / 2;
|
|
237
|
+
const offsetX = Math.round(location.initial.input.clientX - rowRect.left) + badgeHalf;
|
|
238
|
+
const offsetY = Math.round(location.initial.input.clientY - rowRect.top) + badgeHalf;
|
|
239
|
+
const isSelected = rowEl.classList.contains('ncua-table__row--selected');
|
|
240
|
+
const bodyEl = rowEl.closest('.ncua-table__body');
|
|
241
|
+
const selectedRows = isSelected && bodyEl ? Array.from(bodyEl.querySelectorAll('.ncua-table__row--selected')) : [];
|
|
242
|
+
pendingPreview = {
|
|
243
|
+
rows: selectedRows.length > 1 ? selectedRows : [rowEl],
|
|
244
|
+
rowRect,
|
|
245
|
+
offsetX,
|
|
246
|
+
offsetY
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
onDragStart: _ref10 => {
|
|
250
|
+
let {
|
|
251
|
+
location
|
|
252
|
+
} = _ref10;
|
|
253
|
+
setIsDragging(true);
|
|
254
|
+
if (!pendingPreview) return;
|
|
255
|
+
const {
|
|
256
|
+
rows,
|
|
257
|
+
rowRect,
|
|
258
|
+
offsetX,
|
|
259
|
+
offsetY
|
|
260
|
+
} = pendingPreview;
|
|
261
|
+
overlay = buildDragPreview(rows, rowRect);
|
|
262
|
+
overlay.style.left = `${Math.round(location.initial.input.clientX) - offsetX}px`;
|
|
263
|
+
overlay.style.top = `${Math.round(location.initial.input.clientY) - offsetY}px`;
|
|
264
|
+
document.body.appendChild(overlay);
|
|
265
|
+
const onDragOver = e => {
|
|
266
|
+
if (!overlay) return;
|
|
267
|
+
overlay.style.left = `${e.clientX - offsetX}px`;
|
|
268
|
+
overlay.style.top = `${e.clientY - offsetY}px`;
|
|
269
|
+
};
|
|
270
|
+
document.addEventListener('dragover', onDragOver);
|
|
271
|
+
removePointerMove = () => document.removeEventListener('dragover', onDragOver);
|
|
272
|
+
},
|
|
273
|
+
onDrop: () => {
|
|
274
|
+
setIsDragging(false);
|
|
275
|
+
removePointerMove?.();
|
|
276
|
+
removePointerMove = null;
|
|
277
|
+
overlay?.remove();
|
|
278
|
+
overlay = null;
|
|
279
|
+
pendingPreview = null;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}, [isDraggable, dragId, tableId, disabled, setIsDragging]);
|
|
283
|
+
return _jsx("td", {
|
|
284
|
+
className: classNames('ncua-table__drag-cell', className),
|
|
285
|
+
children: _jsxs("span", {
|
|
286
|
+
className: "ncua-table__drag-cell-inner",
|
|
287
|
+
children: [isDraggable && _jsx("button", {
|
|
288
|
+
ref: handleRef,
|
|
289
|
+
type: "button",
|
|
290
|
+
className: "ncua-table__drag-handle",
|
|
291
|
+
"aria-label": "\uD589 \uC21C\uC11C \uBCC0\uACBD",
|
|
292
|
+
disabled: disabled,
|
|
293
|
+
children: _jsx(DotsGrid02, {})
|
|
294
|
+
}), children]
|
|
295
|
+
})
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
DragCell.displayName = 'Table.DragCell';
|
|
299
|
+
const HeaderCell = /*#__PURE__*/forwardRef((_ref11, ref) => {
|
|
75
300
|
let {
|
|
76
301
|
children,
|
|
77
302
|
className,
|
|
@@ -81,7 +306,7 @@ const HeaderCell = /*#__PURE__*/forwardRef((_ref4, ref) => {
|
|
|
81
306
|
minWidth,
|
|
82
307
|
style,
|
|
83
308
|
...rest
|
|
84
|
-
} =
|
|
309
|
+
} = _ref11;
|
|
85
310
|
const isSortable = sortDirection !== undefined && onSort !== undefined;
|
|
86
311
|
const SortIcon = isSortable ? SORT_ICONS[sortDirection] : undefined;
|
|
87
312
|
return _jsx("th", {
|
|
@@ -113,13 +338,13 @@ const HeaderCell = /*#__PURE__*/forwardRef((_ref4, ref) => {
|
|
|
113
338
|
});
|
|
114
339
|
});
|
|
115
340
|
HeaderCell.displayName = 'Table.HeaderCell';
|
|
116
|
-
const Cell = /*#__PURE__*/forwardRef((
|
|
341
|
+
const Cell = /*#__PURE__*/forwardRef((_ref12, ref) => {
|
|
117
342
|
let {
|
|
118
343
|
children,
|
|
119
344
|
className,
|
|
120
345
|
isHeader,
|
|
121
346
|
...rest
|
|
122
|
-
} =
|
|
347
|
+
} = _ref12;
|
|
123
348
|
if (isHeader) {
|
|
124
349
|
return _jsx("th", {
|
|
125
350
|
ref: ref,
|
|
@@ -137,55 +362,57 @@ const Cell = /*#__PURE__*/forwardRef((_ref5, ref) => {
|
|
|
137
362
|
});
|
|
138
363
|
});
|
|
139
364
|
Cell.displayName = 'Table.Cell';
|
|
140
|
-
const Footer =
|
|
365
|
+
const Footer = _ref13 => {
|
|
141
366
|
let {
|
|
142
367
|
children,
|
|
143
368
|
className
|
|
144
|
-
} =
|
|
369
|
+
} = _ref13;
|
|
145
370
|
return _jsx("div", {
|
|
146
371
|
className: classNames('ncua-table__footer', className),
|
|
147
372
|
children: children
|
|
148
373
|
});
|
|
149
374
|
};
|
|
150
375
|
Footer.displayName = 'Table.Footer';
|
|
151
|
-
const Pagination =
|
|
376
|
+
const Pagination = _ref14 => {
|
|
152
377
|
let {
|
|
153
378
|
children,
|
|
154
379
|
className
|
|
155
|
-
} =
|
|
380
|
+
} = _ref14;
|
|
156
381
|
return _jsx("div", {
|
|
157
382
|
className: classNames('ncua-table__pagination', className),
|
|
158
383
|
children: children
|
|
159
384
|
});
|
|
160
385
|
};
|
|
161
386
|
Pagination.displayName = 'Table.Pagination';
|
|
162
|
-
const ColGroup =
|
|
387
|
+
const ColGroup = _ref15 => {
|
|
163
388
|
let {
|
|
164
389
|
widths,
|
|
165
390
|
minWidths
|
|
166
|
-
} =
|
|
391
|
+
} = _ref15;
|
|
167
392
|
const resolveColWidth = width => {
|
|
168
393
|
if (width === undefined || width === 'auto') return undefined;
|
|
169
394
|
if (typeof width === 'number') return `${width}px`;
|
|
170
395
|
return width;
|
|
171
396
|
};
|
|
172
397
|
return _jsx("colgroup", {
|
|
173
|
-
children: widths.map((width, index) =>
|
|
174
|
-
|
|
175
|
-
_jsx("col", {
|
|
176
|
-
style: {
|
|
398
|
+
children: widths.map((width, index) => {
|
|
399
|
+
const colStyle = {
|
|
177
400
|
width: resolveColWidth(width),
|
|
178
401
|
minWidth: resolveColWidth(minWidths?.[index])
|
|
179
|
-
}
|
|
180
|
-
|
|
402
|
+
};
|
|
403
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: colgroup columns never reorder or change
|
|
404
|
+
return _jsx("col", {
|
|
405
|
+
style: colStyle
|
|
406
|
+
}, index);
|
|
407
|
+
})
|
|
181
408
|
});
|
|
182
409
|
};
|
|
183
410
|
ColGroup.displayName = 'Table.ColGroup';
|
|
184
|
-
const Empty =
|
|
411
|
+
const Empty = _ref16 => {
|
|
185
412
|
let {
|
|
186
413
|
colSpan,
|
|
187
414
|
children
|
|
188
|
-
} =
|
|
415
|
+
} = _ref16;
|
|
189
416
|
return _jsx("tr", {
|
|
190
417
|
children: _jsx("td", {
|
|
191
418
|
colSpan: colSpan,
|
|
@@ -230,7 +457,7 @@ const sortChildren = children => {
|
|
|
230
457
|
// ──────────────────────────────────────────────
|
|
231
458
|
// Main Table component
|
|
232
459
|
// ──────────────────────────────────────────────
|
|
233
|
-
const TableComponent = /*#__PURE__*/forwardRef((
|
|
460
|
+
const TableComponent = /*#__PURE__*/forwardRef((_ref17, ref) => {
|
|
234
461
|
let {
|
|
235
462
|
type = 'horizontal',
|
|
236
463
|
fixedHeader = false,
|
|
@@ -239,16 +466,21 @@ const TableComponent = /*#__PURE__*/forwardRef((_ref0, ref) => {
|
|
|
239
466
|
selectable = false,
|
|
240
467
|
horizontalScroll = false,
|
|
241
468
|
minWidth,
|
|
469
|
+
draggable = false,
|
|
242
470
|
children,
|
|
243
471
|
className,
|
|
244
472
|
...rest
|
|
245
|
-
} =
|
|
473
|
+
} = _ref17;
|
|
474
|
+
const tableIdRef = useRef(`ncua-table-${Math.random().toString(TABLE_DND_ID_RADIX).slice(TABLE_DND_ID_SLICE_START, TABLE_DND_ID_SLICE_END)}`);
|
|
475
|
+
const tableId = tableIdRef.current;
|
|
476
|
+
const effectiveDraggable = draggable && type === 'horizontal';
|
|
246
477
|
const tableClasses = classNames('ncua-table', className, {
|
|
247
478
|
'ncua-table--horizontal': type === 'horizontal',
|
|
248
479
|
'ncua-table--vertical': type === 'vertical',
|
|
249
480
|
'ncua-table--fixed-header': fixedHeader,
|
|
250
481
|
'ncua-table--hoverable': hoverable && type === 'horizontal',
|
|
251
|
-
'ncua-table--selectable': selectable
|
|
482
|
+
'ncua-table--selectable': selectable,
|
|
483
|
+
'ncua-table--draggable': effectiveDraggable
|
|
252
484
|
});
|
|
253
485
|
const {
|
|
254
486
|
headerContent,
|
|
@@ -288,6 +520,13 @@ const TableComponent = /*#__PURE__*/forwardRef((_ref0, ref) => {
|
|
|
288
520
|
hScrollbarRef,
|
|
289
521
|
hThumbRef
|
|
290
522
|
});
|
|
523
|
+
// fixedHeader 모드에서 드래그 시 자동 스크롤
|
|
524
|
+
useEffect(() => {
|
|
525
|
+
if (!effectiveDraggable || !fixedScrollEnabled || !scrollContainerRef.current) return;
|
|
526
|
+
return autoScrollForElements({
|
|
527
|
+
element: scrollContainerRef.current
|
|
528
|
+
});
|
|
529
|
+
}, [effectiveDraggable, fixedScrollEnabled]);
|
|
291
530
|
// <colgroup> + <thead> + <tbody> 묶음 — fixed-header 분기와 horizontalScroll 분기 모두에서 재사용
|
|
292
531
|
const renderTable = () => _jsxs("table", {
|
|
293
532
|
className: "ncua-table__table",
|
|
@@ -319,6 +558,10 @@ const TableComponent = /*#__PURE__*/forwardRef((_ref0, ref) => {
|
|
|
319
558
|
})]
|
|
320
559
|
}) : renderTable();
|
|
321
560
|
};
|
|
561
|
+
const dndContextValue = {
|
|
562
|
+
isDraggable: effectiveDraggable,
|
|
563
|
+
tableId
|
|
564
|
+
};
|
|
322
565
|
// horizontalScroll=true 시 외곽 wrapper + FloatingProvider 부착.
|
|
323
566
|
// 핵심 — __h-scroll-container 는 <table>(또는 scroll-area) 만 감싸고, footer/pagination 은
|
|
324
567
|
// 그 바깥에서 항상 고정 위치. 세로 스크롤바는 h-scroll-container 형제로 배치되어
|
|
@@ -330,55 +573,61 @@ const TableComponent = /*#__PURE__*/forwardRef((_ref0, ref) => {
|
|
|
330
573
|
const innerStyle = {
|
|
331
574
|
'--ncua-table-min-width': typeof resolvedMinWidth === 'number' ? `${resolvedMinWidth}px` : resolvedMinWidth
|
|
332
575
|
};
|
|
333
|
-
return _jsx(
|
|
334
|
-
value:
|
|
335
|
-
children:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
...rest,
|
|
576
|
+
return _jsx(TableDndContext.Provider, {
|
|
577
|
+
value: dndContextValue,
|
|
578
|
+
children: _jsx(FloatingProvider, {
|
|
579
|
+
value: FLOATING_PORTAL_VALUE,
|
|
580
|
+
children: _jsxs("div", {
|
|
581
|
+
ref: ref,
|
|
582
|
+
className: "ncua-table-wrapper",
|
|
583
|
+
style: WRAPPER_STYLE,
|
|
342
584
|
children: [_jsxs("div", {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
children: [
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
children:
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
585
|
+
className: tableClasses,
|
|
586
|
+
...rest,
|
|
587
|
+
children: [_jsxs("div", {
|
|
588
|
+
ref: hScrollContainerRef,
|
|
589
|
+
className: "ncua-table__h-scroll-container",
|
|
590
|
+
children: [_jsx("div", {
|
|
591
|
+
className: "ncua-table__h-scroll-inner",
|
|
592
|
+
style: innerStyle,
|
|
593
|
+
children: renderScrollableArea(false)
|
|
594
|
+
}), _jsx("div", {
|
|
595
|
+
ref: hScrollbarRef,
|
|
596
|
+
className: "ncua-table__h-scrollbar",
|
|
597
|
+
"aria-hidden": "true",
|
|
598
|
+
children: _jsx("div", {
|
|
599
|
+
ref: hThumbRef,
|
|
600
|
+
className: "ncua-table__h-scrollbar-thumb",
|
|
601
|
+
onMouseDown: handleHThumbMouseDown
|
|
602
|
+
})
|
|
603
|
+
})]
|
|
604
|
+
}), fixedScrollEnabled && _jsx("div", {
|
|
605
|
+
ref: scrollbarRef,
|
|
606
|
+
className: "ncua-table__scrollbar",
|
|
352
607
|
"aria-hidden": "true",
|
|
353
608
|
children: _jsx("div", {
|
|
354
|
-
ref:
|
|
355
|
-
className: "ncua-
|
|
356
|
-
onMouseDown:
|
|
609
|
+
ref: thumbRef,
|
|
610
|
+
className: "ncua-table__scrollbar-thumb",
|
|
611
|
+
onMouseDown: handleThumbMouseDown
|
|
357
612
|
})
|
|
358
|
-
})]
|
|
359
|
-
}),
|
|
360
|
-
|
|
361
|
-
className: "ncua-table__scrollbar",
|
|
362
|
-
"aria-hidden": "true",
|
|
363
|
-
children: _jsx("div", {
|
|
364
|
-
ref: thumbRef,
|
|
365
|
-
className: "ncua-table__scrollbar-thumb",
|
|
366
|
-
onMouseDown: handleThumbMouseDown
|
|
367
|
-
})
|
|
368
|
-
}), footerContent]
|
|
369
|
-
}), paginationContent]
|
|
613
|
+
}), footerContent]
|
|
614
|
+
}), paginationContent]
|
|
615
|
+
})
|
|
370
616
|
})
|
|
371
617
|
});
|
|
372
618
|
}
|
|
373
|
-
return
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
619
|
+
return _jsx(TableDndContext.Provider, {
|
|
620
|
+
value: dndContextValue,
|
|
621
|
+
children: _jsxs("div", {
|
|
622
|
+
ref: ref,
|
|
623
|
+
className: "ncua-table-wrapper",
|
|
624
|
+
style: WRAPPER_STYLE,
|
|
625
|
+
children: [_jsxs("div", {
|
|
626
|
+
className: tableClasses,
|
|
627
|
+
...rest,
|
|
628
|
+
children: [renderScrollableArea(), footerContent]
|
|
629
|
+
}), paginationContent]
|
|
630
|
+
})
|
|
382
631
|
});
|
|
383
632
|
});
|
|
384
633
|
TableComponent.displayName = 'Table';
|
|
@@ -389,6 +638,8 @@ export const Table = Object.assign(TableComponent, {
|
|
|
389
638
|
Header,
|
|
390
639
|
Body,
|
|
391
640
|
Row,
|
|
641
|
+
DragHeaderCell,
|
|
642
|
+
DragCell,
|
|
392
643
|
HeaderCell,
|
|
393
644
|
Cell,
|
|
394
645
|
Footer,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
const TableDndContext = /*#__PURE__*/createContext({
|
|
3
|
+
isDraggable: false,
|
|
4
|
+
tableId: ''
|
|
5
|
+
});
|
|
6
|
+
const RowDndContext = /*#__PURE__*/createContext({
|
|
7
|
+
dragId: undefined,
|
|
8
|
+
setIsDragging: _v => undefined
|
|
9
|
+
});
|
|
10
|
+
export { TableDndContext, RowDndContext };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const TABLE_DND_ID_RADIX = 36;
|
|
2
|
+
const TABLE_DND_ID_SLICE_START = 2;
|
|
3
|
+
const TABLE_DND_ID_SLICE_END = 9;
|
|
4
|
+
const PREVIEW_MAX_ROWS = 3;
|
|
5
|
+
const PREVIEW_STACK_OFFSET = 5;
|
|
6
|
+
const PREVIEW_MAX_STACK_OFFSET = PREVIEW_STACK_OFFSET * PREVIEW_MAX_ROWS;
|
|
7
|
+
const PREVIEW_BADGE_SIZE = 24;
|
|
8
|
+
const PREVIEW_BADGE_HALF = PREVIEW_BADGE_SIZE / 2;
|
|
9
|
+
// hover의 color-mix 반투명 배경을 우회해 불투명 RGB 배경색을 클래스 기반으로 직접 결정
|
|
10
|
+
const getRowBackground = rowEl => {
|
|
11
|
+
const resolveCssVar = name => getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
12
|
+
const isSelected = rowEl.classList.contains('ncua-table__row--selected');
|
|
13
|
+
const isError = rowEl.classList.contains('ncua-table__row--error');
|
|
14
|
+
const isWarning = rowEl.classList.contains('ncua-table__row--warning');
|
|
15
|
+
if (isSelected && isError) return resolveCssVar('--primary-red-50');
|
|
16
|
+
if (isSelected && isWarning) return resolveCssVar('--orange-50');
|
|
17
|
+
if (isSelected) return resolveCssVar('--gray-50');
|
|
18
|
+
return resolveCssVar('--base-white');
|
|
19
|
+
};
|
|
20
|
+
// cloneNode는 HTML attribute만 복사하므로 checkbox.checked를 직접 동기화
|
|
21
|
+
const cloneRowWithState = sourceRow => {
|
|
22
|
+
const clone = sourceRow.cloneNode(true);
|
|
23
|
+
const srcInputs = sourceRow.querySelectorAll('input[type="checkbox"]');
|
|
24
|
+
const clnInputs = clone.querySelectorAll('input[type="checkbox"]');
|
|
25
|
+
srcInputs.forEach((src, i) => {
|
|
26
|
+
const cln = clnInputs[i];
|
|
27
|
+
if (cln) cln.checked = src.checked;
|
|
28
|
+
});
|
|
29
|
+
clone.style.backgroundColor = getRowBackground(sourceRow);
|
|
30
|
+
// 열 너비는 테이블 레이아웃 컨텍스트에서 결정되므로 직접 복사 — ceil로 올림해 프리뷰 셀이 실제보다 좁아지지 않게 함
|
|
31
|
+
const srcCells = Array.from(sourceRow.querySelectorAll('td, th'));
|
|
32
|
+
const clnCells = Array.from(clone.querySelectorAll('td, th'));
|
|
33
|
+
srcCells.forEach((src, i) => {
|
|
34
|
+
const cln = clnCells[i];
|
|
35
|
+
if (!cln) return;
|
|
36
|
+
const w = `${Math.ceil(src.getBoundingClientRect().width)}px`;
|
|
37
|
+
cln.style.width = w;
|
|
38
|
+
cln.style.minWidth = w;
|
|
39
|
+
cln.style.maxWidth = w;
|
|
40
|
+
});
|
|
41
|
+
return clone;
|
|
42
|
+
};
|
|
43
|
+
const buildCountBadge = count => {
|
|
44
|
+
const badge = document.createElement('div');
|
|
45
|
+
badge.className = 'ncua-table-dnd-preview__badge';
|
|
46
|
+
badge.textContent = String(count);
|
|
47
|
+
return badge;
|
|
48
|
+
};
|
|
49
|
+
const buildSummaryCard = rowRect => {
|
|
50
|
+
const wrapper = document.createElement('div');
|
|
51
|
+
wrapper.className = 'ncua-table-dnd-preview__summary';
|
|
52
|
+
Object.assign(wrapper.style, {
|
|
53
|
+
top: `${Math.round(PREVIEW_BADGE_HALF + PREVIEW_MAX_STACK_OFFSET)}px`,
|
|
54
|
+
left: `${Math.round(PREVIEW_BADGE_HALF + PREVIEW_MAX_STACK_OFFSET)}px`,
|
|
55
|
+
width: `${Math.round(rowRect.width) - PREVIEW_MAX_STACK_OFFSET}px`,
|
|
56
|
+
height: `${Math.round(rowRect.height)}px`
|
|
57
|
+
});
|
|
58
|
+
return wrapper;
|
|
59
|
+
};
|
|
60
|
+
const buildRowCard = _ref => {
|
|
61
|
+
let {
|
|
62
|
+
row,
|
|
63
|
+
cardLeft,
|
|
64
|
+
cardTop,
|
|
65
|
+
cardWidth,
|
|
66
|
+
rowHeight,
|
|
67
|
+
zIndex,
|
|
68
|
+
parentTable
|
|
69
|
+
} = _ref;
|
|
70
|
+
const wrapper = document.createElement('div');
|
|
71
|
+
wrapper.className = 'ncua-table-dnd-preview__card';
|
|
72
|
+
Object.assign(wrapper.style, {
|
|
73
|
+
zIndex: String(zIndex),
|
|
74
|
+
top: `${cardTop}px`,
|
|
75
|
+
left: `${cardLeft}px`,
|
|
76
|
+
width: `${cardWidth}px`,
|
|
77
|
+
height: `${rowHeight}px`,
|
|
78
|
+
backgroundColor: getRowBackground(row)
|
|
79
|
+
});
|
|
80
|
+
const table = document.createElement('table');
|
|
81
|
+
if (parentTable) table.className = parentTable.className;
|
|
82
|
+
table.style.width = `${cardWidth}px`;
|
|
83
|
+
const tbody = document.createElement('tbody');
|
|
84
|
+
tbody.className = 'ncua-table__body';
|
|
85
|
+
tbody.appendChild(cloneRowWithState(row));
|
|
86
|
+
table.appendChild(tbody);
|
|
87
|
+
wrapper.appendChild(table);
|
|
88
|
+
return wrapper;
|
|
89
|
+
};
|
|
90
|
+
const buildDragPreview = (rows, rowRect) => {
|
|
91
|
+
const visibleCount = Math.min(rows.length, PREVIEW_MAX_ROWS);
|
|
92
|
+
const hasSummary = rows.length > visibleCount;
|
|
93
|
+
const totalCards = visibleCount + (hasSummary ? 1 : 0);
|
|
94
|
+
const isMulti = rows.length > 1;
|
|
95
|
+
const parentTable = rows[0]?.closest('table') ?? null;
|
|
96
|
+
const rowWidth = Math.round(rowRect.width);
|
|
97
|
+
const rowHeight = Math.round(rowRect.height);
|
|
98
|
+
const container = document.createElement('div');
|
|
99
|
+
container.className = 'ncua-table-dnd-preview';
|
|
100
|
+
Object.assign(container.style, {
|
|
101
|
+
width: `${PREVIEW_BADGE_HALF + rowWidth}px`,
|
|
102
|
+
height: `${PREVIEW_BADGE_HALF + rowHeight + PREVIEW_STACK_OFFSET * (totalCards - 1)}px`
|
|
103
|
+
});
|
|
104
|
+
if (isMulti) container.appendChild(buildCountBadge(rows.length));
|
|
105
|
+
if (hasSummary) container.appendChild(buildSummaryCard(rowRect));
|
|
106
|
+
// 뒤쪽 행 카드부터 렌더해서 앞쪽이 위로 올라오게
|
|
107
|
+
for (let i = visibleCount - 1; i >= 0; i--) {
|
|
108
|
+
const stackShift = isMulti ? i * PREVIEW_STACK_OFFSET : 0;
|
|
109
|
+
container.appendChild(buildRowCard({
|
|
110
|
+
row: rows[i],
|
|
111
|
+
cardLeft: Math.round(PREVIEW_BADGE_HALF + stackShift),
|
|
112
|
+
cardTop: Math.round(PREVIEW_BADGE_HALF + stackShift),
|
|
113
|
+
cardWidth: rowWidth - stackShift,
|
|
114
|
+
rowHeight,
|
|
115
|
+
zIndex: visibleCount - i,
|
|
116
|
+
parentTable
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
return container;
|
|
120
|
+
};
|
|
121
|
+
export { TABLE_DND_ID_RADIX, TABLE_DND_ID_SLICE_START, TABLE_DND_ID_SLICE_END, PREVIEW_BADGE_SIZE, buildDragPreview };
|