Skip to content

Commit

Permalink
Patterns: alternative grid layout to improve keyboard accessibility (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
glendaviesnz committed Jul 10, 2023
1 parent 38fca8a commit b6ac42a
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 138 deletions.
233 changes: 108 additions & 125 deletions packages/edit-site/src/components/page-patterns/grid-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
MenuGroup,
MenuItem,
__experimentalHStack as HStack,
__unstableCompositeItem as CompositeItem,
Tooltip,
Flex,
} from '@wordpress/components';
Expand All @@ -31,7 +30,6 @@ import {
} from '@wordpress/icons';
import { store as noticesStore } from '@wordpress/notices';
import { store as reusableBlocksStore } from '@wordpress/reusable-blocks';
import { DELETE, BACKSPACE } from '@wordpress/keycodes';

/**
* Internal dependencies
Expand Down Expand Up @@ -65,12 +63,6 @@ function GridItem( { categoryId, item, ...props } ) {
categoryType: item.type,
} );

const onKeyDown = ( event ) => {
if ( DELETE === event.keyCode || BACKSPACE === event.keyCode ) {
setIsDeleteDialogOpen( true );
}
};

const isEmpty = ! item.blocks?.length;
const patternClassNames = classnames( 'edit-site-patterns__pattern', {
'is-placeholder': isEmpty,
Expand Down Expand Up @@ -137,129 +129,120 @@ function GridItem( { categoryId, item, ...props } ) {
);

return (
<>
<div className={ patternClassNames }>
<CompositeItem
className={ previewClassNames }
role="option"
as="div"
// Even though still incomplete, passing ids helps performance.
// @see https://reakit.io/docs/composite/#performance.
id={ `edit-site-patterns-${ item.name }` }
{ ...props }
onClick={ item.type !== PATTERNS ? onClick : undefined }
onKeyDown={ isCustomPattern ? onKeyDown : undefined }
aria-label={ item.title }
aria-describedby={
ariaDescriptions.length
? ariaDescriptions
.map(
( _, index ) =>
`${ descriptionId }-${ index }`
)
.join( ' ' )
: undefined
}
<li className={ patternClassNames }>
<button
className={ previewClassNames }
// Even though still incomplete, passing ids helps performance.
// @see https://reakit.io/docs/composite/#performance.
id={ `edit-site-patterns-${ item.name }` }
{ ...props }
onClick={ item.type !== PATTERNS ? onClick : undefined }
aria-disabled={ item.type !== PATTERNS ? 'false' : 'true' }
aria-label={ item.title }
aria-describedby={
ariaDescriptions.length
? ariaDescriptions
.map(
( _, index ) =>
`${ descriptionId }-${ index }`
)
.join( ' ' )
: undefined
}
>
{ isEmpty && __( 'Empty pattern' ) }
{ ! isEmpty && <BlockPreview blocks={ item.blocks } /> }
</button>
{ ariaDescriptions.map( ( ariaDescription, index ) => (
<div
key={ index }
hidden
id={ `${ descriptionId }-${ index }` }
>
{ isEmpty && __( 'Empty pattern' ) }
{ ! isEmpty && <BlockPreview blocks={ item.blocks } /> }
</CompositeItem>
{ ariaDescriptions.map( ( ariaDescription, index ) => (
<div
key={ index }
hidden
id={ `${ descriptionId }-${ index }` }
>
{ ariaDescription }
</div>
) ) }
{ ariaDescription }
</div>
) ) }
<HStack
className="edit-site-patterns__footer"
justify="space-between"
>
<HStack
aria-hidden="true"
className="edit-site-patterns__footer"
justify="space-between"
alignment="center"
justify="left"
spacing={ 3 }
className="edit-site-patterns__pattern-title"
>
<HStack
alignment="center"
justify="left"
spacing={ 3 }
className="edit-site-patterns__pattern-title"
>
{ itemIcon && (
<Icon
className="edit-site-patterns__pattern-icon"
icon={ itemIcon }
/>
) }
<Flex as="span" gap={ 0 } justify="left">
{ item.title }
{ item.type === PATTERNS && (
<Tooltip
position="top center"
text={ __(
'Theme patterns cannot be edited.'
) }
>
<span className="edit-site-patterns__pattern-lock-icon">
<Icon icon={ lockSmall } size={ 24 } />
</span>
</Tooltip>
) }
</Flex>
</HStack>
<DropdownMenu
icon={ moreHorizontal }
label={ __( 'Actions' ) }
className="edit-site-patterns__dropdown"
popoverProps={ { placement: 'bottom-end' } }
toggleProps={ {
className: 'edit-site-patterns__button',
isSmall: true,
describedBy: sprintf(
/* translators: %s: pattern name */
__( 'Action menu for %s pattern' ),
item.title
),
// The dropdown menu is not focusable using the
// keyboard as this would interfere with the grid's
// roving tab index system. Instead, keyboard users
// use keyboard shortcuts to trigger actions.
tabIndex: -1,
} }
>
{ ( { onClose } ) => (
<MenuGroup>
{ isCustomPattern && ! hasThemeFile && (
<RenameMenuItem
item={ item }
onClose={ onClose }
/>
{ itemIcon && (
<Icon
className="edit-site-patterns__pattern-icon"
icon={ itemIcon }
/>
) }
<Flex as="span" gap={ 0 } justify="left">
{ item.title }
{ item.type === PATTERNS && (
<Tooltip
position="top center"
text={ __(
'Theme patterns cannot be edited.'
) }
<DuplicateMenuItem
categoryId={ categoryId }
>
<span className="edit-site-patterns__pattern-lock-icon">
<Icon icon={ lockSmall } size={ 24 } />
</span>
</Tooltip>
) }
</Flex>
</HStack>
<DropdownMenu
icon={ moreHorizontal }
label={ __( 'Actions' ) }
className="edit-site-patterns__dropdown"
popoverProps={ { placement: 'bottom-end' } }
toggleProps={ {
className: 'edit-site-patterns__button',
isSmall: true,
describedBy: sprintf(
/* translators: %s: pattern name */
__( 'Action menu for %s pattern' ),
item.title
),
} }
>
{ ( { onClose } ) => (
<MenuGroup>
{ isCustomPattern && ! hasThemeFile && (
<RenameMenuItem
item={ item }
onClose={ onClose }
label={
isNonUserPattern
? __( 'Copy to My patterns' )
: __( 'Duplicate' )
}
/>
{ isCustomPattern && (
<MenuItem
onClick={ () =>
setIsDeleteDialogOpen( true )
}
>
{ hasThemeFile
? __( 'Clear customizations' )
: __( 'Delete' ) }
</MenuItem>
) }
</MenuGroup>
) }
</DropdownMenu>
</HStack>
</div>
) }
<DuplicateMenuItem
categoryId={ categoryId }
item={ item }
onClose={ onClose }
label={
isNonUserPattern
? __( 'Copy to My patterns' )
: __( 'Duplicate' )
}
/>
{ isCustomPattern && (
<MenuItem
onClick={ () =>
setIsDeleteDialogOpen( true )
}
>
{ hasThemeFile
? __( 'Clear customizations' )
: __( 'Delete' ) }
</MenuItem>
) }
</MenuGroup>
) }
</DropdownMenu>
</HStack>

{ isDeleteDialogOpen && (
<ConfirmDialog
confirmButtonText={ confirmButtonText }
Expand All @@ -269,7 +252,7 @@ function GridItem( { categoryId, item, ...props } ) {
{ confirmPrompt }
</ConfirmDialog>
) }
</>
</li>
);
}

Expand Down
13 changes: 3 additions & 10 deletions packages/edit-site/src/components/page-patterns/grid.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
/**
* WordPress dependencies
*/
import {
__unstableComposite as Composite,
__unstableUseCompositeState as useCompositeState,
__experimentalText as Text,
} from '@wordpress/components';
import { __experimentalText as Text } from '@wordpress/components';
import { useRef } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';

Expand All @@ -17,7 +13,6 @@ import GridItem from './grid-item';
const PAGE_SIZE = 100;

export default function Grid( { categoryId, items, ...props } ) {
const composite = useCompositeState( { wrap: true } );
const gridRef = useRef();

if ( ! items?.length ) {
Expand All @@ -29,8 +24,7 @@ export default function Grid( { categoryId, items, ...props } ) {

return (
<>
<Composite
{ ...composite }
<ul
role="listbox"
className="edit-site-patterns__grid"
{ ...props }
Expand All @@ -41,10 +35,9 @@ export default function Grid( { categoryId, items, ...props } ) {
key={ item.name }
item={ item }
categoryId={ categoryId }
{ ...composite }
/>
) ) }
</Composite>
</ul>
{ restLength > 0 && (
<Text variant="muted" as="p" align="center">
{ sprintf(
Expand Down
20 changes: 17 additions & 3 deletions packages/edit-site/src/components/page-patterns/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@
}
}

.edit-site-patterns__section-header {
.screen-reader-shortcut:focus {
top: 0;
}
}

.edit-site-patterns__grid {
display: grid;
grid-template-columns: 1fr;
Expand All @@ -77,20 +83,28 @@
display: flex;
flex-direction: column;
.edit-site-patterns__preview {
border-radius: $radius-block-ui;
box-shadow: none;
border: none;
padding: 0;
background-color: unset;
box-sizing: border-box;
border-radius: 4px;
cursor: pointer;
overflow: hidden;

&:focus {
box-shadow: inset 0 0 0 2px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);

box-shadow: inset 0 0 0 0 $white, 0 0 0 2px var(--wp-admin-theme-color);
// Windows High Contrast mode will show this outline, but not the box-shadow.
outline: 2px solid transparent;
}

&.is-inactive {
cursor: default;
}
&.is-inactive:focus {
box-shadow: 0 0 0 var(--wp-admin-border-width-focus) $gray-800;
opacity: 0.8;
}
}

.edit-site-patterns__footer,
Expand Down

1 comment on commit b6ac42a

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in b6ac42a.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/5503998765
📝 Reported issues:

Please sign in to comment.