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

feat: Allow a custom flip boundary container for Popover #1122

Merged
merged 11 commits into from
Jul 14, 2020
Merged
16 changes: 16 additions & 0 deletions babel-build.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const rootBabel = require('./babel.config');

// TODO: Remove this once https://github.com/storybookjs/storybook/issues/11246 is fixed
const popper2AliasRemovalPlugin = [
'babel-plugin-module-resolver',
{
alias: {
'react-popper-2': 'react-popper'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm jumping through hoops to get the generated output to look like

var _reactPopper = require("react-popper");

instead of

var _reactPopper = require("react-popper-2");

and still have the examples work in storybook

}
}
];

module.exports = {
...rootBabel,
plugins: [...rootBabel.plugins, popper2AliasRemovalPlugin]
};
4 changes: 4 additions & 0 deletions devtools/buildIndexFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ const path = require('path');

const srcPath = path.join(__dirname, '../src');

// Ignore errors from the browser API not being defined in node
// eslint-disable-next-line no-undefined
global.Element = undefined;

const isComponentDirectory = (source) => {
const ignoredDirectories = ['utils', 'Docs'];
return lstatSync(source).isDirectory() && !ignoredDirectories.some(ignored => source.includes(ignored));
Expand Down
4 changes: 4 additions & 0 deletions devtools/buildVisualStories.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ const path = require('path');

const srcPath = path.join(__dirname, '../src');

// Ignore errors from the browser API not being defined in node
// eslint-disable-next-line no-undefined
global.Element = undefined;

const isComponentDirectory = (source) => {
const ignoredDirectories = ['utils', 'Docs'];
return lstatSync(source).isDirectory() && !ignoredDirectories.some(ignored => source.includes(ignored));
Expand Down
61 changes: 60 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"scripts": {
"build:index": "babel-node devtools/buildIndexFiles.js",
"build:storybook": "babel-node devtools/buildVisualStories.js && babel-node scripts/copy-assets.js",
"build:cjs": "cross-env NODE_ENV=production BABEL_ENV=cjs babel src --ignore \"src/**/*.test.js\",\"src/**/*.stories.js\",\"src/Docs/*.stories.mdx\" --out-dir lib",
"build:cjs": "cross-env NODE_ENV=production BABEL_ENV=cjs babel src --config-file ./babel-build.config.js --ignore \"src/**/*.test.js\",\"src/**/*.stories.js\",\"src/Docs/*.stories.mdx\" --out-dir lib",
"build": "npm run build:index && rm -rf lib && npm run build:cjs",
"deploy": "gh-pages -d storybook-static",
"dry-run": "npm run build && npm publish --dry-run",
Expand Down Expand Up @@ -59,7 +59,6 @@
"react-focus-lock": "^2.3.1",
"react-overlays": "^3.2.0",
"react-popper": "^2.2.3",
"react-popper-2": "npm:react-popper@^2.2.3",
"shortid": "^2.2.14",
"tabbable": "^4.0.0"
},
Expand Down Expand Up @@ -92,6 +91,7 @@
"babel-jest": "^24.8.0",
"babel-loader": "8.1.0",
"babel-plugin-client-only-require": "^1.0.1",
"babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-named-asset-import": "^0.3.2",
"babel-plugin-require-context-hook": "^1.0.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.23",
Expand Down Expand Up @@ -123,6 +123,7 @@
"puppeteer": "^3.3.0",
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react-popper-2": "npm:react-popper@^2.2.3",
"react-router-dom": "^5.2.0",
"react-syntax-highlighter": "^12.2.1",
"react-test-renderer": "^16.8.0",
Expand Down
27 changes: 26 additions & 1 deletion src/DatePicker/__stories__/DatePicker.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import DatePicker from '../DatePicker';
import FormLabel from '../../Forms/FormLabel';
import LayoutGrid from '../../LayoutGrid/LayoutGrid';
import moment from 'moment';
import React from 'react';
import {
boolean,
date,
Expand All @@ -13,6 +12,7 @@ import {
text,
withKnobs
} from '@storybook/addon-knobs';
import React, { useEffect, useRef, useState } from 'react';

export default {
title: 'Component API/DatePicker',
Expand Down Expand Up @@ -72,6 +72,31 @@ export const compact = () => (

compact.storyName = 'Compact';

export const withCustomFlipContainer = () => {
const containerRef = useRef();
const [flipContainer, setFlipContainer] = useState();

useEffect(() => {
setFlipContainer(containerRef.current);
});

return (
<div style={{ alignItems: 'center', display: 'flex' }}>
<div ref={containerRef} style={{
border: '1px solid black',
padding: '420px 40px 450px 240px'
}}>
<DatePicker popoverProps={{ flipContainer }} />
</div>
<div style={{
backgroundColor: '#444',
width: '180px',
height: '120px'
}} />
</div>
);
};

export const disabled = () => (
<DatePicker disabled />
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1426,3 +1426,83 @@ exports[`Storyshots Component API/DatePicker Weekday Start (Monday Start) 1`] =
</div>
</div>
`;

exports[`Storyshots Component API/DatePicker With Custom Flip Container 1`] = `
<div
dir="ltr"
>
<div
style={
Object {
"alignItems": "center",
"display": "flex",
}
}
>
<div
style={
Object {
"border": "1px solid black",
"padding": "420px 40px 450px 240px",
}
}
>
<div
defaultValue=""
onChange={[Function]}
onFocus={[Function]}
>
<div
className="fd-popover"
>
<div
onFocus={[Function]}
onMouseDown={[Function]}
onTouchStart={[Function]}
>
<div
className="fd-popover__control"
>
<div
aria-expanded={false}
aria-haspopup="true"
className="fd-input-group--control fd-input-group"
>
<input
className="fd-input fd-input-group__input"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyPress={[Function]}
placeholder="MM/DD/YYYY"
type="text"
value=""
/>
<span
className="fd-input-group__addon fd-input-group__addon--button"
>
<button
aria-label="Choose date"
className="fd-button fd-button--transparent sap-icon--appointment-2 fd-input-group__button"
onClick={[Function]}
type="button"
/>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div
style={
Object {
"backgroundColor": "#444",
"height": "120px",
"width": "180px",
}
}
/>
</div>
</div>
`;
11 changes: 11 additions & 0 deletions src/Popover/Popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ class Popover extends Component {
disableEdgeDetection,
disableKeyPressHandler,
disableTriggerOnClick,
fallbackPlacements,
flipContainer,
firstFocusIndex,
onClickOutside,
onEscapeKey,
Expand Down Expand Up @@ -171,6 +173,8 @@ class Popover extends Component {
<Popper
cssBlock='fd-popover'
disableEdgeDetection={disableEdgeDetection}
fallbackPlacements={fallbackPlacements}
flipContainer={flipContainer}
innerRef={innerRef}
noArrow={noArrow}
onClickOutside={chain(this.handleOutsideClick, onClickOutside)}
Expand Down Expand Up @@ -209,8 +213,15 @@ Popover.propTypes = {
/** Set to **true** to remove default triggerBody handler used in onClick.
* Useful for when a custom method is desired to open the Popover */
disableTriggerOnClick: PropTypes.bool,
/** Where else to position the popover when the original placement is out of bounds */
fallbackPlacements: PropTypes.arrayOf(PropTypes.oneOf(POPPER_PLACEMENTS)),
/** Index of the focusable item to focus first within the Popover */
firstFocusIndex: PropTypes.number,
/** The bounding container to use when determining if the popover is out of bounds */
flipContainer: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.instanceOf(Element)),
PropTypes.instanceOf(Element)
]),
/** Set to **true** to render a popover without an arrow */
noArrow: PropTypes.bool,
/** 'bottom-start',
Expand Down
46 changes: 46 additions & 0 deletions src/Popover/Popover.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Icon from '../Icon/Icon';
import Menu from '../Menu/Menu';
import { mount } from 'enzyme';
import Popover from './Popover';
import Popper from '../utils/_Popper';
import React from 'react';
import { Popper as ReactPopper } from 'react-popper-2';

Expand Down Expand Up @@ -234,6 +235,51 @@ describe('<Popover />', () => {
});
});

describe('flip modifiers', () => {
test('disableEdgeDetection props', () => {
const popoverWithParent = mount(popOver).setProps({ disableEdgeDetection: true });
const popperComponent = popoverWithParent.find(ReactPopper);
const flipModifier = popperComponent.props().modifiers.find(m => m.name === 'flip');
expect(flipModifier.enabled).toBe(false);
});

test('disableEdgeDetection default', () => {
const popoverWithParent = mount(popOver).setProps({ });
const popperComponent = popoverWithParent.find(ReactPopper);
const flipModifier = popperComponent.props().modifiers.find(m => m.name === 'flip');
expect(flipModifier.enabled).not.toBeDefined();
});

test('fallback placements props', () => {
const popoverWithParent = mount(popOver).setProps({ fallbackPlacements: ['top'] });
const popperComponent = popoverWithParent.find(ReactPopper);
const flipModifier = popperComponent.props().modifiers.find(m => m.name === 'flip');
expect(flipModifier.options.fallbackPlacements).toEqual(['top']);
});

test('fallback placements default', () => {
const popoverWithParent = mount(popOver).setProps({ });
const popperComponent = popoverWithParent.find(ReactPopper);
const flipModifier = popperComponent.props().modifiers.find(m => m.name === 'flip');
expect(flipModifier.options.fallbackPlacements).toEqual(Popper.defaultProps.fallbackPlacements);
});

test('flip container props', () => {
const boundary = document.createElement('div');
const popoverWithParent = mount(popOver).setProps({ flipContainer: boundary });
const popperComponent = popoverWithParent.find(ReactPopper);
const flipModifier = popperComponent.props().modifiers.find(m => m.name === 'flip');
expect(flipModifier.options.boundary).toBe(boundary);
});

test('flip container default', () => {
const popoverWithParent = mount(popOver).setProps({ });
const popperComponent = popoverWithParent.find(ReactPopper);
const flipModifier = popperComponent.props().modifiers.find(m => m.name === 'flip');
expect(flipModifier.options.boundary).not.toBeDefined();
});
});

describe('Callback handler', () => {
test('should dispatch the onClickOutside callback with the event', () => {
let f = jest.fn();
Expand Down
Loading