-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use aria-describedBy to communicate border button style and color state #54469
base: trunk
Are you sure you want to change the base?
Changes from all commits
a5562bd
d5ce7dd
b5173c2
64dd3c8
60bd713
19574be
32394a8
ddbf1f1
9315c6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ import type { CSSProperties } from 'react'; | |
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useInstanceId } from '@wordpress/compose'; | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { closeSmall } from '@wordpress/icons'; | ||
|
||
|
@@ -24,13 +25,12 @@ import { contextConnect } from '../../context'; | |
import { useBorderControlDropdown } from './hook'; | ||
import { StyledLabel } from '../../base-control/styles/base-control-styles'; | ||
import DropdownContentWrapper from '../../dropdown/dropdown-content-wrapper'; | ||
|
||
import type { ColorObject } from '../../color-palette/types'; | ||
import { isMultiplePaletteArray } from '../../color-palette/utils'; | ||
import type { DropdownProps as DropdownComponentProps } from '../../dropdown/types'; | ||
import type { ColorProps, DropdownProps } from '../types'; | ||
|
||
const getAriaLabelColorValue = ( colorValue: string ) => { | ||
const getAriaDescriptionColorValue = ( colorValue: string ) => { | ||
// Leave hex values as-is. Remove the `var()` wrapper from CSS vars. | ||
return colorValue.replace( /^var\((.+)\)$/, '$1' ); | ||
}; | ||
|
@@ -65,68 +65,77 @@ const getColorObject = ( | |
return colors.find( ( color ) => color.color === colorValue ); | ||
}; | ||
|
||
const getToggleAriaLabel = ( | ||
const getToggleDescription = ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be good to extract this function and write some simple tests for the various conditionals. These are outlined in your test instructinos for the PR and it would provide a lot of confidence if they were encoded into tests:
I would also say it's worth refactoring it with the aim of removing the branches and conditionals but that could be done in a follow up and should not even be attempted unless we have tests in place 🙏 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that this should all get refactored. As I was working within the context of what was here, I didn't want to change up things too much since I only wanted to provide a better description. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It needs tidying up. I appreciate that's a big ask for this PR though. Feel free to add as a follow up task if you like. I would help but I don't have bandwidth at this time 🙇 |
||
colorValue: CSSProperties[ 'borderColor' ], | ||
colorObject: ColorObject | undefined, | ||
Comment on lines
69
to
70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this function need to know so much? Can it not simply accept a colorValue and the consumer can pass the appropriate value. This could reduce the number of confusing conditionals. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see it needs to get the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In which case it could accept
That way it would not need to know about the structure of a Again let's not do this refactoring without tests 🙏 |
||
style: CSSProperties[ 'borderStyle' ], | ||
isStyleEnabled: boolean | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this boolean signify? What is "style enabled"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm honestly not sure -- I only renamed it to feel more accurately named and used what I needed from it. I haven't looked into it further. I do agree a refactor would be nice, but I'd prefer it as a follow-up. |
||
) => { | ||
if ( isStyleEnabled ) { | ||
if ( colorObject ) { | ||
const ariaLabelValue = getAriaLabelColorValue( colorObject.color ); | ||
const ariaDescriptionValue = getAriaDescriptionColorValue( | ||
colorObject.color | ||
); | ||
return style | ||
? sprintf( | ||
// translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code e.g.: "#f00:". %3$s: The current border style selection e.g. "solid". | ||
'Border color and style picker. The currently selected color is called "%1$s" and has a value of "%2$s". The currently selected style is "%3$s".', | ||
'The currently selected color is called "%1$s" and has a value of "%2$s". The currently selected style is "%3$s".', | ||
colorObject.name, | ||
ariaLabelValue, | ||
ariaDescriptionValue, | ||
style | ||
) | ||
: sprintf( | ||
// translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code e.g.: "#f00:". | ||
'Border color and style picker. The currently selected color is called "%1$s" and has a value of "%2$s".', | ||
'The currently selected color is called "%1$s" and has a value of "%2$s".', | ||
colorObject.name, | ||
ariaLabelValue | ||
ariaDescriptionValue | ||
); | ||
} | ||
|
||
if ( colorValue ) { | ||
const ariaLabelValue = getAriaLabelColorValue( colorValue ); | ||
const ariaDescriptionValue = | ||
getAriaDescriptionColorValue( colorValue ); | ||
return style | ||
? sprintf( | ||
// translators: %1$s: The color's hex code e.g.: "#f00:". %2$s: The current border style selection e.g. "solid". | ||
'Border color and style picker. The currently selected color has a value of "%1$s". The currently selected style is "%2$s".', | ||
ariaLabelValue, | ||
'The currently selected color has a value of "%1$s". The currently selected style is "%2$s".', | ||
ariaDescriptionValue, | ||
style | ||
) | ||
: sprintf( | ||
// translators: %1$s: The color's hex code e.g: "#f00". | ||
'Border color and style picker. The currently selected color has a value of "%1$s".', | ||
ariaLabelValue | ||
'The currently selected color has a value of "%1$s".', | ||
ariaDescriptionValue | ||
); | ||
} | ||
|
||
return __( 'Border color and style picker.' ); | ||
if ( style ) { | ||
return sprintf( | ||
// translators: %1$s: The current border style selection e.g. "solid". | ||
'The currently selected style is "%1$s". No color is selected. Select a color to display the border.', | ||
style | ||
); | ||
} | ||
} | ||
|
||
if ( colorObject ) { | ||
return sprintf( | ||
// translators: %1$s: The name of the color e.g. "vivid red". %2$s: The color's hex code e.g: "#f00". | ||
'Border color picker. The currently selected color is called "%1$s" and has a value of "%2$s".', | ||
'The currently selected color is called "%1$s" and has a value of "%2$s".', | ||
colorObject.name, | ||
getAriaLabelColorValue( colorObject.color ) | ||
getAriaDescriptionColorValue( colorObject.color ) | ||
); | ||
} | ||
|
||
if ( colorValue ) { | ||
return sprintf( | ||
// translators: %1$s: The color's hex code e.g: "#f00". | ||
'Border color picker. The currently selected color has a value of "%1$s".', | ||
getAriaLabelColorValue( colorValue ) | ||
'The currently selected color has a value of "%1$s".', | ||
getAriaDescriptionColorValue( colorValue ) | ||
); | ||
} | ||
|
||
return __( 'Border color picker.' ); | ||
return __( 'Select a border color and style.' ); | ||
}; | ||
|
||
const BorderControlDropdown = ( | ||
|
@@ -155,8 +164,12 @@ const BorderControlDropdown = ( | |
|
||
const { color, style } = border || {}; | ||
const colorObject = getColorObject( color, colors ); | ||
const toggleDescriptionId = useInstanceId( | ||
BorderControlDropdown, | ||
'border-control-dropdown' | ||
); | ||
|
||
const toggleAriaLabel = getToggleAriaLabel( | ||
const toggleDescription = getToggleDescription( | ||
color, | ||
colorObject, | ||
style, | ||
|
@@ -174,10 +187,10 @@ const BorderControlDropdown = ( | |
<Button | ||
onClick={ onToggle } | ||
variant="tertiary" | ||
aria-label={ toggleAriaLabel } | ||
tooltipPosition={ dropdownPosition } | ||
label={ __( 'Border color and style picker' ) } | ||
showTooltip={ true } | ||
aria-describedby={ toggleDescriptionId } | ||
> | ||
<span className={ indicatorWrapperClassName }> | ||
<ColorIndicator | ||
|
@@ -243,15 +256,20 @@ const BorderControlDropdown = ( | |
); | ||
|
||
return ( | ||
<Dropdown | ||
renderToggle={ renderToggle } | ||
renderContent={ renderContent } | ||
popoverProps={ { | ||
...__unstablePopoverProps, | ||
} } | ||
{ ...otherProps } | ||
ref={ forwardedRef } | ||
/> | ||
<> | ||
<div id={ toggleDescriptionId } style={ { display: 'none' } }> | ||
{ toggleDescription } | ||
</div> | ||
<Dropdown | ||
renderToggle={ renderToggle } | ||
renderContent={ renderContent } | ||
popoverProps={ { | ||
...__unstablePopoverProps, | ||
} } | ||
{ ...otherProps } | ||
ref={ forwardedRef } | ||
/> | ||
</> | ||
); | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function should be renamed to remove anything to do with
aria
. Small point but this seems to just be strippedvar()
from any CSS vars am I right? In which case let'sThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this could be made into a more generic utility function. Looks like it would be useful elsewhere too. Would you be OK with this also being a follow-up? It's starting to expand the purpose of this PR a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have a number of tasks that should happen as follow ups. Whilst I'm fine with them being follow ups I really think that whilst we're in the guts of this it would be good to leave the code in a better place than we found it if possible 🙏