Skip to content

Commit

Permalink
Components: Fix inaccessible disabled Buttons (#62306)
Browse files Browse the repository at this point in the history
* Components: Fix inaccessible disabled `Button`s

* Fix in RangeControl

* Fix in ToolbarButton

* Fix unit test

* List block: Try normalizing `disabled`

* Revert "Fix in ToolbarButton"

This reverts commit 2cea1eb.

* Revert "List block: Try normalizing `disabled`"

This reverts commit f95b0b2.

* ToolbarButton: Defer fix

* Revert "Fix unit test"

This reverts commit ed03d97.

* Add changelog

* ToolbarButton: Add back focusable when not within ToolbarItem

* Revert "Revert "Fix unit test""

This reverts commit 5f92fdc.

Co-authored-by: mirka <[email protected]>
Co-authored-by: ciampo <[email protected]>
  • Loading branch information
3 people authored Jul 3, 2024
1 parent 14ee526 commit 89bfae4
Show file tree
Hide file tree
Showing 11 changed files with 28 additions and 6 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ module.exports = {
'no-restricted-syntax': [
'error',
...restrictedSyntax,
...restrictedSyntaxComponents,
{
selector:
':matches(Literal[value=/--wp-admin-theme-/],TemplateElement[value.cooked=/--wp-admin-theme-/])',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,19 @@ describe( 'BlockSwitcher', () => {
expect( items[ 1 ] ).toHaveTextContent( headingBlockType.title );
} );

test( 'should render disabled block switcher when we have a single selected block without styles and we cannot remove it', () => {
test( 'should render accessibly disabled block switcher when we have a single selected block without styles and we cannot remove it', () => {
useSelect.mockImplementation( () => ( {
blocks: [ headingBlock1 ],
icon: copy,
hasBlockStyles: false,
canRemove: false,
} ) );
render( <BlockSwitcher clientIds={ [ headingBlock1.clientId ] } /> );
expect(
screen.getByRole( 'button', {
name: 'Block Name',
} )
).toBeDisabled();
const blockSwitcher = screen.getByRole( 'button', {
name: 'Block Name',
} );
expect( blockSwitcher ).toBeEnabled();
expect( blockSwitcher ).toHaveAttribute( 'aria-disabled', 'true' );
} );

test( 'should render message for no available transforms', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- `UnitControl`: Fix colors when disabled. ([#62970](https://github.com/WordPress/gutenberg/pull/62970))
- `useUpdateEffect`: Correctly track mounted state in strict mode. ([#62974](https://github.com/WordPress/gutenberg/pull/62974))
- `UnitControl`: Fix an issue where keyboard shortcuts unintentionally shift focus on Windows OS. ([#62988](https://github.com/WordPress/gutenberg/pull/62988))
- Fix inaccessibly disabled `Button`s ([#62306](https://github.com/WordPress/gutenberg/pull/62306)).

### Internal

Expand Down
1 change: 1 addition & 0 deletions packages/components/src/autocomplete/autocompleter-ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function ListBox( {
id={ `components-autocomplete-item-${ instanceId }-${ option.key }` }
role="option"
aria-selected={ index === selectedIndex }
__experimentalIsFocusable
disabled={ option.isDisabled }
className={ clsx(
'components-autocomplete__result',
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/button/stories/e2e/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const VariantStates: StoryFn< typeof Button > = (
key={ variant ?? 'undefined' }
>
<Button { ...props } variant={ variant } />
{ /* eslint-disable-next-line no-restricted-syntax */ }
<Button { ...props } variant={ variant } disabled />
<Button
{ ...props }
Expand Down
6 changes: 6 additions & 0 deletions packages/components/src/button/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ describe( 'Button', () => {
} );

it( 'should add a disabled prop to the button', () => {
// eslint-disable-next-line no-restricted-syntax
render( <Button disabled /> );

expect( screen.getByRole( 'button' ) ).toBeDisabled();
Expand Down Expand Up @@ -536,6 +537,7 @@ describe( 'Button', () => {

it( 'should become a button again when disabled is supplied', () => {
// @ts-expect-error - a button should not have `href`
// eslint-disable-next-line no-restricted-syntax
render( <Button href="https://wordpress.org/" disabled /> );

expect( screen.getByRole( 'button' ) ).toBeVisible();
Expand Down Expand Up @@ -624,8 +626,12 @@ describe( 'Button', () => {
<Button href="foo" />
{ /* @ts-expect-error - `target` requires `href` */ }
<Button target="foo" />

{ /* eslint-disable no-restricted-syntax */ }
{ /* @ts-expect-error - `disabled` is only for buttons */ }
<Button href="foo" disabled />
{ /* eslint-enable no-restricted-syntax */ }

<Button href="foo" type="image/png" />
{ /* @ts-expect-error - if button, type must be submit/reset/button */ }
<Button type="image/png" />
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/combobox-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ function ComboboxControl( props: ComboboxControlProps ) {
<Button
className="components-combobox-control__reset"
icon={ closeSmall }
// Disable reason: Focus returns to input field when reset is clicked.
// eslint-disable-next-line no-restricted-syntax
disabled={ ! value }
onClick={ handleOnReset }
label={ __( 'Reset' ) }
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/dropdown-menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ function UnconnectedDropdownMenu( dropdownMenuProps: DropdownMenuProps ) {
? control.role
: 'menuitem'
}
__experimentalIsFocusable
disabled={ control.isDisabled }
>
{ control.title }
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/form-token-field/token.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export default function Token( {
className="components-form-token-field__remove-token"
icon={ closeSmall }
onClick={ ! disabled ? onClick : undefined }
// Disable reason: Even when FormTokenField itself is accessibly disabled, token reset buttons shouldn't be in the tab sequence.
// eslint-disable-next-line no-restricted-syntax
disabled={ disabled }
label={ messages.remove }
aria-describedby={ `components-form-token-field__token-text-${ instanceId }` }
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/range-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@ function UnforwardedRangeControl(
<ActionRightWrapper>
<Button
className="components-range-control__reset"
// If the RangeControl itself is disabled, the reset button shouldn't be in the tab sequence.
__experimentalIsFocusable={ ! disabled }
disabled={ disabled || value === undefined }
variant="secondary"
size="small"
Expand Down
5 changes: 5 additions & 0 deletions packages/components/src/toolbar/toolbar-button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ function UnforwardedToolbarButton(
className
) }
isPressed={ isActive }
__experimentalIsFocusable
disabled={ isDisabled }
data-toolbar-item
{ ...extraProps }
Expand All @@ -85,6 +86,10 @@ function UnforwardedToolbarButton(
<Button
label={ title }
isPressed={ isActive }
// TODO: Should be focusable disabled, but adding `__experimentalIsFocusable` will trigger a
// focus bug when ToolbarButton is disabled via the `disabled` prop.
// Must address first: https://github.com/WordPress/gutenberg/issues/63070
// eslint-disable-next-line no-restricted-syntax
disabled={ isDisabled }
{ ...toolbarItemProps }
>
Expand Down

0 comments on commit 89bfae4

Please sign in to comment.