Skip to content

Commit a6cc91f

Browse files
committed
feat(components/draggable): 重构并优化 draggable 组件
1. 使用 react 事件绑定代替原来的 rxjs 事件绑定 2. 可以指定组件包裹元素类型,也可以让包裹元素为空 3. 列表元素 draggable 为 false 时不可拖拽
1 parent 8e5ba63 commit a6cc91f

File tree

16 files changed

+304
-168
lines changed

16 files changed

+304
-168
lines changed

packages/components/src/draggable/Draggable.tsx

Lines changed: 24 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,38 @@
1-
import { useForceUpdate, useForwardRef, getClasses } from '@pkg/shared';
2-
import type { DraggableProps, DraggableFC } from './draggable.types';
31
// import EnUS from './locale/en-US';
4-
import React, { useEffect, Children, useRef } from 'react';
2+
import type { DraggableProps, DraggableFC } from './draggable.types';
3+
import { createElement, forwardRef, FC } from 'react';
54
// import { useLocale } from '~/config-provider/useLocale';
65
import type { RequiredPart } from '@tool-pack/types';
76
import { getClassNames } from '@tool-pack/basic';
8-
import children from '~/timeline/demo/children';
9-
import { moveItem, drag } from './utils';
7+
import { useDraggableChildren } from './hooks';
8+
import { getClasses } from '@pkg/shared';
109

11-
const cls = getClasses('draggable', ['ghost'], []);
12-
const defaultProps = {} satisfies Partial<DraggableProps>;
10+
export const cls = getClasses('draggable', ['ghost', 'item'], []);
11+
const defaultProps = {
12+
tag: 'div',
13+
list: [],
14+
} satisfies Partial<DraggableProps>;
1315

14-
export const _Draggable: React.FC<DraggableProps> = React.forwardRef<
16+
export const _Draggable: FC<DraggableProps> = forwardRef<
1517
HTMLDivElement,
1618
DraggableProps
17-
>((props, outerRef) => {
19+
>((props, ref) => {
1820
// 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();
21+
const { attrs = {}, tag } = props as RequiredPart<
22+
DraggableProps,
23+
keyof typeof defaultProps
24+
>;
25+
const children = useDraggableChildren(props);
2726

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(
27+
if (tag === null) return children;
28+
return createElement(
29+
tag,
30+
{
31+
...attrs,
32+
className: getClassNames(cls.root, attrs.className),
3833
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>
34+
},
35+
children,
6036
);
6137
});
6238

packages/components/src/draggable/__tests__/Draggable.test.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,35 @@ describe('Draggable', () => {
2626
const r = render(<App />);
2727
expect(r.container.firstChild).toMatchSnapshot();
2828
});
29+
test('tag', () => {
30+
expect(
31+
(
32+
render(
33+
<Draggable list={[]}>
34+
<span>1</span>
35+
</Draggable>,
36+
).container.firstChild as HTMLElement
37+
).tagName,
38+
).toBe('DIV');
39+
40+
expect(
41+
(
42+
render(
43+
<Draggable tag="section" list={[]}>
44+
<span>1</span>
45+
</Draggable>,
46+
).container.firstChild as HTMLElement
47+
).tagName,
48+
).toBe('SECTION');
49+
50+
expect(
51+
(
52+
render(
53+
<Draggable tag={null} list={[]}>
54+
<span>1</span>
55+
</Draggable>,
56+
).container.firstChild as HTMLElement
57+
).tagName,
58+
).toBe('SPAN');
59+
});
2960
});

packages/components/src/draggable/__tests__/__snapshots__/Draggable.test.tsx.snap

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ exports[`Draggable base 1`] = `
55
class="t-draggable"
66
>
77
<div
8-
class="draggable-item"
8+
class="draggable-item t-draggable__item"
9+
draggable="true"
910
>
1011
<span>
1112
1
@@ -21,7 +22,8 @@ exports[`Draggable base 1`] = `
2122
</span>
2223
</div>
2324
<div
24-
class="draggable-item"
25+
class="draggable-item t-draggable__item"
26+
draggable="true"
2527
>
2628
<span>
2729
2
@@ -37,7 +39,8 @@ exports[`Draggable base 1`] = `
3739
</span>
3840
</div>
3941
<div
40-
class="draggable-item"
42+
class="draggable-item t-draggable__item"
43+
draggable="true"
4144
>
4245
<span>
4346
3

packages/components/src/draggable/demo/basic.module.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
}
77
.t-draggable {
88
flex: 1;
9-
cursor: move;
109
}
1110
.draggable-item {
1211
padding: 0 0.5rem;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.root {
2+
:global {
3+
.main {
4+
display: flex;
5+
margin-top: 1rem;
6+
}
7+
.t-draggable {
8+
flex: 1;
9+
}
10+
.draggable-item {
11+
padding: 0 0.5rem;
12+
border: 1px solid #e6e6e6;
13+
background: #fff1d7;
14+
line-height: 32px;
15+
&[draggable='false'] {
16+
background: #f6f4f0;
17+
}
18+
}
19+
.data {
20+
padding: 0 20px;
21+
white-space: pre-wrap;
22+
}
23+
}
24+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* title: draggable
3+
* description: html 元素设置 draggable 为 false 时不可拖动。
4+
*/
5+
6+
import { Draggable } from '@tool-pack/react-ui';
7+
import styles from './draggable.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+
<div className="main">
20+
<Draggable onChange={setState} list={state}>
21+
{state.map((item, index) => (
22+
<div className="draggable-item" draggable={index > 1} key={item.id}>
23+
<span>{index + 1}.</span> <span>{item.name}</span>{' '}
24+
<span>{item.id}</span>
25+
</div>
26+
))}
27+
</Draggable>
28+
<div className="data">
29+
[
30+
{state.map((it) => (
31+
<div key={it.id}>{JSON.stringify(it)}</div>
32+
))}
33+
]
34+
</div>
35+
</div>
36+
</div>
37+
);
38+
};
39+
40+
export default App;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.root {
2+
:global {
3+
.main {
4+
display: flex;
5+
margin-top: 1rem;
6+
}
7+
.draggable-item {
8+
padding: 0 0.5rem;
9+
border: 1px solid #e6e6e6;
10+
background: #fff1d7;
11+
line-height: 32px;
12+
&[draggable='true'] {
13+
cursor: col-resize;
14+
}
15+
}
16+
.data {
17+
padding: 0 20px;
18+
white-space: pre-wrap;
19+
}
20+
}
21+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* title: tag
3+
* description: 默认根元素为 div,当 tag 为 null 时 Draggable 组件不提供根元素。
4+
*/
5+
6+
import { Draggable } from '@tool-pack/react-ui';
7+
import styles from './tag.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+
<div className="main">
20+
<Draggable onChange={setState} list={state} tag={null}>
21+
{state.map((item, index) => (
22+
<div className="draggable-item" key={item.id}>
23+
<span>{index + 1}.</span> <span>{item.name}</span>{' '}
24+
<span>{item.id}</span>
25+
</div>
26+
))}
27+
</Draggable>
28+
<div className="data">
29+
[
30+
{state.map((it) => (
31+
<div key={it.id}>{JSON.stringify(it)}</div>
32+
))}
33+
]
34+
</div>
35+
</div>
36+
</div>
37+
);
38+
};
39+
40+
export default App;

packages/components/src/draggable/draggable.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { PropsBase } from '@pkg/shared';
22
import type { ReactElement } from 'react';
33

44
export interface DraggableProps<T = unknown> extends PropsBase<HTMLDivElement> {
5+
tag?: keyof HTMLElementTagNameMap | null;
56
onChange?: (list: T[]) => void;
67
list: T[];
78
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useDraggableChildren';

0 commit comments

Comments
 (0)