Skip to content

Commit 70bbf15

Browse files
committed
chore(Menu): use React.forwardRef() (#4254)
1 parent 5367b01 commit 70bbf15

File tree

8 files changed

+170
-170
lines changed

8 files changed

+170
-170
lines changed

src/collections/Menu/Menu.js

Lines changed: 83 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types'
44
import React from 'react'
55

66
import {
7-
ModernAutoControlledComponent as Component,
87
childrenUtils,
98
customPropTypes,
109
createShorthandFactory,
@@ -15,6 +14,7 @@ import {
1514
useKeyOrValueAndKey,
1615
useValueAndKey,
1716
useWidthProp,
17+
useAutoControlledValue,
1818
} from '../../lib'
1919
import MenuHeader from './MenuHeader'
2020
import MenuItem from './MenuItem'
@@ -24,90 +24,95 @@ import MenuMenu from './MenuMenu'
2424
* A menu displays grouped navigation actions.
2525
* @see Dropdown
2626
*/
27-
class Menu extends Component {
28-
handleItemOverrides = (predefinedProps) => ({
29-
onClick: (e, itemProps) => {
30-
const { index } = itemProps
31-
32-
this.setState({ activeIndex: index })
33-
34-
_.invoke(predefinedProps, 'onClick', e, itemProps)
35-
_.invoke(this.props, 'onItemClick', e, itemProps)
36-
},
27+
const Menu = React.forwardRef(function (props, ref) {
28+
const {
29+
attached,
30+
borderless,
31+
children,
32+
className,
33+
color,
34+
compact,
35+
fixed,
36+
floated,
37+
fluid,
38+
icon,
39+
inverted,
40+
items,
41+
pagination,
42+
pointing,
43+
secondary,
44+
size,
45+
stackable,
46+
tabular,
47+
text,
48+
vertical,
49+
widths,
50+
} = props
51+
const [activeIndex, setActiveIndex] = useAutoControlledValue({
52+
state: props.activeIndex,
53+
defaultState: props.defaultActiveIndex,
54+
initialState: -1,
3755
})
3856

39-
renderItems() {
40-
const { items } = this.props
41-
const { activeIndex } = this.state
42-
43-
return _.map(items, (item, index) =>
44-
MenuItem.create(item, {
45-
defaultProps: {
46-
active: parseInt(activeIndex, 10) === index,
47-
index,
48-
},
49-
overrideProps: this.handleItemOverrides,
50-
}),
51-
)
52-
}
53-
54-
render() {
55-
const {
56-
attached,
57-
borderless,
58-
children,
59-
className,
60-
color,
61-
compact,
62-
fixed,
63-
floated,
64-
fluid,
65-
icon,
66-
inverted,
67-
pagination,
68-
pointing,
69-
secondary,
70-
size,
71-
stackable,
72-
tabular,
73-
text,
74-
vertical,
75-
widths,
76-
} = this.props
77-
const classes = cx(
78-
'ui',
79-
color,
80-
size,
81-
useKeyOnly(borderless, 'borderless'),
82-
useKeyOnly(compact, 'compact'),
83-
useKeyOnly(fluid, 'fluid'),
84-
useKeyOnly(inverted, 'inverted'),
85-
useKeyOnly(pagination, 'pagination'),
86-
useKeyOnly(pointing, 'pointing'),
87-
useKeyOnly(secondary, 'secondary'),
88-
useKeyOnly(stackable, 'stackable'),
89-
useKeyOnly(text, 'text'),
90-
useKeyOnly(vertical, 'vertical'),
91-
useKeyOrValueAndKey(attached, 'attached'),
92-
useKeyOrValueAndKey(floated, 'floated'),
93-
useKeyOrValueAndKey(icon, 'icon'),
94-
useKeyOrValueAndKey(tabular, 'tabular'),
95-
useValueAndKey(fixed, 'fixed'),
96-
useWidthProp(widths, 'item'),
97-
className,
98-
'menu',
99-
)
100-
const rest = getUnhandledProps(Menu, this.props)
101-
const ElementType = getElementType(Menu, this.props)
102-
57+
const classes = cx(
58+
'ui',
59+
color,
60+
size,
61+
useKeyOnly(borderless, 'borderless'),
62+
useKeyOnly(compact, 'compact'),
63+
useKeyOnly(fluid, 'fluid'),
64+
useKeyOnly(inverted, 'inverted'),
65+
useKeyOnly(pagination, 'pagination'),
66+
useKeyOnly(pointing, 'pointing'),
67+
useKeyOnly(secondary, 'secondary'),
68+
useKeyOnly(stackable, 'stackable'),
69+
useKeyOnly(text, 'text'),
70+
useKeyOnly(vertical, 'vertical'),
71+
useKeyOrValueAndKey(attached, 'attached'),
72+
useKeyOrValueAndKey(floated, 'floated'),
73+
useKeyOrValueAndKey(icon, 'icon'),
74+
useKeyOrValueAndKey(tabular, 'tabular'),
75+
useValueAndKey(fixed, 'fixed'),
76+
useWidthProp(widths, 'item'),
77+
className,
78+
'menu',
79+
)
80+
const rest = getUnhandledProps(Menu, props)
81+
const ElementType = getElementType(Menu, props)
82+
83+
if (!childrenUtils.isNil(children)) {
10384
return (
104-
<ElementType {...rest} className={classes}>
105-
{childrenUtils.isNil(children) ? this.renderItems() : children}
85+
<ElementType {...rest} className={classes} ref={ref}>
86+
{children}
10687
</ElementType>
10788
)
10889
}
109-
}
11090

91+
return (
92+
<ElementType {...rest} className={classes} ref={ref}>
93+
{_.map(items, (item, index) =>
94+
MenuItem.create(item, {
95+
defaultProps: {
96+
active: parseInt(activeIndex, 10) === index,
97+
index,
98+
},
99+
overrideProps: (predefinedProps) => ({
100+
onClick: (e, itemProps) => {
101+
const itemIndex = itemProps.index
102+
103+
setActiveIndex(itemIndex)
104+
105+
_.invoke(predefinedProps, 'onClick', e, itemProps)
106+
_.invoke(props, 'onItemClick', e, itemProps)
107+
},
108+
}),
109+
}),
110+
)}
111+
</ElementType>
112+
)
113+
})
114+
115+
Menu.displayName = 'Menu'
111116
Menu.propTypes = {
112117
/** An element type to render as (string or function). */
113118
as: PropTypes.elementType,
@@ -190,8 +195,6 @@ Menu.propTypes = {
190195
widths: PropTypes.oneOf(SUI.WIDTHS),
191196
}
192197

193-
Menu.autoControlledProps = ['activeIndex']
194-
195198
Menu.Header = MenuHeader
196199
Menu.Item = MenuItem
197200
Menu.Menu = MenuMenu

src/collections/Menu/MenuHeader.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,20 @@ import { childrenUtils, customPropTypes, getElementType, getUnhandledProps } fro
77
/**
88
* A menu item may include a header or may itself be a header.
99
*/
10-
function MenuHeader(props) {
10+
const MenuHeader = React.forwardRef(function (props, ref) {
1111
const { children, className, content } = props
1212
const classes = cx('header', className)
1313
const rest = getUnhandledProps(MenuHeader, props)
1414
const ElementType = getElementType(MenuHeader, props)
1515

1616
return (
17-
<ElementType {...rest} className={classes}>
17+
<ElementType {...rest} className={classes} ref={ref}>
1818
{childrenUtils.isNil(children) ? content : children}
1919
</ElementType>
2020
)
21-
}
21+
})
2222

23+
MenuHeader.displayName = 'MenuHeader'
2324
MenuHeader.propTypes = {
2425
/** An element type to render as (string or function). */
2526
as: PropTypes.elementType,

src/collections/Menu/MenuItem.js

Lines changed: 54 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import cx from 'clsx'
22
import _ from 'lodash'
33
import PropTypes from 'prop-types'
4-
import React, { Component } from 'react'
4+
import React from 'react'
55

66
import {
77
childrenUtils,
@@ -12,70 +12,70 @@ import {
1212
SUI,
1313
useKeyOnly,
1414
useKeyOrValueAndKey,
15+
useEventCallback,
1516
} from '../../lib'
1617
import Icon from '../../elements/Icon'
1718

1819
/**
1920
* A menu can contain an item.
2021
*/
21-
export default class MenuItem extends Component {
22-
handleClick = (e) => {
23-
const { disabled } = this.props
24-
25-
if (!disabled) _.invoke(this.props, 'onClick', e, this.props)
26-
}
27-
28-
render() {
29-
const {
30-
active,
31-
children,
32-
className,
33-
color,
34-
content,
35-
disabled,
36-
fitted,
37-
header,
38-
icon,
39-
link,
40-
name,
41-
onClick,
42-
position,
43-
} = this.props
44-
45-
const classes = cx(
46-
color,
47-
position,
48-
useKeyOnly(active, 'active'),
49-
useKeyOnly(disabled, 'disabled'),
50-
useKeyOnly(icon === true || (icon && !(name || content)), 'icon'),
51-
useKeyOnly(header, 'header'),
52-
useKeyOnly(link, 'link'),
53-
useKeyOrValueAndKey(fitted, 'fitted'),
54-
'item',
55-
className,
56-
)
57-
const ElementType = getElementType(MenuItem, this.props, () => {
58-
if (onClick) return 'a'
59-
})
60-
const rest = getUnhandledProps(MenuItem, this.props)
61-
62-
if (!childrenUtils.isNil(children)) {
63-
return (
64-
<ElementType {...rest} className={classes} onClick={this.handleClick}>
65-
{children}
66-
</ElementType>
67-
)
22+
const MenuItem = React.forwardRef(function (props, ref) {
23+
const {
24+
active,
25+
children,
26+
className,
27+
color,
28+
content,
29+
disabled,
30+
fitted,
31+
header,
32+
icon,
33+
link,
34+
name,
35+
onClick,
36+
position,
37+
} = props
38+
39+
const classes = cx(
40+
color,
41+
position,
42+
useKeyOnly(active, 'active'),
43+
useKeyOnly(disabled, 'disabled'),
44+
useKeyOnly(icon === true || (icon && !(name || content)), 'icon'),
45+
useKeyOnly(header, 'header'),
46+
useKeyOnly(link, 'link'),
47+
useKeyOrValueAndKey(fitted, 'fitted'),
48+
'item',
49+
className,
50+
)
51+
const ElementType = getElementType(MenuItem, props, () => {
52+
if (onClick) return 'a'
53+
})
54+
const rest = getUnhandledProps(MenuItem, props)
55+
56+
const handleClick = useEventCallback((e) => {
57+
if (!disabled) {
58+
_.invoke(props, 'onClick', e, props)
6859
}
60+
})
6961

62+
if (!childrenUtils.isNil(children)) {
7063
return (
71-
<ElementType {...rest} className={classes} onClick={this.handleClick}>
72-
{Icon.create(icon, { autoGenerateKey: false })}
73-
{childrenUtils.isNil(content) ? _.startCase(name) : content}
64+
<ElementType {...rest} className={classes} onClick={handleClick} ref={ref}>
65+
{children}
7466
</ElementType>
7567
)
7668
}
77-
}
7869

70+
return (
71+
<ElementType {...rest} className={classes} onClick={handleClick} ref={ref}>
72+
{Icon.create(icon, { autoGenerateKey: false })}
73+
{childrenUtils.isNil(content) ? _.startCase(name) : content}
74+
</ElementType>
75+
)
76+
})
77+
78+
MenuItem.displayName = 'MenuItem'
7979
MenuItem.propTypes = {
8080
/** An element type to render as (string or function). */
8181
as: PropTypes.elementType,
@@ -130,3 +130,5 @@ MenuItem.propTypes = {
130130
}
131131

132132
MenuItem.create = createShorthandFactory(MenuItem, (val) => ({ content: val, name: val }))
133+
134+
export default MenuItem

src/collections/Menu/MenuMenu.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@ import { childrenUtils, customPropTypes, getElementType, getUnhandledProps } fro
77
/**
88
* A menu can contain a sub menu.
99
*/
10-
function MenuMenu(props) {
10+
const MenuMenu = React.forwardRef(function (props, ref) {
1111
const { children, className, content, position } = props
1212

1313
const classes = cx(position, 'menu', className)
1414
const rest = getUnhandledProps(MenuMenu, props)
1515
const ElementType = getElementType(MenuMenu, props)
1616

1717
return (
18-
<ElementType {...rest} className={classes}>
18+
<ElementType {...rest} className={classes} ref={ref}>
1919
{childrenUtils.isNil(children) ? content : children}
2020
</ElementType>
2121
)
22-
}
22+
})
2323

24+
MenuMenu.displayName = 'MenuMenu'
2425
MenuMenu.propTypes = {
2526
/** An element type to render as (string or function). */
2627
as: PropTypes.elementType,

0 commit comments

Comments
 (0)