From 4287f737c38d03cfc0a909b69368680b47101cfb Mon Sep 17 00:00:00 2001 From: DanisAvko Date: Thu, 12 Sep 2024 15:47:18 +0300 Subject: [PATCH] feat(Table): drag column added --- README.md | 4 +- package-lock.json | 2 +- package.json | 1 + .../BaseDragHandle/BaseDragHandle.tsx | 43 ------------- src/components/BaseDragHandle/index.ts | 1 - .../BaseDraggableRowMarker.classname.ts | 3 - .../BaseDraggableRowMarker.scss | 16 ----- .../BaseDraggableRowMarker.tsx | 13 ---- .../BaseDraggableRowMarker/index.ts | 1 - src/components/BaseTable/BaseTable.scss | 1 - .../__stories__/stories/ReorderingStory.tsx | 4 +- .../stories/ReorderingTreeStory.tsx | 4 +- .../ReorderingWithVirtualizationStory.tsx | 4 +- .../DragHandle.classname.ts} | 0 .../DragHandle.scss} | 2 + src/components/DragHandle/DragHandle.tsx | 28 +++++++++ src/components/DragHandle/index.ts | 1 + .../Table/__stories__/Table.stories.tsx | 10 +++ .../__stories__/stories/ReorderingStory.tsx | 53 ++++++++++++++++ .../ReorderingWithVirtualizationStory.tsx | 62 +++++++++++++++++++ src/components/index.ts | 3 +- src/constants/defaultDragHandleColumn.tsx | 13 ---- src/constants/dragHandleColumn.tsx | 13 ++++ src/constants/index.ts | 2 +- src/index.ts | 2 +- 25 files changed, 182 insertions(+), 104 deletions(-) delete mode 100644 src/components/BaseDragHandle/BaseDragHandle.tsx delete mode 100644 src/components/BaseDragHandle/index.ts delete mode 100644 src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.classname.ts delete mode 100644 src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.scss delete mode 100644 src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.tsx delete mode 100644 src/components/BaseDraggableRowMarker/index.ts rename src/components/{BaseDragHandle/BaseDragHandle.classname.ts => DragHandle/DragHandle.classname.ts} (100%) rename src/components/{BaseDragHandle/BaseDragHandle.scss => DragHandle/DragHandle.scss} (77%) create mode 100644 src/components/DragHandle/DragHandle.tsx create mode 100644 src/components/DragHandle/index.ts create mode 100644 src/components/Table/__stories__/stories/ReorderingStory.tsx create mode 100644 src/components/Table/__stories__/stories/ReorderingWithVirtualizationStory.tsx delete mode 100644 src/constants/defaultDragHandleColumn.tsx create mode 100644 src/constants/dragHandleColumn.tsx diff --git a/README.md b/README.md index dfacfa7..bda56b1 100644 --- a/README.md +++ b/README.md @@ -188,10 +188,10 @@ const GroupingExample = () => { ```tsx import type {ReorderingProviderProps} from '@gravity-ui/table'; -import {defaultDragHandleColumn, ReorderingProvider} from '@gravity-ui/table'; +import {baseDragHandleColumn, ReorderingProvider} from '@gravity-ui/table'; const columns: ColumnDef[] = [ - defaultDragHandleColumn, + baseDragHandleColumn, // ...other columns ]; diff --git a/package-lock.json b/package-lock.json index 3d6aab9..120f3b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,7 @@ "peerDependencies": { "@dnd-kit/core": "^6.0.0", "@dnd-kit/sortable": "^8.0.0", + "@gravity-ui/icons": "^2.0.0", "@gravity-ui/uikit": "^6.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" @@ -3238,7 +3239,6 @@ "version": "2.9.1", "resolved": "https://registry.npmjs.org/@gravity-ui/icons/-/icons-2.9.1.tgz", "integrity": "sha512-0OH6YrVxFpHjk3x1f5GWlC+3Ek7LncNy95C9g94C13MhRtMRe0l/ZwSgbOmQ72lDxvQrmidt095Ft+mfID9XmQ==", - "dev": true, "peerDependencies": { "react": "*" }, diff --git a/package.json b/package.json index dc91da4..6a3427b 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "@dnd-kit/core": "^6.0.0", "@dnd-kit/sortable": "^8.0.0", "@gravity-ui/uikit": "^6.0.0", + "@gravity-ui/icons": "^2.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }, diff --git a/src/components/BaseDragHandle/BaseDragHandle.tsx b/src/components/BaseDragHandle/BaseDragHandle.tsx deleted file mode 100644 index 6f0194e..0000000 --- a/src/components/BaseDragHandle/BaseDragHandle.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; - -import {useSortable} from '@dnd-kit/sortable'; -import type {Row, Table} from '@tanstack/react-table'; - -import {useDraggableRowDepth} from '../../hooks'; -import {BaseDraggableRowMarker} from '../BaseDraggableRowMarker'; -import {SortableListContext} from '../SortableListContext'; - -import {b} from './BaseDragHandle.classname'; - -import './BaseDragHandle.scss'; - -export interface BaseDragHandleProps { - row: Row; - table: Table; -} - -export const BaseDragHandle = ({row, table}: BaseDragHandleProps) => { - const {attributes, listeners, isDragging} = useSortable({ - id: row.id, - }); - - const {enableNesting} = React.useContext(SortableListContext) || {}; - - const {depth} = useDraggableRowDepth({row, table, isDragging}); - - return ( - - - - - - - {isDragging && enableNesting && } - - ); -}; - -BaseDragHandle.displayName = 'BaseDragHandle'; diff --git a/src/components/BaseDragHandle/index.ts b/src/components/BaseDragHandle/index.ts deleted file mode 100644 index ed78f25..0000000 --- a/src/components/BaseDragHandle/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BaseDragHandle'; diff --git a/src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.classname.ts b/src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.classname.ts deleted file mode 100644 index 10fd04b..0000000 --- a/src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.classname.ts +++ /dev/null @@ -1,3 +0,0 @@ -import {block} from '../../utils'; - -export const b = block('draggable-row-marker'); diff --git a/src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.scss b/src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.scss deleted file mode 100644 index ad953ef..0000000 --- a/src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.scss +++ /dev/null @@ -1,16 +0,0 @@ -@use '../variables'; - -$block: '.#{variables.$ns}draggable-row-marker'; - -#{$block} { - position: absolute; - inset-block-start: 0; - inset-inline-end: 0; - z-index: 2; - - height: 5px; - - background: #027bf3; - - margin-inline-start: calc(var(--gc-tree-view-padding) + var(--gc-draggable-row-marker-offset)); -} diff --git a/src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.tsx b/src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.tsx deleted file mode 100644 index 5362bc8..0000000 --- a/src/components/BaseDraggableRowMarker/BaseDraggableRowMarker.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -import {b} from './BaseDraggableRowMarker.classname'; - -import './BaseDraggableRowMarker.scss'; - -export interface BaseDraggableRowMarkerProps { - left: number; -} - -export const BaseDraggableRowMarker = ({left}: BaseDraggableRowMarkerProps) => { - return ; -}; diff --git a/src/components/BaseDraggableRowMarker/index.ts b/src/components/BaseDraggableRowMarker/index.ts deleted file mode 100644 index 296dac5..0000000 --- a/src/components/BaseDraggableRowMarker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BaseDraggableRowMarker'; diff --git a/src/components/BaseTable/BaseTable.scss b/src/components/BaseTable/BaseTable.scss index 2301965..22fd795 100644 --- a/src/components/BaseTable/BaseTable.scss +++ b/src/components/BaseTable/BaseTable.scss @@ -4,7 +4,6 @@ $block: '.#{variables.$ns}table'; #{$block} { --gc-tree-view-padding: 0px; - --gc-draggable-row-marker-offset: 20px; border-spacing: 0; border-collapse: separate; diff --git a/src/components/BaseTable/__stories__/stories/ReorderingStory.tsx b/src/components/BaseTable/__stories__/stories/ReorderingStory.tsx index 8f870f4..212f88e 100644 --- a/src/components/BaseTable/__stories__/stories/ReorderingStory.tsx +++ b/src/components/BaseTable/__stories__/stories/ReorderingStory.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type {ColumnDef} from '@tanstack/react-table'; -import {defaultDragHandleColumn} from '../../../../constants'; +import {dragHandleColumn} from '../../../../constants'; import {useTable} from '../../../../hooks'; import {BaseTable} from '../../../BaseTable'; import {ReorderingProvider} from '../../../ReorderingProvider'; @@ -11,7 +11,7 @@ import {columns as originalColumns} from '../constants/columns'; import {data as originalData} from '../constants/data'; import type {Item} from '../types'; -const columns: ColumnDef[] = [defaultDragHandleColumn as ColumnDef, ...originalColumns]; +const columns: ColumnDef[] = [dragHandleColumn as ColumnDef, ...originalColumns]; export const ReorderingStory = () => { const [data, setData] = React.useState(originalData); diff --git a/src/components/BaseTable/__stories__/stories/ReorderingTreeStory.tsx b/src/components/BaseTable/__stories__/stories/ReorderingTreeStory.tsx index b01ba2b..4e97536 100644 --- a/src/components/BaseTable/__stories__/stories/ReorderingTreeStory.tsx +++ b/src/components/BaseTable/__stories__/stories/ReorderingTreeStory.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type {ColumnDef, ExpandedState} from '@tanstack/react-table'; -import {defaultDragHandleColumn} from '../../../../constants'; +import {dragHandleColumn} from '../../../../constants'; import {useTable} from '../../../../hooks'; import {ReorderingProvider} from '../../../ReorderingProvider'; import {BaseTable} from '../../BaseTable'; @@ -11,7 +11,7 @@ import {draggableTreeColumns, data as originalData} from '../constants/tree'; import {useTreeDataReordering} from '../hooks/useTreeDataReordering'; const columns: ColumnDef[] = [ - defaultDragHandleColumn as ColumnDef, + dragHandleColumn as ColumnDef, ...draggableTreeColumns, ]; diff --git a/src/components/BaseTable/__stories__/stories/ReorderingWithVirtualizationStory.tsx b/src/components/BaseTable/__stories__/stories/ReorderingWithVirtualizationStory.tsx index da3de48..a2d1506 100644 --- a/src/components/BaseTable/__stories__/stories/ReorderingWithVirtualizationStory.tsx +++ b/src/components/BaseTable/__stories__/stories/ReorderingWithVirtualizationStory.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type {ColumnDef} from '@tanstack/react-table'; -import {defaultDragHandleColumn} from '../../../../constants'; +import {dragHandleColumn} from '../../../../constants'; import {useTable, useWindowRowVirtualizer} from '../../../../hooks'; import {getVirtualRowRangeExtractor} from '../../../../utils'; import {BaseTable} from '../../../BaseTable'; @@ -16,7 +16,7 @@ import {cnVirtualizationStory} from './VirtualizationStory.classname'; import './VirtualizationStory.scss'; -const columns: ColumnDef[] = [defaultDragHandleColumn as ColumnDef, ...originalColumns]; +const columns: ColumnDef[] = [dragHandleColumn as ColumnDef, ...originalColumns]; export const ReorderingWithVirtualizationStory = () => { const tableRef = React.useRef(null); diff --git a/src/components/BaseDragHandle/BaseDragHandle.classname.ts b/src/components/DragHandle/DragHandle.classname.ts similarity index 100% rename from src/components/BaseDragHandle/BaseDragHandle.classname.ts rename to src/components/DragHandle/DragHandle.classname.ts diff --git a/src/components/BaseDragHandle/BaseDragHandle.scss b/src/components/DragHandle/DragHandle.scss similarity index 77% rename from src/components/BaseDragHandle/BaseDragHandle.scss rename to src/components/DragHandle/DragHandle.scss index 5579ef1..9753fde 100644 --- a/src/components/BaseDragHandle/BaseDragHandle.scss +++ b/src/components/DragHandle/DragHandle.scss @@ -5,6 +5,8 @@ $block: '.#{variables.$ns}drag-handle'; #{$block} { cursor: grab; + color: var(--g-color-text-secondary); + &[aria-pressed] { cursor: grabbing; } diff --git a/src/components/DragHandle/DragHandle.tsx b/src/components/DragHandle/DragHandle.tsx new file mode 100644 index 0000000..66f57ed --- /dev/null +++ b/src/components/DragHandle/DragHandle.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import {useSortable} from '@dnd-kit/sortable'; +import {Grip as GripIcon} from '@gravity-ui/icons'; +import {Icon} from '@gravity-ui/uikit'; +import type {Row} from '@tanstack/react-table'; + +import {b} from './DragHandle.classname'; + +import './DragHandle.scss'; + +export interface DragHandleProps { + row: Row; +} + +export const DragHandle = ({row}: DragHandleProps) => { + const {attributes, listeners} = useSortable({ + id: row.id, + }); + + return ( + + + + ); +}; + +DragHandle.displayName = 'DragHandle'; diff --git a/src/components/DragHandle/index.ts b/src/components/DragHandle/index.ts new file mode 100644 index 0000000..21aa91a --- /dev/null +++ b/src/components/DragHandle/index.ts @@ -0,0 +1 @@ +export * from './DragHandle'; diff --git a/src/components/Table/__stories__/Table.stories.tsx b/src/components/Table/__stories__/Table.stories.tsx index 83a1161..7f02a07 100644 --- a/src/components/Table/__stories__/Table.stories.tsx +++ b/src/components/Table/__stories__/Table.stories.tsx @@ -3,6 +3,8 @@ import type {Meta, StoryObj} from '@storybook/react'; import {Table} from '../index'; import {DefaultStory} from './stories/DefaultStory'; +import {ReorderingStory} from './stories/ReorderingStory'; +import {ReorderingWithVirtualizationStory} from './stories/ReorderingWithVirtualizationStory'; import {StickyHeaderStory} from './stories/StickyHeaderStory'; import {VirtualizationStory} from './stories/VirtualizationStory'; import {WindowVirtualizationStory} from './stories/WindowVirtualizationStory'; @@ -18,6 +20,10 @@ export const Default: StoryObj = { render: DefaultStory, }; +export const Reordering: StoryObj = { + render: ReorderingStory, +}; + export const Virtualization: StoryObj = { render: VirtualizationStory, }; @@ -26,6 +32,10 @@ export const WindowVirtualization: StoryObj = render: WindowVirtualizationStory, }; +export const ReorderingWithVirtualization: StoryObj = { + render: ReorderingWithVirtualizationStory, +}; + export const StickyHeader: StoryObj = { render: StickyHeaderStory, }; diff --git a/src/components/Table/__stories__/stories/ReorderingStory.tsx b/src/components/Table/__stories__/stories/ReorderingStory.tsx new file mode 100644 index 0000000..e567b7a --- /dev/null +++ b/src/components/Table/__stories__/stories/ReorderingStory.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import type {ColumnDef} from '@tanstack/react-table'; + +import {dragHandleColumn} from '../../../../constants'; +import {useTable} from '../../../../hooks'; +import {columns as originalColumns} from '../../../BaseTable/__stories__/constants/columns'; +import {data as originalData} from '../../../BaseTable/__stories__/constants/data'; +import type {Item} from '../../../BaseTable/__stories__/types'; +import type {ReorderingProviderProps} from '../../../ReorderingProvider'; +import {ReorderingProvider} from '../../../ReorderingProvider'; +import {Table} from '../../Table'; + +const columns: ColumnDef[] = [dragHandleColumn as ColumnDef, ...originalColumns]; + +export const ReorderingStory = () => { + const [data, setData] = React.useState(originalData); + + const table = useTable({ + columns, + data, + getRowId: (item) => item.id, + }); + + const handleReorder = React.useCallback< + NonNullable['onReorder']> + >(({draggedItemKey, baseItemKey}) => { + setData((prevData) => { + const dataClone = prevData.slice(); + + const index = dataClone.findIndex((item) => item.id === draggedItemKey); + + if (index >= 0) { + const dragged = dataClone.splice(index, 1)[0] as Item; + const insertIndex = dataClone.findIndex((item) => item.id === baseItemKey); + + if (insertIndex >= 0) { + dataClone.splice(insertIndex + 1, 0, dragged); + } else { + dataClone.unshift(dragged); + } + } + + return dataClone; + }); + }, []); + + return ( + + + + ); +}; diff --git a/src/components/Table/__stories__/stories/ReorderingWithVirtualizationStory.tsx b/src/components/Table/__stories__/stories/ReorderingWithVirtualizationStory.tsx new file mode 100644 index 0000000..913929f --- /dev/null +++ b/src/components/Table/__stories__/stories/ReorderingWithVirtualizationStory.tsx @@ -0,0 +1,62 @@ +import React from 'react'; + +import type {ColumnDef} from '@tanstack/react-table'; + +import {dragHandleColumn} from '../../../../constants'; +import {useTable, useWindowRowVirtualizer} from '../../../../hooks'; +import {getVirtualRowRangeExtractor} from '../../../../utils'; +import {columns as originalColumns} from '../../../BaseTable/__stories__/constants/columns'; +import type {Item} from '../../../BaseTable/__stories__/types'; +import {generateData} from '../../../BaseTable/__stories__/utils'; +import type {ReorderingProviderProps} from '../../../ReorderingProvider'; +import {ReorderingProvider} from '../../../ReorderingProvider'; +import {Table} from '../../Table'; + +const columns: ColumnDef[] = [dragHandleColumn as ColumnDef, ...originalColumns]; + +export const ReorderingWithVirtualizationStory = () => { + const tableRef = React.useRef(null); + const [data, setData] = React.useState(() => generateData(300)); + + const table = useTable({ + columns, + data, + getRowId: (item) => item.id, + }); + + const rowVirtualizer = useWindowRowVirtualizer({ + count: table.getRowModel().rows.length, + estimateSize: () => 20, + overscan: 5, + rangeExtractor: getVirtualRowRangeExtractor(tableRef.current), + }); + + const handleReorder = React.useCallback< + NonNullable['onReorder']> + >(({draggedItemKey, baseItemKey}) => { + setData((prevData) => { + const dataClone = prevData.slice(); + + const index = dataClone.findIndex((item) => item.id === draggedItemKey); + + if (index >= 0) { + const dragged = dataClone.splice(index, 1)[0] as Item; + const insertIndex = dataClone.findIndex((item) => item.id === baseItemKey); + + if (insertIndex >= 0) { + dataClone.splice(insertIndex + 1, 0, dragged); + } else { + dataClone.unshift(dragged); + } + } + + return dataClone; + }); + }, []); + + return ( + +
+ + ); +}; diff --git a/src/components/index.ts b/src/components/index.ts index 7d61c68..a199cb5 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,7 +1,5 @@ export * from './BaseCell'; -export * from './BaseDragHandle'; export * from './BaseDraggableRow'; -export * from './BaseDraggableRowMarker'; export * from './BaseFooterCell'; export * from './BaseGroupHeader'; export * from './BaseHeaderCell'; @@ -10,6 +8,7 @@ export * from './ReorderingProvider'; export * from './BaseResizeHandle'; export * from './BaseRow'; export * from './BaseSortIndicator'; +export * from './DragHandle'; export * from './SortableList'; export * from './SortableListContext'; export * from './SortableListDndContext'; diff --git a/src/constants/defaultDragHandleColumn.tsx b/src/constants/defaultDragHandleColumn.tsx deleted file mode 100644 index b56dfd5..0000000 --- a/src/constants/defaultDragHandleColumn.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -import type {ColumnDef} from '@tanstack/react-table'; - -import {BaseDragHandle} from '../components'; - -export const defaultDragHandleColumn: ColumnDef = { - id: '_drag', - header: '', - cell: ({row, table}) => , - size: 14, - minSize: 14, -}; diff --git a/src/constants/dragHandleColumn.tsx b/src/constants/dragHandleColumn.tsx new file mode 100644 index 0000000..bacff84 --- /dev/null +++ b/src/constants/dragHandleColumn.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import type {ColumnDef} from '@tanstack/react-table'; + +import {DragHandle} from '../components'; + +export const dragHandleColumn: ColumnDef = { + id: '_drag', + header: '', + cell: ({row, table}) => , + size: 32, + minSize: 32, +}; diff --git a/src/constants/index.ts b/src/constants/index.ts index 8ebbb4e..07a6f63 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,2 +1,2 @@ -export * from './defaultDragHandleColumn'; export * from './defaultSelectionColumn'; +export * from './dragHandleColumn'; diff --git a/src/index.ts b/src/index.ts index 1337ca7..0bbdf59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ export {BaseTable, ReorderingProvider, Table} from './components'; export type {BaseTableProps, ReorderingProviderProps, TableProps} from './components'; -export {defaultDragHandleColumn, defaultSelectionColumn} from './constants'; +export {dragHandleColumn, defaultSelectionColumn} from './constants'; export {useDraggableRowDepth, useRowVirtualizer, useTable, useWindowRowVirtualizer} from './hooks';