From 9350e598c80e21366aa5f86e392a8ba4c2656fd7 Mon Sep 17 00:00:00 2001 From: guifu Date: Wed, 18 Apr 2018 17:03:37 +0800 Subject: [PATCH 1/8] chore: refactor/remove mixin --- src/Menu.jsx | 41 ++-- src/MenuMixin.js | 269 ---------------------- src/SubMenu.jsx | 40 +++- src/SubPopupMenu.js | 306 ++++++++++++++++++++++---- tests/__snapshots__/Menu.spec.js.snap | 1 + 5 files changed, 326 insertions(+), 331 deletions(-) delete mode 100644 src/MenuMixin.js diff --git a/src/Menu.jsx b/src/Menu.jsx index c8874da8..746b678f 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import { Provider, create } from 'mini-store'; -import { default as MenuMixin, getActiveKey } from './MenuMixin'; +import { default as SubPopupMenu, getActiveKey } from './SubPopupMenu'; import { noop } from './util'; const Menu = createReactClass({ @@ -10,6 +10,7 @@ const Menu = createReactClass({ propTypes: { defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), + defaultActiveFirst: PropTypes.bool, selectedKeys: PropTypes.arrayOf(PropTypes.string), defaultOpenKeys: PropTypes.arrayOf(PropTypes.string), openKeys: PropTypes.arrayOf(PropTypes.string), @@ -29,10 +30,12 @@ const Menu = createReactClass({ selectable: PropTypes.bool, multiple: PropTypes.bool, children: PropTypes.any, + className: PropTypes.string, + style: PropTypes.object, + activeKey: PropTypes.string, + prefixCls: PropTypes.string, }, - mixins: [MenuMixin], - isRootMenu: true, getDefaultProps() { @@ -47,6 +50,10 @@ const Menu = createReactClass({ subMenuOpenDelay: 0.1, subMenuCloseDelay: 0.1, triggerSubMenuAction: 'hover', + prefixCls: 'rc-menu', + className: '', + mode: 'vertical', + style: {}, }; }, @@ -175,27 +182,21 @@ const Menu = createReactClass({ return transitionName; }, - renderMenuItem(c, i, subMenuKey) { - /* istanbul ignore if */ - if (!c) { - return null; - } - const state = this.store.getState(); - const extraProps = { - openKeys: state.openKeys, - selectedKeys: state.selectedKeys, - triggerSubMenuAction: this.props.triggerSubMenuAction, - subMenuKey, - }; - return this.renderCommonMenuItem(c, i, extraProps); - }, - render() { - const props = { ...this.props }; + let { children, ...props } = this.props; props.className += ` ${props.prefixCls}-root`; + props = { + ...props, + onClick: this.onClick, + onOpenChange: this.onOpenChange, + onDeselect: this.onDeselect, + onSelect: this.onSelect, + openTransitionName: this.getOpenTransitionName(), + parentMenu: this, + } return ( - {this.renderRoot(props)} + {children} ); }, diff --git a/src/MenuMixin.js b/src/MenuMixin.js deleted file mode 100644 index d81ad6e7..00000000 --- a/src/MenuMixin.js +++ /dev/null @@ -1,269 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import KeyCode from 'rc-util/lib/KeyCode'; -import createChainedFunction from 'rc-util/lib/createChainedFunction'; -import classNames from 'classnames'; -import { getKeyFromChildrenIndex, loopMenuItem } from './util'; -import DOMWrap from './DOMWrap'; - -function allDisabled(arr) { - if (!arr.length) { - return true; - } - return arr.every(c => !!c.props.disabled); -} - -function updateActiveKey(store, menuId, activeKey) { - const state = store.getState(); - store.setState({ - activeKey: { - ...state.activeKey, - [menuId]: activeKey, - }, - }); -} - -export function getActiveKey(props, originalActiveKey) { - let activeKey = originalActiveKey; - const { children, eventKey } = props; - if (activeKey) { - let found; - loopMenuItem(children, (c, i) => { - if (c && !c.props.disabled && activeKey === getKeyFromChildrenIndex(c, eventKey, i)) { - found = true; - } - }); - if (found) { - return activeKey; - } - } - activeKey = null; - if (props.defaultActiveFirst) { - loopMenuItem(children, (c, i) => { - if (!activeKey && c && !c.props.disabled) { - activeKey = getKeyFromChildrenIndex(c, eventKey, i); - } - }); - return activeKey; - } - return activeKey; -} - -function saveRef(index, c) { - if (c) { - this.instanceArray[index] = c; - } -} - -const MenuMixin = { - propTypes: { - focusable: PropTypes.bool, - multiple: PropTypes.bool, - style: PropTypes.object, - defaultActiveFirst: PropTypes.bool, - visible: PropTypes.bool, - activeKey: PropTypes.string, - selectedKeys: PropTypes.arrayOf(PropTypes.string), - defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), - defaultOpenKeys: PropTypes.arrayOf(PropTypes.string), - openKeys: PropTypes.arrayOf(PropTypes.string), - children: PropTypes.any, - }, - - getDefaultProps() { - return { - prefixCls: 'rc-menu', - className: '', - mode: 'vertical', - level: 1, - inlineIndent: 24, - visible: true, - focusable: true, - style: {}, - }; - }, - - componentWillReceiveProps(nextProps) { - const originalActiveKey = 'activeKey' in nextProps ? nextProps.activeKey : - this.getStore().getState().activeKey[this.getEventKey()]; - const activeKey = getActiveKey(nextProps, originalActiveKey); - if (activeKey !== originalActiveKey) { - updateActiveKey(this.getStore(), this.getEventKey(), activeKey); - } - }, - - shouldComponentUpdate(nextProps) { - return this.props.visible || nextProps.visible; - }, - - componentWillMount() { - this.instanceArray = []; - }, - - // all keyboard events callbacks run from here at first - // FIXME: callback is currently used by rc-select, should be more explicit - onKeyDown(e, callback) { - const keyCode = e.keyCode; - let handled; - this.getFlatInstanceArray().forEach((obj) => { - if (obj && obj.props.active && obj.onKeyDown) { - handled = obj.onKeyDown(e); - } - }); - if (handled) { - return 1; - } - let activeItem = null; - if (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN) { - activeItem = this.step(keyCode === KeyCode.UP ? -1 : 1); - } - if (activeItem) { - e.preventDefault(); - updateActiveKey(this.getStore(), this.getEventKey(), activeItem.props.eventKey); - - if (typeof callback === 'function') { - callback(activeItem); - } - - return 1; - } - }, - - onItemHover(e) { - const { key, hover } = e; - updateActiveKey(this.getStore(), this.getEventKey(), hover ? key : null); - }, - - getEventKey() { - // when eventKey not available ,it's menu and return menu id '0-menu-' - return this.props.eventKey || '0-menu-'; - }, - - getStore() { - const store = this.store || this.props.store; - - return store; - }, - - getFlatInstanceArray() { - return this.instanceArray; - }, - - renderCommonMenuItem(child, i, extraProps) { - const state = this.getStore().getState(); - const props = this.props; - const key = getKeyFromChildrenIndex(child, props.eventKey, i); - const childProps = child.props; - const isActive = key === state.activeKey; - const newChildProps = { - mode: props.mode, - level: props.level, - inlineIndent: props.inlineIndent, - renderMenuItem: this.renderMenuItem, - rootPrefixCls: props.prefixCls, - index: i, - parentMenu: this, - // customized ref function, need to be invoked manually in child's componentDidMount - manualRef: childProps.disabled ? undefined : - createChainedFunction(child.ref, saveRef.bind(this, i)), - eventKey: key, - active: !childProps.disabled && isActive, - multiple: props.multiple, - onClick: this.onClick, - onItemHover: this.onItemHover, - openTransitionName: this.getOpenTransitionName(), - openAnimation: props.openAnimation, - subMenuOpenDelay: props.subMenuOpenDelay, - subMenuCloseDelay: props.subMenuCloseDelay, - forceSubMenuRender: props.forceSubMenuRender, - onOpenChange: this.onOpenChange, - onDeselect: this.onDeselect, - onSelect: this.onSelect, - ...extraProps, - }; - if (props.mode === 'inline') { - newChildProps.triggerSubMenuAction = 'click'; - } - return React.cloneElement(child, newChildProps); - }, - - renderRoot(props) { - this.instanceArray = []; - const className = classNames( - props.prefixCls, - props.className, - `${props.prefixCls}-${props.mode}`, - ); - const domProps = { - className, - role: 'menu', - 'aria-activedescendant': '', - }; - if (props.id) { - domProps.id = props.id; - } - if (props.focusable) { - domProps.tabIndex = '0'; - domProps.onKeyDown = this.onKeyDown; - } - return ( - // ESLint is not smart enough to know that the type of `children` was checked. - /* eslint-disable */ - - {React.Children.map( - props.children, - (c, i) => this.renderMenuItem(c, i, props.eventKey || '0-menu-'), - )} - - /*eslint-enable */ - ); - }, - - step(direction) { - let children = this.getFlatInstanceArray(); - const activeKey = this.getStore().getState().activeKey[this.getEventKey()]; - const len = children.length; - if (!len) { - return null; - } - if (direction < 0) { - children = children.concat().reverse(); - } - // find current activeIndex - let activeIndex = -1; - children.every((c, ci) => { - if (c && c.props.eventKey === activeKey) { - activeIndex = ci; - return false; - } - return true; - }); - if (!this.props.defaultActiveFirst && activeIndex !== -1) { - if (allDisabled(children.slice(activeIndex, len - 1))) { - return undefined; - } - } - const start = (activeIndex + 1) % len; - let i = start; - for (; ;) { - const child = children[i]; - if (!child || child.props.disabled) { - i = (i + 1 + len) % len; - // complete a loop - if (i === start) { - return null; - } - } else { - return child; - } - } - }, -}; - -export default MenuMixin; diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index 75cc9faf..7f3d6763 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -8,6 +8,7 @@ import classNames from 'classnames'; import { connect } from 'mini-store'; import SubPopupMenu from './SubPopupMenu'; import placements from './placements'; +import Animate from 'rc-animate'; import { noop, loopMenuItemRecursively, @@ -350,6 +351,7 @@ const SubMenu = createReactClass({ openAnimation: props.openAnimation, onOpenChange: this.onOpenChange, subMenuOpenDelay: props.subMenuOpenDelay, + parentMenu: this, subMenuCloseDelay: props.subMenuCloseDelay, forceSubMenuRender: props.forceSubMenuRender, triggerSubMenuAction: props.triggerSubMenuAction, @@ -360,7 +362,43 @@ const SubMenu = createReactClass({ id: this._menuId, manualRef: this.saveMenuInstance, }; - return {children}; + + const haveRendered = this.haveRendered; + this.haveRendered = true; + + this.haveOpened = this.haveOpened || baseProps.visible || baseProps.forceSubMenuRender; + // never rendered not planning to, don't render + if (!this.haveOpened) { + return
; + } + + // don't show transition on first rendering (no animation for opened menu) + // show appear transition if it's not visible (not sure why) + // show appear transition if it's not inline mode + const transitionAppear = haveRendered || !baseProps.visible || !baseProps.mode === 'inline'; + + baseProps.className += ` ${baseProps.prefixCls}-sub`; + const animProps = {}; + + if (baseProps.openTransitionName) { + animProps.transitionName = baseProps.openTransitionName; + } else if (typeof baseProps.openAnimation === 'object') { + animProps.animation = { ...baseProps.openAnimation }; + if (!transitionAppear) { + delete animProps.animation.appear; + } + } + + return ( + + {children} + + ); }, saveSubMenuTitle(subMenuTitle) { diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index 92974136..cd10a265 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -3,7 +3,60 @@ import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import Animate from 'rc-animate'; import { connect } from 'mini-store'; -import { default as MenuMixin, getActiveKey } from './MenuMixin'; +import KeyCode from 'rc-util/lib/KeyCode'; +import createChainedFunction from 'rc-util/lib/createChainedFunction'; +import classNames from 'classnames'; +import { getKeyFromChildrenIndex, loopMenuItem } from './util'; +import DOMWrap from './DOMWrap'; + +function allDisabled(arr) { + if (!arr.length) { + return true; + } + return arr.every(c => !!c.props.disabled); +} + +function updateActiveKey(store, menuId, activeKey) { + const state = store.getState(); + store.setState({ + activeKey: { + ...state.activeKey, + [menuId]: activeKey, + }, + }); +} + +export function getActiveKey(props, originalActiveKey) { + let activeKey = originalActiveKey; + const { children, eventKey } = props; + if (activeKey) { + let found; + loopMenuItem(children, (c, i) => { + if (c && !c.props.disabled && activeKey === getKeyFromChildrenIndex(c, eventKey, i)) { + found = true; + } + }); + if (found) { + return activeKey; + } + } + activeKey = null; + if (props.defaultActiveFirst) { + loopMenuItem(children, (c, i) => { + if (!activeKey && c && !c.props.disabled) { + activeKey = getKeyFromChildrenIndex(c, eventKey, i); + } + }); + return activeKey; + } + return activeKey; +} + +function saveRef(index, c) { + if (c) { + this.instanceArray[index] = c; + } +} const SubPopupMenu = createReactClass({ displayName: 'SubPopupMenu', @@ -19,9 +72,22 @@ const SubPopupMenu = createReactClass({ openKeys: PropTypes.arrayOf(PropTypes.string), visible: PropTypes.bool, children: PropTypes.any, - }, + parentMenu: PropTypes.object, - mixins: [MenuMixin], + // adding in refactor + focusable: PropTypes.bool, + multiple: PropTypes.bool, + style: PropTypes.object, + defaultActiveFirst: PropTypes.bool, + activeKey: PropTypes.string, + selectedKeys: PropTypes.arrayOf(PropTypes.string), + defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), + defaultOpenKeys: PropTypes.arrayOf(PropTypes.string), + openKeys: PropTypes.arrayOf(PropTypes.string), + level: PropTypes.number, + mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), + inlineIndent: PropTypes.number, + }, getInitialState() { const props = this.props; @@ -35,9 +101,90 @@ const SubPopupMenu = createReactClass({ return {}; }, + getDefaultProps() { + return { + prefixCls: 'rc-menu', + className: '', + mode: 'vertical', + level: 1, + inlineIndent: 24, + visible: true, + focusable: true, + style: {}, + }; + }, + + componentWillReceiveProps(nextProps) { + const originalActiveKey = 'activeKey' in nextProps ? nextProps.activeKey : + this.getStore().getState().activeKey[this.getEventKey()]; + const activeKey = getActiveKey(nextProps, originalActiveKey); + if (activeKey !== originalActiveKey) { + updateActiveKey(this.getStore(), this.getEventKey(), activeKey); + } + }, + componentDidMount() { // invoke customized ref to expose component to mixin - this.props.manualRef(this); + if (this.props.manualRef) { + this.props.manualRef(this); + } + }, + + shouldComponentUpdate(nextProps) { + return this.props.visible || nextProps.visible; + }, + + componentWillMount() { + this.instanceArray = []; + }, + + // all keyboard events callbacks run from here at first + // FIXME: callback is currently used by rc-select, should be more explicit + onKeyDown(e, callback) { + const keyCode = e.keyCode; + let handled; + this.getFlatInstanceArray().forEach((obj) => { + if (obj && obj.props.active && obj.onKeyDown) { + handled = obj.onKeyDown(e); + } + }); + if (handled) { + return 1; + } + let activeItem = null; + if (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN) { + activeItem = this.step(keyCode === KeyCode.UP ? -1 : 1); + } + if (activeItem) { + e.preventDefault(); + updateActiveKey(this.getStore(), this.getEventKey(), activeItem.props.eventKey); + + if (typeof callback === 'function') { + callback(activeItem); + } + + return 1; + } + }, + + onItemHover(e) { + const { key, hover } = e; + updateActiveKey(this.getStore(), this.getEventKey(), hover ? key : null); + }, + + getEventKey() { + // when eventKey not available ,it's menu and return menu id '0-menu-' + return this.props.eventKey || '0-menu-'; + }, + + getStore() { + const store = this.props.store; + + return store; + }, + + getFlatInstanceArray() { + return this.instanceArray; }, onDeselect(selectInfo) { @@ -65,60 +212,137 @@ const SubPopupMenu = createReactClass({ return this.props.openTransitionName; }, + renderCommonMenuItem(child, i, extraProps) { + const state = this.getStore().getState(); + const props = this.props; + const key = getKeyFromChildrenIndex(child, props.eventKey, i); + const childProps = child.props; + const isActive = key === state.activeKey; + const newChildProps = { + mode: props.mode, + level: props.level, + inlineIndent: props.inlineIndent, + renderMenuItem: this.renderMenuItem, + rootPrefixCls: props.prefixCls, + index: i, + parentMenu: props.parentMenu, + // customized ref function, need to be invoked manually in child's componentDidMount + manualRef: childProps.disabled ? undefined : + createChainedFunction(child.ref, saveRef.bind(this, i)), + eventKey: key, + active: !childProps.disabled && isActive, + multiple: props.multiple, + onClick: this.onClick, + onItemHover: this.onItemHover, + openTransitionName: this.getOpenTransitionName(), + openAnimation: props.openAnimation, + subMenuOpenDelay: props.subMenuOpenDelay, + subMenuCloseDelay: props.subMenuCloseDelay, + forceSubMenuRender: props.forceSubMenuRender, + onOpenChange: this.onOpenChange, + onDeselect: this.onDeselect, + onSelect: this.onSelect, + ...extraProps, + }; + if (props.mode === 'inline') { + newChildProps.triggerSubMenuAction = 'click'; + } + return React.cloneElement(child, newChildProps); + }, + renderMenuItem(c, i, subMenuKey) { - /* istanbul ignore next */ + /* istanbul ignore if */ if (!c) { return null; } - const props = this.props; + const state = this.getStore().getState(); const extraProps = { - openKeys: props.openKeys, - selectedKeys: props.selectedKeys, - triggerSubMenuAction: props.triggerSubMenuAction, + openKeys: state.openKeys, + selectedKeys: state.selectedKeys, + triggerSubMenuAction: this.props.triggerSubMenuAction, subMenuKey, }; return this.renderCommonMenuItem(c, i, extraProps); }, render() { - const props = { ...this.props }; - const haveRendered = this.haveRendered; - this.haveRendered = true; - - this.haveOpened = this.haveOpened || props.visible || props.forceSubMenuRender; - // never rendered not planning to, don't render - if (!this.haveOpened) { - return null; + const props = this.props; + this.instanceArray = []; + const className = classNames( + props.prefixCls, + props.className, + `${props.prefixCls}-${props.mode}`, + ); + const domProps = { + className, + role: 'menu', + 'aria-activedescendant': '', + }; + if (props.id) { + domProps.id = props.id; } - - // don't show transition on first rendering (no animation for opened menu) - // show appear transition if it's not visible (not sure why) - // show appear transition if it's not inline mode - const transitionAppear = haveRendered || !props.visible || !props.mode === 'inline'; - - props.className += ` ${props.prefixCls}-sub`; - const animProps = {}; - - if (props.openTransitionName) { - animProps.transitionName = props.openTransitionName; - } else if (typeof props.openAnimation === 'object') { - animProps.animation = { ...props.openAnimation }; - if (!transitionAppear) { - delete animProps.animation.appear; - } + if (props.focusable) { + domProps.tabIndex = '0'; + domProps.onKeyDown = this.onKeyDown; } - return ( - - {this.renderRoot(props)} - + {React.Children.map( + props.children, + (c, i) => this.renderMenuItem(c, i, props.eventKey || '0-menu-'), + )} + + /*eslint-enable */ ); }, + + step(direction) { + let children = this.getFlatInstanceArray(); + const activeKey = this.getStore().getState().activeKey[this.getEventKey()]; + const len = children.length; + if (!len) { + return null; + } + if (direction < 0) { + children = children.concat().reverse(); + } + // find current activeIndex + let activeIndex = -1; + children.every((c, ci) => { + if (c && c.props.eventKey === activeKey) { + activeIndex = ci; + return false; + } + return true; + }); + if (!this.props.defaultActiveFirst && activeIndex !== -1) { + if (allDisabled(children.slice(activeIndex, len - 1))) { + return undefined; + } + } + const start = (activeIndex + 1) % len; + let i = start; + for (; ;) { + const child = children[i]; + if (!child || child.props.disabled) { + i = (i + 1 + len) % len; + // complete a loop + if (i === start) { + return null; + } + } else { + return child; + } + } + }, }); export default connect()(SubPopupMenu); diff --git a/tests/__snapshots__/Menu.spec.js.snap b/tests/__snapshots__/Menu.spec.js.snap index d5de1dad..241f13d0 100644 --- a/tests/__snapshots__/Menu.spec.js.snap +++ b/tests/__snapshots__/Menu.spec.js.snap @@ -188,6 +188,7 @@ exports[`Menu render renders inline menu correctly 1`] = ` class="rc-menu-submenu-arrow" />
+
`; From 8529ac3cc2c76d549b14334ebb78be0e54c0b1d5 Mon Sep 17 00:00:00 2001 From: guifu Date: Thu, 19 Apr 2018 14:09:08 +0800 Subject: [PATCH 2/8] fix: provide onkeydown as a menu method --- src/Menu.jsx | 9 ++++++++- src/SubPopupMenu.js | 1 - 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Menu.jsx b/src/Menu.jsx index 746b678f..ccd05e59 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -117,6 +117,13 @@ const Menu = createReactClass({ this.props.onClick(e); }, + // onKeyDown needs to be exposed as a instance method + // e.g., in rc-select, we need to navigate menu item while + // current active item is rc-select input box rather than the menu itself + onKeyDown(e, callback) { + this.innerMenu.getWrappedInstance().onKeyDown(e, callback); + }, + onOpenChange(event) { const props = this.props; const openKeys = this.store.getState().openKeys.concat(); @@ -196,7 +203,7 @@ const Menu = createReactClass({ } return ( - {children} + this.innerMenu = c}>{children} ); }, diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index cd10a265..37305f5c 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -139,7 +139,6 @@ const SubPopupMenu = createReactClass({ }, // all keyboard events callbacks run from here at first - // FIXME: callback is currently used by rc-select, should be more explicit onKeyDown(e, callback) { const keyCode = e.keyCode; let handled; From 091f70855c4a7303424eac0ea9676d7966b3034a Mon Sep 17 00:00:00 2001 From: guifu Date: Thu, 19 Apr 2018 14:50:59 +0800 Subject: [PATCH 3/8] chore: migrate to es6 --- src/DOMWrap.jsx | 25 ++-- src/Divider.jsx | 21 ++-- src/Menu.jsx | 90 +++++++-------- src/MenuItem.jsx | 70 ++++++------ src/MenuItemGroup.jsx | 28 ++--- src/SubMenu.jsx | 159 +++++++++++++------------- src/SubPopupMenu.js | 108 +++++++++-------- src/{placements.jsx => placements.js} | 0 tests/SubMenu.spec.js | 13 ++- 9 files changed, 246 insertions(+), 268 deletions(-) rename src/{placements.jsx => placements.js} (100%) diff --git a/src/DOMWrap.jsx b/src/DOMWrap.jsx index e4efc189..6027cf45 100644 --- a/src/DOMWrap.jsx +++ b/src/DOMWrap.jsx @@ -1,22 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -const DOMWrap = createReactClass({ - displayName: 'DOMWrap', - - propTypes: { +export default class DOMWrap extends React.Component { + static propTypes = { tag: PropTypes.string, hiddenClassName: PropTypes.string, visible: PropTypes.bool, - }, + }; - getDefaultProps() { - return { - tag: 'div', - className: '', - }; - }, + static defaultProps = { + tag: 'div', + className: '', + }; render() { const props = { ...this.props }; @@ -28,7 +23,5 @@ const DOMWrap = createReactClass({ delete props.hiddenClassName; delete props.visible; return ; - }, -}); - -export default DOMWrap; + } +} diff --git a/src/Divider.jsx b/src/Divider.jsx index 4dd63357..9360996f 100644 --- a/src/Divider.jsx +++ b/src/Divider.jsx @@ -1,24 +1,19 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -const Divider = createReactClass({ - displayName: 'Divider', - - propTypes: { +export default class Divider extends React.Component { + static propTypes = { className: PropTypes.string, rootPrefixCls: PropTypes.string, - }, + }; - getDefaultProps() { + static defaultProps = { // To fix keyboard UX. - return { disabled: true }; - }, + disabled: true, + }; render() { const { className = '', rootPrefixCls } = this.props; return
  • ; - }, -}); - -export default Divider; + } +} diff --git a/src/Menu.jsx b/src/Menu.jsx index ccd05e59..3cadcc10 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -1,14 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { Provider, create } from 'mini-store'; import { default as SubPopupMenu, getActiveKey } from './SubPopupMenu'; import { noop } from './util'; -const Menu = createReactClass({ - displayName: 'Menu', - - propTypes: { +export default class Menu extends React.Component { + static propTypes = { defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), defaultActiveFirst: PropTypes.bool, selectedKeys: PropTypes.arrayOf(PropTypes.string), @@ -34,31 +31,30 @@ const Menu = createReactClass({ style: PropTypes.object, activeKey: PropTypes.string, prefixCls: PropTypes.string, - }, - - isRootMenu: true, - - getDefaultProps() { - return { - selectable: true, - onClick: noop, - onSelect: noop, - onOpenChange: noop, - onDeselect: noop, - defaultSelectedKeys: [], - defaultOpenKeys: [], - subMenuOpenDelay: 0.1, - subMenuCloseDelay: 0.1, - triggerSubMenuAction: 'hover', - prefixCls: 'rc-menu', - className: '', - mode: 'vertical', - style: {}, - }; - }, + }; + + static defaultProps = { + selectable: true, + onClick: noop, + onSelect: noop, + onOpenChange: noop, + onDeselect: noop, + defaultSelectedKeys: [], + defaultOpenKeys: [], + subMenuOpenDelay: 0.1, + subMenuCloseDelay: 0.1, + triggerSubMenuAction: 'hover', + prefixCls: 'rc-menu', + className: '', + mode: 'vertical', + style: {}, + }; + + constructor(props) { + super(props); + + this.isRootMenu = true; - getInitialState() { - const props = this.props; let selectedKeys = props.defaultSelectedKeys; let openKeys = props.defaultOpenKeys; if ('selectedKeys' in props) { @@ -73,9 +69,7 @@ const Menu = createReactClass({ openKeys, activeKey: { '0-menu-': getActiveKey(props, props.activeKey) }, }); - - return {}; - }, + } componentWillReceiveProps(nextProps) { if ('selectedKeys' in nextProps) { @@ -88,9 +82,9 @@ const Menu = createReactClass({ openKeys: nextProps.openKeys || [], }); } - }, + } - onSelect(selectInfo) { + onSelect = (selectInfo) => { const props = this.props; if (props.selectable) { // root menu @@ -111,20 +105,20 @@ const Menu = createReactClass({ selectedKeys, }); } - }, + } - onClick(e) { + onClick = (e) => { this.props.onClick(e); - }, + } // onKeyDown needs to be exposed as a instance method // e.g., in rc-select, we need to navigate menu item while // current active item is rc-select input box rather than the menu itself - onKeyDown(e, callback) { + onKeyDown = (e, callback) => { this.innerMenu.getWrappedInstance().onKeyDown(e, callback); - }, + } - onOpenChange(event) { + onOpenChange = (event) => { const props = this.props; const openKeys = this.store.getState().openKeys.concat(); let changed = false; @@ -156,9 +150,9 @@ const Menu = createReactClass({ } props.onOpenChange(openKeys); } - }, + } - onDeselect(selectInfo) { + onDeselect = (selectInfo) => { const props = this.props; if (props.selectable) { const selectedKeys = this.store.getState().selectedKeys.concat(); @@ -177,9 +171,9 @@ const Menu = createReactClass({ selectedKeys, }); } - }, + } - getOpenTransitionName() { + getOpenTransitionName = () => { const props = this.props; let transitionName = props.openTransitionName; const animationName = props.openAnimation; @@ -187,7 +181,7 @@ const Menu = createReactClass({ transitionName = `${props.prefixCls}-open-${animationName}`; } return transitionName; - }, + } render() { let { children, ...props } = this.props; @@ -206,7 +200,5 @@ const Menu = createReactClass({ this.innerMenu = c}>{children} ); - }, -}); - -export default Menu; + } +}; diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index 48f06b61..c3a9a3a4 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -1,7 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import KeyCode from 'rc-util/lib/KeyCode'; import classNames from 'classnames'; import scrollIntoView from 'dom-scroll-into-view'; @@ -10,10 +9,8 @@ import { noop } from './util'; /* eslint react/no-is-mounted:0 */ -export const MenuItem = createReactClass({ - displayName: 'MenuItem', - - propTypes: { +export class MenuItem extends React.Component { + static propTypes = { rootPrefixCls: PropTypes.string, eventKey: PropTypes.string, active: PropTypes.bool, @@ -29,27 +26,29 @@ export const MenuItem = createReactClass({ onDestroy: PropTypes.func, onMouseEnter: PropTypes.func, onMouseLeave: PropTypes.func, - }, + }; - getDefaultProps() { - return { - onSelect: noop, - onMouseEnter: noop, - onMouseLeave: noop, - }; - }, + static defaultProps = { + onSelect: noop, + onMouseEnter: noop, + onMouseLeave: noop, + }; + + constructor(props) { + super(props); + } componentWillUnmount() { const props = this.props; if (props.onDestroy) { props.onDestroy(props.eventKey); } - }, + } componentDidMount() { // invoke customized ref to expose component to mixin this.callRef(); - }, + } componentDidUpdate() { if (this.props.active) { @@ -59,18 +58,17 @@ export const MenuItem = createReactClass({ } this.callRef(); - }, - + } - onKeyDown(e) { + onKeyDown = (e) => { const keyCode = e.keyCode; if (keyCode === KeyCode.ENTER) { this.onClick(e); return true; } - }, + }; - onMouseLeave(e) { + onMouseLeave = (e) => { const { eventKey, onItemHover, onMouseLeave } = this.props; onItemHover({ key: eventKey, @@ -80,9 +78,9 @@ export const MenuItem = createReactClass({ key: eventKey, domEvent: e, }); - }, + }; - onMouseEnter(e) { + onMouseEnter = (e) => { const { eventKey, onItemHover, onMouseEnter } = this.props; onItemHover({ key: eventKey, @@ -92,9 +90,9 @@ export const MenuItem = createReactClass({ key: eventKey, domEvent: e, }); - }, + }; - onClick(e) { + onClick = (e) => { const { eventKey, multiple, onClick, onSelect, onDeselect, isSelected } = this.props; const info = { key: eventKey, @@ -112,29 +110,29 @@ export const MenuItem = createReactClass({ } else if (!isSelected) { onSelect(info); } - }, + }; getPrefixCls() { return `${this.props.rootPrefixCls}-item`; - }, + } getActiveClassName() { return `${this.getPrefixCls()}-active`; - }, + } getSelectedClassName() { return `${this.getPrefixCls()}-selected`; - }, + } getDisabledClassName() { return `${this.getPrefixCls()}-disabled`; - }, + } callRef() { if (this.props.manualRef) { this.props.manualRef(this); } - }, + } render() { const props = this.props; @@ -174,12 +172,14 @@ export const MenuItem = createReactClass({ {props.children}
  • ); - }, -}); + } +} -MenuItem.isMenuItem = 1; - -export default connect(({ activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ +const connected = connect(({ activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ active: activeKey[subMenuKey] === eventKey, isSelected: selectedKeys.indexOf(eventKey) !== -1, }))(MenuItem); + +connected.isMenuItem = true; + +export default connected; diff --git a/src/MenuItemGroup.jsx b/src/MenuItemGroup.jsx index bb52fe2a..479468b8 100644 --- a/src/MenuItemGroup.jsx +++ b/src/MenuItemGroup.jsx @@ -1,26 +1,26 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -const MenuItemGroup = createReactClass({ - displayName: 'MenuItemGroup', - - propTypes: { +class MenuItemGroup extends React.Component { + static propTypes = { renderMenuItem: PropTypes.func, index: PropTypes.number, className: PropTypes.string, rootPrefixCls: PropTypes.string, - }, + }; + + static defaultProps = { + disabled: true, + }; - getDefaultProps() { - // To fix keyboard UX. - return { disabled: true }; - }, + constructor(props) { + super(props); + } - renderInnerMenuItem(item) { + renderInnerMenuItem = (item) => { const { renderMenuItem, index } = this.props; return renderMenuItem(item, index, this.props.subMenuKey); - }, + } render() { const props = this.props; @@ -40,8 +40,8 @@ const MenuItemGroup = createReactClass({ ); - }, -}); + } +} MenuItemGroup.isMenuItemGroup = true; diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index 7f3d6763..3d398c00 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -1,7 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import Trigger from 'rc-trigger'; import KeyCode from 'rc-util/lib/KeyCode'; import classNames from 'classnames'; @@ -35,10 +34,8 @@ const updateDefaultActiveFirst = (store, eventKey, defaultActiveFirst) => { }); }; -const SubMenu = createReactClass({ - displayName: 'SubMenu', - - propTypes: { +export class SubMenu extends React.Component { + static propTypes = { parentMenu: PropTypes.object, title: PropTypes.node, children: PropTypes.any, @@ -61,27 +58,25 @@ const SubMenu = createReactClass({ onTitleMouseLeave: PropTypes.func, onTitleClick: PropTypes.func, isOpen: PropTypes.bool, - }, - - isRootMenu: false, - - getDefaultProps() { - return { - onMouseEnter: noop, - onMouseLeave: noop, - onTitleMouseEnter: noop, - onTitleMouseLeave: noop, - onTitleClick: noop, - title: '', - }; - }, - - getInitialState() { - this.isSubMenu = 1; - const props = this.props; + }; + + static defaultProps = { + onMouseEnter: noop, + onMouseLeave: noop, + onTitleMouseEnter: noop, + onTitleMouseLeave: noop, + onTitleClick: noop, + title: '', + }; + + constructor(props) { + super(props); const store = props.store; const eventKey = props.eventKey; const defaultActiveFirst = store.getState().defaultActiveFirst; + + this.isRootMenu = false; + let value = false; if (defaultActiveFirst) { @@ -89,15 +84,13 @@ const SubMenu = createReactClass({ } updateDefaultActiveFirst(store, eventKey, value); - - return {}; - }, + } componentDidMount() { this.componentDidUpdate(); - }, + } - adjustWidth() { + adjustWidth = () => { /* istanbul ignore if */ if (!this.subMenuTitle || !this.menuInstance) { return; @@ -109,7 +102,7 @@ const SubMenu = createReactClass({ /* istanbul ignore next */ popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px`; - }, + }; componentDidUpdate() { const { mode, parentMenu, manualRef } = this.props; @@ -124,7 +117,7 @@ const SubMenu = createReactClass({ } this.minWidthTimeout = setTimeout(() => this.adjustWidth(), 0); - }, + } componentWillUnmount() { const { onDestroy, eventKey } = this.props; @@ -141,13 +134,13 @@ const SubMenu = createReactClass({ if (this.mouseenterTimeout) { clearTimeout(this.mouseenterTimeout); } - }, + }; onDestroy(key) { this.props.onDestroy(key); - }, + }; - onKeyDown(e) { + onKeyDown = (e) => { const keyCode = e.keyCode; const menu = this.menuInstance; const { @@ -188,26 +181,26 @@ const SubMenu = createReactClass({ if (isOpen && (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN)) { return menu.onKeyDown(e); } - }, + }; - onOpenChange(e) { + onOpenChange = (e) => { this.props.onOpenChange(e); - }, + }; - onPopupVisibleChange(visible) { + onPopupVisibleChange = (visible) => { this.triggerOpenChange(visible, visible ? 'mouseenter' : 'mouseleave'); - }, + }; - onMouseEnter(e) { + onMouseEnter = (e) => { const { eventKey: key, onMouseEnter, store } = this.props; updateDefaultActiveFirst(store, this.props.eventKey, false); onMouseEnter({ key, domEvent: e, }); - }, + }; - onMouseLeave(e) { + onMouseLeave = (e) => { const { parentMenu, eventKey, @@ -218,9 +211,9 @@ const SubMenu = createReactClass({ key: eventKey, domEvent: e, }); - }, + }; - onTitleMouseEnter(domEvent) { + onTitleMouseEnter = (domEvent) => { const { eventKey: key, onItemHover, onTitleMouseEnter } = this.props; onItemHover({ key, @@ -230,9 +223,9 @@ const SubMenu = createReactClass({ key, domEvent, }); - }, + }; - onTitleMouseLeave(e) { + onTitleMouseLeave = (e) => { const { parentMenu, eventKey, onItemHover, onTitleMouseLeave } = this.props; parentMenu.subMenuInstance = this; onItemHover({ @@ -243,9 +236,9 @@ const SubMenu = createReactClass({ key: eventKey, domEvent: e, }); - }, + }; - onTitleClick(e) { + onTitleClick = (e) => { const { props } = this; props.onTitleClick({ key: props.eventKey, @@ -256,53 +249,53 @@ const SubMenu = createReactClass({ } this.triggerOpenChange(!props.isOpen, 'click'); updateDefaultActiveFirst(props.store, this.props.eventKey, false); - }, + }; - onSubMenuClick(info) { + onSubMenuClick = (info) => { this.props.onClick(this.addKeyPath(info)); - }, + }; - onSelect(info) { + onSelect = (info) => { this.props.onSelect(info); - }, + }; - onDeselect(info) { + onDeselect = (info) => { this.props.onDeselect(info); - }, + }; - getPrefixCls() { + getPrefixCls = () => { return `${this.props.rootPrefixCls}-submenu`; - }, + }; - getActiveClassName() { + getActiveClassName = () => { return `${this.getPrefixCls()}-active`; - }, + }; - getDisabledClassName() { + getDisabledClassName = () => { return `${this.getPrefixCls()}-disabled`; - }, + }; - getSelectedClassName() { + getSelectedClassName = () => { return `${this.getPrefixCls()}-selected`; - }, + }; - getOpenClassName() { + getOpenClassName = () => { return `${this.props.rootPrefixCls}-submenu-open`; - }, + }; - saveMenuInstance(c) { + saveMenuInstance = (c) => { // children menu instance this.menuInstance = c; - }, + }; - addKeyPath(info) { + addKeyPath = (info) => { return { ...info, keyPath: (info.keyPath || []).concat(this.props.eventKey), }; - }, + }; - triggerOpenChange(open, type) { + triggerOpenChange = (open, type) => { const key = this.props.eventKey; const openChange = () => { this.onOpenChange({ @@ -320,17 +313,17 @@ const SubMenu = createReactClass({ } else { openChange(); } - }, + } - isChildrenSelected() { + isChildrenSelected = () => { const ret = { find: false }; loopMenuItemRecursively(this.props.children, this.props.selectedKeys, ret); return ret.find; - }, + } - isOpen() { + isOpen = () => { return this.props.openKeys.indexOf(this.props.eventKey) !== -1; - }, + } renderChildren(children) { const props = this.props; @@ -399,11 +392,11 @@ const SubMenu = createReactClass({ {children} ); - }, + } - saveSubMenuTitle(subMenuTitle) { + saveSubMenuTitle = (subMenuTitle) => { this.subMenuTitle = subMenuTitle; - }, + } render() { const props = this.props; @@ -496,13 +489,15 @@ const SubMenu = createReactClass({ )} ); - }, -}); - -SubMenu.isSubMenu = 1; + } +}; -export default connect(({ openKeys, activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ +const connected = connect(({ openKeys, activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ isOpen: openKeys.indexOf(eventKey) > -1, active: activeKey[subMenuKey] === eventKey, selectedKeys, }))(SubMenu); + +connected.isSubMenu = true; + +export default connected; diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index 37305f5c..4c36ecd8 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -1,6 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import Animate from 'rc-animate'; import { connect } from 'mini-store'; import KeyCode from 'rc-util/lib/KeyCode'; @@ -58,10 +57,8 @@ function saveRef(index, c) { } } -const SubPopupMenu = createReactClass({ - displayName: 'SubPopupMenu', - - propTypes: { +export class SubPopupMenu extends React.Component { + static propTypes = { onSelect: PropTypes.func, onClick: PropTypes.func, onDeselect: PropTypes.func, @@ -87,32 +84,29 @@ const SubPopupMenu = createReactClass({ level: PropTypes.number, mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), inlineIndent: PropTypes.number, - }, + }; + + static defaultProps = { + prefixCls: 'rc-menu', + className: '', + mode: 'vertical', + level: 1, + inlineIndent: 24, + visible: true, + focusable: true, + style: {}, + }; + + constructor(props) { + super(props); - getInitialState() { - const props = this.props; props.store.setState({ activeKey: { ...props.store.getState().activeKey, [props.eventKey]: getActiveKey(props, props.activeKey), }, }); - - return {}; - }, - - getDefaultProps() { - return { - prefixCls: 'rc-menu', - className: '', - mode: 'vertical', - level: 1, - inlineIndent: 24, - visible: true, - focusable: true, - style: {}, - }; - }, + } componentWillReceiveProps(nextProps) { const originalActiveKey = 'activeKey' in nextProps ? nextProps.activeKey : @@ -121,25 +115,25 @@ const SubPopupMenu = createReactClass({ if (activeKey !== originalActiveKey) { updateActiveKey(this.getStore(), this.getEventKey(), activeKey); } - }, + } componentDidMount() { // invoke customized ref to expose component to mixin if (this.props.manualRef) { this.props.manualRef(this); } - }, + } shouldComponentUpdate(nextProps) { return this.props.visible || nextProps.visible; - }, + } componentWillMount() { this.instanceArray = []; - }, + } // all keyboard events callbacks run from here at first - onKeyDown(e, callback) { + onKeyDown = (e, callback) => { const keyCode = e.keyCode; let handled; this.getFlatInstanceArray().forEach((obj) => { @@ -164,54 +158,54 @@ const SubPopupMenu = createReactClass({ return 1; } - }, + }; - onItemHover(e) { + onItemHover = (e) => { const { key, hover } = e; updateActiveKey(this.getStore(), this.getEventKey(), hover ? key : null); - }, + }; - getEventKey() { + getEventKey = () => { // when eventKey not available ,it's menu and return menu id '0-menu-' return this.props.eventKey || '0-menu-'; - }, + }; - getStore() { + getStore = () => { const store = this.props.store; return store; - }, + }; - getFlatInstanceArray() { + getFlatInstanceArray = () => { return this.instanceArray; - }, + }; - onDeselect(selectInfo) { + onDeselect = (selectInfo) => { this.props.onDeselect(selectInfo); - }, + }; - onSelect(selectInfo) { + onSelect = (selectInfo) => { this.props.onSelect(selectInfo); - }, + } - onClick(e) { + onClick = (e) => { this.props.onClick(e); - }, + }; - onOpenChange(e) { + onOpenChange = (e) => { this.props.onOpenChange(e); - }, + }; - onDestroy(key) { + onDestroy = (key) => { /* istanbul ignore next */ this.props.onDestroy(key); - }, + }; - getOpenTransitionName() { + getOpenTransitionName = () => { return this.props.openTransitionName; - }, + }; - renderCommonMenuItem(child, i, extraProps) { + renderCommonMenuItem = (child, i, extraProps) => { const state = this.getStore().getState(); const props = this.props; const key = getKeyFromChildrenIndex(child, props.eventKey, i); @@ -247,9 +241,9 @@ const SubPopupMenu = createReactClass({ newChildProps.triggerSubMenuAction = 'click'; } return React.cloneElement(child, newChildProps); - }, + }; - renderMenuItem(c, i, subMenuKey) { + renderMenuItem = (c, i, subMenuKey) => { /* istanbul ignore if */ if (!c) { return null; @@ -262,7 +256,7 @@ const SubPopupMenu = createReactClass({ subMenuKey, }; return this.renderCommonMenuItem(c, i, extraProps); - }, + }; render() { const props = this.props; @@ -301,7 +295,7 @@ const SubPopupMenu = createReactClass({ /*eslint-enable */ ); - }, + } step(direction) { let children = this.getFlatInstanceArray(); @@ -341,7 +335,7 @@ const SubPopupMenu = createReactClass({ return child; } } - }, -}); + } +} export default connect()(SubPopupMenu); diff --git a/src/placements.jsx b/src/placements.js similarity index 100% rename from src/placements.jsx rename to src/placements.js diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index 8540b4dc..13a14907 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -256,10 +256,19 @@ describe('SubMenu', () => { describe('horizontal menu', () => { it('should automatically adjust width', () => { - const wrapper = mount(createMenu({ + const props = { mode: 'horizontal', openKeys: ['s1'], - })); + }; + + const wrapper = mount( + + 1 + + 2 + + + ); const subMenuInstance = wrapper.find('SubMenu').first().instance(); const adjustWidthSpy = jest.spyOn(subMenuInstance, 'adjustWidth'); From 9b905cf025df9d909fceaccc9ad5a8969b558390 Mon Sep 17 00:00:00 2001 From: guifu Date: Thu, 19 Apr 2018 16:24:04 +0800 Subject: [PATCH 4/8] update mini-store to 1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 18a15bcd..16ae8de3 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "classnames": "2.x", "create-react-class": "^15.5.2", "dom-scroll-into-view": "1.x", - "mini-store": "^1.0.2", + "mini-store": "^1.1.0", "prop-types": "^15.5.6", "rc-animate": "2.x", "rc-trigger": "^2.3.0", From 6a4fd1889d40744c8c2cfd77915c0a1f1a6dbb3b Mon Sep 17 00:00:00 2001 From: guifu Date: Thu, 19 Apr 2018 15:46:22 +0800 Subject: [PATCH 5/8] chore: migrate examples to es6 --- examples/keyPath.js | 11 +++++------ examples/openKeys.js | 25 +++++++++++------------- examples/selectedKeys.js | 41 +++++++++++++++++++--------------------- 3 files changed, 35 insertions(+), 42 deletions(-) diff --git a/examples/keyPath.js b/examples/keyPath.js index fa76a1bc..7779e24c 100644 --- a/examples/keyPath.js +++ b/examples/keyPath.js @@ -3,14 +3,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; -import createReactClass from 'create-react-class'; import 'rc-menu/assets/index.less'; -const Test = createReactClass({ +class Test extends React.Component { onClick(info) { console.log('click ', info); - }, + } getMenu() { return ( @@ -33,14 +32,14 @@ const Test = createReactClass({ item3 ); - }, + } render() { return (
    {this.getMenu()}
    ); - }, -}); + } +} ReactDOM.render(, document.getElementById('__react-content')); diff --git a/examples/openKeys.js b/examples/openKeys.js index 13115bd4..6a27f912 100644 --- a/examples/openKeys.js +++ b/examples/openKeys.js @@ -3,27 +3,25 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; -import createReactClass from 'create-react-class'; import 'rc-menu/assets/index.less'; -const Test = createReactClass({ - getInitialState() { - return { - openKeys: [], - }; - }, +class Test extends React.Component { + state = { + openKeys: [], + }; onClick(info) { console.log('click ', info); - }, + } - onOpenChange(openKeys) { + onOpenChange = (openKeys) => { console.log('onOpenChange', openKeys); this.setState({ openKeys, }); - }, + } + getMenu() { return ( item3 ); - }, + } render() { return (
    {this.getMenu()}
    ); - }, -}); - + } +} ReactDOM.render(, document.getElementById('__react-content')); diff --git a/examples/selectedKeys.js b/examples/selectedKeys.js index ba09a076..e5a257eb 100644 --- a/examples/selectedKeys.js +++ b/examples/selectedKeys.js @@ -3,38 +3,35 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Menu, { SubMenu, Item as MenuItem } from 'rc-menu'; -import createReactClass from 'create-react-class'; import 'rc-menu/assets/index.less'; -const Test = createReactClass({ - getInitialState() { - return { - destroyed: false, - selectedKeys: [], - openKeys: [], - }; - }, +class Test extends React.Component { + state = { + destroyed: false, + selectedKeys: [], + openKeys: [], + }; - onSelect(info) { + onSelect = (info) => { console.log('selected ', info); this.setState({ selectedKeys: info.selectedKeys, }); - }, + }; onDeselect(info) { console.log('deselect ', info); - }, + } - onOpenChange(openKeys) { + onOpenChange = (openKeys) => { console.log('onOpenChange ', openKeys); this.setState({ openKeys, }); - }, + }; - onCheck(e) { + onCheck = (e) => { const value = e.target.value; if (e.target.checked) { this.setState({ @@ -50,9 +47,9 @@ const Test = createReactClass({ selectedKeys, }); } - }, + }; - onOpenCheck(e) { + onOpenCheck = (e) => { const value = e.target.value; if (e.target.checked) { this.setState({ @@ -68,7 +65,7 @@ const Test = createReactClass({ openKeys, }); } - }, + }; getMenu() { return ( @@ -91,13 +88,13 @@ const Test = createReactClass({ item3 ); - }, + } destroy() { this.setState({ destroyed: true, }); - }, + } render() { if (this.state.destroyed) { @@ -141,8 +138,8 @@ const Test = createReactClass({
    {this.getMenu()}
    ); - }, -}); + } +}; ReactDOM.render(, document.getElementById('__react-content')); From f64f09fd6242444b09def60281dd820eb927ecb4 Mon Sep 17 00:00:00 2001 From: guifu Date: Thu, 19 Apr 2018 15:47:43 +0800 Subject: [PATCH 6/8] address cr --- src/SubPopupMenu.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index 4c36ecd8..d2f43c17 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -171,9 +171,7 @@ export class SubPopupMenu extends React.Component { }; getStore = () => { - const store = this.props.store; - - return store; + return this.props.store; }; getFlatInstanceArray = () => { @@ -316,25 +314,22 @@ export class SubPopupMenu extends React.Component { } return true; }); - if (!this.props.defaultActiveFirst && activeIndex !== -1) { - if (allDisabled(children.slice(activeIndex, len - 1))) { - return undefined; - } + if (!this.props.defaultActiveFirst && activeIndex !== -1 && allDisabled(children.slice(activeIndex, len - 1))) { + return undefined; } const start = (activeIndex + 1) % len; let i = start; - for (; ;) { + + do { const child = children[i]; if (!child || child.props.disabled) { - i = (i + 1 + len) % len; - // complete a loop - if (i === start) { - return null; - } + i = (i + 1) % len; } else { return child; } - } + } while (i !== start); + + return null; } } From e1d27b9db37d0f942404789a592f844789c37c08 Mon Sep 17 00:00:00 2001 From: guifu Date: Thu, 19 Apr 2018 16:20:34 +0800 Subject: [PATCH 7/8] chore: fix lint --- examples/selectedKeys.js | 3 +- package.json | 1 - src/Menu.jsx | 8 +-- src/MenuItem.jsx | 18 +++-- src/MenuItemGroup.jsx | 1 + src/SubMenu.jsx | 47 +++++++------ src/SubPopupMenu.js | 138 +++++++++++++++++++++------------------ 7 files changed, 117 insertions(+), 99 deletions(-) diff --git a/examples/selectedKeys.js b/examples/selectedKeys.js index e5a257eb..ae1c1b4c 100644 --- a/examples/selectedKeys.js +++ b/examples/selectedKeys.js @@ -139,7 +139,6 @@ class Test extends React.Component {
    {this.getMenu()}
    ); } -}; - +} ReactDOM.render(, document.getElementById('__react-content')); diff --git a/package.json b/package.json index 16ae8de3..cc4eaf2c 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "dependencies": { "babel-runtime": "6.x", "classnames": "2.x", - "create-react-class": "^15.5.2", "dom-scroll-into-view": "1.x", "mini-store": "^1.1.0", "prop-types": "^15.5.6", diff --git a/src/Menu.jsx b/src/Menu.jsx index 3cadcc10..eb15dc03 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -184,7 +184,7 @@ export default class Menu extends React.Component { } render() { - let { children, ...props } = this.props; + let { ...props } = this.props; props.className += ` ${props.prefixCls}-root`; props = { ...props, @@ -194,11 +194,11 @@ export default class Menu extends React.Component { onSelect: this.onSelect, openTransitionName: this.getOpenTransitionName(), parentMenu: this, - } + }; return ( - this.innerMenu = c}>{children} + this.innerMenu = c}>{this.props.children} ); } -}; +} diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index c3a9a3a4..3920b7b5 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -26,23 +26,20 @@ export class MenuItem extends React.Component { onDestroy: PropTypes.func, onMouseEnter: PropTypes.func, onMouseLeave: PropTypes.func, + multiple: PropTypes.bool, + isSelected: PropTypes.bool, + manualRef: PropTypes.func, }; static defaultProps = { onSelect: noop, onMouseEnter: noop, onMouseLeave: noop, + manualRef: noop, }; constructor(props) { super(props); - } - - componentWillUnmount() { - const props = this.props; - if (props.onDestroy) { - props.onDestroy(props.eventKey); - } } componentDidMount() { @@ -60,6 +57,13 @@ export class MenuItem extends React.Component { this.callRef(); } + componentWillUnmount() { + const props = this.props; + if (props.onDestroy) { + props.onDestroy(props.eventKey); + } + } + onKeyDown = (e) => { const keyCode = e.keyCode; if (keyCode === KeyCode.ENTER) { diff --git a/src/MenuItemGroup.jsx b/src/MenuItemGroup.jsx index 479468b8..1a1e4184 100644 --- a/src/MenuItemGroup.jsx +++ b/src/MenuItemGroup.jsx @@ -6,6 +6,7 @@ class MenuItemGroup extends React.Component { renderMenuItem: PropTypes.func, index: PropTypes.number, className: PropTypes.string, + subMenuKey: PropTypes.string, rootPrefixCls: PropTypes.string, }; diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index 3d398c00..9c9c6bae 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -58,6 +58,9 @@ export class SubMenu extends React.Component { onTitleMouseLeave: PropTypes.func, onTitleClick: PropTypes.func, isOpen: PropTypes.bool, + store: PropTypes.object, + mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), + manualRef: PropTypes.func, }; static defaultProps = { @@ -66,6 +69,8 @@ export class SubMenu extends React.Component { onTitleMouseEnter: noop, onTitleMouseLeave: noop, onTitleClick: noop, + manualRef: noop, + mode: 'vertical', title: '', }; @@ -90,20 +95,6 @@ export class SubMenu extends React.Component { this.componentDidUpdate(); } - adjustWidth = () => { - /* istanbul ignore if */ - if (!this.subMenuTitle || !this.menuInstance) { - return; - } - const popupMenu = ReactDOM.findDOMNode(this.menuInstance); - if (popupMenu.offsetWidth >= this.subMenuTitle.offsetWidth) { - return; - } - - /* istanbul ignore next */ - popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px`; - }; - componentDidUpdate() { const { mode, parentMenu, manualRef } = this.props; @@ -134,9 +125,9 @@ export class SubMenu extends React.Component { if (this.mouseenterTimeout) { clearTimeout(this.mouseenterTimeout); } - }; + } - onDestroy(key) { + onDestroy = (key) => { this.props.onDestroy(key); }; @@ -325,6 +316,24 @@ export class SubMenu extends React.Component { return this.props.openKeys.indexOf(this.props.eventKey) !== -1; } + adjustWidth = () => { + /* istanbul ignore if */ + if (!this.subMenuTitle || !this.menuInstance) { + return; + } + const popupMenu = ReactDOM.findDOMNode(this.menuInstance); + if (popupMenu.offsetWidth >= this.subMenuTitle.offsetWidth) { + return; + } + + /* istanbul ignore next */ + popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px`; + }; + + saveSubMenuTitle = (subMenuTitle) => { + this.subMenuTitle = subMenuTitle; + } + renderChildren(children) { const props = this.props; const baseProps = { @@ -394,10 +403,6 @@ export class SubMenu extends React.Component { ); } - saveSubMenuTitle = (subMenuTitle) => { - this.subMenuTitle = subMenuTitle; - } - render() { const props = this.props; const isOpen = props.isOpen; @@ -490,7 +495,7 @@ export class SubMenu extends React.Component { ); } -}; +} const connected = connect(({ openKeys, activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ isOpen: openKeys.indexOf(eventKey) > -1, diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index d2f43c17..dfdb783a 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -1,11 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Animate from 'rc-animate'; import { connect } from 'mini-store'; import KeyCode from 'rc-util/lib/KeyCode'; import createChainedFunction from 'rc-util/lib/createChainedFunction'; import classNames from 'classnames'; -import { getKeyFromChildrenIndex, loopMenuItem } from './util'; +import { getKeyFromChildrenIndex, loopMenuItem, noop } from './util'; import DOMWrap from './DOMWrap'; function allDisabled(arr) { @@ -70,6 +69,11 @@ export class SubPopupMenu extends React.Component { visible: PropTypes.bool, children: PropTypes.any, parentMenu: PropTypes.object, + eventKey: PropTypes.string, + store: PropTypes.shape({ + getState: PropTypes.func, + setState: PropTypes.func, + }), // adding in refactor focusable: PropTypes.bool, @@ -80,10 +84,11 @@ export class SubPopupMenu extends React.Component { selectedKeys: PropTypes.arrayOf(PropTypes.string), defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), defaultOpenKeys: PropTypes.arrayOf(PropTypes.string), - openKeys: PropTypes.arrayOf(PropTypes.string), level: PropTypes.number, mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), + triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']), inlineIndent: PropTypes.number, + manualRef: PropTypes.func, }; static defaultProps = { @@ -95,6 +100,7 @@ export class SubPopupMenu extends React.Component { visible: true, focusable: true, style: {}, + manualRef: noop, }; constructor(props) { @@ -108,13 +114,8 @@ export class SubPopupMenu extends React.Component { }); } - componentWillReceiveProps(nextProps) { - const originalActiveKey = 'activeKey' in nextProps ? nextProps.activeKey : - this.getStore().getState().activeKey[this.getEventKey()]; - const activeKey = getActiveKey(nextProps, originalActiveKey); - if (activeKey !== originalActiveKey) { - updateActiveKey(this.getStore(), this.getEventKey(), activeKey); - } + componentWillMount() { + this.instanceArray = []; } componentDidMount() { @@ -124,12 +125,17 @@ export class SubPopupMenu extends React.Component { } } - shouldComponentUpdate(nextProps) { - return this.props.visible || nextProps.visible; + componentWillReceiveProps(nextProps) { + const originalActiveKey = 'activeKey' in nextProps ? nextProps.activeKey : + this.getStore().getState().activeKey[this.getEventKey()]; + const activeKey = getActiveKey(nextProps, originalActiveKey); + if (activeKey !== originalActiveKey) { + updateActiveKey(this.getStore(), this.getEventKey(), activeKey); + } } - componentWillMount() { - this.instanceArray = []; + shouldComponentUpdate(nextProps) { + return this.props.visible || nextProps.visible; } // all keyboard events callbacks run from here at first @@ -165,19 +171,6 @@ export class SubPopupMenu extends React.Component { updateActiveKey(this.getStore(), this.getEventKey(), hover ? key : null); }; - getEventKey = () => { - // when eventKey not available ,it's menu and return menu id '0-menu-' - return this.props.eventKey || '0-menu-'; - }; - - getStore = () => { - return this.props.store; - }; - - getFlatInstanceArray = () => { - return this.instanceArray; - }; - onDeselect = (selectInfo) => { this.props.onDeselect(selectInfo); }; @@ -199,10 +192,64 @@ export class SubPopupMenu extends React.Component { this.props.onDestroy(key); }; + getFlatInstanceArray = () => { + return this.instanceArray; + }; + + getStore = () => { + return this.props.store; + }; + + getEventKey = () => { + // when eventKey not available ,it's menu and return menu id '0-menu-' + return this.props.eventKey || '0-menu-'; + }; + getOpenTransitionName = () => { return this.props.openTransitionName; }; + step = (direction) => { + let children = this.getFlatInstanceArray(); + const activeKey = this.getStore().getState().activeKey[this.getEventKey()]; + const len = children.length; + if (!len) { + return null; + } + if (direction < 0) { + children = children.concat().reverse(); + } + // find current activeIndex + let activeIndex = -1; + children.every((c, ci) => { + if (c && c.props.eventKey === activeKey) { + activeIndex = ci; + return false; + } + return true; + }); + if ( + !this.props.defaultActiveFirst && activeIndex !== -1 + && + allDisabled(children.slice(activeIndex, len - 1)) + ) { + return undefined; + } + const start = (activeIndex + 1) % len; + let i = start; + + do { + const child = children[i]; + if (!child || child.props.disabled) { + i = (i + 1) % len; + } else { + return child; + } + } while (i !== start); + + return null; + }; + renderCommonMenuItem = (child, i, extraProps) => { const state = this.getStore().getState(); const props = this.props; @@ -294,43 +341,6 @@ export class SubPopupMenu extends React.Component { /*eslint-enable */ ); } - - step(direction) { - let children = this.getFlatInstanceArray(); - const activeKey = this.getStore().getState().activeKey[this.getEventKey()]; - const len = children.length; - if (!len) { - return null; - } - if (direction < 0) { - children = children.concat().reverse(); - } - // find current activeIndex - let activeIndex = -1; - children.every((c, ci) => { - if (c && c.props.eventKey === activeKey) { - activeIndex = ci; - return false; - } - return true; - }); - if (!this.props.defaultActiveFirst && activeIndex !== -1 && allDisabled(children.slice(activeIndex, len - 1))) { - return undefined; - } - const start = (activeIndex + 1) % len; - let i = start; - - do { - const child = children[i]; - if (!child || child.props.disabled) { - i = (i + 1) % len; - } else { - return child; - } - } while (i !== start); - - return null; - } } export default connect()(SubPopupMenu); From d8e200bd2583d60cd6f574499813a670c3092487 Mon Sep 17 00:00:00 2001 From: guifu Date: Fri, 20 Apr 2018 10:33:45 +0800 Subject: [PATCH 8/8] address more cr --- src/MenuItemGroup.jsx | 4 ---- src/SubPopupMenu.js | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/MenuItemGroup.jsx b/src/MenuItemGroup.jsx index 1a1e4184..55ea7d19 100644 --- a/src/MenuItemGroup.jsx +++ b/src/MenuItemGroup.jsx @@ -14,10 +14,6 @@ class MenuItemGroup extends React.Component { disabled: true, }; - constructor(props) { - super(props); - } - renderInnerMenuItem = (item) => { const { renderMenuItem, index } = this.props; return renderMenuItem(item, index, this.props.subMenuKey); diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index dfdb783a..7bcecb5b 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -87,7 +87,7 @@ export class SubPopupMenu extends React.Component { level: PropTypes.number, mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']), - inlineIndent: PropTypes.number, + inlineIndent: PropTypes.oneOfType(PropTypes.number, PropTypes.string), manualRef: PropTypes.func, };