Skip to content
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

Expand and scroll container when opening menu #3669

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"baseUrl": "http://localhost:8000/cypress-tests",
"screenshotOnHeadlessFailure": false,
"videoRecording": false
"video": false
}
64 changes: 64 additions & 0 deletions cypress/integration/menu_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import baseSelectors from '../fixtures/selectors.json';

const selector = {
caseDefault: '[data-cy="case-default"]',
caseExpandDown: '[data-cy="case-expand-down"]',
scrollContainer: '[data-cy="scroll-container"]',
};

Cypress.Screenshot.defaults({
screenshotOnRunFailure: false,
});

describe('Menus', () => {
before(function() {
cy.visit('http://localhost:8000/cypress-menu-tests');
});

it('test page renders successfully', () => {
cy.get('h1').should('contain', 'Test Page for React-Select Menus');
});

describe('Inside scroll containers', () => {
describe('by default', () => {
it('menu should be visible when select is clicked', () => {
cy.get(selector.caseDefault)
.find('.react-select__control')
.click()
.get(selector.caseDefault)
.find(baseSelectors.menu)
.should('be.visible');
});
});

describe('At bottom of container', () => {
it('menu should be visible when select is clicked and should expand container', () => {
cy.get(selector.caseExpandDown)
.find(selector.scrollContainer)
.then(scrollContainer => {
expect(scrollContainer).to.have.length(1);
const scrollContainerElement = scrollContainer[0];
// Start out at top
expect(scrollContainerElement.scrollTop).to.equal(0);
// Save height so we can tell if it grows later
const originalHeight = scrollContainerElement.scrollHeight;

cy.get(selector.caseExpandDown)
.find('.react-select__control')
.click()
.get(selector.caseExpandDown)
.find(baseSelectors.menu)
.should('be.visible')
.then(() => {
// Make sure the container has expanded
expect(scrollContainerElement.scrollHeight).to.be.gt(
originalHeight
);
// And that it's not scrolled to top anymore
expect(scrollContainerElement.scrollTop).to.be.gt(0);
});
});
});
});
});
});
4 changes: 4 additions & 0 deletions cypress/integration/select_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ const selector = require('../fixtures/selectors.json');

const viewport = ['macbook-15', 'iphone-6'];

Cypress.Screenshot.defaults({
screenshotOnRunFailure: false
});

describe('New Select', function() {
before(function() {
cy.visit('http://localhost:8000/cypress-tests');
Expand Down
2 changes: 2 additions & 0 deletions docs/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import Section from './Section';
import PageNav from './PageNav';
import Tests from '../Tests';
import TestsMenu from '../TestsMenu';

const sections = [
{ label: 'Home', path: '/home' },
Expand All @@ -37,6 +38,7 @@ export default class App extends Component<*> {
<BrowserRouter>
<Switch>
<Route exact path="/cypress-tests" component={Tests} />
<Route exact path="/cypress-menu-tests" component={TestsMenu} />
<Route>
<div>
<Header>
Expand Down
51 changes: 51 additions & 0 deletions docs/TestsMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// @flow

import * as React from 'react';
import Select from 'react-select';
import { stateOptions } from './data';
import { H1, H2, Note, ScrollContainer } from './styled-components';

const defaultSelectProps = {
maxMenuHeight: 250,
classNamePrefix: 'react-select',
placeholder: 'Choose a state…',
options: stateOptions,
};

export default function TestsMenu() {
return (
<div
style={{
margin: 'auto',
maxWidth: 440,
padding: 20,
}}
>
<H1>Test Page for React-Select Menus</H1>
<H2>Inside scroll containers</H2>

<div data-cy="case-default">
<h3>Default, plenty of height</h3>
<ScrollContainer height={defaultSelectProps.maxMenuHeight + 100}>
<Select {...defaultSelectProps} />
</ScrollContainer>
<Note>
Nothing special going on here, just the base case of opening a menu in
a scroll container
</Note>
</div>

<div data-cy="case-expand-down">
<h3>Expand and Scroll Down</h3>
<ScrollContainer height={400}>
<div style={{ height: 300 }} />
<Select {...defaultSelectProps} />
</ScrollContainer>
<Note>
If the menu doesn’t have enough room to open down, by default the
container should expand down
</Note>
</div>
</div>
);
}
41 changes: 38 additions & 3 deletions docs/styled-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/** @jsx emotionJSX */
import { jsx as emotionJSX } from '@emotion/core';

import * as React from 'react';
import SyntaxHighlighter, {
registerLanguage,
} from 'react-syntax-highlighter/prism-light';
Expand Down Expand Up @@ -49,13 +50,19 @@ export const Note = ({ Tag = 'div', ...props }: { Tag?: string }) => (
export const H1 = (props: any) => <h1 css={{ marginTop: 0 }} {...props} />;
export const H2 = (props: any) => <h2 css={{ marginTop: '2em' }} {...props} />;

export const ColorSample = ({ name, color }: { color: string, name: string }) => (
export const ColorSample = ({
name,
color,
}: {
color: string,
name: string,
}) => (
<div
css={{
display: 'inline-flex',
marginBottom: '0.5em',
alignItems: 'center',
minWidth: '10em'
minWidth: '10em',
}}
>
<span
Expand All @@ -66,13 +73,41 @@ export const ColorSample = ({ name, color }: { color: string, name: string }) =>
borderRadius: 3,
width: '1em',
height: '1em',
backgroundColor: color
backgroundColor: color,
}}
/>
<Code>{name}</Code>
</div>
);

type ScrollContainerProps = {
children: React.Node,
height?: number | string,
width?: number | string,
};

export const ScrollContainer = ({
children,
height,
width,
}: ScrollContainerProps) => {
return (
<div
data-cy="scroll-container"
css={{
overflow: 'scroll',
height: height || 'auto',
width: width || '100%',
border: '1px solid hsl(0, 0%, 40%)',
position: 'relative',
boxSizing: 'border-box',
}}
>
{children}
</div>
);
};

// ==============================
// Code
// ==============================
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"coveralls": "^2.11.12",
"cross-env": "^5.1.3",
"css-loader": "^0.28.7",
"cypress": "^1.4.1",
"cypress": "^3.3.2",
"dataloader": "^1.4.0",
"dotenv": "^7.0.0",
"emotion": "^9.1.2",
Expand Down Expand Up @@ -97,6 +97,7 @@
"test": "npm run test:jest && npm run test:cypress",
"test:jest": "jest --coverage",
"e2e": "concurrently --kill-others --success=first --names 'SERVER,E2E' 'yarn start --progress=false --no-info' 'yarn test:cypress'",
"e2e-watch": "concurrently --kill-others --success=first --names 'SERVER,E2E' 'yarn start --progress=false --no-info' 'yarn test:cypress-watch'",
"test:cypress": "cypress run --spec ./cypress/integration/select_spec.js",
"test:cypress-watch": "node ./node_modules/.bin/cypress open",
"precommit": "flow check && lint-staged",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-select/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"react-transition-group": "^2.2.1"
},
"devDependencies": {
"cypress": "^1.4.1",
"cypress": "^3.3.2",
"enzyme": "^3.8.0",
"enzyme-to-json": "^3.3.0",
"jest-in-case": "^1.0.2",
Expand Down
15 changes: 11 additions & 4 deletions packages/react-select/src/components/Menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,17 @@ export function getMenuPlacement({

// we can't trust `scrollParent.scrollHeight` --> it may increase when
// the menu is rendered
const { height: scrollHeight } = scrollParent.getBoundingClientRect();
const { height: scrollHeight, top: scrollParentTop } = scrollParent.getBoundingClientRect();
const {
bottom: menuBottom,
height: menuHeight,
top: menuTop,
top: menuClientTop,
} = menuEl.getBoundingClientRect();
// If not fixed position, need to possibly adjust based on scroll parent
const menuTop = isFixedPosition ? menuClientTop : menuClientTop - scrollParentTop;

const { top: containerTop } = menuEl.offsetParent.getBoundingClientRect();
const viewHeight = window.innerHeight;
const viewHeight = isFixedPosition ? window.innerHeight : scrollHeight;
const scrollTop = getScrollTop(scrollParent);

const marginBottom = parseInt(getComputedStyle(menuEl).marginBottom, 10);
Expand Down Expand Up @@ -146,7 +148,12 @@ export function getMenuPlacement({
// BOTTOM: allow browser to increase scrollable area and immediately set scroll
if (placement === 'bottom') {
scrollTo(scrollParent, scrollDown);
return { placement: 'bottom', maxHeight };
// We might also need to constrain the height
let constrainedHeight = maxHeight;
if (maxHeight > scrollHeight && !isFixedPosition) {
constrainedHeight = scrollHeight - marginTop - marginBottom;
};
return { placement: 'bottom', maxHeight: constrainedHeight };
}
break;
case 'top':
Expand Down
Loading