diff --git a/packages/components/package.json b/packages/components/package.json
index 6af8d5c..a36dc3c 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -74,7 +74,10 @@
"ts-jest": "^26.1.4",
"ts-loader": "^8.0.1",
"typescript": "^3.9.7",
- "webpack": "^4.44.1"
+ "webpack": "^4.44.1",
+ "@popperjs/core": "^2.4.4",
+ "lodash": "^4.17.19",
+ "react-popper-2": "npm:react-popper@^2.2.3"
},
"scripts": {
"start": "start-storybook -p 9001 -c ./.storybook",
@@ -94,6 +97,6 @@
"build:storybook": "build-storybook -c .storybook -o dist-storybook/"
},
"dependencies": {
- "lodash": "^4.17.19"
+
}
}
diff --git a/packages/components/src/components/Popper/index.ts b/packages/components/src/components/Popper/index.ts
new file mode 100644
index 0000000..e95dd72
--- /dev/null
+++ b/packages/components/src/components/Popper/index.ts
@@ -0,0 +1 @@
+export { Popper } from './src/Popper';
diff --git a/packages/components/src/components/Popper/src/Popper.stories.tsx b/packages/components/src/components/Popper/src/Popper.stories.tsx
new file mode 100644
index 0000000..e4fe71e
--- /dev/null
+++ b/packages/components/src/components/Popper/src/Popper.stories.tsx
@@ -0,0 +1,47 @@
+import React, { useState } from 'react';
+import { withKnobs, text, select, boolean } from '@storybook/addon-knobs';
+import { Popper, PopperPlacement } from './Popper';
+import { Button } from '../../Button';
+
+export default {
+ title: 'Popper',
+ decorators: [withKnobs],
+};
+
+const placements: PopperPlacement[] = [
+ 'auto',
+ 'auto-start',
+ 'auto-end',
+ 'top',
+ 'bottom',
+ 'right',
+ 'left',
+ 'top-start',
+ 'top-end',
+ 'bottom-start',
+ 'bottom-end',
+ 'right-start',
+ 'right-end',
+ 'left-start',
+ 'left-end',
+];
+
+export const Default = () => {
+ const [isShown, setShown] = useState(false);
+ return (
+
+
setShown((prev) => !prev)}>trigger
+ }
+ placement={select('placement', placements, 'auto')}
+ >
+ {isShown ? popperElement
: undefined}
+
+
+ );
+};
diff --git a/packages/components/src/components/Popper/src/Popper.tsx b/packages/components/src/components/Popper/src/Popper.tsx
new file mode 100644
index 0000000..040c02e
--- /dev/null
+++ b/packages/components/src/components/Popper/src/Popper.tsx
@@ -0,0 +1,73 @@
+import React, { useState, useRef, useMemo } from 'react';
+import { Placement, Options } from '@popperjs/core';
+import { usePopper } from 'react-popper-2';
+import { Portal } from '../../Portal';
+
+export type PopperPlacement = Placement;
+export interface PopperProps {
+ trigger: React.ReactElement;
+ children?: React.ReactElement;
+ /** Formatted like "0, 8px" — how far to offset the Popper from the Reference. Changes automatically based on the placement */
+ offset?: [number | null | undefined, number | null | undefined];
+ placement?: Placement;
+}
+
+export const Popper: React.FC = ({
+ trigger,
+ children,
+ placement = 'bottom',
+ offset = [0, 8],
+}) => {
+ const referenceElement = useRef();
+ const popperElement = useRef();
+
+ const options: Options = useMemo(() => {
+ return {
+ placement: placement,
+ strategy: 'fixed',
+ modifiers: [
+ {
+ name: 'hide',
+ },
+ {
+ name: 'offset',
+ options: {
+ offset: offset,
+ },
+ },
+ {
+ name: 'preventOverflow',
+ options: {
+ altAxis: true,
+ padding: 40,
+ },
+ },
+ ],
+ };
+ }, [placement, offset]);
+
+ const { styles, attributes } = usePopper(
+ referenceElement.current,
+ popperElement.current,
+ options,
+ );
+
+ return (
+
+ {/** trigger element */}
+ {React.cloneElement(trigger, {
+ ...trigger.props,
+ ref: referenceElement,
+ })}
+
+ {children &&
+ React.cloneElement(children, {
+ style: styles.popper,
+ ...attributes.popper,
+ ...children.props,
+ ref: popperElement,
+ })}
+
+
+ );
+};
diff --git a/packages/components/src/components/index.ts b/packages/components/src/components/index.ts
index 128da57..20b2567 100644
--- a/packages/components/src/components/index.ts
+++ b/packages/components/src/components/index.ts
@@ -19,3 +19,4 @@ export { Table } from './Table';
export { Stack, StackingContext } from './Stack';
export { Backdrop } from './Backdrop';
+export { Popper } from './Popper';
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index be650d1..0ed56c6 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -20,4 +20,5 @@ export {
Typography,
TypographyContext,
Paragraph,
+ Popper,
} from './components';
diff --git a/packages/components/yarn.lock b/packages/components/yarn.lock
index 48d4e21..5bd87e9 100644
--- a/packages/components/yarn.lock
+++ b/packages/components/yarn.lock
@@ -1574,6 +1574,11 @@
"@nodelib/fs.scandir" "2.1.3"
fastq "^1.6.0"
+"@popperjs/core@^2.4.4":
+ version "2.4.4"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.4.tgz#11d5db19bd178936ec89cd84519c4de439574398"
+ integrity sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==
+
"@reach/router@^1.2.1":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c"
@@ -11376,6 +11381,14 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
+"react-popper-2@npm:react-popper@^2.2.3":
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.3.tgz#33d425fa6975d4bd54d9acd64897a89d904b9d97"
+ integrity sha512-mOEiMNT1249js0jJvkrOjyHsGvqcJd3aGW/agkiMoZk3bZ1fXN1wQszIQSjHIai48fE67+zwF8Cs+C4fWqlfjw==
+ dependencies:
+ react-fast-compare "^3.0.1"
+ warning "^4.0.2"
+
react-popper-tooltip@^2.8.3:
version "2.11.1"
resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-2.11.1.tgz#3c4bdfd8bc10d1c2b9a162e859bab8958f5b2644"