Popover does not show up in React - javascript

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

Related

How to pass properties to icons with Storybook and React

How do I pass props (like colors and size) to an svg with storybook and react?.
My code is looking like this:
const ICONS = { icon: <Icon />, icon2: <Icon2 /> };
// This icons are simple svg files of an icon.
const noIcon = undefined;
export default {
title: "Components/Button",
component: Button,
argTypes: {
handleClick: { action: "handleClick" },
icon: {
control: {
type: "select",
options: [undefined, ...Object.keys(ICONS)],
},
},
},
};
const Template = ({ iconName, ...rest }) => {
const icon = ICONS[iconName];
return <Button icon={icon} {...rest} />;
};
Button.js is this code:
<button
onClick={handleClick}
style={style}
className={`button ${size === "m" ? "button-m" : "button-s"}`}
disabled={disabled}
>
<div>{icon}</div>
<span
style={styleButton}
className={size === "m" ? "button-content-m" : "button-content-s"}
>
{content}
</span>
</button>
Currently, I can pass the object with icons, but I don't know how to change colors and sizes of the svg.
I'm expecting to find a way to change properties of the icons

How to drag and drop html element in inner card or box?

I am trying to drag and drop any html element in nested level of container.
First level of drag and drop of elements are working but nested level is not working.
Nested level means "Dropping button inside card element which also an element".
I am taking card as control and container.
I am developing in reactjs, react-dnd.
Code :
app.js
const App = props =>{
const [controlsList, setControlList]= useState([
{ email_txt }, { button }, { card } , {textarea } ...
])
return (
<>
<div className="draggable">
{
controlsList.map(({_id, type, title}, index)=>{
<ControlsAndContainers _id={_id} type={type} title={title} />
})
}
</div>
<div className="droppable">
<DropBox/>
</div>
</>
)
}
ControlsAndContainer.js
import { useDrag } from 'react-dnd'
const ControlsAndContainer = ({_id, type, title })=>{
const [ {opacity}, drag ] = useDrag(()=>({
type,
item: { _id, type, title },
end: (item, monitor)=>{
//
},
collect: (monitor) =>({
opacity: monitor.isDragging()? 0.4 : 1
})
}), [title, type]);
const box_style = {
cursor: 'move', border: '1px dashed gray'
}
return (
<div ref={drag} style={{ ...box_style, opacity}}>
{title}
</div>
)
}
dropbox.js
import { useDrop } from 'react-dnd'
const DropBox = () =>{
let temp =[];
const [dataState, setDataState] = useState([]);
const [{isOver }, drop] = useDrop(()=> ({
accept: ['button', 'email', 'card', 'textarea'],
drop(item, monitor){
temp.push(item);
setDataState(temp);
},
collect:(monitor)=>{
isOver: monitor.isOver(), ​
​}
​}), []);
​const ButtonControl = () => {
​return ( <div> <button>Button</button> </div>)
​}
​.... email, textarea
​// card code is from react-bootsrap
​const CardControl = () => {
​<Card style={{ width: '18rem' }}>
​<Card.Header>Header</Card.Header>
​<Card.Body>
​Drop other element here
​</Card.Body>
​<Card.Footer>Footer</Card.Footer>
​</Card>
​}
​return (
​<div ref={drop}>
​dataState.map((data,index)=>{
​let container;
​switch(data.type){
​case 'button': container=<ButtonControl />
​break;
​case 'button': container=<CardControl />
​break;
​default: break;
​}
​return (
​<> <div key={data._id}> { container } </div></>
​)
​})
​</div>
​)
}
I am trying to drag and drop button inside "Card" control which is not working but card drag and drop is working and outside the card is also working.
What I am missing ?
Please somebody help
I solved this problem. There are two ways to solve it.
create 2nd drop ref, I mean
const [, nestedDrop] = useDrop(()=>{ accept, drop, ... }));
use nestedDrop inside inner container like this ,
​<Card.Body>
​<div ref={nestedDrop}></div>
</Card.Body>
way is inspired from this official example :
nested drop area
you can customize nested dropbox according to your need.

React ComboBox (npm component) returning [object Object]

I've currently set a task for myself of creating a language selection box, using https://react-combobox.netlify.app/demo as a base and working off of that as I learn React. The code so far I have is returning [object Object] as a result when I select an entry in the combobox.
Code:
import React, { useState } from "react";
import ComboBox from "react-responsive-combo-box";
export default function ComboBoxLang(props) {
const [selectedOption, setSelectedOption] = useState("");
const [highlightedOption, setHighlightedOption] = useState("");
const options = props.dataSet.map ( (inputValue) => {
// const columnWidth = props.columnsWidth.language_name + "px";
const columnWidth = props.columnsWidth;
let keys = Object.keys(columnWidth);
console.log ('keys are' + keys)
const renderedColumns = keys.map( columnName =>{
return(
<div style={{display:"inline-block", width:columnWidth[columnName]}}>{inputValue[columnName]} </div>
);
})
return (
<div key={inputValue.id}>
{renderedColumns}
</div>
)
})
return (
<div>
<h1>React Combo Box</h1>
<p>
The selected option -{" "}
<span style={{ fontWeight: "bold" }}>
{" "}
{selectedOption.length > 0 ? selectedOption : "None"}
</span>
</p>
<p>
The highlighted option -{" "}
<span style={{ fontWeight: "bold" }}>
{" "}
{highlightedOption.length > 0 ? highlightedOption : "None"}
</span>
</p>
<ComboBox
options={options}
placeholder="choose country"
defaultIndex={4}
optionsListMaxHeight={300}
style={{
width: "500px",
margin: "0 auto",
marginTop: "50px"
}}
focusColor="#20C374"
renderOptions={(option) => (
<div className="comboBoxOption">{option}</div>
)}
onSelect={(option) => setSelectedOption(option)}
onChange={(event) => console.log(event.target.value)}
enableAutocomplete
onOptionsChange={(option) => setHighlightedOption(option)}
/>
</div>
);
}
index.js code:
import React from 'react';
import ReactDOM from 'react-dom';
import ComboBoxLang from './combobox_test.js';
const languages = [
{
"id": 1,
"language_name": "Afrikaans",
"language_native_name": "Afrikaanse taal",
"language_full_name": "Afrikaanse taal (Afrikaans)",
"language_code": "af",
"translation_engine_id": 2,
"translation_engine_name": "Google"
},
{
"id": 2,
"language_name": "Albanian",
"language_native_name": "Gjuha shqipe",
"language_full_name": "Gjuha shqipe (Albanian)",
"language_code": "sq",
"translation_engine_id": 2,
"translation_engine_name": "Google"
}
];
class App extends React.Component {
render () {
return (
<div>
<ComboBoxLang dataSet={languages} columnsWidth={{language_name:200,language_native_name:300}}/>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('drop-down-menu')
);
The goal is to effectively have the language, and the native language (or language code at a later date) to display within the combo-box once selected. I've left the 2 "Displayed" and "Selected" boxes so that, once it works, I can understand a bit how it works, and allow other things to interact or reference those items. For now, I am stuck trying to figure out why it spit's out [object Object] and where it is actually in need of correcting.

How can I add descriptions to my slideshow?

I am working on a web app in the ReactJS framework and I'm using react-slideshow-image component for a simple slideshow. However due to how the component works, I can only render the images. I would like to add a short description for each image and have it appear with it. Is there a way to modify this code to render a description under the image? (I was thinking about useEffect hook but I'm not sure.)
const Slideshow = () => {
return (
<SlideContainer>
//styled component
<Zoom scale={0.4}>
//Zoom component comes from react-slideshow-image
{
slideshowImages.map((each, index) => <img key={index} style={{padding: "0", margin: "0", width: "20vw"}} src={each} />)
}
</Zoom>
</SlideContainer>
)
};
Try this behavior, Here you can show the description on the image.
const images = [
{
id: "img1",
url: "../images/image-1.png",
description: "image 1 description here"
},
{
id: "img2",
url: "../images/image-2.png",
description: "image 2 description here"
}
];
const Slideshow = () => {
return (
<div className="slide-container">
<Zoom {...zoomOutProperties}>
{images.map((each, index) => (
<div
style={{
background: `url(${each.url}) no-repeat`,
width: "300px",
height: "240px"
}}
>
<b>{each.description}</b>
</div>
))}
</Zoom>
</div>
);
};
Hope you are looking for the same use case.
Working demo:- https://codesandbox.io/s/modest-proskuriakova-28qsk?file=/src/App.js
This wasn't quite what I wanted but it pointed me in the right direction. Here's my solution inspired by yours and official React documentation:
const slideshowImages = [
{
id: 1,
image: "/imageUrl",
desc: //description
},
//as many elements as you want
]
const Slideshow = () => {
//SlideContainer and DescriptionWrapper are styled components
return (
<SlideContainer>
<Zoom {...zoomOutProperties}>
{
slideshowImages.map(
(slide) =>
<div key={slide.id}>
<img style={{padding: "0", margin: "0", width: "20vw"}} src={slide.image} />
<DescriptionWrapper>{slide.desc}</DescriptionWrapper>
</div>
)
}
</Zoom>
</SlideContainer>
)
};
export default Slideshow;
Thanks a lot, I wouldn't have thought of something like this.

mdbreact is showing an unexpected behaviour

I am using mdbreact package to make table for my data. It's having an action button in the last column which opens a modal for editing the data.
Scenario is
I loaded the table with initial data
And then applied some sorting on it
And now I click on the edit button to open the modal for editing the data
Now the sorting gets automatically removed and it's looking really weird that modal is opening and data is changing in the background.
What do I need ?
I don't want data to be changed on the backend. Also, I don't know how to store that sorted data in the state even as I am using mdbreact for the first time.
Here you can check the exact issue I am facing.
File where I am formatting data and adding event and action to each row:
import React from 'react'
import PayRatesTable from './PayRatesTable'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faPencilAlt, faAngleRight, faAngleLeft } from '#fortawesome/free-solid-svg-icons'
import { Button, Row, Col } from 'reactstrap'
const columns =
[
{
label: 'Certificate',
field: 'certificate',
sort: 'asc'
},
{
label: 'Speciality',
field: 'speciality',
sort: 'asc'
},
{
label: 'Pay Rate ($)',
field: 'pay_rate',
sort: 'disabled'
},
{
label: 'Weekend Pay Rate ($)',
field: 'weekend_pay_rate',
sort: 'disabled'
},
{
label: 'Action',
field: 'action',
sort: 'disabled'
}
]
const formatCertSpec = (data, certificates, handleModelClose) => {
var cert = []
data && data.map((item) => (
certificates && certificates.map((certs) => {
if (certs.id == item.certificateId) {
certs.specialities && certs.specialities.map((certSpec) => {
if (item.speciality == certSpec.id) {
cert.push({
certificate: certs.abbreviation,
speciality: certSpec.name,
pay_rate: item.payRateCents ? `$${(item.payRateCents / 100).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}` : '',
weekend_pay_rate: item.weekendPayRateCents ? `$${(item.weekendPayRateCents / 100).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}` : '',
action: <Button color="link" onClick={(event) => {
event.preventDefault()
handleModelClose({
certificate: certs.abbreviation,
speciality: certSpec.name,
id: item.id,
pay_rate: item.payRateCents / 100,
weekend_pay_rate: item.weekendPayRateCents / 100,
})}}>
<FontAwesomeIcon key="edit" className="ml-2" icon={faPencilAlt} />
</Button>
})
}
})
}
})
))
return cert
}
function AddPayRatesComp({
data,
certificates,
handleModelClose,
handleNextPrevTabs
}) {
const certAndSpecPayData = formatCertSpec(data, certificates, handleModelClose)
console.log(certAndSpecPayData)
return (
<div className="container-fluid">
<PayRatesTable
columns={columns}
certificates={certificates}
certs={certAndSpecPayData}
/>
<Row className="mb-2 text-center">
<Col className="col-md-3">
</Col>
<Button
type="button"
onClick={() => handleNextPrevTabs('prev')}
outline
color="secondary"
className="btn-rounded font-weight-bold py-1 mr-2 mt-2 col-sm-12 col-md-3"
><FontAwesomeIcon icon={faAngleLeft} /> Previous</Button>
<Button
type="button"
onClick={() => handleNextPrevTabs('next')}
outline
color="secondary"
disabled
className="btn-rounded font-weight-bold py-1 mr-2 mt-2 col-sm-12 col-md-3"
>Next <FontAwesomeIcon icon={faAngleRight} /></Button>
</Row>
</div>
);
}
export default AddPayRatesComp;
PayRatesTable.js
import React from 'react'
import { MDBDataTable } from 'mdbreact'
const PayRatesTable = ({ columns, certs }) => {
const data = {
columns: columns,
rows: certs
}
return (
<>
<MDBDataTable
striped
bordered
medium="true"
responsive
data={data}
order={['certificate', 'asc' ]}
/>
<style jsx global>{`
#import "../styles/bootstrap-custom/jsx-import";
.table thead:last-child{
display:none;
}
`}</style>
</>
);
}
export default PayRatesTable;
This is all that I can provide due to security issues.

Categories