Popper
The component is our React
-wrapper over the popular popper.js library.
We use it in all pop-ups: dropdown, tooltip, select, etc.
Controlled and uncontrolled mode
The component can function in both uncontrolled
and controlled
modes.
import React from 'react';
import Popper from '@semcore/popper';
import Button from '@semcore/button';
import { Flex } from '@semcore/flex-box';
const style = { background: '#FFF', color: '#000', border: '1px solid #000', padding: '10px' };
const Demo = () => {
const [visible, setVisible] = React.useState(false);
const toggleVisible = () => {
setVisible(!visible);
};
return (
<Flex justifyContent='space-between'>
<Popper visible={visible} onVisibleChange={toggleVisible}>
<Popper.Trigger tag={Button}>Controlled</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<Popper>
<Popper.Trigger tag={Button} ml='auto'>
Uncontrolled
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
</Flex>
);
};
export default Demo;
Trigger event
Using the interaction
prop, you can specify trigger events which are to be subscribed to to show and hide the popper.
When these events are activated, the onVisibleChange
handler is called with the changed visibility value.
import React from 'react';
import Popper from '@semcore/popper';
import Button from '@semcore/button';
const style = { background: '#FFF', color: '#000', border: '1px solid #000', padding: '10px' };
const Demo = () => (
<Popper interaction='hover'>
<Popper.Trigger tag={Button}>Open popper on hover or focus</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
);
export default Demo;
Outside click
You can subscribe to the onOutsideClick
event. It will be called when a clicked is made on any element other than Trigger
and Popper
.
TIP
excludeRefs
prop is also provided. An array of nodes which must be excluded to trigger a click can be passed to it. The Trigger
and Popper
nodes will be passed thereto by default.
import React from 'react';
import Popper from '@semcore/popper';
import Button from '@semcore/button';
const style = { background: '#FFF', color: '#000', border: '1px solid #000', padding: '10px' };
const Demo = () => (
<Popper
onOutsideClick={() => {
// cancel hide popper
return false;
}}
>
<Popper.Trigger tag={Button}>Open popper</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
);
export default Demo;
Placement
Since we are using popper.js, the placement prop comes from there.
Placement may be 'auto-start' | 'auto' | 'auto-end' | 'top-start' | 'top' | 'top-end' | 'right-start' | 'right' | 'right-end' | 'bottom-end' | 'bottom' | 'bottom-start' | 'left-end' | 'left' | 'left-start'
import React from 'react';
import { Box } from '@semcore/flex-box';
import Popper from '@semcore/popper';
import Button from '@semcore/button';
const style = { background: '#FFF', color: '#000', border: '1px solid #000', padding: '10px' };
const styleBox = {
display: 'grid',
gridTemplateRows: '1fr 1fr 1fr',
gridTemplateColumns: '1fr 1fr 1fr',
gridGap: '2vw',
padding: '60px',
};
const Demo = () => (
<Box style={styleBox}>
<Popper placement='top-start' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
TOP START
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<Popper placement='top' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
TOP
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<Popper placement='top-end' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
TOP END
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<Popper placement='left-start' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
LEFT START
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<div />
<Popper placement='right-start' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
RIGHT START
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<Popper placement='left' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
LEFT
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<div />
<Popper placement='right' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
RIGHT
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<Popper placement='left-end' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
LEFT END
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<div />
<Popper placement='right-end' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
RIGHT END
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<Popper placement='bottom-start' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
BOTTOM START
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<Popper placement='bottom' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
BOTTOM
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
<Popper placement='bottom-end' interaction='hover'>
<Popper.Trigger w='100px' tag={Button}>
BOTTOM END
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
</Box>
);
export default Demo;
Custom tag
tag
is the name of the HTML tag for the displayed element. The trigger and popper support changing this prop.
You can pass either another component or a string to the tag
. For example, tag="a"
or tag={Button}
.
TIP
The tag
for Trigger
and Popper
is a Box
by default, so all props from the trigger go to the Box
.
import React from 'react';
import Popper from '@semcore/popper';
import Button from '@semcore/button';
import HamburgerM from '@semcore/icon/Hamburger/m';
const style = { background: '#FFF', color: '#000', border: '1px solid #000', padding: '10px' };
const Demo = () => (
<Popper>
<Popper.Trigger tag={Button}>
<Button.Addon>
<HamburgerM />
</Button.Addon>
<Button.Text>Menu</Button.Text>
</Popper.Trigger>
<Popper.Popper style={style}>Attached content</Popper.Popper>
</Popper>
);
export default Demo;
Render functions
You can change the trigger by passing a function instead of the Popper
body.
Inside the function, the first argument provides the component props and the getTriggerProps
functions for the trigger and getPopperProps
for the popper, respectively. By calling them, you get the props that you need to put on the required elements.
Important! You can pass custom props required for the component in getTriggerProps
, getPopperProps
. In this case, these props will merge with the props required from Popper
.
For example, getPopperProps()
will return style and ref, if we do <input {...getPopperProps()} ref={myRef}/>
, this will not work since we will overwrite ref
. And if we pass ref
inside the function, <input {... getPopperProps ({ref: myRef})} />
, then we call two functions one after another, and everything will function.
TIP
The second argument of the render-function will provide handlers
, functions for changing the internal state of the component.
import React, { useRef } from 'react';
import Button from '@semcore/button';
import Popper from '@semcore/popper';
const style = { background: '#FFF', color: '#000', border: '1px solid #000', padding: '10px' };
const Demo = () => {
const buttonRef = useRef<HTMLButtonElement>(null);
return (
<Popper>
{(props, handlers) => {
// function for managing the visibility state of Popper.Popper
const { visible } = handlers;
return (
<>
<Button onClick={() => visible(true)} ref={buttonRef} mr={4}>
Open popper
</Button>
<Popper.Trigger style={style}>Attach trigger</Popper.Trigger>
<Popper.Popper style={style}>
<p>Attached content</p>
<Button
onClick={() => {
visible(false);
setTimeout(() => buttonRef.current?.focus(), 1);
}}
>
Close popper
</Button>
</Popper.Popper>
</>
);
}}
</Popper>
);
};
export default Demo;
Disabled portal
The popper is rendered in the end of the body
and absolutely positioned. to render the Popper
next to the Trigger
, you need to specify disablePortal
.
This is usually needed to optimize position recalculation when the Trigger
is located in a block that scrolls separately from the page.
TIP
Inspect the page to find out where the popper is located.
import React from 'react';
import Popper from '@semcore/popper';
import Button from '@semcore/button';
const style = { background: '#FFF', color: '#000', border: '1px solid #000', padding: '10px' };
const Demo = () => (
<>
<Popper disablePortal>
<Popper.Trigger tag={Button} mr={8}>
disablePortal = true
</Popper.Trigger>
<Popper.Popper style={style}>disablePortal = true</Popper.Popper>
</Popper>
<Popper>
<Popper.Trigger tag={Button}>disablePortal = false</Popper.Trigger>
<Popper.Popper style={style}>disablePortal = false</Popper.Popper>
</Popper>
</>
);
export default Demo;
Fixed position
The Popper is positioned absolutely, but this behavior can be changed to fixed position (display: fixed)
.
This is usually needed to optimize the recalculation of the Popper's position relative to the Trigger
when the Trigger
is located in a block that has display: fixed
🤯
By specifying strategy=fixed
, you can generally get rid of recalculations when scrolling the page.
<Popper strategy="fixed">
<Popper.Trigger />
<Popper.Popper />
</Popper>