Increase number when inView with useEffect and useState - reactjs

I need to show a number, that starts at 0 and increases to X. This "counting" needs to happen when user's reach the number in view, like this example.
For this, I'm trying to use useEffect, useState and useInView, from react-intersection-observer.
But, in my code, my number is a bit crazy, changing from 1 to 2 only.
I've created a component that I can use many times, this way:
function NumericIndicator(props) {
const [counting, setCounting] = useState(false);
const [number, setNumber] = useState(0);
const [ref, inView] = useInView();
useEffect(() => {
setInterval(() => {
if (number < props.value) {
setNumber(number + 1);
} else {
setNumber(props.value);
}
}, 500);
}, [counting]);
useEffect(() => {
if (inView && !counting) {
setCounting(true);
}
}, [inView]);
return (
<div ref={ref}>
<h1>{number}</h1>
<p>
<small>This needs to increase from 0 to {props.value}</small>
</p>
</div>
);
}
Here's a full StackBlitz with my code
Thank you!

Try
import React from 'react';
import { useEffect, useState } from 'react';
import { useInView } from 'react-intersection-observer';
import './style.css';
export default function App() {
return (
<div>
<div style={{ minHeight: '100vh' }}>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin
convallis sagittis finibus. Maecenas mauris odio, feugiat vel orci
non, consectetur placerat turpis. Proin tincidunt ut tellus posuere
euismod. Donec id venenatis dui. Quisque ultrices dignissim ipsum ut
tempus. Praesent mollis elit ac magna laoreet tempus at a dui.
Suspendisse sed venenatis arcu, quis commodo sapien. Ut at malesuada
dui. Curabitur feugiat elementum ligula, a pellentesque tortor. Fusce
id nisl ornare, congue dui vitae, ornare ex. Class aptent taciti
sociosqu ad litora torquent per conubia nostra, per inceptos
himenaeos.
</p>
<p>
Vivamus blandit leo ac magna efficitur vulputate. Praesent vitae magna
elit. Nullam non metus nec mauris maximus tincidunt a ac velit. Aenean
elit metus, aliquet at gravida ornare, commodo gravida nisl. Cras
vestibulum lacinia erat sit amet cursus. Donec pretium enim porttitor
magna tincidunt, a venenatis sem bibendum. Aenean ornare enim et odio
pharetra iaculis. Class aptent taciti sociosqu ad litora torquent.
</p>
</div>
<div style={{ height: '50vh', backgroundColor: '#27ae60' }}>
<NumericIndicator value={10} />
</div>
<div style={{ height: '50vh', backgroundColor: '#e67e22' }}>
<NumericIndicator value={70} />
</div>
<div style={{ height: '50vh', backgroundColor: '#9b59b6' }}>
<NumericIndicator value={90} />
</div>
<div style={{ height: '50vh', backgroundColor: '#2c3e50' }}>
<NumericIndicator value={110} />
</div>
</div>
);
}
function NumericIndicator(props) {
const [number, setNumber] = useState(0);
const [ref, inView] = useInView();
useEffect(() => {
if (!inView) return () => {};
const interval = setInterval(() => {
setNumber((prev) => Math.min(prev + 1, props.value));
}, 500);
return () => clearInterval(interval);
}, [inView, props.value, number]);
return (
<div ref={ref}>
<h1>{number}</h1>
<p>
<small>This needs to increase from 0 to {props.value}</small>
</p>
</div>
);
}

Related

How to apply CSS animations to React components that re-render

I am attempting to reproduce the sliding image effect seen on this website https://chiwawa.es/en/.
I can see how it works on this website (see changeImages function in 63c2acc.js; however, getting this to work in React is proving difficult for me.
To illustrate what I have managed to do, I've published https://dskdirhhwk.vercel.app/.
Solved - (this link now correctly shows animations thanks to answer below)
The difficulty I'm running into is animating the images. The Sections are getting re-rendered by React upon state update, affecting how I apply the classes to the markup. I used Reacts Profiler to verify these are updates, not remounts.
I initially had the Intersection Observer outside the Section component, but this didn't appear to work. I also had a simple setState mechanism and have since built it into a reducer, so that I could add the active class after the other classes.
I am pulling my hair out here. I want a few hints and a push in the right direction!
This is on Next.js. Here is my code.
import React, {
Fragment,
useState,
forwardRef,
useEffect,
useLayoutEffect,
useCallback,
useContext,
useRef,
useMemo,
} from 'react';
import cx from 'classnames';
// yarn add classnames
const useIntersect = ({root = null, rootMargin, threshold = 0}) => {
const [entry, updateEntry] = useState({});
const [node, setNode] = useState(null);
const observer = useRef(null);
useEffect(() => {
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(([entry]) => updateEntry(entry), {
root,
rootMargin,
threshold,
});
const {current: currentObserver} = observer;
if (node) currentObserver.observe(node);
return () => currentObserver.disconnect();
}, [node]);
return [setNode, entry];
};
const Home = () => {
const initialState = {active: 'teal', current: 'teal'};
const [state, dispatch] = React.useReducer(reducer, initialState);
function reducer(state, {section, type}) {
switch (type) {
case 'reset':
return initialState;
case 'changeSection':
return {
...state,
previous: state?.current,
current: section,
};
case 'makeActive':
return {
...state,
active: section,
};
default:
return state;
}
}
const activeSection = (section) => {
dispatch({section: section, type: 'changeSection'});
setTimeout(() => {
dispatch({type: 'makeActive', section: section});
}, 1000);
};
const Section = ({sectionID, className, children}) => {
const [ref, entry] = useIntersect({
threshold: '0.7',
});
useEffect(() => {
if (state?.current == sectionID) return;
if (entry.isIntersecting) {
activeSection(sectionID);
}
}, [entry]);
const childrenWithProps = React.Children.map(children, (child, index) => {
if (React.isValidElement(child)) {
if (index == 0)
return React.cloneElement(child, {
className: cx(
'fixed top-0 w-1/2 h-screen transition-all duration-[1000ms] place-items-center place-content-center transform-gpu',
{
'translate-y-[-100vh]': sectionID != state?.active,
'z-40 translate-y-0': sectionID == state?.current && sectionID == state?.active,
}
),
data: 'observable',
id: sectionID,
});
}
return child;
});
return (
<section className={className} ref={ref}>
{childrenWithProps}
</section>
);
};
return (
<>
<div className="fixed bottom-0 z-50 w-1/2 opacity-50">
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<Section sectionID="teal" className="bg-teal-200">
<div>
<img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=1" />
</div>
<div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
<p className="mb-6">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
</p>
</div>
</Section>
<Section sectionID="blue" className="bg-blue-200">
<div>
<img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=2" />
</div>
<div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
<p className="mb-6">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
</p>
</div>
</Section>
<Section sectionID="orange" className="bg-orange-200">
<div>
<img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=3" />
</div>
<div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
<p className="mb-6">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
</p>
</div>
</Section>
<Section sectionID="pink" className="bg-pink-200">
<div>
<img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=4" />
</div>
<div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
<p className="mb-6">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
</p>
</div>
</Section>
</>
);
};
export default Home;
You should (almost) never ever ever declare a component inside of another functional component. Doing so essentially creates an entirely new React component class every render, which means it is impossible for React to reconcile which component is which between renders. Try moving your <Section> functional component definition out of the scope of <Home>.

How to rewrite class component to React Functional?

I am new to React and I am learning the basics, but right now I need to rewrite a Class component in order to use React Hooks. So I guess I need to rewrite it to a functional component.
I already tried changing some of the things but in the end everything breaks and I will get a 'props not defined' error.
This is the code:
class Main extends React.Component {
render() {
let close = (
<div
className="close"
onClick={() => {
this.props.onCloseArticle()
}}
></div>
)
return (
<div
ref={this.props.setWrapperRef}
id="main"
style={this.props.timeout ? { display: 'flex' } : { display: 'none' }}
>
<article
id="vision"
className={`${this.props.article === 'vision' ? 'active' : ''} ${
this.props.articleTimeout ? 'timeout' : ''
}`}
style={{ display: 'none' }}
>
<h2 className="major">Vision</h2>
<span className="image main">
<img src={pic01} alt="" />
</span>
<p>
Adipiscing magna sed dolor elit. Praesent eleifend dignissim arcu,
at eleifend sapien imperdiet ac. Aliquam erat volutpat. Praesent
urna nisi, fringila lorem et vehicula lacinia quam. Integer
sollicitudin mauris nec lorem luctus ultrices.
</p>
<p>
Nullam et orci eu lorem consequat tincidunt vivamus et sagittis
libero. Mauris aliquet magna magna sed nunc rhoncus pharetra.
Pellentesque condimentum sem. In efficitur ligula tate urna.
Maecenas laoreet massa vel lacinia pellentesque lorem ipsum dolor.
Nullam et orci eu lorem consequat tincidunt. Vivamus et sagittis
libero. Mauris aliquet magna magna sed nunc rhoncus amet feugiat
tempus.
</p>
{close}
</article>
</div>
)
}
}
Main.propTypes = {
route: PropTypes.object,
article: PropTypes.string,
articleTimeout: PropTypes.bool,
onCloseArticle: PropTypes.func,
timeout: PropTypes.bool,
setWrapperRef: PropTypes.func.isRequired,
}
export default Main
What I did is changing class main to const Main = () => {, remove the render() but after that I am confused..
This should do the work
Replace class by const
Remove the render lifecycle method used in class components
Add the props in the parameter of the function
Remove all the this
const Main = (props) => {
let close = (
<div
className="close"
onClick={() => {
props.onCloseArticle()
}}
></div>
)
return (
<div
ref={props.setWrapperRef}
id="main"
style={props.timeout ? { display: 'flex' } : { display: 'none' }}
>
<article
id="vision"
className={`${props.article === 'vision' ? 'active' : ''} ${
props.articleTimeout ? 'timeout' : ''
}`}
style={{ display: 'none' }}
>
<h2 className="major">Vision</h2>
<span className="image main">
<img src={pic01} alt="" />
</span>
<p>
Adipiscing magna sed dolor elit. Praesent eleifend dignissim arcu,
at eleifend sapien imperdiet ac. Aliquam erat volutpat. Praesent
urna nisi, fringila lorem et vehicula lacinia quam. Integer
sollicitudin mauris nec lorem luctus ultrices.
</p>
<p>
Nullam et orci eu lorem consequat tincidunt vivamus et sagittis
libero. Mauris aliquet magna magna sed nunc rhoncus pharetra.
Pellentesque condimentum sem. In efficitur ligula tate urna.
Maecenas laoreet massa vel lacinia pellentesque lorem ipsum dolor.
Nullam et orci eu lorem consequat tincidunt. Vivamus et sagittis
libero. Mauris aliquet magna magna sed nunc rhoncus amet feugiat
tempus.
</p>
{close}
</article>
</div>
)
}

how to create Top drawer with Materail ui react which start from the bottom of navbar?

actually, I am trying to create a responsive app bar using the material UI/app drawer. when I clicked the but it starts from the top of the page and makes the background dark but I want to make it start from the bottom of the app bar without making the background dark. please help me and give example using codesandbox https://codesandbox.io/s/64ulq
I trying to do this
but it shows like that
you can make a mix of multiple exemple of material ui drawer (Persistent and clipped one) and it give you something like this :
import React from 'react';
import { createStyles, Theme, makeStyles } from '#material-ui/core/styles';
import Drawer from '#material-ui/core/Drawer';
import AppBar from '#material-ui/core/AppBar';
import CssBaseline from '#material-ui/core/CssBaseline';
import Toolbar from '#material-ui/core/Toolbar';
import List from '#material-ui/core/List';
import Typography from '#material-ui/core/Typography';
import Divider from '#material-ui/core/Divider';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import InboxIcon from '#material-ui/icons/MoveToInbox';
import MailIcon from '#material-ui/icons/Mail';
import IconButton from '#material-ui/core/IconButton';
import MenuIcon from '#material-ui/icons/Menu';
const drawerWidth = "100vw";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
},
drawer: {
width:"100vw",
},
drawerPaper: {
width: drawerWidth,
},
drawerContainer: {
overflow: 'auto',
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
},
}),
);
export default function ClippedDrawer() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleDrawerOpen = () => {
setOpen(!open);
};
return (
<div className={classes.root}>
<CssBaseline />
<AppBar position="fixed" className={classes.appBar}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
Clipped drawer
</Typography>
</Toolbar>
</AppBar>
<Drawer
anchor="top"
className={classes.drawer}
variant="persistent"
open={open}
classes={{
paper: classes.drawerPaper,
}}
>
<Toolbar />
<div className={classes.drawerContainer}>
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</div>
</Drawer>
<main className={classes.content}>
<Toolbar />
<Typography paragraph>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
ut labore et dolore magna aliqua. Rhoncus dolor purus non enim praesent elementum
facilisis leo vel. Risus at ultrices mi tempus imperdiet. Semper risus in hendrerit
gravida rutrum quisque non tellus. Convallis convallis tellus id interdum velit laoreet id
donec ultrices. Odio morbi quis commodo odio aenean sed adipiscing. Amet nisl suscipit
adipiscing bibendum est ultricies integer quis. Cursus euismod quis viverra nibh cras.
Metus vulputate eu scelerisque felis imperdiet proin fermentum leo. Mauris commodo quis
imperdiet massa tincidunt. Cras tincidunt lobortis feugiat vivamus at augue. At augue eget
arcu dictum varius duis at consectetur lorem. Velit sed ullamcorper morbi tincidunt. Lorem
donec massa sapien faucibus et molestie ac.
</Typography>
<Typography paragraph>
Consequat mauris nunc congue nisi vitae suscipit. Fringilla est ullamcorper eget nulla
facilisi etiam dignissim diam. Pulvinar elementum integer enim neque volutpat ac
tincidunt. Ornare suspendisse sed nisi lacus sed viverra tellus. Purus sit amet volutpat
consequat mauris. Elementum eu facilisis sed odio morbi. Euismod lacinia at quis risus sed
vulputate odio. Morbi tincidunt ornare massa eget egestas purus viverra accumsan in. In
hendrerit gravida rutrum quisque non tellus orci ac. Pellentesque nec nam aliquam sem et
tortor. Habitant morbi tristique senectus et. Adipiscing elit duis tristique sollicitudin
nibh sit. Ornare aenean euismod elementum nisi quis eleifend. Commodo viverra maecenas
accumsan lacus vel facilisis. Nulla posuere sollicitudin aliquam ultrices sagittis orci a.
</Typography>
</main>
</div>
);
}
code sandbox

Create a component that abstracts logic (from existing code)

I create this code:
import React from 'react'
import { range } from 'lodash'
const DIV_NUMBER = 5
export default class App extends React.Component {
constructor(props) {
super(props)
this.divs = []
}
handleScroll = divIdx => () => {
const divRef = this.divs[divIdx]
const left = divRef.scrollLeft
const top = divRef.scrollTop
this.divs.forEach(div => (div.scrollLeft = left))
this.divs.forEach(div => (div.scrollTop = top))
}
render() {
return (
<div style={{ border: '1px solid tomato' }}>
{range(DIV_NUMBER).map(i => {
return (
<div
key={i}
ref={divElem => (this.divs[i] = divElem)}
onScroll={this.handleScroll(i)}
style={{
width: 300,
height: 100,
margin: '2px',
overflow: 'auto',
border: '1px solid black',
}}
>
<div
style={{
width: 500,
height: 400,
}}
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Scelerisque eu ultrices vitae auctor eu
augue ut lectus. In fermentum et sollicitudin ac orci. Velit sed ullamcorper morbi
tincidunt ornare. Auctor eu augue ut lectus arcu bibendum. Non nisi est sit amet.
Facilisis magna etiam tempor orci eu lobortis. Et tortor at risus viverra adipiscing
at in tellus integer. Lacus luctus accumsan tortor posuere ac ut consequat semper
viverra. Fermentum dui faucibus in ornare quam viverra orci sagittis. Porttitor eget
dolor morbi non. Pulvinar pellentesque habitant morbi tristique senectus et.
Tincidunt eget nullam non nisi est sit amet facilisis magna. Purus semper eget duis
at tellus at urna condimentum. Ipsum dolor sit amet consectetur adipiscing. Sit amet
aliquam id diam maecenas ultricies mi eget mauris. Faucibus scelerisque eleifend
donec pretium vulputate sapien nec sagittis. Tristique senectus et netus et
malesuada fames ac turpis. Egestas integer eget aliquet nibh. Enim ut tellus
elementum sagittis vitae. Urna condimentum mattis pellentesque id nibh tortor id
aliquet. Magna eget est lorem ipsum dolor. Felis imperdiet proin fermentum leo vel
orci porta. Eget egestas purus viverra accumsan in nisl nisi. Adipiscing commodo
elit at imperdiet. Facilisis magna etiam tempor orci eu lobortis. Volutpat est velit
egestas dui id ornare arcu odio. Praesent elementum facilisis leo vel fringilla.
Laoreet non curabitur gravida arcu ac tortor dignissim convallis aenean. Sodales ut
etiam sit amet nisl. Turpis massa tincidunt dui ut ornare. Viverra mauris in aliquam
sem fringilla ut morbi tincidunt augue.
</div>
</div>
)
})}
</div>
)
}
}
It works.
It creates 5 div elements, scrolling one of them, all the div scroll togheter. I use React references to do that.
What I would like to do now is creates a component that abstracts this logic.
I imagine something like that:
<ScrollDivs>
{range(DIV_NUMBER).map(i => {
return (
<div
key={i}
style={{
width: 500,
height: 400,
}}
>
all the text...
</div>
)
})}
</ScrollDivs>
So a magic component ScrollDivs that deals with logic.
Who uses this component does not have to worry about how it is done, he just needs to wrap the divs that wants to be able to scroll together inside this component.
How can I do? I don't know where to start.
Any help is appreciate
React.Children.map can help you to iterate over props.children and React.cloneElement can help with passing new props to children:
export default class ScrollDivsSync extends React.Component {
divs = [];
handleScroll = e => {
const { scrollTop, scrollLeft } = e.target;
this.divs.forEach(div => {
div.scrollLeft = scrollLeft;
div.scrollTop = scrollTop;
});
};
render() {
const { children } = this.props;
let i = 0;
const enhancedChildren = React.Children.map(children, child =>
React.cloneElement(child, {
onScroll: this.handleScroll,
ref: divElem => (this.divs[i++] = divElem)
})
);
return enhancedChildren;
}
}
This is a CodeSandbox with the example

MUI: how to show tooltip component over selected text?

Like on medium:
Tooltip from matetial-ui react framework.
some ideas:
wrap selected text with <span> and use that element as reference node
of tooltip (over complicated I guess)
create tooltip w/o child and specify
absolute position based on selected text
any other ideas?
The simplest: https://www.w3schools.com/css/css_tooltip.asp
The more effective: https://chrisbracco.com/a-simple-css-tooltip/
:)
Medium.com uses popper.js. I found this out by searching the tag parameters in Medium's HTML.
<div class="bz fx mc yw"
style=
"position: absolute; inset: auto auto 0px 0px; transform: translate(692px, 1233px);"
data-popper-reference-hidden="false"
data-popper-escaped="false"
data-popper-placement="top">
https://popper.js.org/
It does the thing where it repositons the popup if you scroll, I checked.
I used the popper component instead of tooltip component link: https://mui.com/material-ui/react-popper/#virtual-element
demo: https://codesandbox.io/s/iztu66?file=/demo.js
code
import * as React from 'react';
import Popper from '#mui/material/Popper';
import Typography from '#mui/material/Typography';
import Fade from '#mui/material/Fade';
import Paper from '#mui/material/Paper';
export default function VirtualElementPopper() {
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClose = () => {
setOpen(false);
};
const handleMouseUp = () => {
const selection = window.getSelection();
// Resets when the selection has a length of 0
if (!selection || selection.anchorOffset === selection.focusOffset) {
handleClose();
return;
}
const getBoundingClientRect = () =>
selection.getRangeAt(0).getBoundingClientRect();
setOpen(true);
setAnchorEl({
getBoundingClientRect,
});
};
const id = open ? 'virtual-element-popper' : undefined;
return (
<div onMouseLeave={handleClose}>
<Typography aria-describedby={id} onMouseUp={handleMouseUp}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ipsum purus,
bibendum sit amet vulputate eget, porta semper ligula. Donec bibendum
vulputate erat, ac fringilla mi finibus nec. Donec ac dolor sed dolor
porttitor blandit vel vel purus. Fusce vel malesuada ligula. Nam quis
vehicula ante, eu finibus est. Proin ullamcorper fermentum orci, quis finibus
massa. Nunc lobortis, massa ut rutrum ultrices, metus metus finibus ex, sit
amet facilisis neque enim sed neque. Quisque accumsan metus vel maximus
consequat. Suspendisse lacinia tellus a libero volutpat maximus.
</Typography>
<Popper
id={id}
open={open}
anchorEl={anchorEl}
transition
placement="bottom-start"
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper>
<Typography sx={{ p: 2 }}>The content of the Popper.</Typography>
</Paper>
</Fade>
)}
</Popper>
</div>
);
}
You can add your own buttons after fade component or inside in paper component.

Resources