UNPKG

@react-ui-org/react-ui

Version:

React UI is a themeable UI library for React apps.

388 lines (338 loc) 12.5 kB
# Popover Popover displays additional information without interrupting user flow. ## Basic Usage To implement the Popover component, you need to import it first: ```js import { Popover, PopoverWrapper } from '@react-ui-org/react-ui'; ``` And use it: ```docoff-react-preview React.createElement(() => { const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); // All inline styles in this example are for demonstration purposes only. return ( <div style={{ display: 'grid', placeContent: 'center', minWidth: '20rem', minHeight: '10rem', }} > <PopoverWrapper> <Button aria-describedby={isPopoverOpen ? 'my-popover' : undefined} label="Want to see a popover? Click me!" onClick={() => setIsPopoverOpen(!isPopoverOpen)} /> {isPopoverOpen && ( <Popover id="my-popover"> Hello there! </Popover> )} </PopoverWrapper> </div> ); }); ``` See [API](#api) for all available options. ## Placement Available placements are: top, right, bottom, and left. Additionally, all basic placements can be aligned to the center (default, no suffix), start (e.g. `top-start`), or end (e.g. `bottom-end`). Check Popover [API](#api) for the complete list of accepted values. ```docoff-react-preview React.createElement(() => { const [align, setAlign] = React.useState(''); // All inline styles in this example are for demonstration purposes only. return ( <> <Toolbar align="baseline"> <ToolbarItem> <span id="alignment-options-label">Alignment:</span> </ToolbarItem> <ToolbarItem> <ButtonGroup aria-labelledby="alignment-options-label" priority="outline"> <Button aria-pressed={align === '-start'} color={align === '-start' ? 'selected' : 'secondary'} label="start" onClick={() => setAlign('-start')} /> <Button aria-pressed={align === ''} color={align === '' ? 'selected' : 'secondary'} label="center" onClick={() => setAlign('')} /> <Button aria-pressed={align === '-end'} color={align === '-end' ? 'selected' : 'secondary'} label="end" onClick={() => setAlign('-end')} /> </ButtonGroup> </ToolbarItem> </Toolbar> <div style={{ display: 'grid', placeContent: 'center', minWidth: '20rem', minHeight: '15rem', }} > <PopoverWrapper> <docoff-placeholder bordered aria-describedby="my-popover-top"> Popovers <br /> all day long… </docoff-placeholder> <Popover id="my-popover-top" placement={`top${align}`}> Top side </Popover> <Popover id="my-popover-right" placement={`right${align}`}> Right side </Popover> <Popover id="my-popover-bottom" placement={`bottom${align}`}> Bottom side </Popover> <Popover id="my-popover-left" placement={`left${align}`}> Left side </Popover> </PopoverWrapper> </div> </> ); }); ``` ## PopoverWrapper PopoverWrapper is an optional wrapper to make positioning of Popover even easier. By default, Popover is placed relative to the closest parent element with `position: relative` or `position: absolute`. Maybe you already have one of these in your CSS. PopoverWrapper is here for situations when you don't. ```jsx <PopoverWrapper> <Button aria-describedby={isPopoverOpen ? 'my-popover' : undefined} label="Want to see a popover? Click me!" onClick={() => setIsPopoverOpen(!isPopoverOpen)} /> {isPopoverOpen && <Popover id="my-popover">Hello there!</Popover>} </PopoverWrapper> ``` How do you know you may need PopoverWrapper? - You are **not** rendering Popover in a React portal. - You are using Popover in a complex layout and it does not pop up where you need it. - You are using Floating UI with `absolute` positioning strategy (see [Advanced Positioning](#advanced-positioning) below) and your Popover keeps to be misplaced. - You have no idea what CSS `position` is and just want to get it working. To sum it up, usually you will need either PopoverWrapper around your content or `position: [ relative | absolute ]` somewhere in your CSS (but you never need both!). Nevertheless, in the simplest situations, like in a single-column page layout, you may not need either of these at all. Head to PopoverWrapper [API](#popoverwrapper-api) for all available options. ## Advanced Positioning While the basic setup can be sufficient in some scenarios, dropping a Popover usually won't be so easy. To handle all tricky situations and edge cases automatically, including smart position updates to ensure Popover visibility, we recommend to involve an external library designed specifically for this purpose. To position the popover, you need to provide the `placementStyle` prop with the style you want to apply to the popover. This prop should only be used to position the popover. The allowed props are: - `position` - `inset` - `inset-inline-start` - `inset-inline-end` - `inset-block-start` - `inset-block-end` - `top` - `right` - `bottom` - `left` - `translate` - `transform-origin` ⚠️ [`inset`][mdn-inset] is a shorthand for `top right bottom left`, not for `inset-*` properties. As opposed to `top right bottom left` and the `inset` shorthand, `inset-*` properties are writing-direction aware. ℹ️ The following example is using external library [Floating UI]. To use Floating UI, install it first: ```shell npm install --save @floating-ui/react-dom ``` And import it along with Popover, e.g.: ```js import FloatingUIReactDOM from '@floating-ui/react-dom'; import { Popover } from '@react-ui-org/react-ui'; ``` As opposed to the [basic setup](#placement), Popover will be placed according to its triggering component (`reference`), but still recognizing the closest parent element with `position: relative` or `position: absolute` if there is any. Popover reacts on the `ref` option, necessary for advanced positioning: when `ref` is set, Popover resets its built-in positioning and relies on provided `style`. 👉 Please consult [Floating UI] documentation to understand how it works and to get an idea of all possible cases you may need to cover. 🖱 Try scrolling the example to see how Popover placement is updated. ```docoff-react-preview React.createElement(() => { const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); const [placement, setPlacement] = React.useState('top'); const { x, y, reference, floating, placement: finalPlacement, strategy, } = FloatingUIReactDOM.useFloating({ placement, middleware: [FloatingUIReactDOM.flip()], whileElementsMounted: FloatingUIReactDOM.autoUpdate, }); const placementOptions = [ 'top', 'top-start', 'top-end', 'right', 'right-start', 'right-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', ]; // All inline styles in this example EXCEPT Popover `style` are for // demonstration purposes only. return ( <> <Toolbar> <ToolbarItem> <SelectField label="Suggested placement:" onChange={e => setPlacement(e.target.value)} options={placementOptions.map((el) => ({ label: el, value: el, }))} value={placement} /> </ToolbarItem> <ToolbarItem> <div className="mb-2">Final placement:</div> <code>{finalPlacement}</code> </ToolbarItem> </Toolbar> <div style={{ width: '40rem', maxWidth: '100%', height: '10rem', overflow: 'auto', }} > <div style={{ position: 'relative', width: '60rem', height: '20rem', paddingBlock: '7rem', textAlign: 'center', }} > <Button aria-describedby={isPopoverOpen ? 'my-advanced-popover' : undefined} label="Trigger Popover" onClick={() => setIsPopoverOpen(!isPopoverOpen)} ref={reference} /> {isPopoverOpen && ( <Popover id="my-advanced-popover" placement={finalPlacement} placementStyle={{ position: strategy, top: `${y}px`, left: `${x}px`, }} ref={floating} > Auto-flipping Popover </Popover> )} </div> </div> </> ); }); ``` ## Controlled Popover Popover API can be used to control visibility of Popover component. You need to set `id` on the trigger element and matching `popoverTargetId` attribute on the Popover component. This leverages the browser's Popover API to control the popover, automatically closing it when the trigger or the backdrop is pressed. ```docoff-react-preview React.createElement(() => { // All inline styles in this example are for demonstration purposes only. return ( <div style={{ display: 'grid', placeContent: 'center', minWidth: '20rem', minHeight: '10rem', }} > <PopoverWrapper> <Button label="Want to see a popover? Click me!" popovertarget="my-popover-helper" /> <Popover id="my-popover" popoverTargetId="my-popover-helper"> Hello there! </Popover> </PopoverWrapper> </div> ); }); ``` ## Forwarding HTML Attributes In addition to the options below in the [component's API](#api) section, you can specify **any HTML attribute you like.** All attributes that don't interfere with the API of the React component and that aren't filtered out by [`transferProps`](/docs/js-helpers/transferProps) helper are forwarded to the root `<div>` HTML element. This enables making the component interactive and helps to improve its accessibility. 👉 For the full list of supported attributes refer to: - [`<div>` HTML element attributes][div-attributes]{:target="_blank"} - [React common props]{:target="_blank"} ## Forwarding ref If you provide [ref], it is forwarded to the root native HTML `<div>` element, which enables [Advanced Positioning](#advanced-positioning). ## API <docoff-react-props src="/components/Popover/Popover.jsx"></docoff-react-props> ### PopoverWrapper API <docoff-react-props src="/components/Popover/PopoverWrapper.jsx"></docoff-react-props> ## Theming | Custom Property | Description | |------------------------------------------------------|--------------------------------------------------------------| | `--rui-Popover__width` | Popover width | | `--rui-Popover__padding` | Popover padding | | `--rui-Popover__border-width` | Border width | | `--rui-Popover__border-color` | Border color | | `--rui-Popover__border-radius` | Corner radius | | `--rui-Popover__color` | Text color | | `--rui-Popover__background-color` | Background color | | `--rui-Popover__box-shadow` | Popover box shadow | [div-attributes]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes [Floating UI]: https://floating-ui.com/docs/react-dom [mdn-inset]: https://developer.mozilla.org/en-US/docs/Web/CSS/inset [React common props]: https://react.dev/reference/react-dom/components/common#common-props [ref]: https://reactjs.org/docs/refs-and-the-dom.html