React Popper. How to move element relatively parent? - popper.js

I'm trying to move Popper that is inside parent component. How i can do it?
I tried to use offset property in modifiers object.
e.g.:
offset: {
offset: 20,
}
But offset only move Popper in one direction. If placement={'top' || 'bottom'} => it moves right and left, if placement={'right' || 'left'} => it moves top and bottom.
const Layer = ({
children,
align,
inner = true,
parent,
portal,
}) => (
<Manager>
<Popper
placement={align}
modifiers={{
inner: {
enabled: inner,
},
}}
referenceElement={parent}
>
{({ ref, style, placement }) => (
<div
ref={ref}
data-placement={placement}
style={style}
>
{console.log(style)}
{children}
</div>
)}
</Popper>
</Manager>
)

Just dealt with this: Place MaterialUI Tooltip "on top" of anchor element? (React)
PopperProps={{
popperOptions: {
modifiers: {
flip: { enabled: false },
offset: {
enabled: true,
offset: '20px 20px'
}
}
}
}}

I solved it like that. Added topOffset and leftOffset props to popper child and used spread syntax. Don't think it is cleanest solution.
const Layer = ({
children,
align,
inner = true,
parent,
portal,
topOffset,
leftOffset,
}) => (
<Manager>
<Popper
placement={align}
modifiers={{
inner: {
enabled: inner,
},
}}
referenceElement={parent}
>
{({ ref, style, placement }) => (
<div
ref={ref}
data-placement={placement}
style={{
...style,
top: style.top + topOffset,
left: style.left + leftOffset,
}}
>
{console.log(style)}
{children}
</div>
)}
</Popper>
</Manager>
)

Related

I tried to implement this HTML Text demo with React Konva, but in vain, because

What do I want to do:
I want to make React Konva Text editable. That is, if I double click on the Text... (I can move it to anywhere in the Konva Stage) ...I can show a text area to get the edits from the user, any changes he/she would like to do to the default text.
Conditions:
Enter key (keyboard) should produce a new line.
Double click on Text should show this text area on the same offset X and Y of this Text.
Double click on div should take back to Text. Now if there was any change done to the text, then it should get reflected on the Text component's label
I tried to implement this HTML Text demo with React Konva, but in vain, because of limitations
The html Konva demo that I want to reproduce in React Konva
Things I did:
Since HTML 5.0 is incompatible if used in conjugation with React Konva components, like Text, Image, RegularPolygon ... etc.
I used { Html } from 'react-konva-utils' so that I could move that content along with the Text component like in the demo in the link above.
Things that I observed:
The HTML can take padding and margin (we can use normal html within tag.), but not top, left.
I did try to put X property of Text in the margin top of the root div and value of Text's Y property in the margin left attribute of the same div, but had to revert as it was not close to the demo above.
Code:
import React, { useState, useRef, useEffect, Fragment } from "react";
import { Html } from 'react-konva-utils';
import { Text, Transformer } from "react-konva";
/*
Konva warning: tr.setNode(shape), tr.node(shape) and tr.attachTo(shape) methods are deprecated. Please use tr.nodes(nodesArray) instead.
*/
const KText = ({ stage, id, properties, isSelected, onSelect, onChange, setActiveText }) => {
const shapeRef = useRef();
const trRef = useRef();
const [toggleEdit, setToggleEdit] = useState(false)
useEffect(() => {
if (isSelected) {
trRef.current.nodes([shapeRef.current]);
trRef.current.getLayer().batchDraw();
}
}, [isSelected]);
// console.log("KText", `properties: ${JSON.stringify(properties)}`)
// console.log("KText", ` properties.text: ${properties.text}`)
const EditTextField = () => {
var textProps
const updateText = (data) => {
textProps = data
// console.log("EditTextField", `textProps: ${JSON.stringify(textProps)}`)
}
// var mAreaPos = areaPosition()
const areaPosition = () => {
let stage1 = stage.current.getStage()
return ({
x: stage1.container().offsetLeft + properties.x,
y: stage1.container().offsetTop + properties.y,
})
};
return (
<Html >
<div style={{
margin: "200px", padding: "20px", background: "lavender",
borderRadius: 20, borderStyle: "solid", borderColor: "green",
top: areaPosition().x, left: areaPosition().y
}}
onDoubleClick={() => setToggleEdit(!toggleEdit)}>
<label htmlFor="inputText">Please enter some text below:</label><p>
<textarea onChange={(evt) => (updateText({ text: evt.target.value, id: id }))}
id="inputText" name="inputText" rows="4" cols="50" placeholder="Please enter here" />
<br />
<button type="text" onClick={() => {
setToggleEdit(!toggleEdit)
setActiveText(textProps)
}}>Close</button>
</p>
</div>{/* */}
</Html >
)
}
const MainText = () => {
return (
<>
<Fragment>
<Text
stroke={"black"}
strokeWidth={1}
onTap={onSelect}
onClick={onSelect}
onDblClick={() => setToggleEdit(!toggleEdit)}
ref={shapeRef}
// {...shapeProps}
name="text"
x={properties.x}
y={properties.y}
text={properties.text}
fontFamily={properties.fontFamily}//"Serif"
fontSize={properties.fontSize}//50
fontWeight={properties.fontWeight} //"bold"
fillLinearGradientStartPoint={{ x: 0, y: 0 }}
fillLinearGradientEndPoint={{ x: 100, y: 100 }}
fillLinearGradientColorStops={[
0,
"rgba(0,0,0,0.7)",
1,
"rgba(255,155,255,0.5)"
]}
fillPriority={"linear-gradient"}
draggable
onDragEnd={e => {
/* onChange({
...shapeProps,
x: e.target.x(),
y: e.target.y(),
});*/
}}
onTransformEnd={e => {
// transformer is changing scale
/* const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
width: node.width() * scaleX,
height: node.height() * scaleY,
}); */
}}
/>
{isSelected && <Transformer ref={trRef} />}
</Fragment>
</>
)
}
const RenderThis = () => {
let inText = "" + properties.text
if (inText.trim().length === 0 || toggleEdit) {
return (
<EditTextField />
)
} else return (
<MainText />
)
}
// rendering function
return (
<RenderThis />
);
};
export default KText;

Mouse Out of Element and Mouse Into Another Element Does Not Reset State

Code: https://codesandbox.io/s/objective-darwin-w0i5pk?file=/src/App.js
Description:
This is just 4 gray squares that each get their own shade of gray. I want to change the background color of each square when the user hovers over each, but I want the hover color to be +10 in RGB of what it was originally.
Issue:
When I mouse/hover out of one of the gray squares and mouse/hover into another gray square, the first square does not switch back to its initial color state.
Help:
Can someone explain why it is doing this and how to fix it because I have no idea?
Note:
I am trying not to use CSS for the hover because I am specifying the backgroundColor with JS.
import React, { useState } from "react";
import "./styles.css";
const tabs = [
{ name: "1", img: [] },
{ name: "2", img: [] },
{ name: "3", img: [] },
{ name: "4", img: [] }
];
const initialState = {};
tabs.forEach((t, i) => {
initialState[i] = false;
});
export default function App() {
const [hover, setHover] = useState(initialState);
return (
<div className="App">
{tabs.map((t, i) => {
const v = 50 - (i + 1) * 10;
const val = hover[i] ? v + 10 : v;
return (
<div
key={t.name}
className="tab"
onMouseOver={() => {
setHover({
...hover,
[i]: true
});
}}
onMouseLeave={() => {
setHover({
...hover,
[i]: false
});
}}
onMouseOut={() => {
setHover({
...hover,
[i]: false
});
}}
style={{
backgroundColor: `rgb(${val}, ${val}, ${val})`,
height: "100px",
width: "100px"
}}
>
<p>{t.name}</p>
</div>
);
})}
</div>
);
}
.App {
font-family: sans-serif;
text-align: center;
margin: 0;
padding: 0;
}
* {
margin: 0;
padding: 0;
}
This picture only shows the initial state:
setState calls are not what a human would consider "immediate". Instead, the calls to the state setter as queued inside React internal mechanisms. Consider this:
const [state, setState] = useState(0)
// somewhere
setState(state + 1)
setState(state + 1)
In this case, you do not end up with 2 but 1, because while you call setState twice to increment by one, you really are calling it as:
setState(1)
setState(1)
This is the exact issue in your code with the callbacks, you have
// enter
setState({ ...state, [i]: true })
// leave
setState({ ...state, [i]: false })
so when both get called, you apply the "leave" with the wrong previous state.
This is why setState has another pattern, setState(prevState => nextState)
setState(prevState => prevState + 1)
setState(prevState => prevState + 1)
Like this, you do end up with the value 2 because the second call is then using the "correct" previous state.
In your case, you need:
// enter
setState(prevState => ({ ...prevState, [i]: true }))
// leave
setState(prevState => ({ ...prevState, [i]: false }))
This is happening because you also keep the previous values in your state. You should update in this way
onMouseOver={() => {
setHover({
[i]: true
});
}}
onMouseLeave={() => {
setHover({
[i]: false
});
}}
onMouseOut={() => {
setHover({
[i]: false
});
}}

Struggling to get framer-motion's exit animation working. [React.JS]

RenderPokemonInfo.js:
const RenderPokemonInfo = (props) => {
const pokemon = props.pokemonToUse;
const pokemonCurrentTypes = [];
let gradientString = "";
const animateFromTop = {
before: {
y: '-100vh',
opacity: 0,
},
onScreen: {
y: '0',
opacity: 1,
},
after: {
y: '100vh',
opacity: 0,
}
}
pokemon.types.map((typeData) => {
pokemonCurrentTypes.push(typeData.type.name);
})
if(pokemonCurrentTypes.length === 1) {
gradientString = getGradient({typeString: pokemonCurrentTypes[0]});
} else {
gradientString = getGradient({typeString: pokemonCurrentTypes[0]}) + ', ' + getGradient({typeString: pokemonCurrentTypes[1]});
}
return (
<>
<motion.div variants={animateFromTop} initial='before' animate='onScreen' exit='after' key={pokemon.name}>
<div style={{
position: 'absolute',
width: '420px',
height: '640px',
left: '75%',
top: '30px',
transform: 'translateX(-50%)',
backgroundImage: 'linear-gradient(225deg, ' + gradientString + ')',
boxShadow: '0 20px 20px 0 rgba(0,0,0,0.2)',
borderRadius: '30px'}} key={'poke-pic-' + pokemon.name}>
<fieldset className="poke-height-fieldset">
<legend style={{position: 'absolute', left: '10px'}}>{pokemon.height * 10} cm</legend>
<div className='big-poke-img-container'>
<img src={'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/dream-world/' + pokemon.id + '.svg'} className='big-poke-img'></img>
</div>
</fieldset>
<div className='poke-type-container'>
{pokemon.types.map((type) => <PokemonTypeButton typeString={type.type.name} isButton={false} key={type.type.name}/>)}
</div>
</div>
</motion.div>
</>
)
}
export default RenderPokemonInfo;
Part of my App.js that calls RenderPokemonInfo:
<div className='content-container'>
{(pokemon.length === 0) ? (
<p>Pick a pokemon!</p>
) : (
<AnimatePresence exitBeforeEnter={true}>
<RenderPokemonInfo pokemonToUse={pokemon} key={pokemon.name}/>
</AnimatePresence>
)}
</div>
When coming on screen the animations are fine, but when it leaves it just vanishes, no animation. I've seen on framer-motion's docs that you should put set the initial prop to false in AnimatePresence but when I do that it doesn't have an enter animation or an exit animation... am I doing something wrong here?
Is this entire component getting removed? If so, then you'll need to move the AnimatePresence tag up into the parent component. AnimatePresence watches for children getting removed from the DOM and runs the exit animation before removing them. If you have the AnimatePresence tag inside the component that gets removed, then it has no chance to run the exit animation, because the AnimatePresence tag is gone immediately along with the rest of the component.
You'd need something more like this (in your parent component):
<AnimatePresence>
{ pokemons.map(pokemon => <PokemonCard key={pokemon.name} />) }
</AnimatePresence>
And your PokemonCard would return the motion.div you show above (without the AnimatePresence tag)
This all came down to me not fully understanding how React uses keys. I used React.Child.toArray() to wrap everything I rendered from a map, this makes React give everything a unique key.

Using callback ref to modify scrollLeft property in React

I've got a container div with a lot of children, and an overflow-x set to scroll.
This allows me to scroll the content to the left and right using my trackpad (or drag on mobile).
But I really would like the default scroll position to be the maximum horizontal value. (i.e. scrolled all the way to the right). The relevant code for this is Element.scrollLeft = <some large value>.
I'm trying to figure out how to achieve this with React and callback refs. I have the following code:
const containerRef = useCallback(
(node) => {
if (node !== null) {
node.scrollLeft = values.length * 20;
}
},
[values.length]
);
return (
<div className={"scroll-wrapper"} ref={containerRef}>
values.map(()=>{
// Some stuff in here that's irrelevant.
});
</div>
)
(This code was based on https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node)
When I put a breakpoint in the useCallback function, I see it is properly invoked with a non-0 value, but right after the call when I inspect node.scrollLeft it is still 0.
When I inspect the DOM in Chrome, I can actually force the scrollLeft of the element to be high number, which does work.
So why doesn't the React useCallback ref work?
You should use useRef hook to work with refs.
To create ref use this:
const containerRef = useRef();
And add it to ref of div as you did before:
<div className={"scroll-wrapper"} ref={containerRef}>
...
</div>
And you should use useEffect and call it one time to set scrollLeft to the component in ref:
useEffect(() => {
ref.current.scrollLeft = 4000;
}, []);
Here is the full example: https://codesandbox.io/s/lingering-glitter-s0gok?file=/src/App.js
This is how I fixed
then just make some css issue for that
const [scrollX, setScrollX] = useState({
side: ""
});
const scrollLeftRef = useRef();
const handleScroll = (data) => {
setScrollX(prev => ({ ...prev, side: data.side }));
}
useEffect(() => {
if (scrollX.side === "right") {
scrollLeftRef.current.scrollLeft += 200;
} else {
scrollLeftRef.current.scrollLeft -= 200;
}
}, [scrollX]);
<div class="slide-sample">
<div id="slideRight" onClick={() => handleScroll({ side: "left" })} class="preSlide">
<i className="fas fa-caret-square-left" />
</div>
<div ref={scrollLeftRef} class="slideouter">
<div class="slideinner srcl">
<ul>
{
categories.map((category, i) => {
return (
<Button
key={category.id}
onClick={() => { dispatch(filterfood(category.name)); handleItemClick(i) }}
variant={activeclass === i ? "contained" : "outlined"}
size="medium"
>
{category.name}
</Button>
)
})}
</ul>
</div>
</div>
<div id="slideRight" onClick={() => handleScroll({ side: "right" })} className="nextSlide">
<i className="fas fa-caret-square-right" />
</div>
</div>

How to wrap whole row to React Router Link in antd

I have a table and need wrap every table row to Link. How can I do that? I need that when I click on the row I go to another route. I’ve already figured out how to make a link every cell, but with the whole row I don’t know how to make
const AccountList = props => {
const columns = [
{
title: "Acc",
dataIndex: "fullName",
key: "fullName",
width: 170,
},
{
title: "Number",
dataIndex: "ID",
key: "ID",
width: 100
},
];
return (
<div style={{ margin: "15px" }}>
<Table
columns={columns}
dataSource={accInfoProps}
pagination={false}
size={"small"}
title={() => <h2 style={{ float: "left" }}>Список аккаунтов</h2>}
onRow={(record) => {
return {
onClick: () => {
console.log(record.id)
},
};
}}
/>
,
<Pagination
onChange={onChange}
style={{ float: "right", marginTop: "15px" }}
defaultCurrent={1}
total={500}
/>
</div>
);
};
export default AccountList;
Thanks in advance
Use onRow event handler on Table component to route to desired link onClick. If using react-router-dom, you can use the Redirect component to navigate to the new link.
<Table
onRow={(record, rowIndex) => {
return {
onClick: event => <Redirect push to={record.link}/>
};
}}