Add keybindings for color picker (#647)

* Add keybindings for color picker

This adds the ability to navigate using left/right/bottom/up keys and shows key bindings for all the different colors. This is only optimized for the qwerty keyboard layout, but unfortunately it's not possible to detect other keyboard layouts :(

* add aria-keyshortcuts and keybinding in title

* make focus select color, confirm on enter

Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
Christopher Chedeau 2020-02-01 12:56:15 +00:00 committed by GitHub
parent 1e4ce77612
commit 46791e6da1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 12 deletions

View File

@ -107,3 +107,11 @@
margin-right: 0.25rem;
border: 1px solid #dee2e6;
}
.color-picker-keybinding {
position: absolute;
bottom: 2px;
right: 2px;
font-size: 0.7em;
color: #ccc;
}

View File

@ -8,6 +8,14 @@ import { t } from "../i18n";
// This is a narrow reimplementation of the awesome react-color Twitter component
// https://github.com/casesandberg/react-color/blob/master/src/components/twitter/Twitter.js
// Unfortunately, we can't detect keyboard layout in the browser. So this will
// only work well for QWERTY but not AZERTY or others...
const keyBindings = [
["1", "2", "3", "4", "5"],
["q", "w", "e", "r", "t"],
["a", "s", "d", "f", "g"],
].flat();
const Picker = function({
colors,
color,
@ -22,12 +30,18 @@ const Picker = function({
label: string;
}) {
const firstItem = React.useRef<HTMLButtonElement>();
const activeItem = React.useRef<HTMLButtonElement>();
const gallery = React.useRef<HTMLDivElement>();
const colorInput = React.useRef<HTMLInputElement>();
React.useEffect(() => {
// After the component is first mounted
// focus on first input
if (firstItem.current) firstItem.current.focus();
if (activeItem.current) {
activeItem.current.focus();
} else if (firstItem.current) {
firstItem.current.focus();
}
}, []);
const handleKeyDown = (e: React.KeyboardEvent) => {
@ -44,10 +58,41 @@ const Picker = function({
e.preventDefault();
}
}
} else if (e.key === KEYS.ESCAPE) {
} else if (
e.key === KEYS.ARROW_RIGHT ||
e.key === KEYS.ARROW_LEFT ||
e.key === KEYS.ARROW_UP ||
e.key === KEYS.ARROW_DOWN
) {
const { activeElement } = document;
const index = Array.prototype.indexOf.call(
gallery!.current!.children,
activeElement,
);
if (index !== -1) {
const length = gallery!.current!.children.length;
const nextIndex =
e.key === KEYS.ARROW_RIGHT
? (index + 1) % length
: e.key === KEYS.ARROW_LEFT
? (length + index - 1) % length
: e.key === KEYS.ARROW_DOWN
? (index + 5) % length
: e.key === KEYS.ARROW_UP
? (length + index - 5) % length
: index;
(gallery!.current!.children![nextIndex] as any).focus();
}
e.preventDefault();
} else if (keyBindings.includes(e.key.toLowerCase())) {
const index = keyBindings.indexOf(e.key.toLowerCase());
(gallery!.current!.children![index] as any).focus();
e.preventDefault();
} else if (e.key === KEYS.ESCAPE || e.key === KEYS.ENTER) {
e.preventDefault();
onClose();
e.nativeEvent.stopImmediatePropagation();
}
e.nativeEvent.stopImmediatePropagation();
};
return (
@ -61,26 +106,37 @@ const Picker = function({
<div className="color-picker-triangle-shadow"></div>
<div className="color-picker-triangle"></div>
<div className="color-picker-content">
<div className="colors-gallery">
{colors.map((color, i) => (
<div
className="colors-gallery"
ref={el => {
if (el) gallery.current = el;
}}
>
{colors.map((_color, i) => (
<button
className="color-picker-swatch"
onClick={() => {
onChange(color);
onChange(_color);
}}
title={color}
aria-label={color}
style={{ backgroundColor: color }}
key={color}
title={`${_color}${keyBindings[i].toUpperCase()}`}
aria-label={_color}
aria-keyshortcuts={keyBindings[i]}
style={{ backgroundColor: _color }}
key={_color}
ref={el => {
if (i === 0 && el) firstItem.current = el;
if (el && i === 0) firstItem.current = el;
if (el && _color === color) activeItem.current = el;
}}
onFocus={() => {
onChange(_color);
}}
>
{color === "transparent" ? (
{_color === "transparent" ? (
<div className="color-picker-transparent"></div>
) : (
undefined
)}
<span className="color-picker-keybinding">{keyBindings[i]}</span>
</button>
))}
</div>