Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.
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,39 @@
import React from 'react'
import { Menu, Provider } from '@stardust-ui/react'

const items = [
{
key: 'editorials',
content: 'Editorials',
menu: {
items: [
{ key: '1', content: 'item1' },
{
key: '2',
content: 'item2',
menu: { items: [{ key: '1', content: 'item1' }, { key: '2', content: 'item2' }] },
},
],
},
},
{ key: 'review', content: 'Reviews' },
{ key: 'events', content: 'Upcoming Events' },
]

const MenuExampleVerticalWithSubmenu = () => (
<Provider
theme={{
componentStyles: {
Menu: {
root: {
zIndex: 1000,
},
},
},
}}
>
<Menu defaultActiveIndex={0} vertical items={items} />
</Provider>
)

export default MenuExampleVerticalWithSubmenu
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react'
import { Menu, Provider } from '@stardust-ui/react'

const items = [
{
key: 'editorials',
content: 'Editorials',
menu: {
items: [
{ key: '1', content: 'item1' },
{
key: '2',
content: 'item2',
menu: { items: [{ key: '1', content: 'item1' }, { key: '2', content: 'item2' }] },
},
{
key: '3',
content: 'item3',
menu: { items: [{ key: '1', content: 'item1' }, { key: '2', content: 'item2' }] },
},
],
},
},
{
key: 'review',
content: 'Reviews',
menu: {
items: [
{ key: '1', content: 'item1' },
{
key: '2',
content: 'item2',
menu: { items: [{ key: '1', content: 'item1' }, { key: '2', content: 'item2' }] },
},
],
},
},
{ key: 'events', content: 'Upcoming Events' },
]

const MenuExampleWithSubMenu = () => (
<Provider
theme={{
componentStyles: {
Menu: {
root: {
zIndex: 1000,
},
},
},
}}
>
<Menu defaultActiveIndex={0} items={items} />
</Provider>
)

export default MenuExampleWithSubMenu
10 changes: 10 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,16 @@ const Types = () => (
description="A vertical menu displays elements vertically."
examplePath="components/Menu/Types/MenuExampleVertical"
/>
<ComponentExample
title="Menu with Submenus"
description="A menu can have submenus."
examplePath="components/Menu/Types/MenuExampleWithSubmenu"
/>
<ComponentExample
title="Vertical submenus"
description="A vertical menu can have submenu."
examplePath="components/Menu/Types/MenuExampleVerticalWithSubmenu"
/>
</ExampleSection>
)

Expand Down
18 changes: 17 additions & 1 deletion src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,22 @@ class Menu extends AutoControlledComponent<Extendable<IMenuProps>, any> {

static Item = MenuItem

state = {
submenuOpen: false,
activeIndex: '',
}

handleItemOverrides = predefinedProps => ({
onClick: (e, itemProps) => {
const { index } = itemProps

this.setState(prev => {
Copy link
Member Author

Choose a reason for hiding this comment

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

When should we use setState vs. trySetState? Can someone please explain in brief the purpose of trySetState.

Copy link
Contributor

Choose a reason for hiding this comment

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

lets schedule a short sync for tomorrow, I will explain the concept to you. Before that, please, consider to read about Controlled and Uncontrolled Components, as well as try to make experiments with input component being used in these different modes - note how the value updates for each, when onChange handler is triggered, how value updates in controlled mode.

if (prev.activeIndex === index) {
return { submenuOpen: !prev.submenuOpen }
}
return { submenuOpen: true }
})

this.trySetState({ activeIndex: index })

_.invoke(predefinedProps, 'onClick', e, itemProps)
Expand All @@ -133,7 +145,7 @@ class Menu extends AutoControlledComponent<Extendable<IMenuProps>, any> {

renderItems = (variables: ComponentVariablesObject) => {
const { iconOnly, items, pills, pointing, renderItem, type, underlined, vertical } = this.props
const { activeIndex } = this.state
const { activeIndex, submenuOpen } = this.state

return _.map(items, (item, index) =>
MenuItem.create(item, {
Expand All @@ -147,6 +159,10 @@ class Menu extends AutoControlledComponent<Extendable<IMenuProps>, any> {
vertical,
index,
active: parseInt(activeIndex, 10) === index,
...(activeIndex === index && { submenuOpen }),
...(item.menu && {
styles: { position: 'relative' },
}),
},
overrideProps: this.handleItemOverrides,
render: renderItem,
Expand Down
70 changes: 49 additions & 21 deletions src/components/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as React from 'react'

import { childrenExist, createShorthandFactory, customPropTypes, UIComponent } from '../../lib'
import Icon from '../Icon'
import Menu from '../Menu'
// import Provider from '../Provider'
import { menuItemBehavior } from '../../lib/accessibility'
import { Accessibility, AccessibilityActionHandlers } from '../../lib/accessibility/interfaces'
import IsFromKeyboard from '../../lib/isFromKeyboard'
Expand All @@ -29,10 +31,12 @@ export interface IMenuItemProps {
icon?: ShorthandValue
iconOnly?: boolean
index?: number
menu?: any
onClick?: ComponentEventHandler<IMenuItemProps>
pills?: boolean
pointing?: boolean | 'start' | 'end'
renderIcon?: ShorthandRenderFunction
submenuOpen?: boolean
type?: 'primary' | 'secondary'
underlined?: boolean
vertical?: boolean
Expand Down Expand Up @@ -82,6 +86,9 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, IMenuItemState> {
/** MenuItem index inside Menu. */
index: PropTypes.number,

/** MenuItem's submenu */
menu: PropTypes.any,

/**
* Called on click. When passed, the component will render as an `a`
* tag by default instead of a `div`.
Expand All @@ -100,6 +107,9 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, IMenuItemState> {
*/
pointing: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['start', 'end'])]),

/** */
submenuOpen: PropTypes.bool,

/** The menu can have primary or secondary type */
type: PropTypes.oneOf(['primary', 'secondary']),

Expand Down Expand Up @@ -136,7 +146,7 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, IMenuItemState> {
state = IsFromKeyboard.initial

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

return (
<ElementType
Expand All @@ -145,29 +155,47 @@ class MenuItem extends UIComponent<Extendable<IMenuItemProps>, IMenuItemState> {
{...accessibility.keyHandlers.root}
{...rest}
>
{childrenExist(children) ? (
children
) : (
<a
className={cx('ui-menu__item__anchor', classes.anchor)}
onClick={this.handleClick}
onBlur={this.handleBlur}
onFocus={this.handleFocus}
{...accessibility.attributes.anchor}
{...accessibility.keyHandlers.anchor}
>
{icon &&
Icon.create(this.props.icon, {
defaultProps: { xSpacing: !!content ? 'after' : 'none' },
render: renderIcon,
})}
{content}
</a>
)}
{childrenExist(children) ? children : this.renderMenuItem(accessibility, classes)}
</ElementType>
)
}

private renderMenuItem = (accessibility, classes) => {
const { content, icon, renderIcon, menu, type, vertical, submenuOpen } = this.props
return (
<>
<a
className={cx('ui-menu__item__anchor', classes.anchor)}
onClick={this.handleClick}
onBlur={this.handleBlur}
onFocus={this.handleFocus}
{...accessibility.attributes.anchor}
{...accessibility.keyHandlers.anchor}
>
{icon &&
Icon.create(this.props.icon, {
defaultProps: { xSpacing: !!content ? 'after' : 'none' },
render: renderIcon,
})}
{content}
</a>
{menu && submenuOpen ? (
<Menu
accessibility={null}
items={menu.items}
vertical
type={type}
styles={{
// background: 'white',
// zIndex: '1000',
position: 'absolute',
top: vertical ? '0' : '100%',
left: vertical ? '100%' : '0',
}}
/>
) : null}
</>
)
}
protected actionHandlers: AccessibilityActionHandlers = {
performClick: event => this.handleClick(event),
}
Expand Down
2 changes: 1 addition & 1 deletion src/themes/teams/components/Menu/menuVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface IMenuVariables {
export default (siteVars: any): IMenuVariables => {
return {
defaultColor: siteVars.gray02,
defaultBackgroundColor: 'transparent',
defaultBackgroundColor: '#FFF',

defaultActiveColor: siteVars.black,
defaultActiveBackgroundColor: siteVars.gray10,
Expand Down