From 6de7d056182fd66b6fab41d35eddc3565f147bcd Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Tue, 24 Jul 2018 18:29:38 +0800 Subject: [PATCH 01/18] support arrow icon. --- README.md | 6 + assets/custom-arrow-icon.less | 294 ++++++++++++++++++++++++++++++++ examples/custom-arrow-icon.html | 1 + examples/custom-arrow-icon.js | 162 ++++++++++++++++++ src/SubMenu.jsx | 9 +- src/SubPopupMenu.js | 1 + src/util.js | 3 + 7 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 assets/custom-arrow-icon.less create mode 100644 examples/custom-arrow-icon.html create mode 100644 examples/custom-arrow-icon.js diff --git a/README.md b/README.md index a0b9fa1b..c381a7e6 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,12 @@ ReactDOM.render( The offset of the popup submenu, in an x, y coordinate array. e.g.: `[0,15]` + + arrowIcon + React.Node + '' + specific the arrow icon. + diff --git a/assets/custom-arrow-icon.less b/assets/custom-arrow-icon.less new file mode 100644 index 00000000..de072a90 --- /dev/null +++ b/assets/custom-arrow-icon.less @@ -0,0 +1,294 @@ +@menuPrefixCls: rc-menu; + +@font-face { + font-family: 'FontAwesome'; + src: url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0'); + src: url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +.@{menuPrefixCls} { + outline: none; + margin-bottom: 0; + padding-left: 0; // Override default ul/ol + list-style: none; + border: 1px solid #d9d9d9; + box-shadow: 0 0 4px #d9d9d9; + border-radius: 3px; + color: #666; + + &-hidden { + display: none; + } + + &-collapse { + overflow: hidden; + &-active { + transition: height .3s ease-out; + } + } + + &-item-group-list { + margin: 0; + padding: 0; + } + + &-item-group-title { + color: #999; + line-height: 1.5; + padding: 8px 10px; + border-bottom: 1px solid #dedede; + } + + &-item-active, + &-submenu-active > &-submenu-title { + background-color: #eaf8fe; + } + + &-item-selected { + background-color: #eaf8fe; + // fix chrome render bug + transform: translateZ(0); + } + + &-submenu-selected { + background-color: #eaf8fe; + } + + & > li&-submenu { + padding: 0; + } + + &-horizontal&-sub, + &-vertical&-sub, + &-vertical-left&-sub, + &-vertical-right&-sub { + min-width: 160px; + margin-top: 0; + } + + &-item, &-submenu-title { + margin: 0; + position: relative; + display: block; + padding: 7px 7px 7px 16px; + white-space: nowrap; + + // Disabled state sets text to gray and nukes hover/tab effects + &.@{menuPrefixCls}-item-disabled, &.@{menuPrefixCls}-submenu-disabled { + color: #777 !important; + } + } + & > &-item-divider { + height: 1px; + margin: 1px 0; + overflow: hidden; + padding: 0; + line-height: 0; + background-color: #e5e5e5; + } + + &-submenu { + &-popup { + position: absolute; + } + > .@{menuPrefixCls} { + background-color: #fff; + } + } + + .@{menuPrefixCls}-submenu-title, .@{menuPrefixCls}-item { + .anticon { + width: 14px; + height: 14px; + margin-right: 8px; + top: -1px; + } + } + + &-horizontal { + background-color: #F3F5F7; + border: none; + border-bottom: 1px solid #d9d9d9; + box-shadow: none; + + & > .@{menuPrefixCls}-item, & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { + padding: 15px 20px; + } + + & > .@{menuPrefixCls}-submenu, & > .@{menuPrefixCls}-item { + float: left; + border-bottom: 2px solid transparent; + + &-active { + border-bottom: 2px solid #2db7f5; + background-color: #F3F5F7; + color: #2baee9; + } + } + + &:after { + content: "\20"; + display: block; + height: 0; + clear: both; + } + } + + &-vertical, + &-vertical-left, + &-vertical-right, + &-inline { + padding: 12px 0; + & > .@{menuPrefixCls}-item, & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { + padding: 12px 8px 12px 24px; + } + .@{menuPrefixCls}-submenu-arrow { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + vertical-align: baseline; + text-align: center; + text-transform: none; + text-rendering: auto; + position: absolute; + right: 16px; + line-height: 1.5em; + } + } + &-inline { + .@{menuPrefixCls}-submenu-arrow { + transform: rotate(90deg); + transition: transform .3s; + } + & .@{menuPrefixCls}-submenu-open > .@{menuPrefixCls}-submenu-title { + .@{menuPrefixCls}-submenu-arrow { + transform: rotate(-90deg); + } + } + } + + &-vertical&-sub, + &-vertical-left&-sub, + &-vertical-right&-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; + } + } + + .effect() { + animation-duration: .3s; + animation-fill-mode: both; + transform-origin: 0 0; + } + + &-open { + + &-slide-up-enter, &-slide-up-appear { + .effect(); + opacity: 0; + animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); + animation-play-state: paused; + } + + &-slide-up-leave { + .effect(); + opacity: 1; + animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); + animation-play-state: paused; + } + + &-slide-up-enter&-slide-up-enter-active, &-slide-up-appear&-slide-up-appear-active { + animation-name: rcMenuOpenSlideUpIn; + animation-play-state: running; + } + + &-slide-up-leave&-slide-up-leave-active { + animation-name: rcMenuOpenSlideUpOut; + animation-play-state: running; + } + + @keyframes rcMenuOpenSlideUpIn { + 0% { + opacity: 0; + transform-origin: 0% 0%; + transform: scaleY(0); + } + 100% { + opacity: 1; + transform-origin: 0% 0%; + transform: scaleY(1); + } + } + @keyframes rcMenuOpenSlideUpOut { + 0% { + opacity: 1; + transform-origin: 0% 0%; + transform: scaleY(1); + } + 100% { + opacity: 0; + transform-origin: 0% 0%; + transform: scaleY(0); + } + } + + &-zoom-enter, &-zoom-appear { + opacity: 0; + .effect(); + animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); + animation-play-state: paused; + } + + &-zoom-leave { + .effect(); + animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); + animation-play-state: paused; + } + + &-zoom-enter&-zoom-enter-active, &-zoom-appear&-zoom-appear-active { + animation-name: rcMenuOpenZoomIn; + animation-play-state: running; + } + + &-zoom-leave&-zoom-leave-active { + animation-name: rcMenuOpenZoomOut; + animation-play-state: running; + } + + @keyframes rcMenuOpenZoomIn { + 0% { + opacity: 0; + transform: scale(0, 0); + } + 100% { + opacity: 1; + transform: scale(1, 1); + } + } + @keyframes rcMenuOpenZoomOut { + 0% { + + transform: scale(1, 1); + } + 100% { + opacity: 0; + transform: scale(0, 0); + } + } + } + +} + diff --git a/examples/custom-arrow-icon.html b/examples/custom-arrow-icon.html new file mode 100644 index 00000000..b3a42524 --- /dev/null +++ b/examples/custom-arrow-icon.html @@ -0,0 +1 @@ +placeholder \ No newline at end of file diff --git a/examples/custom-arrow-icon.js b/examples/custom-arrow-icon.js new file mode 100644 index 00000000..c6892366 --- /dev/null +++ b/examples/custom-arrow-icon.js @@ -0,0 +1,162 @@ +/* eslint no-console:0 */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; +import 'rc-menu/assets/custom-arrow-icon.less'; +import animate from 'css-animation'; + +const getSvgIcon = () => { + const path = 'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h' + + '-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v' + + '60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91' + + '.5c1.9 0 3.8-0.7 5.2-2L869 536.2c14.7-12.8 14.7-35.6 0-48.4z'; + return ( + + + + ); +}; + +function handleClick(info) { + console.log(`clicked ${info.key}`); + console.log(info); +} + +const animation = { + enter(node, done) { + let height; + return animate(node, 'rc-menu-collapse', { + start() { + height = node.offsetHeight; + node.style.height = 0; + }, + active() { + node.style.height = `${height}px`; + }, + end() { + node.style.height = ''; + done(); + }, + }); + }, + + appear() { + return this.enter.apply(this, arguments); + }, + + leave(node, done) { + return animate(node, 'rc-menu-collapse', { + start() { + node.style.height = `${node.offsetHeight}px`; + }, + active() { + node.style.height = 0; + }, + end() { + node.style.height = ''; + done(); + }, + }); + }, +}; + +const reactContainer = document.getElementById('__react-content'); + +const nestSubMenu = ( + offset sub menu 2} + key="4" + popupOffset={[10, 15]} + > + inner inner + + sub menu 3} + arrowIcon={getSvgIcon()} + > + + inner inner + inner inner2 + + inn + sub menu 4} key="4-2-2" arrowIcon={getSvgIcon()}> + inner inner + inner inner2 + + + inner inner + inner inner2 + + + +); + +function onOpenChange(value) { + console.log('onOpenChange', value); +} +const commonMenu = ( + sub menu} key="1" arrowIcon={getSvgIcon()}> + 0-1 + 0-2 + + {nestSubMenu} + 1 + outer + disabled + outer3 +); + +function render(container) { + const horizontalMenu = React.cloneElement(commonMenu, { + mode: 'horizontal', + // use openTransition for antd + openAnimation: 'slide-up', + }); + + const horizontalMenu2 = React.cloneElement(commonMenu, { + mode: 'horizontal', + openAnimation: 'slide-up', + triggerSubMenuAction: 'click', + }); + + const verticalMenu = React.cloneElement(commonMenu, { + mode: 'vertical', + openAnimation: 'zoom', + }); + + const inlineMenu = React.cloneElement(commonMenu, { + mode: 'inline', + defaultOpenKeys: ['1'], + openAnimation: animation, + }); + + ReactDOM.render(
+

antd menu

+ +
+

horizontal

+ +
{horizontalMenu}
+

horizontal and click

+ +
{horizontalMenu2}
+

vertical

+ +
{verticalMenu}
+

inline

+ +
{inlineMenu}
+
+
, container); +} + +render(reactContainer); diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index d086587e..d80e9da2 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -63,6 +63,7 @@ export class SubMenu extends React.Component { store: PropTypes.object, mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), manualRef: PropTypes.func, + arrowIcon: PropTypes.node, }; static defaultProps = { @@ -74,6 +75,7 @@ export class SubMenu extends React.Component { manualRef: noop, mode: 'vertical', title: '', + arrowIcon: '', }; constructor(props) { @@ -366,6 +368,7 @@ export class SubMenu extends React.Component { prefixCls: props.rootPrefixCls, id: this._menuId, manualRef: this.saveMenuInstance, + arrowIcon: props.arrowIcon, }; const haveRendered = this.haveRendered; @@ -461,6 +464,8 @@ export class SubMenu extends React.Component { }; } + const arrowIcon = props.mode !== 'horizontal' ? props.arrowIcon : null; + const title = (
{props.title} - + + {arrowIcon} +
); const children = this.renderChildren(props.children); diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index 8f96a77e..2eb4fec6 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -101,6 +101,7 @@ export class SubPopupMenu extends React.Component { triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']), inlineIndent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), manualRef: PropTypes.func, + arrowIcon: PropTypes.node, }; static defaultProps = { diff --git a/src/util.js b/src/util.js index eeaae638..386528f6 100644 --- a/src/util.js +++ b/src/util.js @@ -104,4 +104,7 @@ export const menuAllProps = [ 'inlineCollapsed', 'menu', 'theme', + + // arrow icon + 'arrowIcon', ]; From c0379c644fc6f72bd160c488dd265afa011a3ae0 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Tue, 24 Jul 2018 20:11:46 +0800 Subject: [PATCH 02/18] add test cases --- README.md | 2 +- src/SubMenu.jsx | 1 - tests/SubMenu.spec.js | 26 ++++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c381a7e6..592556c8 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,7 @@ ReactDOM.render( arrowIcon React.Node - '' + specific the arrow icon. diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index d80e9da2..9b0e0ef3 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -75,7 +75,6 @@ export class SubMenu extends React.Component { manualRef: noop, mode: 'vertical', title: '', - arrowIcon: '', }; constructor(props) { diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index 00112e28..fc3391d0 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -54,6 +54,32 @@ describe('SubMenu', () => { expect(popupAlign).toEqual({ offset: [0, 15] }); }); + it('should render custom arrow icon correctly.', () => { + const wrapper = mount( + + + 1 + + + ); + + const childText = wrapper.find('.rc-menu-submenu-arrow').first().text(); + expect(childText).toEqual('test-text'); + }); + + it('should Not render custom arrow icon in horizontal mode.', () => { + const wrapper = mount( + + + 1 + + + ); + + const childText = wrapper.find('.rc-menu-submenu-arrow').first().text(); + expect(childText).toEqual(''); + }); + describe('openSubMenuOnMouseEnter and closeSubMenuOnMouseLeave are true', () => { it('toggles when mouse enter and leave', () => { const wrapper = mount(createMenu()); From 9468e295c58b2d52195b149761c1fef7c2b951b6 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Wed, 25 Jul 2018 18:05:40 +0800 Subject: [PATCH 03/18] update demo --- README.md | 2 +- assets/custom-arrow-icon.less | 267 +------------------------------- examples/antd.js | 227 ++++++++++++++++----------- examples/custom-arrow-icon.html | 1 - examples/custom-arrow-icon.js | 162 ------------------- src/SubMenu.jsx | 5 +- tests/SubMenu.spec.js | 8 +- 7 files changed, 150 insertions(+), 522 deletions(-) delete mode 100644 examples/custom-arrow-icon.html delete mode 100644 examples/custom-arrow-icon.js diff --git a/README.md b/README.md index 592556c8..640ba74f 100644 --- a/README.md +++ b/README.md @@ -315,7 +315,7 @@ ReactDOM.render( arrowIcon - React.Node + React.ReactNode specific the arrow icon. diff --git a/assets/custom-arrow-icon.less b/assets/custom-arrow-icon.less index de072a90..7346aa8e 100644 --- a/assets/custom-arrow-icon.less +++ b/assets/custom-arrow-icon.less @@ -1,150 +1,11 @@ @menuPrefixCls: rc-menu; -@font-face { - font-family: 'FontAwesome'; - src: url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0'); - src: url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('https://cdn.bootcss.com/font-awesome/4.2.0/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg'); - font-weight: normal; - font-style: normal; -} - .@{menuPrefixCls} { - outline: none; - margin-bottom: 0; - padding-left: 0; // Override default ul/ol - list-style: none; - border: 1px solid #d9d9d9; - box-shadow: 0 0 4px #d9d9d9; - border-radius: 3px; - color: #666; - - &-hidden { - display: none; - } - - &-collapse { - overflow: hidden; - &-active { - transition: height .3s ease-out; - } - } - - &-item-group-list { - margin: 0; - padding: 0; - } - - &-item-group-title { - color: #999; - line-height: 1.5; - padding: 8px 10px; - border-bottom: 1px solid #dedede; - } - - &-item-active, - &-submenu-active > &-submenu-title { - background-color: #eaf8fe; - } - - &-item-selected { - background-color: #eaf8fe; - // fix chrome render bug - transform: translateZ(0); - } - - &-submenu-selected { - background-color: #eaf8fe; - } - - & > li&-submenu { - padding: 0; - } - - &-horizontal&-sub, - &-vertical&-sub, - &-vertical-left&-sub, - &-vertical-right&-sub { - min-width: 160px; - margin-top: 0; - } - - &-item, &-submenu-title { - margin: 0; - position: relative; - display: block; - padding: 7px 7px 7px 16px; - white-space: nowrap; - - // Disabled state sets text to gray and nukes hover/tab effects - &.@{menuPrefixCls}-item-disabled, &.@{menuPrefixCls}-submenu-disabled { - color: #777 !important; - } - } - & > &-item-divider { - height: 1px; - margin: 1px 0; - overflow: hidden; - padding: 0; - line-height: 0; - background-color: #e5e5e5; - } - - &-submenu { - &-popup { - position: absolute; - } - > .@{menuPrefixCls} { - background-color: #fff; - } - } - - .@{menuPrefixCls}-submenu-title, .@{menuPrefixCls}-item { - .anticon { - width: 14px; - height: 14px; - margin-right: 8px; - top: -1px; - } - } - - &-horizontal { - background-color: #F3F5F7; - border: none; - border-bottom: 1px solid #d9d9d9; - box-shadow: none; - - & > .@{menuPrefixCls}-item, & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { - padding: 15px 20px; - } - - & > .@{menuPrefixCls}-submenu, & > .@{menuPrefixCls}-item { - float: left; - border-bottom: 2px solid transparent; - - &-active { - border-bottom: 2px solid #2db7f5; - background-color: #F3F5F7; - color: #2baee9; - } - } - - &:after { - content: "\20"; - display: block; - height: 0; - clear: both; - } - } - &-vertical, &-vertical-left, &-vertical-right, &-inline { - padding: 12px 0; - & > .@{menuPrefixCls}-item, & > .@{menuPrefixCls}-submenu > .@{menuPrefixCls}-submenu-title { - padding: 12px 8px 12px 24px; - } - .@{menuPrefixCls}-submenu-arrow { + .@{menuPrefixCls}-submenu-arrow-icon { display: inline-block; font: normal normal normal 14px/1 FontAwesome; font-size: inherit; @@ -158,137 +19,15 @@ } } &-inline { - .@{menuPrefixCls}-submenu-arrow { + .@{menuPrefixCls}-submenu-arrow-icon { transform: rotate(90deg); transition: transform .3s; } & .@{menuPrefixCls}-submenu-open > .@{menuPrefixCls}-submenu-title { - .@{menuPrefixCls}-submenu-arrow { + .@{menuPrefixCls}-submenu-arrow-icon { transform: rotate(-90deg); } } } - - &-vertical&-sub, - &-vertical-left&-sub, - &-vertical-right&-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; - } - } - - .effect() { - animation-duration: .3s; - animation-fill-mode: both; - transform-origin: 0 0; - } - - &-open { - - &-slide-up-enter, &-slide-up-appear { - .effect(); - opacity: 0; - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-play-state: paused; - } - - &-slide-up-leave { - .effect(); - opacity: 1; - animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); - animation-play-state: paused; - } - - &-slide-up-enter&-slide-up-enter-active, &-slide-up-appear&-slide-up-appear-active { - animation-name: rcMenuOpenSlideUpIn; - animation-play-state: running; - } - - &-slide-up-leave&-slide-up-leave-active { - animation-name: rcMenuOpenSlideUpOut; - animation-play-state: running; - } - - @keyframes rcMenuOpenSlideUpIn { - 0% { - opacity: 0; - transform-origin: 0% 0%; - transform: scaleY(0); - } - 100% { - opacity: 1; - transform-origin: 0% 0%; - transform: scaleY(1); - } - } - @keyframes rcMenuOpenSlideUpOut { - 0% { - opacity: 1; - transform-origin: 0% 0%; - transform: scaleY(1); - } - 100% { - opacity: 0; - transform-origin: 0% 0%; - transform: scaleY(0); - } - } - - &-zoom-enter, &-zoom-appear { - opacity: 0; - .effect(); - animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); - animation-play-state: paused; - } - - &-zoom-leave { - .effect(); - animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); - animation-play-state: paused; - } - - &-zoom-enter&-zoom-enter-active, &-zoom-appear&-zoom-appear-active { - animation-name: rcMenuOpenZoomIn; - animation-play-state: running; - } - - &-zoom-leave&-zoom-leave-active { - animation-name: rcMenuOpenZoomOut; - animation-play-state: running; - } - - @keyframes rcMenuOpenZoomIn { - 0% { - opacity: 0; - transform: scale(0, 0); - } - 100% { - opacity: 1; - transform: scale(1, 1); - } - } - @keyframes rcMenuOpenZoomOut { - 0% { - - transform: scale(1, 1); - } - 100% { - opacity: 0; - transform: scale(0, 0); - } - } - } - } diff --git a/examples/antd.js b/examples/antd.js index bba71251..4758779f 100644 --- a/examples/antd.js +++ b/examples/antd.js @@ -4,12 +4,28 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; import 'rc-menu/assets/index.less'; +import 'rc-menu/assets/custom-arrow-icon.less'; import animate from 'css-animation'; -function handleClick(info) { - console.log(`clicked ${info.key}`); - console.log(info); -} +const getSvgIcon = () => { + const path = 'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h' + + '-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v' + + '60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91' + + '.5c1.9 0 3.8-0.7 5.2-2L869 536.2c14.7-12.8 14.7-35.6 0-48.4z'; + return ( + + + + + + ); +}; const animation = { enter(node, done) { @@ -49,88 +65,125 @@ const animation = { }, }; -const reactContainer = document.getElementById('__react-content'); - -const nestSubMenu = (offset sub menu 2} key="4" popupOffset={[10, 15]}> - inner inner - - sub menu 3} - > - - inner inner - inner inner2 - - inn - sub menu 4} key="4-2-2"> - inner inner - inner inner2 - - - inner inner - inner inner2 - - -); - -function onOpenChange(value) { - console.log('onOpenChange', value); -} -const commonMenu = ( - sub menu} key="1"> - 0-1 - 0-2 - - {nestSubMenu} - 1 - outer - disabled - outer3 -); - -function render(container) { - const horizontalMenu = React.cloneElement(commonMenu, { - mode: 'horizontal', - // use openTransition for antd - openAnimation: 'slide-up', - }); - - const horizontalMenu2 = React.cloneElement(commonMenu, { - mode: 'horizontal', - openAnimation: 'slide-up', - triggerSubMenuAction: 'click', - }); - - const verticalMenu = React.cloneElement(commonMenu, { - mode: 'vertical', - openAnimation: 'zoom', - }); - - const inlineMenu = React.cloneElement(commonMenu, { - mode: 'inline', - defaultOpenKeys: ['1'], - openAnimation: animation, - }); - - ReactDOM.render(
-

antd menu

- -
-

horizontal

- -
{horizontalMenu}
-

horizontal and click

- -
{horizontalMenu2}
-

vertical

- -
{verticalMenu}
-

inline

- -
{inlineMenu}
-
-
, container); +class Demo extends React.Component { + + state = { + useIcon: false, + }; + + onOpenChange = (value) => { + console.log('onOpenChange', value); + } + + handleClick = (info) => { + console.log(`clicked ${info.key}`); + console.log(info); + } + + handleToggleCustomIcon = () => { + this.setState({ + useIcon: !this.state.useIcon, + }); + } + + renderArrowIcon = () => { + return this.state.useIcon ? getSvgIcon() : undefined; + } + + renderNestSubMenu = (props = {}) => { + return ( + offset sub menu 2} key="4" popupOffset={[10, 15]} {...props}> + inner inner + + sub menu 3} + arrowIcon={this.renderArrowIcon()} + > + + inner inner + inner inner2 + + inn + sub menu 4} key="4-2-2" arrowIcon={this.renderArrowIcon()}> + inner inner + inner inner2 + + + inner inner + inner inner2 + + + + ); + } + + renderCommonMenu = (props = {}) => { + return ( + + sub menu} key="1" arrowIcon={this.renderArrowIcon()}> + 0-1 + 0-2 + + {this.renderNestSubMenu({ arrowIcon: this.renderArrowIcon() })} + 1 + outer + disabled + outer3 + + ); + } + + render() { + const horizontalMenu = this.renderCommonMenu({ + mode: 'horizontal', + // use openTransition for antd + openAnimation: 'slide-up', + }); + + const horizontalMenu2 = this.renderCommonMenu({ + mode: 'horizontal', + openAnimation: 'slide-up', + triggerSubMenuAction: 'click', + }); + + const verticalMenu = this.renderCommonMenu({ + mode: 'vertical', + openAnimation: 'zoom', + }); + + const inlineMenu = this.renderCommonMenu({ + mode: 'inline', + defaultOpenKeys: ['1'], + openAnimation: animation, + }); + + return ( +
+

Antd menu

+
+
+ + is using icon: {this.state.useIcon && 'true' || 'false'} +
+

horizontal

+ +
{horizontalMenu}
+

horizontal and click

+ +
{horizontalMenu2}
+

vertical

+ +
{verticalMenu}
+

inline

+ +
{inlineMenu}
+
+
+ ); + } } -render(reactContainer); +ReactDOM.render(, document.getElementById('__react-content')); diff --git a/examples/custom-arrow-icon.html b/examples/custom-arrow-icon.html deleted file mode 100644 index b3a42524..00000000 --- a/examples/custom-arrow-icon.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/custom-arrow-icon.js b/examples/custom-arrow-icon.js deleted file mode 100644 index c6892366..00000000 --- a/examples/custom-arrow-icon.js +++ /dev/null @@ -1,162 +0,0 @@ -/* eslint no-console:0 */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; -import 'rc-menu/assets/custom-arrow-icon.less'; -import animate from 'css-animation'; - -const getSvgIcon = () => { - const path = 'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h' + - '-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v' + - '60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91' + - '.5c1.9 0 3.8-0.7 5.2-2L869 536.2c14.7-12.8 14.7-35.6 0-48.4z'; - return ( - - - - ); -}; - -function handleClick(info) { - console.log(`clicked ${info.key}`); - console.log(info); -} - -const animation = { - enter(node, done) { - let height; - return animate(node, 'rc-menu-collapse', { - start() { - height = node.offsetHeight; - node.style.height = 0; - }, - active() { - node.style.height = `${height}px`; - }, - end() { - node.style.height = ''; - done(); - }, - }); - }, - - appear() { - return this.enter.apply(this, arguments); - }, - - leave(node, done) { - return animate(node, 'rc-menu-collapse', { - start() { - node.style.height = `${node.offsetHeight}px`; - }, - active() { - node.style.height = 0; - }, - end() { - node.style.height = ''; - done(); - }, - }); - }, -}; - -const reactContainer = document.getElementById('__react-content'); - -const nestSubMenu = ( - offset sub menu 2} - key="4" - popupOffset={[10, 15]} - > - inner inner - - sub menu 3} - arrowIcon={getSvgIcon()} - > - - inner inner - inner inner2 - - inn - sub menu 4} key="4-2-2" arrowIcon={getSvgIcon()}> - inner inner - inner inner2 - - - inner inner - inner inner2 - - - -); - -function onOpenChange(value) { - console.log('onOpenChange', value); -} -const commonMenu = ( - sub menu} key="1" arrowIcon={getSvgIcon()}> - 0-1 - 0-2 - - {nestSubMenu} - 1 - outer - disabled - outer3 -); - -function render(container) { - const horizontalMenu = React.cloneElement(commonMenu, { - mode: 'horizontal', - // use openTransition for antd - openAnimation: 'slide-up', - }); - - const horizontalMenu2 = React.cloneElement(commonMenu, { - mode: 'horizontal', - openAnimation: 'slide-up', - triggerSubMenuAction: 'click', - }); - - const verticalMenu = React.cloneElement(commonMenu, { - mode: 'vertical', - openAnimation: 'zoom', - }); - - const inlineMenu = React.cloneElement(commonMenu, { - mode: 'inline', - defaultOpenKeys: ['1'], - openAnimation: animation, - }); - - ReactDOM.render(
-

antd menu

- -
-

horizontal

- -
{horizontalMenu}
-

horizontal and click

- -
{horizontalMenu2}
-

vertical

- -
{verticalMenu}
-

inline

- -
{inlineMenu}
-
-
, container); -} - -render(reactContainer); diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index 9b0e0ef3..751798f6 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -463,6 +463,7 @@ export class SubMenu extends React.Component { }; } + // expand arrow icon should NOT be displayed in menu with horizontal mode. const arrowIcon = props.mode !== 'horizontal' ? props.arrowIcon : null; const title = ( @@ -478,9 +479,7 @@ export class SubMenu extends React.Component { title={typeof props.title === 'string' ? props.title : undefined} > {props.title} - - {arrowIcon} - + {arrowIcon || } ); const children = this.renderChildren(props.children); diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index fc3391d0..f61ac564 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -63,8 +63,8 @@ describe('SubMenu', () => {
); - const childText = wrapper.find('.rc-menu-submenu-arrow').first().text(); - expect(childText).toEqual('test-text'); + const childText = wrapper.find('.rc-menu-submenu-title').first().text(); + expect(childText).toEqual('submenutest-text'); }); it('should Not render custom arrow icon in horizontal mode.', () => { @@ -76,8 +76,8 @@ describe('SubMenu', () => {
); - const childText = wrapper.find('.rc-menu-submenu-arrow').first().text(); - expect(childText).toEqual(''); + const childText = wrapper.find('.rc-menu-submenu-title').first().text(); + expect(childText).toEqual('submenu'); }); describe('openSubMenuOnMouseEnter and closeSubMenuOnMouseLeave are true', () => { From 00f4c2f36973dd8673106d72d7f11f2906fce9d8 Mon Sep 17 00:00:00 2001 From: Wei Zhu Date: Mon, 30 Jul 2018 12:16:38 +0800 Subject: [PATCH 04/18] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 640ba74f..4116021a 100644 --- a/README.md +++ b/README.md @@ -315,7 +315,7 @@ ReactDOM.render( arrowIcon - React.ReactNode + ReactNode specific the arrow icon. From 4efb5c8b4a22c4f61298a91ba49d3363c9d25795 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Mon, 30 Jul 2018 17:05:29 +0800 Subject: [PATCH 05/18] update demo --- assets/custom-arrow-icon.less | 33 --------------------------------- examples/antd.js | 12 +++++++----- 2 files changed, 7 insertions(+), 38 deletions(-) delete mode 100644 assets/custom-arrow-icon.less diff --git a/assets/custom-arrow-icon.less b/assets/custom-arrow-icon.less deleted file mode 100644 index 7346aa8e..00000000 --- a/assets/custom-arrow-icon.less +++ /dev/null @@ -1,33 +0,0 @@ -@menuPrefixCls: rc-menu; - -.@{menuPrefixCls} { - &-vertical, - &-vertical-left, - &-vertical-right, - &-inline { - .@{menuPrefixCls}-submenu-arrow-icon { - display: inline-block; - font: normal normal normal 14px/1 FontAwesome; - font-size: inherit; - vertical-align: baseline; - text-align: center; - text-transform: none; - text-rendering: auto; - position: absolute; - right: 16px; - line-height: 1.5em; - } - } - &-inline { - .@{menuPrefixCls}-submenu-arrow-icon { - transform: rotate(90deg); - transition: transform .3s; - } - & .@{menuPrefixCls}-submenu-open > .@{menuPrefixCls}-submenu-title { - .@{menuPrefixCls}-submenu-arrow-icon { - transform: rotate(-90deg); - } - } - } -} - diff --git a/examples/antd.js b/examples/antd.js index 4758779f..d1784246 100644 --- a/examples/antd.js +++ b/examples/antd.js @@ -4,16 +4,15 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; import 'rc-menu/assets/index.less'; -import 'rc-menu/assets/custom-arrow-icon.less'; import animate from 'css-animation'; -const getSvgIcon = () => { +const getSvgIcon = (style = {}) => { const path = 'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h' + '-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v' + '60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91' + '.5c1.9 0 3.8-0.7 5.2-2L869 536.2c14.7-12.8 14.7-35.6 0-48.4z'; return ( - + { fill="currentColor" style={{ verticalAlign: '-.125em' }} > - + ); @@ -87,7 +86,10 @@ class Demo extends React.Component { } renderArrowIcon = () => { - return this.state.useIcon ? getSvgIcon() : undefined; + return this.state.useIcon ? getSvgIcon({ + position: 'absolute', + right: '16px', + }) : undefined; } renderNestSubMenu = (props = {}) => { From 474b1e33bfe9e453e6feb388183ddf2e23dc76f9 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Tue, 7 Aug 2018 18:17:31 +0800 Subject: [PATCH 06/18] fix tslint --- src/SubMenu.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index 751798f6..8575c324 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -479,7 +479,7 @@ export class SubMenu extends React.Component { title={typeof props.title === 'string' ? props.title : undefined} > {props.title} - {arrowIcon || } + {arrowIcon || } ); const children = this.renderChildren(props.children); From a6e64344c6d0dabff6258c6af16949594b691fe0 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Thu, 9 Aug 2018 11:44:42 +0800 Subject: [PATCH 07/18] use customIcon --- README.md | 12 +- examples/antd.js | 229 +++++++++++++++----------------------- examples/custom-icon.html | 0 examples/custom-icon.js | 177 +++++++++++++++++++++++++++++ src/Menu.jsx | 2 + src/MenuItem.jsx | 8 +- src/SubMenu.jsx | 19 +++- src/SubPopupMenu.js | 1 - src/util.js | 4 +- tests/MenuItem.spec.js | 21 +++- tests/SubMenu.spec.js | 36 +++--- 11 files changed, 335 insertions(+), 174 deletions(-) create mode 100644 examples/custom-icon.html create mode 100644 examples/custom-icon.js diff --git a/README.md b/README.md index 4116021a..d5c3c24b 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,12 @@ ReactDOM.render( see placements.jsx Describes how the popup menus should be positioned + + customIcon + (props: MenuItemProps | SubMenuProps) => ReactNode + + specific the arrow icon. + @@ -313,12 +319,6 @@ ReactDOM.render( The offset of the popup submenu, in an x, y coordinate array. e.g.: `[0,15]` - - arrowIcon - ReactNode - - specific the arrow icon. - diff --git a/examples/antd.js b/examples/antd.js index d1784246..bba71251 100644 --- a/examples/antd.js +++ b/examples/antd.js @@ -6,25 +6,10 @@ import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; import 'rc-menu/assets/index.less'; import animate from 'css-animation'; -const getSvgIcon = (style = {}) => { - const path = 'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h' + - '-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v' + - '60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91' + - '.5c1.9 0 3.8-0.7 5.2-2L869 536.2c14.7-12.8 14.7-35.6 0-48.4z'; - return ( - - - - - - ); -}; +function handleClick(info) { + console.log(`clicked ${info.key}`); + console.log(info); +} const animation = { enter(node, done) { @@ -64,128 +49,88 @@ const animation = { }, }; -class Demo extends React.Component { - - state = { - useIcon: false, - }; - - onOpenChange = (value) => { - console.log('onOpenChange', value); - } - - handleClick = (info) => { - console.log(`clicked ${info.key}`); - console.log(info); - } - - handleToggleCustomIcon = () => { - this.setState({ - useIcon: !this.state.useIcon, - }); - } - - renderArrowIcon = () => { - return this.state.useIcon ? getSvgIcon({ - position: 'absolute', - right: '16px', - }) : undefined; - } - - renderNestSubMenu = (props = {}) => { - return ( - offset sub menu 2} key="4" popupOffset={[10, 15]} {...props}> - inner inner - - sub menu 3} - arrowIcon={this.renderArrowIcon()} - > - - inner inner - inner inner2 - - inn - sub menu 4} key="4-2-2" arrowIcon={this.renderArrowIcon()}> - inner inner - inner inner2 - - - inner inner - inner inner2 - - - - ); - } - - renderCommonMenu = (props = {}) => { - return ( - - sub menu} key="1" arrowIcon={this.renderArrowIcon()}> - 0-1 - 0-2 - - {this.renderNestSubMenu({ arrowIcon: this.renderArrowIcon() })} - 1 - outer - disabled - outer3 - - ); - } - - render() { - const horizontalMenu = this.renderCommonMenu({ - mode: 'horizontal', - // use openTransition for antd - openAnimation: 'slide-up', - }); - - const horizontalMenu2 = this.renderCommonMenu({ - mode: 'horizontal', - openAnimation: 'slide-up', - triggerSubMenuAction: 'click', - }); - - const verticalMenu = this.renderCommonMenu({ - mode: 'vertical', - openAnimation: 'zoom', - }); - - const inlineMenu = this.renderCommonMenu({ - mode: 'inline', - defaultOpenKeys: ['1'], - openAnimation: animation, - }); - - return ( -
-

Antd menu

-
-
- - is using icon: {this.state.useIcon && 'true' || 'false'} -
-

horizontal

- -
{horizontalMenu}
-

horizontal and click

- -
{horizontalMenu2}
-

vertical

- -
{verticalMenu}
-

inline

- -
{inlineMenu}
-
-
- ); - } +const reactContainer = document.getElementById('__react-content'); + +const nestSubMenu = (offset sub menu 2} key="4" popupOffset={[10, 15]}> + inner inner + + sub menu 3} + > + + inner inner + inner inner2 + + inn + sub menu 4} key="4-2-2"> + inner inner + inner inner2 + + + inner inner + inner inner2 + + +); + +function onOpenChange(value) { + console.log('onOpenChange', value); +} +const commonMenu = ( + sub menu} key="1"> + 0-1 + 0-2 + + {nestSubMenu} + 1 + outer + disabled + outer3 +); + +function render(container) { + const horizontalMenu = React.cloneElement(commonMenu, { + mode: 'horizontal', + // use openTransition for antd + openAnimation: 'slide-up', + }); + + const horizontalMenu2 = React.cloneElement(commonMenu, { + mode: 'horizontal', + openAnimation: 'slide-up', + triggerSubMenuAction: 'click', + }); + + const verticalMenu = React.cloneElement(commonMenu, { + mode: 'vertical', + openAnimation: 'zoom', + }); + + const inlineMenu = React.cloneElement(commonMenu, { + mode: 'inline', + defaultOpenKeys: ['1'], + openAnimation: animation, + }); + + ReactDOM.render(
+

antd menu

+ +
+

horizontal

+ +
{horizontalMenu}
+

horizontal and click

+ +
{horizontalMenu2}
+

vertical

+ +
{verticalMenu}
+

inline

+ +
{inlineMenu}
+
+
, container); } -ReactDOM.render(, document.getElementById('__react-content')); +render(reactContainer); diff --git a/examples/custom-icon.html b/examples/custom-icon.html new file mode 100644 index 00000000..e69de29b diff --git a/examples/custom-icon.js b/examples/custom-icon.js new file mode 100644 index 00000000..fa59811f --- /dev/null +++ b/examples/custom-icon.js @@ -0,0 +1,177 @@ +/* eslint no-console:0 */ + +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; +import 'rc-menu/assets/index.less'; +import animate from 'css-animation'; + +const getSvgIcon = (style = {}) => { + const path = 'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h' + + '-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v' + + '60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91' + + '.5c1.9 0 3.8-0.7 5.2-2L869 536.2c14.7-12.8 14.7-35.6 0-48.4z'; + return ( + + + + + + ); +}; + +const animation = { + enter(node, done) { + let height; + return animate(node, 'rc-menu-collapse', { + start() { + height = node.offsetHeight; + node.style.height = 0; + }, + active() { + node.style.height = `${height}px`; + }, + end() { + node.style.height = ''; + done(); + }, + }); + }, + + appear() { + return this.enter.apply(this, arguments); + }, + + leave(node, done) { + return animate(node, 'rc-menu-collapse', { + start() { + node.style.height = `${node.offsetHeight}px`; + }, + active() { + node.style.height = 0; + }, + end() { + node.style.height = ''; + done(); + }, + }); + }, +}; + +class Demo extends React.Component { + + onOpenChange = (value) => { + console.log('onOpenChange', value); + } + + handleClick = (info) => { + console.log(`clicked ${info.key}`); + console.log(info); + } + + renderNestSubMenu = (props = {}) => { + return ( + offset sub menu 2} key="4" popupOffset={[10, 15]} {...props}> + inner inner + + sub menu 3} + > + + inner inner + inner inner2 + + inn + sub menu 4} key="4-2-2"> + inner inner + inner inner2 + + + inner inner + inner inner2 + + + + ); + } + + renderCommonMenu = (props = {}) => { + return ( + + sub menu} key="1"> + 0-1 + 0-2 + + {this.renderNestSubMenu()} + 1 + outer + disabled + outer3 + + ); + } + + render() { + const verticalMenu = this.renderCommonMenu({ + mode: 'vertical', + openAnimation: 'zoom', + customIcon(props) { + if (props.isSubMenu) { + return getSvgIcon({ + position: 'absolute', + right: '1rem', + color: 'lightblue', + }); + } + return getSvgIcon({ + position: 'absolute', + right: '1rem', + }); + }, + }); + + const inlineMenu = this.renderCommonMenu({ + mode: 'inline', + defaultOpenKeys: ['1'], + openAnimation: animation, + customIcon(props) { + if (props.isSubMenu) { + return getSvgIcon({ + position: 'absolute', + right: '1rem', + color: 'lightblue', + transition: 'transform .2s', + transform: `rotate(${props.isOpen ? 90 : 0}deg)`, + }); + } + return getSvgIcon({ + position: 'absolute', + right: '1rem', + }); + }, + }); + + return ( +
+

Antd menu - custom icon

+
+

vertical

+ +
{verticalMenu}
+

inline

+ +
{inlineMenu}
+
+
+ ); + } +} + +ReactDOM.render(, document.getElementById('__react-content')); diff --git a/src/Menu.jsx b/src/Menu.jsx index 5d6d6df6..877ada14 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -32,6 +32,7 @@ class Menu extends React.Component { activeKey: PropTypes.string, prefixCls: PropTypes.string, builtinPlacements: PropTypes.object, + customIcon: PropTypes.func, }; static defaultProps = { @@ -70,6 +71,7 @@ class Menu extends React.Component { selectedKeys, openKeys, activeKey: { '0-menu-': getActiveKey(props, props.activeKey) }, + customIcon: props.customIcon, }); } diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index beec9d6f..b1c471a2 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -30,6 +30,7 @@ export class MenuItem extends React.Component { multiple: PropTypes.bool, isSelected: PropTypes.bool, manualRef: PropTypes.func, + customIcon: PropTypes.func, }; static defaultProps = { @@ -182,6 +183,9 @@ export class MenuItem extends React.Component { style.paddingLeft = props.inlineIndent * props.level; } menuAllProps.forEach(key => delete props[key]); + + const icon = typeof this.props.customIcon === 'function' ? + React.createElement(this.props.customIcon, this.props) : null; return (
  • {props.children} + {icon}
  • ); } @@ -197,9 +202,10 @@ export class MenuItem extends React.Component { MenuItem.isMenuItem = true; -const connected = connect(({ activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ +const connected = connect(({ activeKey, selectedKeys, customIcon }, { eventKey, subMenuKey }) => ({ active: activeKey[subMenuKey] === eventKey, isSelected: selectedKeys.indexOf(eventKey) !== -1, + customIcon, }))(MenuItem); export default connected; diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index 8575c324..cca1c3a1 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -63,7 +63,7 @@ export class SubMenu extends React.Component { store: PropTypes.object, mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), manualRef: PropTypes.func, - arrowIcon: PropTypes.node, + customIcon: PropTypes.func, }; static defaultProps = { @@ -367,7 +367,6 @@ export class SubMenu extends React.Component { prefixCls: props.rootPrefixCls, id: this._menuId, manualRef: this.saveMenuInstance, - arrowIcon: props.arrowIcon, }; const haveRendered = this.haveRendered; @@ -463,8 +462,12 @@ export class SubMenu extends React.Component { }; } - // expand arrow icon should NOT be displayed in menu with horizontal mode. - const arrowIcon = props.mode !== 'horizontal' ? props.arrowIcon : null; + // expand custom icon should NOT be displayed in menu with horizontal mode. + let icon = null; + if (props.mode !== 'horizontal') { + icon = typeof this.props.customIcon === 'function' ? + React.createElement(this.props.customIcon, { ...this.props, isSubMenu: true }) : null; + } const title = (
    {props.title} - {arrowIcon || } + {icon || }
    ); const children = this.renderChildren(props.children); @@ -534,10 +537,14 @@ export class SubMenu extends React.Component { } } -const connected = connect(({ openKeys, activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ +const connected = connect(( + { openKeys, activeKey, selectedKeys, customIcon }, + { eventKey, subMenuKey } +) => ({ isOpen: openKeys.indexOf(eventKey) > -1, active: activeKey[subMenuKey] === eventKey, selectedKeys, + customIcon, }))(SubMenu); connected.isSubMenu = true; diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index 2eb4fec6..8f96a77e 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -101,7 +101,6 @@ export class SubPopupMenu extends React.Component { triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']), inlineIndent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), manualRef: PropTypes.func, - arrowIcon: PropTypes.node, }; static defaultProps = { diff --git a/src/util.js b/src/util.js index 386528f6..e509a4b0 100644 --- a/src/util.js +++ b/src/util.js @@ -104,7 +104,5 @@ export const menuAllProps = [ 'inlineCollapsed', 'menu', 'theme', - - // arrow icon - 'arrowIcon', + 'customIcon', ]; diff --git a/tests/MenuItem.spec.js b/tests/MenuItem.spec.js index d4d1cef1..2d9b1e8f 100644 --- a/tests/MenuItem.spec.js +++ b/tests/MenuItem.spec.js @@ -7,12 +7,31 @@ import Menu, { MenuItem, MenuItemGroup, SubMenu } from '../src'; import { MenuItem as NakedMenuItem } from '../src/MenuItem'; describe('MenuItem', () => { + function customIcon({ isSubMenu }) { + if (isSubMenu) { + return SubMenuIcon; + } + return MenuItemIcon; + } + + describe('custom icon', () => { + it('should render custom arrow icon correctly.', () => { + const wrapper = mount( + + 1 + + ); + const menuItemText = wrapper.find('.rc-menu-item').first().text(); + expect(menuItemText).toEqual('1MenuItemIcon'); + }); + }); + describe('disabled', () => { it('can not be active by key down', () => { const wrapper = mount( 1 - + 2 ); diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index f61ac564..64d74f3f 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -29,6 +29,13 @@ describe('SubMenu', () => { ); } + function customIcon({ isSubMenu }) { + if (isSubMenu) { + return SubMenuIcon; + } + return MenuItemIcon; + } + it('don\'t show submenu when disabled', () => { const wrapper = mount( @@ -56,21 +63,22 @@ describe('SubMenu', () => { it('should render custom arrow icon correctly.', () => { const wrapper = mount( - - + + 1 + 2 ); - const childText = wrapper.find('.rc-menu-submenu-title').first().text(); - expect(childText).toEqual('submenutest-text'); + const subMenuText = wrapper.find('.rc-menu-submenu-title').first().text(); + expect(subMenuText).toEqual('submenuSubMenuIcon'); }); it('should Not render custom arrow icon in horizontal mode.', () => { const wrapper = mount( - + 1 @@ -221,8 +229,8 @@ describe('SubMenu', () => { const titles = wrapper.find('.rc-menu-submenu-title'); titles.first().simulate('mouseEnter') - .simulate('keyDown', { keyCode: KeyCode.LEFT }) - .simulate('keyDown', { keyCode: KeyCode.DOWN }); + .simulate('keyDown', { keyCode: KeyCode.LEFT }) + .simulate('keyDown', { keyCode: KeyCode.DOWN }); expect(wrapper.find('.rc-menu-submenu').last().is('.rc-menu-submenu-active')).toBe(true); titles.last().simulate('keyDown', { keyCode: KeyCode.UP }); @@ -236,12 +244,12 @@ describe('SubMenu', () => { // testing keydown event after submenu is closed and then opened again firstItem.simulate('mouseEnter') - .simulate('keyDown', { keyCode: KeyCode.RIGHT }) - .simulate('keyDown', { keyCode: KeyCode.LEFT }) - .simulate('keyDown', { keyCode: KeyCode.RIGHT }) - .simulate('keyDown', { keyCode: KeyCode.DOWN }) - .simulate('keyDown', { keyCode: KeyCode.DOWN }) - .simulate('keyDown', { keyCode: KeyCode.DOWN }); + .simulate('keyDown', { keyCode: KeyCode.RIGHT }) + .simulate('keyDown', { keyCode: KeyCode.LEFT }) + .simulate('keyDown', { keyCode: KeyCode.RIGHT }) + .simulate('keyDown', { keyCode: KeyCode.DOWN }) + .simulate('keyDown', { keyCode: KeyCode.DOWN }) + .simulate('keyDown', { keyCode: KeyCode.DOWN }); expect( wrapper @@ -319,7 +327,7 @@ describe('SubMenu', () => { }); describe('submenu animation', () => { - const appear = () => {}; + const appear = () => { }; it('should animate with transition class', () => { const wrapper = mount(createMenu({ From f4d465067956973c0cabf06c0e629c8bbc38b04c Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Thu, 9 Aug 2018 17:16:49 +0800 Subject: [PATCH 08/18] customIcon => itemIcon --- README.md | 18 +++++++++++++--- examples/custom-icon.js | 48 ++++++++++++++++------------------------- src/Menu.jsx | 3 +-- src/MenuItem.jsx | 9 ++++---- src/SubMenu.jsx | 10 ++++----- src/SubPopupMenu.js | 2 ++ src/util.js | 2 +- tests/MenuItem.spec.js | 24 ++++++++++++++++----- tests/SubMenu.spec.js | 6 +++--- 9 files changed, 69 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index d5c3c24b..5c9c5cd8 100644 --- a/README.md +++ b/README.md @@ -193,10 +193,10 @@ ReactDOM.render( Describes how the popup menus should be positioned - customIcon - (props: MenuItemProps | SubMenuProps) => ReactNode + itemIcon + (props: MenuItemProps) => ReactNode - specific the arrow icon. + specific the menu item icon. @@ -243,6 +243,12 @@ ReactDOM.render( + + itemIcon + (props: MenuItemProps) => ReactNode + + specific the menu item icon. + @@ -319,6 +325,12 @@ ReactDOM.render( The offset of the popup submenu, in an x, y coordinate array. e.g.: `[0,15]` + + itemIcon + (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode + + specific the menu item icon. + diff --git a/examples/custom-icon.js b/examples/custom-icon.js index fa59811f..2c7ec867 100644 --- a/examples/custom-icon.js +++ b/examples/custom-icon.js @@ -26,6 +26,22 @@ const getSvgIcon = (style = {}) => { ); }; +function itemIcon(props) { + if (props.isSubMenu) { + return getSvgIcon({ + position: 'absolute', + right: '1rem', + color: 'lightblue', + transform: `rotate(${props.isOpen ? 90 : 0}deg)`, + }); + } + return getSvgIcon({ + position: 'absolute', + right: '1rem', + color: props.isSelected ? 'pink' : 'inherit', + }); +} + const animation = { enter(node, done) { let height; @@ -105,7 +121,7 @@ class Demo extends React.Component { renderCommonMenu = (props = {}) => { return ( - sub menu} key="1"> + sub menu} itemIcon={() => '123'} key="1"> 0-1 0-2 @@ -122,40 +138,14 @@ class Demo extends React.Component { const verticalMenu = this.renderCommonMenu({ mode: 'vertical', openAnimation: 'zoom', - customIcon(props) { - if (props.isSubMenu) { - return getSvgIcon({ - position: 'absolute', - right: '1rem', - color: 'lightblue', - }); - } - return getSvgIcon({ - position: 'absolute', - right: '1rem', - }); - }, + itemIcon, }); const inlineMenu = this.renderCommonMenu({ mode: 'inline', defaultOpenKeys: ['1'], openAnimation: animation, - customIcon(props) { - if (props.isSubMenu) { - return getSvgIcon({ - position: 'absolute', - right: '1rem', - color: 'lightblue', - transition: 'transform .2s', - transform: `rotate(${props.isOpen ? 90 : 0}deg)`, - }); - } - return getSvgIcon({ - position: 'absolute', - right: '1rem', - }); - }, + itemIcon, }); return ( diff --git a/src/Menu.jsx b/src/Menu.jsx index 877ada14..ba526fbc 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -32,7 +32,7 @@ class Menu extends React.Component { activeKey: PropTypes.string, prefixCls: PropTypes.string, builtinPlacements: PropTypes.object, - customIcon: PropTypes.func, + itemIcon: PropTypes.func, }; static defaultProps = { @@ -71,7 +71,6 @@ class Menu extends React.Component { selectedKeys, openKeys, activeKey: { '0-menu-': getActiveKey(props, props.activeKey) }, - customIcon: props.customIcon, }); } diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index b1c471a2..a464249b 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -30,7 +30,7 @@ export class MenuItem extends React.Component { multiple: PropTypes.bool, isSelected: PropTypes.bool, manualRef: PropTypes.func, - customIcon: PropTypes.func, + itemIcon: PropTypes.func, }; static defaultProps = { @@ -184,8 +184,8 @@ export class MenuItem extends React.Component { } menuAllProps.forEach(key => delete props[key]); - const icon = typeof this.props.customIcon === 'function' ? - React.createElement(this.props.customIcon, this.props) : null; + const icon = typeof this.props.itemIcon === 'function' ? + React.createElement(this.props.itemIcon, this.props) : null; return (
  • ({ +const connected = connect(({ activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ active: activeKey[subMenuKey] === eventKey, isSelected: selectedKeys.indexOf(eventKey) !== -1, - customIcon, }))(MenuItem); export default connected; diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index cca1c3a1..cb98532a 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -63,7 +63,7 @@ export class SubMenu extends React.Component { store: PropTypes.object, mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), manualRef: PropTypes.func, - customIcon: PropTypes.func, + itemIcon: PropTypes.func, }; static defaultProps = { @@ -367,6 +367,7 @@ export class SubMenu extends React.Component { prefixCls: props.rootPrefixCls, id: this._menuId, manualRef: this.saveMenuInstance, + itemIcon: props.itemIcon, }; const haveRendered = this.haveRendered; @@ -465,8 +466,8 @@ export class SubMenu extends React.Component { // expand custom icon should NOT be displayed in menu with horizontal mode. let icon = null; if (props.mode !== 'horizontal') { - icon = typeof this.props.customIcon === 'function' ? - React.createElement(this.props.customIcon, { ...this.props, isSubMenu: true }) : null; + icon = typeof this.props.itemIcon === 'function' ? + React.createElement(this.props.itemIcon, { ...this.props, isSubMenu: true }) : null; } const title = ( @@ -538,13 +539,12 @@ export class SubMenu extends React.Component { } const connected = connect(( - { openKeys, activeKey, selectedKeys, customIcon }, + { openKeys, activeKey, selectedKeys }, { eventKey, subMenuKey } ) => ({ isOpen: openKeys.indexOf(eventKey) > -1, active: activeKey[subMenuKey] === eventKey, selectedKeys, - customIcon, }))(SubMenu); connected.isSubMenu = true; diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index 8f96a77e..a413596e 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -101,6 +101,7 @@ export class SubPopupMenu extends React.Component { triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']), inlineIndent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), manualRef: PropTypes.func, + itemIcon: PropTypes.func, }; static defaultProps = { @@ -286,6 +287,7 @@ export class SubPopupMenu extends React.Component { onDeselect: this.onDeselect, onSelect: this.onSelect, builtinPlacements: props.builtinPlacements, + itemIcon: childProps.itemIcon || this.props.itemIcon, ...extraProps, }; if (props.mode === 'inline') { diff --git a/src/util.js b/src/util.js index e509a4b0..6f842a0c 100644 --- a/src/util.js +++ b/src/util.js @@ -104,5 +104,5 @@ export const menuAllProps = [ 'inlineCollapsed', 'menu', 'theme', - 'customIcon', + 'itemIcon', ]; diff --git a/tests/MenuItem.spec.js b/tests/MenuItem.spec.js index 2d9b1e8f..f7d216e7 100644 --- a/tests/MenuItem.spec.js +++ b/tests/MenuItem.spec.js @@ -7,22 +7,36 @@ import Menu, { MenuItem, MenuItemGroup, SubMenu } from '../src'; import { MenuItem as NakedMenuItem } from '../src/MenuItem'; describe('MenuItem', () => { - function customIcon({ isSubMenu }) { + const subMenuIconText = 'SubMenuIcon'; + const menuItemIconText = 'MenuItemIcon'; + function itemIcon({ isSubMenu }) { if (isSubMenu) { - return SubMenuIcon; + return {subMenuIconText}; } - return MenuItemIcon; + return {menuItemIconText}; } describe('custom icon', () => { it('should render custom arrow icon correctly.', () => { const wrapper = mount( - + 1 ); const menuItemText = wrapper.find('.rc-menu-item').first().text(); - expect(menuItemText).toEqual('1MenuItemIcon'); + expect(menuItemText).toEqual(`1${menuItemIconText}`); + }); + + it('should render custom arrow icon correctly (with children props).', () => { + const targetText = 'target'; + const wrapper = mount( + + {targetText}}>1 + 2 + + ); + const menuItemText = wrapper.find('.rc-menu-item').first().text(); + expect(menuItemText).toEqual(`1${targetText}`); }); }); diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index 64d74f3f..5fe09f80 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -29,7 +29,7 @@ describe('SubMenu', () => { ); } - function customIcon({ isSubMenu }) { + function itemIcon({ isSubMenu }) { if (isSubMenu) { return SubMenuIcon; } @@ -63,7 +63,7 @@ describe('SubMenu', () => { it('should render custom arrow icon correctly.', () => { const wrapper = mount( - + 1 2 @@ -78,7 +78,7 @@ describe('SubMenu', () => { it('should Not render custom arrow icon in horizontal mode.', () => { const wrapper = mount( - + 1 From b8f7e6137ae472118d57d400a75be943abafca65 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Fri, 10 Aug 2018 15:15:09 +0800 Subject: [PATCH 09/18] split itemIcon & expandIcon --- README.md | 12 ++++++++++++ examples/custom-icon.js | 21 ++++++++++++--------- src/Menu.jsx | 1 + src/SubMenu.jsx | 6 ++++-- src/SubPopupMenu.js | 2 ++ src/util.js | 1 + tests/MenuItem.spec.js | 13 +++++++------ tests/SubMenu.spec.js | 13 +++++++------ 8 files changed, 46 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 5c9c5cd8..ccdb6fdc 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,12 @@ ReactDOM.render( specific the menu item icon. + + expandIcon + (props: SubMenuProps) => ReactNode + + specific the menu item icon. + @@ -325,6 +331,12 @@ ReactDOM.render( The offset of the popup submenu, in an x, y coordinate array. e.g.: `[0,15]` + + expandIcon + (props: SubMenuProps) => ReactNode + + specific the menu item icon. + itemIcon (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode diff --git a/examples/custom-icon.js b/examples/custom-icon.js index 2c7ec867..9fc457e0 100644 --- a/examples/custom-icon.js +++ b/examples/custom-icon.js @@ -27,14 +27,6 @@ const getSvgIcon = (style = {}) => { }; function itemIcon(props) { - if (props.isSubMenu) { - return getSvgIcon({ - position: 'absolute', - right: '1rem', - color: 'lightblue', - transform: `rotate(${props.isOpen ? 90 : 0}deg)`, - }); - } return getSvgIcon({ position: 'absolute', right: '1rem', @@ -42,6 +34,15 @@ function itemIcon(props) { }); } +function expandIcon(props) { + return getSvgIcon({ + position: 'absolute', + right: '1rem', + color: 'lightblue', + transform: `rotate(${props.isOpen ? 90 : 0}deg)`, + }); +} + const animation = { enter(node, done) { let height; @@ -121,7 +122,7 @@ class Demo extends React.Component { renderCommonMenu = (props = {}) => { return ( - sub menu} itemIcon={() => '123'} key="1"> + sub menu} key="1"> 0-1 0-2 @@ -139,6 +140,7 @@ class Demo extends React.Component { mode: 'vertical', openAnimation: 'zoom', itemIcon, + expandIcon, }); const inlineMenu = this.renderCommonMenu({ @@ -146,6 +148,7 @@ class Demo extends React.Component { defaultOpenKeys: ['1'], openAnimation: animation, itemIcon, + expandIcon, }); return ( diff --git a/src/Menu.jsx b/src/Menu.jsx index ba526fbc..f3eb0884 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -33,6 +33,7 @@ class Menu extends React.Component { prefixCls: PropTypes.string, builtinPlacements: PropTypes.object, itemIcon: PropTypes.func, + expandIcon: PropTypes.func, }; static defaultProps = { diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index cb98532a..fbc0057a 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -63,6 +63,7 @@ export class SubMenu extends React.Component { store: PropTypes.object, mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']), manualRef: PropTypes.func, + expandIcon: PropTypes.func, itemIcon: PropTypes.func, }; @@ -368,6 +369,7 @@ export class SubMenu extends React.Component { id: this._menuId, manualRef: this.saveMenuInstance, itemIcon: props.itemIcon, + expandIcon: props.expandIcon, }; const haveRendered = this.haveRendered; @@ -466,8 +468,8 @@ export class SubMenu extends React.Component { // expand custom icon should NOT be displayed in menu with horizontal mode. let icon = null; if (props.mode !== 'horizontal') { - icon = typeof this.props.itemIcon === 'function' ? - React.createElement(this.props.itemIcon, { ...this.props, isSubMenu: true }) : null; + icon = typeof this.props.expandIcon === 'function' ? + React.createElement(this.props.expandIcon, { ...this.props, isSubMenu: true }) : null; } const title = ( diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index a413596e..5ad2e15d 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -102,6 +102,7 @@ export class SubPopupMenu extends React.Component { inlineIndent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), manualRef: PropTypes.func, itemIcon: PropTypes.func, + expandIcon: PropTypes.func, }; static defaultProps = { @@ -288,6 +289,7 @@ export class SubPopupMenu extends React.Component { onSelect: this.onSelect, builtinPlacements: props.builtinPlacements, itemIcon: childProps.itemIcon || this.props.itemIcon, + expandIcon: childProps.expandIcon || this.props.expandIcon, ...extraProps, }; if (props.mode === 'inline') { diff --git a/src/util.js b/src/util.js index 6f842a0c..51f8a1c9 100644 --- a/src/util.js +++ b/src/util.js @@ -105,4 +105,5 @@ export const menuAllProps = [ 'menu', 'theme', 'itemIcon', + 'expandIcon', ]; diff --git a/tests/MenuItem.spec.js b/tests/MenuItem.spec.js index f7d216e7..b80fa01b 100644 --- a/tests/MenuItem.spec.js +++ b/tests/MenuItem.spec.js @@ -9,17 +9,18 @@ import { MenuItem as NakedMenuItem } from '../src/MenuItem'; describe('MenuItem', () => { const subMenuIconText = 'SubMenuIcon'; const menuItemIconText = 'MenuItemIcon'; - function itemIcon({ isSubMenu }) { - if (isSubMenu) { - return {subMenuIconText}; - } + function itemIcon() { return {menuItemIconText}; } + function expandIcon() { + return {subMenuIconText}; + } + describe('custom icon', () => { it('should render custom arrow icon correctly.', () => { const wrapper = mount( - + 1 ); @@ -30,7 +31,7 @@ describe('MenuItem', () => { it('should render custom arrow icon correctly (with children props).', () => { const targetText = 'target'; const wrapper = mount( - + {targetText}}>1 2 diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index 5fe09f80..b7dfdb0d 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -29,13 +29,14 @@ describe('SubMenu', () => { ); } - function itemIcon({ isSubMenu }) { - if (isSubMenu) { - return SubMenuIcon; - } + function itemIcon() { return MenuItemIcon; } + function expandIcon() { + return SubMenuIcon; + } + it('don\'t show submenu when disabled', () => { const wrapper = mount( @@ -63,7 +64,7 @@ describe('SubMenu', () => { it('should render custom arrow icon correctly.', () => { const wrapper = mount( - + 1 2 @@ -78,7 +79,7 @@ describe('SubMenu', () => { it('should Not render custom arrow icon in horizontal mode.', () => { const wrapper = mount( - + 1 From b421fa49b093d068045337ca70d3278ec1b45b4d Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Mon, 13 Aug 2018 11:04:10 +0800 Subject: [PATCH 10/18] support getting icon from context --- examples/custom-icon.js | 54 ++++++++++++++++++++++++++++++++++++++--- src/Menu.jsx | 11 +++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/examples/custom-icon.js b/examples/custom-icon.js index 9fc457e0..c05274bc 100644 --- a/examples/custom-icon.js +++ b/examples/custom-icon.js @@ -1,12 +1,19 @@ /* eslint no-console:0 */ - import * as React from 'react'; import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; import 'rc-menu/assets/index.less'; import animate from 'css-animation'; -const getSvgIcon = (style = {}) => { +const getSvgIcon = (style = {}, text) => { + if (text) { + return ( + + {text} + + ); + } const path = 'M869 487.8L491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h' + '-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v' + '60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91' + @@ -83,6 +90,34 @@ const animation = { class Demo extends React.Component { + static childContextTypes = { + expandIcon: PropTypes.func, + itemIcon: PropTypes.func, + } + + state = { + useContext: false, + }; + + getChildContext() { + if (!this.state.useContext) { + return {}; + } + return { + expandIcon: (props) => getSvgIcon({ + position: 'absolute', + right: '1rem', + color: 'lightblue', + transform: `rotate(${props.isOpen ? 90 : 0}deg)`, + }, '>>'), + itemIcon: (props) => getSvgIcon({ + position: 'absolute', + right: '1rem', + color: props.isSelected ? 'pink' : 'inherit', + }, '--'), + }; + } + onOpenChange = (value) => { console.log('onOpenChange', value); } @@ -92,6 +127,12 @@ class Demo extends React.Component { console.log(info); } + toggleContext = () => { + this.setState({ + useContext: !this.state.useContext, + }); + } + renderNestSubMenu = (props = {}) => { return ( offset sub menu 2} key="4" popupOffset={[10, 15]} {...props}> @@ -154,12 +195,17 @@ class Demo extends React.Component { return (

    Antd menu - custom icon

    +
    + use context +

    vertical

    -
    {verticalMenu}

    inline

    -
    {inlineMenu}
    diff --git a/src/Menu.jsx b/src/Menu.jsx index f3eb0884..34185b6e 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -36,6 +36,11 @@ class Menu extends React.Component { expandIcon: PropTypes.func, }; + static contextTypes = { + itemIcon: PropTypes.func, + expandIcon: PropTypes.func, + } + static defaultProps = { selectable: true, onClick: noop, @@ -197,6 +202,10 @@ class Menu extends React.Component { render() { let { ...props } = this.props; + const { + itemIcon: itemIconFromCtx, + expandIcon: expandIconFromCtx, + } = this.context; props.className += ` ${props.prefixCls}-root`; props = { ...props, @@ -206,6 +215,8 @@ class Menu extends React.Component { onSelect: this.onSelect, openTransitionName: this.getOpenTransitionName(), parentMenu: this, + itemIcon: itemIconFromCtx || props.itemIcon, + expandIcon: expandIconFromCtx || props.expandIcon, }; return ( From fc36cd9761ef2b207c0e4d58d6e6d186d1f2f56b Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Mon, 13 Aug 2018 17:56:37 +0800 Subject: [PATCH 11/18] remove context --- README.md | 4 ++-- examples/custom-icon.js | 42 ----------------------------------------- src/Menu.jsx | 15 ++------------- src/MenuItem.jsx | 9 +++++---- src/SubMenu.jsx | 18 ++++++++++-------- src/SubPopupMenu.js | 4 ++-- tests/MenuItem.spec.js | 2 +- tests/SubMenu.spec.js | 35 +++++++++++++++++++--------------- 8 files changed, 42 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index ccdb6fdc..bfc5fc62 100644 --- a/README.md +++ b/README.md @@ -194,13 +194,13 @@ ReactDOM.render( itemIcon - (props: MenuItemProps) => ReactNode + ReactNode | (props: MenuItemProps) => ReactNode specific the menu item icon. expandIcon - (props: SubMenuProps) => ReactNode + ReactNode | (props: SubMenuProps) => ReactNode specific the menu item icon. diff --git a/examples/custom-icon.js b/examples/custom-icon.js index c05274bc..85315490 100644 --- a/examples/custom-icon.js +++ b/examples/custom-icon.js @@ -1,7 +1,6 @@ /* eslint no-console:0 */ import * as React from 'react'; import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; import Menu, { SubMenu, Item as MenuItem, Divider } from 'rc-menu'; import 'rc-menu/assets/index.less'; import animate from 'css-animation'; @@ -90,34 +89,6 @@ const animation = { class Demo extends React.Component { - static childContextTypes = { - expandIcon: PropTypes.func, - itemIcon: PropTypes.func, - } - - state = { - useContext: false, - }; - - getChildContext() { - if (!this.state.useContext) { - return {}; - } - return { - expandIcon: (props) => getSvgIcon({ - position: 'absolute', - right: '1rem', - color: 'lightblue', - transform: `rotate(${props.isOpen ? 90 : 0}deg)`, - }, '>>'), - itemIcon: (props) => getSvgIcon({ - position: 'absolute', - right: '1rem', - color: props.isSelected ? 'pink' : 'inherit', - }, '--'), - }; - } - onOpenChange = (value) => { console.log('onOpenChange', value); } @@ -127,12 +98,6 @@ class Demo extends React.Component { console.log(info); } - toggleContext = () => { - this.setState({ - useContext: !this.state.useContext, - }); - } - renderNestSubMenu = (props = {}) => { return ( offset sub menu 2} key="4" popupOffset={[10, 15]} {...props}> @@ -195,13 +160,6 @@ class Demo extends React.Component { return (

    Antd menu - custom icon

    -
    - use context -

    vertical

    {verticalMenu}
    diff --git a/src/Menu.jsx b/src/Menu.jsx index 34185b6e..c3bcfa0d 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -32,15 +32,10 @@ class Menu extends React.Component { activeKey: PropTypes.string, prefixCls: PropTypes.string, builtinPlacements: PropTypes.object, - itemIcon: PropTypes.func, - expandIcon: PropTypes.func, + itemIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + expandIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), }; - static contextTypes = { - itemIcon: PropTypes.func, - expandIcon: PropTypes.func, - } - static defaultProps = { selectable: true, onClick: noop, @@ -202,10 +197,6 @@ class Menu extends React.Component { render() { let { ...props } = this.props; - const { - itemIcon: itemIconFromCtx, - expandIcon: expandIconFromCtx, - } = this.context; props.className += ` ${props.prefixCls}-root`; props = { ...props, @@ -215,8 +206,6 @@ class Menu extends React.Component { onSelect: this.onSelect, openTransitionName: this.getOpenTransitionName(), parentMenu: this, - itemIcon: itemIconFromCtx || props.itemIcon, - expandIcon: expandIconFromCtx || props.expandIcon, }; return ( diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index a464249b..194caada 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -30,7 +30,7 @@ export class MenuItem extends React.Component { multiple: PropTypes.bool, isSelected: PropTypes.bool, manualRef: PropTypes.func, - itemIcon: PropTypes.func, + itemIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), }; static defaultProps = { @@ -183,9 +183,10 @@ export class MenuItem extends React.Component { style.paddingLeft = props.inlineIndent * props.level; } menuAllProps.forEach(key => delete props[key]); - - const icon = typeof this.props.itemIcon === 'function' ? - React.createElement(this.props.itemIcon, this.props) : null; + let icon = this.props.itemIcon; + if (typeof this.props.itemIcon === 'function') { + icon = React.createElement(this.props.itemIcon, this.props); + } return (
  • ({ +const connected = connect(({ openKeys, activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ isOpen: openKeys.indexOf(eventKey) > -1, active: activeKey[subMenuKey] === eventKey, selectedKeys, diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index 5ad2e15d..6ed02679 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -101,8 +101,8 @@ export class SubPopupMenu extends React.Component { triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']), inlineIndent: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), manualRef: PropTypes.func, - itemIcon: PropTypes.func, - expandIcon: PropTypes.func, + itemIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + expandIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), }; static defaultProps = { diff --git a/tests/MenuItem.spec.js b/tests/MenuItem.spec.js index b80fa01b..b0b9a883 100644 --- a/tests/MenuItem.spec.js +++ b/tests/MenuItem.spec.js @@ -46,7 +46,7 @@ describe('MenuItem', () => { const wrapper = mount( 1 - + 2 ); diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index b7dfdb0d..79b19d16 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -33,10 +33,6 @@ describe('SubMenu', () => { return MenuItemIcon; } - function expandIcon() { - return SubMenuIcon; - } - it('don\'t show submenu when disabled', () => { const wrapper = mount( @@ -64,7 +60,11 @@ describe('SubMenu', () => { it('should render custom arrow icon correctly.', () => { const wrapper = mount( - + SubMenuIconNode} + > 1 2 @@ -73,13 +73,18 @@ describe('SubMenu', () => { ); const subMenuText = wrapper.find('.rc-menu-submenu-title').first().text(); - expect(subMenuText).toEqual('submenuSubMenuIcon'); + expect(subMenuText).toEqual('submenuSubMenuIconNode'); }); it('should Not render custom arrow icon in horizontal mode.', () => { const wrapper = mount( - + SubMenuIconNode} + > 1 @@ -230,8 +235,8 @@ describe('SubMenu', () => { const titles = wrapper.find('.rc-menu-submenu-title'); titles.first().simulate('mouseEnter') - .simulate('keyDown', { keyCode: KeyCode.LEFT }) - .simulate('keyDown', { keyCode: KeyCode.DOWN }); + .simulate('keyDown', { keyCode: KeyCode.LEFT }) + .simulate('keyDown', { keyCode: KeyCode.DOWN }); expect(wrapper.find('.rc-menu-submenu').last().is('.rc-menu-submenu-active')).toBe(true); titles.last().simulate('keyDown', { keyCode: KeyCode.UP }); @@ -245,12 +250,12 @@ describe('SubMenu', () => { // testing keydown event after submenu is closed and then opened again firstItem.simulate('mouseEnter') - .simulate('keyDown', { keyCode: KeyCode.RIGHT }) - .simulate('keyDown', { keyCode: KeyCode.LEFT }) - .simulate('keyDown', { keyCode: KeyCode.RIGHT }) - .simulate('keyDown', { keyCode: KeyCode.DOWN }) - .simulate('keyDown', { keyCode: KeyCode.DOWN }) - .simulate('keyDown', { keyCode: KeyCode.DOWN }); + .simulate('keyDown', { keyCode: KeyCode.RIGHT }) + .simulate('keyDown', { keyCode: KeyCode.LEFT }) + .simulate('keyDown', { keyCode: KeyCode.RIGHT }) + .simulate('keyDown', { keyCode: KeyCode.DOWN }) + .simulate('keyDown', { keyCode: KeyCode.DOWN }) + .simulate('keyDown', { keyCode: KeyCode.DOWN }); expect( wrapper From 98d394dc48cf7f675d2fc117687fd095148a9057 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Mon, 13 Aug 2018 18:08:14 +0800 Subject: [PATCH 12/18] remove useless --- tests/SubMenu.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index 79b19d16..1c924ac1 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -333,7 +333,7 @@ describe('SubMenu', () => { }); describe('submenu animation', () => { - const appear = () => { }; + const appear = () => {}; it('should animate with transition class', () => { const wrapper = mount(createMenu({ From d27ee527853fb98aef306698d2990eba9e019b9d Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Mon, 13 Aug 2018 18:10:01 +0800 Subject: [PATCH 13/18] update readme.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bfc5fc62..bb06476b 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ ReactDOM.render( expandIcon - ReactNode | (props: SubMenuProps) => ReactNode + ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode specific the menu item icon. @@ -251,7 +251,7 @@ ReactDOM.render( itemIcon - (props: MenuItemProps) => ReactNode + ReactNode | (props: MenuItemProps) => ReactNode specific the menu item icon. @@ -333,13 +333,13 @@ ReactDOM.render( expandIcon - (props: SubMenuProps) => ReactNode + ReactNode | (props: SubMenuProps) => ReactNode specific the menu item icon. itemIcon - (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode + ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode specific the menu item icon. From af3fdce967c208864382885ebd5aba4c3f1c2dee Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Thu, 16 Aug 2018 12:10:36 +0800 Subject: [PATCH 14/18] request ci --- examples/custom-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/custom-icon.js b/examples/custom-icon.js index 85315490..015c5a56 100644 --- a/examples/custom-icon.js +++ b/examples/custom-icon.js @@ -159,7 +159,7 @@ class Demo extends React.Component { return (
    -

    Antd menu - custom icon

    +

    Antd menu - Custom icon

    vertical

    {verticalMenu}
    From c87971e3bfa259d4741a77cd4b6535bd4593b311 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Fri, 17 Aug 2018 11:45:06 +0800 Subject: [PATCH 15/18] remove isSubMenu --- src/SubMenu.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index ee3e3b74..d3a6d84c 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -472,7 +472,7 @@ export class SubMenu extends React.Component { if (typeof this.props.expandIcon === 'function') { icon = React.createElement( this.props.expandIcon, - { ...this.props, isSubMenu: true } + { ...this.props } ); } } From 3885f3ca21d9a73d808150a915e9472c3fba4eac Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Fri, 17 Aug 2018 19:04:22 +0800 Subject: [PATCH 16/18] test coverage --- tests/SubMenu.spec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index be1d5b31..8b199496 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -72,8 +72,23 @@ describe('SubMenu', () => {
    ); + const wrapperWithExpandIconFunction = mount( + SubMenuIconNode} + > + + 1 + 2 + + + ); + const subMenuText = wrapper.find('.rc-menu-submenu-title').first().text(); + const subMenuTextWithExpandIconFunction = wrapperWithExpandIconFunction.find('.rc-menu-submenu-title').first().text(); expect(subMenuText).toEqual('submenuSubMenuIconNode'); + expect(subMenuTextWithExpandIconFunction).toEqual('submenuSubMenuIconNode'); }); it('should Not render custom arrow icon in horizontal mode.', () => { From c8ca3d73765f6fa13827d73a07004dd3c8c45e67 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Fri, 17 Aug 2018 19:04:32 +0800 Subject: [PATCH 17/18] eslint --- tests/SubMenu.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index 8b199496..79f9f2cc 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -86,7 +86,8 @@ describe('SubMenu', () => { ); const subMenuText = wrapper.find('.rc-menu-submenu-title').first().text(); - const subMenuTextWithExpandIconFunction = wrapperWithExpandIconFunction.find('.rc-menu-submenu-title').first().text(); + const subMenuTextWithExpandIconFunction = + wrapperWithExpandIconFunction.find('.rc-menu-submenu-title').first().text(); expect(subMenuText).toEqual('submenuSubMenuIconNode'); expect(subMenuTextWithExpandIconFunction).toEqual('submenuSubMenuIconNode'); }); From 34356d7941210f34114449b23f592f7c69d18981 Mon Sep 17 00:00:00 2001 From: HeskeyBaozi Date: Wed, 22 Aug 2018 14:04:27 +0800 Subject: [PATCH 18/18] update readme.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bb06476b..f4d52730 100644 --- a/README.md +++ b/README.md @@ -196,13 +196,13 @@ ReactDOM.render( itemIcon ReactNode | (props: MenuItemProps) => ReactNode - specific the menu item icon. + Specify the menu item icon. expandIcon ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode - specific the menu item icon. + Specify the menu item icon. @@ -253,7 +253,7 @@ ReactDOM.render( itemIcon ReactNode | (props: MenuItemProps) => ReactNode - specific the menu item icon. + Specify the menu item icon. @@ -335,13 +335,13 @@ ReactDOM.render( expandIcon ReactNode | (props: SubMenuProps) => ReactNode - specific the menu item icon. + Specify the menu item icon. itemIcon ReactNode | (props: SubMenuProps & { isSubMenu: boolean }) => ReactNode - specific the menu item icon. + Specify the menu item icon.