UNPKG

@workday/canvas-kit-docs

Version:

Documentation components of Canvas Kit components

272 lines (187 loc) • 12.3 kB
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 @workday/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" />