demo: add List drag sorting examples (#55077)

* docs: List drag sorting examples

* refactor: standardize React imports and use consistent import patterns

* fix: add null checks and remove optional chaining in drag sorting handlers

* Update components/list/demo/grid-drag-sorting.md

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: FatahChan <ahmadfathallah89@gmail.com>

* Update components/list/demo/grid-drag-sorting-handler.md

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: FatahChan <ahmadfathallah89@gmail.com>

* chore: add React import to list demo components

* fix: tests

* refactor: improve drag sorting accessibility by wrapping list items in semantic containers

* Update components/list/demo/drag-sorting-handler.tsx

Co-authored-by: lijianan <574980606@qq.com>
Signed-off-by: FatahChan <ahmadfathallah89@gmail.com>

* Update components/list/demo/grid-drag-sorting.tsx

Co-authored-by: lijianan <574980606@qq.com>
Signed-off-by: FatahChan <ahmadfathallah89@gmail.com>

* Update components/list/demo/drag-sorting.tsx

Co-authored-by: lijianan <574980606@qq.com>
Signed-off-by: FatahChan <ahmadfathallah89@gmail.com>

* Update components/list/demo/grid-drag-sorting-handler.tsx

Co-authored-by: lijianan <574980606@qq.com>
Signed-off-by: FatahChan <ahmadfathallah89@gmail.com>

* fix: remove itemkey prop from list items in drag-sorting demos

* test: skip drag sorting demos in list component tests

* fix: update drag-sorting aria-describedby IDs to be more semantic and consistent

* test: update DndLiveRegion IDs in list component snapshots

* test: skip drag sorting demos in list component tests

dnd ids produce difference ids in react 17 vs react 19, and fails the demo-extend as the snap is different

* refactor: update list drag sorting to use object items with unique keys

* update code style

* update code style

---------

Signed-off-by: FatahChan <ahmadfathallah89@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: lijianan <574980606@qq.com>
This commit is contained in:
FatahChan
2025-09-19 00:55:38 +03:00
committed by GitHub
parent 74c26d895d
commit 6562c20134
13 changed files with 2566 additions and 1 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,11 @@
import { extendTest } from '../../../tests/shared/demoTest';
extendTest('list', { skip: ['virtual-list.tsx'] });
extendTest('list', {
skip: [
'virtual-list.tsx',
'grid-drag-sorting.tsx',
'grid-drag-sorting-handler.tsx',
'drag-sorting.tsx',
'drag-sorting-handler.tsx',
],
});

View File

@@ -0,0 +1,7 @@
## zh-CN
使用 [dnd-kit](https://github.com/clauderic/dnd-kit) 来实现一个拖拽操作列。
## en-US
Alternatively you can implement drag sorting with handler using [dnd-kit](https://github.com/clauderic/dnd-kit).

View File

@@ -0,0 +1,113 @@
import React, { createContext, useContext, useMemo, useState } from 'react';
import { HolderOutlined } from '@ant-design/icons';
import type { DragEndEvent, DraggableAttributes } from '@dnd-kit/core';
import { DndContext } from '@dnd-kit/core';
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Button, GetProps, List } from 'antd';
interface SortableListItemContextProps {
setActivatorNodeRef?: (element: HTMLElement | null) => void;
listeners?: SyntheticListenerMap;
attributes?: DraggableAttributes;
}
const SortableListItemContext = createContext<SortableListItemContextProps>({});
const DragHandle: React.FC = () => {
const { setActivatorNodeRef, listeners, attributes } = useContext(SortableListItemContext);
return (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{ cursor: 'move' }}
ref={setActivatorNodeRef}
{...attributes}
{...listeners}
/>
);
};
const SortableListItem: React.FC<GetProps<typeof List.Item> & { itemKey: number }> = (props) => {
const { itemKey, style, ...rest } = props;
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: itemKey });
const listStyle: React.CSSProperties = {
...style,
transform: CSS.Translate.toString(transform),
transition,
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
const memoizedValue = useMemo<SortableListItemContextProps>(
() => ({ setActivatorNodeRef, listeners, attributes }),
[setActivatorNodeRef, listeners, attributes],
);
return (
<SortableListItemContext.Provider value={memoizedValue}>
<List.Item {...rest} ref={setNodeRef} style={listStyle} />
</SortableListItemContext.Provider>
);
};
const App: React.FC = () => {
const [data, setData] = useState([
{ key: 1, content: 'Racing car sprays burning fuel into crowd.' },
{ key: 2, content: 'Japanese princess to wed commoner.' },
{ key: 3, content: 'Australian walks 100km after outback crash.' },
{ key: 4, content: 'Man charged over missing wedding girl.' },
{ key: 5, content: 'Los Angeles battles huge wildfires.' },
]);
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (!active || !over) {
return;
}
if (active.id !== over.id) {
setData((prevState) => {
const activeIndex = prevState.findIndex((i) => i.key === active.id);
const overIndex = prevState.findIndex((i) => i.key === over.id);
return arrayMove(prevState, activeIndex, overIndex);
});
}
};
return (
<DndContext
modifiers={[restrictToVerticalAxis]}
onDragEnd={onDragEnd}
id="list-drag-sorting-handler"
>
<SortableContext items={data.map((item) => item.key)} strategy={verticalListSortingStrategy}>
<List
dataSource={data}
renderItem={(item) => (
<SortableListItem key={item.key} itemKey={item.key}>
<DragHandle /> {item.key} {item.content}
</SortableListItem>
)}
/>
</SortableContext>
</DndContext>
);
};
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
使用自定义元素,我们可以集成 [dnd-kit](https://github.com/clauderic/dnd-kit) 来实现拖拽排序。
## en-US
By using `components`, we can integrate List with [dnd-kit](https://github.com/clauderic/dnd-kit) to implement drag sorting function.

View File

@@ -0,0 +1,91 @@
import React, { useState } from 'react';
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { List } from 'antd';
import type { GetProps } from 'antd';
const SortableListItem: React.FC<GetProps<typeof List.Item> & { itemKey: number }> = (props) => {
const { itemKey, style, children, ...rest } = props;
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: itemKey,
});
const listStyle: React.CSSProperties = {
...style,
transform: CSS.Translate.toString(transform),
transition,
cursor: 'move',
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
return (
<List.Item {...rest} ref={setNodeRef} style={listStyle}>
<div {...attributes} {...listeners}>
{children}
</div>
</List.Item>
);
};
const App: React.FC = () => {
const [data, setData] = useState([
{ key: 1, content: 'Racing car sprays burning fuel into crowd.' },
{ key: 2, content: 'Japanese princess to wed commoner.' },
{ key: 3, content: 'Australian walks 100km after outback crash.' },
{ key: 4, content: 'Man charged over missing wedding girl.' },
{ key: 5, content: 'Los Angeles battles huge wildfires.' },
]);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
// https://docs.dndkit.com/api-documentation/sensors/pointer#activation-constraints
distance: 1,
},
}),
);
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (!active || !over) {
return;
}
if (active.id !== over.id) {
setData((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 (
<DndContext
sensors={sensors}
modifiers={[restrictToVerticalAxis]}
onDragEnd={onDragEnd}
id="list-drag-sorting"
>
<SortableContext items={data.map((item) => item.key)} strategy={verticalListSortingStrategy}>
<List
dataSource={data}
renderItem={(item) => (
<SortableListItem key={item.key} itemKey={item.key}>
{item.key} {item.content}
</SortableListItem>
)}
/>
</SortableContext>
</DndContext>
);
};
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
使用自定义元素和拖拽手柄,我们可以集成 [dnd-kit](https://github.com/clauderic/dnd-kit) 来实现网格布局的拖拽排序。
## en-US
By using custom components and drag handles, we can integrate List with [dnd-kit](https://github.com/clauderic/dnd-kit) to implement drag sorting function for grid layout.

View File

@@ -0,0 +1,114 @@
import React, { createContext, useContext, useMemo, useState } from 'react';
import { HolderOutlined } from '@ant-design/icons';
import type { DragEndEvent, DraggableAttributes } from '@dnd-kit/core';
import { DndContext } from '@dnd-kit/core';
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Button, Card, GetProps, List } from 'antd';
interface SortableListItemContextProps {
setActivatorNodeRef?: (element: HTMLElement | null) => void;
listeners?: SyntheticListenerMap;
attributes?: DraggableAttributes;
}
const SortableListItemContext = createContext<SortableListItemContextProps>({});
const DragHandle: React.FC = () => {
const { setActivatorNodeRef, listeners, attributes } = useContext(SortableListItemContext);
return (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{ cursor: 'move' }}
ref={setActivatorNodeRef}
{...attributes}
{...listeners}
/>
);
};
const SortableListItem: React.FC<GetProps<typeof List.Item> & { itemKey: number }> = (props) => {
const { itemKey, style, ...rest } = props;
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: itemKey });
const listStyle: React.CSSProperties = {
...style,
transform: CSS.Translate.toString(transform),
transition,
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
const memoizedValue = useMemo<SortableListItemContextProps>(
() => ({ setActivatorNodeRef, listeners, attributes }),
[setActivatorNodeRef, listeners, attributes],
);
return (
<SortableListItemContext.Provider value={memoizedValue}>
<List.Item {...rest} ref={setNodeRef} style={listStyle} />
</SortableListItemContext.Provider>
);
};
const App: React.FC = () => {
const [data, setData] = useState([
{ key: 1, title: 'Title 1' },
{ key: 2, title: 'Title 2' },
{ key: 3, title: 'Title 3' },
{ key: 4, title: 'Title 4' },
{ key: 5, title: 'Title 5' },
{ key: 6, title: 'Title 6' },
]);
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (!active || !over) {
return;
}
if (active.id !== over.id) {
setData((prevState) => {
const activeIndex = prevState.findIndex((i) => i.key === active.id);
const overIndex = prevState.findIndex((i) => i.key === over.id);
return arrayMove(prevState, activeIndex, overIndex);
});
}
};
return (
<DndContext onDragEnd={onDragEnd} id="list-grid-drag-sorting-handler">
<SortableContext items={data.map((i) => i.key)}>
<List
grid={{ gutter: 16, column: 4 }}
dataSource={data}
renderItem={(item) => (
<SortableListItem key={item.key} itemKey={item.key}>
<Card
title={
<>
<DragHandle />
{item.title}
</>
}
>
Card content
</Card>
</SortableListItem>
)}
/>
</SortableContext>
</DndContext>
);
};
export default App;

View File

@@ -0,0 +1,7 @@
## zh-CN
使用自定义元素,我们可以集成 [dnd-kit](https://github.com/clauderic/dnd-kit) 来实现网格布局的拖拽排序。
## en-US
By using custom components, we can integrate List with [dnd-kit](https://github.com/clauderic/dnd-kit) to implement drag sorting function for grid layout.

View File

@@ -0,0 +1,76 @@
import React, { useState } from 'react';
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Card, List } from 'antd';
import type { GetProps } from 'antd';
const SortableListItem: React.FC<GetProps<typeof List.Item> & { itemKey: number }> = (props) => {
const { itemKey, style, ...rest } = props;
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: itemKey,
});
const listStyle: React.CSSProperties = {
...style,
transform: CSS.Translate.toString(transform),
transition,
cursor: 'move',
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
return <List.Item {...rest} ref={setNodeRef} style={listStyle} {...attributes} {...listeners} />;
};
const App: React.FC = () => {
const [data, setData] = useState([
{ key: 1, title: 'Title 1' },
{ key: 2, title: 'Title 2' },
{ key: 3, title: 'Title 3' },
{ key: 4, title: 'Title 4' },
{ key: 5, title: 'Title 5' },
{ key: 6, title: 'Title 6' },
]);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
// https://docs.dndkit.com/api-documentation/sensors/pointer#activation-constraints
distance: 1,
},
}),
);
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (!active || !over) {
return;
}
if (active.id !== over.id) {
setData((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 (
<DndContext sensors={sensors} onDragEnd={onDragEnd} id="list-grid-drag-sorting">
<SortableContext items={data.map((item) => item.key)}>
<List
grid={{ gutter: 16, column: 4 }}
dataSource={data}
renderItem={(item) => (
<SortableListItem key={item.key} itemKey={item.key}>
<Card title={item.title}>Card content</Card>
</SortableListItem>
)}
/>
</SortableContext>
</DndContext>
);
};
export default App;

View File

@@ -23,6 +23,10 @@ A list can be used to display content related to a single subject. The content c
<code src="./demo/grid-test.tsx" debug>Test Grid</code>
<code src="./demo/responsive.tsx">Responsive grid list</code>
<code src="./demo/infinite-load.tsx">Scrolling loaded</code>
<code src="./demo/drag-sorting.tsx">Drag sorting</code>
<code src="./demo/drag-sorting-handler.tsx">Drag sorting with handler</code>
<code src="./demo/grid-drag-sorting.tsx">Grid Drag sorting</code>
<code src="./demo/grid-drag-sorting-handler.tsx">Grid Drag sorting with handler</code>
<code src="./demo/virtual-list.tsx">virtual list</code>
<code src="./demo/component-token.tsx" debug>custom component token</code>

View File

@@ -24,6 +24,10 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*tBzwQ7raKX8AAA
<code src="./demo/grid-test.tsx" debug>测试栅格列表</code>
<code src="./demo/responsive.tsx">响应式的栅格列表</code>
<code src="./demo/infinite-load.tsx">滚动加载</code>
<code src="./demo/drag-sorting.tsx">拖拽排序</code>
<code src="./demo/drag-sorting-handler.tsx">拖拽排序(拖拽手柄)</code>
<code src="./demo/grid-drag-sorting.tsx">栅格拖拽排序</code>
<code src="./demo/grid-drag-sorting-handler.tsx">栅格拖拽排序(拖拽手柄)</code>
<code src="./demo/virtual-list.tsx">滚动加载无限长列表</code>
<code src="./demo/component-token.tsx" debug>自定义组件 token</code>