Popover

分类:

other

日期:

2022-4-22

标签:

chakra

Popover is a non-modal dialog that floats around a trigger. It is 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.

Import#

  • Popover: The wrapper that provides props, state, and context to its 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: A button to close the popover.
  • PopoverAnchor: Used to wrap the position-reference element.
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverHeader,
PopoverBody,
PopoverFooter,
PopoverArrow,
PopoverCloseButton,
PopoverAnchor,
} from '@chakra-ui/react';

Basic Usage#

When using this component, ensure the children passed to PopoverTrigger is focusable and has a forwarded ref. Users can tab to it using their keyboard and is critical for accessibility. If you are using a custom component inside PopoverTrigger (or PopoverAnchor) it needs Chakra's forwardRef.

A11y: When Popover opens, focus is sent to PopoverContent. When it closes, focus is returned to the trigger.

<Popover>
<PopoverTrigger>
<Button>Trigger</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverArrow />
<PopoverCloseButton />
<PopoverHeader>Confirmation!</PopoverHeader>
<PopoverBody>Are you sure you want to have that milkshake?</PopoverBody>
</PopoverContent>
</Popover>

Rendering the Popover in a Portal#

By default, the Popover doesn't render in a Portal. To make them display in a portal, wrap the PopoverContent in a Portal

You might need to Inspect Element to see this in action. Notice that PopoverContent is rendered as a child of <body>

<Popover>
<PopoverTrigger>
<Button>Trigger</Button>
</PopoverTrigger>
<Portal>
<PopoverContent>
<PopoverArrow />
<PopoverHeader>Header</PopoverHeader>
<PopoverCloseButton />
<PopoverBody>
<Button colorScheme="blue">Button</Button>
</PopoverBody>
<PopoverFooter>This is the footer</PopoverFooter>
</PopoverContent>
</Portal>
</Popover>

Focus an element when Popover opens#

By default, focus is to sent to PopoverContent when it opens. Pass the initialFocusRef prop to send focus to a specific element instead.

Focusing a child element only works for popovers with a click trigger, not hover.

function WalkthroughPopover() {
const initialFocusRef = React.useRef();
return (
<Popover
initialFocusRef={initialFocusRef}
placement="bottom"
closeOnBlur={false}
>
<PopoverTrigger>
<Button>Trigger</Button>
</PopoverTrigger>
<PopoverContent color="white" bg="blue.800" borderColor="blue.800">
<PopoverHeader pt={4} fontWeight="bold" border="0">
Manage Your Channels
</PopoverHeader>
<PopoverArrow />
<PopoverCloseButton />
<PopoverBody>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore.
</PopoverBody>
<PopoverFooter
border="0"
display="flex"
alignItems="center"
justifyContent="space-between"
pb={4}
>
<Box fontSize="sm">Step 2 of 4</Box>
<ButtonGroup size="sm">
<Button colorScheme="green">Setup Email</Button>
<Button colorScheme="blue" ref={initialFocusRef}>
Next
</Button>
</ButtonGroup>
</PopoverFooter>
</PopoverContent>
</Popover>
);
}

Trapping Focus within Popover#

If the popover contains a form, you might need to trap focus within the popover and close it when the user fills the form and hits "save".

You can leverage react-focus-lock to trap focus within the PopoverContent.

// import FocusLock from "react-focus-lock"
// 1. Create a text input component
const TextInput = React.forwardRef((props, ref) => {
return (
<FormControl>
<FormLabel htmlFor={props.id}>{props.label}</FormLabel>
<Input ref={ref} id={props.id} {...props} />
</FormControl>
);
});
// 2. Create the form
const 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 display="flex" justifyContent="flex-end">
<Button variant="outline" onClick={onCancel}>
Cancel
</Button>
<Button isDisabled colorScheme="teal">
Save
</Button>
</ButtonGroup>
</Stack>
);
};
// 3. Create the Popover
// Ensure you set `closeOnBlur` prop to false so it doesn't close on outside click
const PopoverForm = () => {
const { onOpen, onClose, isOpen } = useDisclosure();
const firstFieldRef = React.useRef(null);
return (
<>
<Box display="inline-block" mr={3}>
John Smith
</Box>
<Popover
isOpen={isOpen}
initialFocusRef={firstFieldRef}
onOpen={onOpen}
onClose={onClose}
placement="right"
closeOnBlur={false}
>
<PopoverTrigger>
<IconButton size="sm" icon={<EditIcon />} />
</PopoverTrigger>
<PopoverContent p={5}>
<FocusLock returnFocus persistentFocus={false}>
<PopoverArrow />
<PopoverCloseButton />
<Form firstFieldRef={firstFieldRef} onCancel={onClose} />
</FocusLock>
</PopoverContent>
</Popover>
</>
);
};
render(<PopoverForm />);

Controlled Usage#

You can control the opening and closing of the popover by passing the isOpen, and onClose props.

Sometimes you might need to set the returnFocusOnClose prop to false to prevent popover from returning focus to PopoverTrigger's children.

function ControlledUsage() {
const { isOpen, onToggle, onClose } = useDisclosure();
return (
<>
<Button mr={5} onClick={onToggle}>
Trigger
</Button>
<Popover
returnFocusOnClose={false}
isOpen={isOpen}
onClose={onClose}
placement="right"
closeOnBlur={false}
>
<PopoverTrigger>
<Button colorScheme="pink">Popover Target</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader fontWeight="semibold">Confirmation</PopoverHeader>
<PopoverArrow />
<PopoverCloseButton />
<PopoverBody>
Are you sure you want to continue with your action?
</PopoverBody>
<PopoverFooter display="flex" justifyContent="flex-end">
<ButtonGroup size="sm">
<Button variant="outline">Cancel</Button>
<Button colorScheme="red">Apply</Button>
</ButtonGroup>
</PopoverFooter>
</PopoverContent>
</Popover>
</>
);
}

Popover Anchor#

You can wrap your component with PopoverAnchor to prevent trigger any action. The wrapped component will become a position reference. Actions will only be triggered by components inside PopoverTrigger.

In this case, you can only open and close the popover with Button. If you click on Input, it acts same as the original input and doesn't trigger any action about popover.

🚨 Note: As with PopoverTrigger, if you use a custom component inside PopoverAnchor ensure that it uses Chakra's forwardRef so the popover is triggered successfully.

function WithPopoverAnchor() {
const [isEditing, setIsEditing] = useBoolean();
const [color, setColor] = React.useState('red');
return (
<Popover
isOpen={isEditing}
onOpen={setIsEditing.on}
onClose={setIsEditing.off}
closeOnBlur={false}
isLazy
lazyBehavior="keepMounted"
>
<HStack>
<PopoverAnchor>
<Input
color={color}
w="auto"
display="inline-flex"
isDisabled={!isEditing}
defaultValue="Popover Anchor"
/>
</PopoverAnchor>
<PopoverTrigger>
<Button h="40px" colorScheme="pink">
{isEditing ? 'Save' : 'Edit'}
</Button>
</PopoverTrigger>
</HStack>
<PopoverContent>
<PopoverBody>
Colors:
<RadioGroup value={color} onChange={newColor => setColor(newColor)}>
<Radio value="red">red</Radio>
<Radio value="blue">blue</Radio>
<Radio value="green">green</Radio>
<Radio value="purple">purple</Radio>
</RadioGroup>
</PopoverBody>
</PopoverContent>
</Popover>
);
}

Accessing Internal state#

Chakra provides access to two internal details: isOpen and onClose. Use the render prop pattern to gain access to them.

function InternalStateEx() {
const initRef = React.useRef();
return (
<Popover closeOnBlur={false} placement="left" initialFocusRef={initRef}>
{({ isOpen, onClose }) => (
<>
<PopoverTrigger>
<Button>Click to {isOpen ? 'close' : 'open'}</Button>
</PopoverTrigger>
<Portal>
<PopoverContent>
<PopoverHeader>This is the header</PopoverHeader>
<PopoverCloseButton />
<PopoverBody>
<Box>
Hello. Nice to meet you! This is the body of the popover
</Box>
<Button
mt={4}
colorScheme="blue"
onClick={onClose}
ref={initRef}
>
Close
</Button>
</PopoverBody>
<PopoverFooter>This is the footer</PopoverFooter>
</PopoverContent>
</Portal>
</>
)}
</Popover>
);
}

Customizing the Popover#

Chakra exports all the components you need to customize the look and feel of the popover. You can change the background, arrow size, box shadow and so on.

<Popover>
<PopoverTrigger>
<Box
tabIndex="0"
role="button"
aria-label="Some box"
p={5}
w="120px"
bg="gray.300"
children="Click"
/>
</PopoverTrigger>
<PopoverContent bg="tomato" color="white">
<PopoverHeader fontWeight="semibold">Customization</PopoverHeader>
<PopoverArrow bg="pink.500" />
<PopoverCloseButton bg="purple.500" />
<PopoverBody>
Tadaa!! The arrow color and background color is customized. Check the
props for each component.
</PopoverBody>
</PopoverContent>
</Popover>

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 the event that available space at the specified placement isn't enough.

<Popover placement="top-start">
<PopoverTrigger>
<Button>Click me</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader fontWeight="semibold">Popover placement</PopoverHeader>
<PopoverArrow />
<PopoverCloseButton />
<PopoverBody>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore.
</PopoverBody>
</PopoverContent>
</Popover>

Lazily mounting Popover#

By default, the Popover component renders children of PopoverContent to the DOM, meaning that invisible popover contents are still rendered but are hidden by styles.

If you want to defer rendering of popover content until that Popover is opened, you can use the isLazy prop. This is useful if your PopoverContent needs to be extra performant, or make network calls on mount that should only happen when the component is displayed.

<Popover isLazy>
<PopoverTrigger>
<Button>Click me</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader fontWeight="semibold">Popover placement</PopoverHeader>
<PopoverArrow />
<PopoverCloseButton />
<PopoverBody>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore.
</PopoverBody>
</PopoverContent>
</Popover>

Accessibility#

When you see the word "trigger", it is referring to the children of PopoverTrigger

Keyboard and Focus#

  • When the popover is opened, focus is moved to the PopoverContent. If the initialFocusRef is set, then focus moves to the element with that ref.
  • When the popover is closed, focus returns to the trigger. If you set returnFocusOnClose to false, 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 Space or Enter when focus is on the trigger will open the popover.
    • Clicking the trigger again will close the popover.
  • Hitting the Esc key while the popover is open and focus is within the PopoverContent, will close the popover. If you set closeOnEsc to false, it will not close.
  • Clicking outside or blurring out of the PopoverContent closes the popover. If you set closeOnBlur to false, it will not close.

ARIA Attributes#

  • If the trigger is set to click, the PopoverContent element has role set to dialog. If the trigger is set to hover, the PopoverContent has role set to tooltip.
  • The PopoverContent has aria-labelledby set to the id of the PopoverHeader.
  • The PopoverContent has aria-describedby set to the id of the PopoverBody.
  • The PopoverContent has aria-hidden set to true or false depending on the open/closed state of the popover.
  • The trigger has aria-haspopup set to true to denote that it triggers a popover.
  • The trigger has aria-controls set to the id of the PopoverContent to associate the popover and the trigger.
  • The trigger has aria-expanded set to true or false depending on the open/closed state of the popover.

Props#

Popover Props#

arrowPadding

The padding required to prevent the arrow from reaching the very edge of the popper.

number
8

arrowShadowColor

The `box-shadow` of the popover arrow

string

arrowSize

The size of the popover arrow

number

autoFocus

If true, focus will be transferred to the first interactive element when the popover opens

boolean
true

boundary

The boundary area for the popper. Used within the preventOverflow modifier

HTMLElement | "clippingParents" | "scrollParent"
"clippingParents"

children

The content of the popover. It is usually the PopoverTrigger, and PopoverContent

MaybeRenderProp<{ isOpen: boolean; onClose: () => void; forceUpdate: (() => void); }>

closeDelay

number
200

closeOnBlur

If true, the popover will close when you blur out it by clicking outside or tabbing out

boolean
true

closeOnEsc

If true, the popover will close when you hit the Esc key

boolean
true

colorScheme

Color Schemes Popover

(string & {})

computePositionOnMount

If true, the popover will be positioned when it mounts (even if it's not open) Note 🚨: We don't recommend using this in a popover/menu intensive UI or page as it might affect scrolling performance.

boolean

defaultIsOpen

If true, the popover will be initially opened.

boolean

direction

Theme direction ltr or rtl. Popper's placement will be set accordingly

"ltr" | "rtl"
"ltr"

eventListeners

If provided, determines whether the popper will reposition itself on scroll and resize of the window.

boolean | { scroll?: boolean; resize?: boolean; } | undefined
true

flip

If true, the popper will change its placement and flip when it's about to overflow its boundary area.

boolean
true

gutter

The distance or margin between the reference and popper. It is used internally to create an offset modifier. NB: If you define offset prop, it'll override the gutter.

number
8

id

The html id attribute of the popover. If not provided, we generate a unique id. This id is also used to auto-generate the `aria-labelledby` and `aria-describedby` attributes that points to the PopoverHeader and PopoverBody

string

initialFocusRef

The ref of the element that should receive focus when the popover opens.

RefObject<FocusableElement>

isLazy

Performance 🚀: If true, the PopoverContent rendering will be deferred until the popover is open.

boolean

isOpen

If true, the popover will be opened in controlled mode.

boolean

lazyBehavior

Performance 🚀: The lazy behavior of popover's content when not visible. Only works when `isLazy={true}` - "unmount": The popover's content is always unmounted when not open. - "keepMounted": The popover's content initially unmounted, but stays mounted when popover is open.

LazyBehavior
"unmount"

matchWidth

If true, the popper will match the width of the reference at all times. It's useful for autocomplete, `date-picker` and select patterns.

boolean

modifiers

Array of popper.js modifiers. Check the docs to see the list of possible modifiers you can pass. @see Docs https://popper.js.org/docs/v2/modifiers/

Partial<Modifier<string, any>>[]

offset

The main and cross-axis offset to displace popper element from its reference element.

[number, number]

onClose

Callback fired when the popover closes

(() => void)

onOpen

Callback fired when the popover opens

(() => void)

openDelay

number
200

orientation

"horizontal" | "vertical"

placement

The placement of the popper relative to its reference.

PlacementWithLogical
"bottom"

preventOverflow

If true, will prevent the popper from being cut off and ensure it's visible within the boundary area.

boolean
true

returnFocusOnClose

If true, focus will be returned to the element that triggers the popover when it closes

boolean
true

size

Sizes Popover

string

strategy

The CSS positioning strategy to use.

"fixed" | "absolute"
"absolute"

styleConfig

Dict<any>

trigger

The interaction that triggers the popover. hover - means the popover will open when you hover with mouse or focus with keyboard on the popover trigger click - means the popover will open on click or press Enter to Space on keyboard

"click" | "hover"
"click"

variant

Variants Popover

string

Other Props#

  • PopoverContent composes Box and has the ability to smartly position itself. Thanks to popper.js.
  • PopoverArrow, PopoverHeader, PopoverFooter and PopoverBody composes Box.
  • PopoverCloseButton composes Box component.
footer logo

© 2022 Designed with chakra ui. Powered by contentlayerjs and nextjs etc. All rights reserved

TwitterYouTubeGithub