Skip to content
This repository has been archived by the owner on Mar 4, 2020. It is now read-only.

feat(ToolbarMenuItem): add menu prop #1984

Merged
merged 43 commits into from
Oct 22, 2019
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
23a3f64
-init
Sep 30, 2019
de4473c
-cleanups
Sep 30, 2019
1e05ec7
-fixed styles for submenu indicator
Sep 30, 2019
bea7d36
-fixed tests
Oct 1, 2019
95e74fb
-added submenuIndicator defaultProp
Oct 1, 2019
ce99d4f
-added example
Oct 1, 2019
63ee471
-fixed issues
Oct 1, 2019
1652fef
-added onItemClick in the example
Oct 1, 2019
b0f7006
-fixes
Oct 1, 2019
6906d21
-fixed broken popup
Oct 1, 2019
1f45b7d
-fixed popup
Oct 1, 2019
2870ab1
-updated changelog
Oct 1, 2019
dcc9ea6
-fix test
Oct 1, 2019
e568d0e
-fixed popup issues
Oct 1, 2019
58fef76
-fixed issues
Oct 1, 2019
6ae0267
Merge branch 'master' into feat/toolbar-menu-submenu
Oct 18, 2019
332e80a
-fixed closing of the menu when enter/spacebar is pressed
Oct 18, 2019
26db261
-added toolbarMenuBehavior
Oct 18, 2019
7ad7377
-added test
Oct 18, 2019
bddd58a
-fixed click on popup closing menus
Oct 18, 2019
0a0155b
-reverted inline popup in ToolbarItem
Oct 18, 2019
b2a7d0b
-removed inline popups
Oct 18, 2019
e0ed631
-disabled non-working tests
Oct 18, 2019
b3976eb
-fixed popups onClick and onKeyDown
Oct 18, 2019
148e70f
-reverted disabled tests
Oct 18, 2019
fab1cab
-fixed not closing first open menu
Oct 19, 2019
7ece426
-fixed typings
Oct 21, 2019
bc8ae00
-fixed typings
Oct 21, 2019
a1b54ab
-fixed typings
Oct 21, 2019
5f42073
-reverted typings changes
Oct 21, 2019
2bacbcc
-addressed PR comments
Oct 22, 2019
6aa486e
-added e2e tests
Oct 22, 2019
6b4c800
Merge branch 'master' into feat/toolbar-menu-submenu
Oct 22, 2019
95a7fd5
-added more e2e for popups in submenus
Oct 22, 2019
3463080
-fixed typing errors
Oct 22, 2019
13a1ef6
-reverted changes
Oct 22, 2019
fe39d82
fix typings
layershifter Oct 22, 2019
299d8f7
Merge branch 'feat/toolbar-menu-submenu' of https://github.com/stardu…
layershifter Oct 22, 2019
b9a6c8e
Merge branch 'master' into feat/toolbar-menu-submenu
Oct 22, 2019
87429ca
-updated test-cirtucalrs/config
Oct 22, 2019
f4d5cd6
add circulars snapshot
layershifter Oct 22, 2019
ea82d17
Merge branch 'feat/toolbar-menu-submenu' of https://github.com/stardu…
layershifter Oct 22, 2019
3dddd90
Merge branch 'feat/toolbar-menu-submenu' of https://github.com/micros…
Oct 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Features
- Export `robot`, `tabs` and `plugs` icon to Teams theme @codepretty ([#2026](https://github.com/stardust-ui/react/pull/2026))
- Add CSSinJS debug panel @levithomason @miroslavstastny @mnajdova ([#1974](https://github.com/stardust-ui/react/pull/1974))
- Add `menu` prop on `ToolbarMenuItem` component @mnajdova ([#1984](https://github.com/stardust-ui/react/pull/1984))

### Fixes
- Correctly handle RTL in `Alert` component @miroslavstastny ([#2018](https://github.com/stardust-ui/react/pull/2018))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ToolbarItem, ToolbarMenuItem } from '@stardust-ui/react'

const config: ScreenerTestsConfig = {
themes: ['teams', 'teamsDark', 'teamsHighContrast'],
steps: [
(builder, keys) =>
builder
.click(`.${ToolbarItem.className}:nth-child(1)`)
.snapshot('Shows menu')
.keys(`.${ToolbarMenuItem.className}:nth-child(1)`, keys.rightArrow)
.snapshot('Opens submenu'),
],
}

export default config
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { createCallbackLogFormatter } from '@stardust-ui/code-sandbox'
import { useLogKnob } from '@stardust-ui/docs-components'
import { Toolbar } from '@stardust-ui/react'
import * as React from 'react'

const ToolbarExampleMenuShorthand = () => {
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
const [menuOpen, setMenuOpen] = React.useState(false)

const onMenuOpenChange = useLogKnob(
'onMenuOpenChange',
(e, { menuOpen }) => setMenuOpen(menuOpen),
createCallbackLogFormatter(['menuOpen']),
)

return (
<Toolbar
items={[
{
key: 'more',
icon: 'more',
active: menuOpen,
menu: {
items: [
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
{
key: 'play',
content: 'Play',
icon: 'play',
menu: {
items: [
'Play with audio',
{ content: 'Play with video', menu: ['HD', 'Full HD'] },
],
},
},
{ key: 'pause', content: 'Pause', icon: 'pause' },
{ key: 'divider', kind: 'divider' },
'Without icon',
],
},
menuOpen,
onMenuOpenChange,
},
]}
/>
)
}

export default ToolbarExampleMenuShorthand
5 changes: 5 additions & 0 deletions docs/src/examples/components/Toolbar/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ const Content = () => (
description="Toolbar item can open a menu which can contain radio groups."
examplePath="components/Toolbar/Content/ToolbarExampleMenuRadioGroup"
/>
<ComponentExample
title="Toolbar can contain a submenu in a menu"
description="Toolbar item can open a menu with submenu."
examplePath="components/Toolbar/Content/ToolbarExampleMenuWithSubmenu"
/>
<ComponentExample
title="Toolbar can contain custom content"
description="Toolbar item can contain custom content."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const toolbarItemBehavior: Accessibility<ToolbarItemBehaviorProps> = props => {
}
behaviorData.keyActions.wrapper = {
...behaviorData.keyActions.wrapper,
performWrapperClick: {
keyCombinations: [{ keyCode: keyboardKey.Enter }, { keyCode: keyboardKey.Spacebar }],
},
closeMenuAndFocusTrigger: {
keyCombinations:
props.menu && props.menuOpen
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Accessibility } from '../../types'
import { FocusZoneMode, FocusZoneDirection } from '../../focusZone/types'
import toolbarMenuItemBehavior from './toolbarMenuItemBehavior'
import * as keyboardKey from 'keyboard-key'

/**
* @description
* The 'menu' role is used to identify an element that creates a list of common actions or functions that a user can invoke.
*
* @specification
* Adds role='menu'.
* Embeds component into FocusZone.
* Provides arrow key navigation in vertical direction.
* Keyboard navigation is circular.
* Component will get focus when mounted.
*/
const toolbarMenuBehavior: Accessibility = () => ({
attributes: {
root: {
role: 'menu',
},
},

keyActions: {
root: {
performClick: {
keyCombinations: [{ keyCode: keyboardKey.Enter }, { keyCode: keyboardKey.Spacebar }],
},
},
},
focusZone: {
mode: FocusZoneMode.Embed,
props: {
isCircularNavigation: true,
shouldFocusOnMount: true,
direction: FocusZoneDirection.vertical,
},
},
childBehaviors: { item: toolbarMenuItemBehavior },
})

export default toolbarMenuBehavior
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Accessibility, AccessibilityAttributes } from '../../types'
import menuItemBehavior from '../Menu/menuItemBehavior'

/**
* @description
* The behavior is designed for particular structure of menu item. The item consists of root element and anchor inside the root element.
*
* @specification
* Adds role 'presentation' to 'wrapper' slot.
* Adds role 'menuitem' to 'root' slot.
* Adds attribute 'tabIndex=0' to 'root' slot.
* Adds attribute 'data-is-focusable=false' to 'root' slot if 'disabled' property is true. Sets the attribute to 'true' otherwise.
* Adds attribute 'aria-label' based on the property 'aria-label' to 'root' slot.
* Adds attribute 'aria-labelledby' based on the property 'aria-labelledby' to 'root' slot.
* Adds attribute 'aria-describedby' based on the property 'aria-describedby' to 'root' slot.
* Adds attribute 'aria-expanded=true' based on the property 'menuOpen' if the component has 'menu' property to 'root' slot.
* Adds attribute 'aria-haspopup=true' to 'root' slot if 'menu' property is set.
* Adds attribute 'aria-disabled=true' to 'root' slot 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'.
* Triggers 'closeMenuAndFocusTrigger' action with 'Escape' on 'wrapper'.
* Triggers 'closeAllMenusAndFocusNextParentItem' action with 'ArrowRight' on 'wrapper'.
* Triggers 'closeMenu' action with 'ArrowLeft' on 'wrapper'.
* Triggers 'openMenu' action with 'ArrowRight' on 'wrapper'.
*/
const toolbarMenuItemBehavior: Accessibility<ToolbarMenuItemBehaviorProps> = props => {
return menuItemBehavior({ ...props, vertical: true })
}

export default toolbarMenuItemBehavior

export type ToolbarMenuItemBehaviorProps = {
/** Indicated if menu item has submenu. */
menu?: boolean | object
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
/** Defines if submenu is opened. */
menuOpen?: boolean
/** If a menu item can is currently unable to be interacted with. */
disabled?: boolean
} & Pick<AccessibilityAttributes, 'aria-label' | 'aria-labelledby' | 'aria-describedby'>
2 changes: 2 additions & 0 deletions packages/accessibility/src/behaviors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export { default as iconBehavior } from './Icon/iconBehavior'
export { default as tabBehavior } from './Tab/tabBehavior'
export { default as tabListBehavior } from './Tab/tabListBehavior'
export { default as menuAsToolbarBehavior } from './Toolbar/menuAsToolbarBehavior'
export { default as toolbarMenuBehavior } from './Toolbar/toolbarMenuBehavior'
export { default as toolbarMenuItemBehavior } from './Toolbar/toolbarMenuItemBehavior'
export {
default as menuItemAsToolbarButtonBehavior,
} from './Toolbar/menuItemAsToolbarButtonBehavior'
Expand Down
4 changes: 4 additions & 0 deletions packages/accessibility/test/behaviors/behavior-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ import {
chatMessageBehavior,
toolbarBehavior,
toolbarItemBehavior,
toolbarMenuBehavior,
toolbarMenuItemCheckboxBehavior,
toolbarMenuItemBehavior,
toolbarMenuItemRadioBehavior,
toolbarMenuRadioGroupBehavior,
toolbarRadioGroupBehavior,
Expand Down Expand Up @@ -117,6 +119,8 @@ testHelper.addBehavior('chatBehavior', chatBehavior)
testHelper.addBehavior('chatMessageBehavior', chatMessageBehavior)
testHelper.addBehavior('toolbarBehavior', toolbarBehavior)
testHelper.addBehavior('toolbarItemBehavior', toolbarItemBehavior)
testHelper.addBehavior('toolbarMenuBehavior', toolbarMenuBehavior)
testHelper.addBehavior('toolbarMenuItemBehavior', toolbarMenuItemBehavior)
testHelper.addBehavior('toolbarMenuItemCheckboxBehavior', toolbarMenuItemCheckboxBehavior)
testHelper.addBehavior('toolbarMenuItemRadioBehavior', toolbarMenuItemRadioBehavior)
testHelper.addBehavior('toolbarMenuRadioGroupBehavior', toolbarMenuRadioGroupBehavior)
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class Menu extends AutoControlledComponent<WithAsProp<MenuProps>, MenuState> {
underlined: PropTypes.bool,
vertical: PropTypes.bool,
submenu: PropTypes.bool,
indicator: customPropTypes.itemShorthand,
indicator: customPropTypes.itemShorthandWithoutJSX,
}

static defaultProps = {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ class MenuItem extends AutoControlledComponent<WithAsProp<MenuItemProps>, MenuIt
defaultMenuOpen: PropTypes.bool,
onActiveChanged: PropTypes.func,
inSubmenu: PropTypes.bool,
indicator: customPropTypes.itemShorthand,
indicator: customPropTypes.itemShorthandWithoutJSX,
onMenuOpenChange: PropTypes.func,
}

Expand Down
63 changes: 43 additions & 20 deletions packages/react/src/components/Toolbar/ToolbarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ class ToolbarItem extends UIComponent<WithAsProp<ToolbarItemProps>> {
event.preventDefault()
this.handleClick(event)
},
performWrapperClick: event => {
this.handleWrapperClick(event)
},
closeMenuAndFocusTrigger: event => {
this.trySetMenuOpen(false, event)
if (this.itemRef) {
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -173,8 +176,9 @@ class ToolbarItem extends UIComponent<WithAsProp<ToolbarItemProps>> {
}

itemRef = React.createRef<HTMLElement>()
menuRef = React.createRef<HTMLElement>()

handleMenuOverrides = (getRefs: GetRefs, variables) => (predefinedProps: ToolbarItemProps) => ({
handleMenuOverrides = (getRefs: GetRefs, variables) => (predefinedProps: ToolbarMenuProps) => ({
onBlur: (e: React.FocusEvent) => {
const isInside = _.some(getRefs(), (childRef: NodeRef) => {
return childRef.current.contains(e.relatedTarget as HTMLElement)
Expand All @@ -184,14 +188,15 @@ class ToolbarItem extends UIComponent<WithAsProp<ToolbarItemProps>> {
this.trySetMenuOpen(false, e)
}
},
onItemClick: (e, itemProps: ToolbarItemProps) => {
onItemClick: (e, itemProps: ToolbarMenuItemProps) => {
const { popup, menuOpen } = itemProps
_.invoke(predefinedProps, 'onItemClick', e, itemProps)
if (itemProps.popup) {
if (popup) {
return
}
// TODO: should we pass toolbarMenuItem to the user callback so he can decide if he wants to close the menu?
this.trySetMenuOpen(false, e)
if (this.itemRef) {
this.trySetMenuOpen(menuOpen, e)
if (!menuOpen && this.itemRef) {
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
this.itemRef.current.focus()
}
},
Expand Down Expand Up @@ -221,20 +226,22 @@ class ToolbarItem extends UIComponent<WithAsProp<ToolbarItemProps>> {
{(getRefs, nestingRef) => (
<>
<Ref innerRef={nestingRef}>
<Popper
align="start"
position="above"
modifiers={{
preventOverflow: {
escapeWithReference: false, // escapeWithReference breaks positioning of ToolbarMenu in overflow mode because Popper components sets modifiers on scrollable container
},
}}
targetRef={this.itemRef}
>
{ToolbarMenu.create(menu, {
overrideProps: this.handleMenuOverrides(getRefs, variables),
})}
</Popper>
<Ref innerRef={this.menuRef}>
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
<Popper
align="start"
position="above"
modifiers={{
preventOverflow: {
escapeWithReference: false, // escapeWithReference breaks positioning of ToolbarMenu in overflow mode because Popper components sets modifiers on scrollable container
},
}}
targetRef={this.itemRef}
>
{ToolbarMenu.create(menu, {
overrideProps: this.handleMenuOverrides(getRefs, variables),
})}
</Popper>
</Ref>
</Ref>
<EventListener
listener={this.handleOutsideClick(getRefs)}
Expand Down Expand Up @@ -275,8 +282,12 @@ class ToolbarItem extends UIComponent<WithAsProp<ToolbarItemProps>> {
...accessibility.attributes.wrapper,
...applyAccessibilityKeyHandlers(accessibility.keyHandlers.wrapper, wrapper),
},
overrideProps: () => ({
overrideProps: predefinedProps => ({
children: contentElement,
onClick: e => {
this.handleWrapperClick(e)
_.invoke(predefinedProps, 'onClick', e)
},
}),
})
}
Expand Down Expand Up @@ -310,6 +321,18 @@ class ToolbarItem extends UIComponent<WithAsProp<ToolbarItemProps>> {
_.invoke(this.props, 'onClick', e, this.props)
}

handleWrapperClick = e => {
const { menu } = this.props
if (menu) {
if (doesNodeContainClick(this.menuRef.current, e, this.context.target)) {
this.trySetMenuOpen(false, e)
if (this.itemRef) {
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
this.itemRef.current.focus()
}
}
}
}

handleOutsideClick = (getRefs: GetRefs) => (e: MouseEvent) => {
const isItemClick = doesNodeContainClick(this.itemRef.current, e, this.context.target)
const isNestedClick = _.some(getRefs(), (childRef: NodeRef) => {
Expand Down
Loading