I am working with the MUI library, specifically with the Draggable Dialog component seen here.
My question is how do I stop the dialog from being dragged off the screen?
I have tried this inside the Paper Component, but its not working:
import * as React from 'react';
import Paper, { PaperProps } from '#mui/material/Paper';
import Draggable, { DraggableEvent, DraggableData } from 'react-draggable';
export const PaperComponent = (props: PaperProps) => {
const widgetContainerRef = React.useRef<HTMLInputElement>(null);
const [widgetState, setWidgetState] = React.useState({
visible: false,
disabled: true,
bounds: { left: 0, top: 0, bottom: 0, right: 0 },
});
// const draggleRef = useRef<HTMLInputElement>(null);
const onStart = (event: DraggableEvent, uiData: DraggableData) => {
const { clientWidth, clientHeight } = window?.document?.documentElement;
const targetRect = widgetContainerRef?.current?.getBoundingClientRect();
if (targetRect) {
setWidgetState((prevState) => ({
...prevState,
bounds: {
left: -targetRect?.left + uiData?.x,
right: clientWidth - (targetRect?.right - uiData?.x),
top: -targetRect?.top + uiData?.y,
bottom: clientHeight - (targetRect?.bottom - uiData?.y),
},
}));
}
};
return (
<Draggable
handle="#draggable-dialog-title"
cancel={'[class*="MuiDialogContent-root"]'}
nodeRef={widgetContainerRef}
onStart={(event, uiData) => onStart(event, uiData)}
>
{/* <Resizable height={scaler.height} width={scaler.width} onResize={onResize}> */}
<div ref={widgetContainerRef}>
<Paper {...props} />
</div>
</Draggable>
);
};
Would anyone know how to prevent my user from dragging the dialog off the screen?
Looks like I overlooked a little thing here. It works if you add :
<Draggable
...
bounds={widgetState.bounds}
>
<div ref={widgetContainerRef}>
<Paper {...props} />
</div>
</Draggable>
Use onDrag Prop to prevent from being dragged, just return false on a specific scenario
onDrag={(e: any) => {
const from = e.relatedTarget || e.toElement;
if (!from || from.nodeName === EVENT_NODE_NAME.HTML) {
// stop your drag event here
return false;
}
}}
handle='#event-max-dialog'
cancel={'[class*="MuiDialogContent-root"]'}>```
Related
I use the throttle from lodash in this way on mouseMove event to track the mouse position in the div square, but the throttle function that I wrote seems not working. where I am wrong?
import React, { useState } from "react";
import "./index.css";
import _throttle from "lodash/throttle";
function Throttle() {
const [state, setState] = useState({
clientX: 0,
clientY: 0
});
const update = ({ clientX, clientY }) => {
setState({ clientX, clientY });
};
const throtteFn = _throttle(update, 1000);
const handleMouseMove = (event) => {
event.persist();
throtteFn(event);
};
return (
<>
<div
style={{
width: "10em",
backgroundColor: "green",
height: "10em"
}}
onMouseMove={handleMouseMove}
/>
<div>
<p>clientX: {state.clientX}</p>
<p>clientY: {state.clientY}</p>
</div>
</>
);
}
export default Throttle;
You need to consider the position of the rectangle in the viewport. Keep a ref to get the left and top positions of the rectangle.
Try like this
const ref = useRef();
const update = ({ clientX, clientY }) => {
const { top, left } = ref.current.getBoundingClientRect();
// deduct the left and top offset values before setting the values
setState({
clientX: clientX - left >= 0 ? clientX - left : 0,
clientY: clientY - top >= 0 ? clientY - top : 0
});
};
...
...
<>
<div
style={{
width: "10em",
backgroundColor: "green",
height: "10em"
}}
onMouseMove={handleMouseMove}
ref={ref}
/>
...
...
</>
Code Sandbox
You must use usecallback in the throttle function.
For now, throttleFn is constantly being created so it doesn't work the way you want it to.
const throtteFn = useCallback(_throttle(update, 1000), []);
sandbox live
I created this react Collapse component with react-spring which have the open and close functionality.
I want to have a defaultOpen prop, so the content stays open on initial render then it use isOpen state for closing and opening.
How can i do that?
here is a codesandbox link: https://codesandbox.io/s/awesome-fire-wsvml
For that, you will need to have Collapsible component own state
And pass defaultOpen from the parent component
Something like this:
const Collapse = ({ defaultOpen = false, children }) => {
const [isOpen, setIsOpen] = useState(defaultOpen)
const [ref, { height: viewHeight }] = useMeasure()
const { height, opacity } = useSpring({
from: { height: 0, opacity: 1 },
to: {
height: isOpen ? viewHeight : 0,
opacity: isOpen ? 1 : 0,
},
})
return (
<>
<button onClick={() => setIsOpen(isOpen => !isOpen)}>{isOpen ? 'close' : 'open'}</button>
<Content style={{ opacity, height }}>
<a.div ref={ref}>{children}</a.div>
</Content>
</>
)
}
Working Example:
If you wan't to make use of the property defaultOpen provide it to the App component like this:
export default function App({ defaultOpen = true }) {
const [isOpen, setIsOpen] = useState(defaultOpen)
return (
<Collapse isOpen={isOpen}>
...
</Collapse>
)
}
I have a simple list of options in a menu like so:
Option 1
Option 2
Option 3
When the user clicks on an option, there should be a highlighted bar that shows which one they selected. And when the user clicks on different options, the highlighted bar should slide up and down depending on what they chose. I'm trying to use react-spring, but I can't seem to get the animation and clicking behavior to happen properly.
With my current code, the highlighted bar does not slide up and down; it just shows and hides upon user selection. And clicking on an option once does not put the highlighted bar on it, instead, I have to click twice for it to show up correctly on the selected option.
Help is appreciated! This is my first time using react-spring so I'm a bit lost on this.
Below is the code snippet for the animations and rendering the component:
const [currentIndex, setCurrentIndex] = useState<number>(0);
const [previousIndex, setPreviousIndex] = useState<number>(0);
const onClick = (name: string, index: number) => {
setPreviousIndex(currentIndex);
setCurrentIndex(index);
setSpring(fn());
};
// Spring animation code
const fn = () => (
{
transform: `translateY(${currentIndex * 52}px)`,
from: {
transform: `translateY(${previousIndex * 52}px)`,
},
}
);
const [spring, setSpring] = useState<any>(useSpring(fn()));
// Rendering component
return (
<div>
{options.map((option, index) => (
<>
{currentIndex === index && <animated.div style={{...spring, ...{ background: 'orange', height: 52, width: '100%', position: 'absolute', left: 0, zIndex: 1}}}></animated.div>}
<div onClick={() => onClick(option.name, index)}>
<TextWithIcon icon={currentIndex === index ? option.filledIcon : option.outlineIcon} text={option.name} />
</div>
</>
))}
</div>
);
And here is the custom component, TextWithIcon:
// Interfaces
interface TextWithIconProps {
containerStyle?: Record<any, any>;
icon: ReactElement;
text: string;
textStyle?: Record<any, any>;
}
// TextWithIcon component
const TextWithIcon: React.FC<TextWithIconProps> = ({ containerStyle, icon, text, textStyle}) => {
return (
<div id='menu-items' style={{...styles.container, ...containerStyle}}>
{icon}
<Text style={{...styles.text, ...textStyle}}>{text}</Text>
</div>
)
};
You have to set the duration property of the config property inside the useSpring's parameter to a value. Try the following:
const [currentIndex, setCurrentIndex] = useState<number>(0);
const [previousIndex, setPreviousIndex] = useState<number>(0);
const onClick = (name: string, index: number) => {
setPreviousIndex(currentIndex);
setCurrentIndex(index);
setSpring(fn());
};
// Spring animation code
const fn = () => (
{
transform: `translateY(${currentIndex * 52}px)`,
from: {
transform: `translateY(${previousIndex * 52}px)`,
},
config: {
duration: 1250 //this can be a different number
}
}
);
const [spring, setSpring] = useState<any>(useSpring(fn()));
// Rendering component
return (
<div>
{options.map((option, index) => (
<>
{currentIndex === index && <animated.div style={{...spring, ...{ background: 'orange', height: 52, width: '100%', position: 'absolute', left: 0, zIndex: 1}}}></animated.div>}
<div onClick={() => onClick(option.name, index)}>
<TextWithIcon icon={currentIndex === index ? option.filledIcon : option.outlineIcon} text={option.name} />
</div>
</>
))}
</div>
);
References:
StackOverflow. Animation duration in React Spring.https://stackoverflow.com/a/54076843/8121551. (Accessed August 23, 2021).
I am attempting to create a button which pops up a react-color SketchPicker component(akin to the first example listed in react-color's "More Examples"). However, when I open my popup and drag the selector in SketchPicker, the color in the UI does not update, and the selector does not move.
I have perused my component, and I cannot find a single aspect of it in error; I have merely updated the first component in More Examples to utilize useState.
Why is this?
CodeSandbox
You need to pass a color prop to SketchPicker component and also add an onChange handler so whenever you move the cursor this handler triggers and it has access to selected color which you can update the state variable and pass that state variable to color prop.
import React, { useState } from "react";
import "./ColorSelector.css";
import { SketchPicker } from "react-color";
const ColorSelector = () => {
const [display, setDisplay] = useState(false);
const [color, setColor] = useState({
r: "241",
g: "112",
b: "19",
a: "1"
});
const onClickMethod = () => {
setDisplay(!display);
};
const onCloseMethod = () => {
setDisplay(false);
};
const onChangeMethod = (color) => {
setColor({ ...color.rgb });
};
const popover = {
position: "absolute",
zIndex: "3"
};
const cover = {
position: "fixed",
top: "0px",
right: "0px",
bottom: "0px",
left: "0px"
};
return (
<div>
<a className="colorSelector" onClick={onClickMethod}>
select color
</a>
{display ? (
<div style={popover}>
<div style={cover} onClick={onCloseMethod} />
<SketchPicker color={color} onChange={onChangeMethod} />
</div>
) : null}
</div>
);
};
export default ColorSelector;
I would like to provide a way to make an antd Drawer resizable ?
I read a popular answer specifically for material-ui/Drawer but I am looking to do something very similar with antd.
Does anyone have a similar antd example - or have a better idea how to handle info getting chopped off at side of the drawer.
You can extend the width of Drawer by specifying it on the width props. If you don't want to extend it but you want the content to be still fit, you can set the width on bodyStyle prop and use overflow: "auto":
<Drawer
title="Basic Drawer"
placement="right"
closable={false}
visible={isDrawerVisible}
bodyStyle={{
width: 400,
overflow: "auto"
}}
onClose={toggleDrawerVisible}
>
I also made a resizable drawer based on the link that you provide in antd version (react hooks version answer).
ResizableDrawer.jsx
import React, { useState, useEffect } from "react";
import { Drawer } from "antd";
let isResizing = null;
const ResizableDrawer = ({ children, ...props }) => {
const [drawerWidth, setDrawerWidth] = useState(undefined);
const cbHandleMouseMove = React.useCallback(handleMousemove, []);
const cbHandleMouseUp = React.useCallback(handleMouseup, []);
useEffect(() => {
setDrawerWidth(props.width);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.visible]);
function handleMouseup(e) {
if (!isResizing) {
return;
}
isResizing = false;
document.removeEventListener("mousemove", cbHandleMouseMove);
document.removeEventListener("mouseup", cbHandleMouseUp);
}
function handleMousedown(e) {
e.stopPropagation();
e.preventDefault();
// we will only add listeners when needed, and remove them afterward
document.addEventListener("mousemove", cbHandleMouseMove);
document.addEventListener("mouseup", cbHandleMouseUp);
isResizing = true;
}
function handleMousemove(e) {
let offsetRight =
document.body.offsetWidth - (e.clientX - document.body.offsetLeft);
let minWidth = 256;
let maxWidth = 600;
if (offsetRight > minWidth && offsetRight < maxWidth) {
setDrawerWidth(offsetRight);
}
}
return (
<Drawer {...props} width={drawerWidth}>
<div className="sidebar-dragger" onMouseDown={handleMousedown} />
{children}
</Drawer>
);
};
export default ResizableDrawer;
and to use it:
import ResizableDrawer from "./ResizableDrawer";
<ResizableDrawer
title="Resizable Drawer"
placement="right"
closable={false}
visible={isResizableDrawerVisible}
onClose={toggleResizableDrawerVisible}
>
...
</ResizableDrawer>
See working demo here:
Have two states for tracking the width of the drawer and whether or not the drawer is being resized (isResizing).
Add two event listeners on the global document where it will listen for mousemove and mouseup. The mousemove event will resize the drawer, only if isResizing is true. And the mouseup event will set isResizing to false.
Add a div in your drawer that acts as the draggable border for making the drawer resizable. This div will listen for a mousedown event, which will set the state of isResizing to true.
Here's the code that has been improved upon from the basic drawer demo from antd's website.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Drawer, Button } from "antd";
const App = () => {
const [visible, setVisible] = useState(false);
const [isResizing, setIsResizing] = useState(false);
const [width, setWidth] = useState(256);
const showDrawer = () => {
setVisible(true);
};
const onClose = () => {
setVisible(false);
};
const onMouseDown = e => {
setIsResizing(true);
};
const onMouseUp = e => {
setIsResizing(false);
};
const onMouseMove = e => {
if (isResizing) {
let offsetRight =
document.body.offsetWidth - (e.clientX - document.body.offsetLeft);
const minWidth = 50;
const maxWidth = 600;
if (offsetRight > minWidth && offsetRight < maxWidth) {
setWidth(offsetRight);
}
}
};
useEffect(() => {
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
});
return (
<>
<Button type="primary" onClick={showDrawer}>
Open
</Button>
<Drawer
title="Basic Drawer"
placement="right"
closable={false}
onClose={onClose}
visible={visible}
width={width}
>
<div
style={{
position: "absolute",
width: "5px",
padding: "4px 0 0",
top: 0,
left: 0,
bottom: 0,
zIndex: 100,
cursor: "ew-resize",
backgroundColor: "#f4f7f9"
}}
onMouseDown={onMouseDown}
/>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Drawer>
</>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
And here's the demo of the code:
DEMO