Skip to content
This repository has been archived by the owner on Nov 14, 2023. It is now read-only.

Commit

Permalink
feat: Draggable Modal
Browse files Browse the repository at this point in the history
Signed-off-by: zhanghuan <zhang.huan@xsky.com>
  • Loading branch information
xskyAlex committed Sep 19, 2022
1 parent d0a9428 commit 43ad71c
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 23 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"react-addons-css-transition-group": "^15.6.2",
"react-bootstrap": "^0.31.1",
"react-dev-utils": "^11.0.4",
"react-draggable": "^4.4.5",
"react-ios-switch": "^0.1.19",
"wizard-icons": "^0.1.16"
},
Expand Down
16 changes: 16 additions & 0 deletions src/components/Modal/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,20 @@ describe('Modal', () => {
).toBeTruthy();
expect(node.find('Loader').length).toBe(1);
});
it('draggable props', () => {
const draggableNode = mount(
<Modal title="Modal Title" onHide={noOp}>
<strong>Modal Content</strong>
</Modal>,
);
expect(draggableNode.find('.drag-handle').length).toBe(1);

const disDraggableNode = mount(
<Modal title="Modal Title" onHide={noOp} draggable={false}>
<strong>Modal Content</strong>
</Modal>,
);
expect(disDraggableNode.find('.drag-handle').length).toBe(0);
});

});
104 changes: 81 additions & 23 deletions src/components/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,46 @@ import {
ModalFooter,
Button,
} from 'react-bootstrap';
import Draggable, { DraggableEvent, DraggableData } from 'react-draggable';
import { ModalProps } from '../../interface';
import Loader from '../Loader';

import './style.scss';

const useDrag = ()=>{
const [bounds, setBounds] = React.useState({ left: 0, top: 0, bottom: 0, right: 0 });
const draggleRef = React.useRef<HTMLDivElement>(null);
const onStart = (_event: DraggableEvent, uiData: DraggableData) => {
const { clientWidth, clientHeight } = window.document.documentElement;
// 当前版本ts 不支持?.
const targetRect = draggleRef.current && draggleRef.current.getBoundingClientRect();

if (!targetRect) {
return;
}
setBounds({
left: -targetRect.left + uiData.x,
right: clientWidth - (targetRect.right - uiData.x),
top: -targetRect.top + uiData.y,
bottom: clientHeight - (targetRect.bottom - uiData.y),
});
};

return {
draggleRef,
onStart,
bounds
};
}

// 保持容器高度不塌陷
const draggableHiddenTitleStyle: React.CSSProperties = {
visibility: 'hidden'
};

const Modal: React.FC<ModalProps> = props => {
const {onStart, bounds, draggleRef} = useDrag();

const {
title,
onHide,
Expand All @@ -25,6 +60,8 @@ const Modal: React.FC<ModalProps> = props => {
loading,
hideFooter,
hideHeader,
draggable = true,
preventDragByTitle
} = props;
let { bsSize } = props,
dialogClassName = '';
Expand All @@ -33,31 +70,48 @@ const Modal: React.FC<ModalProps> = props => {
bsSize = undefined;
}

// 让 title 在可拖动模块的上层,来使title区域的拖动效果失效
const modalTitleStyle = React.useMemo<React.CSSProperties>(() => (draggable && preventDragByTitle ? { position: 'absolute' } : {}), [draggable, preventDragByTitle]);

return (
<BaseModal
bsSize={bsSize}
className="Modal"
dialogClassName={dialogClassName}
style={style}
backdrop="static"
onHide={onHide}
show={show}
<Draggable
disabled={!draggable}
bounds={bounds}
handle=".drag-handle"
onStart={(event, uiData) => onStart(event, uiData)}
>
{!hideHeader && (
<ModalHeader key="header" closeButton>
<ModalTitle>{title}</ModalTitle>
</ModalHeader>
)}
<ModalBody key="body">{children}</ModalBody>
{!hideFooter && (
<ModalFooter key="footer">
<Button type="submit" disabled={loading} bsStyle={okStyle} onClick={onOk}>
{loading && <Loader bsSize="xs" />}
{confirmText}
</Button>
</ModalFooter>
)}
</BaseModal>
<BaseModal
bsSize={bsSize}
className="Modal"
dialogClassName={dialogClassName}
style={style}
backdrop="static"
onHide={onHide}
show={show}
>
<div ref={draggleRef}>
{draggable && <div className="drag-handle" ref={draggleRef}/>}
<div>
{!hideHeader && (
<ModalHeader key="header" closeButton>
<ModalTitle style={modalTitleStyle}>{title}</ModalTitle>
{draggable && preventDragByTitle ? <ModalTitle style={draggableHiddenTitleStyle}>{title}</ModalTitle> : null}
</ModalHeader>
)}
</div>

<ModalBody key="body">{children}</ModalBody>
{!hideFooter && (
<ModalFooter key="footer">
<Button type="submit" disabled={loading} bsStyle={okStyle} onClick={onOk}>
{loading && <Loader bsSize="xs" />}
{confirmText}
</Button>
</ModalFooter>
)}
</div>
</BaseModal>
</Draggable>
);
};

Expand All @@ -84,6 +138,10 @@ Modal.propTypes = {
hideFooter: PropTypes.bool,
/** 隐藏 头部 */
hideHeader: PropTypes.bool,
/** 开启拖拽功能(默认为true) */
draggable: PropTypes.bool,
/** 在title区域禁掉拖拽功能 */
preventDragByTitle: PropTypes.bool,
};

Modal.defaultProps = {
Expand Down
7 changes: 7 additions & 0 deletions src/components/Modal/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@
}
}
}
.drag-handle {
width: 100%;
height: 45px;
position: absolute;
top: -5px;
cursor: move;
}
}
2 changes: 2 additions & 0 deletions src/interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ export interface ModalProps {
loading?: boolean;
hideFooter?: boolean;
hideHeader?: boolean;
draggable?: boolean;
preventDragByTitle?: boolean;
}

interface SwitchInput {
Expand Down
1 change: 1 addition & 0 deletions stories/Modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const ModalPicker = () => {
onHide={handleHide}
onOk={handleHide}
hideFooter
preventDragByTitle
>
<DatePicker defaultValue="2019-08-13 12:02:53" />
</Modal>
Expand Down
22 changes: 22 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4068,6 +4068,11 @@ clone@^1.0.2:
resolved "https://registry.npm.taobao.org/clone/download/clone-1.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fclone%2Fdownload%2Fclone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=

clsx@^1.1.1:
version "1.2.1"
resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==

co@^4.6.0:
version "4.6.0"
resolved "https://registry.npm.taobao.org/co/download/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
Expand Down Expand Up @@ -10523,6 +10528,15 @@ prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, pr
object-assign "^4.1.1"
react-is "^16.8.1"

prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"

property-information@^5.0.0:
version "5.6.0"
resolved "https://registry.npm.taobao.org/property-information/download/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69"
Expand Down Expand Up @@ -10964,6 +10978,14 @@ react-draggable@^4.0.3:
classnames "^2.2.5"
prop-types "^15.6.0"

react-draggable@^4.4.5:
version "4.4.5"
resolved "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz#9e37fe7ce1a4cf843030f521a0a4cc41886d7e7c"
integrity sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==
dependencies:
clsx "^1.1.1"
prop-types "^15.8.1"

react-error-overlay@^6.0.3, react-error-overlay@^6.0.9:
version "6.0.9"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
Expand Down

0 comments on commit 43ad71c

Please sign in to comment.