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: stabilize new ariakit implementation #63564

Merged
merged 39 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c8567d7
Point legacy exports directly to the source (instead of folder root)
ciampo Jul 15, 2024
9f65aaf
Swap default folder export to new version
ciampo Jul 15, 2024
89f623c
Apply compound component naming
ciampo Jul 15, 2024
0e0a729
Export new version from the package
ciampo Jul 15, 2024
bf8cf39
Update (fix) private APIs exports
ciampo Jul 15, 2024
951b7c8
Update composite implementation to use new compound naming
ciampo Jul 15, 2024
0da1b62
Update references to Composite inside components package
ciampo Jul 15, 2024
e737a18
Update Storybook entry points for both legacy and current
ciampo Jul 15, 2024
5c65788
Fix Storybook generated docs
ciampo Jul 15, 2024
051fb4a
Add todo
ciampo Jul 15, 2024
45f8dcb
Remove unncecessary code
ciampo Jul 15, 2024
8d57789
CHANGELOG
ciampo Jul 15, 2024
d706c5e
README
ciampo Jul 15, 2024
0a68587
Add JSDocs to Composite exports
ciampo Jul 15, 2024
1acf18d
Move current implementation out of `current` folder
ciampo Jul 15, 2024
a5b0ae4
Fix import in the legacy implementation
ciampo Jul 15, 2024
f648f69
Update docs manifest
ciampo Jul 15, 2024
deab309
Fix type in Storybook example
ciampo Jul 15, 2024
b9246dc
Add JSDocs for Storybook docs
ciampo Jul 15, 2024
2bd8e5e
Apply Overloaded naming convention
ciampo Jul 31, 2024
0df3c5a
Update README
ciampo Jul 31, 2024
1ef744c
Fix typo
ciampo Jul 31, 2024
cc11ab4
Update legacy storybook title/id, make sure JSDocs refer to unstable …
ciampo Aug 8, 2024
ae1f17f
Derive types instead of importing them directly from ariakit
ciampo Aug 8, 2024
e1ba58d
Add JSDoc snippet for stable component
ciampo Aug 8, 2024
ea4461b
Remove unnecessary JSDoc code
ciampo Aug 8, 2024
f8d6d18
Remove unnecessary display name
ciampo Aug 8, 2024
70bb89c
Assign display names via Object.assign to comply with TS and get corr…
ciampo Aug 8, 2024
d91a679
Update subcomponent TS ignore comment to align with other components
ciampo Aug 8, 2024
68650df
Remove unnecessary store prop in circular option picker
ciampo Aug 8, 2024
0b4cb3e
Add first-party types, rewrite components with one unique forwardRef …
ciampo Aug 8, 2024
9bafd47
Use the newly added types instead of using the Parameters<> util
ciampo Aug 8, 2024
f511dc3
Fix Storybook story type
ciampo Aug 8, 2024
09c07af
Remove unnecessary ts-expect-error
ciampo Aug 8, 2024
db9d27d
Use `CompositeStore` type directly
ciampo Aug 8, 2024
918ae6a
Manual Storybook args table
ciampo Aug 8, 2024
7586d54
Tweak display name fallback
ciampo Aug 9, 2024
176314a
README
ciampo Aug 9, 2024
c41ff65
Mark `store` prop on `Composite` as required
ciampo Aug 9, 2024
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
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 }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed because in theory Composite.Item should inherit the store from its parent Composite. The only scenario in which it would break, is if the option and its parent component are rendered across a slot / fill.

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
ciampo marked this conversation as resolved.
Show resolved Hide resolved
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).
ciampo marked this conversation as resolved.
Show resolved Hide resolved

## 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
Loading