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
tellthemachines committed Jul 10, 2023
1 parent 34b374b commit b8faaa8
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 144 deletions.
239 changes: 108 additions & 131 deletions packages/edit-site/src/components/page-patterns/grid-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import {
DropdownMenu,
MenuGroup,
MenuItem,
__experimentalHeading as Heading,
__experimentalHStack as HStack,
__unstableCompositeItem as CompositeItem,
Tooltip,
Flex,
} from '@wordpress/components';
Expand All @@ -32,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 @@ -66,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 @@ -138,134 +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={ Heading }
level={ 5 }
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 @@ -275,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

0 comments on commit b8faaa8

Please sign in to comment.