Skip to content

Commit a762fb8

Browse files
committed
feat(modal): modal support resizable
1 parent 830e17c commit a762fb8

12 files changed

+311
-29
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"@types/jest": "^29.2.3",
8383
"@types/lodash-es": "^4.17.12",
8484
"@types/react": "^18.0.0",
85+
"@types/react-resizable": "^3.0.8",
8586
"@types/shortid": "^0.0.31",
8687
"@types/showdown": "^1.9.0",
8788
"@types/testing-library__jest-dom": "^5.14.5",
@@ -119,6 +120,7 @@
119120
"rc-drawer": "~5.1.0",
120121
"rc-virtual-list": "^3.4.13",
121122
"react-draggable": "~4.4.6",
123+
"react-resizable": "^3.0.5",
122124
"shortid": "^2.2.16",
123125
"showdown": "^1.9.0",
124126
"use-clippy": "^1.0.9"

pnpm-lock.yaml

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/float/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import React, { useState } from 'react';
22
import Draggable, { DraggableEventHandler, type DraggableProps } from 'react-draggable';
33
import classNames from 'classnames';
44

5-
import useMergeOption from './useMergeOption';
5+
import useMergeOption, { MergeOption } from './useMergeOption';
66
import './index.scss';
77

88
export interface IFloatProps {
99
className?: string;
1010
style?: React.CSSProperties;
11-
draggable?: boolean | Partial<Omit<DraggableProps, 'position'>>;
11+
draggable?: MergeOption<Partial<Omit<DraggableProps, 'position'>>>;
1212
position?: DraggableProps['position'];
1313
onChange?: DraggableProps['onStop'];
1414
}

src/float/useMergeOption.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@ export type ReturnMergeOption<T extends Record<string, any>> = {
88
};
99

1010
export default function useMergeOption<T extends Record<string, any>>(
11-
opt: MergeOption<T>
11+
opt: MergeOption<T>,
12+
defaultOpt?: T
1213
): ReturnMergeOption<T> {
1314
return useMemo(() => {
14-
if (typeof opt === 'object') {
15+
if (typeof opt === 'object' && !!opt) {
1516
return {
1617
disabled: false,
17-
options: opt,
18+
options: { ...defaultOpt, ...opt },
1819
};
1920
}
2021
return {
2122
disabled: !opt,
22-
options: <T>{},
23+
options: <T>{ ...defaultOpt },
2324
};
2425
}, [opt]);
2526
}

src/modal/demos/draggable.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Modal, TinyTag } from 'dt-react-component';
44

55
export default function Basic() {
66
const [visible, setVisible] = useState(false);
7+
const [position, setPosition] = useState({ x: 120, y: 120 });
78

89
return (
910
<>
@@ -23,6 +24,8 @@ export default function Basic() {
2324
draggable={{
2425
bounds: 'body',
2526
}}
27+
position={position}
28+
onPositionChange={(_, { x, y }) => setPosition({ x, y })}
2629
visible={visible}
2730
onCancel={() => setVisible(false)}
2831
onOk={() => setVisible(false)}

src/modal/demos/resizable.tsx

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React, { useState } from 'react';
2+
import { Button } from 'antd';
3+
import { Modal, Resize, useModal } from 'dt-react-component';
4+
import { RectState } from 'dt-react-component/modal';
5+
6+
export default function Basic() {
7+
const modal = useModal<void>();
8+
const [rect, setRect] = useState<RectState>({ width: 520, height: 520 });
9+
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });
10+
11+
return (
12+
<Resize onResize={() => setSize({ width: window.innerWidth, height: window.innerHeight })}>
13+
<Modal
14+
title="Resizable Modal"
15+
visible={modal.visible}
16+
resizable={{
17+
maxConstraints: [size.width - 40, size.height - 150],
18+
}}
19+
rect={rect}
20+
onRectChange={setRect}
21+
onCancel={() => modal.close()}
22+
onOk={() => modal.close()}
23+
>
24+
<ul>
25+
{Array.from({ length: 300 }).map((_, i) => (
26+
<li key={i} style={{ height: 30 }}>
27+
{i}
28+
</li>
29+
))}
30+
</ul>
31+
</Modal>
32+
<Button type="primary" onClick={() => modal.open()}>
33+
Resizable Modal
34+
</Button>
35+
</Resize>
36+
);
37+
}

src/modal/demos/window.tsx

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React, { useRef, useState } from 'react';
2+
import { ResizeHandle } from 'react-resizable';
3+
import { Button } from 'antd';
4+
import { Modal, Resize } from 'dt-react-component';
5+
import { RectState } from 'dt-react-component/modal';
6+
7+
export default function Basic() {
8+
const [visible, setVisible] = useState(false);
9+
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });
10+
11+
const [position, setPosition] = useState({ x: 120, y: 120 });
12+
const [rect, setRect] = useState<RectState>({ width: 520, height: 520 });
13+
14+
const resizeDirection = useRef<ResizeHandle>('e');
15+
16+
// 限制 resize 超出当前屏幕
17+
const getMaxConstraints = (): [number, number] => {
18+
switch (resizeDirection.current) {
19+
case 'e':
20+
return [size.width - position.x, size.height];
21+
case 'n':
22+
return [size.width, position.y + rect.height];
23+
case 's':
24+
return [size.width, size.height - position.y];
25+
case 'w':
26+
return [position.x + rect.width, size.height];
27+
case 'ne':
28+
return [size.width - position.x, position.y + rect.height];
29+
case 'nw':
30+
return [position.x + rect.width, position.y + rect.height];
31+
case 'se':
32+
return [size.width - position.x, size.height - position.y];
33+
case 'sw':
34+
return [position.x + rect.width, size.height - position.y];
35+
default:
36+
return [0, 0];
37+
}
38+
};
39+
40+
return (
41+
<Resize onResize={() => setSize({ width: window.innerWidth, height: window.innerHeight })}>
42+
<Modal
43+
title="Window Modal"
44+
visible={visible}
45+
mask={false}
46+
resizable={{
47+
maxConstraints: getMaxConstraints(),
48+
onResize: (_, data) => {
49+
resizeDirection.current = data.handle;
50+
},
51+
}}
52+
rect={rect}
53+
draggable={{
54+
bounds: 'body',
55+
}}
56+
position={position}
57+
onPositionChange={setPosition}
58+
onRectChange={setRect}
59+
onCancel={() => setVisible(false)}
60+
onOk={() => setVisible(false)}
61+
>
62+
<>Just Dtstack It</>
63+
</Modal>
64+
<Button type="primary" onClick={() => setVisible(true)}>
65+
Window Modal
66+
</Button>
67+
</Resize>
68+
);
69+
}

src/modal/handle/index.scss

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
.dt-modal-resize-handle {
2+
position: absolute;
3+
z-index: 1;
4+
pointer-events: initial;
5+
&.handle-w {
6+
cursor: col-resize;
7+
left: -5px;
8+
bottom: 0;
9+
width: 10px;
10+
height: 100%;
11+
}
12+
&.handle-e {
13+
cursor: col-resize;
14+
right: -5px;
15+
bottom: 0;
16+
width: 10px;
17+
height: 100%;
18+
}
19+
&.handle-n {
20+
cursor: row-resize;
21+
left: 0;
22+
top: -5px;
23+
width: 100%;
24+
height: 10px;
25+
}
26+
&.handle-s {
27+
cursor: row-resize;
28+
left: 0;
29+
bottom: -5px;
30+
width: 100%;
31+
height: 10px;
32+
}
33+
&.handle-se {
34+
cursor: se-resize;
35+
right: 0;
36+
bottom: -5px;
37+
width: 10px;
38+
height: 10px;
39+
z-index: 11;
40+
}
41+
&.handle-ne {
42+
cursor: ne-resize;
43+
right: 0;
44+
top: -5px;
45+
width: 10px;
46+
height: 10px;
47+
z-index: 11;
48+
}
49+
&.handle-nw {
50+
cursor: nw-resize;
51+
left: 0;
52+
top: -5px;
53+
width: 10px;
54+
height: 10px;
55+
z-index: 11;
56+
}
57+
&.handle-sw {
58+
cursor: sw-resize;
59+
left: 0;
60+
bottom: -5px;
61+
width: 10px;
62+
height: 10px;
63+
z-index: 11;
64+
}
65+
}

src/modal/handle/index.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
3+
import './index.scss';
4+
5+
const Handler = React.forwardRef<HTMLDivElement, any>((props, ref) => {
6+
const { handleAxis, ...restProps } = props;
7+
return (
8+
<div ref={ref} className={`dt-modal-resize-handle handle-${handleAxis}`} {...restProps} />
9+
);
10+
});
11+
12+
export default Handler;

src/modal/index.md

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ toc: content
1919
<code src="./demos/banner.tsx" title="支持 banner"></code>
2020
<code src="./demos/bannerProps.tsx" title="支持传 banner 的 Props 属性"></code>
2121
<code src="./demos/draggable.tsx" title="draggable"></code>
22+
<code src="./demos/resizable.tsx" title="resizable"></code>
23+
<code src="./demos/window.tsx" title="窗口模式即支持 draggable 同时也支持 resizable"></code>
2224

2325
## API
2426

src/modal/index.scss

+10-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@ $modal-max-height: 80vh;
66
left: 0;
77
margin: 0;
88
}
9+
&__resizable {
10+
padding: 0;
11+
}
12+
// resizable 的时候会设置尺寸,所以不需要最大尺寸
13+
&:not(&__resizable) {
14+
.ant-modal-content {
15+
max-height: $modal-max-height;
16+
}
17+
}
918
.ant-modal-content {
10-
max-height: $modal-max-height;
19+
height: 100%;
1120
display: flex;
1221
flex-direction: column;
1322
.ant-modal-body {

0 commit comments

Comments
 (0)