@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.
Files changed (24) hide show
  1. package/dist/cjs/src/components/data-display/data-grid/DataGrid.js +3 -1
  2. package/dist/cjs/src/components/data-display/table/Table.js +326 -75
  3. package/dist/cjs/src/components/data-display/table/dnd-context.js +15 -0
  4. package/dist/cjs/src/components/data-display/table/dnd-preview.js +127 -0
  5. package/dist/esm/src/components/data-display/data-grid/DataGrid.js +3 -1
  6. package/dist/esm/src/components/data-display/table/Table.js +328 -77
  7. package/dist/esm/src/components/data-display/table/dnd-context.js +10 -0
  8. package/dist/esm/src/components/data-display/table/dnd-preview.js +121 -0
  9. package/dist/temp/src/components/data-display/data-grid/DataGrid.js +1 -1
  10. package/dist/temp/src/components/data-display/data-grid/DataGrid.types.d.ts +5 -0
  11. package/dist/temp/src/components/data-display/table/Table.d.ts +14 -4
  12. package/dist/temp/src/components/data-display/table/Table.js +172 -14
  13. package/dist/temp/src/components/data-display/table/dnd-context.d.ts +12 -0
  14. package/dist/temp/src/components/data-display/table/dnd-context.js +10 -0
  15. package/dist/temp/src/components/data-display/table/dnd-preview.d.ts +6 -0
  16. package/dist/temp/src/components/data-display/table/dnd-preview.js +120 -0
  17. package/dist/temp/src/components/data-display/table/types.d.ts +24 -1
  18. package/dist/types/src/components/data-display/data-grid/DataGrid.types.d.ts +5 -0
  19. package/dist/types/src/components/data-display/table/Table.d.ts +14 -4
  20. package/dist/types/src/components/data-display/table/dnd-context.d.ts +12 -0
  21. package/dist/types/src/components/data-display/table/dnd-preview.d.ts +6 -0
  22. package/dist/types/src/components/data-display/table/types.d.ts +24 -1
  23. package/dist/ui-admin/assets/styles/style.css +166 -2
  24. package/package.json +2 -2
@@ -1,8 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ChevronDown, ChevronSelectorVertical, ChevronUp } from '@ncds/ui-admin-icon';
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((_ref3, ref) => {
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
- } = _ref3;
62
- return _jsx("tr", {
63
- ref: ref,
64
- className: classNames('ncua-table__row', className, {
65
- 'ncua-table__row--selected': selected,
66
- 'ncua-table__row--warning': status === 'warning',
67
- 'ncua-table__row--error': status === 'error'
68
- }),
69
- ...rest,
70
- children: children
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 HeaderCell = /*#__PURE__*/forwardRef((_ref4, ref) => {
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
- } = _ref4;
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((_ref5, ref) => {
341
+ const Cell = /*#__PURE__*/forwardRef((_ref12, ref) => {
117
342
  let {
118
343
  children,
119
344
  className,
120
345
  isHeader,
121
346
  ...rest
122
- } = _ref5;
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 = _ref6 => {
365
+ const Footer = _ref13 => {
141
366
  let {
142
367
  children,
143
368
  className
144
- } = _ref6;
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 = _ref7 => {
376
+ const Pagination = _ref14 => {
152
377
  let {
153
378
  children,
154
379
  className
155
- } = _ref7;
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 = _ref8 => {
387
+ const ColGroup = _ref15 => {
163
388
  let {
164
389
  widths,
165
390
  minWidths
166
- } = _ref8;
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
- // biome-ignore lint/suspicious/noArrayIndexKey: colgroup columns never reorder or change
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
- }, index))
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 = _ref9 => {
411
+ const Empty = _ref16 => {
185
412
  let {
186
413
  colSpan,
187
414
  children
188
- } = _ref9;
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((_ref0, ref) => {
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
- } = _ref0;
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(FloatingProvider, {
334
- value: FLOATING_PORTAL_VALUE,
335
- children: _jsxs("div", {
336
- ref: ref,
337
- className: "ncua-table-wrapper",
338
- style: WRAPPER_STYLE,
339
- children: [_jsxs("div", {
340
- className: tableClasses,
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
- ref: hScrollContainerRef,
344
- className: "ncua-table__h-scroll-container",
345
- children: [_jsx("div", {
346
- className: "ncua-table__h-scroll-inner",
347
- style: innerStyle,
348
- children: renderScrollableArea(false)
349
- }), _jsx("div", {
350
- ref: hScrollbarRef,
351
- className: "ncua-table__h-scrollbar",
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: hThumbRef,
355
- className: "ncua-table__h-scrollbar-thumb",
356
- onMouseDown: handleHThumbMouseDown
609
+ ref: thumbRef,
610
+ className: "ncua-table__scrollbar-thumb",
611
+ onMouseDown: handleThumbMouseDown
357
612
  })
358
- })]
359
- }), fixedScrollEnabled && _jsx("div", {
360
- ref: scrollbarRef,
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 _jsxs("div", {
374
- ref: ref,
375
- className: "ncua-table-wrapper",
376
- style: WRAPPER_STYLE,
377
- children: [_jsxs("div", {
378
- className: tableClasses,
379
- ...rest,
380
- children: [renderScrollableArea(), footerContent]
381
- }), paginationContent]
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 };