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"