Body.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import classNames from 'classnames';
  2. import {
  3. Dispatch,
  4. MutableRefObject,
  5. ReactNode,
  6. SetStateAction,
  7. forwardRef,
  8. useEffect,
  9. useState,
  10. } from 'react';
  11. import BodyTr from './BodyTr';
  12. import {Empty} from 'antd';
  13. import {HeaderGroup, Row, RowModel} from '@tanstack/react-table';
  14. import {
  15. DndContext,
  16. DragEndEvent,
  17. DragOverlay,
  18. DragStartEvent,
  19. PointerSensor,
  20. closestCenter,
  21. useSensor,
  22. } from '@dnd-kit/core';
  23. import {
  24. SortableContext,
  25. arrayMove,
  26. verticalListSortingStrategy,
  27. } from '@dnd-kit/sortable';
  28. import {debounce} from 'lodash-es';
  29. type Props = {
  30. scrollProgess: 'start' | 'end' | 'process',
  31. getCenterTotalSize: () => number,
  32. getRowModel: () => RowModel<Record<string, any>>,
  33. highlightValue?: unknown,
  34. hightlightKey?: any,
  35. getHeaderGroups: () => HeaderGroup<Record<string, any>>[],
  36. enableDnd?: boolean,
  37. rawKey?: any,
  38. data: any[],
  39. setData: Dispatch<SetStateAction<any[]>>,
  40. isSecondLevel?: boolean,
  41. renderSubComponent?: (row: Row<Record<string, any>>) => ReactNode,
  42. };
  43. type ActiveState = {
  44. row: Row<Record<string, any>>,
  45. } | null;
  46. export default forwardRef<HTMLDivElement, Props>(function TableBody(
  47. {
  48. scrollProgess,
  49. getCenterTotalSize,
  50. getRowModel,
  51. highlightValue,
  52. hightlightKey,
  53. getHeaderGroups,
  54. data,
  55. enableDnd,
  56. rawKey,
  57. setData,
  58. isSecondLevel,
  59. renderSubComponent,
  60. },
  61. ref,
  62. ) {
  63. const sensor = useSensor(PointerSensor, {
  64. activationConstraint: {distance: 10},
  65. });
  66. const [active, setActive] = useState<ActiveState>(null);
  67. function onDragStart(event: DragStartEvent) {
  68. setActive(event.active.data.current as ActiveState);
  69. }
  70. function onDragEnd({over, active}: DragEndEvent) {
  71. setActive(null);
  72. if (!over)
  73. return;
  74. const {id: activeId} = active,
  75. {id: overId} = over;
  76. if (activeId === overId)
  77. return;
  78. setData(function(prev) {
  79. const fromIdx = prev.findIndex(val => val[rawKey ?? 'id'] === activeId),
  80. toIdx = prev.findIndex(val => val[rawKey ?? 'id'] === overId);
  81. return arrayMove(prev, fromIdx, toIdx);
  82. });
  83. }
  84. const [tableWidth, setTableWidth] = useState(0);
  85. useEffect(function() {
  86. if (!enableDnd) return;
  87. const dom = (ref as MutableRefObject<HTMLDivElement>).current;
  88. const obsever = new ResizeObserver(debounce(
  89. function([entrie]) {
  90. setTableWidth(entrie.contentRect.width);
  91. },
  92. 300,
  93. {leading: false, trailing: true},
  94. ));
  95. obsever.observe(dom);
  96. return () => obsever.disconnect();
  97. }, [enableDnd, ref]);
  98. return (
  99. <DndContext
  100. sensors={[sensor]}
  101. collisionDetection={closestCenter}
  102. onDragStart={onDragStart}
  103. onDragEnd={onDragEnd}
  104. >
  105. <SortableContext
  106. items={data.map(val => val[rawKey ?? 'id'])}
  107. strategy={verticalListSortingStrategy}
  108. >
  109. <div className="ld-table-body-wrapper" ref={ref}>
  110. <table
  111. className={classNames('ld-table', {
  112. 'ld-table-enabled-right-fixed-shadow': scrollProgess !== 'end',
  113. 'ld-table-enabled-left-fixed-shadow': scrollProgess !== 'start',
  114. })}
  115. style={{width: getCenterTotalSize()}}
  116. >
  117. <tbody className="ld-table-body">
  118. {getRowModel().rows.length > 0 ? (
  119. getRowModel().rows.map(function(row) {
  120. return (
  121. <BodyTr
  122. key={row.id}
  123. highlight={row.original[hightlightKey as string ?? 'id'] === highlightValue}
  124. enableDnd={enableDnd}
  125. row={row}
  126. renderSubComponent={renderSubComponent}
  127. />
  128. );
  129. })
  130. ) : (
  131. <tr>
  132. <td
  133. colSpan={getHeaderGroups()[0].headers.length}
  134. style={{padding: 0}}
  135. >
  136. <Empty
  137. image={Empty.PRESENTED_IMAGE_SIMPLE}
  138. style={{
  139. width: '400px',
  140. position: 'sticky',
  141. left: '50%',
  142. transform: 'translateX(-50%)',
  143. overflow: 'hidden',
  144. margin: 0,
  145. padding: '50px 10px',
  146. }}
  147. />
  148. </td>
  149. </tr>
  150. )}
  151. </tbody>
  152. </table>
  153. </div>
  154. <DragOverlay>
  155. {active && (
  156. <div
  157. className={classNames({
  158. 'ld-table-preview-transform': isSecondLevel,
  159. })}
  160. style={{width: tableWidth, overflow: 'hidden'}}
  161. >
  162. <table
  163. className={classNames('ld-table', {
  164. 'ld-table-enabled-right-fixed-shadow': scrollProgess !== 'end',
  165. 'ld-table-enabled-left-fixed-shadow': scrollProgess !== 'start',
  166. })}
  167. style={{width: getCenterTotalSize()}}
  168. >
  169. <tbody>
  170. <BodyTr {...active} preview />
  171. </tbody>
  172. </table>
  173. </div>
  174. )}
  175. </DragOverlay>
  176. </SortableContext>
  177. </DndContext>
  178. );
  179. });