UNPKG

5.68 kBJavaScriptView Raw
1/**
2* This source code is quoted from rc-menu.
3* homepage: https://github.com/react-component/menu
4*/
5import React from 'react';
6import ReactDOM from 'react-dom';
7import PropTypes from 'prop-types';
8import { KeyCode } from 'tinper-bee-core';
9import classNames from 'classnames';
10import scrollIntoView from 'dom-scroll-into-view';
11import { connect } from 'mini-store';
12import { noop, menuAllProps } from './util';
13
14/* eslint react/no-is-mounted:0 */
15
16export class MenuItem extends React.Component {
17 static propTypes = {
18 attribute: PropTypes.object,
19 rootPrefixCls: PropTypes.string,
20 eventKey: PropTypes.string,
21 active: PropTypes.bool,
22 children: PropTypes.any,
23 selectedKeys: PropTypes.array,
24 disabled: PropTypes.bool,
25 title: PropTypes.string,
26 onItemHover: PropTypes.func,
27 onSelect: PropTypes.func,
28 onClick: PropTypes.func,
29 onDeselect: PropTypes.func,
30 parentMenu: PropTypes.object,
31 onDestroy: PropTypes.func,
32 onMouseEnter: PropTypes.func,
33 onMouseLeave: PropTypes.func,
34 multiple: PropTypes.bool,
35 isSelected: PropTypes.bool,
36 manualRef: PropTypes.func,
37 itemIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
38 };
39
40 static defaultProps = {
41 onSelect: noop,
42 onMouseEnter: noop,
43 onMouseLeave: noop,
44 manualRef: noop,
45 };
46
47 constructor(props) {
48 super(props);
49 }
50
51 componentDidMount() {
52 // invoke customized ref to expose component to mixin
53 this.callRef();
54 }
55
56 componentDidUpdate() {
57 if (this.props.active) {
58 scrollIntoView(ReactDOM.findDOMNode(this), ReactDOM.findDOMNode(this.props.parentMenu), {
59 onlyScrollIfNeeded: true,
60 });
61 }
62 this.callRef();
63 }
64
65 componentWillUnmount() {
66 const props = this.props;
67 if (props.onDestroy) {
68 props.onDestroy(props.eventKey);
69 }
70 }
71
72 onKeyDown = (e) => {
73 const keyCode = e.keyCode;
74 if (keyCode === KeyCode.ENTER) {
75 this.onClick(e);
76 return true;
77 }
78 };
79
80 onMouseLeave = (e) => {
81 const { eventKey, onItemHover, onMouseLeave } = this.props;
82 onItemHover({
83 key: eventKey,
84 hover: false,
85 });
86 onMouseLeave({
87 key: eventKey,
88 domEvent: e,
89 });
90 };
91
92 onMouseEnter = (e) => {
93 const { eventKey, onItemHover, onMouseEnter } = this.props;
94 onItemHover({
95 key: eventKey,
96 hover: true,
97 });
98 onMouseEnter({
99 key: eventKey,
100 domEvent: e,
101 });
102 };
103
104 onClick = (e) => {
105 const { eventKey, multiple, onClick, onSelect, onDeselect, isSelected } = this.props;
106 const info = {
107 key: eventKey,
108 keyPath: [eventKey],
109 item: this,
110 domEvent: e,
111 };
112 onClick(info);
113 if (multiple) {
114 if (isSelected) {
115 onDeselect(info);
116 } else {
117 onSelect(info);
118 }
119 } else if (!isSelected) {
120 onSelect(info);
121 }
122 };
123
124 getPrefixCls() {
125 return `${this.props.rootPrefixCls}-item`;
126 }
127
128 getActiveClassName() {
129 return `${this.getPrefixCls()}-active`;
130 }
131
132 getSelectedClassName() {
133 return `${this.getPrefixCls()}-selected`;
134 }
135
136 getDisabledClassName() {
137 return `${this.getPrefixCls()}-disabled`;
138 }
139
140 callRef() {
141 if (this.props.manualRef) {
142 this.props.manualRef(this);
143 }
144 }
145
146 render() {
147 const props = { ...this.props };
148 const className = classNames(this.getPrefixCls(), props.className, {
149 [this.getActiveClassName()]: !props.disabled && props.active,
150 [this.getSelectedClassName()]: props.isSelected,
151 [this.getDisabledClassName()]: props.disabled,
152 });
153 let attrs = {
154 ...props.attribute,
155 title: props.title,
156 className,
157 // set to menuitem by default
158 role: props.role || 'menuitem',
159 'aria-disabled': props.disabled,
160 };
161
162 if (props.role === 'option') {
163 // overwrite to option
164 attrs = {
165 ...attrs,
166 role: 'option',
167 'aria-selected': props.isSelected,
168 };
169 } else if (props.role === null || props.role === 'none') {
170 // sometimes we want to specify role inside <li/> element
171 // <li><a role='menuitem'>Link</a></li> would be a good example
172 // in this case the role on <li/> should be "none" to
173 // remove the implied listitem role.
174 // https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html
175 attrs.role = 'none';
176 }
177 // In case that onClick/onMouseLeave/onMouseEnter is passed down from owner
178 const mouseEvent = {
179 onClick: props.disabled ? null : this.onClick,
180 onMouseLeave: props.disabled ? null : this.onMouseLeave,
181 onMouseEnter: props.disabled ? null : this.onMouseEnter,
182 };
183 const style = {
184 ...props.style,
185 };
186 if (props.mode === 'inline') {
187 style.paddingLeft = props.inlineIndent * props.level;
188 }
189 menuAllProps.forEach(key => delete props[key]);
190 let icon = this.props.itemIcon;
191 if (typeof this.props.itemIcon === 'function') {
192 icon = React.createElement(this.props.itemIcon, this.props);
193 }
194 return (
195 <li
196 {...props}
197 {...attrs}
198 {...mouseEvent}
199 style={style}
200 >
201 {props.children}
202 {icon}
203 </li>
204 );
205 }
206}
207
208MenuItem.isMenuItem = true;
209
210const connected = connect(({ activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({
211 active: activeKey[subMenuKey] === eventKey,
212 isSelected: selectedKeys.indexOf(eventKey) !== -1,
213}))(MenuItem);
214
215export default connected;