From 39ca2e2151b70997b38349fe1bca4cb052ea180d Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Fri, 23 Nov 2018 11:29:28 +0100 Subject: [PATCH 01/16] work in progress --- docs/src/components/Sidebar/Sidebar.tsx | 8 ++ .../ChatMessageWithPopover.tsx | 51 ++++++++ .../chatMessageWithPopover/Popover.tsx | 113 ++++++++++++++++++ .../chatMessageWithPopover/index.tsx | 1 + docs/src/routes.tsx | 6 + src/components/Menu/Menu.tsx | 1 + .../Behaviors/Chat/chatMessageBehavior.ts | 1 + .../Behaviors/Toolbar/toolbarBehavior.ts | 4 +- 8 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx create mode 100644 docs/src/prototypes/chatMessageWithPopover/Popover.tsx create mode 100644 docs/src/prototypes/chatMessageWithPopover/index.tsx diff --git a/docs/src/components/Sidebar/Sidebar.tsx b/docs/src/components/Sidebar/Sidebar.tsx index 9d933d42d0..ef0ffb0285 100644 --- a/docs/src/components/Sidebar/Sidebar.tsx +++ b/docs/src/components/Sidebar/Sidebar.tsx @@ -254,6 +254,14 @@ class Sidebar extends React.Component { Chat Pane + + Chat message with popover + + + Link Hover me to see the actions Some Link + + } + avatar={janeAvatar} + onFocus={handleFocus} + onBlur={handleBlur} + /> + ) + } +} + +const CustomChat = () => ( + }, + { key: 'b', content: }, + { key: 'c', content: }, + ]} + /> +) + +export default CustomChat diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx new file mode 100644 index 0000000000..7b3b6c25a3 --- /dev/null +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -0,0 +1,113 @@ +import * as React from 'react' +import { Menu, Popup, toolbarBehavior, popupFocusTrapBehavior } from '@stardust-ui/react' + +export interface PopoverProps { + className?: string +} + +class Popover extends React.Component { + render() { + return ( + + ) + } +} + +export default Popover + +const renderContextMenu = (MenuItem, props) => { + if (props.icon !== 'ellipsis horizontal') { + return + } + + return ( + + + + } + > + + + ) +} + +export const handleFocus = e => { + const currentTarget = e.currentTarget + const target = e.target + + if (currentTarget === target || currentTarget.contains(target)) { + currentTarget.classList.add('focused') + } else { + currentTarget.classList.remove('focused') + } +} + +export const handleBlur = e => { + const currentTarget = e.currentTarget + const relatedTarget = e.relatedTarget + + if (currentTarget.contains(relatedTarget)) { + if (!currentTarget.classList.contains('focused')) { + currentTarget.classList.add('focused') + } + } else { + currentTarget.classList.remove('focused') + } +} diff --git a/docs/src/prototypes/chatMessageWithPopover/index.tsx b/docs/src/prototypes/chatMessageWithPopover/index.tsx new file mode 100644 index 0000000000..ed301ef5b2 --- /dev/null +++ b/docs/src/prototypes/chatMessageWithPopover/index.tsx @@ -0,0 +1 @@ +export { default } from './ChatMessageWithPopover' diff --git a/docs/src/routes.tsx b/docs/src/routes.tsx index 2fd45732d5..2bf8da67a7 100644 --- a/docs/src/routes.tsx +++ b/docs/src/routes.tsx @@ -28,6 +28,12 @@ const Router = () => ( path="/prototype-chat-pane" component={require('./prototypes/chatPane/index').default} />, + , , any> { handleItemOverrides = predefinedProps => ({ onClick: (e, itemProps) => { + // const props = itemProps || predefinedProps const { index } = itemProps this.trySetState({ activeIndex: index }) diff --git a/src/lib/accessibility/Behaviors/Chat/chatMessageBehavior.ts b/src/lib/accessibility/Behaviors/Chat/chatMessageBehavior.ts index f9a769d522..becfbf6a52 100644 --- a/src/lib/accessibility/Behaviors/Chat/chatMessageBehavior.ts +++ b/src/lib/accessibility/Behaviors/Chat/chatMessageBehavior.ts @@ -23,6 +23,7 @@ const chatMessageBehavior: Accessibility = (props: any) => ({ handleTabKey: FocusZoneTabbableElements.all, isCircularNavigation: true, direction: FocusZoneDirection.vertical, + shouldEnterInnerZone: event => keyboardKey.getCode(event) === keyboardKey.Enter, }, }, keyActions: { diff --git a/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts b/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts index d7126bb2fd..358c41b17a 100644 --- a/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts +++ b/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts @@ -9,13 +9,15 @@ const toolbarBehavior: Accessibility = (props: any) => ({ attributes: { root: { role: 'toolbar', + 'data-is-focusable': true, }, }, focusZone: { - mode: FocusZoneMode.Wrap, + mode: FocusZoneMode.Embed, props: { isCircularNavigation: false, preventDefaultWhenHandled: true, + shouldFocusFirstElementWhenReceivedFocus: true, }, }, }) From 8631f9550847a61e03092d88b554e55d58b0d56d Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Fri, 23 Nov 2018 14:16:23 +0100 Subject: [PATCH 02/16] WIP --- docs/src/prototypes/chatMessageWithPopover/Popover.tsx | 1 + src/components/Menu/Menu.tsx | 1 - src/components/Popup/Popup.tsx | 4 ++-- src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index 7b3b6c25a3..ab4029f85c 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -44,6 +44,7 @@ class Popover extends React.Component { onFocus={handleFocus} onBlur={handleBlur} accessibility={toolbarBehavior} + data-is-focusable={true} /> ) } diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 7d5a0e4e03..5421bcba49 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -105,7 +105,6 @@ class Menu extends AutoControlledComponent, any> { handleItemOverrides = predefinedProps => ({ onClick: (e, itemProps) => { - // const props = itemProps || predefinedProps const { index } = itemProps this.trySetState({ activeIndex: index }) diff --git a/src/components/Popup/Popup.tsx b/src/components/Popup/Popup.tsx index b1001185e6..c2c974f4dc 100644 --- a/src/components/Popup/Popup.tsx +++ b/src/components/Popup/Popup.tsx @@ -209,9 +209,9 @@ export default class Popup extends AutoControlledComponent {React.cloneElement(triggerElement, { - onClick: e => { + onClick: (e, ...rest) => { this.trySetOpen(!this.state.open, e) - _.invoke(triggerElement, 'props.onClick', e) + _.invoke(triggerElement, 'props.onClick', e, ...rest) }, ...accessibility.attributes.trigger, ...accessibility.keyHandlers.trigger, diff --git a/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts b/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts index 358c41b17a..40e07cf7af 100644 --- a/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts +++ b/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts @@ -9,7 +9,6 @@ const toolbarBehavior: Accessibility = (props: any) => ({ attributes: { root: { role: 'toolbar', - 'data-is-focusable': true, }, }, focusZone: { From 10b0b296640ef8c5872c56b060e25d9acd4dfcf1 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Fri, 23 Nov 2018 15:13:59 +0100 Subject: [PATCH 03/16] Improvements to menu behaviors --- .../Behaviors/Menu/menuBehavior.ts | 5 +- .../Behaviors/Tab/tabListBehavior.ts | 5 +- .../Behaviors/Toolbar/toolbarBehavior.ts | 5 +- test/specs/behaviors/testDefinitions.ts | 92 ++++++++++++++----- 4 files changed, 79 insertions(+), 28 deletions(-) diff --git a/src/lib/accessibility/Behaviors/Menu/menuBehavior.ts b/src/lib/accessibility/Behaviors/Menu/menuBehavior.ts index 2a87c0c234..377936f75f 100644 --- a/src/lib/accessibility/Behaviors/Menu/menuBehavior.ts +++ b/src/lib/accessibility/Behaviors/Menu/menuBehavior.ts @@ -6,7 +6,7 @@ import { Accessibility, FocusZoneMode } from '../../types' * * @specification * Adds role='menu'. - * Wraps component in FocusZone allowing circular arrow key navigation through the children of the component. + * Embeds FocusZone into component allowing circular arrow key navigation through the children of the component. */ const menuBehavior: Accessibility = (props: any) => ({ @@ -16,10 +16,11 @@ const menuBehavior: Accessibility = (props: any) => ({ }, }, focusZone: { - mode: FocusZoneMode.Wrap, + mode: FocusZoneMode.Embed, props: { isCircularNavigation: true, preventDefaultWhenHandled: true, + shouldFocusFirstElementWhenReceivedFocus: true, }, }, }) diff --git a/src/lib/accessibility/Behaviors/Tab/tabListBehavior.ts b/src/lib/accessibility/Behaviors/Tab/tabListBehavior.ts index 4f8b3123cb..994530e795 100644 --- a/src/lib/accessibility/Behaviors/Tab/tabListBehavior.ts +++ b/src/lib/accessibility/Behaviors/Tab/tabListBehavior.ts @@ -3,7 +3,7 @@ import { Accessibility, FocusZoneMode } from '../../types' /** * @specification * Adds role 'tablist' to 'root' component's part. - * Wraps component in FocusZone allowing arrow key navigation through the children of the component. + * Embeds FocusZone into component allowing arrow key navigation through the children of the component. */ const tabListBehavior: Accessibility = (props: any) => ({ attributes: { @@ -12,10 +12,11 @@ const tabListBehavior: Accessibility = (props: any) => ({ }, }, focusZone: { - mode: FocusZoneMode.Wrap, + mode: FocusZoneMode.Embed, props: { isCircularNavigation: false, preventDefaultWhenHandled: true, + shouldFocusFirstElementWhenReceivedFocus: true, }, }, }) diff --git a/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts b/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts index d7126bb2fd..64bcef199b 100644 --- a/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts +++ b/src/lib/accessibility/Behaviors/Toolbar/toolbarBehavior.ts @@ -3,7 +3,7 @@ import { Accessibility, FocusZoneMode } from '../../types' /** * @specification * Adds role 'toolbar' to 'root' component's part. - * Wraps component in FocusZone allowing arrow key navigation through the children of the component. + * Embeds FocusZone into component allowing arrow key navigation through the children of the component. */ const toolbarBehavior: Accessibility = (props: any) => ({ attributes: { @@ -12,10 +12,11 @@ const toolbarBehavior: Accessibility = (props: any) => ({ }, }, focusZone: { - mode: FocusZoneMode.Wrap, + mode: FocusZoneMode.Embed, props: { isCircularNavigation: false, preventDefaultWhenHandled: true, + shouldFocusFirstElementWhenReceivedFocus: true, }, }, }) diff --git a/test/specs/behaviors/testDefinitions.ts b/test/specs/behaviors/testDefinitions.ts index f944ddb400..8015733f50 100644 --- a/test/specs/behaviors/testDefinitions.ts +++ b/test/specs/behaviors/testDefinitions.ts @@ -1,4 +1,5 @@ import { TestDefinition, TestMethod, TestHelper } from './testHelper' +import { FocusZoneMode, FocusZoneDefinition } from '../../../src/lib/accessibility/types' import * as keyboardKey from 'keyboard-key' const definitions: TestDefinition[] = [] @@ -181,22 +182,57 @@ definitions.push({ }, }) +// Embeds FocusZone into component allowing arrow key navigation through the children of the component. +definitions.push({ + regexp: /Embeds FocusZone into component allowing arrow key navigation through the children of the component\.+/g, + testMethod: (parameters: TestMethod) => { + const actualFocusZone = parameters.behavior({}).focusZone + + const expectedFocusZone: FocusZoneDefinition = { + mode: FocusZoneMode.Embed, + props: { + isCircularNavigation: false, + preventDefaultWhenHandled: true, + }, + } + + verifyFocusZones(expectedFocusZone, actualFocusZone) + }, +}) + +// [Circular navigation] Embeds FocusZone into component allowing circular arrow key navigation through the children of the component. +definitions.push({ + regexp: /Embeds FocusZone into component allowing circular arrow key navigation through the children of the component\.+/g, + testMethod: (parameters: TestMethod) => { + const actualFocusZone = parameters.behavior({}).focusZone + + const expectedFocusZone: FocusZoneDefinition = { + mode: FocusZoneMode.Embed, + props: { + isCircularNavigation: true, + preventDefaultWhenHandled: true, + }, + } + + verifyFocusZones(expectedFocusZone, actualFocusZone) + }, +}) + // Wraps component in FocusZone allowing arrow key navigation through the children of the component. definitions.push({ regexp: /Wraps component in FocusZone allowing arrow key navigation through the children of the component\.+/g, testMethod: (parameters: TestMethod) => { - const property = { - isCircularNavigation: undefined, - preventDefaultWhenHandled: undefined, + const actualFocusZone = parameters.behavior({}).focusZone + + const expectedFocusZone: FocusZoneDefinition = { + mode: FocusZoneMode.Wrap, + props: { + isCircularNavigation: false, + preventDefaultWhenHandled: true, + }, } - const expectedMode = parameters.behavior(property).focusZone.mode - const expectedIsCircularNav = parameters.behavior(property).focusZone.props.isCircularNavigation - const expectedPreventDefault = parameters.behavior(property).focusZone.props - .preventDefaultWhenHandled - - expect(expectedMode).toBe(1) - expect(expectedIsCircularNav).toBe(false) - expect(expectedPreventDefault).toBe(true) + + verifyFocusZones(expectedFocusZone, actualFocusZone) }, }) @@ -204,21 +240,33 @@ definitions.push({ definitions.push({ regexp: /Wraps component in FocusZone allowing circular arrow key navigation through the children of the component\.+/g, testMethod: (parameters: TestMethod) => { - const property = { - isCircularNavigation: undefined, - preventDefaultWhenHandled: undefined, + const actualFocusZone = parameters.behavior({}).focusZone + + const expectedFocusZone: FocusZoneDefinition = { + mode: FocusZoneMode.Wrap, + props: { + isCircularNavigation: true, + preventDefaultWhenHandled: true, + }, } - const expectedMode = parameters.behavior(property).focusZone.mode - const expectedIsCircularNav = parameters.behavior(property).focusZone.props.isCircularNavigation - const expectedPreventDefault = parameters.behavior(property).focusZone.props - .preventDefaultWhenHandled - - expect(expectedMode).toBe(1) - expect(expectedIsCircularNav).toBe(true) - expect(expectedPreventDefault).toBe(true) + + verifyFocusZones(expectedFocusZone, actualFocusZone) }, }) +function verifyFocusZones( + expectedFocusZone: FocusZoneDefinition, + actualFocusZone: FocusZoneDefinition, +) { + expect(expectedFocusZone.mode).toBe(actualFocusZone.mode) + expect(expectedFocusZone.props.isCircularNavigation).toBe( + actualFocusZone.props.isCircularNavigation, + ) + expect(expectedFocusZone.props.preventDefaultWhenHandled).toBe( + actualFocusZone.props.preventDefaultWhenHandled, + ) +} + // [FocusTrapZone] Traps focus inside component definitions.push({ regexp: /Traps focus inside component/, From 499081c1d8599a94ddbd062a4570741f3d20ae71 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Mon, 26 Nov 2018 12:32:24 +0100 Subject: [PATCH 04/16] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5480bd1a5b..1690fc043d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Features - Add `renderComponent` function in the public API @mnajdova ([#503](https://github.com/stardust-ui/react/pull/503)) - Apply `dir=auto` attribute to string content of `Text` @kuzhelov ([#5](https://github.com/stardust-ui/react/pull/5)) +- Improve `Menu` accessibility behaviors @sophieH29 ([#523](https://github.com/stardust-ui/react/pull/523)) ### Fixes - Fix the behaviour of `AutoControlledComponent` when `undefined` is passed as a prop value @layershifter ([#499](https://github.com/stardust-ui/react/pull/499)) From 01c020cf7d6431ed739172bbe8de769dcdd281d7 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Mon, 26 Nov 2018 14:19:39 +0100 Subject: [PATCH 05/16] Small improvement --- .../src/prototypes/chatMessageWithPopover/Popover.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index ab4029f85c..2472b084e2 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -62,6 +62,7 @@ const renderContextMenu = (MenuItem, props) => { key={props.key} position="below" accessibility={popupFocusTrapBehavior} + trigger={} content={
{ />
} - > - - + /> ) } export const handleFocus = e => { const currentTarget = e.currentTarget - const target = e.target - - if (currentTarget === target || currentTarget.contains(target)) { + if (!currentTarget.classList.contains('focused')) { currentTarget.classList.add('focused') - } else { - currentTarget.classList.remove('focused') } } From 3b084eca62e50c4aad41b2dc5889864706ac0572 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Mon, 26 Nov 2018 15:09:58 +0100 Subject: [PATCH 06/16] Small improvement --- .../ChatMessageWithPopover.tsx | 17 +++++++++++------ .../chatMessageWithPopover/Popover.tsx | 9 +++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx index f0a274b6cc..d32585fcd8 100644 --- a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx @@ -21,15 +21,20 @@ class ChatMessageWithPopover extends React.Component { ':hover .actions': { opacity: 1, }, + '& a': { + color: '#6264A7', + }, }} author="Jane Doe" timestamp="Yesterday, 10:15 PM" - content={ - <> - - Link Hover me to see the actions Some Link - - } + content={{ + content: ( +
+ + Link Hover me to see the actions Some Link +
+ ), + }} avatar={janeAvatar} onFocus={handleFocus} onBlur={handleBlur} diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index 2472b084e2..d6545ac609 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -30,6 +30,15 @@ class Popover extends React.Component { '&:hover .smile-emoji': { display: 'flex', }, + + '& a:focus': { + textDecoration: 'none', + color: 'inherit', + }, + + '& a': { + color: 'inherit', + }, }} iconOnly className={this.props.className} From d1c14f282980aa2df8b857a3119fe38915728932 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Mon, 26 Nov 2018 15:22:13 +0100 Subject: [PATCH 07/16] Small improvement --- src/lib/accessibility/Behaviors/Chat/chatMessageBehavior.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/accessibility/Behaviors/Chat/chatMessageBehavior.ts b/src/lib/accessibility/Behaviors/Chat/chatMessageBehavior.ts index becfbf6a52..f9a769d522 100644 --- a/src/lib/accessibility/Behaviors/Chat/chatMessageBehavior.ts +++ b/src/lib/accessibility/Behaviors/Chat/chatMessageBehavior.ts @@ -23,7 +23,6 @@ const chatMessageBehavior: Accessibility = (props: any) => ({ handleTabKey: FocusZoneTabbableElements.all, isCircularNavigation: true, direction: FocusZoneDirection.vertical, - shouldEnterInnerZone: event => keyboardKey.getCode(event) === keyboardKey.Enter, }, }, keyActions: { From 1259d8bdbf02b1fee96d77f153a452f50c8241dc Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Tue, 27 Nov 2018 13:50:43 +0100 Subject: [PATCH 08/16] Adressed CR comments --- .../ChatMessageWithPopover.tsx | 84 +++++++----- .../chatMessageWithPopover/Popover.tsx | 126 +++++++++--------- 2 files changed, 117 insertions(+), 93 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx index d32585fcd8..a44f9a5c8b 100644 --- a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx @@ -1,7 +1,7 @@ -import { Chat } from '@stardust-ui/react' +import { Chat, Provider } from '@stardust-ui/react' import * as React from 'react' -import Popover, { handleBlur, handleFocus } from './Popover' +import Popover from './Popover' const janeAvatar = { image: 'public/images/avatar/small/ade.jpg', @@ -9,41 +9,63 @@ const janeAvatar = { } class ChatMessageWithPopover extends React.Component { + state = { + focused: false, + } + + changeFocusState = (isFocused: boolean) => { + this.state.focused !== isFocused && this.setState({ focused: isFocused }) + } + + handleFocus = () => { + this.changeFocusState(true) + } + + handleBlur = e => { + const shouldPreserveFocusState = e.currentTarget.contains(e.relatedTarget) + this.changeFocusState(shouldPreserveFocusState) + } + render() { return ( - - - Link Hover me to see the actions Some Link - - ), - }} - avatar={janeAvatar} - onFocus={handleFocus} - onBlur={handleBlur} + ( + + + Link Hover me to see the actions Some Link + + ), + }} + avatar={janeAvatar} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + className={this.state.focused ? 'focused' : undefined} + /> + )} /> ) } } -const CustomChat = () => ( +const ChatWithPopover = () => ( }, @@ -53,4 +75,4 @@ const CustomChat = () => ( /> ) -export default CustomChat +export default ChatWithPopover diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index d6545ac609..43fefb1e9d 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -1,59 +1,81 @@ +import { Menu, Popup, toolbarBehavior, popupFocusTrapBehavior, Provider } from '@stardust-ui/react' import * as React from 'react' -import { Menu, Popup, toolbarBehavior, popupFocusTrapBehavior } from '@stardust-ui/react' +import * as cx from 'classnames' export interface PopoverProps { className?: string } class Popover extends React.Component { + state = { + focused: false, + } + + changeFocusState = (isFocused: boolean) => { + this.state.focused !== isFocused && this.setState({ focused: isFocused }) + } + + handleFocus = () => { + this.changeFocusState(true) + } + + handleBlur = e => { + const shouldPreserveFocusState = e.currentTarget.contains(e.relatedTarget) + this.changeFocusState(shouldPreserveFocusState) + } + render() { return ( - ( + + )} /> ) } @@ -96,23 +118,3 @@ const renderContextMenu = (MenuItem, props) => { /> ) } - -export const handleFocus = e => { - const currentTarget = e.currentTarget - if (!currentTarget.classList.contains('focused')) { - currentTarget.classList.add('focused') - } -} - -export const handleBlur = e => { - const currentTarget = e.currentTarget - const relatedTarget = e.relatedTarget - - if (currentTarget.contains(relatedTarget)) { - if (!currentTarget.classList.contains('focused')) { - currentTarget.classList.add('focused') - } - } else { - currentTarget.classList.remove('focused') - } -} From 5c401bbfff910d32281b725730facb07866080b0 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Tue, 27 Nov 2018 13:57:45 +0100 Subject: [PATCH 09/16] Adressed CR comments --- .../chatMessageWithPopover/Popover.tsx | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index 43fefb1e9d..3aad1bdf3f 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -89,32 +89,36 @@ const renderContextMenu = (MenuItem, props) => { } return ( - } - content={ -
- -
- } + ( + } + content={ +
+ +
+ } + /> + )} /> ) } From 7f8bb6dbdd3cc03bbd8373299e92419a161f22fa Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Tue, 27 Nov 2018 21:26:41 +0100 Subject: [PATCH 10/16] Small improvements --- .../ChatMessageWithPopover.tsx | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx index a44f9a5c8b..36e4f85329 100644 --- a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx @@ -28,51 +28,56 @@ class ChatMessageWithPopover extends React.Component { render() { return ( - ( - - - Link Hover me to see the actions Some Link - - ), - }} - avatar={janeAvatar} - onFocus={this.handleFocus} - onBlur={this.handleBlur} - className={this.state.focused ? 'focused' : undefined} - /> - )} + + + Link Hover me to see the actions Some Link + + ), + }} + avatar={janeAvatar} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + className={this.state.focused ? 'focused' : undefined} /> ) } } const ChatWithPopover = () => ( - }, - { key: 'b', content: }, - { key: 'c', content: }, - ]} - /> + + }, + { key: 'b', content: }, + { key: 'c', content: }, + ]} + /> + ) export default ChatWithPopover From d40b59ec8b0debb074f261e99abe27382bf9aa4a Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Wed, 28 Nov 2018 15:35:56 +0100 Subject: [PATCH 11/16] Improvements with styles --- .../ChatMessageWithPopover.tsx | 23 ++- .../chatMessageWithPopover/Popover.tsx | 162 +++++++++--------- 2 files changed, 103 insertions(+), 82 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx index 36e4f85329..0571396690 100644 --- a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx @@ -53,7 +53,7 @@ const ChatWithPopover = () => ( theme={{ componentStyles: { ChatMessage: { - root: { + root: ({ theme: { siteVariables } }) => ({ position: 'relative', '&.focused .actions': { @@ -63,7 +63,26 @@ const ChatWithPopover = () => ( opacity: 1, }, '& a': { - color: '#6264A7', + color: siteVariables.brand, + }, + }), + }, + ContextMenu: { + root: ({ theme: { siteVariables } }) => ({ + background: siteVariables.white, + boxShadow: '0 0.2rem 1.6rem 0 rgba(37,36,35,.3)', + borderRadius: '.3rem', + marginTop: '5px', + }), + }, + Menu: { + root: { + '& a:focus': { + textDecoration: 'none', + color: 'inherit', + }, + '& a': { + color: 'inherit', }, }, }, diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index 3aad1bdf3f..0c3ec724f4 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -1,4 +1,13 @@ -import { Menu, Popup, toolbarBehavior, popupFocusTrapBehavior, Provider } from '@stardust-ui/react' +import { + Menu, + Popup, + toolbarBehavior, + popupFocusTrapBehavior, + createComponent, + ComponentSlotStyle, + ComponentVariablesInput, +} from '@stardust-ui/react' +import { ReactChildren } from 'types/utils' import * as React from 'react' import * as cx from 'classnames' @@ -24,58 +33,47 @@ class Popover extends React.Component { this.changeFocusState(shouldPreserveFocusState) } - render() { - return ( - ( - ({ + transition: 'opacity 0.2s', + position: 'absolute', + top: '-20px', + right: '5px', + background: siteVariables.white, + boxShadow: '0px 2px 4px #ddd', + borderRadius: '.3rem', + opacity: 0, - '& .smile-emoji': { - display: 'none', - }, + '& .smile-emoji': { + display: 'none', + }, - '&.focused .smile-emoji': { - display: 'flex', - }, + '&.focused .smile-emoji': { + display: 'flex', + }, - '&:hover .smile-emoji': { - display: 'flex', - }, + '&:hover .smile-emoji': { + display: 'flex', + }, + }) - '& a:focus': { - textDecoration: 'none', - color: 'inherit', - }, - - '& a': { - color: 'inherit', - }, - }} - iconOnly - className={cx(this.props.className, this.state.focused ? 'focused' : '')} - items={[ - { key: 'smile', icon: 'smile', className: 'smile-emoji' }, - { key: 'smile2', icon: 'smile', className: 'smile-emoji' }, - { key: 'smile3', icon: 'smile', className: 'smile-emoji' }, - { key: 'a', icon: 'thumbs up' }, - { key: 'c', icon: 'ellipsis horizontal' }, - ]} - renderItem={renderContextMenu} - onFocus={this.handleFocus} - onBlur={this.handleBlur} - accessibility={toolbarBehavior} - data-is-focusable={true} - /> - )} + render() { + return ( + ) } @@ -83,42 +81,46 @@ class Popover extends React.Component { export default Popover -const renderContextMenu = (MenuItem, props) => { +const ContextMenu = createComponent({ + displayName: 'ContextMenu', + render: ({ stardust, className, children }) => { + const { classes } = stardust + return
{children}
+ }, +}) + +interface ContextMenuProps { + className?: string + styles?: ComponentSlotStyle + variables?: ComponentVariablesInput + children?: ReactChildren +} + +const renderItemOrContextMenu = (MenuItem, props) => { if (props.icon !== 'ellipsis horizontal') { return } return ( - ( - } - content={ -
- -
- } - /> - )} + } + content={ + + + + } /> ) } From 4fd8c5427bee24b23095fe120606931da447bb59 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Thu, 29 Nov 2018 15:58:39 +0100 Subject: [PATCH 12/16] Updates --- .../chatMessageWithPopover/Popover.tsx | 94 ++++++++++++------- 1 file changed, 61 insertions(+), 33 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index 0c3ec724f4..edc1496dfb 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -15,9 +15,15 @@ export interface PopoverProps { className?: string } -class Popover extends React.Component { +interface PopoverState { + focused: boolean + popupOpened: boolean +} + +class Popover extends React.Component { state = { focused: false, + popupOpened: false, } changeFocusState = (isFocused: boolean) => { @@ -29,8 +35,18 @@ class Popover extends React.Component { } handleBlur = e => { - const shouldPreserveFocusState = e.currentTarget.contains(e.relatedTarget) - this.changeFocusState(shouldPreserveFocusState) + // if e.relatedTarget === null, so the click was outside this container + if (!this.state.popupOpened || e.relatedTarget === null) { + const shouldPreserveFocusState = e.currentTarget.contains(e.relatedTarget) + this.changeFocusState(shouldPreserveFocusState) + } else { + e.stopPropagation() + } + } + + handleMenuClick = () => { + // close popup when other MenuItem clicked, but the event propagation was stopped + this.state.popupOpened && this.setState({ popupOpened: false }) } menuStyles = ({ theme: { siteVariables } }) => ({ @@ -69,14 +85,55 @@ class Popover extends React.Component { { key: 'a', icon: 'thumbs up' }, { key: 'c', icon: 'ellipsis horizontal' }, ]} - renderItem={renderItemOrContextMenu} + renderItem={this.renderItemOrContextMenu} onFocus={this.handleFocus} onBlur={this.handleBlur} + onClick={this.handleMenuClick} accessibility={toolbarBehavior} data-is-focusable={true} /> ) } + + renderItemOrContextMenu = (MenuItem, props) => { + if (props.icon !== 'ellipsis horizontal') { + return + } + + return ( + { + this.setState(prev => ({ popupOpened: !prev.popupOpened })) + }} + /> + } + open={this.state.popupOpened} + onOpenChange={(e, newProps) => { + this.setState({ popupOpened: newProps.open }) + }} + content={ + + + + } + /> + ) + } } export default Popover @@ -95,32 +152,3 @@ interface ContextMenuProps { variables?: ComponentVariablesInput children?: ReactChildren } - -const renderItemOrContextMenu = (MenuItem, props) => { - if (props.icon !== 'ellipsis horizontal') { - return - } - - return ( - } - content={ - - - - } - /> - ) -} From e0ac38042f8002152c8b14ea85ed5f2054246f73 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Mon, 3 Dec 2018 11:54:47 -0800 Subject: [PATCH 13/16] Add toolbarButtonBehavior --- .../chatMessageWithPopover/Popover.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index edc1496dfb..e79d92418b 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -6,6 +6,7 @@ import { createComponent, ComponentSlotStyle, ComponentVariablesInput, + toolbarButtonBehavior, } from '@stardust-ui/react' import { ReactChildren } from 'types/utils' import * as React from 'react' @@ -79,9 +80,24 @@ class Popover extends React.Component { iconOnly className={cx(this.props.className, this.state.focused ? 'focused' : '')} items={[ - { key: 'smile', icon: 'smile', className: 'smile-emoji' }, - { key: 'smile2', icon: 'smile', className: 'smile-emoji' }, - { key: 'smile3', icon: 'smile', className: 'smile-emoji' }, + { + key: 'smile', + icon: 'smile', + className: 'smile-emoji', + accessibility: toolbarButtonBehavior, + }, + { + key: 'smile2', + icon: 'smile', + className: 'smile-emoji', + accessibility: toolbarButtonBehavior, + }, + { + key: 'smile3', + icon: 'smile', + className: 'smile-emoji', + accessibility: toolbarButtonBehavior, + }, { key: 'a', icon: 'thumbs up' }, { key: 'c', icon: 'ellipsis horizontal' }, ]} From d2df165d4e4224861457e9a75fc23d65b865cf34 Mon Sep 17 00:00:00 2001 From: Milan Turon Date: Tue, 4 Dec 2018 15:33:37 +0100 Subject: [PATCH 14/16] adding aria-label and adding toolbarButton behavior --- .../prototypes/chatMessageWithPopover/Popover.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index e79d92418b..541adac2e2 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -85,21 +85,29 @@ class Popover extends React.Component { icon: 'smile', className: 'smile-emoji', accessibility: toolbarButtonBehavior, + 'aria-label': 'smile one', }, { key: 'smile2', icon: 'smile', className: 'smile-emoji', accessibility: toolbarButtonBehavior, + 'aria-label': 'smile two', }, { key: 'smile3', icon: 'smile', className: 'smile-emoji', accessibility: toolbarButtonBehavior, + 'aria-label': 'smile three', }, - { key: 'a', icon: 'thumbs up' }, - { key: 'c', icon: 'ellipsis horizontal' }, + { + key: 'a', + icon: 'thumbs up', + accessibility: toolbarButtonBehavior, + 'aria-label': 'thumbs up', + }, + { key: 'c', icon: 'ellipsis horizontal', 'aria-label': 'more options' }, ]} renderItem={this.renderItemOrContextMenu} onFocus={this.handleFocus} From 5fa45ba03374839637c9b96112a6685a2a7f09d8 Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Tue, 4 Dec 2018 09:33:57 -0800 Subject: [PATCH 15/16] Small improvements --- docs/src/prototypes/chatMessageWithPopover/Popover.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index 541adac2e2..29d982bc07 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -107,7 +107,12 @@ class Popover extends React.Component { accessibility: toolbarButtonBehavior, 'aria-label': 'thumbs up', }, - { key: 'c', icon: 'ellipsis horizontal', 'aria-label': 'more options' }, + { + key: 'c', + icon: 'ellipsis horizontal', + accessibility: toolbarButtonBehavior, + 'aria-label': 'more options', + }, ]} renderItem={this.renderItemOrContextMenu} onFocus={this.handleFocus} From 8a7de82532c1b41ed3938a1f67b905365855010b Mon Sep 17 00:00:00 2001 From: Sofiya Huts Date: Wed, 5 Dec 2018 12:28:21 -0800 Subject: [PATCH 16/16] Improvements / fixes after CR --- .../ChatMessageWithPopover.tsx | 73 +++++-------------- .../ChatWithPopover.tsx | 56 ++++++++++++++ .../chatMessageWithPopover/Popover.tsx | 11 +-- .../chatMessageWithPopover/index.tsx | 2 +- 4 files changed, 80 insertions(+), 62 deletions(-) create mode 100644 docs/src/prototypes/chatMessageWithPopover/ChatWithPopover.tsx diff --git a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx index 0571396690..a5b00b4df5 100644 --- a/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/ChatMessageWithPopover.tsx @@ -1,6 +1,7 @@ -import { Chat, Provider } from '@stardust-ui/react' +import { ChatMessage } from '@stardust-ui/react' import * as React from 'react' +import * as cx from 'classnames' import Popover from './Popover' const janeAvatar = { @@ -8,7 +9,18 @@ const janeAvatar = { status: { color: 'green', icon: 'check' }, } -class ChatMessageWithPopover extends React.Component { +interface ChatMessageWithPopoverProps { + className?: string +} + +interface ChatMessageWithPopoverState { + focused: boolean +} + +class ChatMessageWithPopover extends React.Component< + ChatMessageWithPopoverProps, + ChatMessageWithPopoverState +> { state = { focused: false, } @@ -28,7 +40,7 @@ class ChatMessageWithPopover extends React.Component { render() { return ( - ) } } -const ChatWithPopover = () => ( - ({ - position: 'relative', - - '&.focused .actions': { - opacity: 1, - }, - ':hover .actions': { - opacity: 1, - }, - '& a': { - color: siteVariables.brand, - }, - }), - }, - ContextMenu: { - root: ({ theme: { siteVariables } }) => ({ - background: siteVariables.white, - boxShadow: '0 0.2rem 1.6rem 0 rgba(37,36,35,.3)', - borderRadius: '.3rem', - marginTop: '5px', - }), - }, - Menu: { - root: { - '& a:focus': { - textDecoration: 'none', - color: 'inherit', - }, - '& a': { - color: 'inherit', - }, - }, - }, - }, - }} - > - }, - { key: 'b', content: }, - { key: 'c', content: }, - ]} - /> - -) - -export default ChatWithPopover +export default ChatMessageWithPopover diff --git a/docs/src/prototypes/chatMessageWithPopover/ChatWithPopover.tsx b/docs/src/prototypes/chatMessageWithPopover/ChatWithPopover.tsx new file mode 100644 index 0000000000..6796bae0fa --- /dev/null +++ b/docs/src/prototypes/chatMessageWithPopover/ChatWithPopover.tsx @@ -0,0 +1,56 @@ +import { Chat, Provider } from '@stardust-ui/react' +import * as React from 'react' +import ChatMessageWithPopover from './ChatMessageWithPopover' + +const ChatWithPopover = () => ( + ({ + position: 'relative', + + '&.focused .actions': { + opacity: 1, + }, + ':hover .actions': { + opacity: 1, + }, + '& a': { + color: siteVariables.brand, + }, + }), + }, + ContextMenu: { + root: ({ theme: { siteVariables } }) => ({ + background: siteVariables.white, + boxShadow: '0 0.2rem 1.6rem 0 rgba(37,36,35,.3)', + borderRadius: '.3rem', + marginTop: '5px', + }), + }, + Menu: { + root: { + '& a:focus': { + textDecoration: 'none', + color: 'inherit', + }, + '& a': { + color: 'inherit', + }, + }, + }, + }, + }} + > + }, + { key: 'b', content: }, + { key: 'c', content: }, + ]} + /> + +) + +export default ChatWithPopover diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx index 29d982bc07..64634a72f9 100644 --- a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -36,7 +36,7 @@ class Popover extends React.Component { } handleBlur = e => { - // if e.relatedTarget === null, so the click was outside this container + // if e.relatedTarget === null it means the click was outside this container if (!this.state.popupOpened || e.relatedTarget === null) { const shouldPreserveFocusState = e.currentTarget.contains(e.relatedTarget) this.changeFocusState(shouldPreserveFocusState) @@ -50,7 +50,7 @@ class Popover extends React.Component { this.state.popupOpened && this.setState({ popupOpened: false }) } - menuStyles = ({ theme: { siteVariables } }) => ({ + popoverStyles = ({ theme: { siteVariables } }) => ({ transition: 'opacity 0.2s', position: 'absolute', top: '-20px', @@ -76,7 +76,7 @@ class Popover extends React.Component { render() { return ( { accessibility: toolbarButtonBehavior, 'aria-label': 'more options', }, - ]} - renderItem={this.renderItemOrContextMenu} + ].map(itemShorthandValue => render => + render(itemShorthandValue, this.renderItemOrContextMenu), + )} onFocus={this.handleFocus} onBlur={this.handleBlur} onClick={this.handleMenuClick} diff --git a/docs/src/prototypes/chatMessageWithPopover/index.tsx b/docs/src/prototypes/chatMessageWithPopover/index.tsx index ed301ef5b2..1207c36d78 100644 --- a/docs/src/prototypes/chatMessageWithPopover/index.tsx +++ b/docs/src/prototypes/chatMessageWithPopover/index.tsx @@ -1 +1 @@ -export { default } from './ChatMessageWithPopover' +export { default } from './ChatWithPopover'