mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 12:39:49 +08:00
demo: remove react-dnd, use dnd-kit (#40674)
* demo: remove react-dnd, use dnd-kit * change type
This commit is contained in:
parent
774bd8e6aa
commit
c48c712371
12
.jest.js
12
.jest.js
@ -1,14 +1,4 @@
|
||||
const compileModules = [
|
||||
'array-move',
|
||||
'react-dnd',
|
||||
'react-dnd-html5-backend',
|
||||
'@react-dnd',
|
||||
'dnd-core',
|
||||
'react-sticky-box',
|
||||
'tween-one',
|
||||
'@babel',
|
||||
'@ant-design',
|
||||
];
|
||||
const compileModules = ['dnd-core', 'react-sticky-box', 'tween-one', '@babel', '@ant-design'];
|
||||
|
||||
const ignoreList = [];
|
||||
|
||||
|
@ -1089,54 +1089,60 @@ exports[`renders ./components/tabs/demo/custom-tab-bar-node.tsx extend context c
|
||||
style="transform:translate(0px, 0px)"
|
||||
>
|
||||
<div
|
||||
style="margin-right:24px"
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class="ant-tabs-tab ant-tabs-tab-active"
|
||||
data-node-key="1"
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-tabs-tab ant-tabs-tab-active"
|
||||
data-node-key="1"
|
||||
aria-selected="true"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-selected="true"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
tab 1
|
||||
</div>
|
||||
Tab 1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="margin-right:24px"
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class="ant-tabs-tab"
|
||||
data-node-key="2"
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-tabs-tab"
|
||||
data-node-key="2"
|
||||
aria-selected="false"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
tab 2
|
||||
</div>
|
||||
Tab 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="margin-right:24px"
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class="ant-tabs-tab"
|
||||
data-node-key="3"
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-tabs-tab"
|
||||
data-node-key="3"
|
||||
aria-selected="false"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
tab 3
|
||||
</div>
|
||||
Tab 3
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -956,54 +956,60 @@ exports[`renders ./components/tabs/demo/custom-tab-bar-node.tsx correctly 1`] =
|
||||
style="transform:translate(0px, 0px)"
|
||||
>
|
||||
<div
|
||||
style="margin-right:24px"
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class="ant-tabs-tab ant-tabs-tab-active"
|
||||
data-node-key="1"
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-tabs-tab ant-tabs-tab-active"
|
||||
data-node-key="1"
|
||||
aria-selected="true"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-selected="true"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
tab 1
|
||||
</div>
|
||||
Tab 1
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="margin-right:24px"
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class="ant-tabs-tab"
|
||||
data-node-key="2"
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-tabs-tab"
|
||||
data-node-key="2"
|
||||
aria-selected="false"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
tab 2
|
||||
</div>
|
||||
Tab 2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="margin-right:24px"
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class="ant-tabs-tab"
|
||||
data-node-key="3"
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-tabs-tab"
|
||||
data-node-key="3"
|
||||
aria-selected="false"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-tabs-tab-btn"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
tab 3
|
||||
</div>
|
||||
Tab 3
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -1,13 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
使用 `react-dnd@15+` 实现标签可拖拽。
|
||||
使用 [dnd-kit](https://github.com/clauderic/dnd-kit) 实现标签可拖拽。
|
||||
|
||||
## en-US
|
||||
|
||||
Use `react-dnd@15+` to make tabs draggable.
|
||||
|
||||
```css
|
||||
.dropping {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
```
|
||||
Use [dnd-kit](https://github.com/clauderic/dnd-kit) to make tabs draggable.
|
||||
|
@ -1,125 +1,112 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import type { TabsProps } from 'antd';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import { DndContext, PointerSensor, useSensor } from '@dnd-kit/core';
|
||||
import {
|
||||
arrayMove,
|
||||
horizontalListSortingStrategy,
|
||||
SortableContext,
|
||||
useSortable,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { css } from '@emotion/css';
|
||||
import { Tabs } from 'antd';
|
||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
const type = 'DraggableTabNode';
|
||||
interface DraggableTabPaneProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
index: React.Key;
|
||||
moveNode: (dragIndex: React.Key, hoverIndex: React.Key) => void;
|
||||
'data-node-key': string;
|
||||
onActiveBarTransform: (className: string) => void;
|
||||
}
|
||||
|
||||
const DraggableTabNode = ({ index, children, moveNode }: DraggableTabPaneProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [{ isOver }, drop] = useDrop({
|
||||
accept: type,
|
||||
collect: (monitor) => {
|
||||
const { index: dragIndex } = monitor.getItem() || {};
|
||||
if (dragIndex === index) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
isOver: monitor.isOver(),
|
||||
};
|
||||
},
|
||||
drop: (item: { index: React.Key }) => {
|
||||
moveNode(item.index, index);
|
||||
},
|
||||
const DraggableTabNode = ({ className, onActiveBarTransform, ...props }: DraggableTabPaneProps) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isSorting } = useSortable({
|
||||
id: props['data-node-key'],
|
||||
});
|
||||
const [, drag] = useDrag({
|
||||
type,
|
||||
item: { index },
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
});
|
||||
drop(drag(ref));
|
||||
|
||||
// Style
|
||||
const style: React.CSSProperties = { marginRight: 24 };
|
||||
if (isOver) {
|
||||
style.transition = 'all 0.3s';
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={ref} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DraggableTabs: React.FC<TabsProps> = (props) => {
|
||||
const { items = [] } = props;
|
||||
const [order, setOrder] = useState<React.Key[]>([]);
|
||||
|
||||
const moveTabNode = (dragKey: React.Key, hoverKey: React.Key) => {
|
||||
const newOrder = order.slice();
|
||||
|
||||
items.forEach((item) => {
|
||||
if (item.key && newOrder.indexOf(item.key) === -1) {
|
||||
newOrder.push(item.key);
|
||||
}
|
||||
});
|
||||
|
||||
const dragIndex = newOrder.indexOf(dragKey);
|
||||
const hoverIndex = newOrder.indexOf(hoverKey);
|
||||
|
||||
newOrder.splice(dragIndex, 1);
|
||||
newOrder.splice(hoverIndex, 0, dragKey);
|
||||
|
||||
setOrder(newOrder);
|
||||
const style: React.CSSProperties = {
|
||||
...props.style,
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
cursor: 'move',
|
||||
};
|
||||
|
||||
const renderTabBar: TabsProps['renderTabBar'] = (tabBarProps, DefaultTabBar) => (
|
||||
<DefaultTabBar {...tabBarProps}>
|
||||
{(node) => (
|
||||
<DraggableTabNode key={node.key} index={node.key!} moveNode={moveTabNode}>
|
||||
{node}
|
||||
</DraggableTabNode>
|
||||
)}
|
||||
</DefaultTabBar>
|
||||
);
|
||||
|
||||
const orderItems = [...items].sort((a, b) => {
|
||||
const orderA = order.indexOf(a.key!);
|
||||
const orderB = order.indexOf(b.key!);
|
||||
|
||||
if (orderA !== -1 && orderB !== -1) {
|
||||
return orderA - orderB;
|
||||
}
|
||||
if (orderA !== -1) {
|
||||
return -1;
|
||||
}
|
||||
if (orderB !== -1) {
|
||||
return 1;
|
||||
useEffect(() => {
|
||||
if (!isSorting) {
|
||||
onActiveBarTransform('');
|
||||
} else if (className?.includes('ant-tabs-tab-active')) {
|
||||
onActiveBarTransform(
|
||||
css`
|
||||
.ant-tabs-ink-bar {
|
||||
transform: ${CSS.Transform.toString(transform)};
|
||||
transition: ${transition} !important;
|
||||
}
|
||||
`,
|
||||
);
|
||||
}
|
||||
}, [className, isSorting, transform]);
|
||||
|
||||
const ia = items.indexOf(a);
|
||||
const ib = items.indexOf(b);
|
||||
|
||||
return ia - ib;
|
||||
return React.cloneElement(props.children as React.ReactElement, {
|
||||
ref: setNodeRef,
|
||||
style,
|
||||
...attributes,
|
||||
...listeners,
|
||||
});
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [items, setItems] = useState([
|
||||
{
|
||||
key: '1',
|
||||
label: `Tab 1`,
|
||||
children: 'Content of Tab Pane 1',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: `Tab 2`,
|
||||
children: 'Content of Tab Pane 2',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: `Tab 3`,
|
||||
children: 'Content of Tab Pane 3',
|
||||
},
|
||||
]);
|
||||
|
||||
const [className, setClassName] = useState('');
|
||||
|
||||
const sensor = useSensor(PointerSensor, { activationConstraint: { distance: 10 } });
|
||||
|
||||
const onDragEnd = ({ active, over }: DragEndEvent) => {
|
||||
if (active.id !== over?.id) {
|
||||
setItems((prev) => {
|
||||
const activeIndex = prev.findIndex((i) => i.key === active.id);
|
||||
const overIndex = prev.findIndex((i) => i.key === over?.id);
|
||||
return arrayMove(prev, activeIndex, overIndex);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Tabs renderTabBar={renderTabBar} {...props} items={orderItems} />
|
||||
</DndProvider>
|
||||
<Tabs
|
||||
className={className}
|
||||
items={items}
|
||||
renderTabBar={(tabBarProps, DefaultTabBar) => (
|
||||
<DndContext sensors={[sensor]} onDragEnd={onDragEnd}>
|
||||
<SortableContext items={items.map((i) => i.key)} strategy={horizontalListSortingStrategy}>
|
||||
<DefaultTabBar {...tabBarProps}>
|
||||
{(node) => (
|
||||
<DraggableTabNode
|
||||
{...node.props}
|
||||
key={node.key}
|
||||
onActiveBarTransform={setClassName}
|
||||
>
|
||||
{node}
|
||||
</DraggableTabNode>
|
||||
)}
|
||||
</DefaultTabBar>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<DraggableTabs
|
||||
items={new Array(3).fill(null).map((_, i) => {
|
||||
const id = String(i + 1);
|
||||
return {
|
||||
label: `tab ${id}`,
|
||||
key: id,
|
||||
children: `Content of Tab Pane ${id}`,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
@ -668,8 +668,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx extend context correc
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-done"
|
||||
@ -743,8 +748,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx extend context correc
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-done"
|
||||
@ -818,8 +828,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx extend context correc
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-done"
|
||||
@ -893,8 +908,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx extend context correc
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-done"
|
||||
@ -968,8 +988,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx extend context correc
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-error"
|
||||
@ -1053,7 +1078,7 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx extend context correc
|
||||
class="ant-tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
Upload Error
|
||||
Upload error
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -644,8 +644,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx correctly 1`] = `
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-done"
|
||||
@ -719,8 +724,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx correctly 1`] = `
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-done"
|
||||
@ -794,8 +804,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx correctly 1`] = `
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-done"
|
||||
@ -869,8 +884,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx correctly 1`] = `
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-done"
|
||||
@ -944,8 +964,13 @@ exports[`renders ./components/upload/demo/drag-sorting.tsx correctly 1`] = `
|
||||
class="ant-upload-list-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-draggable-list-item "
|
||||
aria-describedby="DndDescribedBy-0"
|
||||
aria-disabled="false"
|
||||
aria-roledescription="sortable"
|
||||
class=""
|
||||
role="button"
|
||||
style="cursor:move"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="ant-upload-list-item ant-upload-list-item-error"
|
||||
|
@ -1,20 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
使用 `itemRender` ,我们可以集成 react-dnd 来实现对上传列表拖拽排序。
|
||||
使用 `itemRender` ,我们可以集成 [dnd-kit](https://github.com/clauderic/dnd-kit) 来实现对上传列表拖拽排序。
|
||||
|
||||
## en-US
|
||||
|
||||
By using `itemRender`, we can integrate upload with react-dnd to implement drag sorting of uploadList.
|
||||
|
||||
```css
|
||||
#components-upload-demo-drag-sorting .ant-upload-draggable-list-item {
|
||||
border-top: 2px dashed rgba(0, 0, 0, 0);
|
||||
border-bottom: 2px dashed rgba(0, 0, 0, 0);
|
||||
}
|
||||
#components-upload-demo-drag-sorting .ant-upload-draggable-list-item.drop-over-downward {
|
||||
border-bottom-color: #1890ff;
|
||||
}
|
||||
#components-upload-demo-drag-sorting .ant-upload-draggable-list-item.drop-over-upward {
|
||||
border-top-color: #1890ff;
|
||||
}
|
||||
```
|
||||
By using `itemRender`, we can integrate upload with [dnd-kit](https://github.com/clauderic/dnd-kit) to implement drag sorting of uploadList.
|
||||
|
@ -1,59 +1,47 @@
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip, Upload } from 'antd';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import { DndContext } from '@dnd-kit/core';
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { css } from '@emotion/css';
|
||||
import { Button, Upload } from 'antd';
|
||||
import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
|
||||
const type = 'DragableUploadList';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
interface DragableUploadListItemProps {
|
||||
originNode: React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
||||
file: UploadFile;
|
||||
fileList: UploadFile[];
|
||||
moveRow: (dragIndex: any, hoverIndex: any) => void;
|
||||
file: UploadFile<any>;
|
||||
}
|
||||
|
||||
const DragableUploadListItem = ({
|
||||
originNode,
|
||||
moveRow,
|
||||
file,
|
||||
fileList,
|
||||
}: DragableUploadListItemProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const index = fileList.indexOf(file);
|
||||
const [{ isOver, dropClassName }, drop] = useDrop({
|
||||
accept: type,
|
||||
collect: (monitor) => {
|
||||
const { index: dragIndex } = monitor.getItem() || {};
|
||||
if (dragIndex === index) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
isOver: monitor.isOver(),
|
||||
dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
|
||||
};
|
||||
},
|
||||
drop: (item: any) => {
|
||||
moveRow(item.index, index);
|
||||
},
|
||||
const DragableUploadListItem = ({ originNode, file }: DragableUploadListItemProps) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: file.uid,
|
||||
});
|
||||
const [, drag] = useDrag({
|
||||
type,
|
||||
item: { index },
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
});
|
||||
drop(drag(ref));
|
||||
const errorNode = <Tooltip title="Upload Error">{originNode.props.children}</Tooltip>;
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
cursor: 'move',
|
||||
};
|
||||
|
||||
// prevent preview event when drag end
|
||||
const className = isDragging
|
||||
? css`
|
||||
a {
|
||||
pointer-events: none;
|
||||
}
|
||||
`
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`ant-upload-draggable-list-item ${isOver ? dropClassName : ''}`}
|
||||
style={{ cursor: 'move' }}
|
||||
>
|
||||
{file.status === 'error' ? errorNode : originNode}
|
||||
<div ref={setNodeRef} style={style} className={className} {...attributes} {...listeners}>
|
||||
{/* hide error tooltip when dragging */}
|
||||
{file.status === 'error' && isDragging ? originNode.props.children : originNode}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -91,41 +79,35 @@ const App: React.FC = () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const moveRow = useCallback(
|
||||
(dragIndex: number, hoverIndex: number) => {
|
||||
const dragRow = fileList[dragIndex];
|
||||
setFileList((currentFileList) => {
|
||||
const newFileList = [...currentFileList];
|
||||
newFileList.splice(dragIndex, 1);
|
||||
newFileList.splice(hoverIndex, 0, dragRow);
|
||||
return newFileList;
|
||||
const onDragEnd = ({ active, over }: DragEndEvent) => {
|
||||
if (active.id !== over?.id) {
|
||||
setFileList((prev) => {
|
||||
const activeIndex = prev.findIndex((i) => i.uid === active.id);
|
||||
const overIndex = prev.findIndex((i) => i.uid === over?.id);
|
||||
return arrayMove(prev, activeIndex, overIndex);
|
||||
});
|
||||
},
|
||||
[fileList],
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
|
||||
setFileList(newFileList);
|
||||
};
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Upload
|
||||
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
|
||||
fileList={fileList}
|
||||
onChange={onChange}
|
||||
itemRender={(originNode, file, currFileList) => (
|
||||
<DragableUploadListItem
|
||||
originNode={originNode}
|
||||
file={file}
|
||||
fileList={currFileList}
|
||||
moveRow={moveRow}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<Button icon={<UploadOutlined />}>Click to Upload</Button>
|
||||
</Upload>
|
||||
</DndProvider>
|
||||
<DndContext onDragEnd={onDragEnd}>
|
||||
<SortableContext items={fileList.map((i) => i.uid)} strategy={verticalListSortingStrategy}>
|
||||
<Upload
|
||||
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
|
||||
fileList={fileList}
|
||||
onChange={onChange}
|
||||
itemRender={(originNode, file) => (
|
||||
<DragableUploadListItem originNode={originNode} file={file} />
|
||||
)}
|
||||
>
|
||||
<Button icon={<UploadOutlined />}>Click to Upload</Button>
|
||||
</Upload>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -196,7 +196,6 @@
|
||||
"@typescript-eslint/parser": "^5.40.0",
|
||||
"antd-img-crop": "^4.2.8",
|
||||
"antd-token-previewer": "^1.1.0-21",
|
||||
"array-move": "^4.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"cross-env": "^7.0.0",
|
||||
@ -256,8 +255,6 @@
|
||||
"react-color": "^2.17.3",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-countup": "^6.4.0",
|
||||
"react-dnd": "^16.0.0",
|
||||
"react-dnd-html5-backend": "^16.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-draggable": "^4.4.3",
|
||||
"react-fast-marquee": "^1.2.1",
|
||||
|
Loading…
Reference in New Issue
Block a user