-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Try addinig a keyboard navigatble font size selector with visual feed…
…back
- Loading branch information
1 parent
a0a1a79
commit 059790e
Showing
4 changed files
with
273 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { map } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { __, _x, sprintf } from '@wordpress/i18n'; | ||
import { withInstanceId } from '@wordpress/compose'; | ||
import { useState, useRef, useCallback, useEffect } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { | ||
Dashicon, | ||
BaseControl, | ||
Button, | ||
NavigableMenu, | ||
Dropdown, | ||
} from '../'; | ||
|
||
function FontSizePickerSelect( { | ||
fontSizes = [], | ||
onChange, | ||
value, | ||
} ) { | ||
const currentFont = fontSizes.find( ( font ) => font.value === value ); | ||
const currentFontName = | ||
( currentFont && currentFont.name ) || | ||
( ! value && _x( 'Normal', 'font size name' ) ) || | ||
_x( 'Custom', 'font size name' ); | ||
const currentFontSlug = currentFont ? currentFont.slug : ''; | ||
|
||
const onChangeValue = ( event ) => { | ||
const newValue = event.target.value; | ||
if ( newValue === '' ) { | ||
onChange( undefined ); | ||
return; | ||
} | ||
onChange( Number( newValue ) ); | ||
}; | ||
|
||
/** | ||
* CHANGES | ||
*/ | ||
// Work-around to focus active item | ||
const popoverRef = useRef( null ); | ||
const focusActiveItem = useCallback( () => { | ||
// Hack work-arounds to control focus timing... | ||
window.requestAnimationFrame( () => { | ||
const { current } = popoverRef; | ||
if ( ! current ) { | ||
return; | ||
} | ||
const node = current.querySelector( '[aria-selected="true"]' ); | ||
node.blur(); | ||
window.requestAnimationFrame( () => { | ||
if ( node ) { | ||
// Hack work-arounds to control focus timing... | ||
node.focus(); | ||
} | ||
} ); | ||
} ); | ||
}, [] ); | ||
|
||
useEffect( () => { | ||
focusActiveItem(); | ||
}, [ focusActiveItem, value ] ); | ||
|
||
// Work around to manage + force open state outside of Dropdown | ||
const [ isOpen, setOpen ] = useState( false ); | ||
const openDropdown = () => { | ||
setOpen( true ); | ||
}; | ||
const closeDropdown = () => setOpen( false ); | ||
const handleOnToggle = ( nextOpen ) => { | ||
setOpen( nextOpen ); | ||
if ( nextOpen ) { | ||
focusActiveItem(); | ||
} | ||
}; | ||
|
||
// Work around to force dropdown to open via Button | ||
const handleOnButtonKeyDown = ( event ) => { | ||
const { key } = event; | ||
switch ( key ) { | ||
case 'ArrowUp': | ||
openDropdown(); | ||
break; | ||
case 'ArrowDown': | ||
openDropdown(); | ||
break; | ||
default: | ||
} | ||
}; | ||
|
||
// Work around to prevent scrolling. | ||
// Need to adjust navigable-content/container | ||
// https://github.com/WordPress/gutenberg/blob/master/packages/components/src/navigable-container/container.js#L89 | ||
const handleOnKeyDown = ( event ) => { | ||
// event.preventDefault(); | ||
const { key } = event; | ||
switch ( key ) { | ||
case 'ArrowDown': | ||
event.preventDefault(); | ||
break; | ||
case 'ArrowUp': | ||
event.preventDefault(); | ||
break; | ||
default: | ||
break; | ||
} | ||
}; | ||
|
||
// Improve voiceover consistency compared to native select | ||
const buttonRole = 'combobox'; | ||
const ariaActiveDescendant = `item-${ currentFontSlug }`; | ||
const ariaHasPopup = 'listbox'; | ||
const ariaProps = { | ||
role: buttonRole, | ||
'aria-haspopup': ariaHasPopup, | ||
'aria-activedescendant': ariaActiveDescendant, | ||
}; | ||
|
||
/** | ||
* / CHANGES | ||
*/ | ||
|
||
return ( | ||
<BaseControl className="components-font-size-picker__select"> | ||
<Dropdown | ||
className="components-font-size-picker__select-dropdown" | ||
contentClassName="components-font-size-picker__select-dropdown-content" | ||
position="bottom" | ||
focusOnMount="container" | ||
onChange={ onChangeValue } | ||
onOpen={ openDropdown } | ||
onClose={ closeDropdown } | ||
onToggle={ handleOnToggle } | ||
isOpen={ isOpen } | ||
renderToggle={ ( { onToggle } ) => ( | ||
<Button | ||
className="components-font-size-picker__select-selector" | ||
isLarge | ||
onClick={ onToggle } | ||
aria-expanded={ isOpen } | ||
aria-label={ sprintf( __( 'Customize font size. %s' ), currentFontName ) } | ||
onKeyDown={ handleOnButtonKeyDown } | ||
{ ...ariaProps } | ||
> | ||
{ currentFontName } | ||
</Button> | ||
) } | ||
renderContent={ () => { | ||
return ( | ||
<NavigableMenu | ||
role="listbox" | ||
aria-label="Choose Font Size" | ||
onKeyDown={ handleOnKeyDown } | ||
> | ||
<div ref={ popoverRef }> | ||
{ map( fontSizes, ( option ) => { | ||
const isSelected = value === option.value; | ||
const optionLabel = isSelected ? `${ option.label } (Selected)` : option.label; | ||
const itemId = `item-${ option.value }`; | ||
const itemRole = 'option'; | ||
const labelRole = 'presentation'; | ||
|
||
return ( | ||
<Button | ||
key={ option.value } | ||
onClick={ () => { | ||
onChange( option.value ); | ||
closeDropdown(); | ||
} } | ||
className={ `is-font-${ option.value }` } | ||
role={ itemRole } | ||
id={ itemId } | ||
aria-label={ optionLabel } | ||
aria-selected={ isSelected } | ||
> | ||
{ isSelected && <Dashicon icon="saved" /> } | ||
<span | ||
className="components-font-size-picker__select-dropdown-text-size" | ||
style={ option.size ? { fontSize: option.size } : {} } | ||
role={ labelRole } | ||
> | ||
{ option.label } | ||
</span> | ||
</Button> | ||
); | ||
} ) } | ||
</div> | ||
</NavigableMenu> | ||
); | ||
} } | ||
/> | ||
</BaseControl> | ||
); | ||
} | ||
|
||
export default withInstanceId( FontSizePickerSelect ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
.components-font-size-picker__select { | ||
// Need to override a generic rule | ||
// that can apply to all controls in the sidebar | ||
// Ideally that generic rule shouldn't target | ||
// this select in the first place. | ||
margin-bottom: 0 !important; | ||
|
||
.components-base-control__field { | ||
margin-bottom: 0; | ||
} | ||
} | ||
|
||
.components-font-size-picker__select-dropdown { | ||
display: inline-block; | ||
width: 80px; | ||
} | ||
|
||
.components-font-size-picker__select-dropdown-content button { | ||
display: block; | ||
width: 100%; | ||
text-align: left; | ||
position: relative; | ||
padding: 10px 5px; | ||
line-height: 1; | ||
} | ||
|
||
.components-font-size-picker__select-dropdown-content button:hover { | ||
background: #eee !important; | ||
} | ||
|
||
.components-font-size-picker__select-dropdown-content button.highlighted { | ||
background: #eee !important; | ||
} | ||
|
||
.components-font-size-picker__select-dropdown-content button svg { | ||
position: absolute; | ||
left: 5px; | ||
top: 50%; | ||
transform: translateY(-50%); | ||
} | ||
|
||
.components-font-size-picker__select-dropdown-text-size { | ||
margin-left: 30px; | ||
display: block; | ||
} | ||
|
||
.components-font-size-picker__select-selector { | ||
background: #fff !important; | ||
padding: 0 !important; | ||
padding: 0 10px !important; | ||
} | ||
|
||
.components-font-size-picker__select-selector::after { | ||
content: "▼"; | ||
position: relative; | ||
right: -10px; | ||
padding: 0 8px; | ||
} | ||
|
||
.components-font-size-picker__select-selector:focus { | ||
border: 1px solid $blue-medium-focus !important; | ||
outline: none !important; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters