Skip to content

Commit fde7561

Browse files
committed
feat(components): 新增 Draggable 拖拽组件
1 parent 4d8e5be commit fde7561

File tree

17 files changed

+430
-0
lines changed

17 files changed

+430
-0
lines changed

internal/playground/src/router.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ export const baseRouter = [
181181
name: 'virtual-list 虚拟列表',
182182
path: '/virtual-list',
183183
},
184+
{
185+
element: getDemos(import.meta.glob('~/draggable/demo/*.tsx')),
186+
name: 'draggable 拖拽',
187+
path: '/draggable',
188+
},
184189
/* {import insert target} */
185190
];
186191

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { useForceUpdate, useForwardRef, getClasses } from '@pkg/shared';
2+
import type { DraggableProps, DraggableFC } from './draggable.types';
3+
// import EnUS from './locale/en-US';
4+
import React, { useEffect, Children, useRef } from 'react';
5+
// import { useLocale } from '~/config-provider/useLocale';
6+
import type { RequiredPart } from '@tool-pack/types';
7+
import { getClassNames } from '@tool-pack/basic';
8+
import children from '~/timeline/demo/children';
9+
import { moveItem, drag } from './utils';
10+
11+
const cls = getClasses('draggable', ['ghost'], []);
12+
const defaultProps = {} satisfies Partial<DraggableProps>;
13+
14+
export const _Draggable: React.FC<DraggableProps> = React.forwardRef<
15+
HTMLDivElement,
16+
DraggableProps
17+
>((props, outerRef) => {
18+
// const locale = useLocale('draggable', EnUS);
19+
const {
20+
children: outerChildren,
21+
attrs = {},
22+
list = [],
23+
onChange,
24+
} = props as RequiredPart<DraggableProps, keyof typeof defaultProps>;
25+
const ref = useForwardRef(outerRef);
26+
const forceUpdate = useForceUpdate();
27+
28+
const childrenRef = useRef<React.ReactNode[]>(
29+
Children.toArray(outerChildren),
30+
);
31+
const listRef = useRef(list);
32+
33+
useEffect(() => {
34+
listRef.current = list;
35+
childrenRef.current = Children.toArray(outerChildren);
36+
forceUpdate();
37+
return drag(
38+
ref,
39+
cls.__.ghost,
40+
(prevIndex, currIndex) => {
41+
moveItem(childrenRef.current, prevIndex, currIndex);
42+
moveItem(listRef.current, prevIndex, currIndex);
43+
forceUpdate();
44+
},
45+
(prevIndex, currIndex) => {
46+
moveItem(listRef.current, prevIndex, currIndex);
47+
onChange?.(listRef.current.slice());
48+
},
49+
);
50+
}, [children, list, ref]);
51+
52+
return (
53+
<div
54+
{...attrs}
55+
className={getClassNames(cls.root, attrs.className)}
56+
ref={ref}
57+
>
58+
{childrenRef.current}
59+
</div>
60+
);
61+
});
62+
63+
_Draggable.defaultProps = defaultProps;
64+
_Draggable.displayName = 'Draggable';
65+
66+
export const Draggable = _Draggable as DraggableFC;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { render } from '@testing-library/react';
2+
import { testAttrs } from '~/testAttrs';
3+
import { useState } from 'react';
4+
import { Draggable } from '..';
5+
6+
describe('Draggable', () => {
7+
testAttrs(Draggable);
8+
test('base', () => {
9+
const App = () => {
10+
const [state, setState] = useState<{ name: string; id: number }[]>([
11+
{ name: 'John', id: 1 },
12+
{ name: 'Joao', id: 2 },
13+
{ name: 'Jean', id: 3 },
14+
]);
15+
return (
16+
<Draggable onChange={setState} list={state}>
17+
{state.map((item, index) => (
18+
<div className="draggable-item" key={item.id}>
19+
<span>{index + 1}.</span> <span>{item.name}</span>{' '}
20+
<span>{item.id}</span>
21+
</div>
22+
))}
23+
</Draggable>
24+
);
25+
};
26+
const r = render(<App />);
27+
expect(r.container.firstChild).toMatchSnapshot();
28+
});
29+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Draggable base 1`] = `
4+
<div
5+
class="t-draggable"
6+
>
7+
<div
8+
class="draggable-item"
9+
>
10+
<span>
11+
1
12+
.
13+
</span>
14+
15+
<span>
16+
John
17+
</span>
18+
19+
<span>
20+
1
21+
</span>
22+
</div>
23+
<div
24+
class="draggable-item"
25+
>
26+
<span>
27+
2
28+
.
29+
</span>
30+
31+
<span>
32+
Joao
33+
</span>
34+
35+
<span>
36+
2
37+
</span>
38+
</div>
39+
<div
40+
class="draggable-item"
41+
>
42+
<span>
43+
3
44+
.
45+
</span>
46+
47+
<span>
48+
Jean
49+
</span>
50+
51+
<span>
52+
3
53+
</span>
54+
</div>
55+
</div>
56+
`;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.root {
2+
:global {
3+
.main {
4+
display: flex;
5+
margin-top: 1rem;
6+
}
7+
.t-draggable {
8+
flex: 1;
9+
cursor: move;
10+
}
11+
.draggable-item {
12+
padding: 0 0.5rem;
13+
border: 1px solid #e6e6e6;
14+
background: #fff1d7;
15+
line-height: 32px;
16+
}
17+
.data {
18+
padding: 0 20px;
19+
white-space: pre-wrap;
20+
}
21+
}
22+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* title: 基础用法
3+
* description: Draggable 基础用法。
4+
*/
5+
6+
import { ButtonGroup, Draggable, Button } from '@tool-pack/react-ui';
7+
import styles from './basic.module.scss';
8+
import React from 'react';
9+
10+
const App: React.FC = () => {
11+
const [state, setState] = React.useState<{ name: string; id: number }[]>([
12+
{ name: 'John', id: 1 },
13+
{ name: 'Joao', id: 2 },
14+
{ name: 'Jean', id: 3 },
15+
{ name: 'Gerard', id: 4 },
16+
]);
17+
return (
18+
<div className={styles['root']}>
19+
<ButtonGroup>
20+
<Button
21+
onClick={() => {
22+
const id = state.length + 1;
23+
setState([...state, { name: 'anyone', id }]);
24+
}}
25+
type="primary"
26+
>
27+
添加
28+
</Button>
29+
<Button onClick={() => setState(state.slice(0, -1))} type="success">
30+
删减
31+
</Button>
32+
</ButtonGroup>
33+
<div className="main">
34+
<Draggable onChange={setState} list={state}>
35+
{state.map((item, index) => (
36+
<div className="draggable-item" key={item.id}>
37+
<span>{index + 1}.</span> <span>{item.name}</span>{' '}
38+
<span>{item.id}</span>
39+
</div>
40+
))}
41+
</Draggable>
42+
<div className="data">
43+
[
44+
{state.map((it) => (
45+
<div key={it.id}>{JSON.stringify(it)}</div>
46+
))}
47+
]
48+
</div>
49+
</div>
50+
</div>
51+
);
52+
};
53+
54+
export default App;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { PropsBase } from '@pkg/shared';
2+
import type { ReactElement } from 'react';
3+
4+
export interface DraggableProps<T = unknown> extends PropsBase<HTMLDivElement> {
5+
onChange?: (list: T[]) => void;
6+
list: T[];
7+
}
8+
export type DraggableFC = <T>(props: DraggableProps<T>) => ReactElement;
9+
// export interface DraggableLocale {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@use '../namespace' as Name;
2+
3+
$r: Name.$draggable;
4+
5+
.#{$r} {
6+
position: relative;
7+
&__ghost {
8+
opacity: 0.7;
9+
transform: translateZ(0);
10+
}
11+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export type { DraggableProps } from './draggable.types';
2+
export * from './Draggable';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
category: Components
3+
title: Draggable 拖拽
4+
atomId: Draggable
5+
demo:
6+
cols: 2
7+
group:
8+
title: 通用
9+
---
10+
11+
Draggable 拖拽。
12+
13+
## 代码演示
14+
15+
<!-- prettier-ignore -->
16+
<code src="./demo/basic.tsx"></code>
17+
18+
## API
19+
20+
Draggable 的属性说明如下:
21+
22+
| 属性 | 说明 | 类型 | 默认值 | 版本 |
23+
| -------- | -------------- | ----------------------------------------------- | ------ | ---- |
24+
| list | 列表对应的数组 | any[] | -- | -- |
25+
| onChange | 列表改动回调 | (list: T[]) => void | -- | -- |
26+
| attrs | html 标签属性 | Partial\<React.HTMLAttributes\<HTMLDivElement>> | -- | -- |
27+
28+
其他说明。

0 commit comments

Comments
 (0)