diff --git a/edit-post/components/header/index.js b/edit-post/components/header/index.js index 264498faf13827..9fcafc7b325825 100644 --- a/edit-post/components/header/index.js +++ b/edit-post/components/header/index.js @@ -61,7 +61,7 @@ function Header( { onClick={ toggleGeneralSidebar } isToggled={ isEditorSidebarOpened } aria-expanded={ isEditorSidebarOpened } - shortcut={ shortcuts.toggleSidebar.display } + shortcut={ shortcuts.toggleSidebar } /> { __( 'You’ll find more settings for your page and blocks in the sidebar. Click “Settings” to open it.' ) } diff --git a/edit-post/components/keyboard-shortcut-help-modal/config.js b/edit-post/components/keyboard-shortcut-help-modal/config.js index f77f229c1798f3..0801f45fe37ed9 100644 --- a/edit-post/components/keyboard-shortcut-help-modal/config.js +++ b/edit-post/components/keyboard-shortcut-help-modal/config.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { displayShortcutList } from '@wordpress/keycodes'; +import { displayShortcutList, shortcutAriaLabel } from '@wordpress/keycodes'; import { __ } from '@wordpress/i18n'; const { @@ -42,18 +42,21 @@ const globalShortcuts = { { keyCombination: primaryShift( ',' ), description: __( 'Show or hide the settings sidebar.' ), + ariaLabel: shortcutAriaLabel.primaryShift( ',' ), }, { keyCombination: ctrl( '`' ), - description: __( 'Navigate to a the next part of the editor.' ), + description: __( 'Navigate to the next part of the editor.' ), + ariaLabel: shortcutAriaLabel.ctrl( '`' ), }, { keyCombination: ctrlShift( '`' ), description: __( 'Navigate to the previous part of the editor.' ), + ariaLabel: shortcutAriaLabel.ctrlShift( '`' ), }, { keyCombination: shiftAlt( 'n' ), - description: __( 'Navigate to a the next part of the editor (alternative).' ), + description: __( 'Navigate to the next part of the editor (alternative).' ), }, { keyCombination: shiftAlt( 'p' ), @@ -76,6 +79,8 @@ const selectionShortcuts = { { keyCombination: 'Esc', description: __( 'Clear selection.' ), + /* translators: The 'escape' key on a keyboard. */ + ariaLabel: __( 'Escape' ), }, ], }; @@ -102,6 +107,8 @@ const blockShortcuts = { { keyCombination: '/', description: __( 'Change the block type after adding a new paragraph.' ), + /* translators: The forward-slash character. e.g. '/'. */ + ariaLabel: __( 'Forward-slash' ), }, ], }; diff --git a/edit-post/components/keyboard-shortcut-help-modal/index.js b/edit-post/components/keyboard-shortcut-help-modal/index.js index 295d3e9ba5fdcc..cd9715f3123bf9 100644 --- a/edit-post/components/keyboard-shortcut-help-modal/index.js +++ b/edit-post/components/keyboard-shortcut-help-modal/index.js @@ -42,13 +42,13 @@ const mapKeyCombination = ( keyCombination ) => keyCombination.map( ( character, const ShortcutList = ( { shortcuts } ) => (
- { shortcuts.map( ( { keyCombination, description }, index ) => ( + { shortcuts.map( ( { keyCombination, description, ariaLabel }, index ) => (
- + { mapKeyCombination( castArray( keyCombination ) ) }
diff --git a/edit-post/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap b/edit-post/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap index c1ccc4ca67a059..29de6e61e6ace7 100644 --- a/edit-post/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap +++ b/edit-post/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap @@ -63,6 +63,7 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ ], }, Object { + "ariaLabel": "Control + Shift + Comma", "description": "Show or hide the settings sidebar.", "keyCombination": Array [ "Ctrl", @@ -73,7 +74,8 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ ], }, Object { - "description": "Navigate to a the next part of the editor.", + "ariaLabel": "Control + Backtick", + "description": "Navigate to the next part of the editor.", "keyCombination": Array [ "Ctrl", "+", @@ -81,6 +83,7 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ ], }, Object { + "ariaLabel": "Control + Shift + Backtick", "description": "Navigate to the previous part of the editor.", "keyCombination": Array [ "Ctrl", @@ -91,7 +94,7 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ ], }, Object { - "description": "Navigate to a the next part of the editor (alternative).", + "description": "Navigate to the next part of the editor (alternative).", "keyCombination": Array [ "Shift", "+", @@ -139,6 +142,7 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ ], }, Object { + "ariaLabel": "Escape", "description": "Clear selection.", "keyCombination": "Esc", }, @@ -191,6 +195,7 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ ], }, Object { + "ariaLabel": "Forward-slash", "description": "Change the block type after adding a new paragraph.", "keyCombination": "/", }, diff --git a/edit-post/components/sidebar/sidebar-header/index.js b/edit-post/components/sidebar/sidebar-header/index.js index 19b58e26ab5c0b..b0eab693e0f0aa 100644 --- a/edit-post/components/sidebar/sidebar-header/index.js +++ b/edit-post/components/sidebar/sidebar-header/index.js @@ -37,7 +37,7 @@ const SidebarHeader = ( { children, className, closeLabel, closeSidebar, title } onClick={ closeSidebar } icon="no-alt" label={ closeLabel } - shortcut={ shortcuts.toggleSidebar.display } + shortcut={ shortcuts.toggleSidebar } />
diff --git a/edit-post/components/visual-editor/block-inspector-button.js b/edit-post/components/visual-editor/block-inspector-button.js index f29f081f8a2c8d..09a28d76f7eda4 100644 --- a/edit-post/components/visual-editor/block-inspector-button.js +++ b/edit-post/components/visual-editor/block-inspector-button.js @@ -40,7 +40,7 @@ export function BlockInspectorButton( { onClick={ flow( areAdvancedSettingsOpened ? closeSidebar : openEditorSidebar, speakMessage, onClick ) } icon="admin-generic" label={ small ? label : undefined } - shortcut={ shortcuts.toggleSidebar.display } + shortcut={ shortcuts.toggleSidebar } > { ! small && label } diff --git a/edit-post/keyboard-shortcuts.js b/edit-post/keyboard-shortcuts.js index be46a74a601864..0f119a7edf3f60 100644 --- a/edit-post/keyboard-shortcuts.js +++ b/edit-post/keyboard-shortcuts.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; +import { rawShortcut, displayShortcut, shortcutAriaLabel } from '@wordpress/keycodes'; export default { toggleEditorMode: { @@ -11,5 +11,6 @@ export default { toggleSidebar: { raw: rawShortcut.primaryShift( ',' ), display: displayShortcut.primaryShift( ',' ), + ariaLabel: shortcutAriaLabel.primaryShift( ',' ), }, }; diff --git a/packages/components/src/menu-item/index.js b/packages/components/src/menu-item/index.js index 27460ce0e0f1ec..f0c2f3cf6046cf 100644 --- a/packages/components/src/menu-item/index.js +++ b/packages/components/src/menu-item/index.js @@ -13,7 +13,7 @@ import { cloneElement } from '@wordpress/element'; * Internal dependencies */ import Button from '../button'; -import Shortcut from './shortcut'; +import Shortcut from '../shortcut'; import IconButton from '../icon-button'; /** @@ -45,7 +45,7 @@ function MenuItem( { children, className, icon, onClick, shortcut, isSelected, r { ...props } > { children } - + ); } @@ -59,7 +59,7 @@ function MenuItem( { children, className, icon, onClick, shortcut, isSelected, r { ...props } > { children } - + ); } diff --git a/packages/components/src/menu-item/shortcut.js b/packages/components/src/menu-item/shortcut.js deleted file mode 100644 index 582104e2511a84..00000000000000 --- a/packages/components/src/menu-item/shortcut.js +++ /dev/null @@ -1,10 +0,0 @@ -function MenuItemsShortcut( { shortcut } ) { - if ( ! shortcut ) { - return null; - } - return ( - { shortcut } - ); -} - -export default MenuItemsShortcut; diff --git a/packages/components/src/menu-item/test/__snapshots__/index.js.snap b/packages/components/src/menu-item/test/__snapshots__/index.js.snap index be23d018ed82ca..1427a4d1d20837 100644 --- a/packages/components/src/menu-item/test/__snapshots__/index.js.snap +++ b/packages/components/src/menu-item/test/__snapshots__/index.js.snap @@ -9,7 +9,8 @@ exports[`MenuItem should match snapshot when all props provided 1`] = ` role="menuitemcheckbox" > My item - @@ -23,7 +24,8 @@ exports[`MenuItem should match snapshot when isSelected and role are optionally role="menuitem" > My item - @@ -35,6 +37,8 @@ exports[`MenuItem should match snapshot when only label provided 1`] = ` role="menuitem" > My item - + `; diff --git a/packages/components/src/shortcut/index.js b/packages/components/src/shortcut/index.js new file mode 100644 index 00000000000000..6f41d5287260a6 --- /dev/null +++ b/packages/components/src/shortcut/index.js @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { isString, isObject } from 'lodash'; + +function Shortcut( { shortcut, className } ) { + if ( ! shortcut ) { + return null; + } + + let displayText; + let ariaLabel; + + if ( isString( shortcut ) ) { + displayText = shortcut; + } + + if ( isObject( shortcut ) ) { + displayText = shortcut.display; + ariaLabel = shortcut.ariaLabel; + } + + return ( + + { displayText } + + ); +} + +export default Shortcut; diff --git a/packages/components/src/shortcut/test/index.js b/packages/components/src/shortcut/test/index.js new file mode 100644 index 00000000000000..7e75665cd653d9 --- /dev/null +++ b/packages/components/src/shortcut/test/index.js @@ -0,0 +1,41 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import Shortcut from '../'; + +describe( 'Shortcut', () => { + it( 'does not render anything if no shortcut prop is provided', () => { + const wrapper = shallow( ); + + expect( wrapper.children() ).toHaveLength( 0 ); + } ); + + it( 'renders the shortcut display text when a string is passed as the shortcut', () => { + const wrapper = shallow( + + ); + + expect( wrapper.text() ).toBe( 'shortcut text' ); + } ); + + it( 'renders the shortcut display text and aria-label when an object is passed as the shortcut with the correct properties', () => { + const wrapper = shallow( + + ); + + expect( wrapper.text() ).toBe( 'shortcut text' ); + expect( wrapper.prop( 'aria-label' ) ).toBe( 'shortcut label' ); + } ); +} ); diff --git a/packages/components/src/tooltip/index.js b/packages/components/src/tooltip/index.js index 006825866b1f85..de177eeb7492b1 100644 --- a/packages/components/src/tooltip/index.js +++ b/packages/components/src/tooltip/index.js @@ -18,6 +18,7 @@ import { * Internal dependencies */ import Popover from '../popover'; +import Shortcut from '../shortcut'; /** * Time over children to wait before showing tooltip @@ -178,7 +179,7 @@ class Tooltip extends Component { aria-hidden="true" > { text } - { shortcut && { shortcut } } + ), ), diff --git a/packages/components/src/tooltip/test/index.js b/packages/components/src/tooltip/test/index.js index 04be4b06fdde56..313e58b67062da 100644 --- a/packages/components/src/tooltip/test/index.js +++ b/packages/components/src/tooltip/test/index.js @@ -49,7 +49,7 @@ describe( 'Tooltip', () => { expect( button.childAt( 1 ).name() ).toBe( 'Popover' ); expect( popover.prop( 'focusOnMount' ) ).toBe( false ); expect( popover.prop( 'position' ) ).toBe( 'bottom right' ); - expect( popover.children().text() ).toBe( 'Help text' ); + expect( popover.children().first().text() ).toBe( 'Help text' ); } ); it( 'should show popover on focus', () => { diff --git a/packages/keycodes/src/index.js b/packages/keycodes/src/index.js index 1e74b1b994683d..b9479cb7ff6781 100644 --- a/packages/keycodes/src/index.js +++ b/packages/keycodes/src/index.js @@ -14,6 +14,11 @@ */ import { get, mapValues, includes, capitalize } from 'lodash'; +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + /** * Internal dependencies */ @@ -71,18 +76,18 @@ export const rawShortcut = mapValues( modifiers, ( modifier ) => { */ export const displayShortcutList = mapValues( modifiers, ( modifier ) => { return ( character, _isApple = isAppleOS ) => { - const isMac = _isApple(); + const isApple = _isApple(); const replacementKeyMap = { - [ ALT ]: isMac ? '⌥' : 'Alt', - [ CTRL ]: isMac ? '^' : 'Ctrl', + [ ALT ]: isApple ? '⌥' : 'Alt', + [ CTRL ]: isApple ? '^' : 'Ctrl', [ COMMAND ]: '⌘', - [ SHIFT ]: isMac ? '⇧' : 'Shift', + [ SHIFT ]: isApple ? '⇧' : 'Shift', }; const modifierKeys = modifier( _isApple ).reduce( ( accumulator, key ) => { const replacementKey = get( replacementKeyMap, key, key ); // If on the Mac, adhere to platform convention and don't show plus between keys. - if ( isMac ) { + if ( isApple ) { return [ ...accumulator, replacementKey ]; } @@ -100,8 +105,34 @@ export const displayShortcutList = mapValues( modifiers, ( modifier ) => { * * @type {Object} Keyed map of functions to display shortcuts. */ -export const displayShortcut = mapValues( displayShortcutList, ( sequence ) => { - return ( character, _isApple = isAppleOS ) => sequence( character, _isApple ).join( '' ); +export const displayShortcut = mapValues( displayShortcutList, ( shortcutList ) => { + return ( character, _isApple = isAppleOS ) => shortcutList( character, _isApple ).join( '' ); +} ); + +/** + * An object that contains functions to return an aria label for a keyboard shortcut. + * E.g. shortcutAriaLabel.primary( '.' ) will return 'Command + Period' on Mac. + */ +export const shortcutAriaLabel = mapValues( modifiers, ( modifier ) => { + return ( character, _isApple = isAppleOS ) => { + const isApple = _isApple(); + const replacementKeyMap = { + [ SHIFT ]: 'Shift', + [ COMMAND ]: isApple ? 'Command' : 'Control', + [ CTRL ]: 'Control', + [ ALT ]: isApple ? 'Option' : 'Alt', + /* translators: comma as in the character ',' */ + ',': __( 'Comma' ), + /* translators: period as in the character '.' */ + '.': __( 'Period' ), + /* translators: backtick as in the character '`' */ + '`': __( 'Backtick' ), + }; + + return [ ...modifier( _isApple ), character ] + .map( ( key ) => capitalize( get( replacementKeyMap, key, key ) ) ) + .join( isApple ? ' ' : ' + ' ); + }; } ); /** diff --git a/packages/keycodes/src/test/index.js b/packages/keycodes/src/test/index.js index a96e171e7564b3..d795560fcb3a7f 100644 --- a/packages/keycodes/src/test/index.js +++ b/packages/keycodes/src/test/index.js @@ -5,6 +5,7 @@ import { displayShortcutList, displayShortcut, rawShortcut, + shortcutAriaLabel, } from '../'; const isAppleOSFalse = () => false; @@ -130,6 +131,56 @@ describe( 'displayShortcut', () => { } ); } ); +describe( 'shortcutAriaLabel', () => { + describe( 'primary', () => { + it( 'should output "Control + Period" on Windows', () => { + const shortcut = shortcutAriaLabel.primary( '.', isAppleOSFalse ); + expect( shortcut ).toEqual( 'Control + Period' ); + } ); + + it( 'should output "Command Period" on Windows', () => { + const shortcut = shortcutAriaLabel.primary( '.', isAppleOSTrue ); + expect( shortcut ).toEqual( 'Command Period' ); + } ); + } ); + + describe( 'primaryShift', () => { + it( 'should output "Control + Shift + Period" on Windows', () => { + const shortcut = shortcutAriaLabel.primaryShift( '.', isAppleOSFalse ); + expect( shortcut ).toEqual( 'Control + Shift + Period' ); + } ); + + it( 'should output "Shift Command Period" on MacOS', () => { + const shortcut = shortcutAriaLabel.primaryShift( '.', isAppleOSTrue ); + expect( shortcut ).toEqual( 'Shift Command Period' ); + } ); + } ); + + describe( 'secondary', () => { + it( 'should output "Control + Shift + Alt + Period" on Windows', () => { + const shortcut = shortcutAriaLabel.secondary( '.', isAppleOSFalse ); + expect( shortcut ).toEqual( 'Control + Shift + Alt + Period' ); + } ); + + it( 'should output "Shift Option Command Period" on MacOS', () => { + const shortcut = shortcutAriaLabel.secondary( '.', isAppleOSTrue ); + expect( shortcut ).toEqual( 'Shift Option Command Period' ); + } ); + } ); + + describe( 'access', () => { + it( 'should output "Shift + Alt + Period" on Windows', () => { + const shortcut = shortcutAriaLabel.access( '.', isAppleOSFalse ); + expect( shortcut ).toEqual( 'Shift + Alt + Period' ); + } ); + + it( 'should output "Control Option Period" on MacOS', () => { + const shortcut = shortcutAriaLabel.access( '.', isAppleOSTrue ); + expect( shortcut ).toEqual( 'Control Option Period' ); + } ); + } ); +} ); + describe( 'rawShortcut', () => { describe( 'primary', () => { it( 'should output ctrl on Windows', () => {