Skip to content

Commit bf92ba4

Browse files
committed
Fix misplaced Popover when using Floating UI (#566)
1 parent d0d9758 commit bf92ba4

File tree

3 files changed

+69
-3
lines changed

3 files changed

+69
-3
lines changed

src/components/Popover/Popover.jsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createPortal } from 'react-dom';
44
import { withGlobalProps } from '../../providers/globalProps';
55
import { classNames } from '../../utils/classNames';
66
import { transferProps } from '../../utils/transferProps';
7+
import cleanPlacementStyle from './_helpers/cleanPlacementStyle';
78
import getRootSideClassName from './_helpers/getRootSideClassName';
89
import getRootAlignmentClassName from './_helpers/getRootAlignmentClassName';
910
import styles from './Popover.module.scss';
@@ -12,6 +13,7 @@ export const Popover = React.forwardRef((props, ref) => {
1213
const {
1314
placement,
1415
children,
16+
placementStyle,
1517
popoverTargetId,
1618
portalId,
1719
...restProps
@@ -41,6 +43,7 @@ export const Popover = React.forwardRef((props, ref) => {
4143
getRootAlignmentClassName(placement, styles),
4244
)}
4345
ref={ref}
46+
style={placementStyle ? cleanPlacementStyle(placementStyle) : null}
4447
>
4548
{children}
4649
<span className={styles.arrow} />
@@ -57,6 +60,7 @@ export const Popover = React.forwardRef((props, ref) => {
5760

5861
Popover.defaultProps = {
5962
placement: 'bottom',
63+
placementStyle: null,
6064
popoverTargetId: null,
6165
portalId: null,
6266
};
@@ -84,6 +88,24 @@ Popover.propTypes = {
8488
'left-start',
8589
'left-end',
8690
]),
91+
/**
92+
* Used for positioning the popover with a library like Floating UI. It is filtered,
93+
* then passed to the popover as the `style` prop.
94+
*/
95+
placementStyle: PropTypes.shape({
96+
bottom: PropTypes.string,
97+
inset: PropTypes.string,
98+
'inset-block-end': PropTypes.string,
99+
'inset-block-start': PropTypes.string,
100+
'inset-inline-end': PropTypes.string,
101+
'inset-inline-start': PropTypes.string,
102+
left: PropTypes.string,
103+
position: PropTypes.string,
104+
right: PropTypes.string,
105+
top: PropTypes.string,
106+
'transform-origin': PropTypes.string,
107+
translate: PropTypes.string,
108+
}),
87109
/**
88110
* If set, the popover will become controlled, meaning it will be hidden by default and will need a trigger to open.
89111
* This sets the ID of the internal helper element for the popover.

src/components/Popover/README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,29 @@ automatically, including smart position updates to ensure Popover visibility,
162162
we recommend to involve an external library designed specifically for this
163163
purpose.
164164

165+
To position the popover, you need to provide the `placementStyle` prop with the
166+
style you want to apply to the popover. This prop should only be used to
167+
position the popover. The allowed props are:
168+
169+
- `position`
170+
- `inset`
171+
- `inset-inline-start`
172+
- `inset-inline-end`
173+
- `inset-block-start`
174+
- `inset-block-end`
175+
- `top`
176+
- `right`
177+
- `bottom`
178+
- `left`
179+
- `translate`
180+
- `transform-origin`
181+
182+
⚠️ [`inset`][mdn-inset] is a shorthand for `top right bottom left`, not for
183+
`inset-*` properties.
184+
185+
As opposed to `top right bottom left` and the `inset` shorthand, `inset-*`
186+
properties are writing-direction aware.
187+
165188
ℹ️ The following example is using external library [Floating UI]. To use
166189
Floating UI, install it first:
167190

@@ -267,10 +290,10 @@ React.createElement(() => {
267290
<Popover
268291
id="my-advanced-popover"
269292
placement={finalPlacement}
270-
style={{
293+
placementStyle={{
271294
position: strategy,
272-
top: y ? y : '',
273-
left: x ? x : '',
295+
top: `${y}px`,
296+
left: `${x}px`,
274297
}}
275298
ref={floating}
276299
>
@@ -359,5 +382,6 @@ which enables [Advanced Positioning](#advanced-positioning).
359382

360383
[div-attributes]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div#attributes
361384
[Floating UI]: https://floating-ui.com/docs/react-dom
385+
[mdn-inset]: https://developer.mozilla.org/en-US/docs/Web/CSS/inset
362386
[React common props]: https://react.dev/reference/react-dom/components/common#common-props
363387
[ref]: https://reactjs.org/docs/refs-and-the-dom.html
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export default (placementStyle) => {
2+
const validProps = [
3+
'position',
4+
'inset',
5+
'inset-inline-start',
6+
'inset-inline-end',
7+
'inset-block-start',
8+
'inset-block-end',
9+
'top',
10+
'right',
11+
'bottom',
12+
'left',
13+
'translate',
14+
'transform-origin',
15+
];
16+
17+
return Object.fromEntries(
18+
Object.entries(placementStyle).filter(([prop]) => validProps.includes(prop)),
19+
);
20+
};

0 commit comments

Comments
 (0)