55 * This source code is licensed under the license found in the LICENSE file in
66 * the root directory of this source tree.
77 */
8- import Popover from 'components/Popover/Popover.react ' ;
8+ import styles from 'components/BrowserMenu/BrowserMenu.scss ' ;
99import Icon from 'components/Icon/Icon.react' ;
10+ import Popover from 'components/Popover/Popover.react' ;
1011import Position from 'lib/Position' ;
1112import PropTypes from 'lib/PropTypes' ;
1213import React from 'react' ;
13- import styles from 'components/BrowserMenu/BrowserMenu.scss' ;
1414
1515export default class BrowserMenu extends React . Component {
1616 constructor ( ) {
1717 super ( ) ;
1818
19- this . state = { open : false } ;
19+ this . state = { open : false , openToLeft : false } ;
2020 this . wrapRef = React . createRef ( ) ;
2121 }
2222
2323 render ( ) {
2424 let menu = null ;
25+ const isSubmenu = ! ! this . props . parentClose ;
2526 if ( this . state . open ) {
2627 const position = Position . inDocument ( this . wrapRef . current ) ;
2728 const titleStyle = [ styles . title ] ;
@@ -35,20 +36,50 @@ export default class BrowserMenu extends React.Component {
3536 onExternalClick = { ( ) => this . setState ( { open : false } ) }
3637 >
3738 < div className = { styles . menu } >
38- < div className = { titleStyle . join ( ' ' ) } onClick = { ( ) => this . setState ( { open : false } ) } >
39- < Icon name = { this . props . icon } width = { 14 } height = { 14 } />
40- < span > { this . props . title } </ span >
41- </ div >
42- < div className = { styles . body } style = { { minWidth : this . wrapRef . current . clientWidth } } >
43- { React . Children . map ( this . props . children , child =>
44- React . cloneElement ( child , {
45- ...child . props ,
46- onClick : ( ) => {
47- this . setState ( { open : false } ) ;
48- child . props . onClick ( ) ;
49- } ,
50- } )
51- ) }
39+ { ! isSubmenu && (
40+ < div
41+ className = { titleStyle . join ( ' ' ) }
42+ onClick = { ( ) => this . setState ( { open : false } ) }
43+ >
44+ { this . props . icon && < Icon name = { this . props . icon } width = { 14 } height = { 14 } /> }
45+ < span > { this . props . title } </ span >
46+ </ div >
47+ ) }
48+ < div
49+ className = {
50+ isSubmenu
51+ ? this . state . openToLeft
52+ ? styles . subMenuBodyLeft
53+ : styles . subMenuBody
54+ : styles . body
55+ }
56+ style = { {
57+ minWidth : this . wrapRef . current . clientWidth ,
58+ ...( isSubmenu
59+ ? {
60+ top : 0 ,
61+ left : this . state . openToLeft
62+ ? 0
63+ : `${ this . wrapRef . current . clientWidth - 3 } px` ,
64+ transform : this . state . openToLeft
65+ ? 'translateX(calc(-100% + 3px))'
66+ : undefined ,
67+ }
68+ : { } ) ,
69+ } }
70+ >
71+ { React . Children . map ( this . props . children , ( child ) => {
72+ if ( React . isValidElement ( child ) && child . type === BrowserMenu ) {
73+ return React . cloneElement ( child , {
74+ ...child . props ,
75+ parentClose : ( ) => {
76+ this . setState ( { open : false } ) ;
77+ this . props . parentClose ?. ( ) ;
78+ } ,
79+ } ) ;
80+ }
81+ return child ;
82+ } ) }
5283 </ div >
5384 </ div >
5485 </ Popover >
@@ -61,18 +92,37 @@ export default class BrowserMenu extends React.Component {
6192 if ( this . props . disabled ) {
6293 classes . push ( styles . disabled ) ;
6394 }
64- let onClick = null ;
95+ const entryEvents = { } ;
6596 if ( ! this . props . disabled ) {
66- onClick = ( ) => {
67- this . setState ( { open : true } ) ;
68- this . props . setCurrent ( null ) ;
69- } ;
97+ if ( isSubmenu ) {
98+ entryEvents . onMouseEnter = ( ) => {
99+ const rect = this . wrapRef . current . getBoundingClientRect ( ) ;
100+ const width = this . wrapRef . current . clientWidth ;
101+ const openToLeft = rect . right + width > window . innerWidth ;
102+ this . setState ( { open : true , openToLeft } ) ;
103+ this . props . setCurrent ?. ( null ) ;
104+ } ;
105+ } else {
106+ entryEvents . onClick = ( ) => {
107+ this . setState ( { open : true , openToLeft : false } ) ;
108+ this . props . setCurrent ( null ) ;
109+ } ;
110+ }
70111 }
71112 return (
72113 < div className = { styles . wrap } ref = { this . wrapRef } >
73- < div className = { classes . join ( ' ' ) } onClick = { onClick } >
74- < Icon name = { this . props . icon } width = { 14 } height = { 14 } />
114+ < div className = { classes . join ( ' ' ) } { ... entryEvents } >
115+ { this . props . icon && < Icon name = { this . props . icon } width = { 14 } height = { 14 } /> }
75116 < span > { this . props . title } </ span >
117+ { isSubmenu &&
118+ React . Children . toArray ( this . props . children ) . some ( c => React . isValidElement ( c ) && c . type === BrowserMenu ) && (
119+ < Icon
120+ name = "right-outline"
121+ width = { 12 }
122+ height = { 12 }
123+ className = { styles . submenuArrow }
124+ />
125+ ) }
76126 </ div >
77127 { menu }
78128 </ div >
@@ -81,12 +131,12 @@ export default class BrowserMenu extends React.Component {
81131}
82132
83133BrowserMenu . propTypes = {
84- icon : PropTypes . string . isRequired . describe ( 'The name of the icon to place in the menu.' ) ,
134+ icon : PropTypes . string . describe ( 'The name of the icon to place in the menu.' ) ,
85135 title : PropTypes . string . isRequired . describe ( 'The title text of the menu.' ) ,
86- children : PropTypes . oneOfType ( [
87- PropTypes . arrayOf ( PropTypes . node ) ,
88- PropTypes . node ,
89- ] ) . describe (
136+ children : PropTypes . oneOfType ( [ PropTypes . arrayOf ( PropTypes . node ) , PropTypes . node ] ) . describe (
90137 'The contents of the menu when open. It should be a set of MenuItem and Separator components.'
91138 ) ,
139+ parentClose : PropTypes . func . describe (
140+ 'Closes the parent menu when a nested menu item is selected.'
141+ ) ,
92142} ;
0 commit comments