Skip to content

Commit

Permalink
Composite: improve Storybook examples and clean up prop docs (#64397)
Browse files Browse the repository at this point in the history
* More clear tranform function comments

* Add interactive controls

* Add simpler default Storybook example

* Improve `activeId`'s prop description

* Add Groups example

* CHANGELOG

* Removed actions config in Storybook

* Better composite description

* Remove direct references to Ariakit's docs in JSDocs and README

* Add import from `@wordpress/components` in all code snippets

* useCompositeStore: update prop docs by using first-party docs

Instead of using Ariakit's definitions and descriptions, we use our own
version, which a copy or Ariakit's without any references to Ariakit, its
examples, or any other props that we don't expose.

* useCompositeStore: set explicit default values

Along the same fashion as the previous commit, setting explicit default
values will give us more control when propagating ariakit changes, and
will allows to stay true to our first-part props docs.

* Remove unnecessary space

* Provide first-party prop descriptions also for other composite components

* Add default value for store props to avoid errors while destructuring

---

Co-authored-by: ciampo <[email protected]>
Co-authored-by: mirka <[email protected]>
Co-authored-by: tyxla <[email protected]>
  • Loading branch information
4 people authored Aug 9, 2024
1 parent 266361a commit b4ba75f
Show file tree
Hide file tree
Showing 6 changed files with 384 additions and 74 deletions.
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

0 comments on commit b4ba75f

Please sign in to comment.