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

Add possibility to render submenu in MenuItem #126

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react'
import { Menu } from '@stardust-ui/react'

const menuWidthVariable = {
menuItemWidth: 100,
}

const fileSubmenu = {
items: [
{ key: 'new', content: 'New' },
{ key: 'open', content: 'Open' },
{ key: 'edit', content: 'Edit' },
],
}
const editSubmenu = {
items: [
{ key: 'undo', content: 'Undo' },
{ key: 'redo', content: 'Redo' },
{ key: 'cut', content: 'Cut' },
{ key: 'copy', content: 'Copy' },
],
}
const formatSubmenu = {
items: [{ key: 'font', content: 'Font' }, { key: 'text', content: 'Text' }],
}

const items = [
{ key: 'file', content: <span>File ▾</span>, submenu: fileSubmenu },
{ key: 'edit', content: <span>Edit ▾</span>, submenu: editSubmenu },
{ key: 'format', content: <span>Format ▾</span>, submenu: formatSubmenu },
{ key: 'help', content: 'Help' },
]

const MenuExampleWithSubmenu = () => (
<Menu defaultActiveIndex={0} items={items} variables={menuWidthVariable} />
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please try to add some height on the element as the content is not visible (try to wrap the Menu in some wrapper with some bigger height).
image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I would expect to be able to expand only one submenu at a time. I don't think the scenario above (having three different dropdowns should work (try closing the submenu if there is click outside of it).


export default MenuExampleWithSubmenu
5 changes: 5 additions & 0 deletions docs/src/examples/components/Menu/Types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const Types = () => (
description="A vertical menu displays elements vertically."
examplePath="components/Menu/Types/MenuExampleVertical"
/>
<ComponentExample
title="Sub Menu"
description="A menu item may contain another vertical menu nested inside that acts as a grouped sub-menu."
examplePath="components/Menu/Types/MenuExampleWithSubmenu"
/>
</ExampleSection>
)

Expand Down
9 changes: 8 additions & 1 deletion src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import * as _ from 'lodash'
import * as PropTypes from 'prop-types'
import * as React from 'react'

import { AutoControlledComponent, childrenExist, customPropTypes } from '../../lib'
import {
AutoControlledComponent,
childrenExist,
customPropTypes,
createShorthandFactory,
} from '../../lib'
import MenuItem from './MenuItem'
import { MenuBehavior } from '../../lib/accessibility'
import { Accessibility } from '../../lib/accessibility/interfaces'
Expand Down Expand Up @@ -163,4 +168,6 @@ class Menu extends AutoControlledComponent<Extendable<IMenuProps>, any> {
}
}

Menu.create = createShorthandFactory(Menu, {})

export default Menu
50 changes: 47 additions & 3 deletions src/components/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import * as cx from 'classnames'
import * as PropTypes from 'prop-types'
import * as React from 'react'

import { childrenExist, createShorthandFactory, customPropTypes, UIComponent } from '../../lib'
import {
childrenExist,
createShorthandFactory,
customPropTypes,
AutoControlledComponent,
} from '../../lib'
import Icon from '../Icon'
import Menu from '../Menu'
import { MenuItemBehavior } from '../../lib/accessibility'
import { Accessibility } from '../../lib/accessibility/interfaces'

Expand Down Expand Up @@ -34,9 +40,16 @@ export interface IMenuItemProps {
vertical?: boolean
styles?: IComponentPartStylesInput
variables?: ComponentVariablesInput
submenu?: ItemShorthand
submenuOpened?: boolean
defaultSubmenuOpened?: boolean
}

interface MenuItemState {
submenuOpened: boolean
}

class MenuItem extends UIComponent<Extendable<IMenuItemProps>, any> {
class MenuItem extends AutoControlledComponent<Extendable<IMenuItemProps>, MenuItemState> {
static displayName = 'MenuItem'

static className = 'ui-menu__item'
Expand All @@ -59,6 +72,9 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, any> {
/** Shorthand for primary content. */
content: PropTypes.any,

/** Initial submenuOpened value. */
defaultSubmenuOpened: PropTypes.bool,

/** Name or shorthand for Menu Item Icon */
icon: customPropTypes.itemShorthand,

Expand Down Expand Up @@ -101,6 +117,12 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, any> {
/** Custom styles to be applied for component. */
styles: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),

/** Shorthand for Menu Item submenu */
submenu: customPropTypes.itemShorthand,

/** Auto controlled prop that defines if submenu is opened */
submenuOpened: PropTypes.bool,

/** Custom variables to be applied for component. */
variables: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
}
Expand All @@ -110,32 +132,45 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, any> {
accessibility: MenuItemBehavior as Accessibility,
}

static autoControlledProps = ['submenuOpened']

static handledProps = [
'accessibility',
'active',
'as',
'children',
'className',
'content',
'defaultSubmenuOpened',
'icon',
'iconOnly',
'index',
'onClick',
'pills',
'pointing',
'styles',
'submenu',
'submenuOpened',
'type',
'underlined',
'variables',
'vertical',
]

getInitialAutoControlledState() {
return { submenuOpened: false }
}

handleClick = e => {
if (this.props.submenu) {
this.setState({ submenuOpened: !this.state.submenuOpened })
}

_.invoke(this.props, 'onClick', e, this.props)
}

renderComponent({ ElementType, classes, accessibility, rest }) {
const { children, content, icon } = this.props
const { children, content, icon, submenu, variables } = this.props

return (
<ElementType className={classes.root} {...accessibility.attributes.root} {...rest}>
Expand All @@ -154,6 +189,15 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, any> {
{content}
</a>
)}

{this.state.submenuOpened &&
Menu.create(submenu, {
overrideProps: {
className: classes.submenu,
vertical: true,
variables,
},
})}
</ElementType>
)
}
Expand Down
6 changes: 6 additions & 0 deletions src/themes/teams/components/Menu/menuItemStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const menuItemStyles = {
: { marginLeft: iconsMenuItemSpacing }),
},
}),
...(!vertical && variables.menuItemWidth && { width: pxToRem(variables.menuItemWidth) }),
...(pills && {
...(vertical ? { margin: `0 0 ${pxToRem(5)} 0` } : { margin: `0 ${pxToRem(8)} 0 0` }),
borderRadius: pxToRem(5),
Expand Down Expand Up @@ -238,6 +239,11 @@ const menuItemStyles = {
}),
}
},
submenu: {
position: 'absolute',
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally support @notandrew comment, the type should affect the submenu as well.

}

export default menuItemStyles
2 changes: 1 addition & 1 deletion src/themes/teams/components/Menu/menuStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default {
display: 'flex',
...(vertical && {
flexDirection: 'column',
...(!fluid && { width: pxToRem(200) }),
...(!fluid && { width: pxToRem(variables.menuItemWidth || 200) }),
...(iconOnly && {
display: 'inline-block',
width: 'auto',
Expand Down
2 changes: 2 additions & 0 deletions src/themes/teams/components/Menu/menuVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface IMenuVariables {

iconsMenuItemSize?: string
iconsMenuItemSpacing: number | string
menuItemWidth: number
}

export default (siteVars: any): IMenuVariables => {
Expand All @@ -35,5 +36,6 @@ export default (siteVars: any): IMenuVariables => {

iconsMenuItemSize: undefined,
iconsMenuItemSpacing: 0,
menuItemWidth: undefined,
}
}
18 changes: 17 additions & 1 deletion test/specs/components/Menu/MenuItem-test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from 'react'

import { isConformant, handlesAccessibility } from 'test/specs/commonTests'
import { getTestingRenderedComponent } from 'test/utils'
import { getTestingRenderedComponent, mountWithProvider } from 'test/utils'

import MenuItem from 'src/components/Menu/MenuItem'

describe('MenuItem', () => {
Expand Down Expand Up @@ -29,4 +30,19 @@ describe('MenuItem', () => {
expect(menuItem.find('.ui-menu__item').is('li')).toBe(true)
expect(menuItem.text()).toBe('Home')
})

it('menu item renders submenu after click on it', () => {
const submenu = {
vertical: true,
items: [
{ key: 'new', content: 'New' },
{ key: 'open', content: 'Open' },
{ key: 'edit', content: 'Edit' },
],
}
const menuItem = mountWithProvider(<MenuItem submenu={submenu} content="File" />)
expect(menuItem.find('.ui-menu').length).toBe(0)
menuItem.find('a').simulate('click')
expect(menuItem.find('.ui-menu').is('ul')).toBe(true)
})
})