-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
positioning the component in containers that have overflow: scroll;
#810
Comments
Really important feature. There are plenty of situations where you you don't want the drop-down to cause overflow. Actually, it should be the default behavior, since that's how the native select box work. |
+1 |
@oluckyman Having the same issue here! If I have other elements below the Select, I have no issues since there is plenty of space and I also reduce the height of the dropdown, but when the select is closer to the bottom of the page it pushed the main container. Were you able to find a solution for this? |
Has anyone had any luck with this? I can't use a react-select in a modal without it causing scrolling issues. If I try and hijack |
@maxmatthews you can try the following, it worked for me. 1 - Comment out the rule
3 - Add the class .menu-outer-top to the Select to manually change to position of the dropdown (See screenshot below)
Hope it helps! |
@juan0087 WOW! Thanks for that detailed response. Will give it a shot right now, and I'm sure it will help others in the future. |
It's a pleasure! It's not 100% the correct way to do it but it does the job. |
@juan0087 Using your solution I still have a problem with the select menu getting cropped by the bounding modal box/div. Any suggestions? |
To further complicate things, if you scroll in Chrome it uncrops the menu and shows the whole thing. Could that possible be related to this comment in menu.scss?
|
I just ended up setting my modal to |
+1 |
I ended up using |
Can you provide the snippet @oluckyman ? |
My case is pretty complex. So here are only important parts. <Select
dropdownComponent={ DropdownMenu } And here are parts of DropdownMenu component: componentDidMount() {
/*
* DropdownMenu is called from dropdown component. In order to get the
* dropdown component dom node we have to render menu first.
* Now in `componentDidMount` when it was renderd we can access parentNode and
* inherit some CSS styles from it and rerender menu again with proper styling.
* So this is why we call `setState` here and cause second render
*/
const dropdownFieldNode = ReactDOM.findDOMNode(this).parentNode; and in return (
<TetherComponent target={ dropdownFieldNode } options={ options }>
{ this.props.children }
</TetherComponent>
); And here is TetherComponent as is (just a wrapper around tether lib): import React from 'react';
import ReactDOM from 'react-dom';
import Tether from 'tether';
/**
* This component renders `children` in a tetherContainer node and
* positions the children near the `target` node using rules from `options`
*/
const TetherComponent = React.createClass({
propTypes: {
children: React.PropTypes.node,
target: React.PropTypes.object,
options: React.PropTypes.object,
},
componentWillMount() {
// init tether container
this.tetherContainer = document.getElementById('tetherContainer');
if (!this.tetherContainer) {
this.tetherContainer = document.createElement('div');
this.tetherContainer.id = 'tetherContainer';
document.body.appendChild(this.tetherContainer);
}
},
componentDidMount() {
this.update();
},
componentDidUpdate() {
this.update();
},
componentWillUnmount() {
this.destroy();
},
update() {
if (!this.props.target) return;
this.element = ReactDOM.render(this.props.children, this.tetherContainer);
if (!this.tether) {
this.tether = new Tether({
...this.props.options,
element: this.element,
target: this.props.target,
});
}
this.tether.position();
},
destroy() {
ReactDOM.unmountComponentAtNode(this.tetherContainer);
this.tether.destroy();
},
render() {
return <div />;
}
});
export default TetherComponent; |
Would be good to find easier solution to this problem, anybody has ideas where to look? |
@bvaughn can help on this one? thanks |
Maybe it's possible to overwrite react-select 's render method and wrap the components in "react-tether" 's TetherComponent? Currently experimenting with this but I am not sure if this is the right way to go. "react-tether" requires you two have two children next to eachother where the first child is the target (in react-select case that would be the Input/Value components) and the second child has to be the dropdown (renderOuter). Has anyone else tried this approach? |
Thanks @oluckyman for the great solution. I've modified it a bit to work with react-select: TetheredSelect component that overrides Select's menu rendering: import React from 'react';
import Select from 'react-select';
import ReactDOM from 'react-dom';
import TetherComponent from './TetherComponent';
export default class TetheredSelect extends Select {
constructor(props) {
super(props);
this.renderOuter = this._renderOuter;
}
componentDidMount() {
super.componentDidMount.call(this);
this.dropdownFieldNode = ReactDOM.findDOMNode(this);
}
_renderOuter() {
const menu = super.renderOuter.apply(this, arguments);
const options = {
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: 'window',
attachment: 'together',
}
]
};
return (
<TetherComponent
target={this.dropdownFieldNode}
options={options}
matchWidth
>
{/* Apply position:static to our menu so that it's parent will get the correct dimensions and we can tether the parent */}
{React.cloneElement(menu, {style: {position: 'static'}})}
</TetherComponent>
)
}
} (Btw, does anybody know, why renderOuter can't be directly overridden here?) TetherComponent: import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import Tether from 'tether';
class TetheredChildrenComponent extends Component {
render() {
return this.props.children;
}
componentDidMount() {
this.props.position();
}
componentDidUpdate() {
this.props.position();
}
}
export default class TetherComponent extends Component {
componentDidMount() {
this.tetherContainer = document.createElement('div');
document.body.appendChild(this.tetherContainer);
this.renderTetheredContent();
}
componentDidUpdate() {
this.renderTetheredContent();
}
componentWillUnmount() {
this.destroyTetheredContent();
}
renderTetheredContent() {
ReactDOM.render(
<TetheredChildrenComponent
target={this.props.target}
position={this.position}
>
{this.props.children}
</TetheredChildrenComponent>,
this.tetherContainer
);
}
position = () => {
if (!this.tether) {
this.tether = new Tether({
...this.props.options,
element: this.tetherContainer,
target: this.props.target,
});
}
if (this.props.matchWidth) {
this.tetherContainer.style.width = `${this.props.target.clientWidth}px`;
}
this.tether.position();
};
destroyTetheredContent() {
ReactDOM.unmountComponentAtNode(this.tetherContainer);
this.tether.destroy();
document.body.removeChild(this.tetherContainer);
}
render() {
return null;
}
} |
@eng1neer Awesome! Any way to get that working with |
import React from 'react';
import Select from './Select';
const AsyncCreatable = React.createClass({
displayName: 'AsyncCreatableSelect',
render () {
return (
<Select.Async {...this.props}>
{(asyncProps) => (
<Select.Creatable {...this.props}>
{(creatableProps) => (
<Select
{...asyncProps}
{...creatableProps}
onInputChange={(input) => {
creatableProps.onInputChange(input);
return asyncProps.onInputChange(input);
}}
/>
)}
</Select.Creatable>
)}
</Select.Async>
);
}
});
module.exports = AsyncCreatable; I would just replace the inner Select with the subclass |
@eng1neer You'd update the code directly in the npm module with the |
@stinoga No, just copy the
|
@eng1neer Thanks! For anyone who may need it, here's my full code implementing import React from 'react';
import ReactDOM from 'react-dom';
import Select from 'react-select';
import Tether from 'tether';
class TetheredChildrenComponent extends React.Component {
render() {
return this.props.children;
}
componentDidMount() {
this.props.position();
}
componentDidUpdate() {
this.props.position();
}
}
class TetherComponent extends React.Component {
constructor(props) {
super(props);
this.position = this.position.bind(this);
}
componentDidMount() {
this.tetherContainer = document.createElement('div');
document.body.appendChild(this.tetherContainer);
this.renderTetheredContent();
}
componentDidUpdate() {
this.renderTetheredContent();
}
componentWillUnmount() {
this.destroyTetheredContent();
}
position() {
if (!this.tether) {
this.tether = new Tether({
...this.props.options,
element: this.tetherContainer,
target: this.props.target,
});
}
if (this.props.matchWidth) {
this.tetherContainer.style.width = `${this.props.target.clientWidth}px`;
}
this.tether.position();
}
renderTetheredContent() {
ReactDOM.render(
<TetheredChildrenComponent
target={this.props.target}
position={this.position}
>
{this.props.children}
</TetheredChildrenComponent>,
this.tetherContainer
);
}
destroyTetheredContent() {
ReactDOM.unmountComponentAtNode(this.tetherContainer);
this.tether.destroy();
document.body.removeChild(this.tetherContainer);
}
render() {
return null;
}
}
class TetheredSelectWrap extends Select {
constructor(props) {
super(props);
this.renderOuter = this._renderOuter;
}
componentDidMount() {
super.componentDidMount.call(this);
this.dropdownFieldNode = ReactDOM.findDOMNode(this);
}
_renderOuter() {
const menu = super.renderOuter.apply(this, arguments);
// Don't return an updated menu render if we don't have one
if (!menu) {
return;
}
const options = {
attachment: 'top left',
targetAttachment: 'bottom left',
constraints: [
{
to: 'window',
attachment: 'together',
}
]
};
return (
<TetherComponent
target={this.dropdownFieldNode}
options={options}
matchWidth
>
{/* Apply position:static to our menu so that it's parent will get the correct dimensions and we can tether the parent */}
{React.cloneElement(menu, {style: {position: 'static'}})}
</TetherComponent>
);
}
}
// Call the AsyncCreatable code from react-select with our extended tether class
class TetheredSelect extends React.Component {
render () {
return (
<TetheredSelectWrap.Async {...this.props}>
{(asyncProps) => (
<TetheredSelectWrap.Creatable {...this.props}>
{(creatableProps) => (
<TetheredSelectWrap
{...asyncProps}
{...creatableProps}
onInputChange={(input) => {
creatableProps.onInputChange(input);
return asyncProps.onInputChange(input);
}}
/>
)}
</TetheredSelectWrap.Creatable>
)}
</TetheredSelectWrap.Async>
);
}
}
export default TetheredSelect; |
Actually, I was able to shorten this a good bit using react-tether and react-dimensions: import React from 'react';
import Dimensions from 'react-dimensions';
import Select from 'react-select';
import TetherComponent from 'react-tether';
class TetheredSelectWrap extends Select {
constructor(props) {
super(props);
this.renderOuter = this._renderOuter;
}
componentDidMount() {
super.componentDidMount.call(this);
}
_renderOuter() {
const {containerWidth} = this.props;
const menu = super.renderOuter.apply(this, arguments);
// Don't return an updated menu render if we don't have one
if (!menu) {
return;
}
return (
<TetherComponent
renderElementTo="body"
ref="tethered-component"
attachment="top left"
targetAttachment="top left"
constraints={[{
to: 'window',
attachment: 'together',
pin: ['top']
}]}
>
{/* Apply position:static to our menu so that it's parent will get the correct dimensions and we can tether the parent */}
<div></div>
{React.cloneElement(menu, {style: {position: 'static', width: containerWidth}})}
</TetherComponent>
);
}
}
// Call the AsyncCreatable code from react-select with our extended tether class
class TetheredSelect extends React.Component {
render () {
return (
<TetheredSelectWrap.Async {...this.props}>
{(asyncProps) => (
<TetheredSelectWrap.Creatable {...this.props}>
{(creatableProps) => (
<TetheredSelectWrap
{...asyncProps}
{...creatableProps}
onInputChange={(input) => {
creatableProps.onInputChange(input);
return asyncProps.onInputChange(input);
}}
/>
)}
</TetheredSelectWrap.Creatable>
)}
</TetheredSelectWrap.Async>
);
}
}
export default Dimensions()(TetheredSelect); |
@stinoga That's cool! I didn't know that react-tether can replant elements to body |
Is anyone currently working on adding this either as default behaviour or as an option? @stinoga's solution seems to work well. |
@kamagatos Thank you for this! Made my life so much easier. One note, I was using this in a page that is scrollable and I had to make the following addition when setting the top so that it worked when scrolling: top: dimensions.top + dimensions.height + window.pageYOffset I agree that it would be great if this were somehow baked into react-select. Thanks again! |
@kamagatos using Portals is a way to go! The only problem with your example is that when the page is scrolled down, the menu will have the wrong top position: |
There is still one problem with the @kamagatos Portal solution: when you open your menu on a page with a lot of content(with browser scroll bar) and then we scroll with our browser, the menu stays open and floating. Anyone managed to close the react-select menu on browser scrolling? |
@guilleCM Not sure if this has changed since you posted, but onOpen fires before the Select-menu-outer element has been created, so you cannot apply the css to it. |
@MitchellONeill for me is working, and when I debug on the web browser, I can select the $('.Select-menu-outer') element and set the new css rules (previously, in the render method, I set position: fixed in the style inline declaration with react). Sorry about my english |
@spaja I managed to resolve this using a similar approach found within the Select2 jQuery package by creating an overlay which prevents scrolling on subcomponents and adjusting the height of the select dropdown relative to the window scroll. See below for reference: Select Component import React, {Fragment} from 'react';
import ReactSelect from 'react-select';
import 'react-select/dist/react-select.css';
export default class Select extends ReactSelect {
renderOuter(options, valueArray, focusedOption) {
const dimensions = this.wrapper ? this.wrapper.getBoundingClientRect() : null;
const menu = super.renderMenu(options, valueArray, focusedOption)
if (!menu || !dimensions) return null;
const maxHeight = document.body.offsetHeight - (dimensions.top + dimensions.height)
return ReactDOM.createPortal(
<Fragment>
<div className="Select-mask"></div>
<div
ref={ref => { this.menuContainer = ref }}
className="Select-menu-outer"
onClick={(e) => { e.stopPropagation() }}
style={{
...this.props.menuContainerStyle,
zIndex: 9999,
position: 'absolute',
width: dimensions.width,
top: window.scrollY + dimensions.top + dimensions.height,
left: window.scrollX + dimensions.left,
maxHeight: Math.min(maxHeight, 200),
overflow: 'hidden'
}}
>
<div
ref={ref => { this.menu = ref }}
role="listbox"
tabIndex={-1}
className="Select-menu"
id={`${this._instancePrefix}-list`}
style={{
...this.props.menuStyle,
maxHeight: Math.min(maxHeight, 200)
}}
onScroll={this.handleMenuScroll}
onMouseDown={this.handleMouseDownOnMenu}
>
{menu}
</div>
</div>
</Fragment>,
document.body
)
}
} Select CSS .Select-mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
} |
For v2 solution check #2439 |
Thanks @kamagatos and @Enigma007x , your suggestions using Portals helped alot! I'm using |
Thanks @Nehero your solution saved me a lot of work! 👍 I want to marry you hahaha |
@kamagatos with the React Portal solution do you have working touch events in your environment? I've tried your solution and the one from @Nehero but with both solutions I seem to be losing the touch event support and cannot get the selects to work on iPad or Chrome devtools device testing options. Anyone faced similar issues and if so, have you found any ways to work around those? |
Answering my own questions. It seems that in the
My guess is that because with both the To fix this I did override the
With my quick testing this seemed to work. However I don't know if this has any unwanted side effects |
@opami |
Does not work for me, added |
To me this issue gets worse when using Is there a way to trigger positioning computation manually? As far as I understand, Do you think exposing an API to trigger positioning computation could be useful in future releases? Something like the |
For now, I have manually overriden As mentioned by @crohn, I think it should be considered to allow explicitly overriding the menu placement logic. |
Did you try using menuPosition="fixed" like that: |
@andreifg1 menuPosition="fixed" did the trick on v3 👍 |
for me this answer worked. but the dropdown comonent was not taking the class specified i.e |
Hi all, In an effort to sustain the However, if you feel this issue is still relevant and you'd like us to review it - please leave a comment and we'll do our best to get back to you, and we'll re-open it if necessary. |
I recently ran into this issue when using
react-select
components in a modal. For mobile the contents of the modal are scrollable. In this particular case, theSelect
was at the bottom of the content. Activating theSelect
made the container overflow and scrollable.Maybe the
container
notion of http://react-bootstrap.github.io/react-overlays/examples/ might make sense?Alternatively, maybe make use of https://github.com/souporserious/react-tether?
The text was updated successfully, but these errors were encountered: