Skip to content

Commit 850c6ed

Browse files
mirkaciampo
authored andcommitted
UI: Add item labels and descriptions to remaining popups (WordPress#83493)
* UI: Add item labels and descriptions to remaining popup components * UI: Document popup item contract and focus integration tests * UI: Match item JSDoc to package conventions * Storybook: Update component manifest for popup item labels * UI: Describe item descriptions without fixing their placement * UI: Match popup item prop type declarations * UI: Avoid overriding shared item display names * Storybook: Preserve autocomplete Grid emoji size * UI: Validate item description placement * Fix item popup label and description lint errors * Preserve item component names through forwardRef Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: ciampo <mciampini@git.wordpress.org>
1 parent de164bb commit 850c6ed

44 files changed

Lines changed: 1019 additions & 118 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎packages/ui/CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@
44

55
### Breaking Changes
66

7+
- `Autocomplete.Item`, `Combobox.Item`, `SearchableSelect.Item`, `SearchableChipSelect.Item`, `SearchableSelectControl.Item`, and `SearchableChipSelectControl.Item` now require an `ItemLabel` as their first direct child, followed only by optional `ItemDescription` components. Wrap existing text children in `ItemLabel` ([#83493](https://github.com/WordPress/gutenberg/pull/83493)).
78
- `Notice`: Remove built-in screen reader announcements and the `spokenMessage` and `politeness` props. Consumers now announce dynamic updates explicitly, for example with `speak()` from `@wordpress/a11y` ([#82737](https://github.com/WordPress/gutenberg/pull/82737)).
89

910
### New Features
1011

1112
- Add `SearchableSelectControl` component ([#80979](https://github.com/WordPress/gutenberg/pull/80979)).
1213

14+
### Enhancements
15+
16+
- `Autocomplete`, `Combobox`, `SearchableSelect`, `SearchableChipSelect`, `SearchableSelectControl`, and `SearchableChipSelectControl` add `ItemLabel` and `ItemDescription` subcomponents. Searchable selection items also accept an optional `description` string ([#83493](https://github.com/WordPress/gutenberg/pull/83493)).
17+
1318
### Bug Fixes
1419

1520
- `Select`, `SelectControl`, `Combobox`, `SearchableSelect`, `SearchableChipSelect`, `SearchableChipSelectControl`: Align the selected-item checkmark with the first line of the item label while keeping single-line items vertically centered. ([#82942](https://github.com/WordPress/gutenberg/pull/82942))

‎packages/ui/src/form/primitives/autocomplete/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export { GroupLabel } from './group-label';
66
export { InputGroup } from './input-group';
77
export { Input } from './input';
88
export { Item } from './item';
9+
export { ItemDescription } from './item-description';
10+
export { ItemLabel } from './item-label';
911
export { List } from './list';
1012
export { ListBody } from './list-body';
1113
export { Popup } from './popup';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import clsx from 'clsx';
2+
import { forwardRef } from '@wordpress/element';
3+
import { Text } from '../../../text';
4+
import itemPopupStyles from '../../../utils/css/item-popup.module.css';
5+
import type { AutocompleteItemDescriptionProps } from './types';
6+
7+
const ITEM_DESCRIPTION_DIRECT_CHILD = Symbol();
8+
9+
type InternalItemDescriptionProps = AutocompleteItemDescriptionProps & {
10+
validationToken?: typeof ITEM_DESCRIPTION_DIRECT_CHILD;
11+
};
12+
13+
/**
14+
* Supplementary content for an autocomplete item. Its text contributes to the
15+
* item's accessible description.
16+
*/
17+
const ForwardedItemDescription = forwardRef<
18+
HTMLSpanElement,
19+
AutocompleteItemDescriptionProps
20+
>( function ItemDescription( props, ref ) {
21+
const { className, validationToken, ...restProps } =
22+
props as InternalItemDescriptionProps;
23+
if (
24+
process.env.NODE_ENV !== 'production' &&
25+
validationToken !== ITEM_DESCRIPTION_DIRECT_CHILD
26+
) {
27+
throw new Error(
28+
'Autocomplete.ItemDescription: Missing direct autocomplete item parent. Render <Autocomplete.ItemDescription> as a direct child of <Autocomplete.Item>.'
29+
);
30+
}
31+
32+
return (
33+
<Text
34+
ref={ ref }
35+
variant="body-sm"
36+
className={ clsx(
37+
itemPopupStyles[ 'item-description' ],
38+
className
39+
) }
40+
{ ...restProps }
41+
/>
42+
);
43+
} );
44+
45+
export {
46+
ITEM_DESCRIPTION_DIRECT_CHILD,
47+
ForwardedItemDescription as ItemDescription,
48+
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import clsx from 'clsx';
2+
import { forwardRef } from '@wordpress/element';
3+
import { Text } from '../../../text';
4+
import itemPopupStyles from '../../../utils/css/item-popup.module.css';
5+
import type { AutocompleteItemLabelProps } from './types';
6+
7+
/**
8+
* The primary label and accessible name of an autocomplete item.
9+
*/
10+
const ForwardedItemLabel = forwardRef<
11+
HTMLSpanElement,
12+
AutocompleteItemLabelProps
13+
>( function ItemLabel( { className, ...restProps }, ref ) {
14+
return (
15+
<Text
16+
ref={ ref }
17+
variant="body-md"
18+
className={ clsx( itemPopupStyles[ 'item-label' ], className ) }
19+
{ ...restProps }
20+
/>
21+
);
22+
} );
23+
24+
export { ForwardedItemLabel as ItemLabel };

‎packages/ui/src/form/primitives/autocomplete/item.tsx‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,46 @@
11
import { Autocomplete as _Autocomplete } from '@base-ui/react/autocomplete';
22
import clsx from 'clsx';
33
import { forwardRef } from '@wordpress/element';
4+
import { useItemContent } from '../../../utils/item-popup';
45
import defenseStyles from '../../../utils/css/global-css-defense.module.css';
56
import itemPopupStyles from '../../../utils/css/item-popup.module.css';
67
import resetStyles from '../../../utils/css/resets.module.css';
8+
import {
9+
ITEM_DESCRIPTION_DIRECT_CHILD,
10+
ItemDescription,
11+
} from './item-description';
12+
import { ItemLabel } from './item-label';
713
import type { AutocompleteItemProps } from './types';
814

15+
const ITEM_CONTENT_COMPONENTS = {
16+
Label: ItemLabel,
17+
Description: ItemDescription,
18+
descriptionValidationToken: ITEM_DESCRIPTION_DIRECT_CHILD,
19+
validationMessage:
20+
'Autocomplete.ItemLabel must be the first direct child of every autocomplete item, followed only by Autocomplete.ItemDescription components.',
21+
};
22+
923
export const Item = forwardRef< HTMLDivElement, AutocompleteItemProps >(
10-
function UnforwardedItem( { className, children, ...restProps }, ref ) {
24+
function UnforwardedItem(
25+
{
26+
className,
27+
children,
28+
'aria-describedby': ariaDescribedBy,
29+
'aria-label': ariaLabel,
30+
'aria-labelledby': ariaLabelledBy,
31+
...restProps
32+
},
33+
ref
34+
) {
35+
const { contentChildren, itemAriaProps } = useItemContent(
36+
children,
37+
ITEM_CONTENT_COMPONENTS,
38+
{
39+
'aria-describedby': ariaDescribedBy,
40+
'aria-label': ariaLabel,
41+
'aria-labelledby': ariaLabelledBy,
42+
}
43+
);
1144
return (
1245
<_Autocomplete.Item
1346
className={ clsx(
@@ -17,9 +50,12 @@ export const Item = forwardRef< HTMLDivElement, AutocompleteItemProps >(
1750
className
1851
) }
1952
ref={ ref }
53+
{ ...itemAriaProps }
2054
{ ...restProps }
2155
>
22-
{ children }
56+
<div className={ itemPopupStyles[ 'item-text' ] }>
57+
{ contentChildren }
58+
</div>
2359
</_Autocomplete.Item>
2460
);
2561
}

‎packages/ui/src/form/primitives/autocomplete/stories/index.story.tsx‎

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ const meta: Meta< typeof Autocomplete.Root > = {
3636
'Autocomplete.Group': Autocomplete.Group,
3737
'Autocomplete.GroupLabel': Autocomplete.GroupLabel,
3838
'Autocomplete.Item': Autocomplete.Item,
39+
'Autocomplete.ItemLabel': Autocomplete.ItemLabel,
40+
'Autocomplete.ItemDescription': Autocomplete.ItemDescription,
3941
'Autocomplete.Row': Autocomplete.Row,
4042
'Autocomplete.Value': Autocomplete.Value,
4143
'Autocomplete.Empty': Autocomplete.Empty,
@@ -78,7 +80,42 @@ export const Default: Story = {
7880
key={ item.id }
7981
value={ item }
8082
>
81-
{ item.value }
83+
<Autocomplete.ItemLabel>
84+
{ item.value }
85+
</Autocomplete.ItemLabel>
86+
</Autocomplete.Item>
87+
) }
88+
</Autocomplete.Collection>
89+
</Autocomplete.ListBody>
90+
</Autocomplete.List>
91+
</Autocomplete.Popup>,
92+
],
93+
},
94+
};
95+
96+
/**
97+
* Item descriptions contribute to each suggestion's accessible description.
98+
*/
99+
export const WithItemDescriptions: Story = {
100+
args: {
101+
items: URLS.slice( 0, 3 ),
102+
children: [
103+
<Autocomplete.Input aria-label="URL" key="input" />,
104+
<Autocomplete.Popup key="popup">
105+
<Autocomplete.List>
106+
<Autocomplete.ListBody>
107+
<Autocomplete.Collection>
108+
{ ( item: FixtureItem ) => (
109+
<Autocomplete.Item
110+
key={ item.id }
111+
value={ item }
112+
>
113+
<Autocomplete.ItemLabel>
114+
{ item.value }
115+
</Autocomplete.ItemLabel>
116+
<Autocomplete.ItemDescription>
117+
Suggested URL
118+
</Autocomplete.ItemDescription>
82119
</Autocomplete.Item>
83120
) }
84121
</Autocomplete.Collection>
@@ -130,7 +167,9 @@ export const OpenOnlyOnMatch: Story = {
130167
key={ item.id }
131168
value={ item }
132169
>
133-
{ item.value }
170+
<Autocomplete.ItemLabel>
171+
{ item.value }
172+
</Autocomplete.ItemLabel>
134173
</Autocomplete.Item>
135174
) }
136175
</Autocomplete.Collection>
@@ -216,7 +255,9 @@ export const AsyncItems: Story = {
216255
key={ item.id }
217256
value={ item }
218257
>
219-
{ item.value }
258+
<Autocomplete.ItemLabel>
259+
{ item.value }
260+
</Autocomplete.ItemLabel>
220261
</Autocomplete.Item>
221262
) }
222263
</Autocomplete.Collection>
@@ -280,7 +321,9 @@ export const Inline: Story = {
280321
key={ command.id }
281322
value={ command }
282323
>
283-
{ command.value }
324+
<Autocomplete.ItemLabel>
325+
{ command.value }
326+
</Autocomplete.ItemLabel>
284327
</Autocomplete.Item>
285328
) }
286329
</Autocomplete.Collection>
@@ -325,7 +368,9 @@ export const WithSearchIconAndClearButton: Story = {
325368
key={ item.id }
326369
value={ item }
327370
>
328-
{ item.value }
371+
<Autocomplete.ItemLabel>
372+
{ item.value }
373+
</Autocomplete.ItemLabel>
329374
</Autocomplete.Item>
330375
) }
331376
</Autocomplete.Collection>
@@ -456,7 +501,9 @@ export const InlineMentionAutocomplete: Story = {
456501
key={ item.id }
457502
value={ item }
458503
>
459-
{ item.value }
504+
<Autocomplete.ItemLabel>
505+
{ item.value }
506+
</Autocomplete.ItemLabel>
460507
</Autocomplete.Item>
461508
) }
462509
</Autocomplete.Collection>
@@ -510,7 +557,9 @@ export const WithCustomZIndex: Story = {
510557
key={ item.id }
511558
value={ item }
512559
>
513-
{ item.value }
560+
<Autocomplete.ItemLabel>
561+
{ item.value }
562+
</Autocomplete.ItemLabel>
514563
</Autocomplete.Item>
515564
) }
516565
</Autocomplete.Collection>
@@ -553,7 +602,9 @@ export const Grouped: Story = {
553602
key={ item.id }
554603
value={ item }
555604
>
556-
{ item.value }
605+
<Autocomplete.ItemLabel>
606+
{ item.value }
607+
</Autocomplete.ItemLabel>
557608
</Autocomplete.Item>
558609
) }
559610
</Autocomplete.Collection>
@@ -585,6 +636,9 @@ const emojiPickerCellStyle: CSSProperties = {
585636
aspectRatio: '1 / 1',
586637
marginInline: 0,
587638
padding: 'var(--wpds-dimension-padding-xs)',
639+
};
640+
641+
const emojiPickerLabelStyle: CSSProperties = {
588642
fontSize: 'var(--wpds-typography-font-size-xl)',
589643
};
590644

@@ -646,9 +700,15 @@ export const Grid: Story = {
646700
emojiPickerCellStyle
647701
}
648702
>
649-
<span aria-hidden="true">
650-
{ emoji.emoji }
651-
</span>
703+
<Autocomplete.ItemLabel
704+
style={
705+
emojiPickerLabelStyle
706+
}
707+
>
708+
<span aria-hidden="true">
709+
{ emoji.emoji }
710+
</span>
711+
</Autocomplete.ItemLabel>
652712
</Autocomplete.Item>
653713
) ) }
654714
</Autocomplete.Row>

0 commit comments

Comments
 (0)