diff --git a/docs/src/examples/components/Menu/Types/MenuExampleWithSubmenu.shorthand.tsx b/docs/src/examples/components/Menu/Types/MenuExampleWithSubmenu.shorthand.tsx
new file mode 100644
index 0000000000..36879d0f8c
--- /dev/null
+++ b/docs/src/examples/components/Menu/Types/MenuExampleWithSubmenu.shorthand.tsx
@@ -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: File ▾, submenu: fileSubmenu },
+ { key: 'edit', content: Edit ▾, submenu: editSubmenu },
+ { key: 'format', content: Format ▾, submenu: formatSubmenu },
+ { key: 'help', content: 'Help' },
+]
+
+const MenuExampleWithSubmenu = () => (
+
+)
+
+export default MenuExampleWithSubmenu
diff --git a/docs/src/examples/components/Menu/Types/index.tsx b/docs/src/examples/components/Menu/Types/index.tsx
index 8ce62a8f8d..dbf7fcc014 100644
--- a/docs/src/examples/components/Menu/Types/index.tsx
+++ b/docs/src/examples/components/Menu/Types/index.tsx
@@ -19,6 +19,11 @@ const Types = () => (
description="A vertical menu displays elements vertically."
examplePath="components/Menu/Types/MenuExampleVertical"
/>
+
)
diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx
index 60421a30ba..f75ce5d482 100644
--- a/src/components/Menu/Menu.tsx
+++ b/src/components/Menu/Menu.tsx
@@ -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'
@@ -163,4 +168,6 @@ class Menu extends AutoControlledComponent, any> {
}
}
+Menu.create = createShorthandFactory(Menu, {})
+
export default Menu
diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx
index e33ce251d2..78cd03d960 100644
--- a/src/components/Menu/MenuItem.tsx
+++ b/src/components/Menu/MenuItem.tsx
@@ -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'
@@ -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, any> {
+class MenuItem extends AutoControlledComponent, MenuItemState> {
static displayName = 'MenuItem'
static className = 'ui-menu__item'
@@ -59,6 +72,9 @@ class MenuItem extends UIComponent, any> {
/** Shorthand for primary content. */
content: PropTypes.any,
+ /** Initial submenuOpened value. */
+ defaultSubmenuOpened: PropTypes.bool,
+
/** Name or shorthand for Menu Item Icon */
icon: customPropTypes.itemShorthand,
@@ -101,6 +117,12 @@ class MenuItem extends UIComponent, 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]),
}
@@ -110,6 +132,8 @@ class MenuItem extends UIComponent, any> {
accessibility: MenuItemBehavior as Accessibility,
}
+ static autoControlledProps = ['submenuOpened']
+
static handledProps = [
'accessibility',
'active',
@@ -117,6 +141,7 @@ class MenuItem extends UIComponent, any> {
'children',
'className',
'content',
+ 'defaultSubmenuOpened',
'icon',
'iconOnly',
'index',
@@ -124,18 +149,28 @@ class MenuItem extends UIComponent, any> {
'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 (
@@ -154,6 +189,15 @@ class MenuItem extends UIComponent, any> {
{content}
)}
+
+ {this.state.submenuOpened &&
+ Menu.create(submenu, {
+ overrideProps: {
+ className: classes.submenu,
+ vertical: true,
+ variables,
+ },
+ })}
)
}
diff --git a/src/themes/teams/components/Menu/menuItemStyles.ts b/src/themes/teams/components/Menu/menuItemStyles.ts
index af6b552689..a22560f493 100644
--- a/src/themes/teams/components/Menu/menuItemStyles.ts
+++ b/src/themes/teams/components/Menu/menuItemStyles.ts
@@ -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),
@@ -238,6 +239,11 @@ const menuItemStyles = {
}),
}
},
+ submenu: {
+ position: 'absolute',
+ borderTopLeftRadius: 0,
+ borderTopRightRadius: 0,
+ },
}
export default menuItemStyles
diff --git a/src/themes/teams/components/Menu/menuStyles.ts b/src/themes/teams/components/Menu/menuStyles.ts
index 49fd533eee..119e3d425d 100644
--- a/src/themes/teams/components/Menu/menuStyles.ts
+++ b/src/themes/teams/components/Menu/menuStyles.ts
@@ -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',
diff --git a/src/themes/teams/components/Menu/menuVariables.ts b/src/themes/teams/components/Menu/menuVariables.ts
index 67dcd88908..c615322943 100644
--- a/src/themes/teams/components/Menu/menuVariables.ts
+++ b/src/themes/teams/components/Menu/menuVariables.ts
@@ -16,6 +16,7 @@ export interface IMenuVariables {
iconsMenuItemSize?: string
iconsMenuItemSpacing: number | string
+ menuItemWidth: number
}
export default (siteVars: any): IMenuVariables => {
@@ -35,5 +36,6 @@ export default (siteVars: any): IMenuVariables => {
iconsMenuItemSize: undefined,
iconsMenuItemSpacing: 0,
+ menuItemWidth: undefined,
}
}
diff --git a/test/specs/components/Menu/MenuItem-test.tsx b/test/specs/components/Menu/MenuItem-test.tsx
index b58fb28ca9..86bef879a7 100644
--- a/test/specs/components/Menu/MenuItem-test.tsx
+++ b/test/specs/components/Menu/MenuItem-test.tsx
@@ -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', () => {
@@ -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()
+ expect(menuItem.find('.ui-menu').length).toBe(0)
+ menuItem.find('a').simulate('click')
+ expect(menuItem.find('.ui-menu').is('ul')).toBe(true)
+ })
})