chore: add sortable group component which using react-beautiful-dnd

This commit is contained in:
moonrailgun 2024-09-13 00:31:12 +08:00
parent f309000a0c
commit 91ade2ab55
4 changed files with 147 additions and 0 deletions

View File

@ -288,6 +288,9 @@ importers:
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0 version: 18.2.0
react-beautiful-dnd:
specifier: ^13.1.1
version: 13.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-day-picker: react-day-picker:
specifier: ^8.10.1 specifier: ^8.10.1
version: 8.10.1(date-fns@3.6.0)(react@18.2.0) version: 8.10.1(date-fns@3.6.0)(react@18.2.0)
@ -376,6 +379,9 @@ importers:
'@types/react': '@types/react':
specifier: ^18.2.22 specifier: ^18.2.22
version: 18.2.78 version: 18.2.78
'@types/react-beautiful-dnd':
specifier: ^13.1.8
version: 13.1.8
'@types/react-dom': '@types/react-dom':
specifier: ^18.2.7 specifier: ^18.2.7
version: 18.2.7 version: 18.2.7
@ -4707,6 +4713,9 @@ packages:
'@types/range-parser@1.2.4': '@types/range-parser@1.2.4':
resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==}
'@types/react-beautiful-dnd@13.1.8':
resolution: {integrity: sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==}
'@types/react-dom@18.2.7': '@types/react-dom@18.2.7':
resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==}
@ -6103,6 +6112,9 @@ packages:
resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==}
engines: {node: '>=12'} engines: {node: '>=12'}
css-box-model@1.2.1:
resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==}
css-color-converter@2.0.0: css-color-converter@2.0.0:
resolution: {integrity: sha512-oLIG2soZz3wcC3aAl/7Us5RS8Hvvc6I8G8LniF/qfMmrm7fIKQ8RIDDRZeKyGL2SrWfNqYspuLShbnjBMVWm8g==} resolution: {integrity: sha512-oLIG2soZz3wcC3aAl/7Us5RS8Hvvc6I8G8LniF/qfMmrm7fIKQ8RIDDRZeKyGL2SrWfNqYspuLShbnjBMVWm8g==}
@ -8729,6 +8741,9 @@ packages:
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
engines: {node: '>= 4.0.0'} engines: {node: '>= 4.0.0'}
memoize-one@5.2.1:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
meow@12.1.1: meow@12.1.1:
resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==}
engines: {node: '>=16.10'} engines: {node: '>=16.10'}
@ -10417,6 +10432,9 @@ packages:
radix3@1.1.0: radix3@1.1.0:
resolution: {integrity: sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A==} resolution: {integrity: sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A==}
raf-schd@4.0.3:
resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==}
randombytes@2.1.0: randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@ -10764,6 +10782,12 @@ packages:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true hasBin: true
react-beautiful-dnd@13.1.1:
resolution: {integrity: sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==}
peerDependencies:
react: ^16.8.5 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0
react-color@2.17.1: react-color@2.17.1:
resolution: {integrity: sha512-S+I6TkUKJaqfALLkAIfiCZ/MANQyy7dKkf7g9ZU5GTUy2rf8c2Rx62otyvADAviWR+6HRkzdf2vL1Qvz9goCLQ==} resolution: {integrity: sha512-S+I6TkUKJaqfALLkAIfiCZ/MANQyy7dKkf7g9ZU5GTUy2rf8c2Rx62otyvADAviWR+6HRkzdf2vL1Qvz9goCLQ==}
peerDependencies: peerDependencies:
@ -12617,6 +12641,11 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
use-memo-one@1.1.3:
resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
use-sidecar@1.1.2: use-sidecar@1.1.2:
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -18765,6 +18794,10 @@ snapshots:
'@types/range-parser@1.2.4': {} '@types/range-parser@1.2.4': {}
'@types/react-beautiful-dnd@13.1.8':
dependencies:
'@types/react': 18.2.78
'@types/react-dom@18.2.7': '@types/react-dom@18.2.7':
dependencies: dependencies:
'@types/react': 18.2.78 '@types/react': 18.2.78
@ -20466,6 +20499,10 @@ snapshots:
dependencies: dependencies:
type-fest: 1.4.0 type-fest: 1.4.0
css-box-model@1.2.1:
dependencies:
tiny-invariant: 1.3.3
css-color-converter@2.0.0: css-color-converter@2.0.0:
dependencies: dependencies:
color-convert: 0.5.3 color-convert: 0.5.3
@ -23830,6 +23867,8 @@ snapshots:
dependencies: dependencies:
fs-monkey: 1.0.5 fs-monkey: 1.0.5
memoize-one@5.2.1: {}
meow@12.1.1: {} meow@12.1.1: {}
merge-descriptors@1.0.1: {} merge-descriptors@1.0.1: {}
@ -26000,6 +26039,8 @@ snapshots:
radix3@1.1.0: {} radix3@1.1.0: {}
raf-schd@4.0.3: {}
randombytes@2.1.0: randombytes@2.1.0:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
@ -26710,6 +26751,20 @@ snapshots:
minimist: 1.2.8 minimist: 1.2.8
strip-json-comments: 2.0.1 strip-json-comments: 2.0.1
react-beautiful-dnd@13.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.24.0
css-box-model: 1.2.1
memoize-one: 5.2.1
raf-schd: 4.0.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-redux: 7.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
redux: 4.2.1
use-memo-one: 1.1.3(react@18.2.0)
transitivePeerDependencies:
- react-native
react-color@2.17.1(react@18.2.0): react-color@2.17.1(react@18.2.0):
dependencies: dependencies:
'@icons/material': 0.2.4(react@18.2.0) '@icons/material': 0.2.4(react@18.2.0)
@ -26896,6 +26951,18 @@ snapshots:
react-magic-dropzone@1.0.1: {} react-magic-dropzone@1.0.1: {}
react-redux@7.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.24.0
'@types/react-redux': 7.1.33
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
prop-types: 15.8.1
react: 18.2.0
react-is: 17.0.2
optionalDependencies:
react-dom: 18.2.0(react@18.2.0)
react-redux@7.2.9(react-dom@18.2.0(react@18.3.1))(react@18.3.1): react-redux@7.2.9(react-dom@18.2.0(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
'@babel/runtime': 7.24.0 '@babel/runtime': 7.24.0
@ -29066,6 +29133,10 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 18.2.78 '@types/react': 18.2.78
use-memo-one@1.1.3(react@18.2.0):
dependencies:
react: 18.2.0
use-sidecar@1.1.2(@types/react@18.2.78)(react@18.2.0): use-sidecar@1.1.2(@types/react@18.2.78)(react@18.2.0):
dependencies: dependencies:
detect-node-es: 1.1.0 detect-node-es: 1.1.0

View File

@ -0,0 +1,62 @@
import React, { useState } from 'react';
import { DragDropContext, DropResult } from 'react-beautiful-dnd';
import { useEvent } from '@/hooks/useEvent';
import { reorder } from '@/utils/reorder';
interface BasicItem {
type: 'root' | 'group' | 'item';
items?: BasicItem[];
}
interface SortableGroupProps {
list: BasicItem[];
onChange: (list: BasicItem[]) => void;
children: React.ReactNode;
}
export const SortableGroup: React.FC<SortableGroupProps> = React.memo(
(props) => {
const [list, setList] = useState(props.list);
const handleDragEnd = useEvent((result: DropResult) => {
// dropped outside the list
if (!result.destination) {
return;
}
if (result.type === 'root') {
setList(reorder(list, result.source.index, result.destination.index));
return;
}
if (result.type === 'group') {
const nestedIndex = list.findIndex((item): boolean => 'items' in item);
if (nestedIndex === 0) {
return;
}
const nested = list[nestedIndex].items;
if (!nested) {
return;
}
const children = Array.from(list);
children[nestedIndex].items = reorder(
nested,
result.source.index,
result.destination.index
);
setList([...list]);
}
});
return (
<DragDropContext onDragEnd={handleDragEnd}>
{props.children}
</DragDropContext>
);
}
);
SortableGroup.displayName = 'SortableGroup';

View File

@ -75,6 +75,7 @@
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"pretty-ms": "^9.0.0", "pretty-ms": "^9.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-easy-sort": "^1.5.3", "react-easy-sort": "^1.5.3",
@ -106,6 +107,7 @@
"@types/loadable__component": "^5.13.8", "@types/loadable__component": "^5.13.8",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/react": "^18.2.22", "@types/react": "^18.2.22",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.7",
"@types/react-grid-layout": "^1.3.5", "@types/react-grid-layout": "^1.3.5",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",

View File

@ -0,0 +1,12 @@
// a little function to help us with reordering the result
export function reorder<T>(
list: T[],
startIndex: number,
endIndex: number
): T[] {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
}