5
5
* This source code is licensed under the license found in the LICENSE file in
6
6
* the root directory of this source tree.
7
7
*/
8
- import Popover from 'components/Popover/Popover.react ' ;
8
+ import styles from 'components/BrowserMenu/BrowserMenu.scss ' ;
9
9
import Icon from 'components/Icon/Icon.react' ;
10
+ import Popover from 'components/Popover/Popover.react' ;
10
11
import Position from 'lib/Position' ;
11
12
import PropTypes from 'lib/PropTypes' ;
12
13
import React from 'react' ;
13
- import styles from 'components/BrowserMenu/BrowserMenu.scss' ;
14
14
15
15
export default class BrowserMenu extends React . Component {
16
16
constructor ( ) {
17
17
super ( ) ;
18
18
19
- this . state = { open : false } ;
19
+ this . state = { open : false , openToLeft : false } ;
20
20
this . wrapRef = React . createRef ( ) ;
21
21
}
22
22
23
23
render ( ) {
24
24
let menu = null ;
25
+ const isSubmenu = ! ! this . props . parentClose ;
25
26
if ( this . state . open ) {
26
27
const position = Position . inDocument ( this . wrapRef . current ) ;
27
28
const titleStyle = [ styles . title ] ;
@@ -35,20 +36,50 @@ export default class BrowserMenu extends React.Component {
35
36
onExternalClick = { ( ) => this . setState ( { open : false } ) }
36
37
>
37
38
< 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
+ } ) }
52
83
</ div >
53
84
</ div >
54
85
</ Popover >
@@ -61,18 +92,37 @@ export default class BrowserMenu extends React.Component {
61
92
if ( this . props . disabled ) {
62
93
classes . push ( styles . disabled ) ;
63
94
}
64
- let onClick = null ;
95
+ const entryEvents = { } ;
65
96
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
+ }
70
111
}
71
112
return (
72
113
< 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 } /> }
75
116
< 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
+ ) }
76
126
</ div >
77
127
{ menu }
78
128
</ div >
@@ -81,12 +131,12 @@ export default class BrowserMenu extends React.Component {
81
131
}
82
132
83
133
BrowserMenu . 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.' ) ,
85
135
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 (
90
137
'The contents of the menu when open. It should be a set of MenuItem and Separator components.'
91
138
) ,
139
+ parentClose : PropTypes . func . describe (
140
+ 'Closes the parent menu when a nested menu item is selected.'
141
+ ) ,
92
142
} ;
0 commit comments