diff --git a/less/control.less b/less/control.less index 6a5ced741d..f474608513 100644 --- a/less/control.less +++ b/less/control.less @@ -76,7 +76,6 @@ } .is-open > .Select-control { - .border-bottom-radius( 0 ); background: @select-input-bg; border-color: darken(@select-input-border-color, 10%) @select-input-border-color lighten(@select-input-border-color, 5%); @@ -86,6 +85,14 @@ border-color: transparent transparent @select-arrow-color; border-width: 0 @select-arrow-width @select-arrow-width; } + + &--on-bottom { + .border-bottom-radius( 0 ); + } + + &--on-top { + .border-top-radius( 0 ); + } } .is-searchable { diff --git a/less/menu.less b/less/menu.less index 8b9fe457c0..0051b12e7c 100644 --- a/less/menu.less +++ b/less/menu.less @@ -6,23 +6,32 @@ // wrapper around the menu .Select-menu-outer { - // Unfortunately, having both border-radius and allows scrolling using overflow defined on the same - // element forces the browser to repaint on scroll. However, if these definitions are split into an - // outer and an inner element, the browser is able to optimize the scrolling behavior and does not - // have to repaint on scroll. - .border-bottom-radius( @select-input-border-radius ); + position: absolute; background-color: @select-input-bg; border: 1px solid @select-input-border-color; - border-top-color: mix(@select-input-bg, @select-input-border-color, 50%); box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06); box-sizing: border-box; - margin-top: -1px; max-height: @select-menu-max-height; - position: absolute; - top: 100%; width: 100%; z-index: @select-menu-zindex; -webkit-overflow-scrolling: touch; + + + // Unfortunately, having both border-radius and allows scrolling using overflow defined on the same + // element forces the browser to repaint on scroll. However, if these definitions are split into an + // outer and an inner element, the browser is able to optimize the scrolling behavior and does not + // have to repaint on scroll. + &[data-placement=bottom] { + .border-bottom-radius( @select-input-border-radius ); + border-top-color: mix(@select-input-bg, @select-input-border-color, 50%); + margin-top: -1px; + } + + &[data-placement=top] { + .border-top-radius( @select-input-border-radius ); + border-bottom-color: mix(@select-input-bg, @select-input-border-color, 50%); + margin-bottom: -1px; + } } diff --git a/package.json b/package.json index 12151a0505..86f29014ca 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "react-dom": "^15.0", "react-gravatar": "^2.4.5", "react-highlight-words": "^0.3.0", + "react-popper": "^0.4.3", "react-virtualized": "^7.23.0", "react-virtualized-select": "^1.4.0", "sinon": "^1.17.5", diff --git a/src/Select.js b/src/Select.js index 17b8f3b0ff..19c95ad8e6 100644 --- a/src/Select.js +++ b/src/Select.js @@ -5,8 +5,9 @@ */ import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM, { findDOMNode } from 'react-dom'; import AutosizeInput from 'react-input-autosize'; +import { Manager, Popper, Target } from 'react-popper'; import classNames from 'classnames'; import defaultArrowRenderer from './utils/defaultArrowRenderer'; @@ -170,6 +171,7 @@ const Select = React.createClass({ isOpen: false, isPseudoFocused: false, required: false, + placement: 'bottom', }; }, @@ -1045,6 +1047,17 @@ const Select = React.createClass({ return null; }, + updatePlacement () { + return { + enabled: true, + order: 890, + function: (data) => { + this.setState({ placement: data.placement }); + return data; + }, + }; + }, + renderOuter (options, valueArray, focusedOption) { let menu = this.renderMenu(options, valueArray, focusedOption); if (!menu) { @@ -1052,14 +1065,19 @@ const Select = React.createClass({ } return ( -
this.menuContainer = ref} className="Select-menu-outer" style={this.props.menuContainerStyle}> + this.menuContainer = findDOMNode(ref)} + className="Select-menu-outer" + style={this.props.menuContainerStyle} + modifiers={{ updatePlacement: this.updatePlacement() }} + >
this.menu = ref} role="listbox" className="Select-menu" id={this._instancePrefix + '-list'} style={this.props.menuStyle} onScroll={this.handleMenuScroll} onMouseDown={this.handleMouseDownOnMenu}> {menu}
-
+ ); }, @@ -1103,30 +1121,36 @@ const Select = React.createClass({ } return ( -
this.wrapper = ref} - className={className} - style={this.props.wrapperStyle}> - {this.renderHiddenField(valueArray)} -
this.control = ref} - className="Select-control" - style={this.props.style} - onKeyDown={this.handleKeyDown} - onMouseDown={this.handleMouseDown} - onTouchEnd={this.handleTouchEnd} - onTouchStart={this.handleTouchStart} - onTouchMove={this.handleTouchMove} - > - - {this.renderValue(valueArray, isOpen)} - {this.renderInput(valueArray, focusedOptionIndex)} - - {removeMessage} - {this.renderLoading()} - {this.renderClear()} - {this.renderArrow()} + +
this.wrapper = ref} + className={className} + style={this.props.wrapperStyle}> + {this.renderHiddenField(valueArray)} + this.control = ref} + className={classNames( + 'Select-control', + { 'Select-control--on-bottom': this.state.placement === 'bottom' }, + { 'Select-control--on-top': this.state.placement === 'top' } + )} + style={this.props.style} + onKeyDown={this.handleKeyDown} + onMouseDown={this.handleMouseDown} + onTouchEnd={this.handleTouchEnd} + onTouchStart={this.handleTouchStart} + onTouchMove={this.handleTouchMove} + > + + {this.renderValue(valueArray, isOpen)} + {this.renderInput(valueArray, focusedOptionIndex)} + + {removeMessage} + {this.renderLoading()} + {this.renderClear()} + {this.renderArrow()} + + {isOpen ? this.renderOuter(options, !this.props.multi ? valueArray : null, focusedOption) : null}
- {isOpen ? this.renderOuter(options, !this.props.multi ? valueArray : null, focusedOption) : null} -
+ ); }