From a9a60fabd7f4a954786098bc8b51329beb8ffabe Mon Sep 17 00:00:00 2001 From: David Menc Date: Tue, 10 Dec 2024 15:06:18 +0100 Subject: [PATCH] fixup! Fix misplaced `Popover` when using Floating UI (#566) --- src/components/Popover/Popover.jsx | 27 +++++++++++++---- src/components/Popover/README.md | 25 ++++++++++++++-- .../Popover/_helpers/cleanPlacementStyle.js | 29 +++++++++++++++++++ 3 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 src/components/Popover/_helpers/cleanPlacementStyle.js diff --git a/src/components/Popover/Popover.jsx b/src/components/Popover/Popover.jsx index 17eb8bdc..99d2632a 100644 --- a/src/components/Popover/Popover.jsx +++ b/src/components/Popover/Popover.jsx @@ -4,6 +4,7 @@ import { createPortal } from 'react-dom'; import { withGlobalProps } from '../../provider'; import { classNames } from '../../utils/classNames'; import { transferProps } from '../../utils/transferProps'; +import cleanPlacementStyle from './_helpers/cleanPlacementStyle'; import getRootSideClassName from './_helpers/getRootSideClassName'; import getRootAlignmentClassName from './_helpers/getRootAlignmentClassName'; import styles from './Popover.module.scss'; @@ -12,6 +13,7 @@ export const Popover = React.forwardRef((props, ref) => { const { placement, children, + placementStyle, portalId, position, x, @@ -29,11 +31,7 @@ export const Popover = React.forwardRef((props, ref) => { getRootAlignmentClassName(placement, styles), )} ref={ref} - style={{ - left: x, - position, - top: y, - }} + style={cleanPlacementStyle(placementStyle)} > {children} @@ -49,6 +47,7 @@ export const Popover = React.forwardRef((props, ref) => { Popover.defaultProps = { placement: 'bottom', + placementStyle: null, portalId: null, position: null, x: null, @@ -78,6 +77,24 @@ Popover.propTypes = { 'left-start', 'left-end', ]), + /** + * Used for positioning the popover with a library like Floating UI. It is filtered, + * then passed as to the popover as the `style` prop. + */ + placementStyle: PropTypes.shape({ + bottom: PropTypes.string, + inset: PropTypes.string, + 'inset-block-end': PropTypes.string, + 'inset-block-start': PropTypes.string, + 'inset-inline-end': PropTypes.string, + 'inset-inline-start': PropTypes.string, + left: PropTypes.string, + position: PropTypes.string, + right: PropTypes.string, + top: PropTypes.string, + 'transform-origin': PropTypes.string, + translate: PropTypes.string, + }), /** * If set, popover is rendered in the React Portal with that ID. */ diff --git a/src/components/Popover/README.md b/src/components/Popover/README.md index a2e2be4d..24128ee1 100644 --- a/src/components/Popover/README.md +++ b/src/components/Popover/README.md @@ -162,6 +162,23 @@ automatically, including smart position updates to ensure Popover visibility, we recommend to involve an external library designed specifically for this purpose. +To position the popover, you need to provide the `placementStyle` prop with the +style you want to apply to the popover. This prop is filtered and should only +be used to position the popover. The allowed props are + + - `position` + - `inset` + - `inset-inline-start` + - `inset-inline-end` + - `inset-block-start` + - `inset-block-end` + - `top` + - `right` + - `bottom` + - `left` + - `translate` + - `transform-origin` + ℹ️ The following example is using external library [Floating UI]. To use Floating UI, install it first: @@ -267,9 +284,11 @@ React.createElement(() => { Auto-flipping Popover diff --git a/src/components/Popover/_helpers/cleanPlacementStyle.js b/src/components/Popover/_helpers/cleanPlacementStyle.js new file mode 100644 index 00000000..a5260230 --- /dev/null +++ b/src/components/Popover/_helpers/cleanPlacementStyle.js @@ -0,0 +1,29 @@ +export default (placementStyle) => { + const validProps = [ + 'position', + 'inset', + 'inset-inline-start', + 'inset-inline-end', + 'inset-block-start', + 'inset-block-end', + 'top', + 'right', + 'bottom', + 'left', + 'translate', + 'transform-origin', + ]; + + if (process.env.NODE_ENV !== 'production') { + const invalidProps = Object.keys(placementStyle).filter((prop) => !validProps.includes(prop)); + + if (invalidProps.length > 0) { + // eslint-disable-next-line no-console + console.error(`Invalid prop(s) supplied to the "placementStyle" prop: "${invalidProps.join('", "')}"`); + } + } + + return Object.fromEntries( + Object.entries(placementStyle).filter(([prop]) => validProps.includes(prop)), + ); +};