Skip to content

Commit 7e91a17

Browse files
author
Jason Madsen
committed
[added] smart css defaults for MenuOptions placement
allow menu to be placed to the left, right and top / bottom based on current window space.
1 parent 5b44208 commit 7e91a17

File tree

6 files changed

+102
-12
lines changed

6 files changed

+102
-12
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ var Menu = require('react-menu');
6464
Menu.injectCSS();
6565
```
6666

67-
Default styles will be added to the top of the head, and thus any styles you write will override any of the defaults.
67+
Default styles will be added to the top of the head, and thus any styles you
68+
write will override any of the defaults.
6869

6970
The following class names are used / available for modification in your own stylsheets:
7071

@@ -73,4 +74,13 @@ The following class names are used / available for modification in your own styl
7374
.Menu__MenuTrigger
7475
.Menu__MenuOptions
7576
.Menu__MenuOption
77+
.Menu__MenuOptions--vertical--bottom
78+
.Menu__MenuOptions--vertical--top
79+
.Menu__MenuOptions--horizontal--right
80+
.Menu__MenuOptions--horizontal--left
7681
```
82+
83+
The last four class names control the placement of menu options when the menu
84+
would otherwise bleed off the screen. See `/lib/helpers/injectCSS.js` for
85+
defaults. The `.Menu__MenuOptions` element will always have a vertical and
86+
horizontal modifier.

dist/react-menu.js

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ var Menu = module.exports = React.createClass({
2323
getInitialState: function(){
2424
return {
2525
active: false,
26-
selectedIndex: 0
26+
selectedIndex: 0,
27+
horizontalPlacement: 'right', // only 'right' || 'left'
28+
verticalPlacement: 'bottom' // only 'top' || 'bottom'
2729
};
2830
},
2931

@@ -45,15 +47,33 @@ var Menu = module.exports = React.createClass({
4547
},
4648

4749
handleTriggerToggle: function() {
48-
this.setState({active: !this.state.active}, this.attemptOptionsFocus);
50+
this.setState({active: !this.state.active}, this.afterTriggerToggle);
4951
},
5052

51-
attemptOptionsFocus: function() {
52-
if (this.state.active){
53+
afterTriggerToggle: function() {
54+
if (this.state.active) {
5355
this.refs.options.focusOption(0);
56+
this.updatePositioning();
5457
}
5558
},
5659

60+
updatePositioning: function() {
61+
var triggerRect = this.refs.trigger.getDOMNode().getBoundingClientRect();
62+
var optionsRect = this.refs.options.getDOMNode().getBoundingClientRect();
63+
positionState = {};
64+
// horizontal = left if it wont fit on left side
65+
if (triggerRect.left + optionsRect.width > window.innerWidth) {
66+
positionState.horizontalPlacement = 'left';
67+
} else {
68+
positionState.horizontalPlacement = 'right';
69+
}
70+
if (triggerRect.top + optionsRect.height > window.innerHeight) {
71+
positionState.verticalPlacement = 'top';
72+
} else {
73+
positionState.verticalPlacement = 'bottom';
74+
}
75+
this.setState(positionState);
76+
},
5777

5878
handleKeys: function(e) {
5979
if (e.key === 'Escape') {
@@ -93,6 +113,8 @@ var Menu = module.exports = React.createClass({
93113
if (child.type === MenuOptions.type) {
94114
options = cloneWithProps(child, {
95115
ref: 'options',
116+
horizontalPlacement: this.state.horizontalPlacement,
117+
verticalPlacement: this.state.verticalPlacement,
96118
onSelectionMade: this.closeMenu
97119
});
98120
}
@@ -287,10 +309,17 @@ var MenuOptions = module.exports = React.createClass({displayName: 'exports',
287309
}.bind(this));
288310
},
289311

312+
buildName: function() {
313+
var cn = this.buildClassName('Menu__MenuOptions');
314+
cn += ' Menu__MenuOptions--horizontal-' + this.props.horizontalPlacement;
315+
cn += ' Menu__MenuOptions--vertical-' + this.props.verticalPlacement;
316+
return cn;
317+
},
318+
290319
render: function() {
291320
return (
292321
React.DOM.div({
293-
className: this.buildClassName('Menu__MenuOptions'),
322+
className: this.buildName(),
294323
onKeyUp: this.handleKeys
295324
},
296325
this.renderOptions()
@@ -393,6 +422,17 @@ module.exports = function() {
393422
'border-radius': '3px',
394423
padding: '5px',
395424
background: '#FFF'
425+
},
426+
'.Menu__MenuOptions--horizontal-left': {
427+
right: '0px'
428+
},
429+
'.Menu__MenuOptions--horizontal-right': {
430+
left: '0px'
431+
},
432+
'.Menu__MenuOptions--vertical-top': {
433+
bottom: '45px'
434+
},
435+
'.Menu__MenuOptions--vertical-bottom': {
396436
}
397437
});
398438
};

dist/react-menu.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/components/Menu.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ var Menu = module.exports = React.createClass({
2222
getInitialState: function(){
2323
return {
2424
active: false,
25-
selectedIndex: 0
25+
selectedIndex: 0,
26+
horizontalPlacement: 'right', // only 'right' || 'left'
27+
verticalPlacement: 'bottom' // only 'top' || 'bottom'
2628
};
2729
},
2830

@@ -44,15 +46,33 @@ var Menu = module.exports = React.createClass({
4446
},
4547

4648
handleTriggerToggle: function() {
47-
this.setState({active: !this.state.active}, this.attemptOptionsFocus);
49+
this.setState({active: !this.state.active}, this.afterTriggerToggle);
4850
},
4951

50-
attemptOptionsFocus: function() {
51-
if (this.state.active){
52+
afterTriggerToggle: function() {
53+
if (this.state.active) {
5254
this.refs.options.focusOption(0);
55+
this.updatePositioning();
5356
}
5457
},
5558

59+
updatePositioning: function() {
60+
var triggerRect = this.refs.trigger.getDOMNode().getBoundingClientRect();
61+
var optionsRect = this.refs.options.getDOMNode().getBoundingClientRect();
62+
positionState = {};
63+
// horizontal = left if it wont fit on left side
64+
if (triggerRect.left + optionsRect.width > window.innerWidth) {
65+
positionState.horizontalPlacement = 'left';
66+
} else {
67+
positionState.horizontalPlacement = 'right';
68+
}
69+
if (triggerRect.top + optionsRect.height > window.innerHeight) {
70+
positionState.verticalPlacement = 'top';
71+
} else {
72+
positionState.verticalPlacement = 'bottom';
73+
}
74+
this.setState(positionState);
75+
},
5676

5777
handleKeys: function(e) {
5878
if (e.key === 'Escape') {
@@ -92,6 +112,8 @@ var Menu = module.exports = React.createClass({
92112
if (child.type === MenuOptions.type) {
93113
options = cloneWithProps(child, {
94114
ref: 'options',
115+
horizontalPlacement: this.state.horizontalPlacement,
116+
verticalPlacement: this.state.verticalPlacement,
95117
onSelectionMade: this.closeMenu
96118
});
97119
}

lib/components/MenuOptions.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,17 @@ var MenuOptions = module.exports = React.createClass({
8282
}.bind(this));
8383
},
8484

85+
buildName: function() {
86+
var cn = this.buildClassName('Menu__MenuOptions');
87+
cn += ' Menu__MenuOptions--horizontal-' + this.props.horizontalPlacement;
88+
cn += ' Menu__MenuOptions--vertical-' + this.props.verticalPlacement;
89+
return cn;
90+
},
91+
8592
render: function() {
8693
return (
8794
<div
88-
className={this.buildClassName('Menu__MenuOptions')}
95+
className={this.buildName()}
8996
onKeyUp={this.handleKeys}
9097
>
9198
{this.renderOptions()}

lib/helpers/injectCSS.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ module.exports = function() {
3131
'border-radius': '3px',
3232
padding: '5px',
3333
background: '#FFF'
34+
},
35+
'.Menu__MenuOptions--horizontal-left': {
36+
right: '0px'
37+
},
38+
'.Menu__MenuOptions--horizontal-right': {
39+
left: '0px'
40+
},
41+
'.Menu__MenuOptions--vertical-top': {
42+
bottom: '45px'
43+
},
44+
'.Menu__MenuOptions--vertical-bottom': {
3445
}
3546
});
3647
};

0 commit comments

Comments
 (0)