Skip to content

Commit aab941d

Browse files
authored
refactor: migrate sortable (#10204)
* refactor: rename sortable dir * refactor: migrate Sortable to the ui package * feat: add stories for Sortable * refactor: add scroller to the vertical story * refactor: improve hints and width * refactor: simplify item style * fix: lint errors * chore: dependencies * refactor: move hooks * fix: import errors * style: format * style: format
1 parent 1b04fd0 commit aab941d

File tree

24 files changed

+738
-15
lines changed

24 files changed

+738
-15
lines changed

packages/ui/package.json

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,7 @@
1717
"storybook": "storybook dev -p 6006",
1818
"build-storybook": "storybook build"
1919
},
20-
"keywords": [
21-
"ui",
22-
"components",
23-
"react",
24-
"tailwindcss",
25-
"typescript",
26-
"cherry-studio"
27-
],
20+
"keywords": ["ui", "components", "react", "tailwindcss", "typescript", "cherry-studio"],
2821
"author": "Cherry Studio",
2922
"license": "MIT",
3023
"repository": {
@@ -43,6 +36,10 @@
4336
"tailwindcss": "^4.1.13"
4437
},
4538
"dependencies": {
39+
"@dnd-kit/core": "^6.3.1",
40+
"@dnd-kit/modifiers": "^9.0.0",
41+
"@dnd-kit/sortable": "^10.0.0",
42+
"@dnd-kit/utilities": "^3.2.2",
4643
"clsx": "^2.1.1",
4744
"lucide-react": "^0.525.0"
4845
},
@@ -79,10 +76,7 @@
7976
"engines": {
8077
"node": ">=18.0.0"
8178
},
82-
"files": [
83-
"dist",
84-
"README.md"
85-
],
79+
"files": ["dist", "README.md"],
8680
"exports": {
8781
".": {
8882
"types": "./dist/index.d.ts",

packages/ui/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export { default as ImageToolButton } from './interactive/ImageToolButton'
6363
export { default as InfoPopover } from './interactive/InfoPopover'
6464
export { default as InfoTooltip } from './interactive/InfoTooltip'
6565
export { default as Selector } from './interactive/Selector'
66+
export { Sortable } from './interactive/Sortable'
6667
export { default as WarnTooltip } from './interactive/WarnTooltip'
6768

6869
// Composite Components (复合组件)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import type { DraggableSyntheticListeners } from '@dnd-kit/core'
2+
import type { Transform } from '@dnd-kit/utilities'
3+
import { CSS } from '@dnd-kit/utilities'
4+
import React, { useEffect } from 'react'
5+
import styled from 'styled-components'
6+
7+
import { cn } from '../../../utils'
8+
import type { RenderItemType } from './types'
9+
10+
interface ItemRendererProps<T> {
11+
ref?: React.Ref<HTMLDivElement>
12+
index?: number
13+
item: T
14+
renderItem: RenderItemType<T>
15+
dragging?: boolean
16+
dragOverlay?: boolean
17+
ghost?: boolean
18+
transform?: Transform | null
19+
transition?: string | null
20+
listeners?: DraggableSyntheticListeners
21+
}
22+
23+
export function ItemRenderer<T>({
24+
ref,
25+
index,
26+
item,
27+
renderItem,
28+
dragging,
29+
dragOverlay,
30+
ghost,
31+
transform,
32+
transition,
33+
listeners,
34+
...props
35+
}: ItemRendererProps<T>) {
36+
useEffect(() => {
37+
if (!dragOverlay) {
38+
return
39+
}
40+
41+
document.body.style.cursor = 'grabbing'
42+
43+
return () => {
44+
document.body.style.cursor = ''
45+
}
46+
}, [dragOverlay])
47+
48+
const wrapperStyle = {
49+
transition,
50+
transform: CSS.Transform.toString(transform ?? null)
51+
} as React.CSSProperties
52+
53+
return (
54+
<ItemWrapper ref={ref} data-index={index} className={cn({ dragOverlay: dragOverlay })} style={{ ...wrapperStyle }}>
55+
<DraggableItem
56+
className={cn({ dragging: dragging, dragOverlay: dragOverlay, ghost: ghost })}
57+
{...listeners}
58+
{...props}>
59+
{renderItem(item, { dragging: !!dragging })}
60+
</DraggableItem>
61+
</ItemWrapper>
62+
)
63+
}
64+
65+
const ItemWrapper = styled.div`
66+
box-sizing: border-box;
67+
transform-origin: 0 0;
68+
touch-action: manipulation;
69+
70+
&.dragOverlay {
71+
--scale: 1.02;
72+
z-index: 999;
73+
position: relative;
74+
}
75+
`
76+
77+
const DraggableItem = styled.div`
78+
position: relative;
79+
box-sizing: border-box;
80+
cursor: pointer; /* default cursor for items */
81+
touch-action: manipulation;
82+
transform-origin: 50% 50%;
83+
transform: scale(var(--scale, 1));
84+
85+
&.dragging:not(.dragOverlay) {
86+
z-index: 0;
87+
opacity: 0.25;
88+
89+
&:not(.ghost) {
90+
opacity: 0;
91+
}
92+
}
93+
94+
&.dragOverlay {
95+
cursor: inherit;
96+
animation: pop 200ms cubic-bezier(0.18, 0.67, 0.6, 1.22);
97+
transform: scale(var(--scale));
98+
opacity: 1;
99+
pointer-events: none; /* prevent pointer events on drag overlay */
100+
}
101+
102+
@keyframes pop {
103+
0% {
104+
transform: scale(1);
105+
}
106+
100% {
107+
transform: scale(var(--scale));
108+
}
109+
}
110+
`

0 commit comments

Comments
 (0)