I am trying to make a Draggable component with custom context menu in ReactJS, using
react-draggable and react-contextmenu with no success.
I`ve had succeeded to make a button draggable but when I add the context menu it disables the draggable and only the contextmenu works.
I would love to combine them together somehow, thanks!
import React, { useState } from 'react';
import Draggable from 'react-draggable';
import {ContextMenu, ContextMenuTrigger} from 'react-contextmenu';
export default function DraggableButton() {
const [deltaPosition, setDeltaPosition] = useState({ x: 0, y: 0 });
const handleDrag = (e, ui) => {
const { x, y } = deltaPosition;
setDeltaPosition({
x: x + ui.deltaX,
y: y + ui.deltaY,
});
};
return (
<div>
<Draggable
scale={1}
onDrag={handleDrag}
bounds="parent"
defaultPosition={{ x: 40, y: 40 }}
allowAnyClick={false}
>
<ContextMenuTrigger id='trigger' >
<div className="w-24">
<button className="btn btn-primary">
click me
</button>
</div>
</ContextMenuTrigger>
</Draggable>
<ContextMenu id='trigger'>
<h1>test</h1>
</ContextMenu>
</div>
);}
Try wrapping Draggable around ContextMenuTrigger:
<ContextMenuTrigger id='trigger'>
<Draggable
scale={1}
onDrag={handleDrag}
bounds="parent"
defaultPosition={{ x: 40, y: 40 }}
allowAnyClick={false}>
<div className="w-24">
<button className="btn btn-primary">click me</button>
</div>
</Draggable>
</ContextMenuTrigger>
Note: the context menu opens in a different location with this.
Leaving this here as it may help others.
Related
I make a graph using the Victory library. I need to build a grouped VictoryBar, so I use VictoryGroup to do that.
But I have a lot of data, and the data doesn't fit on the graph. How can I enlarge the graph so that the y-axis data doesn't get mixed up?
If there is less data, the data is placed on the graph
https://codesandbox.io/s/zen-edison-fb8dy4?file=/src/App.js
import "./styles.css";
import plotData from "./data.json";
import React from "react";
import { VictoryBar, VictoryChart, VictoryGroup } from "victory";
export default function App() {
let arr1 = [];
let arr2 = [];
let arr1Sum = 0;
let arr2Sum = 0;
plotData.map((plotData, index) => {
arr1Sum += plotData.ofac_share;
arr2Sum += plotData.non_ofac_share;
});
plotData.map((plotData, index) => {
arr1.push({
y: (plotData.ofac_share / arr1Sum) * 100,
x: plotData.validator
});
arr2.push({
y: (plotData.non_ofac_share / arr2Sum) * 100,
x: plotData.validator
});
});
return (
<VictoryChart>
<VictoryGroup offsetY={10} colorScale={["tomato", "orange"]}>
<VictoryBar horizontal barWidth={3} data={arr1} />
<VictoryBar horizontal barWidth={3} data={arr2} />
</VictoryGroup>
</VictoryChart>
);
}
enter image description here
I tried changing the 'offset' and 'barWidth' parameter, but it didn't help me
As per documentation, as well as other layout configurations, the VictoryChart component supports a height property:
<VictoryChart height={600}>
<VictoryGroup
offsetY={10}
colorScale={['tomato', 'orange']}
>
<VictoryBar
horizontal
barWidth={3}
data={arr1}
/>
<VictoryBar
horizontal
barWidth={3}
data={arr2}
/>
</VictoryGroup>
</VictoryChart>;
Here is the link to the relevant Victory documentation section, but here is a snippet from that link that summarises:
Victory components have default width, height, and padding props defined in the default grayscale theme.
Hope this helps.
I'm trying to create a gridstack.js dashboard with Vue 3 and I want the grid stack items to contain reactive vue 3 components.
The problem is that these grid stack items can only be passed HTML. The documentation states you should be able to add Vue components as content but the examples are for Vue 2 and I'm struggling to implement this in Vue 3.
I have the following code:
<template>
<div class="p-6 h-full flex flex-col">
<header class="flex flex-row items-center justify-between mb-6">
<div>
<h1 class="text-3xl font-bold">
Workbench
</h1>
<p class="leading-6 text-gray-600 text-sm mt-2">
{{ info }}
</p>
</div>
<div class="flex flex-row items-center">
<button type="button" #click="addPanel()">Add Panel</button>
</div>
</header>
<div class="flex-1">
<section class="grid-stack"></section>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, defineComponent, createApp } from "vue"
import TestPanel from "./../components/panels/TestPanel.vue"
let grid = null;
const items = [
{ x: 0, y: 0, h: 4, w: 6 },
{ x: 7, y: 0, h: 4, w: 6 },
{ x: 0, y: 5, h: 4, w: 4 },
{ x: 4, y: 5, h: 4, w: 4 },
{ x: 8, y: 5, h: 4, w: 4 },
];
onMounted(() => {
grid = GridStack.init({
// float: true,
cellHeight: "70px",
minRow: 1,
});
grid.load(items)
});
function addPanel() {
const div = document.createElement("div")
div.id = Math.random().toString(24).substring(8)
const componentInstance = defineComponent({
extends: TestPanel, data() {
return {
test: "this is a test"
}
}
})
const app = createApp(componentInstance)
app.mount(div)
let widget = grid.addWidget({
x: 0,
y: 0,
w: 6,
h: 3,
content: div.outerHTML,
})
app.mount(div.id)
}
</script>
<style>
.grid-stack-item-content {
background-color: #18BC9C;
}
</style>
This will load the vue component in a stack grid item but the component is no longer reactive.
Any help would be greatly appreciated, thanks in advance!
This is probably not exactly what the gridstack-creators had in mind but here you go:
<template>
<button #click="addNewWidget()">Add Widget</button> {{ info }}
<section class="grid-stack">
<div
v-for="(component, key, index) in components"
:key="'component'+index"
:gs-id="key"
class="grid-stack-item"
:gs-x="component.gridPos.x"
:gs-y="component.gridPos.y"
:gs-h="component.gridPos.h"
:gs-w="component.gridPos.w"
gs-auto-position="true"
>
<div class="grid-stack-item-content">
<component :is="component.name" v-bind="component.props" />
</div>
</div>
</section>
</template>
<script>
import { ref, onMounted, reactive, nextTick } from 'vue';
import 'gridstack/dist/gridstack.min.css';
import { GridStack } from 'gridstack';
import YourRandomComponent1 from '../YourRandomComponent1.vue';
import YourRandomComponent2 from '../YourRandomComponent2.vue';
import YourRandomComponent3 from '../YourRandomComponent3.vue';
export default {
name: "WidgetGrid",
setup() {
let info = ref("");
let grid = null;
let components = reactive({
yourRandomComponent1: {
name: "YourRandomComponent1", props: {}, gridPos: { x: 0, y: 1, w: 4, h: 5 }
},
yourRandomComponent2: {
name: "YourRandomComponent2", props: {}, gridPos: { x: 0, y: 1, w: 2, h: 5 }
},
});
onMounted(() => {
grid = GridStack.init({
float: true,
cellHeight: "70px",
minRow: 1,
});
grid.on("dragstop", (event, element) => {
console.log("move event!", event, element);
const node = element.gridstackNode;
info.value = `you just dragged node #${node.id} to ${node.x},${node.y} – good job!`;
});
});
// this will of course only work once because of the object-key
function addNewWidget() {
components.yourRandomComponent3= {
name: "YourRandomComponent3", props: {}, gridPos: { x: 0, y: 1, w: 2, h: 5 }
};
// we have to wait for vue to update v-for,
// until then the querySelector wont find the element
nextTick(() => {
console.log(grid);
let compEl = document.querySelector('[gs-id="yourRandomComponent3"]');
console.log(compEl);
grid.makeWidget(compEl);
});
console.warn("i will only work once, fix my inputs to reuse me");
}
return {
info,
components,
};
},
components: {
// eslint-disable-next-line vue/no-unused-components
YourRandomComponent1,
// eslint-disable-next-line vue/no-unused-components
YourRandomComponent2,
},
}
</script>
<style>
.grid-stack {
background-color: #FAFAFF;
border-style: dashed;
}
.grid-stack-item {
color: #2c3e50;
text-align: center;
border-style: solid;
overflow: auto;
z-index: 50;
}
</style>
In my case, a missing div with the grid-stack-item-content-class wrapping the component made the widgets immobile.
I have also added an add-new-widget function that demonstrates how to add a new widget to the grid. The key is to use reactive() so that Vue will re-render the page. After re-rendering, the component needs to be registered as a grid element using grid.makeWidget. For this we need the component's Dom element, which we get after Vue has re-rendered with nextTick.
You can use own component in Vue3 like this
<div class="grid-stack" :style="{ 'background-color': hex }" >
<widget v-for="widget in widgets" :widget="widget" :key="widget" />
</div>
Import your component
import Widget from "src/components/GridStackComponent.vue";
Add component to export
export default {
name: 'GridStack',
components: {
Widget
},
data() {
...
},
...
}
And that's all. Result can look like this
I am trying to make a webapplication with Treeviz dependency. The goal is to place a popover button to each node of the tree and if user clicks to the button he/she can see the description of the node,and after it should be editable. I tried in many ways but for me popover does not work in React.
There is an example for what I would like to do. You can see I have to insert React component to HTML therefor I am using renderToString. All you have to look is the renderNode property of the tree. I am referencing to React component in renderNode like: ${tooltip} ${popover}.
import React from "react";
import { TreevizReact } from "treeviz-react";
import { renderToString } from "react-dom/server";
const data_1 = [
{
id: 1,
text_1: "Chaos",
description: "Void",
father: null,
color: "#FF5722"
},
{
id: 2,
text_1: "Tartarus",
description: "Abyss",
father: 1,
color: "#FFC107"
},
{ id: 3, text_1: "Gaia", description: "Earth", father: 1, color: "#8BC34A" },
{ id: 4, text_1: "Eros", description: "Desire", father: 1, color: "#00BCD4" }
];
export const App = () => {
return (
<div style={{ marginLeft: 10 }}>
<div style={{ display: "flex" }}>
<TreevizReact
data={data_1}
relationnalField={"father"}
nodeWidth={120}
nodeHeight={80}
areaHeight={500}
areaWidth={1000}
mainAxisNodeSpacing={2}
secondaryAxisNodeSpacing={2}
linkShape={"quadraticBeziers"}
renderNode={(data) => {
const nodeData = data.data;
const settings = data.settings;
let result = "";
const tooltip = renderToString(
<strong
data-toggle="tooltip"
data-placement="top"
title={nodeData.description}
>
{nodeData.text_1}
</strong>
);
const popover = renderToString(
<button
type="button"
className="btn btn-secondary"
data-container="body"
data-toggle="popover"
data-placement="top"
data-content={nodeData.description}
>
Popover on top
</button>
);
if (data.depth !== 2) {
result = `<div className="box"
style='cursor:pointer;height:${settings.nodeHeight}px; width:${settings.nodeWidth}px;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:${nodeData.color};border-radius:5px;'>
<div>
${tooltip}
${popover}
</div></div>`;
} else {
result = `<div className="box" style='cursor:pointer;height:${
settings.nodeHeight
}px; width:${
settings.nodeHeight
}px;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:${
nodeData.color
};border-radius:${settings.nodeHeight / 2}px;'><div><strong>
${nodeData.text_1}
</strong></div></div>`;
}
return result;
}}
duration={600}
isHorizontal
linkWidth={(node) => 10}
/>
</div>
</div>
);
};
export default App;
Tooltip is working but popover does not show up.
You can try it: https://codesandbox.io/s/zealous-orla-4bq5f?file=/src/App.js
Also tried
const popover = renderToString(
<Popup
trigger={<button> Trigger</button>}
position="right center"
>
<form onSubmit={saveHandler}>
<ContentEditable
html={text.current}
onChange={handleChange}
/>
<button type="submit">Save</button>
<button>Cancel</button>
</form>
</Popup>
const popoverContent = (
<Popover id="popover-basic">
<Popover.Header as="h3">Popover right</Popover.Header>
<Popover.Body>
And here's some <strong>amazing</strong> content. It's very
engaging. right?
</Popover.Body>
</Popover>
);
const popover = renderToString(
<OverlayTrigger
trigger="click"
placement="right"
overlay={popoverContent}
>
<Button variant="success">Click me to see</Button>
</OverlayTrigger>
);
None of them worked for me.
Probably your approach doesn't work because the dom elements in the tree are created dynamically, and bootstrap doesn't set them up.
A more react-ish way to do it would be using react-bootstrap lib and managing every UI aspect in states. To implement the tooltip, the Overlay component actually as a prop called target that allows you to change over what element the tooltip is shown.
<Overlay target={tooltipTarget} show={showTooltip} placement="right">
{(props) => (
<Tooltip id="overlay-example" {...props}>
{tooltipNode?.data.text_1}
</Tooltip>
)}
</Overlay>
Then you only need to manage all these states in the onNodeMouseEnter and onNodeMouseLeave handlers in the TreevizReact component.
onNodeMouseEnter={(_event, node) => {
const t = document.querySelector(`#node-${node.id}`);
setTooltipTarget(t);
setShowTooltip(true);
setTooltipNode(node);
}}
onNodeMouseLeave={(_event, node) => {
setTooltipTarget(null);
setShowTooltip(false);
setTooltipNode(null);
}}
The popup follows the same logic with another set of states.
<div ref={ref}>
<Overlay
show={!!selectedNode.current}
target={target}
placement="bottom"
container={ref.current}
containerPadding={20}
>
<Popover id="popover-contained">
{/* hack to avoid weird blinking (due mutable state) */}
{!!selectedNode.current && (
<>
some form based on node data
{JSON.stringify(selectedNode.current?.data)}
</>
)}
</Popover>
</Overlay>
</div>
and in onNodeClick handler
onNodeClick={(_event, node) => {
const t = document.querySelector(`#node-${node.id}`);
// unselect if already selected
if (selectedNode.current?.id === node.id) {
selectedNode.current = null;
setTarget(null);
} else {
selectedNode.current = node;
setTarget(t);
}
}}
You might notice this time I used a mutable variable via a ref (selectedNode.current), for some reason using a state caused some issues, maybe due to D3 and react having different rendering cycles, reason why you'll probably encounter some other glitches in this implementation.
https://codesandbox.io/s/quizzical-buck-slofh?file=/src/App.js
I struggle to render a simple grid for my test project.
Didn't want to create grid by hand, because with bigger grids that would not only be a chore, but also the code would be cluttered, so figured I should use lodash for this.
However, I can't seem to render the grid, it's just not visible even when I inspect in dev tools. Can someone point my mistakes?
I am also fine with using other tools than lodash if necessary.
Here is the code:
import React from 'react';
import _ from 'lodash';
import './game.css';
const GRID = [
[{x: 1, y:3}, {x:2, y:3}, {x:3,y:3}],
[{x: 1, y:2}, {x:2, y:2}, {x:3,y:2}],
[{x: 1, y:1}, {x:2, y:1}, {x:3,y:1}],
]
class Game extends React.Component {
renderGrid() {
return _.map(GRID, row =>{
_.map(row, cell =>{
return <div style={{height: 100 + 'px', width: 100+ 'px'}}> {cell.x}, {cell.y} </div>
})
})
}
render() {
return (
<div className="game">
{this.renderGrid()}
</div>
)
}
}
export default Game;
You are not returning the inner map result, once you do that it will work
renderGrid() {
return _.map(GRID, row =>{
return _.map(row, (cell, index) =>{
return <div key={index} style={{height: 100 + 'px', width: 100+ 'px'}}> {cell.x}, {cell.y} </div>
})
})
}
Working codesandbox
In your case, the array buildin map function should be enough.
Don't forget give an unique key for each element in the map
Tips:
items.map(item => item) is the short hand format for items.map(item => { return(item); })
If you put number in inline css style, 'px' unit will be used as default.
Based on your input:
class Game extends Component {
render() {
return (
<div className="game">
{
GRID.map((row, rowIdx) => (
row.map((cell, cellIdx) => (
<div
key={`${rowIdx}-${cellIdx}`}
style={{ height: 100, width: 100 }}
>
{cell.x}, {cell.y}
</div>
))
))
}
</div>
);
}
}
There is the codesandbox demo for this code: https://codesandbox.io/s/2px4znwopr
Hope this answer could help.
Full solution to render a grid using bootstrap:
renderGrid() {
return _.map(GRID, row => {
return <div className="row"> {_.map(row, cell => {
return <div style={{ height: 100 + 'px', width: 100 + 'px' }}> {cell.x}, {cell.y} </div>
})
} </div>
})
}
for the past few hours I have been trying to search for a way to make a react component draggable and resizable. I have found a way to make it draggable with react drag and drop, but I can't find a simple way to make it resizeable :/
Does anyone have any experience on how to make a component draggable and resizable?
Any help or pointers are appreciated!
Using react-grid-layout to solve this problem. Specifically for a scheduling widget where users can scale time blocks and drag and drop them along a horizontal timeline.
react-grid-layout provides a grid with draggable and resizable widgets plus responsive layout, optional auto-packing, among other features.
var ReactGridLayout = require('react-grid-layout');
// React component render function:
render: function() {
return (
<ReactGridLayout className="layout" cols={12} rowHeight={30}>
<div key={1} _grid={{x: 0, y: 0, w: 1, h: 2}}>1</div>
<div key={2} _grid={{x: 1, y: 0, w: 1, h: 2}}>2</div>
<div key={3} _grid={{x: 2, y: 0, w: 1, h: 2}}>3</div>
</ReactGridLayout>
)
}
The child nodes are draggable and resizable. The layout defined in each child "_grid" prop can alternatively be defined directly on the parent "layout" prop:
// React component render function:
render: function() {
// layout is an array of objects, see the demo
var layout = getOrGenerateLayout();
return (
<ReactGridLayout className="layout" layout={layout} cols={12} rowHeight={30}>
<div key={1}>1</div>
<div key={2}>2</div>
<div key={3}>3</div>
</ReactGridLayout>
)
}
Callback functions can be passed to the components as props. Hooking into these should allow you to define any custom behavior:
// Calls when drag starts.
onDragStart: React.PropTypes.func,
// Calls on each drag movement.
onDrag: React.PropTypes.func,
// Calls when drag is complete.
onDragStop: React.PropTypes.func,
// Calls when resize starts.
onResizeStart: React.PropTypes.func,
// Calls when resize movement happens.
onResize: React.PropTypes.func,
// Calls when resize is complete.
onResizeStop: React.PropTypes.func
Code sample from docs:
https://github.com/STRML/react-grid-layout
Demo here:
https://strml.github.io/react-grid-layout/examples/0-showcase.html
I've been using react-rnd and am really happy with it: https://github.com/bokuweb/react-rnd
https://github.com/STRML/react-resizable
This answer is only for resizable component. You can find other answer which has both functionalities.
'use strict';
var React = require('react/addons');
typeof window !== "undefined" && (window.React = React); // for devtools
typeof window !== "undefined" && (window.Perf = React.addons.Perf); // for devtools
var _ = require('lodash');
var ResizableBox = require('../lib/ResizableBox.jsx');
var Resizable = require('../lib/Resizable.jsx');
require('style!css!../css/styles.css');
var TestLayout = module.exports = React.createClass({
displayName: 'TestLayout',
getInitialState() {
return {width: 200, height: 200};
},
onClick() {
this.setState({width: 200, height: 200})
},
onResize(event, {element, size}) {
this.setState({width: size.width, height: size.height});
},
render() {
return (
<div>
<button onClick={this.onClick} style={{'marginBottom': '10px'}}>Reset first element's width/height</button>
<Resizable className="box" height={this.state.height} width={this.state.width} onResize={this.onResize}>
<div className="box" style={{width: this.state.width + 'px', height: this.state.height + 'px'}}>
<span className="text">{"Raw use of <Resizable> element. 200x200, no constraints."}</span>
</div>
</Resizable>
<ResizableBox className="box" width={200} height={200}>
<span className="text">{"<ResizableBox>, same as above."}</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} draggableOpts={{grid: [25, 25]}}>
<span className="text">Resizable box that snaps to even intervals of 25px.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} minConstraints={[150, 150]} maxConstraints={[500, 300]}>
<span className="text">Resizable box, starting at 200x200. Min size is 150x150, max is 500x300.</span>
</ResizableBox>
<ResizableBox className="box box3" width={200} height={200} minConstraints={[150, 150]} maxConstraints={[500, 300]}>
<span className="text">Resizable box with a handle that only appears on hover.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={200} lockAspectRatio={true}>
<span className="text">Resizable square with a locked aspect ratio.</span>
</ResizableBox>
<ResizableBox className="box" width={200} height={120} lockAspectRatio={true}>
<span className="text">Resizable rectangle with a locked aspect ratio.</span>
</ResizableBox>
</div>
);
}
});