From 9603bb049d3f1fe4f89997d1c5dfa5e3f72a6a5e Mon Sep 17 00:00:00 2001 From: epiqueras Date: Sun, 13 Oct 2019 12:35:04 -0700 Subject: [PATCH 01/11] Components: Add a WAI-ARIA compliant custom select. --- package-lock.json | 28 +++- packages/components/package.json | 1 + .../components/src/custom-select/index.js | 123 ++++++++++++++++++ .../src/custom-select/stories/index.js | 30 +++++ .../components/src/custom-select/style.scss | 48 +++++++ packages/components/src/style.scss | 1 + 6 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 packages/components/src/custom-select/index.js create mode 100644 packages/components/src/custom-select/stories/index.js create mode 100644 packages/components/src/custom-select/style.scss diff --git a/package-lock.json b/package-lock.json index 8f777c41671e1..742b334e394bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7712,6 +7712,7 @@ "classnames": "^2.2.5", "clipboard": "^2.0.1", "dom-scroll-into-view": "^1.2.1", + "downshift": "^3.3.4", "lodash": "^4.17.15", "memize": "^1.0.5", "moment": "^2.22.1", @@ -12053,6 +12054,11 @@ } } }, + "compute-scroll-into-view": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.11.tgz", + "integrity": "sha512-uUnglJowSe0IPmWOdDtrlHXof5CTIJitfJEyITHBW6zDVOGu9Pjk5puaLM73SLcwak0L4hEjO7Td88/a6P5i7A==" + }, "computed-style": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz", @@ -14093,6 +14099,24 @@ "dotenv-defaults": "^1.0.2" } }, + "downshift": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-3.4.3.tgz", + "integrity": "sha512-lk0Q1VF4eTDe4EMzYtdVCPdu58ZRFyK3wxEAGUeKqPRDoHDgoS9/TaxW2w+hEbeh9yBMU2IKX8lQkNn6YTfZ4w==", + "requires": { + "@babel/runtime": "^7.4.5", + "compute-scroll-into-view": "^1.0.9", + "prop-types": "^15.7.2", + "react-is": "^16.9.0" + }, + "dependencies": { + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" + } + } + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -22079,7 +22103,7 @@ "dependencies": { "clone-deep": { "version": "0.2.4", - "resolved": "http://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", "dev": true, "requires": { @@ -22113,7 +22137,7 @@ "dependencies": { "kind-of": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", "dev": true, "requires": { diff --git a/packages/components/package.json b/packages/components/package.json index 3b1ebe1dd8125..15f5c06e598a4 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -37,6 +37,7 @@ "classnames": "^2.2.5", "clipboard": "^2.0.1", "dom-scroll-into-view": "^1.2.1", + "downshift": "^3.3.4", "lodash": "^4.17.15", "memize": "^1.0.5", "moment": "^2.22.1", diff --git a/packages/components/src/custom-select/index.js b/packages/components/src/custom-select/index.js new file mode 100644 index 0000000000000..609465f3e77d6 --- /dev/null +++ b/packages/components/src/custom-select/index.js @@ -0,0 +1,123 @@ +/** + * External dependencies + */ +import Downshift, { useSelect } from 'downshift'; +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { Button, Dashicon } from '../'; + +const itemToString = ( item ) => item.name; +// This is needed so that in Windows, where +// the menu does not necessarily open on +// key up/down, you can still switch between +// options with the menu closed. +const stateReducer = ( + { selectedItem }, + { type, changes, props: { items } } +) => { + switch ( type ) { + case Downshift.stateChangeTypes.ToggleButtonKeyDownArrowDown: + // If we already have a selected item, try to select the next one, + // without circular navigation. Otherwise, select the first item. + return { + ...changes, + selectedItem: + items[ + selectedItem ? + Math.min( items.indexOf( selectedItem ) + 1, items.length - 1 ) : + 0 + ], + }; + case Downshift.stateChangeTypes.ToggleButtonKeyDownArrowUp: + // If we already have a selected item, try to select the previous one, + // without circular navigation. Otherwise, select the last item. + return { + ...changes, + selectedItem: + items[ + selectedItem ? + Math.max( items.indexOf( selectedItem ) - 1, 0 ) : + items.length - 1 + ], + }; + default: + return changes; + } +}; +export default function CustomSelect( { label, items } ) { + const { + getLabelProps, + getToggleButtonProps, + getMenuProps, + getItemProps, + isOpen, + highlightedIndex, + selectedItem, + } = useSelect( { + initialSelectedItem: items[ 0 ], + items, + itemToString, + stateReducer, + } ); + const menuProps = getMenuProps( { + className: 'components-custom-select__menu', + } ); + // We need this here, because the null active descendant is not + // fully ARIA compliant. + if ( + menuProps[ 'aria-activedescendant' ] && + menuProps[ 'aria-activedescendant' ].slice( 0, 'downshift-null'.length ) === + 'downshift-null' + ) { + delete menuProps[ 'aria-activedescendant' ]; + } + return ( +
+ { /* eslint-disable-next-line jsx-a11y/label-has-associated-control, jsx-a11y/label-has-for */ } + + { label } + + + +
+ ); +} diff --git a/packages/components/src/custom-select/stories/index.js b/packages/components/src/custom-select/stories/index.js new file mode 100644 index 0000000000000..83310d9e26121 --- /dev/null +++ b/packages/components/src/custom-select/stories/index.js @@ -0,0 +1,30 @@ +/** + * Internal dependencies + */ +import CustomSelect from '../'; + +export default { title: 'CustomSelect', component: CustomSelect }; + +const items = [ + { + key: 'small', + name: 'Small', + style: { fontSize: '50%' }, + }, + { + key: 'normal', + name: 'Normal', + style: { fontSize: '100%' }, + }, + { + key: 'large', + name: 'Large', + style: { fontSize: '200%' }, + }, + { + key: 'huge', + name: 'Huge', + style: { fontSize: '300%' }, + }, +]; +export const _default = () => ; diff --git a/packages/components/src/custom-select/style.scss b/packages/components/src/custom-select/style.scss new file mode 100644 index 0000000000000..68b17d4b814c6 --- /dev/null +++ b/packages/components/src/custom-select/style.scss @@ -0,0 +1,48 @@ +.components-custom-select { + color: $dark-gray-500; +} + +.components-custom-select__label { + display: block; + margin-bottom: 5px; +} + +.components-custom-select__button { + border: 1px solid $dark-gray-200; + border-radius: 4px; + color: $dark-gray-500; + min-height: 28px; + min-width: 130px; + position: relative; + + &:focus { + border-color: $blue-medium-500; + } + + &-icon { + height: 100%; + padding: 0 8px; + position: absolute; + right: 0; + top: 0; + } +} + +.components-custom-select__menu { + padding: 0; +} + +.components-custom-select__item { + align-items: center; + display: flex; + list-style-type: none; + padding: 10px 5px 10px 25px; + + &.is-highlighted { + background: $light-gray-500; + } + + &-icon { + margin-left: -25px; + } +} diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index ec1b58335fedb..dbc80b743351f 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -7,6 +7,7 @@ @import "./circular-option-picker/style.scss"; @import "./color-indicator/style.scss"; @import "./color-picker/style.scss"; +@import "./custom-select/style.scss"; @import "./dashicon/style.scss"; @import "./date-time/style.scss"; @import "./dimension-control/style.scss"; From fa15dda6eae292c3d6fc7ce0d4e61eff80abb637 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Mon, 14 Oct 2019 11:40:14 -0700 Subject: [PATCH 02/11] Custom Select: Fix Downshift constant references. --- packages/components/src/custom-select/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/src/custom-select/index.js b/packages/components/src/custom-select/index.js index 609465f3e77d6..8cfa41ff26690 100644 --- a/packages/components/src/custom-select/index.js +++ b/packages/components/src/custom-select/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import Downshift, { useSelect } from 'downshift'; +import { useSelect } from 'downshift'; import classnames from 'classnames'; /** @@ -19,7 +19,7 @@ const stateReducer = ( { type, changes, props: { items } } ) => { switch ( type ) { - case Downshift.stateChangeTypes.ToggleButtonKeyDownArrowDown: + case useSelect.stateChangeTypes.ToggleButtonKeyDownArrowDown: // If we already have a selected item, try to select the next one, // without circular navigation. Otherwise, select the first item. return { @@ -31,7 +31,7 @@ const stateReducer = ( 0 ], }; - case Downshift.stateChangeTypes.ToggleButtonKeyDownArrowUp: + case useSelect.stateChangeTypes.ToggleButtonKeyDownArrowUp: // If we already have a selected item, try to select the previous one, // without circular navigation. Otherwise, select the last item. return { From d08dcb3ae8c77fd83f698733bf247836f31ef496 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Mon, 14 Oct 2019 14:30:55 -0700 Subject: [PATCH 03/11] Custom Select: Add explicit aria-label to button and export component. --- packages/components/src/custom-select/index.js | 1 + packages/components/src/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/components/src/custom-select/index.js b/packages/components/src/custom-select/index.js index 8cfa41ff26690..d078b8ac04268 100644 --- a/packages/components/src/custom-select/index.js +++ b/packages/components/src/custom-select/index.js @@ -84,6 +84,7 @@ export default function CustomSelect( { label, items } ) { +
    + +
    + +
    - +
      +
        +
          + +`;