From 6677a3f20e7e0ebea284184637559b0ff729188d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 25 May 2020 15:46:44 +0100 Subject: [PATCH] Implement Tabs with Automatic Activation pattern for EmojiPicker header Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/emojipicker/EmojiPicker.js | 4 ++ src/components/views/emojipicker/Header.js | 59 ++++++++++++++++++- src/i18n/strings/en_EN.json | 1 + 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/components/views/emojipicker/EmojiPicker.js b/src/components/views/emojipicker/EmojiPicker.js index cacc15a5f9..16a0fc67e7 100644 --- a/src/components/views/emojipicker/EmojiPicker.js +++ b/src/components/views/emojipicker/EmojiPicker.js @@ -147,8 +147,12 @@ class EmojiPicker extends React.Component { // We update this here instead of through React to avoid re-render on scroll. if (cat.visible) { cat.ref.current.classList.add("mx_EmojiPicker_anchor_visible"); + cat.ref.current.setAttribute("aria-selected", true); + cat.ref.current.setAttribute("tabindex", 0); } else { cat.ref.current.classList.remove("mx_EmojiPicker_anchor_visible"); + cat.ref.current.setAttribute("aria-selected", false); + cat.ref.current.setAttribute("tabindex", -1); } } } diff --git a/src/components/views/emojipicker/Header.js b/src/components/views/emojipicker/Header.js index b3951c9ea5..c53437e02d 100644 --- a/src/components/views/emojipicker/Header.js +++ b/src/components/views/emojipicker/Header.js @@ -18,20 +18,74 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from "classnames"; +import {_t} from "../../../languageHandler"; +import {Key} from "../../../Keyboard"; + class Header extends React.PureComponent { static propTypes = { categories: PropTypes.arrayOf(PropTypes.object).isRequired, onAnchorClick: PropTypes.func.isRequired, - refs: PropTypes.object, + }; + + findNearestEnabled(index, delta) { + index += this.props.categories.length; + const cats = [...this.props.categories, ...this.props.categories, ...this.props.categories]; + + while (index < cats.length && index >= 0) { + if (cats[index].enabled) return index % this.props.categories.length; + index += delta > 0 ? 1 : -1; + } + } + + changeCategoryRelative(delta) { + const current = this.props.categories.findIndex(c => c.visible); + this.changeCategoryAbsolute(current + delta, delta); + } + + changeCategoryAbsolute(index, delta=1) { + const category = this.props.categories[this.findNearestEnabled(index, delta)]; + if (category) { + this.props.onAnchorClick(category.id); + category.ref.current.focus(); + } + } + + // Implements ARIA Tabs with Automatic Activation pattern + // https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html + onKeyDown = (ev) => { + let handled = true; + switch (ev.key) { + case Key.ARROW_LEFT: + this.changeCategoryRelative(-1); + break; + case Key.ARROW_RIGHT: + this.changeCategoryRelative(1); + break; + + case Key.HOME: + this.changeCategoryAbsolute(0); + break; + case Key.END: + this.changeCategoryAbsolute(this.props.categories.length - 1, -1); + break; + default: + handled = false; + } + + if (handled) { + ev.preventDefault(); + ev.stopPropagation(); + } }; render() { return ( -