Reputation:
I've tried making this to work, but this is what happened.
I also found this online https://gist.github.com/burgalon/870a68de68c5ed15c416fab63589d503,
import { Modal } from 'react-bootstrap'
import ModalDialog from 'react-bootstrap/lib/ModalDialog'
import Draggable from 'react-draggable'
class DraggableModalDialog extends React.Component {
render() {
return <Draggable handle=".modal-title"><ModalDialog
{...this.props} /></Draggable>
}
}
// enforceForce=false causes recursion exception otherwise....
export default ({titleIconClass, modalClass, children, title,...props}) =>
<Modal dialogComponent={DraggableModalDialog} show={true} enforceFocus={false} backdrop="static" {...props}>
<Modal.Header closeButton>
<Modal.Title>
{title}
</Modal.Title>
</Modal.Header>
<Modal.Body>
{children}
</Modal.Body>
</Modal>
This code I got from the searching around, I can't actually get this to work.
Especially this,
<ModalDialog {...this.props} />
, I do not get why the props are sent in and what kind of props are sent in.
And
<Modal dialogComponent={DraggableModalDialog} show={true} enforceFocus={false} backdrop="static" {...props}>
<------ {...props} what does that do? it doesn't seem like it's giving props to Modal. what is the purpose of it? Is it relevant to
"<ModalDialog {...this.props} />"
?
Can anyone give me a hint how those above two questions can work if this is a valid work?
Thank you!
Upvotes: 7
Views: 10905
Reputation: 2775
I think this solution is more simple and you could use the whole modal header to drag the modal. However I use reactstrap!
import { Modal, ModalHeader, ModalBody } from "reactstrap";
import Draggable from "react-draggable";
...
<Draggable handle=".handle">
<Modal size="lg" toggle={function noRefCheck(){}}>
<ModalHeader toggle={function noRefCheck(){}} className="handle">
Modal Title
</ModalHeader>
<ModalBody>
Modal Body
</ModalBody>
</Modal>
</Draggable>
Upvotes: 0
Reputation: 11
For anyone still struggling with this, i found a solution without using the draggable library that works smoothly.
Create 4 state variables for your modal. Two for the Modal header, and two for the main Component. Create a ref for the header aswell.
const [headerLeft, setHeaderLeft] = useState(0);
const [left, setLeft] = useState('0');
const [headerTop, setHeaderTop] = useState(0);
const [top, setTop] = useState('0');
const modalHeader = useRef<HTMLDivElement>(null);
Using the ref you created for the header, add a listener on the modal header OnMouseDown. Then add a listener on the document to implement your dragging mechanism.
modalHeader.current?.addEventListener("mousedown", () => {
document.addEventListener("mousemove", onDrag);
});
Then create your dragging function. I've added an id on my modal header to be able to get the HTMLElement using getElementById. Then i'm calculating the new position based on top & left vars and update my state.
function onDrag(e) {
const modalHeaderElement = document.getElementById('modal-header');
if (modalHeaderElement) {
const getStyle = window.getComputedStyle(modalHeaderElement);
const leftVal = parseInt(getStyle.left);
const topVal = parseInt(getStyle.top);
setHeaderLeft(e.movementX + leftVal);
setHeaderTop(e.movementY + topVal);
setLeft(`${leftVal + e.movementX}px`);
setTop(`${topVal + e.movementY}px`);
}
}
Then all you need to do is update the style prop on the modal & modal header components
<Modal style={{ left: left, top: top }} >
<Modal.Header id='modal-header' ref={modalHeader} style={{ left: headerLeft, top: headerTop}}>A Header </Modal.Header>
<Modal.Body></Modal.Body>
</Modal>
Finally add another listener on the document for the mouseUp to stop dragging
document.addEventListener("mouseup", () => {
document.removeEventListener("mousemove", onDrag);
});
In my use-case, i've placed the addEventListeners and OnDrag function inside a useImperativeHandle hook because that's how i utilize my Modal (I'm wrapping it with a forwardRef). But i'm guessing you can do that inside a useEffect and work just as well.
Edit: You can also init the state variables to not keep your modal in last position that you dragged it.
Edit 2: Posting the code below to make it a custom hook and re-use it in your code. The idea is that the custom hook will return the state variables to update the coordinates of your modal. The input of the custom hook is the current of your modalHeader ref , the id you've provided in the Modal.Header element and the show state variable that shows or hides your modal.
export function useDraggable(modalHeader: HTMLDivElement | null, modalHeaderId: string, show: boolean) {
const [headerLeft, setHeaderLeft] = useState(0);
const [left, setLeft] = useState('0px');
const [headerTop, setHeaderTop] = useState(0);
const [top, setTop] = useState('0px');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onDrag = useCallback((e: any) => {
const modalHeaderElement = document.getElementById(modalHeaderId);
if (modalHeaderElement) {
const getStyle = window.getComputedStyle(modalHeaderElement);
const leftVal = parseInt(getStyle.left);
const topVal = parseInt(getStyle.top);
setHeaderLeft(_prevState => e.movementX + leftVal);
setHeaderTop(_prevState => e.movementY + topVal);
setLeft(_prevState => `${leftVal + e.movementX}px`);
setTop(_prevState => `${topVal + e.movementY}px`);
}
}, []);
const onMouseUp = useCallback(() => {
document.removeEventListener("mousemove", onDrag);
const modalHeaderElement = document.getElementById(modalHeaderId);
if (modalHeaderElement) {
modalHeaderElement.classList.remove("active");
}
}, []);
useEffect(() => {
const modalHeaderElement = document.getElementById(modalHeaderId);
if (modalHeaderElement && show) {
modalHeaderElement.addEventListener("mousedown", () => {
modalHeaderElement.classList.add("active");
document.addEventListener("mousemove", onDrag);
});
document.addEventListener("mouseup", onMouseUp);
}
if (!show) {
if (modalHeader) {
document.removeEventListener("mouseup", onMouseUp);
}
setLeft('0px');
setTop('0px');
setHeaderLeft(0);
setHeaderTop(0);
}
}, [modalHeaderId, modalHeader, show, onMouseUp, onDrag])
return {
headerTop,
headerLeft,
top,
left
};
}
Upvotes: 0
Reputation: 565
I had a warning that findDOMNode is deprecated in StrictMode
I was able to get rid of this by using nodeRef instead of handle. My function component/typescript DraggableModalDialog looks like this:
/* eslint-disable react/jsx-props-no-spreading */
import React, { RefObject } from 'react';
import Draggable from 'react-draggable';
import ModalDialog, { ModalDialogProps } from 'react-bootstrap/ModalDialog';
export interface DraggableModalDialogProps extends ModalDialogProps {
nodeRef?: RefObject<HTMLElement>;
}
function DraggableModalDialog({ nodeRef, ...props }: DraggableModalDialogProps) {
return (
<Draggable nodeRef={nodeRef}>
<ModalDialog {...props} />
</Draggable>
);
}
export default DraggableModalDialog;
Modal component is like this (Simplified it here a bit):
import React, { useRef } from 'react';
import { Modal, Button } from 'react-bootstrap';
import DraggableModalDialog from 'components/DraggableModalDialog';
export interface MyModalProps {
visible: boolean;
onClose: () => void;
}
function MyModal({ visible, onClose }: MyModalProps) {
const nodeRef = useRef<HTMLDivElement>(null);
return (
<Modal
nodeRef={nodeRef}
dialogAs={DraggableModalDialog}
show={visible}
centered
size="lg"
backdrop="static"
onHide={onClose}
>
<Modal.Header ref={nodeRef}>
<Modal.Title>TITLE</Modal.Title>
</Modal.Header>
<Modal.Body>BODY</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={onClose}>CLOSE</Button>
</Modal.Footer>
</Modal>
);
}
export default MyModal;
Upvotes: 1
Reputation: 3208
For anyone who might be still struggling with the latest version of react-bootstrap
( mine is 1.0.0-beta.5
at the writing time). Here is the modified version of (https://gist.github.com/burgalon/870a68de68c5ed15c416fab63589d503)
import React, { Component } from "react";
import Modal from "react-bootstrap/Modal";
import Draggable from 'react-draggable';
import ModalDialog from 'react-bootstrap/ModalDialog';
class DraggableModalDialog extends React.Component {
render() {
return <Draggable handle=".modal-title"><ModalDialog {...this.props} />
</Draggable>
}
}
export default class BSModal extends Component {
render() {
return (
<Modal
dialogAs={DraggableModalDialog}
show={this.props.show}
onHide={this.props.close}>
<Modal.Header>
<Modal.Title>{this.props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.props.children}
</Modal.Body>
<Modal.Footer >
</Modal.Footer>
</Modal>
);
}
}
Upvotes: 8