@workday/canvas-kit-docs
Version:
Documentation components of Canvas Kit components
272 lines (187 loc) • 12.3 kB
text/mdx
import {ExampleCodeBlock, Specifications, SymbolDoc} from '@workday/canvas-kit-docs';
import Basic from './examples/Basic';
import CustomTarget from './examples/CustomTarget';
import ExternalWindow from './examples/ExternalWindow';
import FocusRedirect from './examples/FocusRedirect';
import FocusTrap from './examples/FocusTrap';
import FullScreen from './examples/FullScreen';
import InitialFocus from './examples/InitialFocus';
import MultiplePopups from './examples/MultiplePopups';
import NestedPopups from './examples/NestedPopups';
import RTL from './examples/RTL';
# Canvas Kit Popups
A "popup" is a classification for a type of stacked UI element that appears "on top" of statically
positioned content. Tooltips, Modals, Dropdown menus, etc are all examples of "popups". Canvas Kit
has a "stack manager" system for managing these popups. Different types of popups have different
requirements of behavior for UX and accessibility - we can call them behaviors, capabilities, or
traits. Canvas Kit comes with a number of [behavioral hooks](#hooks) in the form of React Hooks.
You should use the most semantic component for your use-case before using `Popup` directly, like
`Modal`, which already has the correct behaviors built-in. If no component already exists that
matches your use case, you can use `Popup` and use our [hooks](#hooks). The `Popup` component comes
with a `Popup.Popper` subcomponent that positions a popup using [PopperJS](https://popper.js.org/)
that registers a popup with the `PopupStack` automatically and sets the popup model's `placement`
property. `Popup.Popper` component and hooks work with the stack management system for correct
rendering and accessibility behavior. If you cannot use `Popup.Popper`, use the
[usePopupStack](#usepoupstack) hook to properly register and deregister the popup at the correct
time. If you cannot use our hooks, consider upgrading your component to use Hooks. If you cannot do
that, you'll have to look up the `PopupStack` package for the direct API and have a look at the
source code for our hooks into the `PopupStack` API.
This package comes with everything you need to build Popup UIs.
[Buttons](/components/buttons/button)
## Installation
```sh
yarn add /canvas-kit-react
```
## Usage
The `Popup` component is a generic
[Compound Component](/get-started/for-developers/documentation/compound-components/) that is used to
build popup UIs that are not already covered by Canvas Kit.
### Basic Example
The Popup has no pre-defined behaviors built in, therefore the `usePopupModel` must always be used
to create a new `model`. This `model` is then used by all behavior hooks to apply additional popup
behaviors to the compound component group. The following example creates a typical popup around a
target element and adds `useCloseOnOutsideClick`, `useCloseOnEscape`, `useInitialFocus`, and
`useReturnFocus` behaviors. You can read through the [hooks](#hooks) section to learn about all the
popup behaviors. For accessibility, these behaviors should be included most of the time.
<ExampleCodeBlock code={Basic} />
### Initial Focus
If you want focus to move to a specific element when the popup is opened, set the `initialFocusRef`
of the model. This is useful for popups that don't have a Close icon button near the top right of
the popup. In general, we recommend setting focus to the first interactive component inside the
popup that is the least destructive action.
<ExampleCodeBlock code={InitialFocus} />
> **Accessibility Note**: When initial focus lands on a control **below** the title (such as the OK
> button in the example above), assign a unique `id` to supplementary text and pass
> `aria-describedby` on `Popup.Card`. This augments the included `aria-labelledby` reference to
> `Popup.Heading` so screen readers can announce both the heading and any supplementary text
> automatically. When initial focus is on the heading itself, add `tabIndex={-1}` to `Popup.Heading`
> so the title can receive programmatic focus. Choose where focus goes based on your product and
> accessibility requirements.
### Focus Redirect
Focus management is important to accessibility of popup contents. The following example shows
`useFocusRedirect` being used to manage focus in and out of a Popup. This is very useful for
non-modal popups. Focus redirection tries to treat the Popup as if it were inline to the document.
Tabbing out of the Popup will close the Popup and move focus to an adjacent focusable element.
<ExampleCodeBlock code={FocusRedirect} />
> **Accessibility Note**: The `useFocusRedirect` hook **will not** have any effect on the reading
> order of a screen reader. Screen reader users may get confused or disoriented when popups are
> portalled to the bottom of the document body. In this example, we're testing the use of
> `aria-owns` on a sibling `<div>` element pointing to the `Popup.Card` component. This remaps the
> hierarchy of the accessibility tree (in supported browsers) to address the reading order problem.
> For more information, see
> [Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).
### Focus Trapping
Focus trapping is similar to the [Focus Redirect](#focus-redirect) example, but will trap focus
inside the popup instead of redirecting focus to adjacent focusable elements. This is necessary for
modal dialogs where users must focus on the contents of the dialog before proceeding.
<ExampleCodeBlock code={FocusTrap} />
> **Accessibility Note**: Focus trapping will not prevent mouse users from breaking out of a focus
> trap, nor will it prevent screen reader users from using virtual reading cursors from breaking
> out. Consider using [Modal](/components/popups/modal/) instead when you need to focus users'
> attention on a specific task inside of a popup..
### Multiple Popups
You can render more than one `Popup` in the same view by giving each its own model. This example
pairs `Popup` with `useDialogModel` and `useModalModel` so you can compare **focus redirection**
(Tab / Shift + Tab can move focus out of the first popup) and **focus trapping** (focus stays inside
the second popup until it closes). Opening one does not close the other.
<ExampleCodeBlock code={MultiplePopups} />
### Nested Popups
If you need nested Popups within the same component, you can create multiple models and pass a
unique model to each Popup. Popup comes with a `Popup.CloseButton` that uses a `Button` and adds
props via the `usePopupCloseButton` hook to ensure the popups hides and focus is returned. The `as`
can be used in a powerful way to do this by using `<Popup.CloseButton as={Popup.CloseButton}>` which
will mix in click handlers from both popups. This is not very intuitive, however. You can create
props that merge a click handler for both Popups by using `usePopupCloseButton` directly. The second
parameter is props to be merged which will effectively hide both popups. Focus management is
preserved.
<ExampleCodeBlock code={NestedPopups} />
> **Accessibility Note**: In this example, observe how users can traverse both opened popups using
> the keyboard. This is likely to be a confusing experience for users and may necessitate focus
> trapping inside each popup with careful consideration for setting initial focus and returning
> focus.
### Custom Target
It is common to have a custom target for your popup. Use the `as` prop to use your custom component.
The `Popup.Target` element will add `onClick` and `ref` to the provided component. Your provided
target component must forward the `onClick` to an element for the Popup to open. The `as` will cause
`Popup.Target` to inherit the interface of your custom target component. This means any props your
target requires, `Popup.Target` now also requires. The example below has a `MyTarget` component that
requires a `label` prop.
> **Note**: If your application needs to programmatically open a Popup without the user interacting
> with the target button first, you'll also need to use `React.forwardRef` in your target component.
> Without this, the Popup will open at the top-left of the window instead of around the target.
<ExampleCodeBlock code={CustomTarget} />
> **Accessibility Note**: Custom targets must be keyboard focusable, otherwise users will not be
> able to access the popup. Bear in mind that click handlers only work with the keyboard when
> applied to HTML `<button>` elements and it is **strongly recommended** to base your custom target
> on a `<button>` element. Otherwise, you will be required to build in your own custom keyboard
> event handlers for invoking the popup.
### Full Screen API
By default, popups are created as children of the `document.body` element, but the `PopupStack`
supports the [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API). When
fullscreen is entered, the `PopupStack` will automatically create a new stacking context for all
future popups. Any existing popups will disappear, but not be removed. They disappear because the
fullscreen API is only showing content within the fullscreen element. There are instances where a
popup may not close when fullscreen is exited:
- The escape key is used to exit fullscreen
- There is a button to exit fullscreen, but the popup doesn't use `useCloseOnOutsideClick`
If fullscreen is exited, popups within the fullscreen stacking context are not removed or
transferred automatically. If you do not handle this case, the popup may not render correctly. This
example shows a popup that closes when fullscreen is entered/exited and another popup that transfers
the popup's stack context when entering/exiting fullscreen.
<ExampleCodeBlock code={FullScreen} />
### Opening an External Window
A popup can open an external window. This isn't supported directly. The `Popup.Popper` subcomponent
is replaced with a custom subcomponent that connects to the Popup model and controls the lifecycle
of the extenal window. Be sure to connect the `unload` event of both the parent `window` and the
external child `window` to the lifecycle of the Popup model to prevent memory leaks or zombie
windows.
<ExampleCodeBlock code={ExternalWindow} />
### RTL
The Popup component automatically handles right-to-left rendering.
> **Note:** This example shows an inaccessible open card for demonstration purposes.
<ExampleCodeBlock code={RTL} />
## Accessibility
Popup content is usually portaled to the bottom of the `document.body`, which can affect **reading
order for screen readers** and **keyboard focus order**. For more information about Popup
accessibility, check out our documentation at
[Guides > Accessibility > Inline Popups](https://workday.github.io/canvas-kit/?path=/docs/guides-accessibility-inline-popups--docs).
- For non-modal dialogs with `aria-owns` built-in to improve reading order for screen readers (that
support it), check out the [**Dialog**](/components/popups/dialog/) component.
- For modal dialogs with built-in overlays and focus traps, check out the
[**Modal**](/components/popups/modal/) component.
## Component API
<>
<SymbolDoc name="Popper" fileName="/react/" />
<SymbolDoc name="Popup" fileName="/react/" />
</>
## Hooks
<>
<SymbolDoc name="usePopupStack" fileName="/react/" />
{' '}
<SymbolDoc name="useAssistiveHideSiblings" fileName="/react/" />
{' '}
<SymbolDoc name="useBringToTopOnClick" fileName="/react/" />
{' '}
<SymbolDoc name="useCloseOnEscape" fileName="/react/" />
{' '}
<SymbolDoc name="useCloseOnOutsideClick" fileName="/react/" />
{' '}
<SymbolDoc name="useAlwaysCloseOnOutsideClick" fileName="/react/" />
{' '}
<SymbolDoc name="useCloseOnTargetHidden" fileName="/react/" />
{' '}
<SymbolDoc name="useDisableBodyScroll" fileName="/react/" />
{' '}
<SymbolDoc name="useFocusRedirect" fileName="/react/" />
{' '}
<SymbolDoc name="useFocusTrap" fileName="/react/" />
{' '}
<SymbolDoc name="useInitialFocus" fileName="/react/" />
{' '}
<SymbolDoc name="useReturnFocus" fileName="/react/" />
{' '}
<SymbolDoc name="useTransferOnFullscreenEnter" fileName="/react/" />
<SymbolDoc name="useTransferOnFullscreenExit" fileName="/react/" />
</>
## Specifications
<Specifications file="./cypress/component/Popup.spec.tsx" name="Popup" />