Skip to content
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

ColorPalette: Add an accessible color picker based on ChromePicker #10564

Merged
merged 38 commits into from
Oct 19, 2018

Conversation

ryelle
Copy link
Contributor

@ryelle ryelle commented Oct 12, 2018

This PR attempts to fork out the ChromePicker from react-color to make accessibility-related improvements. I've introduced a new component ColorPicker in components/src/color-picker, which is visually very similar to the existing color picker. All components were pulled from react-color, then updated to follow GB code standards, then updated with the accessibility improvements. cc @tofumatt

Current ChromePicker (on master)

chromepicker-master

New ColorPicker (this PR)

Hex input RGB input With alpha
color-picker-hex color-picker-rgb color-picker-w-alpha

I've kept the alpha functionality in ChromePicker, as it was referenced in #4726 that we might want to enable this.

The accessibility improvements I made are:

  • Text inputs are type=number so that arrow keys can increase/decrease values
  • Text inputs are correctly labelled, and grouped in fieldsets for RGB/HSL
  • Toggle input type button is labelled and a button so it supports keyboard interaction
  • Hue & Alpha bars have been implemented as sliders, and support arrow keys to increase/decrease values (along with shift+arrows, page up, page down, home, and end)
  • Saturation has 2 axis of data: up and down change brightness, while left + right change saturation. These also have keyboard support, but there isn't a matching aria role. Instead I've added a aria-describedby attribute to say "Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to increase saturation, and right to decrease saturation."

I've tested this on Mac/Chrome, Mac/Safari, Mac/Safari/VoiceOver, Windows/Firefox/NVDA. I'm personally having some trouble with the sliders when using a screen reader, but I have the same problems on the examples, so that might be user error (since I don't use a screen reader daily). I'm also open to the idea of changing the saturation interaction if there is a better role/pattern we can use.

The toggle handles themselves (the little circles that show position over the color graphs) don't have a defined focus style at the moment, so getting a designer's feedback there would be great 🙂 Same for the text inputs, too.

To test yourself:

  • Open the editor
  • Select a block with color settings, for example paragraph
  • In the block settings, open "Color settings"
  • Click on the multicolored "custom color picker" button
  • Interact with the color picker:
    • enter a hex color
    • attempt to change the color using just your keyboard (you can tab forward or shift+tab backwards between controls)
    • pick a color using your mouse (no regressions 🙂 )
    • toggle to RGB input & enter a color in that format
    • try inputting invalid color values

Checklist:

  • My code is tested. I haven't added tests yet, but I'm happy to add that if this approach is valid.
  • My code follows the WordPress code style
  • My code follows the accessibility standards.
  • My code has proper inline documentation.

This PR doesn't remove the react-color dependency, so we'll want to follow up with that (or I can add it here -- wasn't sure of the process).

@tofumatt
Copy link
Member

😮 This is epic, you are awesome!

I'll probably have to wait until the weekend to review this, but I'll look at it ASAP. Thank you several million for working on this! 👍

<div className="components-color-picker__inputs-toggle">
<IconButton
icon="arrow-down-alt2"
label="Toggle input type"
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this label be marked for translation?

@mtias
Copy link
Member

mtias commented Oct 13, 2018

This is great, thank you @ryelle!

@mtias mtias added this to the 4.1 milestone Oct 13, 2018
@mtias mtias added [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). [Feature] UI Components Impacts or related to the UI component system [Type] Enhancement A suggestion for improvement. labels Oct 13, 2018
Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

Awesome work here. I skimmed through the code (not deeply) and it's looking good to me.

Let's get rid of react-color as it's not used anymore. and yeah test would be great but I'm fine leaving for later to get this in the UI-freeze milestone. I'll defer to @tofumatt for accessibility validation.

@youknowriad
Copy link
Contributor

(Looks like there's an outdated snapshot causing the unit tests to fail)

@mtias
Copy link
Member

mtias commented Oct 15, 2018

Are some of our color utilities (contrast, etc) duplicating any of the libraries work (tinycolor2)? cc @jorgefilipecosta

packages/components/src/color-picker/alpha.js Outdated Show resolved Hide resolved
style={ gradient }
/>
{ /* eslint-disable jsx-a11y/no-static-element-interactions */ }
<div
Copy link
Member

Choose a reason for hiding this comment

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

I may be missing some context (as will the hypothetical future maintainer): For what reason do we need to create a fake range, vs. using <input type="range">?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This here was done to make the forking of react-color's ChromePicker as 1:1 as possible. Without looking into it more, I'm not sure if we can (as easily) style the gradient background on a range input.

packages/components/src/color-picker/alpha.js Outdated Show resolved Hide resolved
import { isValidHex } from './utils';
import TextControl from '../text-control';

/* Wrapper for TextControl, only used to handle intermediate state while typing. */
Copy link
Member

Choose a reason for hiding this comment

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

We have a similar need for this behavior in date picker fields. Should we extract as a common component?

Regardless, I'd tend to discourage having more than one component defined per file, if even just for breaking convention.

Copy link
Member

Choose a reason for hiding this comment

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

We should, but we should file a follow-up issue for that… I will set a reminder to do it; trying to fix issues in here first. 😄

Copy link
Member

Choose a reason for hiding this comment

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

Filed #10974 😄

this.handleKeyDown = this.handleKeyDown.bind( this );
}

componentWillReceiveProps( nextProps ) {
Copy link
Member

Choose a reason for hiding this comment

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

componentWillReceiveProps is effectively deprecated:

https://reactjs.org/docs/react-component.html#updating

Since we use StrictMode, this also logs an error in the console.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I saw that while I was working, but I'm not sure how to best handle this - getDerivedStateFromProps doesn't give me access to previous props, and I only want the state to update if the prop has changed (as that indicates a change from interacting with the sliders).

Copy link
Member

Choose a reason for hiding this comment

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

Would getSnapshotBeforeUpdate work? It has prevProps as the first argument. It seems to fire before DOM mutation and reading the docs that implies this.props inside that function would be the equivalent to nextProps here but I'm not sure 🤷‍♂️

packages/components/src/color-picker/inputs.js Outdated Show resolved Hide resolved
packages/components/src/color-picker/inputs.js Outdated Show resolved Hide resolved
@jorgefilipecosta
Copy link
Member

Are some of our color utilities (contrast, etc) duplicating any of the libraries work (tinycolor2)? cc @jorgefilipecosta

Hi @mtias, previously we already made use of tinycolor2 in the color utils we build (they were just specific wrappers around the lib). ChromePicker also used tinycolor2 (it was one of the reasons we decided to use it instead of other color libraries), this PR is forking ChromePicker and continues to use tinycolor2, I think there is no duplication happening.

Copy link
Member

@tofumatt tofumatt left a comment

Choose a reason for hiding this comment

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

Sorry it took me so long to get to this!

So I took this for a spin and the keyboard accessibility is next-level awesome. It's intuitive and I dig it. I'm going to bed soon so I'm gonna run through it with screen-readers tomorrow, but as noted the lack of focus styles on the sliders is the main visual issue I'm having with this. We definitely need those, from maybe @karmatosed or @sarahmonster? Failing that we can just hack something together because it's not clear when I have one focused, but the colour picker itself works a treat.

I'll give a run through with a screenreader tomorrow, but for now I'd say we need a focus style on the slider dots.

Copy link
Member

@tofumatt tofumatt left a comment

Choose a reason for hiding this comment

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

I tried this out with Firefox + NVDA and it largely worked pretty well, there are certainly plenty of little tweaks we could make, but I want to focus on the major ones for now. I've left inline comments everywhere I can so it's easier to address things.

One issue is that keyboard focus isn't trapped inside the modal, at least in Firefox + NVDA. Here's a video (contains audio from NVDA): https://www.dropbox.com/s/7ipcjfhn3gs9gdh/Screen%20Recording%202018-10-16%20at%2015.17.10.mov?dl=0

That's after tabbing to the control. You can here the aria-valuenow non-integer issue in there too.

Initial focus

One last issue is when I press Enter on the custom colour picker I don't have keyboard focus in the colour picker and can't control it. Or actually this could be related to the keyboard up/down/left/right not working in Firefox for the Hue/Lightness picker. It's hard to describe but I can send a screencast if needed, just let me know if I'm not being clear.

Overall though this is great. We should try to keep it simple for now and focus on what I've mentioned, I'll see if there's anything else after but this is so much better than before.

role="slider"
aria-valuemax="1"
aria-valuemin="0"
aria-valuenow={ rgb.a }
Copy link
Member

Choose a reason for hiding this comment

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

We should limit the precision of these when read aloud as they can get pretty long and it's frustrating to hear NVDA read off a lot of decimal places.

I actually wonder if what we should read off is an integer rather than a float; instead of aria-valuenow="0.57" we could have aria-valuenow="57% opacity"?

Actually all that said I don't see this selector being used anywhere.

Copy link
Contributor

Choose a reason for hiding this comment

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

aria-valuetext could be used to announce an extended text with an integer value but I'm not sure it's widely supported https://www.w3.org/TR/wai-aria-1.1/#aria-valuetext
I'd tend to agree an integer value would be the best option. Worth noting aria-valuenow can't be used for arbitrary text: https://www.w3.org/TR/wai-aria-1.1/#aria-valuenow

The value of aria-valuenow is a decimal number.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, right on. Since aria-valuetext is an alternative, even if not super-supported we could use that for the integer value and have aria-valuenow as the less-friendly-but-usable decimal number.

Copy link
Contributor

Choose a reason for hiding this comment

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

NVDA read off a lot of decimal places

Yeah (see below(, same for JAWS. They literally announce all those decimals:

screenshot 105

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, right on. Since aria-valuetext is an alternative, even if not super-supported we could use that for the integer value and have aria-valuenow as the less-friendly-but-usable decimal number.

I guess we can test it with the major browser / screen reader combos and if it has no good support revert to using aria-valuenow with the friendly value.

packages/components/src/color-picker/hue.js Outdated Show resolved Hide resolved
aria-valuemin="359"
aria-valuenow={ hsl.h }
aria-orientation="horizontal"
aria-label={ __( 'Hue value in degrees, from 0 to 359.' ) }
Copy link
Member

Choose a reason for hiding this comment

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

It could be handy to provide instructions in this label too: __( 'Hue value in degrees, from 0 to 359. Slide left or right to adjust.' ). Some controls are left/right and some are up/down/left/right.

Copy link
Contributor

Choose a reason for hiding this comment

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

labels: the shorter, the better 🙂 extra stuff should go in aria-descriptions or visible descriptions

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is a description necessary? up/down will also work for the 1-dimensional sliders (hue/alpha), and follows the expected behavior of a slider.

@sarahmonster
Copy link
Member

Here's a suggested solution for the focus styling:

2018-10-16 20 13 06

I'd also suggest applying a blue-outline focus styling to the "Toggle input type" drop-down button, which should maybe say "Change input type" rather than "toggle" as well, since toggle tends to refer to a binary switch and is represented by physical toggles in the UI.

@tofumatt
Copy link
Member

Those styles work for me. Can you push them to GitHub or paste them as CSS here so we can apply them?

@tofumatt
Copy link
Member

To clarify about focus not being trapped/keyboard up/down escaping the modal; here's a GIF of me pressing up/down/left/right (it's not logged on screen, sorry, but I'm doing it) a bunch in the colour picker after focusing it with the keyboard. You can see it's focused thanks to @sarahmonster's focus styles, but it's not moving and eventually you can notice elements behind the popover are being selected.

Do we need to use another method to trap the keyboard in that colour picker? @aduth, I think you know a bunch about our keyboard/focus traps–can you spot what's not working here?

(I'm in Firefox for Windows, btw.)

@ryelle
Copy link
Contributor Author

ryelle commented Oct 17, 2018

Thanks everyone for all the feedback 🙂 just a status update: I've been at a conference the last few days, but should have time later today to apply these changes.

@afercia
Copy link
Contributor

afercia commented Oct 17, 2018

@ryelle thanks so much for working on this. I've left some comments.

Additionally, seems the saturation "dot" button and the slider buttons don't work as expected with Safari + VoiceOver. On Windows, NVDA and JAWS seem not affected. As discussed on Slack, an option could be attaching a keydown event on the buttons and prevent the default (but also allowing tabbing away). That fixed it for me. Although screen readers tend to intercept keystrokes and events might not behave as expected, I wonder why there's the need to prevent a keydown default in this specific case ¯_(ツ)_/¯

Minor:
there are a few overflow: hidden values, including one from the popover component, that prevent the "dot" focus style to be fully visible when the dot is close to the edges of its container:

screen shot 2018-10-17 at 16 04 49

@afercia
Copy link
Contributor

afercia commented Oct 17, 2018

Forgot to mention I'd propose to consider to expand the input fields labels:
r, g, b, a

and
h, s, l, a

Having just one letter as accessible name of a control is not so ideal for all users, and is problematic for assistive technology users. On the other hand, I understand there's little space available. Maybe some CSS magic could help.

packages/components/src/color-picker/index.js Outdated Show resolved Hide resolved
packages/components/src/color-picker/utils.js Show resolved Hide resolved
packages/components/src/index.js Show resolved Hide resolved
@@ -0,0 +1,93 @@
/**
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a corresponding README file with a simple usage example so it could be exposed in both Gutenberg Handbook and Calypso DevDocs?
A similar example: https://github.com/WordPress/gutenberg/blob/master/packages/components/src/color-palette/README.md.

@gziolo
Copy link
Member

gziolo commented Oct 18, 2018

All components were pulled from react-color, then updated to follow GB code standards, then updated with the accessibility improvements.

We need to include credits in files that were ported from react-color. See one of the previous cases:
https://github.com/WordPress/gutenberg/blob/master/packages/element/src/serialize.js#L1-L26

@tofumatt
Copy link
Member

I'm not able to move the slider using a keyboard in Windows. Were you able to move the slider using the keyboard in Windows? Maybe it's just a bug in my VMWare setup. Here's me trying; note the focus leaves the colour picker:

2018-10-19 02 46 55

@tofumatt
Copy link
Member

Forgot to mention I'd propose to consider to expand the input fields labels:
r, g, b, a

and
h, s, l, a

Having just one letter as accessible name of a control is not so ideal for all users, and is problematic for assistive technology users. On the other hand, I understand there's little space available. Maybe some CSS magic could help.

This is a really good point, but the properly display these they'd need localisation, which is difficult for us to handle with string interpolation + React at the moment (see #9846). Unless we should always display H/S/L but have screen-reader elements in div alongside/as labels for each letter that explained the labels in a localised way… maybe that could work?

I that that's worth doing but could be done after this gets into the UI freeze. There are a fair few follow-up issues to be extracted from this PR, but they're better-handled as iterations as this PR is already getting pretty tough to manage because GitHub hides a lot of comments.

I will go through after it's merged and file follow-ups for anything we don't address here, but it's important we get this in, at least as a basis for iteration, for the UI freeze.

Copy link
Member

@tofumatt tofumatt left a comment

Choose a reason for hiding this comment

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

It looks like all that remains for this to be way better than what we have, an excellent basis for iteration, and something that covers most of the actual visual UI changes we'd want to make here, is to fix the package issues @gziolo mentioned. Let me know if there's anything I can do to help that part of the PR or to clarify anything else I might've missed. I tried to go through all the comments but it can sometimes be tough to tell in these big PRs what's left to do; just the way GitHub handles them 🤷‍♂️

If this is working in Windows for people I need to update my VMWare setup. It also means I think this is good to go and get merged.

If someone can confirm this works well for them on Windows and the new component pieces get added as per @gziolo's request I say we get this merged 👍

Thanks so much for all your work on this @ryelle; it's an epic patch and I am really pleased to see it. It was the big UI component in the Merge: Accessibility milestone I wasn't sure we'd manage to close; happy you proved my doubts wrong 😄

@gziolo
Copy link
Member

gziolo commented Oct 19, 2018

Something went wrong with merging master in. It needs to be fixed before we proceed.

Copy link
Member

@tofumatt tofumatt left a comment

Choose a reason for hiding this comment

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

For me, in Windows, the saturation/lightness control does not work with my keyboard. Focus is changed after keyboard input to things entirely outside the Colour Palette.

That said:

  • this works on Mac fine
  • keyboard users can use all of the controls except the saturation/lightness control (before they could use none)
  • we use speak() when the modes change

This is a starting point and should be improved upon going forward, but I want to get this merged for a few reasons:

  • future updates should not impact visual UI at all and the rest of the UI minimally (basically we just need to fix keyboard on Windows and improve the UI for screen reader users 😄)
  • this is on a fork rather than a branch and it's getting a bit difficult to deal with; we've already had some git user error that needed repair and I'd like to avoid future issues

I'll go through this and file the appropriate follow-up issues (or just make a PR if possible) that will land in 4.2.

Thanks all for your efforts here, this is way better. 👍

@tofumatt tofumatt merged commit 3fa55cb into WordPress:master Oct 19, 2018
@tofumatt
Copy link
Member

I'm going to file some follow-up issues here soon and link to them from here. We're just getting started! 😄

@afercia
Copy link
Contributor

afercia commented Oct 19, 2018

For me, in Windows, the saturation/lightness control does not work with my keyboard

Weird, for me it works. What browser / screen reader combinations are you testing with? please specify the versions.

@tofumatt
Copy link
Member

I would be happy if it's just me. I'm on a Windows 10, x64 VMWare image (from Modern.IE) running:

  • Firefox 62.0.3 (64-bit)
  • Edge 42.17134.1.0
  • latest Chrome

All whilst running NVDA 2018.3.2 and not running any screenreader software.

It happens in all of them, so maybe it is a VM bug...

@afercia
Copy link
Contributor

afercia commented Oct 19, 2018

I guess It's really your VM. I've re-tested on Windows 10 without a screen reader running:

  • Firefox latest
  • Chrome latest
  • Edge
  • IE 11
    they all work.

With a screen reader (NVDA, JAWS) running no, the saturation/lightness control does not work with a keyboard. It needs the fix suggested previously. Will prepare a quick PR.

antpb pushed a commit to antpb/gutenberg that referenced this pull request Oct 26, 2018
…ordPress#10564)

* Add color picker component

* Add color picker styles to build

* Update to buttons for focus handles

* Update styles

* Update class name

* Add color-picker component to exports

* Update name to ColorPicker

* Add labels to color input groups

* Fix describedby prop name

* Update view to use `getDerivedStateFromProps`

* Add “Input” component to handle intermediate state when entering values

Specifically needed for hex colors, which can be invalid while they’re being entered

* Add space around text inputs

* Translate toggle label

* Add focus styles to colourpicker pointers.

* Remove react-color dependency

* Add class name to better find the component in the devtools

* Add tinycolor2 dependency

* Add reference to react-color license & copyright

* Better label

* Update snapshot

* Decouple valueKey and label

so we can use semantic labels.

* Use KeyboardShortcuts component to avoid separate instances of Mousetrap

* Use createRef

* Normalize HSV / HSL values

H: 0 - 260
S, L, V: 0 - 100

* Omit `valueKey` from being passed to input

* Use createRef in all components

* Update saturation handle label

* Prevent key events on slider handles

Fixes the issue where VoiceOver grabs arrow-key input as navigation

* Use a focusable div for slider handle

* Apply focus style to alpha handle

* Add description to hue controls

* Add speak commands for view change

* Update snapshot

* Remove test as the component API surface changed

We have done some changes to the ChromePicker so this test
can no longer be replicated.

* Add README, tests and update Changelog

* Remove react-color dependency

* Add docs manifest file
@afercia afercia mentioned this pull request Jan 23, 2019
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] UI Components Impacts or related to the UI component system [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.