From cdbad7dabada1b8c2809b7c8479a7f94b6276b29 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Wed, 19 Jun 2019 18:13:11 +0200 Subject: [PATCH 01/12] WIP --- .../Types/ToolbarExampleEditor.shorthand.tsx | 14 +++- .../components/Toolbar/ToolbarRadioGroup.tsx | 67 +++++++++++++++++-- .../Toolbar/toolbarRadioGroupBehavior.ts | 31 +++++++++ .../Toolbar/toolbarRadioGroupItemBehavior.ts | 27 ++++++++ packages/react/src/lib/accessibility/index.ts | 4 ++ 5 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupBehavior.ts create mode 100644 packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts diff --git a/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx b/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx index 02690ae115..8195ab2fc6 100644 --- a/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx +++ b/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx @@ -124,11 +124,19 @@ const ToolbarExampleShorthand = () => { { key: 'font-size', icon: { name: 'font-size', outline: true } }, { key: 'remove-format', icon: { name: 'remove-format', outline: true } }, { key: 'divider2', kind: 'divider' }, + { + key: 'radiogroup', + kind: 'group', + items: [ + { key: 'bullets', icon: { name: 'bullets', outline: true } }, + { key: 'number-list', icon: { name: 'number-list', outline: true } }, + { key: 'to-do-list', icon: { name: 'to-do-list', outline: true } }, + ], + }, + { key: 'divider3', kind: 'divider' }, { key: 'outdent', icon: { name: 'outdent', outline: true } }, { key: 'indent', icon: { name: 'indent', outline: true } }, - { key: 'bullets', icon: { name: 'bullets', outline: true } }, - { key: 'number-list', icon: { name: 'number-list', outline: true } }, - { key: 'divider3', kind: 'divider' }, + { key: 'divider4', kind: 'divider' }, { key: 'more', icon: { name: 'more', outline: true } }, ]} /> diff --git a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx index 3820428499..9e82ca2af1 100644 --- a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx +++ b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx @@ -1,6 +1,7 @@ import * as React from 'react' import * as _ from 'lodash' import * as customPropTypes from '@stardust-ui/react-proptypes' +import { Ref } from '@stardust-ui/react-component-ref' import { ChildrenComponentProps, @@ -10,12 +11,13 @@ import { UIComponent, childrenExist, commonPropTypes, + applyAccessibilityKeyHandlers, } from '../../lib' import { mergeComponentVariables } from '../../lib/mergeThemes' import { ShorthandCollection, WithAsProp, withSafeTypeForAs } from '../../types' import { Accessibility } from '../../lib/accessibility/types' -import { defaultBehavior } from '../../lib/accessibility' +import { toolbarRadioGroupBehavior, toolbarRadioGroupItemBehavior } from '../../lib/accessibility' import ToolbarDivider from './ToolbarDivider' import ToolbarItem from './ToolbarItem' @@ -48,29 +50,86 @@ class ToolbarRadioGroup extends UIComponent> } static defaultProps = { - accessibility: defaultBehavior as Accessibility, + accessibility: toolbarRadioGroupBehavior as Accessibility, + } + + // when click on item - focus zone will handle focus + // left/right handled by focus zone + // when press up/down - identify current focused element - with tabIndex === 0 + + actionHandlers = { + nextItem: event => this.setFocusedItem(event, 1), + prevItem: event => this.setFocusedItem(event, -1), + } + + setFocusedItem(event, direction) { + const currentIndex = + _.findIndex(this.itemRefs, (item: React.RefObject) => { + return item.current.tabIndex === 0 + }) || 0 + + const itemsLength = this.itemRefs.length + let nextIndex = currentIndex + direction + + if (nextIndex >= itemsLength) { + nextIndex = 0 + } + + if (nextIndex < 0) { + nextIndex = itemsLength - 1 + } + + const nextItemToFocus = this.itemRefs[nextIndex].current + nextItemToFocus.focus() + + if (document.activeElement === nextItemToFocus) { + event.stopPropagation() + } + event.preventDefault() } handleItemOverrides = variables => predefinedProps => ({ variables: mergeComponentVariables(variables, predefinedProps.variables), }) + itemRefs: React.RefObject[] = [] + renderItems(items, variables) { const itemOverridesFn = this.handleItemOverrides(variables) + this.itemRefs = [] + return _.map(items, (item, index) => { const kind = _.get(item, 'kind', 'item') + const ref = React.createRef() + this.itemRefs[index] = ref + if (kind === 'divider') { return ToolbarDivider.create(item, { overrideProps: itemOverridesFn }) } - return ToolbarItem.create(item, { overrideProps: itemOverridesFn }) + + return ( + + {ToolbarItem.create(item, { + defaultProps: { + accessibility: toolbarRadioGroupItemBehavior, + }, + overrideProps: itemOverridesFn, + })} + + ) }) } renderComponent({ ElementType, classes, variables, accessibility, unhandledProps }) { const { children, items } = this.props return ( - + {childrenExist(children) ? children : this.renderItems(items, variables)} ) diff --git a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupBehavior.ts new file mode 100644 index 0000000000..0fedd4751e --- /dev/null +++ b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupBehavior.ts @@ -0,0 +1,31 @@ +import { Accessibility } from '../../types' +import * as keyboardKey from 'keyboard-key' + +/** + * @description + * Implements ARIA Radio Group design pattern. + * @specification + * Adds role='radiogroup'. This allows screen readers to handle the component as a radio group. + * Triggers 'nextItem' action with 'ArrowDown' or 'ArrowRight' on 'root'. + * Triggers 'prevItem' action with 'ArrowUp' or 'ArrowLeft' on 'root'. + */ +const toolbarRadioGroupBehavior: Accessibility = () => ({ + attributes: { + root: { + role: 'radiogroup', + }, + }, + + keyActions: { + root: { + nextItem: { + keyCombinations: [{ keyCode: keyboardKey.ArrowDown }], + }, + prevItem: { + keyCombinations: [{ keyCode: keyboardKey.ArrowUp }], + }, + }, + }, +}) + +export default toolbarRadioGroupBehavior diff --git a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts new file mode 100644 index 0000000000..878021dcbe --- /dev/null +++ b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts @@ -0,0 +1,27 @@ +import { Accessibility } from '../../types' + +/** + * @specification + * Adds role='radio'. This allows screen readers to handle the component as a radio button. + * Adds attribute 'aria-checked=true' based on the property 'checked'. + * Adds attribute 'aria-disabled=true' based on the property 'disabled'. This can be overriden by providing 'aria-disabled' property directly to the component. + * Implements roving tabIndex. + */ +const toolbarRadioGroupItemBehavior: Accessibility = props => ({ + attributes: { + root: { + role: 'radio', + 'aria-checked': props.active, + 'aria-disabled': props.disabled, + }, + }, +}) + +export default toolbarRadioGroupItemBehavior + +type ToolbarRadioGroupItemBehaviorProps = { + /** Indicates if radio item is selected. */ + active?: boolean + /** Indicates if radio item is disabled. */ + disabled?: boolean +} diff --git a/packages/react/src/lib/accessibility/index.ts b/packages/react/src/lib/accessibility/index.ts index 27cb2e2616..d3fbb850a5 100644 --- a/packages/react/src/lib/accessibility/index.ts +++ b/packages/react/src/lib/accessibility/index.ts @@ -25,6 +25,10 @@ export { default as menuItemAsToolbarButtonBehavior, } from './Behaviors/Toolbar/menuItemAsToolbarButtonBehavior' export { default as toolbarBehavior } from './Behaviors/Toolbar/toolbarBehavior' +export { default as toolbarRadioGroupBehavior } from './Behaviors/Toolbar/toolbarRadioGroupBehavior' +export { + default as toolbarRadioGroupItemBehavior, +} from './Behaviors/Toolbar/toolbarRadioGroupItemBehavior' export { default as radioGroupBehavior } from './Behaviors/Radio/radioGroupBehavior' export { default as radioGroupItemBehavior } from './Behaviors/Radio/radioGroupItemBehavior' export { default as popupBehavior } from './Behaviors/Popup/popupBehavior' From 3a3214d601485b60a6f5863339e48abe53efcca2 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Fri, 21 Jun 2019 10:28:50 +0200 Subject: [PATCH 02/12] Remove redundant comments --- packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx index 9e82ca2af1..a7db4119cf 100644 --- a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx +++ b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx @@ -53,10 +53,6 @@ class ToolbarRadioGroup extends UIComponent> accessibility: toolbarRadioGroupBehavior as Accessibility, } - // when click on item - focus zone will handle focus - // left/right handled by focus zone - // when press up/down - identify current focused element - with tabIndex === 0 - actionHandlers = { nextItem: event => this.setFocusedItem(event, 1), prevItem: event => this.setFocusedItem(event, -1), From 6c2f0b30d4c8c17f47fe73f8dbf8890c92e8fd36 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Fri, 21 Jun 2019 16:00:08 +0200 Subject: [PATCH 03/12] Add examples --- .../Types/ToolbarExampleEditor.shorthand.tsx | 43 +++++++++++++- .../Types/ToolbarExampleRadioGroup.steps.tsx | 3 + .../Types/ToolbarExampleRadioGroup.tsx | 58 +++++++++++++++++++ .../components/Toolbar/Types/index.tsx | 5 ++ 4 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.steps.tsx create mode 100644 docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.tsx diff --git a/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx b/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx index 8195ab2fc6..78d9d9c8e1 100644 --- a/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx +++ b/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx @@ -49,6 +49,10 @@ const ToolbarExampleShorthand = () => { const [highlightOpen, setHighlightOpen] = React.useState(false) const [fontColorActive, setFontColorActive] = React.useState(false) + const [bulletListActive, setBulletListActive] = React.useState(false) + const [numberListActive, setNumberListActive] = React.useState(false) + const [toDoListActive, setToDoListActive] = React.useState(false) + return ( { key: 'radiogroup', kind: 'group', items: [ - { key: 'bullets', icon: { name: 'bullets', outline: true } }, - { key: 'number-list', icon: { name: 'number-list', outline: true } }, - { key: 'to-do-list', icon: { name: 'to-do-list', outline: true } }, + { + key: 'bullets', + icon: { name: 'bullets', outline: true }, + active: bulletListActive, + onClick: () => { + setBulletListActive(!bulletListActive) + + // deselect other radio items + setNumberListActive(false) + setToDoListActive(false) + }, + }, + { + key: 'number-list', + icon: { name: 'number-list', outline: true }, + active: numberListActive, + onClick: () => { + setNumberListActive(!numberListActive) + + // deselect other radio items + setBulletListActive(false) + setToDoListActive(false) + }, + }, + { + key: 'to-do-list', + icon: { name: 'to-do-list', outline: true }, + active: toDoListActive, + onClick: () => { + setToDoListActive(!toDoListActive) + + // deselect other radio items + setBulletListActive(false) + setNumberListActive(false) + }, + }, ], }, { key: 'divider3', kind: 'divider' }, diff --git a/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.steps.tsx b/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.steps.tsx new file mode 100644 index 0000000000..81b7a6894d --- /dev/null +++ b/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.steps.tsx @@ -0,0 +1,3 @@ +const config: ScreenerTestsConfig = { themes: ['teams', 'teamsDark', 'teamsHighContrast'] } + +export default config diff --git a/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.tsx b/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.tsx new file mode 100644 index 0000000000..d45a832a90 --- /dev/null +++ b/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.tsx @@ -0,0 +1,58 @@ +import * as React from 'react' +import { Toolbar } from '@stardust-ui/react' + +const ToolbarExamplePopupShorthand = () => { + const [bulletListActive, setBulletListActive] = React.useState(false) + const [numberListActive, setNumberListActive] = React.useState(false) + const [toDoListActive, setToDoListActive] = React.useState(false) + return ( + { + setBulletListActive(!bulletListActive) + + // deselect other radio items + setNumberListActive(false) + setToDoListActive(false) + }, + }, + { + key: 'number-list', + icon: { name: 'number-list', outline: true }, + active: numberListActive, + onClick: () => { + setNumberListActive(!numberListActive) + + // deselect other radio items + setBulletListActive(false) + setToDoListActive(false) + }, + }, + { + key: 'to-do-list', + icon: { name: 'to-do-list', outline: true }, + active: toDoListActive, + onClick: () => { + setToDoListActive(!toDoListActive) + + // deselect other radio items + setBulletListActive(false) + setNumberListActive(false) + }, + }, + ], + }, + ]} + /> + ) +} + +export default ToolbarExamplePopupShorthand diff --git a/docs/src/examples/components/Toolbar/Types/index.tsx b/docs/src/examples/components/Toolbar/Types/index.tsx index 5e3ae32db9..5b0a81832f 100644 --- a/docs/src/examples/components/Toolbar/Types/index.tsx +++ b/docs/src/examples/components/Toolbar/Types/index.tsx @@ -15,6 +15,11 @@ const Types = () => ( } examplePath="components/Toolbar/Types/ToolbarExamplePopup" /> + Date: Fri, 21 Jun 2019 16:07:10 +0200 Subject: [PATCH 04/12] fix tests --- .../Behaviors/Toolbar/toolbarRadioGroupBehavior.ts | 4 ++-- .../Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts | 3 +-- packages/react/test/specs/behaviors/behavior-test.tsx | 4 ++++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupBehavior.ts index 0fedd4751e..61b1bec7c9 100644 --- a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupBehavior.ts +++ b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupBehavior.ts @@ -6,8 +6,8 @@ import * as keyboardKey from 'keyboard-key' * Implements ARIA Radio Group design pattern. * @specification * Adds role='radiogroup'. This allows screen readers to handle the component as a radio group. - * Triggers 'nextItem' action with 'ArrowDown' or 'ArrowRight' on 'root'. - * Triggers 'prevItem' action with 'ArrowUp' or 'ArrowLeft' on 'root'. + * Triggers 'nextItem' action with 'ArrowDown' on 'root'. + * Triggers 'prevItem' action with 'ArrowUp' on 'root'. */ const toolbarRadioGroupBehavior: Accessibility = () => ({ attributes: { diff --git a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts index 878021dcbe..3fc31e01a4 100644 --- a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts +++ b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts @@ -3,9 +3,8 @@ import { Accessibility } from '../../types' /** * @specification * Adds role='radio'. This allows screen readers to handle the component as a radio button. - * Adds attribute 'aria-checked=true' based on the property 'checked'. + * Adds attribute 'aria-checked=true' based on the property 'active'. * Adds attribute 'aria-disabled=true' based on the property 'disabled'. This can be overriden by providing 'aria-disabled' property directly to the component. - * Implements roving tabIndex. */ const toolbarRadioGroupItemBehavior: Accessibility = props => ({ attributes: { diff --git a/packages/react/test/specs/behaviors/behavior-test.tsx b/packages/react/test/specs/behaviors/behavior-test.tsx index 0a03d414ea..05cb6149f3 100644 --- a/packages/react/test/specs/behaviors/behavior-test.tsx +++ b/packages/react/test/specs/behaviors/behavior-test.tsx @@ -45,6 +45,8 @@ import { chatBehavior, chatMessageBehavior, toolbarBehavior, + toolbarRadioGroupBehavior, + toolbarRadioGroupItemBehavior, } from 'src/lib/accessibility' import { TestHelper } from './testHelper' import definitions from './testDefinitions' @@ -94,5 +96,7 @@ testHelper.addBehavior('accordionContentBehavior', accordionContentBehavior) testHelper.addBehavior('chatBehavior', chatBehavior) testHelper.addBehavior('chatMessageBehavior', chatMessageBehavior) testHelper.addBehavior('toolbarBehavior', toolbarBehavior) +testHelper.addBehavior('toolbarRadioGroupBehavior', toolbarRadioGroupBehavior) +testHelper.addBehavior('toolbarRadioGroupItemBehavior', toolbarRadioGroupItemBehavior) testHelper.run(behaviorMenuItems) From e6fe0a751cd17bb15b548d8a4ae985328fe428ea Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Sun, 23 Jun 2019 16:09:56 +0200 Subject: [PATCH 05/12] small improvements and tests --- .../Types/ToolbarExampleRadioGroup.tsx | 3 + .../components/Toolbar/ToolbarRadioGroup.tsx | 26 +++++-- .../Toolbar/ToolbarRadioGroup-test.tsx | 75 ++++++++++++++++++- 3 files changed, 96 insertions(+), 8 deletions(-) diff --git a/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.tsx b/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.tsx index d45a832a90..b7e2ed345c 100644 --- a/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.tsx +++ b/docs/src/examples/components/Toolbar/Types/ToolbarExampleRadioGroup.tsx @@ -23,6 +23,7 @@ const ToolbarExamplePopupShorthand = () => { setNumberListActive(false) setToDoListActive(false) }, + 'aria-label': 'bullet list', }, { key: 'number-list', @@ -35,6 +36,7 @@ const ToolbarExamplePopupShorthand = () => { setBulletListActive(false) setToDoListActive(false) }, + 'aria-label': 'number list', }, { key: 'to-do-list', @@ -47,6 +49,7 @@ const ToolbarExamplePopupShorthand = () => { setBulletListActive(false) setNumberListActive(false) }, + 'aria-label': 'to do list', }, ], }, diff --git a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx index a7db4119cf..d1bc3c6a79 100644 --- a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx +++ b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx @@ -20,7 +20,7 @@ import { Accessibility } from '../../lib/accessibility/types' import { toolbarRadioGroupBehavior, toolbarRadioGroupItemBehavior } from '../../lib/accessibility' import ToolbarDivider from './ToolbarDivider' -import ToolbarItem from './ToolbarItem' +import ToolbarItem, { ToolbarItemProps } from './ToolbarItem' export type ToolbarRadioGroupItemShorthandKinds = 'divider' | 'item' @@ -58,14 +58,26 @@ class ToolbarRadioGroup extends UIComponent> prevItem: event => this.setFocusedItem(event, -1), } - setFocusedItem(event, direction) { - const currentIndex = - _.findIndex(this.itemRefs, (item: React.RefObject) => { + setFocusedItem = (event, direction) => { + const { items } = this.props + + // filter items which are not disabled + const filteredRadioItems: React.RefObject[] = _.filter( + this.itemRefs, + (item, index) => { + const currentItem = items[index] as ToolbarItemProps + return currentItem && !currentItem.disabled + }, + ) + + // get the index of currently focused element (w/ tabindex = 0) or the first one as default + const currentFocusedIndex = + _.findIndex(filteredRadioItems, (item: React.RefObject) => { return item.current.tabIndex === 0 }) || 0 - const itemsLength = this.itemRefs.length - let nextIndex = currentIndex + direction + const itemsLength = filteredRadioItems.length + let nextIndex = currentFocusedIndex + direction if (nextIndex >= itemsLength) { nextIndex = 0 @@ -75,7 +87,7 @@ class ToolbarRadioGroup extends UIComponent> nextIndex = itemsLength - 1 } - const nextItemToFocus = this.itemRefs[nextIndex].current + const nextItemToFocus = filteredRadioItems[nextIndex].current nextItemToFocus.focus() if (document.activeElement === nextItemToFocus) { diff --git a/packages/react/test/specs/components/Toolbar/ToolbarRadioGroup-test.tsx b/packages/react/test/specs/components/Toolbar/ToolbarRadioGroup-test.tsx index 23b4ea037d..d7b90ebea5 100644 --- a/packages/react/test/specs/components/Toolbar/ToolbarRadioGroup-test.tsx +++ b/packages/react/test/specs/components/Toolbar/ToolbarRadioGroup-test.tsx @@ -1,13 +1,37 @@ -import { isConformant } from 'test/specs/commonTests' +import { isConformant, handlesAccessibility } from 'test/specs/commonTests' import ToolbarRadioGroup from 'src/components/Toolbar/ToolbarRadioGroup' import { ReactWrapper } from 'enzyme' import { mountWithProvider } from 'test/utils' import * as React from 'react' +const getShorthandItems = (props?: { disabledItem?: number; focusedItem?: number }) => [ + { + key: 'test-key1', + tabIndex: props && props.focusedItem === 0 ? 0 : -1, + disabled: props && props.disabledItem === 0, + }, + { + key: 'test-key2', + tabIndex: props && props.focusedItem === 1 ? 0 : -1, + disabled: props && props.disabledItem === 1, + }, + { + key: 'test-key3', + tabIndex: props && props.focusedItem === 2 ? 0 : -1, + disabled: props && props.disabledItem === 2, + }, +] + describe('ToolbarRadioGroup', () => { isConformant(ToolbarRadioGroup) + describe('accessibility', () => { + handlesAccessibility(ToolbarRadioGroup, { + defaultRootRole: 'radiogroup', + }) + }) + describe('variables', () => { function checkMergedVariables(toolbarRadioGroup: ReactWrapper): void { expect( @@ -67,4 +91,53 @@ describe('ToolbarRadioGroup', () => { checkMergedVariables(toolbarRadioGroup) }) }) + + describe('allows cycling between items using UP/DOWN arrow keys', () => { + const arrowUp = 38 + const arrowDown = 40 + const testKeyDown = (testName, items, keyCode, expectedFocusedIndex) => { + it(`keyDown test - ${testName}`, () => { + const radioButtons = mountWithProvider().find('button') + + const expectedActiveElement = radioButtons.at(expectedFocusedIndex).getDOMNode() + + expect(document.activeElement).not.toBe(expectedActiveElement) + + radioButtons.first().simulate('keyDown', { preventDefault() {}, keyCode, which: keyCode }) + + expect(document.activeElement).toBe(expectedActiveElement) + }) + } + + testKeyDown( + 'should move focus to next, second item', + getShorthandItems({ focusedItem: 0 }), + arrowDown, + 1, + ) + testKeyDown( + 'should move focus to next, third item', + getShorthandItems({ focusedItem: 1 }), + arrowDown, + 2, + ) + testKeyDown( + 'should move focus to previous, first item', + getShorthandItems({ focusedItem: 1 }), + arrowUp, + 0, + ) + testKeyDown( + 'should move focus to first item when the focused item is the last one', + getShorthandItems({ focusedItem: 2 }), + arrowDown, + 0, + ) + testKeyDown( + 'should move focus to last item when the focused item is the first one', + getShorthandItems({ focusedItem: 0 }), + arrowUp, + 2, + ) + }) }) From febabf9d366747e2b0caf744dbe73a3fd17809a0 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Sun, 23 Jun 2019 16:16:15 +0200 Subject: [PATCH 06/12] small improvement --- .../react/src/components/Toolbar/ToolbarRadioGroup.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx index d1bc3c6a79..f7e97c90fb 100644 --- a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx +++ b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx @@ -53,6 +53,8 @@ class ToolbarRadioGroup extends UIComponent> accessibility: toolbarRadioGroupBehavior as Accessibility, } + itemRefs: React.RefObject[] = [] + actionHandlers = { nextItem: event => this.setFocusedItem(event, 1), prevItem: event => this.setFocusedItem(event, -1), @@ -88,7 +90,9 @@ class ToolbarRadioGroup extends UIComponent> } const nextItemToFocus = filteredRadioItems[nextIndex].current - nextItemToFocus.focus() + if (nextItemToFocus) { + nextItemToFocus.focus() + } if (document.activeElement === nextItemToFocus) { event.stopPropagation() @@ -100,8 +104,6 @@ class ToolbarRadioGroup extends UIComponent> variables: mergeComponentVariables(variables, predefinedProps.variables), }) - itemRefs: React.RefObject[] = [] - renderItems(items, variables) { const itemOverridesFn = this.handleItemOverrides(variables) this.itemRefs = [] From f17e4945db186ae868256ce80d0b7f140f1e2f77 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Sun, 23 Jun 2019 16:29:20 +0200 Subject: [PATCH 07/12] add keyactions --- .../Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts index 3fc31e01a4..b1ae217f4f 100644 --- a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts +++ b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts @@ -1,4 +1,5 @@ import { Accessibility } from '../../types' +import buttonBehavior, { ButtonBehaviorProps } from '../Button/buttonBehavior' /** * @specification @@ -14,6 +15,7 @@ const toolbarRadioGroupItemBehavior: Accessibility Date: Sun, 23 Jun 2019 16:30:05 +0200 Subject: [PATCH 08/12] small improvement --- .../Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts index b1ae217f4f..b0f3620127 100644 --- a/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts +++ b/packages/react/src/lib/accessibility/Behaviors/Toolbar/toolbarRadioGroupItemBehavior.ts @@ -6,6 +6,7 @@ import buttonBehavior, { ButtonBehaviorProps } from '../Button/buttonBehavior' * Adds role='radio'. This allows screen readers to handle the component as a radio button. * Adds attribute 'aria-checked=true' based on the property 'active'. * Adds attribute 'aria-disabled=true' based on the property 'disabled'. This can be overriden by providing 'aria-disabled' property directly to the component. + * Triggers 'performClick' action with 'Enter' or 'Spacebar' on 'root'. */ const toolbarRadioGroupItemBehavior: Accessibility = props => ({ attributes: { From 258654a55403f8bdc24b3296c19611c70df0c8e9 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Mon, 24 Jun 2019 15:24:02 +0200 Subject: [PATCH 09/12] add aria label to example --- .../Toolbar/Types/ToolbarExampleEditor.shorthand.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx b/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx index 78d9d9c8e1..ccdd2955f8 100644 --- a/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx +++ b/docs/src/examples/components/Toolbar/Types/ToolbarExampleEditor.shorthand.tsx @@ -143,6 +143,7 @@ const ToolbarExampleShorthand = () => { setNumberListActive(false) setToDoListActive(false) }, + 'aria-label': 'bullet list', }, { key: 'number-list', @@ -155,6 +156,7 @@ const ToolbarExampleShorthand = () => { setBulletListActive(false) setToDoListActive(false) }, + 'aria-label': 'number list', }, { key: 'to-do-list', @@ -167,6 +169,7 @@ const ToolbarExampleShorthand = () => { setBulletListActive(false) setNumberListActive(false) }, + 'aria-label': 'to do list', }, ], }, From fe078e48b5bf9d2742d1a9ce618d61a85e707298 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Thu, 27 Jun 2019 11:43:04 +0200 Subject: [PATCH 10/12] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc5dc1dba..c2852cfa97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Features +- Add ARIA attributes and focus handling for `RadioGroup` in `Toolbar` @sophieH29 ([#1526](https://github.com/stardust-ui/react/pull/1526)) + ## [v0.34.0](https://github.com/stardust-ui/react/tree/v0.34.0) (2019-06-26) [Compare changes](https://github.com/stardust-ui/react/compare/v0.33.0...v0.34.0) From 8a47a62fc6cab7e3b07da014df8a1d488a967ffb Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Thu, 27 Jun 2019 12:08:33 +0200 Subject: [PATCH 11/12] small improvement --- .../Toolbar/ToolbarRadioGroup-test.tsx | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/react/test/specs/components/Toolbar/ToolbarRadioGroup-test.tsx b/packages/react/test/specs/components/Toolbar/ToolbarRadioGroup-test.tsx index d7b90ebea5..4fcb3695d3 100644 --- a/packages/react/test/specs/components/Toolbar/ToolbarRadioGroup-test.tsx +++ b/packages/react/test/specs/components/Toolbar/ToolbarRadioGroup-test.tsx @@ -5,24 +5,6 @@ import { ReactWrapper } from 'enzyme' import { mountWithProvider } from 'test/utils' import * as React from 'react' -const getShorthandItems = (props?: { disabledItem?: number; focusedItem?: number }) => [ - { - key: 'test-key1', - tabIndex: props && props.focusedItem === 0 ? 0 : -1, - disabled: props && props.disabledItem === 0, - }, - { - key: 'test-key2', - tabIndex: props && props.focusedItem === 1 ? 0 : -1, - disabled: props && props.disabledItem === 1, - }, - { - key: 'test-key3', - tabIndex: props && props.focusedItem === 2 ? 0 : -1, - disabled: props && props.disabledItem === 2, - }, -] - describe('ToolbarRadioGroup', () => { isConformant(ToolbarRadioGroup) @@ -95,6 +77,25 @@ describe('ToolbarRadioGroup', () => { describe('allows cycling between items using UP/DOWN arrow keys', () => { const arrowUp = 38 const arrowDown = 40 + + const getShorthandItems = (props?: { disabledItem?: number; focusedItem?: number }) => [ + { + key: 'test-key1', + tabIndex: props && props.focusedItem === 0 ? 0 : -1, + disabled: props && props.disabledItem === 0, + }, + { + key: 'test-key2', + tabIndex: props && props.focusedItem === 1 ? 0 : -1, + disabled: props && props.disabledItem === 1, + }, + { + key: 'test-key3', + tabIndex: props && props.focusedItem === 2 ? 0 : -1, + disabled: props && props.disabledItem === 2, + }, + ] + const testKeyDown = (testName, items, keyCode, expectedFocusedIndex) => { it(`keyDown test - ${testName}`, () => { const radioButtons = mountWithProvider().find('button') From b2abfd4c52fede02b1999c0a01e3bf294dccf9b2 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Thu, 27 Jun 2019 13:23:58 +0200 Subject: [PATCH 12/12] fix tests --- .../src/components/Toolbar/ToolbarRadioGroup.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx index f7e97c90fb..6f63ce2436 100644 --- a/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx +++ b/packages/react/src/components/Toolbar/ToolbarRadioGroup.tsx @@ -118,14 +118,16 @@ class ToolbarRadioGroup extends UIComponent> return ToolbarDivider.create(item, { overrideProps: itemOverridesFn }) } + const toolbarItem = ToolbarItem.create(item, { + defaultProps: { + accessibility: toolbarRadioGroupItemBehavior, + }, + overrideProps: itemOverridesFn, + }) + return ( - - {ToolbarItem.create(item, { - defaultProps: { - accessibility: toolbarRadioGroupItemBehavior, - }, - overrideProps: itemOverridesFn, - })} + + {toolbarItem} ) })