Improve experimental select control accessibility (#34510)
* Use downshift built in methods to handle selection of items * Fix menu styling * Fix removal a11y announcement * Don't show menu without results * Fix async example a11y * Add changelog entry * Deselect item in state on remove * Fix formatting issues * Update lock file * Update lock file after pnpm7 * Skip lib check breaking oclif build in package-release * Rebase again and fix up lock file * Skip lib check in monorepo-merge * Fix the lock * Ignore lint Co-authored-by: Sam Seay <samueljseay@gmail.com> Co-authored-by: Jonathan Sadowski <sadowski@automattic.com>
This commit is contained in:
parent
d2b20dc993
commit
f39d8b6b39
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: enhancement
|
||||||
|
|
||||||
|
Improve experimental SelectControl accessibility
|
|
@ -4,14 +4,11 @@
|
||||||
top: 100%;
|
top: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: none;
|
display: none;
|
||||||
|
background: $studio-white;
|
||||||
|
border: 1px solid $studio-gray-5;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
&.is-open {
|
&.is-open.has-results {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.woocommerce-experimental-select-control__menu-inner {
|
|
||||||
background: $studio-white;
|
|
||||||
border: 1px solid $studio-gray-5;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -17,21 +17,19 @@ type MenuProps = {
|
||||||
|
|
||||||
export const Menu = ( { children, getMenuProps, isOpen }: MenuProps ) => {
|
export const Menu = ( { children, getMenuProps, isOpen }: MenuProps ) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<ul
|
||||||
{ ...getMenuProps() }
|
{ ...getMenuProps() }
|
||||||
className={ classnames(
|
className={ classnames(
|
||||||
'woocommerce-experimental-select-control__menu',
|
'woocommerce-experimental-select-control__menu',
|
||||||
{
|
{
|
||||||
'is-open': isOpen,
|
'is-open': isOpen,
|
||||||
|
'has-results': Array.isArray( children )
|
||||||
|
? children.length
|
||||||
|
: Boolean( children ),
|
||||||
}
|
}
|
||||||
) }
|
) }
|
||||||
>
|
>
|
||||||
{ isOpen &&
|
{ isOpen && children }
|
||||||
( ! Array.isArray( children ) || !! children.length ) && (
|
</ul>
|
||||||
<ul className="woocommerce-experimental-select-control__menu-inner">
|
|
||||||
{ children }
|
|
||||||
</ul>
|
|
||||||
) }
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -84,7 +84,14 @@ function SelectControl< ItemType = DefaultItemType >( {
|
||||||
}: SelectControlProps< ItemType > ) {
|
}: SelectControlProps< ItemType > ) {
|
||||||
const [ isFocused, setIsFocused ] = useState( false );
|
const [ isFocused, setIsFocused ] = useState( false );
|
||||||
const [ inputValue, setInputValue ] = useState( '' );
|
const [ inputValue, setInputValue ] = useState( '' );
|
||||||
const { getSelectedItemProps, getDropdownProps } = useMultipleSelection();
|
const {
|
||||||
|
addSelectedItem,
|
||||||
|
getSelectedItemProps,
|
||||||
|
getDropdownProps,
|
||||||
|
removeSelectedItem,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
} = useMultipleSelection( { itemToString } );
|
||||||
let selectedItems = selected === null ? [] : selected;
|
let selectedItems = selected === null ? [] : selected;
|
||||||
selectedItems = Array.isArray( selectedItems )
|
selectedItems = Array.isArray( selectedItems )
|
||||||
? selectedItems
|
? selectedItems
|
||||||
|
@ -104,11 +111,14 @@ function SelectControl< ItemType = DefaultItemType >( {
|
||||||
getComboboxProps,
|
getComboboxProps,
|
||||||
highlightedIndex,
|
highlightedIndex,
|
||||||
getItemProps,
|
getItemProps,
|
||||||
} = useCombobox( {
|
selectItem,
|
||||||
|
selectedItem: singleSelectedItem,
|
||||||
|
} = useCombobox< ItemType | null >( {
|
||||||
inputValue,
|
inputValue,
|
||||||
items: filteredItems,
|
items: filteredItems,
|
||||||
itemToString: getItemLabel,
|
itemToString: getItemLabel,
|
||||||
selectedItem: null,
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
onStateChange: ( { inputValue: value, type, selectedItem } ) => {
|
onStateChange: ( { inputValue: value, type, selectedItem } ) => {
|
||||||
switch ( type ) {
|
switch ( type ) {
|
||||||
case useCombobox.stateChangeTypes.InputChange:
|
case useCombobox.stateChangeTypes.InputChange:
|
||||||
|
@ -121,9 +131,18 @@ function SelectControl< ItemType = DefaultItemType >( {
|
||||||
case useCombobox.stateChangeTypes.InputBlur:
|
case useCombobox.stateChangeTypes.InputBlur:
|
||||||
if ( selectedItem ) {
|
if ( selectedItem ) {
|
||||||
onSelect( selectedItem );
|
onSelect( selectedItem );
|
||||||
setInputValue(
|
if ( multiple ) {
|
||||||
multiple ? '' : getItemLabel( selectedItem )
|
addSelectedItem( selectedItem );
|
||||||
);
|
setInputValue( '' );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectItem( selectedItem );
|
||||||
|
setInputValue( getItemLabel( selectedItem ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! selectedItem && ! multiple ) {
|
||||||
|
setInputValue( getItemLabel( singleSelectedItem ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -133,6 +152,12 @@ function SelectControl< ItemType = DefaultItemType >( {
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
const onRemoveItem = ( item: ItemType ) => {
|
||||||
|
selectItem( null );
|
||||||
|
removeSelectedItem( item );
|
||||||
|
onRemove( item );
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={ classnames( 'woocommerce-experimental-select-control', {
|
className={ classnames( 'woocommerce-experimental-select-control', {
|
||||||
|
@ -150,7 +175,7 @@ function SelectControl< ItemType = DefaultItemType >( {
|
||||||
getItemLabel={ getItemLabel }
|
getItemLabel={ getItemLabel }
|
||||||
getItemValue={ getItemValue }
|
getItemValue={ getItemValue }
|
||||||
getSelectedItemProps={ getSelectedItemProps }
|
getSelectedItemProps={ getSelectedItemProps }
|
||||||
onRemove={ onRemove }
|
onRemove={ onRemoveItem }
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
<ComboBox
|
<ComboBox
|
||||||
|
|
|
@ -106,6 +106,7 @@ export const Async: React.FC = () => {
|
||||||
|
|
||||||
const fetchItems = ( value: string | undefined ) => {
|
const fetchItems = ( value: string | undefined ) => {
|
||||||
setIsFetching( true );
|
setIsFetching( true );
|
||||||
|
setFetchedItems( [] );
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
const results = sampleItems.sort( () => 0.5 - Math.random() );
|
const results = sampleItems.sort( () => 0.5 - Math.random() );
|
||||||
setFetchedItems( results );
|
setFetchedItems( results );
|
||||||
|
@ -117,6 +118,9 @@ export const Async: React.FC = () => {
|
||||||
<>
|
<>
|
||||||
<SelectControl
|
<SelectControl
|
||||||
label="Async"
|
label="Async"
|
||||||
|
getFilteredItems={ ( allItems ) => {
|
||||||
|
return allItems;
|
||||||
|
} }
|
||||||
items={ fetchedItems }
|
items={ fetchedItems }
|
||||||
onInputChange={ fetchItems }
|
onInputChange={ fetchItems }
|
||||||
selected={ selectedItem }
|
selected={ selectedItem }
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./node_modules/@types"
|
"./node_modules/@types"
|
||||||
],
|
],
|
||||||
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./node_modules/@types"
|
"./node_modules/@types"
|
||||||
],
|
],
|
||||||
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
|
|
Loading…
Reference in New Issue