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

feat(MenuButton): add component #1696

Merged
merged 17 commits into from
Aug 8, 2019
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### BREAKING CHANGES
- Fix `firstFocusableSelector` in `FocusTrapZone` and `AutoFocusZone` @sophieH29 ([#1732](https://github.com/stardust-ui/react/pull/1732))
- Rename Popup `shouldTriggerBeTabbable` prop to `tabbableTrigger` @jurokapsiar ([#1696](https://github.com/stardust-ui/react/pull/1696))
- `StackableEventListener` is removed @layershifter ([#1755](https://github.com/stardust-ui/react/pull/1755))
- Rename `Tree` to `HierarchicalTree` @silviuavram ([#1752](https://github.com/stardust-ui/react/pull/1752))

Expand All @@ -36,6 +37,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Export `call-blocked` icon to Teams theme @francescopalmiotto ([#1736](https://github.com/stardust-ui/react/pull/1736))
- Add support for component styles debugging @kuzhelov ([#1726](https://github.com/stardust-ui/react/pull/1726))
- Use FocusZone in selectable list @jurokapsiar ([#1757](https://github.com/stardust-ui/react/pull/1757))
- Add `MenuButton` component @jurokapsiar ([#1696](https://github.com/stardust-ui/react/pull/1696))
- Add `useEventListener` hook @layershifter ([#1755](https://github.com/stardust-ui/react/pull/1755))

### Documentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const unsupportedComponents = [
'ItemLayout',
'Layout',
'List',
'MenuButton',
'Portal',
'Provider',
'RadioGroup',
Expand Down
5 changes: 3 additions & 2 deletions docs/src/components/DocsBehaviorRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ class DocsBehaviorRoot extends React.Component<any, any> {
}

const { match } = this.props
const pageTitle = `${_.capitalize(match.params.name)} accessibility behaviors`
const componentName = _.upperFirst(_.camelCase(match.params.name))
const pageTitle = `${componentName} accessibility behaviors`
return (
<DocumentTitle title={pageTitle}>
<Segment styles={{ backgroundColor: 'transparent' }}>
<Header as="h1" aria-level={2} content={pageTitle} />

{behaviorMenuItems
.find(behavior => behavior.displayName === _.capitalize(match.params.name))
.find(behavior => behavior.displayName === componentName)
.variations.map((variation, keyValue) => (
<React.Fragment key={keyValue}>
<Segment
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Button } from '@stardust-ui/react'

const config: ScreenerTestsConfig = {
steps: [builder => builder.click(`.${Button.className}`).snapshot('RTL: Shows menuButton')],
}

export default config
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as React from 'react'
import { Button, MenuButton } from '@stardust-ui/react'

const MenuButtonExampleRtl = () => (
<MenuButton
trigger={<Button content="ا يجلبه إلينا الأس" />}
menu={['English text!', 'غالباً ونرفض الشعور']}
/>
)

export default MenuButtonExampleRtl
12 changes: 12 additions & 0 deletions docs/src/examples/components/MenuButton/Rtl/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from 'react'

import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
import NonPublicSection from 'docs/src/components/ComponentDoc/NonPublicSection'

const Rtl = () => (
<NonPublicSection title="Rtl">
<ComponentExample examplePath="components/MenuButton/Rtl/MenuButtonExample.rtl" />
</NonPublicSection>
)

export default Rtl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useBooleanKnob } from '@stardust-ui/docs-components'
import * as React from 'react'
import { Button, MenuButton } from '@stardust-ui/react'

const MenuButtonOpenExample = () => {
const [open, setOpen] = useBooleanKnob({ name: 'open', initialValue: true })

return (
<MenuButton
open={open}
onOpenChange={(e, { open }) => setOpen(open)}
trigger={<Button icon="expand" title="Open MenuButton" />}
menu={['1', '2', '3', { content: 'submenu', menu: ['4', '5'] }]}
/>
)
}

export default MenuButtonOpenExample
16 changes: 16 additions & 0 deletions docs/src/examples/components/MenuButton/State/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from 'react'

import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'

const State = () => (
<ExampleSection title="State">
<ComponentExample
title="Open"
description="Note that if MenuButton is controlled, then its 'open' prop value could be changed either by parent component, or by user actions (e.g. key press) - thus it is necessary to handle 'onOpenChange' event."
examplePath="components/MenuButton/State/MenuButtonExampleOpen"
/>
</ExampleSection>
)

export default State
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react'
import { Alert, Button, MenuButton } from '@stardust-ui/react'

const items = ['1', '2', '3', { content: 'submenu', menu: { items: ['4', '5'] } }]

class MenuButtonOnElement extends React.Component {
state = { alert: false }

showAlert = () => {
this.setState({ alert: true })
setTimeout(() => this.setState({ alert: false }), 2000)
}

render() {
return (
<>
<MenuButton
contextMenu
trigger={
<div style={{ padding: '4rem', border: 'red dashed' }}>
<Button content="Random button" onClick={this.showAlert} />
</div>
}
menu={{ items }}
/>
{this.state.alert && <Alert warning content="Click!" />}
</>
)
}
}

export default MenuButtonOnElement
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react'
import { Alert, Button, Flex, MenuButton } from '@stardust-ui/react'

class MenuButtonExampleOn extends React.Component {
state = { alert: false }

showAlert = () => {
this.setState({ alert: true })
setTimeout(() => this.setState({ alert: false }), 2000)
}

render() {
return (
<>
<Flex gap="gap.smaller">
<MenuButton
trigger={<Button icon="expand" content="Click" aria-label="Click button" />}
menu={['1', '2', '3', { content: 'submenu', menu: { items: ['4', '5'] } }]}
on="click"
/>
<MenuButton
trigger={<Button icon="expand" content="Hover" aria-label="Hover button" />}
menu={['1', '2', '3', { content: 'submenu', menu: { items: ['4', '5'] } }]}
on="hover"
/>
<MenuButton
trigger={<Button icon="expand" content="Focus" aria-label="Focus button" />}
menu={['1', '2', '3', { content: 'submenu', menu: { items: ['4', '5'] } }]}
on="focus"
/>
<MenuButton
trigger={
<Button
icon="expand"
content="Context"
aria-label="Context button"
onClick={this.showAlert}
/>
}
menu={['1', '2', '3', { content: 'submenu', menu: { items: ['4', '5'] } }]}
on="context"
/>
</Flex>
{this.state.alert && (
<Alert
warning
content="Right, you can still click the button! Right click opens the MenuButton."
/>
)}
</>
)
}
}

export default MenuButtonExampleOn
21 changes: 21 additions & 0 deletions docs/src/examples/components/MenuButton/Usage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react'

import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'

const Usage = () => (
<ExampleSection title="Usage">
<ComponentExample
title="Triggering menu on different actions"
description="A context menu can be triggered on click, hover, focus or context."
examplePath="components/MenuButton/Usage/MenuButtonExampleOn"
/>
<ComponentExample
title="Context menu"
description="A menu button can be attached to any element to create a context menu."
examplePath="components/MenuButton/Usage/MenuButtonExampleContextMenu"
/>
</ExampleSection>
)

export default Usage
15 changes: 15 additions & 0 deletions docs/src/examples/components/MenuButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from 'react'

import Rtl from './Rtl'
import Usage from './Usage'
import State from './State'

const MenuButtonExamples = () => (
<>
<Usage />
<State />
<Rtl />
</>
)

export default MenuButtonExamples
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class PopupContextOnElement extends React.Component {
<Button content="Random button" onClick={this.showAlert} />
</div>
}
shouldTriggerBeTabbable={false}
tabbableTrigger={false}
content={contentWithButtons}
trapFocus
on="context"
Expand Down
18 changes: 17 additions & 1 deletion packages/react/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ import { menuBehavior } from '../../lib/accessibility'
import { Accessibility } from '../../lib/accessibility/types'
import { ReactAccessibilityBehavior } from '../../lib/accessibility/reactTypes'
import { ComponentVariablesObject, ComponentSlotStylesPrepared } from '../../themes/types'
import { WithAsProp, ShorthandCollection, ShorthandValue, withSafeTypeForAs } from '../../types'
import {
WithAsProp,
ShorthandCollection,
ShorthandValue,
withSafeTypeForAs,
ComponentEventHandler,
} from '../../types'
import MenuDivider from './MenuDivider'
import { IconProps } from '../Icon/Icon'

Expand Down Expand Up @@ -53,6 +59,14 @@ export interface MenuProps extends UIComponentProps, ChildrenComponentProps {
/** Shorthand array of props for Menu. */
items?: ShorthandCollection<MenuItemProps, MenuShorthandKinds>

/**
* Called when a panel title is clicked.
*
* @param {SyntheticEvent} event - React's original SyntheticEvent.
* @param {object} data - All item props.
*/
onItemClick?: ComponentEventHandler<MenuItemProps>

/** A menu can adjust its appearance to de-emphasize its contents. */
pills?: boolean

Expand Down Expand Up @@ -106,6 +120,7 @@ class Menu extends AutoControlledComponent<WithAsProp<MenuProps>, MenuState> {
fluid: PropTypes.bool,
iconOnly: PropTypes.bool,
items: customPropTypes.collectionShorthandWithKindProp(['divider', 'item']),
onItemClick: PropTypes.func,
pills: PropTypes.bool,
pointing: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['start', 'end'])]),
primary: customPropTypes.every([customPropTypes.disallow(['secondary']), PropTypes.bool]),
Expand All @@ -132,6 +147,7 @@ class Menu extends AutoControlledComponent<WithAsProp<MenuProps>, MenuState> {

this.trySetState({ activeIndex: index })

_.invoke(this.props, 'onItemClick', e, itemProps)
_.invoke(predefinedProps, 'onClick', e, itemProps)
},
onActiveChanged: (e, props) => {
Expand Down
Loading