From fca05394e81c7df8ac9d4a48aa352d17a17789b2 Mon Sep 17 00:00:00 2001 From: yiminghe Date: Sat, 8 Aug 2015 00:28:23 +0800 Subject: [PATCH 1/5] support mode --- HISTORY.md | 4 ++ README.md | 48 ++++++------- assets/index.less | 46 +++++++++--- examples/antd.js | 88 +++++++++++++++++++---- examples/multiple.js | 4 +- examples/single.js | 8 +-- package.json | 2 +- src/Menu.jsx | 148 +++++++++++++++++++++++--------------- src/MenuItem.jsx | 10 +-- src/SubMenu.jsx | 64 +++++++++++++---- src/SubMenuStateMixin.jsx | 18 ++--- 11 files changed, 300 insertions(+), 140 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index ed32205f..9ca87c78 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,10 @@ # History ---- +## 3.6.0 / 2015-08-08 + +add props: mode (horizontal, vertical, inline) for Menu, add defaultSelectedKeys, make selectedKeys controllable + ## 3.5.0 / 2015-08-06 add props: vertical/horizontal for Menu, align for SubMenu diff --git a/README.md b/README.md index d4756f7e..18928697 100644 --- a/README.md +++ b/README.md @@ -72,22 +72,16 @@ React.render( additional css class of root dom node + mode + String vertical - boolean - - whether add vertical className - - - horizontal - boolean - - whether add horizontal className + one of ["vertical","horizontal","inline"] activeKey Object - first active item's key - same with active tabPanel's key + + initial and current active menu item's key activeFirst @@ -107,6 +101,12 @@ React.render( [] selected keys of items + + defaultSelectedKeys + String[] + [] + initial selected keys of items + onSelect function(key:String,child:ReactComponent) @@ -114,16 +114,22 @@ React.render( called when select a menu item - onDeselect - function(key:String,child:ReactComponent) - - called when deselect a menu item + openOnHover + boolean + true + whether to open all sub menu on hover - onSelect - Function(key:String) + closeOnDeActive + boolean + true + whether to close all sub menu on deActive + + + onDeselect + function(key:String,child:ReactComponent) - function called with selected menu item's key as param + called when deselect a menu item. only called when allow multiple @@ -180,12 +186,6 @@ React.render( additional css class of root dom node - - align - object - {points:['lt', 'rt'] } - submenu align config. Defaults to align left/top of popup menu with right/top of sub menu item. - title String/ReactElement diff --git a/assets/index.less b/assets/index.less index 3f65a173..58093692 100644 --- a/assets/index.less +++ b/assets/index.less @@ -23,7 +23,16 @@ border-bottom: 1px solid #dedede; } - &-item-active, &-submenu-active { + &-inline > &-item-active, + &-submenu-inline&-submenu-active, + &-submenu-inline&-submenu-active > &-submenu-title:hover { + background-color: #fff; + } + + &-inline > &-item-active:hover, + &-item-active, + &-submenu-active, + &-submenu-inline&-submenu-active > &-submenu-title:hover { background-color: #eaf8fe; } @@ -35,19 +44,23 @@ padding: 0; } - &-submenu-lt-lb > .@{menuPrefixCls} { + &-submenu-horizontal > .@{menuPrefixCls} { top: 100%; left: 0; + position: absolute; + min-width: 160px; margin-top: 4px; } - &-submenu-lt-rt > .@{menuPrefixCls} { + &-submenu-vertical > .@{menuPrefixCls} { top: 0; left: 100%; + position: absolute; + min-width: 160px; margin-left: 4px; } - &-item,&-submenu-title { + &-item, &-submenu-title { margin: 0; position: relative; display: block; @@ -73,8 +86,6 @@ > .@{menuPrefixCls} { display: none; - position: absolute; - min-width: 160px; background-color: #fff; } @@ -101,7 +112,7 @@ border-bottom: 1px solid #d9d9d9; box-shadow: none; - & > .@{menuPrefixCls}-item , & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { + & > .@{menuPrefixCls}-item, & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { padding: 15px 20px; } @@ -124,11 +135,28 @@ } } - &-vertical { + &-vertical, &-inline { padding: 12px 0; - & > .@{menuPrefixCls}-item , & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { + & > .@{menuPrefixCls}-item, & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { padding: 12px 8px 12px 24px; } } + + &-vertical&-sub { + padding: 0; + } + + &-sub&-inline { + padding: 0; + border: none; + border-radius: 0; + box-shadow: none; + + & > .@{menuPrefixCls}-item, & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { + padding-top:8px; + padding-bottom: 8px; + padding-right: 0; + } + } } diff --git a/examples/antd.js b/examples/antd.js index 6a028a08..5be0ee27 100644 --- a/examples/antd.js +++ b/examples/antd.js @@ -9,10 +9,6 @@ function handleSelect(selectedKey) { console.log('selected ' + selectedKey); } -function handleDeselect(selectedKey) { - console.log('deselect ' + selectedKey); -} - var titleRight = sub menu; var titleRight1 = sub menu 1; var titleRight2 = sub menu 2; @@ -22,18 +18,15 @@ var container = document.getElementById('__react-content'); render(container); function render(container) { - var topAlign = { - points: ['lt', 'lb'] - }; - var leftMenu = ( - - + var horizontalMenu = ( + + 0-1 0-2 1 outer - + inner inner outer3 ); - React.render(
-

single selectable menu

-
{leftMenu}
+ + var verticalMenu = ( + + + 0-1 + 0-2 + + 1 + outer + + inner inner + + + inn + + + inner inner + inner inner2 + + + + + disabled + outer3 + + ); + + var inlineMenu = ( + + + 0-1 + 0-2 + + 1 + outer + + inner inner + + inn + + + inner inner + inner inner2 + + + + + disabled + outer3 + + ); + + + React.render(
+

antd menu

+ +
+

horizontal

+
{horizontalMenu}
+

vertical

+
{verticalMenu}
+

inline

+
{inlineMenu}
+
, container); } diff --git a/examples/multiple.js b/examples/multiple.js index a56cba79..19c7881c 100644 --- a/examples/multiple.js +++ b/examples/multiple.js @@ -37,7 +37,9 @@ function save(c) { function render(container) { var leftMenu = ( - + 0-1 0-2 diff --git a/examples/single.js b/examples/single.js index b517a4d1..3338d41f 100644 --- a/examples/single.js +++ b/examples/single.js @@ -14,10 +14,6 @@ function handleClick(selectedKey) { console.log('click ' + selectedKey); } -function handleDeselect(selectedKey) { - console.log(' deselect ' + selectedKey); -} - var titleRight = sub menu ; @@ -37,9 +33,7 @@ render(container); function render(container) { var leftMenu = ( + onClick={handleClick}> 0-1 0-2 diff --git a/package.json b/package.json index ba0a8fb8..e76af20c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-menu", - "version": "3.5.0", + "version": "3.6.0", "description": "menu ui component for react", "keywords": [ "react", diff --git a/src/Menu.jsx b/src/Menu.jsx index 4a091e70..5424f336 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -21,21 +21,29 @@ function getKeyFromChildren(child, children) { return child.key || 'rcMenuItem_' + now + '_' + getChildIndexInChildren(child, children); } +function getKeyFromChildrenIndex(child, index) { + return child.key || 'rcMenuItem_' + now + '_' + index; +} + function getActiveKey(props) { let activeKey = props.activeKey; const children = props.children; if (activeKey) { - return activeKey; - } - React.Children.forEach(children, (c)=> { - if (c.props.active) { - activeKey = getKeyFromChildren(c, children); + let found; + React.Children.forEach(children, (c, i)=> { + if (!c.props.disabled && activeKey === getKeyFromChildrenIndex(c, i)) { + found = true; + } + }); + if (found) { + return activeKey; } - }); - if (!activeKey && props.activeFirst) { - React.Children.forEach(children, (c)=> { + } + activeKey = null; + if (props.activeFirst) { + React.Children.forEach(children, (c, i)=> { if (!activeKey && !c.props.disabled) { - activeKey = getKeyFromChildren(c, children); + activeKey = getKeyFromChildrenIndex(c, i); } }); return activeKey; @@ -52,11 +60,14 @@ function saveRef(name, c) { class Menu extends React.Component { constructor(props) { super(props); + let selectedKeys = props.defaultSelectedKeys; + if ('selectedKeys' in props) { + selectedKeys = props.selectedKeys; + } this.state = { - activeKey: getActiveKey.call(this, props), - selectedKeys: props.selectedKeys || [], + activeKey: getActiveKey(props), + selectedKeys: selectedKeys || [], }; - ['onItemHover', 'onDeselect', 'onSelect', 'onKeyDown', 'onDestroy', 'renderMenuItem'].forEach((m)=> { @@ -65,9 +76,10 @@ class Menu extends React.Component { } componentWillReceiveProps(nextProps) { - const props = { - activeKey: getActiveKey.call(this, nextProps), - }; + const props = {}; + if ('activeKey' in nextProps) { + props.activeKey = getActiveKey(nextProps); + } if ('selectedKeys' in nextProps) { props.selectedKeys = nextProps.selectedKeys || []; } @@ -77,13 +89,13 @@ class Menu extends React.Component { // all keyboard events callbacks run from here at first onKeyDown(e) { const keyCode = e.keyCode; - let ond; + let handled; this.instanceArray.forEach((obj)=> { if (obj.props.active) { - ond = obj.onKeyDown(e); + handled = obj.onKeyDown(e); } }); - if (ond) { + if (handled) { return 1; } let activeItem; @@ -117,33 +129,36 @@ class Menu extends React.Component { onSelect(key, child, e) { const props = this.props; - // not from submenu - // top menu - // TODO: remove sub judge - if (!props.sub) { - if (!props.multiple) { - const selectedDescendant = this.selectedDescendant; - if (selectedDescendant) { - if (selectedDescendant !== child) { - const selectedDescendantProps = selectedDescendant.props; - selectedDescendantProps.onDeselect(selectedDescendantProps.eventKey, selectedDescendant, e, child); + + if (!('selectedKeys' in props)) { + const state = this.state; + // not from submenu + // top menu + // TODO: remove sub judge + if (!props.sub) { + if (!props.multiple) { + const selectedDescendant = this.selectedDescendant; + if (selectedDescendant) { + if (selectedDescendant !== child) { + const selectedDescendantProps = selectedDescendant.props; + selectedDescendantProps.onDeselect(selectedDescendantProps.eventKey, selectedDescendant, e, child); + } } + this.selectedDescendant = child; } - this.selectedDescendant = child; } - } - const state = this.state; - // my child - if (this.instanceArray.indexOf(child) !== -1) { - let selectedKeys; - if (props.multiple) { - selectedKeys = state.selectedKeys.concat([key]); - } else { - selectedKeys = [key]; + // my child + if (this.instanceArray.indexOf(child) !== -1) { + let selectedKeys; + if (props.multiple) { + selectedKeys = state.selectedKeys.concat([key]); + } else { + selectedKeys = [key]; + } + this.setState({ + selectedKeys: selectedKeys, + }); } - this.setState({ - selectedKeys: selectedKeys, - }); } if (props.onSelect) { @@ -152,21 +167,24 @@ class Menu extends React.Component { } onDeselect(key, child, e, __childToBeSelected) { - const state = this.state; - const children = this.instanceArray; - // my children - if (children.indexOf(child) !== -1 && children.indexOf(__childToBeSelected) === -1) { - let selectedKeys = state.selectedKeys; - const index = selectedKeys.indexOf(key); - if (index !== -1) { - selectedKeys = selectedKeys.concat([]); - selectedKeys.splice(index, 1); - this.setState({ - selectedKeys: selectedKeys, - }); + const props = this.props; + if (!('selectedKeys' in props)) { + const state = this.state; + const children = this.instanceArray; + // my children + if (children.indexOf(child) !== -1 && children.indexOf(__childToBeSelected) === -1) { + let selectedKeys = state.selectedKeys; + const index = selectedKeys.indexOf(key); + if (index !== -1) { + selectedKeys = selectedKeys.concat([]); + selectedKeys.splice(index, 1); + this.setState({ + selectedKeys: selectedKeys, + }); + } } } - this.props.onDeselect.apply(null, arguments); + props.onDeselect.apply(null, arguments); } onDestroy(key) { @@ -190,6 +208,11 @@ class Menu extends React.Component { const key = getKeyFromChildren(child, props.children); const childProps = child.props; return React.cloneElement(child, { + mode: props.mode, + level: props.level, + inlineIndent: props.inlineIndent, + globalOpenOnHover: props.openOnHover, + globalCloseOnDeActive: props.closeOnDeActive, renderMenuItem: this.renderMenuItem, rootPrefixCls: props.prefixCls, ref: createChainedFunction(child.ref, saveRef.bind(this, key)), @@ -200,7 +223,7 @@ class Menu extends React.Component { selected: state.selectedKeys.indexOf(key) !== -1, onClick: props.onClick, onDeselect: createChainedFunction(childProps.onDeselect, this.onDeselect), - onDestroy: this.onDestroy, + onDestroy: 'selectedKeys' in props ? noop : this.onDestroy, onSelect: createChainedFunction(childProps.onSelect, this.onSelect), }); } @@ -210,8 +233,8 @@ class Menu extends React.Component { this.instanceArray = []; const classes = { [props.prefixCls]: 1, - [`${props.prefixCls}-horizontal`]: !!props.horizontal, - [`${props.prefixCls}-vertical`]: !!props.vertical, + [`${props.prefixCls}-sub`]: !!props.sub, + [`${props.prefixCls}-${props.mode}`]: 1, [props.className]: !!props.className, }; const domProps = { @@ -227,8 +250,7 @@ class Menu extends React.Component { domProps.onKeyDown = this.onKeyDown; } return ( -
    {React.Children.map(props.children, this.renderMenuItem)}
@@ -275,13 +297,21 @@ Menu.propTypes = { style: React.PropTypes.object, onDeselect: React.PropTypes.func, activeFirst: React.PropTypes.bool, + openOnHover: React.PropTypes.bool, + closeOnDeActive: React.PropTypes.bool, activeKey: React.PropTypes.string, selectedKeys: React.PropTypes.arrayOf(React.PropTypes.string), + defaultSelectedKeys: React.PropTypes.arrayOf(React.PropTypes.string), }; Menu.defaultProps = { prefixCls: 'rc-menu', + mode: 'vertical', + level: 1, + inlineIndent: 24, focusable: true, + openOnHover: true, + closeOnDeActive: true, style: {}, onSelect: noop, onClick: noop, diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index 07fd5be7..282b9d66 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -1,5 +1,3 @@ - - import React from 'react'; import {joinClasses, classSet, KeyCode} from 'rc-util'; @@ -88,11 +86,15 @@ class MenuItem extends React.Component { onMouseEnter: this.onMouseEnter, }; } + const style = {}; + if (props.mode === 'inline') { + style.paddingLeft = props.inlineIndent * props.level; + } return ( -
  • - {props.children} + {props.children}
  • ); } diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index 54246ce5..7755a358 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -4,6 +4,8 @@ import {classSet, createChainedFunction, KeyCode, guid} from 'rc-util'; const SubMenu = React.createClass({ propTypes: { + closeOnDeActive: React.PropTypes.bool, + globalCloseOnDeActive: React.PropTypes.bool, openOnHover: React.PropTypes.bool, title: React.PropTypes.node, onClick: React.PropTypes.func, @@ -22,18 +24,15 @@ const SubMenu = React.createClass({ }; }, + componentWillReceiveProps(nextProps) { - if (!nextProps.active) { + if (!nextProps.active && this.isCloseOnDeActive()) { this.setOpenState(false); } }, getDefaultProps() { return { - openOnHover: true, - align: { - points: ['lt', 'rt'], - }, onMouseEnter() { }, title: '', @@ -85,7 +84,14 @@ const SubMenu = React.createClass({ onMouseEnter() { const props = this.props; props.onHover(props.eventKey); - if (props.openOnHover) { + let openOnHover = props.openOnHover; + if (openOnHover === undefined) { + openOnHover = props.globalOpenOnHover; + } + if (openOnHover === undefined) { + openOnHover = true; + } + if (openOnHover) { this.setOpenState(true); this.setState({ activeFirst: false, @@ -100,7 +106,11 @@ const SubMenu = React.createClass({ }, onClick() { - this.setOpenState(true); + if (this.isCloseOnDeActive()) { + this.setOpenState(true); + } else { + this.setOpenState(!this.state.open); + } this.setState({ activeFirst: false, }); @@ -132,23 +142,35 @@ const SubMenu = React.createClass({ }, renderChildren(children) { + const props = this.props; if (!this.state.open) { // prevent destroy return this._cacheMenu || null; } const childrenCount = React.Children.count(children); + let mode = props.mode; + if (mode !== 'inline') { + mode = undefined; + } const baseProps = { sub: true, + level: props.level + 1, + inlineIndent: props.inlineIndent, + openOnHover: props.globalOpenOnHover, + closeOnDeActive: props.globalCloseOnDeActive, focusable: false, onClick: this.onSubMenuClick, onSelect: this.onSelect, onDeselect: this.onDeselect, activeFirst: this.state.activeFirst, - multiple: this.props.multiple, - prefixCls: this.props.rootPrefixCls, + multiple: props.multiple, + prefixCls: props.rootPrefixCls, id: this._menuId, ref: this.saveMenuInstance, }; + if (mode) { + baseProps.mode = mode; + } if (childrenCount === 1 && children.type === Menu) { const menu = children; baseProps.ref = createChainedFunction(menu.ref, this.saveMenuInstance); @@ -162,18 +184,18 @@ const SubMenu = React.createClass({ render() { const props = this.props; + const prefixCls = this.getPrefixCls(); const classes = { [props.className]: !!props.className, + [`${prefixCls}-${props.mode}`]: 1, }; - const prefixCls = this.getPrefixCls(); + classes[this.getOpenClassName()] = this.state.open; classes[this.getActiveClassName()] = props.active; classes[this.getDisabledClassName()] = props.disabled; this._menuId = this._menuId || guid(); classes[prefixCls] = true; - if (props.align) { - classes[prefixCls + '-' + props.align.points.join('-')] = 1; - } + classes[prefixCls + '-' + props.mode] = 1; let clickEvents = {}; let mouseEvents = {}; let titleMouseEvents = {}; @@ -189,9 +211,14 @@ const SubMenu = React.createClass({ onMouseEnter: this.onMouseEnter, }; } + const style = {}; + if (props.mode === 'inline') { + style.paddingLeft = props.inlineIndent * props.level; + } return (
  • Date: Mon, 10 Aug 2015 13:06:45 +0800 Subject: [PATCH 2/5] make selectedKeys controllable --- HISTORY.md | 5 +- README.md | 18 +++--- examples/antd.js | 17 +++--- examples/multiple.js | 22 ++++--- examples/scrollable.js | 12 ++-- examples/selectedKeys.html | 1 + examples/selectedKeys.js | 94 +++++++++++++++++++++++++++++ examples/single.js | 18 +++--- package.json | 3 +- src/Menu.jsx | 118 ++++++++++++++++++------------------- src/MenuItem.jsx | 13 ++-- src/SubMenu.jsx | 68 ++++++++++----------- 12 files changed, 236 insertions(+), 153 deletions(-) create mode 100644 examples/selectedKeys.html create mode 100644 examples/selectedKeys.js diff --git a/HISTORY.md b/HISTORY.md index 9ca87c78..98554e24 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,9 +1,10 @@ # History ---- -## 3.6.0 / 2015-08-08 +## 4.0.0 / 2015-08-08 -add props: mode (horizontal, vertical, inline) for Menu, add defaultSelectedKeys, make selectedKeys controllable +- add props: mode (horizontal, vertical, inline) for Menu, add defaultSelectedKeys, make selectedKeys controllable +- make selectedKeys controllable ## 3.5.0 / 2015-08-06 diff --git a/README.md b/README.md index 18928697..8859d461 100644 --- a/README.md +++ b/README.md @@ -81,13 +81,13 @@ React.render( activeKey Object - initial and current active menu item's key + initial and current active menu item's key. - activeFirst + defaultActiveFirst Boolean false - whether active first menu item when show if activeKey is not set + whether active first menu item when show if activeKey is not set or invalid multiple @@ -109,25 +109,25 @@ React.render( onSelect - function(key:String,child:ReactComponent) + function({key:String,item:ReactComponent,selectedKeys:String[]}) called when select a menu item - openOnHover + openSubMenuOnHover boolean true - whether to open all sub menu on hover + whether to open sub menu on hover - closeOnDeActive + closeSubMenuOnDeactive boolean true - whether to close all sub menu on deActive + whether to close sub menu on deactive onDeselect - function(key:String,child:ReactComponent) + function({key:String,item:ReactComponent,selectedKeys:String[]}) called when deselect a menu item. only called when allow multiple diff --git a/examples/antd.js b/examples/antd.js index 5be0ee27..2feb076a 100644 --- a/examples/antd.js +++ b/examples/antd.js @@ -5,8 +5,9 @@ import Menu, {SubMenu, Item as MenuItem, ItemGroup as MenuItemGroup, Divider} fr import 'rc-menu/assets/index.less'; -function handleSelect(selectedKey) { - console.log('selected ' + selectedKey); +function handleSelect(info) { + console.log(info); + console.log('selected ' + info.key); } var titleRight = sub menu; @@ -29,9 +30,7 @@ function render(container) { inner inner - inn @@ -59,9 +58,7 @@ function render(container) { inner inner - inn @@ -80,8 +77,8 @@ function render(container) { var inlineMenu = ( + closeSubMenuOnDeactive={false} + openSubMenuOnHover={false}> 0-1 0-2 diff --git a/examples/multiple.js b/examples/multiple.js index 19c7881c..7aad60d3 100644 --- a/examples/multiple.js +++ b/examples/multiple.js @@ -6,12 +6,12 @@ import Menu, {SubMenu, Item as MenuItem, ItemGroup as MenuItemGroup, Divider} fr import 'rc-menu/assets/index.less'; import 'font-awesome/css/font-awesome.css'; -function handleSelect(selectedKey, item, e) { - console.log('selected ' + selectedKey, item, e); +function handleSelect(info) { + console.log('selected ', info); } -function handleDeselect(selectedKey, item, e) { - console.log('deselect ' + selectedKey, item, e); +function handleDeselect(info) { + console.log('deselect ', info); } var titleRight = sub menu @@ -37,9 +37,9 @@ function save(c) { function render(container) { var leftMenu = ( - + 0-1 0-2 @@ -49,11 +49,8 @@ function render(container) { inner inner - + inn @@ -69,6 +66,7 @@ function render(container) { ); React.render(

    multiple selectable menu

    +

    diff --git a/examples/scrollable.js b/examples/scrollable.js index 88091bf3..3742af78 100644 --- a/examples/scrollable.js +++ b/examples/scrollable.js @@ -10,13 +10,11 @@ var children = []; for (var i = 0; i < 20; i++) { children.push({i}); } -var style = '.rc-menu {\ -height: 200px;\ -width:200px;\ -overflow:auto;\ -}'; React.render(

    keyboard scrollable menu

    - - {children} + {children}
    , document.getElementById('__react-content')); diff --git a/examples/selectedKeys.html b/examples/selectedKeys.html new file mode 100644 index 00000000..b3a42524 --- /dev/null +++ b/examples/selectedKeys.html @@ -0,0 +1 @@ +placeholder \ No newline at end of file diff --git a/examples/selectedKeys.js b/examples/selectedKeys.js new file mode 100644 index 00000000..84f738d3 --- /dev/null +++ b/examples/selectedKeys.js @@ -0,0 +1,94 @@ +'use strict'; + +import React from 'react'; +import Menu, {SubMenu, Item as MenuItem, ItemGroup as MenuItemGroup, Divider} from 'rc-menu'; + +import 'rc-menu/assets/index.less'; +import 'font-awesome/css/font-awesome.css'; + +var titleRight = sub menu + +; +var titleRight1 = sub menu 1 + +; +var titleRight2 = sub menu 2 + +; +var titleRight3 = sub menu 3 + +; + +var Test = React.createClass({ + getInitialState(){ + return { + destroyed: false, + selectedKeys: ['2', '1-1'] + }; + }, + + handleSelect(info){ + console.log('selected ', info); + this.setState({ + selectedKeys: info.selectedKeys + }); + }, + + handleDeselect(info) { + console.log('deselect ', info); + }, + + getMenu(){ + return ( + + + 0-1 + 0-2 + + can not deselect me,i'm disabled + outer + + inner inner + + + inn + + + inner inner + inner inner2 + + + + + disabled + outer3 + + ); + }, + + render(){ + if (this.state.destroyed) { + return null; + } + return
    +

    multiple selectable menu

    + +

    + +

    +
    {this.getMenu()}
    +
    ; + }, + + destroy(){ + this.setState({ + destroyed: true + }); + } +}); + + +React.render(, document.getElementById('__react-content')); diff --git a/examples/single.js b/examples/single.js index 3338d41f..fc7cb444 100644 --- a/examples/single.js +++ b/examples/single.js @@ -6,12 +6,12 @@ import Menu, {SubMenu, Item as MenuItem, ItemGroup as MenuItemGroup, Divider} fr import 'rc-menu/assets/index.less'; import 'font-awesome/css/font-awesome.css'; -function handleSelect(selectedKey) { - console.log('selected ' + selectedKey); +function handleSelect(info) { + console.log('selected ', info); } -function handleClick(selectedKey) { - console.log('click ' + selectedKey); +function handleClick(info) { + console.log('click ', info); } var titleRight = sub menu @@ -33,7 +33,7 @@ render(container); function render(container) { var leftMenu = ( + onClick={handleClick}> 0-1 0-2 @@ -45,11 +45,8 @@ function render(container) { inner inner - + inn @@ -65,6 +62,7 @@ function render(container) { ); React.render(

    single selectable menu

    +

    diff --git a/package.json b/package.json index e76af20c..bb295e7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-menu", - "version": "3.6.0", + "version": "4.0.0", "description": "menu ui component for react", "keywords": [ "react", @@ -51,6 +51,7 @@ }, "dependencies": { "dom-scroll-into-view": "1.x", + "object-assign": "3.x", "rc-util": "2.x" }, "precommit": [ diff --git a/src/Menu.jsx b/src/Menu.jsx index 5424f336..5055cd7b 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -1,6 +1,7 @@ import React from 'react'; import {classSet, createChainedFunction, KeyCode} from 'rc-util'; import scrollIntoView from 'dom-scroll-into-view'; +import assign from 'object-assign'; function noop() { } @@ -40,7 +41,7 @@ function getActiveKey(props) { } } activeKey = null; - if (props.activeFirst) { + if (props.defaultActiveFirst) { React.Children.forEach(children, (c, i)=> { if (!activeKey && !c.props.disabled) { activeKey = getKeyFromChildrenIndex(c, i); @@ -86,6 +87,10 @@ class Menu extends React.Component { this.setState(props); } + shouldComponentUpdate(nextProps) { + return this.props.visible || nextProps.visible; + } + // all keyboard events callbacks run from here at first onKeyDown(e) { const keyCode = e.keyCode; @@ -127,78 +132,63 @@ class Menu extends React.Component { }); } - onSelect(key, child, e) { + onSelect(selectInfo) { const props = this.props; - - if (!('selectedKeys' in props)) { - const state = this.state; - // not from submenu - // top menu - // TODO: remove sub judge - if (!props.sub) { - if (!props.multiple) { - const selectedDescendant = this.selectedDescendant; - if (selectedDescendant) { - if (selectedDescendant !== child) { - const selectedDescendantProps = selectedDescendant.props; - selectedDescendantProps.onDeselect(selectedDescendantProps.eventKey, selectedDescendant, e, child); - } - } - this.selectedDescendant = child; - } + // root menu + if (!props.sub) { + let selectedKeys = this.state.selectedKeys; + const selectedKey = selectInfo.key; + if (props.multiple) { + selectedKeys = selectedKeys.concat([selectedKey]); + } else { + selectedKeys = [selectedKey]; } - // my child - if (this.instanceArray.indexOf(child) !== -1) { - let selectedKeys; - if (props.multiple) { - selectedKeys = state.selectedKeys.concat([key]); - } else { - selectedKeys = [key]; - } + if (!('selectedKeys' in props)) { this.setState({ selectedKeys: selectedKeys, }); } - } - - if (props.onSelect) { - props.onSelect(key, child, e); + props.onSelect(assign({}, selectInfo, { + selectedKeys: selectedKeys, + })); + } else { + props.onSelect(selectInfo); } } - onDeselect(key, child, e, __childToBeSelected) { + onDeselect(selectInfo) { const props = this.props; - if (!('selectedKeys' in props)) { - const state = this.state; - const children = this.instanceArray; - // my children - if (children.indexOf(child) !== -1 && children.indexOf(__childToBeSelected) === -1) { - let selectedKeys = state.selectedKeys; - const index = selectedKeys.indexOf(key); - if (index !== -1) { - selectedKeys = selectedKeys.concat([]); - selectedKeys.splice(index, 1); - this.setState({ - selectedKeys: selectedKeys, - }); - } + if (!props.sub) { + const selectedKeys = this.state.selectedKeys.concat(); + const selectedKey = selectInfo.key; + const index = selectedKeys.indexOf(selectedKey); + if (index !== -1) { + selectedKeys.splice(index, 1); } + if (!('selectedKeys' in props)) { + this.setState({ + selectedKeys: selectedKeys, + }); + } + props.onDeselect(assign({}, selectInfo, { + selectedKeys: selectedKeys, + })); + } else { + props.onDeselect(selectInfo); } - props.onDeselect.apply(null, arguments); } onDestroy(key) { const state = this.state; + const props = this.props; const selectedKeys = state.selectedKeys; const index = selectedKeys.indexOf(key); - if (index !== -1) { - // selectedKeys = selectedKeys.concat([]); - selectedKeys.splice(index, 1); - // can not call setState in unmount, will cause render and update unmounted children - // https://github.com/facebook/react/pull/3795 - // this.setState({ - // selectedKeys: selectedKeys - // }); + if (!props.sub) { + if (!('selectedKeys' in props) && index !== -1) { + selectedKeys.splice(index, 1); + } + } else { + props.onDestroy(key); } } @@ -211,8 +201,8 @@ class Menu extends React.Component { mode: props.mode, level: props.level, inlineIndent: props.inlineIndent, - globalOpenOnHover: props.openOnHover, - globalCloseOnDeActive: props.closeOnDeActive, + openSubMenuOnHover: props.openSubMenuOnHover, + closeSubMenuOnDeactive: props.closeSubMenuOnDeactive, renderMenuItem: this.renderMenuItem, rootPrefixCls: props.prefixCls, ref: createChainedFunction(child.ref, saveRef.bind(this, key)), @@ -220,6 +210,7 @@ class Menu extends React.Component { onHover: this.onItemHover, active: !childProps.disabled && key === state.activeKey, multiple: props.multiple, + selectedKeys: state.selectedKeys, selected: state.selectedKeys.indexOf(key) !== -1, onClick: props.onClick, onDeselect: createChainedFunction(childProps.onDeselect, this.onDeselect), @@ -234,6 +225,7 @@ class Menu extends React.Component { const classes = { [props.prefixCls]: 1, [`${props.prefixCls}-sub`]: !!props.sub, + [`${props.prefixCls}-root`]: !props.sub, [`${props.prefixCls}-${props.mode}`]: 1, [props.className]: !!props.className, }; @@ -296,9 +288,10 @@ Menu.propTypes = { onSelect: React.PropTypes.func, style: React.PropTypes.object, onDeselect: React.PropTypes.func, - activeFirst: React.PropTypes.bool, - openOnHover: React.PropTypes.bool, - closeOnDeActive: React.PropTypes.bool, + defaultActiveFirst: React.PropTypes.bool, + visible: React.PropTypes.bool, + openSubMenuOnHover: React.PropTypes.bool, + closeSubMenuOnDeactive: React.PropTypes.bool, activeKey: React.PropTypes.string, selectedKeys: React.PropTypes.arrayOf(React.PropTypes.string), defaultSelectedKeys: React.PropTypes.arrayOf(React.PropTypes.string), @@ -309,9 +302,10 @@ Menu.defaultProps = { mode: 'vertical', level: 1, inlineIndent: 24, + visible: true, focusable: true, - openOnHover: true, - closeOnDeActive: true, + openSubMenuOnHover: true, + closeSubMenuOnDeactive: true, style: {}, onSelect: noop, onClick: noop, diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index 282b9d66..4f7fd051 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -36,15 +36,20 @@ class MenuItem extends React.Component { onClick(e) { const props = this.props; const eventKey = props.eventKey; - props.onClick(eventKey, this, e); + const info = { + key: eventKey, + item: this, + domEvent: e, + }; + props.onClick(info); if (props.multiple) { if (props.selected) { - props.onDeselect(eventKey, this, e); + props.onDeselect(info); } else { - props.onSelect(eventKey, this, e); + props.onSelect(info); } } else if (!props.selected) { - props.onSelect(eventKey, this, e); + props.onSelect(info); } } diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index 7755a358..1d08500b 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -4,9 +4,10 @@ import {classSet, createChainedFunction, KeyCode, guid} from 'rc-util'; const SubMenu = React.createClass({ propTypes: { - closeOnDeActive: React.PropTypes.bool, - globalCloseOnDeActive: React.PropTypes.bool, + closeOnDeactive: React.PropTypes.bool, + closeSubMenuOnDeactive: React.PropTypes.bool, openOnHover: React.PropTypes.bool, + openSubMenuOnHover: React.PropTypes.bool, title: React.PropTypes.node, onClick: React.PropTypes.func, rootPrefixCls: React.PropTypes.string, @@ -20,13 +21,12 @@ const SubMenu = React.createClass({ getInitialState() { return { - activeFirst: false, + defaultActiveFirst: false, }; }, - componentWillReceiveProps(nextProps) { - if (!nextProps.active && this.isCloseOnDeActive()) { + if (!nextProps.active && this.isCloseOnDeactive()) { this.setOpenState(false); } }, @@ -46,7 +46,7 @@ const SubMenu = React.createClass({ if (keyCode === KeyCode.ENTER) { this.onClick(e); this.setState({ - activeFirst: true, + defaultActiveFirst: true, }); return true; } @@ -57,7 +57,7 @@ const SubMenu = React.createClass({ } else { this.setOpenState(true); this.setState({ - activeFirst: true, + defaultActiveFirst: true, }); } return true; @@ -86,7 +86,7 @@ const SubMenu = React.createClass({ props.onHover(props.eventKey); let openOnHover = props.openOnHover; if (openOnHover === undefined) { - openOnHover = props.globalOpenOnHover; + openOnHover = props.openSubMenuOnHover; } if (openOnHover === undefined) { openOnHover = true; @@ -94,7 +94,7 @@ const SubMenu = React.createClass({ if (openOnHover) { this.setOpenState(true); this.setState({ - activeFirst: false, + defaultActiveFirst: false, }); } }, @@ -106,27 +106,26 @@ const SubMenu = React.createClass({ }, onClick() { - if (this.isCloseOnDeActive()) { + if (this.isCloseOnDeactive()) { this.setOpenState(true); } else { this.setOpenState(!this.state.open); } this.setState({ - activeFirst: false, + defaultActiveFirst: false, }); }, - onSubMenuClick(key, menuItem, e) { - this.props.onClick(key, menuItem, e); + onSubMenuClick(info) { + this.props.onClick(info); }, - onSelect(childKey, child, e) { - // propagate - this.props.onSelect(childKey, child, e); + onSelect(info) { + this.props.onSelect(info); }, - onDeselect() { - this.props.onDeselect.apply(null, arguments); + onDeselect(info) { + this.props.onDeselect(info); }, getPrefixCls() { @@ -143,10 +142,6 @@ const SubMenu = React.createClass({ renderChildren(children) { const props = this.props; - if (!this.state.open) { - // prevent destroy - return this._cacheMenu || null; - } const childrenCount = React.Children.count(children); let mode = props.mode; if (mode !== 'inline') { @@ -154,15 +149,17 @@ const SubMenu = React.createClass({ } const baseProps = { sub: true, + visible: this.state.open, level: props.level + 1, inlineIndent: props.inlineIndent, - openOnHover: props.globalOpenOnHover, - closeOnDeActive: props.globalCloseOnDeActive, + openSubMenuOnHover: props.openSubMenuOnHover, + closeSubMenuOnDeactive: props.closeSubMenuOnDeactive, focusable: false, onClick: this.onSubMenuClick, onSelect: this.onSelect, onDeselect: this.onDeselect, - activeFirst: this.state.activeFirst, + selectedKeys: props.selectedKeys, + defaultActiveFirst: this.state.defaultActiveFirst, multiple: props.multiple, prefixCls: props.rootPrefixCls, id: this._menuId, @@ -175,14 +172,13 @@ const SubMenu = React.createClass({ const menu = children; baseProps.ref = createChainedFunction(menu.ref, this.saveMenuInstance); baseProps.onClick = createChainedFunction(menu.props.onClick, this.onSubMenuClick); - this._cacheMenu = React.cloneElement(menu, baseProps); - } else { - this._cacheMenu = {children}; + return React.cloneElement(menu, baseProps); } - return this._cacheMenu; + return {children}; }, render() { + this.haveOpened = this.haveOpened || this.state.opened; const props = this.props; const prefixCls = this.getPrefixCls(); const classes = { @@ -237,15 +233,15 @@ const SubMenu = React.createClass({ this.menuInstance = c; }, - isCloseOnDeActive() { - let closeOnDeActive = this.props.closeOnDeActive; - if (closeOnDeActive === undefined) { - closeOnDeActive = this.props.globalCloseOnDeActive; + isCloseOnDeactive() { + let closeOnDeactive = this.props.closeOnDeactive; + if (closeOnDeactive === undefined) { + closeOnDeactive = this.props.closeSubMenuOnDeactive; } - if (closeOnDeActive === undefined) { - closeOnDeActive = true; + if (closeOnDeactive === undefined) { + closeOnDeactive = true; } - return closeOnDeActive; + return closeOnDeactive; }, }); From ecf6f52154ba787b71ac27372a96a38028fbffb5 Mon Sep 17 00:00:00 2001 From: yiminghe Date: Mon, 10 Aug 2015 16:25:10 +0800 Subject: [PATCH 3/5] support inline menu --- README.md | 16 ++-------------- examples/antd.js | 2 -- src/Menu.jsx | 11 +++++++++-- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 8859d461..fbf03072 100644 --- a/README.md +++ b/README.md @@ -109,25 +109,13 @@ React.render( onSelect - function({key:String,item:ReactComponent,selectedKeys:String[]}) + function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[]}) called when select a menu item - - openSubMenuOnHover - boolean - true - whether to open sub menu on hover - - - closeSubMenuOnDeactive - boolean - true - whether to close sub menu on deactive - onDeselect - function({key:String,item:ReactComponent,selectedKeys:String[]}) + function({key:String, item:ReactComponent, domEvent:Event, selectedKeys:String[]}) called when deselect a menu item. only called when allow multiple diff --git a/examples/antd.js b/examples/antd.js index 2feb076a..831cb243 100644 --- a/examples/antd.js +++ b/examples/antd.js @@ -77,8 +77,6 @@ function render(container) { var inlineMenu = ( 0-1 0-2 diff --git a/src/Menu.jsx b/src/Menu.jsx index 5055cd7b..304305e4 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -197,12 +197,19 @@ class Menu extends React.Component { const props = this.props; const key = getKeyFromChildren(child, props.children); const childProps = child.props; + const mode = props.mode; + let openSubMenuOnHover = props.openSubMenuOnHover; + let closeSubMenuOnDeactive = props.closeSubMenuOnDeactive; + if (mode === 'inline') { + openSubMenuOnHover = false; + closeSubMenuOnDeactive = false; + } return React.cloneElement(child, { mode: props.mode, level: props.level, inlineIndent: props.inlineIndent, - openSubMenuOnHover: props.openSubMenuOnHover, - closeSubMenuOnDeactive: props.closeSubMenuOnDeactive, + openSubMenuOnHover: openSubMenuOnHover, + closeSubMenuOnDeactive: closeSubMenuOnDeactive, renderMenuItem: this.renderMenuItem, rootPrefixCls: props.prefixCls, ref: createChainedFunction(child.ref, saveRef.bind(this, key)), From 489f95fe6baecece87e3fc6553f3e6e4fee585f9 Mon Sep 17 00:00:00 2001 From: yiminghe Date: Mon, 10 Aug 2015 16:26:20 +0800 Subject: [PATCH 4/5] update docs --- HISTORY.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 98554e24..687c012f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,10 +1,11 @@ # History ---- -## 4.0.0 / 2015-08-08 +## 4.0.0 / 2015-08-10 -- add props: mode (horizontal, vertical, inline) for Menu, add defaultSelectedKeys, make selectedKeys controllable -- make selectedKeys controllable +- add props: mode (horizontal, vertical, inline) for Menu +- add defaultSelectedKeys, make selectedKeys controllable +- change param of onSelect onDeselect ## 3.5.0 / 2015-08-06 From 6b2dacfa7031afde5a38f436b18a477965691c20 Mon Sep 17 00:00:00 2001 From: yiminghe Date: Mon, 10 Aug 2015 16:31:32 +0800 Subject: [PATCH 5/5] update demo --- examples/antd.js | 2 +- tests/Menu.spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/antd.js b/examples/antd.js index 831cb243..2386e55d 100644 --- a/examples/antd.js +++ b/examples/antd.js @@ -76,7 +76,7 @@ function render(container) { ); var inlineMenu = ( - 0-1 0-2 diff --git a/tests/Menu.spec.js b/tests/Menu.spec.js index 6a00d14e..64b8f120 100644 --- a/tests/Menu.spec.js +++ b/tests/Menu.spec.js @@ -46,8 +46,8 @@ describe('Menu', function () { it('Should call on select when item is selected', function (done) { var count = 0; - function handleSelect(key) { - expect(key).to.be('item2'); + function handleSelect(e) { + expect(e.key).to.be('item2'); count++; if (count === 2) { done();