diff --git a/babel-build.config.js b/babel-build.config.js
new file mode 100644
index 000000000..a336d504f
--- /dev/null
+++ b/babel-build.config.js
@@ -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'
+ }
+ }
+];
+
+module.exports = {
+ ...rootBabel,
+ plugins: [...rootBabel.plugins, popper2AliasRemovalPlugin]
+};
diff --git a/devtools/buildIndexFiles.js b/devtools/buildIndexFiles.js
index 779625ad2..6f368a9b9 100644
--- a/devtools/buildIndexFiles.js
+++ b/devtools/buildIndexFiles.js
@@ -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));
diff --git a/devtools/buildVisualStories.js b/devtools/buildVisualStories.js
index 269fa5a3a..3626e2c83 100644
--- a/devtools/buildVisualStories.js
+++ b/devtools/buildVisualStories.js
@@ -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));
diff --git a/package-lock.json b/package-lock.json
index 8bc359a1f..e2dbe803a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9536,6 +9536,39 @@
"babel-helper-is-void-0": "^0.4.3"
}
},
+ "babel-plugin-module-resolver": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.0.0.tgz",
+ "integrity": "sha512-3pdEq3PXALilSJ6dnC4wMWr0AZixHRM4utpdpBR9g5QG7B7JwWyukQv7a9hVxkbGFl+nQbrHDqqQOIBtTXTP/Q==",
+ "dev": true,
+ "requires": {
+ "find-babel-config": "^1.2.0",
+ "glob": "^7.1.6",
+ "pkg-up": "^3.1.0",
+ "reselect": "^4.0.0",
+ "resolve": "^1.13.1"
+ },
+ "dependencies": {
+ "pkg-up": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
+ "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ },
+ "resolve": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+ "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ }
+ }
+ },
"babel-plugin-named-asset-import": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.6.tgz",
@@ -14795,6 +14828,24 @@
"unpipe": "~1.0.0"
}
},
+ "find-babel-config": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz",
+ "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==",
+ "dev": true,
+ "requires": {
+ "json5": "^0.5.1",
+ "path-exists": "^3.0.0"
+ },
+ "dependencies": {
+ "json5": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
+ "dev": true
+ }
+ }
+ },
"find-cache-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
@@ -26849,6 +26900,7 @@
"version": "npm:react-popper@2.2.3",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.2.3.tgz",
"integrity": "sha512-mOEiMNT1249js0jJvkrOjyHsGvqcJd3aGW/agkiMoZk3bZ1fXN1wQszIQSjHIai48fE67+zwF8Cs+C4fWqlfjw==",
+ "dev": true,
"requires": {
"react-fast-compare": "^3.0.1",
"warning": "^4.0.2"
@@ -26857,7 +26909,8 @@
"react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
- "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
+ "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==",
+ "dev": true
}
}
},
@@ -27924,6 +27977,12 @@
"integrity": "sha1-5UBLgVV+91225JxacgBIk/4D4WI=",
"dev": true
},
+ "reselect": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
+ "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==",
+ "dev": true
+ },
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
diff --git a/package.json b/package.json
index 515eeea96..02ec93af4 100644
--- a/package.json
+++ b/package.json
@@ -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",
@@ -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"
},
@@ -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",
@@ -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",
diff --git a/src/DatePicker/__stories__/DatePicker.stories.js b/src/DatePicker/__stories__/DatePicker.stories.js
index 6a0a35239..8cf6aaa70 100644
--- a/src/DatePicker/__stories__/DatePicker.stories.js
+++ b/src/DatePicker/__stories__/DatePicker.stories.js
@@ -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,
@@ -13,6 +12,7 @@ import {
text,
withKnobs
} from '@storybook/addon-knobs';
+import React, { useEffect, useRef, useState } from 'react';
export default {
title: 'Component API/DatePicker',
@@ -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 (
+
+ );
+};
+
export const disabled = () => (
);
diff --git a/src/DatePicker/__stories__/__snapshots__/DatePicker.stories.storyshot b/src/DatePicker/__stories__/__snapshots__/DatePicker.stories.storyshot
index b0c2e66e4..44ec7e52c 100644
--- a/src/DatePicker/__stories__/__snapshots__/DatePicker.stories.storyshot
+++ b/src/DatePicker/__stories__/__snapshots__/DatePicker.stories.storyshot
@@ -1426,3 +1426,83 @@ exports[`Storyshots Component API/DatePicker Weekday Start (Monday Start) 1`] =
`;
+
+exports[`Storyshots Component API/DatePicker With Custom Flip Container 1`] = `
+
+`;
diff --git a/src/Popover/Popover.js b/src/Popover/Popover.js
index c40d9893a..8499d557e 100644
--- a/src/Popover/Popover.js
+++ b/src/Popover/Popover.js
@@ -97,6 +97,8 @@ class Popover extends Component {
disableEdgeDetection,
disableKeyPressHandler,
disableTriggerOnClick,
+ fallbackPlacements,
+ flipContainer,
firstFocusIndex,
onClickOutside,
onEscapeKey,
@@ -171,6 +173,8 @@ class Popover extends Component {
', () => {
});
});
+ 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();
diff --git a/src/Popover/__stories__/Popover.stories.js b/src/Popover/__stories__/Popover.stories.js
index 7242f16a3..0a1f55fb7 100644
--- a/src/Popover/__stories__/Popover.stories.js
+++ b/src/Popover/__stories__/Popover.stories.js
@@ -5,11 +5,14 @@ import Dialog from '../../Dialog/Dialog';
import Icon from '../../Icon/Icon';
import Menu from '../../Menu/Menu';
import Popover from '../Popover';
+import Popper from '../../utils/_Popper';
+import { POPPER_PLACEMENTS } from '../../utils/constants';
import {
+ array,
boolean,
select
} from '@storybook/addon-knobs';
-import React, { useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
export default {
title: 'Component API/Popover',
@@ -205,6 +208,36 @@ export const disableEdgeDetection = () => (
>
);
+export const withCustomFlipContainer = () => {
+ const containerRef = useRef();
+ const [container, setContainer] = useState();
+
+ useEffect(() => {
+ setContainer(containerRef.current);
+ });
+
+ return (
+
+
+
+
}
+ fallbackPlacements={['left', 'right', 'top']}
+ flipContainer={container}
+ placement='bottom' />
+
+
+ );
+};
+
export const noArrow = () => (
<>
{
);
};
-export const dev = () => (
- }
- disableEdgeDetection={boolean('disableEdgeDetection', false)}
- disableKeyPressHandler={boolean('disableKeyPressHandler', false)}
- disabled={boolean('disabled', false)}
- noArrow={boolean('noArrow', false)}
- placement={select('placement', {
- 'bottom-start': 'bottom-start',
- 'bottom': 'bottom',
- 'bottom-end': 'bottom-end',
- 'left-start': 'left-start',
- 'left': 'left',
- 'left-end': 'left-end',
- 'right-start': 'right-start',
- 'right': 'right',
- 'right-end': 'right-end',
- 'top-start': 'top-start',
- 'top': 'top',
- 'top-end': 'top-end'
- })}
- type={select('type', {
- 'dialog': 'dialog',
- 'grid': 'grid',
- 'listbox': 'listbox',
- 'menu': 'menu',
- 'tree': 'tree'
- })}
- useArrowKeyNavigation={boolean('useArrowKeyNavigation', false)}
- widthSizingType={select('widthSizingType', {
- 'none': 'none',
- 'matchTarget': 'matchTarget',
- 'minTarget': 'minTarget',
- 'maxTarget': 'maxTarget'
- })} />
-);
+export const dev = () => {
+ const containerRef = useRef();
+ const [container, setContainer] = useState();
+
+ useEffect(() => {
+ setContainer(containerRef.current);
+ });
+
+ const useContainer = boolean('Use flip container', false);
+
+ const popover = (
+ }
+ disableEdgeDetection={boolean('disableEdgeDetection', false)}
+ disableKeyPressHandler={boolean('disableKeyPressHandler', false)}
+ disabled={boolean('disabled', false)}
+ fallbackPlacements={array('fallbackPlacements',
+ Popper.defaultProps.fallbackPlacements
+ )}
+ // eslint-disable-next-line no-undefined
+ flipContainer={useContainer ? container : undefined}
+ noArrow={boolean('noArrow', false)}
+ placement={select(
+ 'placement',
+ POPPER_PLACEMENTS.reduce((a, b) => ({ ...a, [b]: b }), {}),
+ 'bottom-start'
+ )}
+ type={select('type', {
+ 'dialog': 'dialog',
+ 'grid': 'grid',
+ 'listbox': 'listbox',
+ 'menu': 'menu',
+ 'tree': 'tree'
+ })}
+ useArrowKeyNavigation={boolean('useArrowKeyNavigation', false)}
+ widthSizingType={select('widthSizingType', {
+ 'none': 'none',
+ 'matchTarget': 'matchTarget',
+ 'minTarget': 'minTarget',
+ 'maxTarget': 'maxTarget'
+ })} />
+ );
+
+ if (useContainer) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {popover}
+
+ );
+};
dev.parameters = {
docs: { disable: true }
};
diff --git a/src/Popover/__stories__/__snapshots__/Popover.stories.storyshot b/src/Popover/__stories__/__snapshots__/Popover.stories.storyshot
index 5f46369d9..c4ac1772b 100644
--- a/src/Popover/__stories__/__snapshots__/Popover.stories.storyshot
+++ b/src/Popover/__stories__/__snapshots__/Popover.stories.storyshot
@@ -5,27 +5,35 @@ exports[`Storyshots Component API/Popover Dev 1`] = `
dir="ltr"
>
@@ -814,3 +822,62 @@ exports[`Storyshots Component API/Popover Width Sizing Types 1`] = `
`;
+
+exports[`Storyshots Component API/Popover With Custom Flip Container 1`] = `
+
+`;
diff --git a/src/utils/_Popper.js b/src/utils/_Popper.js
index caa535a0c..a5365b0ed 100644
--- a/src/utils/_Popper.js
+++ b/src/utils/_Popper.js
@@ -17,23 +17,35 @@ const defaultModifiers = [
}
},
{
- name: 'preventOverflow',
- options: {
- boundary: []
- }
+ name: 'preventOverflow'
},
{
name: 'hide' // adds the isReferenceHidden attribute
- },
- {
- name: 'flip',
- options: {
- boundary: [] // https://github.com/popperjs/popper-core/issues/1110
- }
+
+
}
];
-const disableEdgeDetectionModifier = { name: 'flip', enabled: false };
+const createFlipModifier = ({
+ disableEdgeDetection,
+ fallbackPlacements,
+ flipContainer
+}) => {
+ if (disableEdgeDetection) {
+ return {
+ name: 'flip',
+ enabled: false
+ };
+ }
+
+ return {
+ name: 'flip',
+ options: {
+ fallbackPlacements,
+ boundary: flipContainer
+ }
+ };
+};
const matchTargetModifier = {
name: 'matchTargetModifier',
@@ -128,6 +140,8 @@ class Popper extends React.Component {
children,
cssBlock,
disableEdgeDetection,
+ fallbackPlacements,
+ flipContainer,
innerRef,
noArrow,
onClickOutside,
@@ -145,11 +159,9 @@ class Popper extends React.Component {
const modifiers = [
...defaultModifiers,
+ createFlipModifier({ disableEdgeDetection, fallbackPlacements, flipContainer }),
...popperModifiers
];
- if (disableEdgeDetection) {
- modifiers.push(disableEdgeDetectionModifier);
- }
if (widthSizingType === 'matchTarget') {
modifiers.push(matchTargetModifier);
}
@@ -235,6 +247,11 @@ Popper.propTypes = {
cssBlock: PropTypes.string.isRequired,
referenceComponent: PropTypes.element.isRequired,
disableEdgeDetection: PropTypes.bool,
+ fallbackPlacements: PropTypes.arrayOf(PropTypes.oneOf(POPPER_PLACEMENTS)),
+ flipContainer: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.instanceOf(Element)),
+ PropTypes.instanceOf(Element)
+ ]),
innerRef: PropTypes.func,
noArrow: PropTypes.bool,
popperClassName: PropTypes.string,
@@ -252,6 +269,7 @@ Popper.propTypes = {
};
Popper.defaultProps = {
+ fallbackPlacements: ['bottom-start', 'top-start', 'bottom-end', 'top-end'],
popperModifiers: [],
popperPlacement: 'bottom-start',
onClickOutside: () => { },
diff --git a/src/utils/constants.js b/src/utils/constants.js
index 03ddd5fea..1cfa6bea6 100644
--- a/src/utils/constants.js
+++ b/src/utils/constants.js
@@ -73,6 +73,9 @@ export const POPOVER_TYPES = [
];
export const POPPER_PLACEMENTS = [
+ 'auto',
+ 'auto-start',
+ 'auto-end',
'bottom-start',
'bottom',
'bottom-end',
diff --git a/storybook-testing/__image_snapshots__/DatePicker-snap.png b/storybook-testing/__image_snapshots__/DatePicker-snap.png
index 1d261b441..f11f52abf 100644
Binary files a/storybook-testing/__image_snapshots__/DatePicker-snap.png and b/storybook-testing/__image_snapshots__/DatePicker-snap.png differ
diff --git a/storybook-testing/__image_snapshots__/Popover-snap.png b/storybook-testing/__image_snapshots__/Popover-snap.png
index 9cc778552..13b8df689 100644
Binary files a/storybook-testing/__image_snapshots__/Popover-snap.png and b/storybook-testing/__image_snapshots__/Popover-snap.png differ
diff --git a/storybook-testing/__image_snapshots__/Shellbar-snap.png b/storybook-testing/__image_snapshots__/Shellbar-snap.png
index a6ec5ee53..a70c38542 100644
Binary files a/storybook-testing/__image_snapshots__/Shellbar-snap.png and b/storybook-testing/__image_snapshots__/Shellbar-snap.png differ