1 |
|
2 |
|
3 |
|
4 |
|
5 | import React from 'react';
|
6 | import ReactDOM from 'react-dom';
|
7 | import PropTypes from 'prop-types';
|
8 | import { KeyCode } from 'tinper-bee-core';
|
9 | import classNames from 'classnames';
|
10 | import scrollIntoView from 'dom-scroll-into-view';
|
11 | import { connect } from 'mini-store';
|
12 | import { noop, menuAllProps } from './util';
|
13 |
|
14 |
|
15 |
|
16 | export 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 |
|
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 |
|
158 | role: props.role || 'menuitem',
|
159 | 'aria-disabled': props.disabled,
|
160 | };
|
161 |
|
162 | if (props.role === 'option') {
|
163 |
|
164 | attrs = {
|
165 | ...attrs,
|
166 | role: 'option',
|
167 | 'aria-selected': props.isSelected,
|
168 | };
|
169 | } else if (props.role === null || props.role === 'none') {
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | attrs.role = 'none';
|
176 | }
|
177 |
|
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 |
|
208 | MenuItem.isMenuItem = true;
|
209 |
|
210 | const connected = connect(({ activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({
|
211 | active: activeKey[subMenuKey] === eventKey,
|
212 | isSelected: selectedKeys.indexOf(eventKey) !== -1,
|
213 | }))(MenuItem);
|
214 |
|
215 | export default connected;
|