Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composite: improve Storybook examples and clean up prop docs #64397

Merged
merged 15 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### Enhancements

- `Composite`: improve Storybook examples and add interactive controls ([#64397](https://github.com/WordPress/gutenberg/pull/64397)).
- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).

## 28.5.0 (2024-08-07)
Expand Down
62 changes: 52 additions & 10 deletions packages/components/src/composite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

`Composite` provides a single tab stop on the page and allows navigation through the focusable descendants with arrow keys. This abstract component is based on the [WAI-ARIA Composite Role⁠](https://w3c.github.io/aria/#composite).

See the [Ariakit docs for the `Composite` component](https://ariakit.org/components/composite).

## Usage

```jsx
import { Composite, useCompositeStore } from '@wordpress/components';

const store = useCompositeStore();
<Composite store={store}>
<Composite.Group>
Expand All @@ -27,7 +27,10 @@ Creates a composite store.

##### `activeId`: `string | null`

The current active item id. The active item is the element within the composite widget that has either DOM or virtual focus.
The current active item `id`. The active item is the element within the composite widget that has either DOM or virtual focus (in case the `virtualFocus` prop is enabled).

- `null` represents the base composite element (the one with a [composite role](https://w3c.github.io/aria/#composite)). Users will be able to navigate out of it using arrow keys.
- If `activeId` is initially set to `null`, the base composite element itself will have focus and users will be able to navigate to it using arrow keys.

- Required: no

Expand All @@ -39,48 +42,87 @@ The composite item id that should be active by default when the composite widget

##### `setActiveId`: `((activeId: string | null | undefined) => void)`

A callback that gets called when the activeId state changes.
A callback that gets called when the `activeId` state changes.

- Required: no

##### `focusLoop`: `boolean | 'horizontal' | 'vertical' | 'both'`

Determines how the focus behaves when the user reaches the end of the composite widget.

On one-dimensional composite widgets:

- `true` loops from the last item to the first item and vice-versa.
- `horizontal` loops only if `orientation` is `horizontal` or not set.
- `vertical` loops only if `orientation` is `vertical` or not set.
- If `activeId` is initially set to `null`, the composite element will be focused in between the last and first items.

On two-dimensional composite widgets (ie. when using `CompositeRow`):

- `true` loops from the last row/column item to the first item in the same row/column and vice-versa. If it's the last item in the last row, it moves to the first item in the first row and vice-versa.
- `horizontal` loops only from the last row item to the first item in the same row.
- `vertical` loops only from the last column item to the first item in the column row.
- If `activeId` is initially set to `null`, vertical loop will have no effect as moving down from the last row or up from the first row will focus on the composite element.
- If `focusWrap` matches the value of `focusLoop`, it'll wrap between the last item in the last row or column and the first item in the first row or column and vice-versa.

- Required: no
- Default: `false`

##### `focusShift`: `boolean`

Works only on two-dimensional composite widgets. If enabled, moving up or down when there's no next item or when the next item is disabled will shift to the item right before it.
**Works only on two-dimensional composite widgets**.

If enabled, moving up or down when there's no next item or when the next item is disabled will shift to the item right before it.

- Required: no
- Default: `false`

##### `focusWrap`: `boolean`

Works only on two-dimensional composite widgets. If enabled, moving to the next item from the last one in a row or column will focus on the first item in the next row or column and vice-versa.
**Works only on two-dimensional composite widgets**.

If enabled, moving to the next item from the last one in a row or column
will focus on the first item in the next row or column and vice-versa.

- `true` wraps between rows and columns.
- `horizontal` wraps only between rows.
- `vertical` wraps only between columns.
- If `focusLoop` matches the value of `focusWrap`, it'll wrap between the
last item in the last row or column and the first item in the first row or
column and vice-versa.

- Required: no
- Default: `false`

##### `virtualFocus`: `boolean`

If enabled, the composite element will act as an aria-activedescendant⁠ container instead of roving tabindex⁠. DOM focus will remain on the composite element while its items receive virtual focus. In both scenarios, the item in focus will carry the data-active-item attribute.
If enabled, the composite element will act as an [`aria-activedescendant`](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_activedescendant)
container instead of [roving tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex). DOM focus will remain on the composite element while its items receive
virtual focus.

In both scenarios, the item in focus will carry the `data-active-item` attribute.

- Required: no
- Default: `false`

##### `orientation`: `'horizontal' | 'vertical' | 'both'`

Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the orientation value determines which arrow keys can be used to move focus. It doesn't have any effect on two-dimensional composites.
Defines the orientation of the composite widget. If the composite has a single row or column (one-dimensional), the `orientation` value determines which arrow keys can be used to move focus:

- `both`: all arrow keys work.
- `horizontal`: only left and right arrow keys work.
- `vertical`: only up and down arrow keys work.

It doesn't have any effect on two-dimensional composites.

- Required: no
- Default: `'both'`
- Default: `both`

##### `rtl`: `boolean`

Determines how the next and previous functions will behave. If rtl is set to true, they will be inverted. This only affects the composite widget behavior. You still need to set dir=`rtl` on HTML/CSS.
Determines how the `store`'s `next` and `previous` functions will behave. If `rtl` is set to `true`, they will be inverted.

This only affects the composite widget behavior. You still need to set `dir="rtl"` on HTML/CSS.

- Required: no
- Default: `false`
Expand Down
49 changes: 39 additions & 10 deletions packages/components/src/composite/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ import type {

/**
* Creates a composite store.
* @param props
* @see https://ariakit.org/reference/use-composite-store
*
* @example
* ```jsx
* import { Composite, useCompositeStore } from '@wordpress/components';
*
* const store = useCompositeStore();
* <Composite store={store}>
* <Composite.Item>Item</Composite.Item>
Expand All @@ -45,8 +46,24 @@ import type {
* </Composite>
* ```
*/
export function useCompositeStore( props: CompositeStoreProps ) {
return Ariakit.useCompositeStore( props );
export function useCompositeStore( {
focusLoop = false,
focusWrap = false,
focusShift = false,
virtualFocus = false,
orientation = 'both',
rtl = false,
...props
}: CompositeStoreProps = {} ) {
return Ariakit.useCompositeStore( {
focusLoop,
focusWrap,
focusShift,
virtualFocus,
orientation,
rtl,
...props,
} );
}

const Group = forwardRef<
Expand Down Expand Up @@ -82,10 +99,14 @@ const Row = forwardRef<
Row.displayName = 'Composite.Row';

/**
* Renders a composite widget.
* @see https://ariakit.org/reference/composite
* Renders a widget based on the WAI-ARIA [`composite`](https://w3c.github.io/aria/#composite)
* role, which provides a single tab stop on the page and arrow key navigation
* through the focusable descendants.
*
* @example
* ```jsx
* import { Composite, useCompositeStore } from '@wordpress/components';
*
* const store = useCompositeStore();
* <Composite store={store}>
* <Composite.Item>Item 1</Composite.Item>
Expand All @@ -104,9 +125,11 @@ export const Composite = Object.assign(
displayName: 'Composite',
/**
* Renders a group element for composite items.
* @see https://ariakit.org/reference/composite-group
*
* @example
* ```jsx
* import { Composite, useCompositeStore } from '@wordpress/components';
*
* const store = useCompositeStore();
* <Composite store={store}>
* <Composite.Group>
Expand All @@ -122,9 +145,11 @@ export const Composite = Object.assign(
* Renders a label in a composite group. This component must be wrapped with
* `Composite.Group` so the `aria-labelledby` prop is properly set on the
* composite group element.
* @see https://ariakit.org/reference/composite-group-label
*
* @example
* ```jsx
* import { Composite, useCompositeStore } from '@wordpress/components';
*
* const store = useCompositeStore();
* <Composite store={store}>
* <Composite.Group>
Expand All @@ -138,9 +163,11 @@ export const Composite = Object.assign(
GroupLabel,
/**
* Renders a composite item.
* @see https://ariakit.org/reference/composite-item
*
* @example
* ```jsx
* import { Composite, useCompositeStore } from '@wordpress/components';
*
* const store = useCompositeStore();
* <Composite store={store}>
* <Composite.Item>Item 1</Composite.Item>
Expand All @@ -154,9 +181,11 @@ export const Composite = Object.assign(
* Renders a composite row. Wrapping `Composite.Item` elements within
* `Composite.Row` will create a two-dimensional composite widget, such as a
* grid.
* @see https://ariakit.org/reference/composite-row
*
* @example
* ```jsx
* import { Composite, useCompositeStore } from '@wordpress/components';
*
* const store = useCompositeStore();
* <Composite store={store}>
* <Composite.Row>
Expand Down
Loading
Loading