Popover
Popover is a non-modal dialog that floats around a trigger. It's used to display contextual information to the user, and should be paired with a clickable trigger element.
Popover is built on top of the Popper.js library, and composes the Popper component.
Import
Popover: The wrapper that provides props, state, and context to it's children.PopoverTrigger: Used to wrap the reference (or trigger) element.PopoverContent: The popover itself.PopoverHeader: The header of the popover.PopoverBody: The body of the popover.PopoverArrow: A visual arrow that points to the reference (or trigger).PopoverCloseButton: Obviously, a button to close the popover.
import {Popover,PopoverTrigger,PopoverContent,PopoverHeader,PopoverBody,PopoverFooter,PopoverArrow,PopoverCloseButton,} from '@outfitio/outkit';
Usage
When using this component, ensure the child passed to PopoverTrigger is focusable, user can tab to it using their
keyboard, and it can take a ref. It's critical for accessiblity.
Rendering the Popover in a Portal
By default, the Popover doesn't render in a Portal. To make them display in a portal, pass the usePortal prop.
You might need to Inspect Element to see this in action. Notice the
PopoverContentis rendered as a child of<body>
Focus an element when Popover opens
By default, focus is to sent to the PopoverContent when it opens, you might want to send focus to a specific element
when it opens. Pass the initialFocusRef prop to do so.
Trapping Focus within Popover
If the popover contains a form, you might need to trap focus within the popover and close it when the users fills the form and hit "save".
You can leverage react-focus-lock to trap focus within the
PopoverContent.
// import FocusLock from "react-focus-lock"// 1. Create a text input componentconst TextInput = forwardRef((props, ref) => {return (<FormControl><FormLabel htmlFor={props.id}>{props.label}</FormLabel><Input ref={ref} id={props.id} {...props} /></FormControl>);});// 2. Create the formconst Form = ({ firstFieldRef, onCancel }) => {return (<Stack spacing={4}><TextInput label="First name" id="first-name" ref={firstFieldRef} defaultValue="John" /><TextInput label="Last name" id="last-name" defaultValue="Smith" /><ButtonGroup d="flex" justifyContent="flex-end"><Button variant="outline" onClick={onCancel}>Cancel</Button><Button isDisabled variantColor="primary">Save</Button></ButtonGroup></Stack>);};// 3. Create the Popover// Ensure you set `closeOnBlur` prop to false so it doesn't close on outside clickconst PopoverForm = () => {const [isOpen, setIsOpen] = useState(false);const firstFieldRef = React.useRef(null);const open = () => setIsOpen(true);const close = () => setIsOpen(false);return (<Fragment><Box d="inline-block" mr={3}>John Smith</Box><PopoverisOpen={isOpen}initialFocusRef={firstFieldRef}onOpen={open}onClose={close}placement="right"closeOnBlur={false}><PopoverTrigger><IconButton size="sm" icon="edit" /></PopoverTrigger><PopoverContent zIndex={8} p={5}><FocusLock returnFocus persistentFocus={false}><PopoverArrow bg="white" /><PopoverCloseButton /><Form firstFieldRef={firstFieldRef} onCancel={close} /></FocusLock></PopoverContent></Popover></Fragment>);};
Controlled usage
You can completely control the opening and closing of the popover by passing the isOpen, and onClose.
Sometime, you might need to set returnFocusOnClose to false to prevent the popover from returning focus to
PopoverTrigger's children.
Accessing internal state
Outkit provides access to two internal details, isOpen and onClose. Use the render prop pattern to gain access to
them.
Popover placements
Since popover is powered by PopperJS, you can change the placement of the popover by passing the placement prop. See
the props for the possible placement values.
Even though you specified the placement, Popover will try to reposition itself in event the available space at the specified placment isn't enough.
Hover trigger
To show the popover when you mouse over or focus on the trigger, pass the prop trigger and set it to hover. When you
focus on or mouse over the popover trigger, the popover will open.
If you quickly move your cursor to the popover content when it's open, it'll remain open till you leave.
Accessiblity
When you see the word "trigger", it's referring to the
childrenofPopoverTrigger
Keyboard support
- When the popover is opened, focus is moved to the
PopoverContent. If theinitialFocusRefis set, then focus moves to the element with thatref. - When the popover is closed, focus returns to the trigger. If you set
returnFocusOnClosetofalse, focus will not return. - If trigger is set to
hover:- Focusing on or mousing over the trigger will open the popover
- Blurring or mousing out of the trigger will close the popover. If you move your mouse into the
PopoverContent, it'll remain visible.
- If trigger is set to
click:- Clicking the trigger or using the
SpaceorEnterwhen focus is on the trigger will open the popover. - Clicking the trigger again will close the popover.
- Clicking the trigger or using the
- Hitting the
Esckey while the popover is open and focus is within thePopoverContent, will close the popover. If you setcloseOnEsctofalse, it will not close. - Clicking outside or blurring out of the
PopoverContentcloses the popover. If you setcloseOnBlurtofalse, it will not close.
ARIA Attributes
- If the trigger is set to
click, thePopoverContentelement has role set todialog. If the trigger is set tohover, thePopoverContenthasroleset totooltip. - The
PopoverContenthasaria-labelledbyset to theidof thePopoverHeader. - The
PopoverContenthasaria-describedbyset to theidof thePopoverBody. - The
PopoverContenthasaria-hiddenset totrueorfalsedepending on the open/closed state of the popover. - The trigger has
aria-haspopupset totrueto denote that it triggers a popover. - The trigger has
aria-controlsset to theidof thePopoverContentto associate the popover and the trigger. - The trigger has
aria-expandedset totrueorfalsedepending on the open/closed state of the popover.
Props
Popover Props
| Name | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | If true, the popover is shown | |
defaultIsOpen | boolean | If true, the popover is shown initially. | |
initialFocusRef | React.Ref | The ref of the element that should receive focus when the popover opens. | |
trigger | hover, click | click | The interaction that triggers the popover. |
placement | PopperJS.placement | bottom | The placement of the popover. |
returnFocusOnClose | boolean | true | If true, the popover will return focus to the trigger when it closes |
closeOnBlur | boolean | true | If true, the popover will close when you blur out it by clicking outside or tabbing out |
closeOnEsc | boolean | true | If true, close the popover when the esc key is pressed |
children | React.ReactNode, (props: InternalState) => React.ReactNode | The children of the popover | |
gutter | number | 4 | The gap (in pixels) to apply between the popover and the target. Used by popper.js |
usePortal | boolean | false | If true the popover is displayed with a Portal. Rendering content inside a Portal allows the popover content to escape the physical bounds of its parent while still being positioned correctly relative to its target |
onOpen | () => void | Callback fired when the popover opens | |
onClose | () => void | Callback fired when the popover closes |
Placement Props
The auto placements will choose the side with most space.
| default | start | end |
|---|---|---|
auto | auto-start | auto-end |
top | top-start | top-end |
bottom | bottom-start | bottom-end |
right | right-start | right-end |
left | left-start | left-end |
Other Props
PopoverContentcomposesPseudoBoxand has the ability to smartly position itself. Thanks to popper.jsPopoverArrow,PopoverHeader,PopoverFooterandPopoverBodycomposesBox.PopoverCloseButtoncomposesPseudoBoxcomponent.