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

Commit 31bd156

Browse files
feat(menu): Vertical menu (#21)
* feat(menu): Vertical menu - initial impl * feat(menu): Vertical menu - fixed width * refactor(menu): add typings
1 parent c7b7621 commit 31bd156

File tree

8 files changed

+142
-27
lines changed

8 files changed

+142
-27
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1717

1818
## [Unreleased]
1919

20+
### Features
21+
- Add Menu `vertical` prop @miroslavstastny ([#21](https://github.com/stardust-ui/react/pull/21))
22+
2023
### Documentation
2124
- Improve UX for "knobs" form on component examples @levithomason ([#20](https://github.com/stardust-ui/react/pull/20))
2225

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react'
2+
import { Menu } from '@stardust-ui/react'
3+
4+
const items = [
5+
{ key: 'editorials', content: 'Editorials' },
6+
{ key: 'review', content: 'Reviews' },
7+
{ key: 'events', content: 'Upcoming Events' },
8+
]
9+
10+
class MenuExampleVerticalShorthand extends React.Component {
11+
render() {
12+
return <Menu defaultActiveIndex={0} items={items} vertical />
13+
}
14+
}
15+
16+
export default MenuExampleVerticalShorthand
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react'
2+
import _ from 'lodash'
3+
import { Menu, MenuItem } from '@stardust-ui/react'
4+
5+
const items = [
6+
{ key: 'editorials', content: 'Editorials' },
7+
{ key: 'review', content: 'Reviews' },
8+
{ key: 'events', content: 'Upcoming Events' },
9+
]
10+
11+
class MenuExampleVertical extends React.Component {
12+
state = { activeIndex: 0 }
13+
14+
handleItemClick = activeIndex => () => {
15+
this.setState({ activeIndex })
16+
}
17+
18+
render() {
19+
const { activeIndex } = this.state
20+
21+
return (
22+
<Menu vertical>
23+
{_.times(3, i => {
24+
return (
25+
<MenuItem
26+
key={items[i].key}
27+
onClick={this.handleItemClick(i)}
28+
content={items[i].content}
29+
vertical
30+
active={activeIndex === i}
31+
/>
32+
)
33+
})}
34+
</Menu>
35+
)
36+
}
37+
}
38+
39+
export default MenuExampleVertical

docs/src/examples/components/Menu/Types/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ const Types = () => (
1414
description="A menu can point to show its relationship to nearby content."
1515
examplePath="components/Menu/Types/MenuExamplePrimary"
1616
/>
17+
<ComponentExample
18+
title="Vertical Menu"
19+
description="A vertical menu displays elements vertically."
20+
examplePath="components/Menu/Types/MenuExampleVertical"
21+
/>
1722
</ExampleSection>
1823
)
1924

src/components/Menu/Menu.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
import _ from 'lodash'
22
import PropTypes from 'prop-types'
3-
import React from 'react'
3+
import React, { ReactNode } from 'react'
44

55
import { AutoControlledComponent, childrenExist, customPropTypes } from '../../lib'
66
import MenuItem from './MenuItem'
77
import menuRules from './menuRules'
88
import menuVariables from './menuVariables'
99

10-
class Menu extends AutoControlledComponent<any, any> {
10+
export type MenuType = 'primary' | 'secondary'
11+
export type MenuShape = 'pills' | 'pointing' | 'underlined'
12+
13+
export interface IMenuProps {
14+
as?: string
15+
activeIndex?: number | string
16+
children?: ReactNode
17+
className?: string
18+
defaultActiveIndex?: number | string
19+
items?: any
20+
shape?: MenuShape
21+
type?: MenuType
22+
vertical?: boolean
23+
}
24+
25+
class Menu extends AutoControlledComponent<IMenuProps, any> {
1126
static displayName = 'Menu'
1227

1328
static className = 'ui-menu'
@@ -35,10 +50,13 @@ class Menu extends AutoControlledComponent<any, any> {
3550
/** Shorthand array of props for Menu. */
3651
items: customPropTypes.collectionShorthand,
3752

53+
shape: PropTypes.oneOf(['pills', 'pointing', 'underlined']),
54+
3855
/** The menu can have primary or secondary type */
3956
type: PropTypes.oneOf(['primary', 'secondary']),
4057

41-
shape: PropTypes.oneOf(['pills', 'pointing', 'underlined']),
58+
/** A vertical menu displays elements vertically. */
59+
vertical: PropTypes.bool,
4260
}
4361

4462
static defaultProps = {
@@ -54,6 +72,7 @@ class Menu extends AutoControlledComponent<any, any> {
5472
'items',
5573
'shape',
5674
'type',
75+
'vertical',
5776
]
5877

5978
static autoControlledProps = ['activeIndex']
@@ -73,14 +92,15 @@ class Menu extends AutoControlledComponent<any, any> {
7392
})
7493

7594
renderItems = () => {
76-
const { items, type, shape } = this.props
95+
const { items, type, shape, vertical } = this.props
7796
const { activeIndex } = this.state
7897

7998
return _.map(items, (item, index) =>
8099
MenuItem.create(item, {
81100
defaultProps: {
82101
type,
83102
shape,
103+
vertical,
84104
index,
85105
active: parseInt(activeIndex, 10) === index,
86106
},

src/components/Menu/MenuItem.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
11
import _ from 'lodash'
22
import cx from 'classnames'
33
import PropTypes from 'prop-types'
4-
import React from 'react'
4+
import React, { ReactNode } from 'react'
55

66
import { childrenExist, createShorthandFactory, customPropTypes, UIComponent } from '../../lib'
77

8+
import { MenuType, MenuShape } from './Menu'
9+
810
import menuItemRules from './menuItemRules'
911
import menuVariables from './menuVariables'
1012

11-
class MenuItem extends UIComponent<any, any> {
13+
export interface IMenuItemProps {
14+
active?: boolean
15+
as?: string
16+
children?: ReactNode
17+
className?: string
18+
content?: ReactNode
19+
index?: number
20+
onClick?: (any, IMenuItemProps) => void
21+
shape?: MenuShape
22+
type?: MenuType
23+
vertical?: boolean
24+
}
25+
26+
class MenuItem extends UIComponent<IMenuItemProps, any> {
1227
static displayName = 'MenuItem'
1328

1429
static className = 'ui-menu__item'
@@ -47,13 +62,13 @@ class MenuItem extends UIComponent<any, any> {
4762
*/
4863
onClick: PropTypes.func,
4964

50-
/** A menu can point to show its relationship to nearby content. */
51-
pointing: PropTypes.bool,
65+
shape: PropTypes.oneOf(['pills', 'pointing', 'underlined']),
5266

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

56-
shape: PropTypes.oneOf(['pills', 'pointing', 'underlined']),
70+
/** A vertical menu displays elements vertically. */
71+
vertical: PropTypes.bool,
5772
}
5873

5974
static defaultProps = {
@@ -68,9 +83,9 @@ class MenuItem extends UIComponent<any, any> {
6883
'content',
6984
'index',
7085
'onClick',
71-
'pointing',
7286
'shape',
7387
'type',
88+
'vertical',
7489
]
7590

7691
handleClick = e => {

src/components/Menu/menuItemRules.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,37 @@
11
import { pxToRem } from '../../lib'
2+
import { IMenuItemProps } from './MenuItem'
23

34
const underlinedItem = (color: string) => ({
45
borderBottom: `solid 5px ${color}`,
56
transition: 'color .1s ease',
67
})
78

9+
const itemSeparator = ({ props, variables }: { props: IMenuItemProps; variables: any }) => {
10+
const { active, shape, type, vertical } = props
11+
return {
12+
...((!shape || shape === 'pointing') && {
13+
':before': {
14+
position: 'absolute',
15+
content: '""',
16+
top: 0,
17+
right: 0,
18+
...(vertical ? { width: '100%', height: '1px' } : { width: '1px', height: '100%' }),
19+
background: variables.defaultBorderColor,
20+
...(type === 'primary' && {
21+
background: variables.typePrimaryBorderColor,
22+
}),
23+
},
24+
...(vertical && {
25+
':first-child:before': {
26+
display: 'none',
27+
},
28+
}),
29+
}),
30+
}
31+
}
32+
833
export default {
9-
root: ({ props, variables }) => {
34+
root: ({ props, variables }: { props: IMenuItemProps; variables: any }) => {
1035
const { active, shape, type } = props
1136
return {
1237
color: variables.defaultColor,
@@ -26,20 +51,7 @@ export default {
2651
boxShadow: 'none',
2752
color: variables.defaultColor,
2853
}),
29-
...((!shape || shape === 'pointing') && {
30-
':before': {
31-
position: 'absolute',
32-
content: '""',
33-
top: 0,
34-
right: 0,
35-
height: '100%',
36-
width: '1px',
37-
background: variables.defaultBorderColor,
38-
...(type === 'primary' && {
39-
background: variables.typePrimaryBorderColor,
40-
}),
41-
},
42-
}),
54+
...itemSeparator({ props, variables }),
4355

4456
':hover': {
4557
color: variables.defaultActiveColor,

src/components/Menu/menuRules.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import { pxToRem } from '../../lib'
2+
import { IMenuProps } from './Menu'
23

34
const solidBorder = (color: string) => ({
45
border: `1px solid ${color}`,
56
})
67

78
export default {
8-
root: ({ props, variables }) => {
9-
const { type, shape } = props
9+
root: ({ props, variables }: { props: IMenuProps; variables: any }) => {
10+
const { type, shape, vertical } = props
1011
return {
1112
display: 'flex',
13+
...(vertical && {
14+
flexDirection: 'column',
15+
width: pxToRem(200),
16+
}),
1217
...(shape !== 'pills' &&
1318
shape !== 'underlined' && {
1419
...solidBorder(variables.defaultBorderColor),

0 commit comments

Comments
 (0)