-
Notifications
You must be signed in to change notification settings - Fork 587
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1084 from primer/overlay
Overlay
- Loading branch information
Showing
21 changed files
with
958 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
--- | ||
title: Overlay | ||
--- | ||
|
||
An `Overlay` is a flexible floating surface, used to display transient content such as menus, selection options, dialogs, and more. Overlays use shadows to express elevation. The `Overlay` component handles all behaviors needed by overlay UIs as well as the common styles that all overlays should have. `Overlay` is the base component for many of our overlay-type components. | ||
|
||
Behaviors include: | ||
|
||
- Rendering the overlay in a React Portal so that it always renders on top of other content on the page | ||
- Positioning the overlay according to passed in settings, using our context-aware positioning algorithms | ||
- Trapping focus | ||
- Calling a user provided function when the user presses `Escape` | ||
- Calling a user provided function when the user clicks outside of the container | ||
- Focusing either user provided element, or the first focusable element in the container when it is opened | ||
- Returning focus to an element when container is closed | ||
|
||
## Accessibility considerations | ||
|
||
- The `Overlay` must either have: | ||
- A value set for the `aria-labelledby` attribute that refers to a visible title. | ||
- An `aria-label` attribute | ||
- If the `Overlay` should also have a longer description, use `aria-describedby` | ||
- The `Overlay` component has a `role="dialog"` set on it, if you are using `Overlay` for alerts, you can pass in `role="alertdialog"` instead. Please read the [W3C guidelines](https://www.w3.org/TR/wai-aria-1.1/#alertdialog) to determine which role is best for your use case | ||
- The `Overlay` component has `aria-modal` set to `true` by default and should not be overridden as all `Overlay`s behave as modals. | ||
|
||
See the W3C accessibility recommendations for modals [here](https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_roles_states_props). | ||
|
||
## Default example | ||
|
||
```javascript live noinline | ||
const Demo = () => { | ||
// you must manage your own open state | ||
const [isOpen, setIsOpen] = React.useState(false) | ||
const noButtonRef = React.useRef(null) | ||
const anchorRef = React.useRef(null) | ||
return ( | ||
<> | ||
<Button ref={anchorRef} onClick={() => setIsOpen(!isOpen)}> | ||
open overlay | ||
</Button> | ||
{/* be sure to conditionally render the Overlay. This helps with performance and is required. */} | ||
{isOpen && | ||
<Overlay | ||
anchorRef={anchorRef} | ||
initialFocusRef={noButtonRef} | ||
returnFocusRef={anchorRef} | ||
ignoreClickRefs={[anchorRef]} | ||
onEscape={() => setIsOpen(!isOpen)} | ||
onClickOutside={() => setIsOpen(false)} | ||
aria-labelledby="title" | ||
> | ||
<Flex flexDirection="column" p={2}> | ||
<Text id="title">Are you sure you would like to delete this item?</Text> | ||
<Button >yes</Button> | ||
<Button ref={noButtonRef}>no</Button> | ||
</Flex> | ||
</Overlay> | ||
} | ||
|
||
</> | ||
) | ||
} | ||
|
||
render(<Demo/>) | ||
``` | ||
## System props | ||
`Overlay` gets `COMMON` system props. Read the [System Props](/system-props) doc page for a full list of available props. | ||
## Component props | ||
| Name | Type | Default | Description | | ||
| :--- | :----- | :-----: | :---------------------------------- | | ||
| positionSettings | See the [`PositionSettings interface`]() section of the `anchoredPosition` docs | `{side: 'outside-bottom', align: 'start', anchorOffset: 4, alignmentOffset: 4, allowOutOfBounds: false }` | Optional. Settings used to position the `Overlay`. If none are provided, `Overlay` is positioned on the bottom left of the `anchorRef`. | | ||
| positionDeps | `React.DependencyList` | `undefined` | Optional. If defined, the position of the `Overlay` will only be recalulated when one of the dependencies in this array changes. | | ||
| ignoreClickRefs | `React.RefObject<HTMLElement> []` | `undefined` | Optional. An array of ref objects to ignore clicks on in the `onOutsideClick` behavior. This is often used to ignore clicking on the element that toggles the open/closed state for the `Overlay` to prevent the `Overlay` from being toggled twice. | | ||
| initialFocusRef | `React.RefObject<HTMLElement>` | `undefined` | Optional. Ref for the element to focus when the `Overlay` is opened. If nothing is provided, the first focusable element in the `Overlay` body is focused. | | ||
| anchorRef | `React.RefObject<HTMLElement>` | `undefined` | Required. Element the `Overlay` should be anchored to. | | ||
| returnFocusRef | `React.RefObject<HTMLElement>` | `undefined` | Required. Ref for the element to focus when the `Overlay` is closed. | | ||
| onClickOutside | `function` | `undefined` | Required. Function to call when clicking outside of the `Overlay`. Typically this function sets the `Overlay` visibility state to `false`. | | ||
| onEscape | `function` | `undefined` | Required. Function to call when user presses `Escape`. Typically this function sets the `Overlay` visibility state to `false`. | | ||
| width | `'sm', 'md', 'lg', 'xl', 'auto'` | `auto` | Sets the width of the `Overlay`, pick from our set list of widths, or pass `auto` to automatically set the width based on the content of the `Overlay`. `sm` corresponds to `256px`, `md` corresponds to `320px`, `lg` corresponds to `480px`, and `xl` corresponds to `640px`. | | ||
| height | `'sm', 'md', 'auto'` | `auto` | Sets the height of the `Overlay`, pick from our set list of heights, or pass `auto` to automatically set the height based on the content of the `Overlay`. `sm` corresponds to `480px` and `md` corresponds to `640px`. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
--- | ||
title: useOnEscapePress | ||
--- | ||
|
||
`useOnEscapePress` is a simple utility Hook that calls a user provided function when the `Escape` key is pressed. | ||
|
||
### Usage | ||
|
||
```javascript live noinline | ||
const OverlayDemo = ({onEscape, children}) => { | ||
useOnEscapePress({onEscape}) | ||
return ( | ||
<Box height="200px"> | ||
{children} | ||
</Box> | ||
) | ||
} | ||
|
||
function DemoComponent() { | ||
const [isOpen, setIsOpen] = React.useState(false) | ||
return ( | ||
<> | ||
<Button onClick={() => setIsOpen(!isOpen)}>toggle</Button> | ||
{isOpen && | ||
<OverlayDemo onEscape={() => setIsOpen(false)}> | ||
<Button>Button One</Button> | ||
<Button>Button Two</Button> | ||
</OverlayDemo>} | ||
</> | ||
) | ||
} | ||
|
||
render(<DemoComponent/>) | ||
``` | ||
|
||
|
||
#### useOnEscapePress settings | ||
|
||
| Name | Type | Default | Description | | ||
| :- | :- | :-: | :- | | ||
| onEscape | `function` | | Function to call when user presses the Escape key | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
--- | ||
title: useOnOutsideClick | ||
--- | ||
|
||
`useOnOutsideClick` is a utility Hook that calls a user provided callback function when the user clicks outside of the provided container. | ||
|
||
You can also pass an array of `ignoredRefs` to prevent calling the callback function on additional elements on the page. This can be handy for ignoring clicks on trigger buttons that already manage the open/closed state of content. | ||
|
||
|
||
### Usage | ||
|
||
```jsx live | ||
<State> | ||
{([isOpen, setIsOpen]) => { | ||
const containerRef = React.useRef(null) | ||
const triggerRef = React.useRef(null) | ||
|
||
const closeOverlay = React.useCallback(() => { | ||
setIsOpen(false) | ||
}, [setIsOpen]) | ||
|
||
const toggleOverlay = React.useCallback(() => { | ||
setIsOpen(!isOpen) | ||
}, [setIsOpen, isOpen]) | ||
|
||
useOnOutsideClick({onClickOutside: closeOverlay, containerRef, ignoreClickRefs: [triggerRef]}) | ||
|
||
return ( | ||
<> | ||
<Button ref={triggerRef} onClick={toggleOverlay}>toggle</Button> | ||
{isOpen && | ||
<BorderBox height="200px" bg="green.4" ref={containerRef}> | ||
content | ||
</BorderBox> | ||
} | ||
</> | ||
) | ||
}} | ||
</State> | ||
``` | ||
|
||
|
||
#### useOnOutsideClick settings | ||
|
||
| Name | Type | Default | Description | | ||
| :- | :- | :-: | :- | | ||
| onOutsideClick | `function` | | Function to call when user clicks outside of the container. Usually this manages the state of the visibilitiy of the container. | | ||
| ignoredRefs| `React.RefObject<HTMLElement> []` | | Elements outside of the container to ignore clicks on. | | ||
| containerRef | `React.RefObject<HTMLElement>` | | Required. A ref for the containing element. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
--- | ||
title: useOpenAndCloseFocus | ||
--- | ||
|
||
`useOpenAndCloseFocus` is a utility Hook that manages focusing an element when a component is first mounted, and returns focus to an element on the page when that component unmounts. | ||
|
||
If no ref is passed to `inititalFocusRef` , the hook focuses the first focusable element inside of the container. | ||
|
||
|
||
### Usage | ||
|
||
```javascript live noinline | ||
const Overlay = ({returnFocusRef, initialFocusRef, children}) => { | ||
const containerRef = React.useRef(null) | ||
useOpenAndCloseFocus({containerRef, returnFocusRef, initialFocusRef}) | ||
return ( | ||
<Box height="200px" ref={containerRef}> | ||
{children} | ||
</Box> | ||
) | ||
} | ||
|
||
function Component() { | ||
const returnFocusRef = React.useRef(null) | ||
const initialFocusRef = React.useRef(null) | ||
const [isOpen, setIsOpen] = React.useState(false) | ||
return ( | ||
<Box sx={{'*': { ':focus' : { backgroundColor: 'red.5'}}}}> | ||
<Button ref={returnFocusRef} onClick={() => setIsOpen(!isOpen)}>toggle</Button> | ||
{isOpen && | ||
<Overlay returnFocusRef={returnFocusRef} initialFocusRef={initialFocusRef}> | ||
<Button>Button One</Button> | ||
<Button ref={initialFocusRef}>Button Two</Button> | ||
</Overlay>} | ||
</Box> | ||
) | ||
} | ||
|
||
render(<Component/>) | ||
``` | ||
|
||
|
||
#### useOpenAndCloseFocus settings | ||
|
||
| Name | Type | Default | Description | | ||
| :- | :- | :-: | :- | | ||
| initialFocusRef | `React.RefObject<HTMLElement>` | | Optional. The element to focus when the container is mounted on the page. | | ||
| returnFocusRef | `React.RefObject<HTMLElement>` | | Required. The element to focus when the container is unmounted. | | ||
| containerRef | `React.RefObject<HTMLElement>` | | Required. A ref for the containing element. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
--- | ||
title: useOverlay | ||
--- | ||
|
||
`useOverlay` calls all of the relevant behavior Hooks that all `Overlay` components & composite components should have and returns a ref to be passed down to the overlay's container. | ||
|
||
These behaviors include: | ||
|
||
- Correctly positioning the component based on the values provided to `positionSettings` and `positionDeps`. | ||
- Trapping focus | ||
- Calling a user provided function when the user presses `Escape` | ||
- Calling a user provided function when the user clicks outside of the container | ||
- Focusing the either a user provided element, or the first focusable element in the container when it is opened. | ||
- Returning focus to an element when container is closed | ||
|
||
**Note:** `useOverlay` and `Overlay` do not manage the open state of the overlay. We leave control of the open state up to the user. All behaviors are built with the assumption that the overlay will not be rendered on the page & fully unmounted when it is not visible. See the examples for details on how to conditionally render a component in JSX. | ||
|
||
### Usage | ||
|
||
```javascript live noinline | ||
|
||
const DemoOverlay = ({onClickOutside, initialFocusRef, returnFocusRef, ignoreClickRefs, onEscape, ...rest}) => { | ||
const overlayProps = useOverlay({returnFocusRef, onEscape, ignoreClickRefs, onClickOutside, initialFocusRef}) | ||
return <Box height="200px" bg="green.4" {...overlayProps} {...rest}/> | ||
} | ||
|
||
const DemoComponent = () => { | ||
const returnFocusRef = React.useRef(null) | ||
const initialFocusRef = React.useRef(null) | ||
const [isOpen, setIsOpen] = React.useState(false) | ||
const closeOverlay = () => setIsOpen(false) | ||
return ( | ||
<> | ||
<Button ref={returnFocusRef} onClick={() => setIsOpen(!isOpen)}>toggle</Button> | ||
{isOpen && | ||
<DemoOverlay | ||
returnFocusRef={returnFocusRef} | ||
ignoreClickRefs={[returnFocusRef]} | ||
initialFocusRef={initialFocusRef} | ||
onEscape={closeOverlay} | ||
onClickOutside={closeOverlay} | ||
> | ||
<Button>Button One</Button> | ||
<Button ref={initialFocusRef}>Button Two</Button> | ||
</DemoOverlay>} | ||
</> | ||
) | ||
} | ||
|
||
render(<DemoComponent/>) | ||
``` | ||
|
||
|
||
#### useOnEscapePress settings | ||
|
||
| Name | Type | Required | Description | | ||
| :- | :- | :-: | :- | | ||
| onEscapePress | `function` | required | Function to call when user presses the Escape key | | ||
| onOutsideClick | `function` | required | Function to call when user clicks outside of the overlay | | ||
| ignoreClickRefs | `React.RefObject<HTMLElement> []` | optional | Refs to click clicks on in the `onOutsideClick` function, useful for ignoring clicks on elements that trigger the overlay visibility. | | ||
| initialFocusRef | `React.RefObject<HTMLElement>` | optional | Ref to focus when overlay is mounted. | | ||
| returnFocusRef | `React.RefObject<HTMLElement>` | required | Ref to focus when overlay is unmounted. Important for accessibility. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.