Skip to content

Commit

Permalink
Composite: stabilize new ariakit implementation (#63564)
Browse files Browse the repository at this point in the history
* Point legacy exports directly to the source (instead of folder root)

* Swap default folder export to new version

* Apply compound component naming

* Export new version from the package

* Update (fix) private APIs exports

* Update composite implementation to use new compound naming

* Update references to Composite inside components package

* Update Storybook entry points for both legacy and current

* Fix Storybook generated docs

* Add todo

* Remove unncecessary code

* CHANGELOG

* README

* Add JSDocs to Composite exports

* Move current implementation out of `current` folder

* Fix import in the legacy implementation

* Update docs manifest

* Fix type in Storybook example

* Add JSDocs for Storybook docs

* Apply Overloaded naming convention

* Update README

* Fix typo

* Update legacy storybook title/id, make sure JSDocs refer to unstable version

* Derive types instead of importing them directly from ariakit

* Add JSDoc snippet for stable component

* Remove unnecessary JSDoc code

* Remove unnecessary display name

* Assign display names via Object.assign to comply with TS and get correct results in Storybook

* Update subcomponent TS ignore comment to align with other components

* Remove unnecessary store prop in circular option picker

Composite.Item should pick up the store from context without explicit prop

* Add first-party types, rewrite components with one unique forwardRef call

* Use the newly added types instead of using the Parameters<> util

* Fix Storybook story type

* Remove unnecessary ts-expect-error

* Use `CompositeStore` type directly

* Manual Storybook args table

* Tweak display name fallback

* README

* Mark `store` prop on `Composite` as required

---

Co-authored-by: ciampo <mciampini@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: mirka <0mirka00@git.wordpress.org>
  • Loading branch information
4 people authored and getdave committed Aug 14, 2024
1 parent 7d1deb7 commit 8b75823
Show file tree
Hide file tree
Showing 22 changed files with 700 additions and 161 deletions.
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,12 @@
"markdown_source": "../packages/components/src/combobox-control/README.md",
"parent": "components"
},
{
"title": "Composite",
"slug": "composite",
"markdown_source": "../packages/components/src/composite/README.md",
"parent": "components"
},
{
"title": "ConfirmDialog",
"slug": "confirm-dialog",
Expand Down
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Features

- `Composite`: add stable version of the component ([#63564](https://github.com/WordPress/gutenberg/pull/63564)).

### Enhancements

- `TimePicker`: add `hideLabelFromVision` prop ([#64267](https://github.com/WordPress/gutenberg/pull/64267)).
Expand Down
6 changes: 3 additions & 3 deletions packages/components/src/alignment-matrix-control/cell.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Internal dependencies
*/
import { CompositeItem } from '../composite/v2';
import { Composite } from '../composite';
import Tooltip from '../tooltip';
import { VisuallyHidden } from '../visually-hidden';

Expand All @@ -26,7 +26,7 @@ export default function Cell( {

return (
<Tooltip text={ tooltipText }>
<CompositeItem
<Composite.Item
id={ id }
render={ <CellView { ...props } role="gridcell" /> }
>
Expand All @@ -35,7 +35,7 @@ export default function Cell( {
hidden element instead of aria-label. */ }
<VisuallyHidden>{ value }</VisuallyHidden>
<Point isActive={ isActive } role="presentation" />
</CompositeItem>
</Composite.Item>
</Tooltip>
);
}
6 changes: 3 additions & 3 deletions packages/components/src/alignment-matrix-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useInstanceId } from '@wordpress/compose';
* Internal dependencies
*/
import Cell from './cell';
import { Composite, CompositeRow, useCompositeStore } from '../composite/v2';
import { Composite, useCompositeStore } from '../composite';
import { Root, Row } from './styles/alignment-matrix-control-styles';
import AlignmentMatrixControlIcon from './icon';
import { GRID, getItemId, getItemValue } from './utils';
Expand Down Expand Up @@ -87,7 +87,7 @@ export function AlignmentMatrixControl( {
}
>
{ GRID.map( ( cells, index ) => (
<CompositeRow render={ <Row role="row" /> } key={ index }>
<Composite.Row render={ <Row role="row" /> } key={ index }>
{ cells.map( ( cell ) => {
const cellId = getItemId( baseId, cell );
const isActive = cellId === activeId;
Expand All @@ -101,7 +101,7 @@ export function AlignmentMatrixControl( {
/>
);
} ) }
</CompositeRow>
</Composite.Row>
) ) }
</Composite>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { Icon, check } from '@wordpress/icons';
*/
import { CircularOptionPickerContext } from './circular-option-picker-context';
import Button from '../button';
import { CompositeItem } from '../composite/v2';
import { Composite } from '../composite';
import Tooltip from '../tooltip';
import type { OptionProps, CircularOptionPickerCompositeStore } from './types';
import type { OptionProps } from './types';

function UnforwardedOptionAsButton(
props: {
Expand All @@ -45,7 +45,9 @@ function UnforwardedOptionAsOption(
id: string;
className?: string;
isSelected?: boolean;
compositeStore: CircularOptionPickerCompositeStore;
compositeStore: NonNullable<
React.ComponentProps< typeof Composite >[ 'store' ]
>;
},
forwardedRef: ForwardedRef< any >
) {
Expand All @@ -57,7 +59,7 @@ function UnforwardedOptionAsOption(
}

return (
<CompositeItem
<Composite.Item
render={
<Button
{ ...additionalProps }
Expand All @@ -66,7 +68,6 @@ function UnforwardedOptionAsOption(
ref={ forwardedRef }
/>
}
store={ compositeStore }
id={ id }
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { isRTL } from '@wordpress/i18n';
* Internal dependencies
*/
import { CircularOptionPickerContext } from './circular-option-picker-context';
import { Composite, useCompositeStore } from '../composite/v2';
import { Composite, useCompositeStore } from '../composite';
import type {
CircularOptionPickerProps,
ListboxCircularOptionPickerProps,
Expand Down
5 changes: 2 additions & 3 deletions packages/components/src/circular-option-picker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { Icon } from '@wordpress/icons';
import type { ButtonAsButtonProps } from '../button/types';
import type { DropdownProps } from '../dropdown/types';
import type { WordPressComponentProps } from '../context';
import type { CompositeStore } from '../composite/v2';
import type { Composite } from '../composite';

type CommonCircularOptionPickerProps = {
/**
Expand Down Expand Up @@ -123,8 +123,7 @@ export type OptionProps = Omit<
>;
};

export type CircularOptionPickerCompositeStore = CompositeStore;
export type CircularOptionPickerContextProps = {
baseId?: string;
compositeStore?: CircularOptionPickerCompositeStore;
compositeStore?: React.ComponentProps< typeof Composite >[ 'store' ];
};
176 changes: 176 additions & 0 deletions packages/components/src/composite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# `Composite`

`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
const store = useCompositeStore();
<Composite store={store}>
<Composite.Group>
<Composite.GroupLabel>Label</Composite.GroupLabel>
<Composite.Item>Item 1</Composite.Item>
<Composite.Item>Item 2</Composite.Item>
</CompositeGroup>
</Composite>
```

## Hooks

### `useCompositeStore`

Creates a composite store.

#### Props

##### `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.

- Required: no

##### `defaultActiveId`: `string | null`

The composite item id that should be active by default when the composite widget is rendered. If `null`, the composite element itself will have focus and users will be able to navigate to it using arrow keys. If `undefined`, the first enabled item will be focused.

- Required: no

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

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.

- 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.

- 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.

- 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.

- 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.

- Required: no
- 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.

- Required: no
- Default: `false`

## Components

### `Composite`

Renders a composite widget.

#### Props

##### `store`: `CompositeStore<CompositeStoreItem>`

Object returned by the `useCompositeStore` hook.

- Required: yes

##### `render`: `RenderProp<React.HTMLAttributes<any> & { ref?: React.Ref<any> | undefined; }> | React.ReactElement<any, string | React.JSXElementConstructor<any>>`

Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.

- Required: no

##### `children`: `React.ReactNode`

The contents of the component.

- Required: no

### `Composite.Group`

Renders a group element for composite items.

##### `render`: `RenderProp<React.HTMLAttributes<any> & { ref?: React.Ref<any> | undefined; }> | React.ReactElement<any, string | React.JSXElementConstructor<any>>`

Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.

- Required: no

##### `children`: `React.ReactNode`

The contents of the component.

- Required: no

### `Composite.GroupLabel`

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.

##### `render`: `RenderProp<React.HTMLAttributes<any> & { ref?: React.Ref<any> | undefined; }> | React.ReactElement<any, string | React.JSXElementConstructor<any>>`

Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.

- Required: no

##### `children`: `React.ReactNode`

The contents of the component.

- Required: no

### `Composite.Item`

Renders a composite item.

##### `render`: `RenderProp<React.HTMLAttributes<any> & { ref?: React.Ref<any> | undefined; }> | React.ReactElement<any, string | React.JSXElementConstructor<any>>`

Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.

- Required: no

##### `children`: `React.ReactNode`

The contents of the component.

- Required: no

### `Composite.Row`

Renders a composite row. Wrapping `Composite.Item` elements within `Composite.Row` will create a two-dimensional composite widget, such as a grid.

##### `render`: `RenderProp<React.HTMLAttributes<any> & { ref?: React.Ref<any> | undefined; }> | React.ReactElement<any, string | React.JSXElementConstructor<any>>`

Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged.

- Required: no

##### `children`: `React.ReactNode`

The contents of the component.

- Required: no
20 changes: 0 additions & 20 deletions packages/components/src/composite/current/index.ts

This file was deleted.

Loading

0 comments on commit 8b75823

Please sign in to comment.