+ {/**
+ * This hack is needed because the default behavior of the Popover API is to place the popover into a
+ * top-layer. It is currently not possible to position an element in the top-layer relative to a normal element.
+ * This will create a hidden browser popover, then with CSS it will open and close the RUI popover.
+ */}
+ {!!popoverTargetId && (
+
)}
- ref={ref}
- >
- {children}
-
-
+
+ {children}
+
+
+ >
);
if (portalId === null) {
@@ -41,6 +57,7 @@ export const Popover = React.forwardRef((props, ref) => {
Popover.defaultProps = {
placement: 'bottom',
+ popoverTargetId: null,
portalId: null,
};
@@ -67,6 +84,12 @@ Popover.propTypes = {
'left-start',
'left-end',
]),
+ /**
+ * If set, the popover will become controlled, meaning it will be hidden by default and will need a trigger to open.
+ * This sets the ID of the internal helper element for the popover.
+ * Assign the same ID to `popovertarget` of a trigger to make it open and close.
+ */
+ popoverTargetId: PropTypes.string,
/**
* If set, popover is rendered in the React Portal with that ID.
*/
diff --git a/src/components/Popover/Popover.module.scss b/src/components/Popover/Popover.module.scss
index 1c3d1873..b9b554be 100644
--- a/src/components/Popover/Popover.module.scss
+++ b/src/components/Popover/Popover.module.scss
@@ -1,6 +1,12 @@
-// 1. Reset positioning for controlled variant.
-// 2. Shift Popover so there is space for the arrow between Popover and reference element.
-// 3. Add top offset in case it's not defined by external library.
+// 1. Hide the popover by default. This is needed because the popover is
+// controlled via CSS with the help of the helper popover. The popover can't
+// be displayed directly, because relative positioning doesn't work with
+// elements on the top-layer, so this CSS hack is needed.
+// 2. Hide the popover helper element.
+// 3. If the popover helper is open, show the actual popover.
+// 4. Reset positioning for controlled variant.
+// 5. Shift Popover so there is space for the arrow between Popover and reference element.
+// 6. Add top offset in case it's not defined by external library.
@use "theme";
@@ -49,6 +55,28 @@
}
}
+ // Controlled popover
+ .controlledPopover {
+ display: none; // 1.
+ }
+
+ .helper {
+ position: fixed; // 2.
+ inset: unset;
+ top: 0;
+ right: 0;
+ width: auto;
+ height: auto;
+ padding: 0;
+ border: none;
+ background: transparent;
+ pointer-events: none;
+ }
+
+ .helper:popover-open ~ .controlledPopover {
+ display: block; // 3.
+ }
+
// Sides
.isRootAtTop {
bottom: calc(100% + #{theme.$arrow-gap} - #{theme.$arrow-safe-rendering-overlap});
@@ -212,27 +240,27 @@
.isRootControlled.isRootAtBottom,
.isRootControlled.isRootAtLeft,
.isRootControlled.isRootAtRight {
- inset: unset; // 1.
+ inset: unset; // 4.
}
.isRootControlled.isRootAtTop {
- transform: translate(0, calc(-1 * #{theme.$arrow-height})); // 2.
+ transform: translate(0, calc(-1 * #{theme.$arrow-height})); // 5.
}
.isRootControlled.isRootAtBottom {
- transform: translate(0, #{theme.$arrow-height}); // 2.
+ transform: translate(0, #{theme.$arrow-height}); // 5.
}
.isRootControlled.isRootAtLeft {
- transform: translate(calc(-1 * #{theme.$arrow-height}), 0); // 2.
+ transform: translate(calc(-1 * #{theme.$arrow-height}), 0); // 5.
}
.isRootControlled.isRootAtRight {
- transform: translate(#{theme.$arrow-height}, 0); // 2.
+ transform: translate(#{theme.$arrow-height}, 0); // 5.
}
.isRootControlled.isRootAtLeft.isRootAtStart,
.isRootControlled.isRootAtRight.isRootAtStart {
- top: 0; // 3.
+ top: 0; // 6.
}
}
diff --git a/src/components/Popover/README.md b/src/components/Popover/README.md
index dac06ce8..46f98c9a 100644
--- a/src/components/Popover/README.md
+++ b/src/components/Popover/README.md
@@ -284,6 +284,39 @@ React.createElement(() => {
});
```
+## Controlled Popover
+
+Popover API can be used to control visibility of Popover component. You need to
+set `id` on the trigger element and matching `popoverTargetId` attribute on the
+Popover component. This leverages the browser's Popover API to control the
+popover, automatically closing it when the trigger or the backdrop is pressed.
+
+```docoff-react-preview
+React.createElement(() => {
+ // All inline styles in this example are for demonstration purposes only.
+ return (
+
+
+
+
+ Hello there!
+
+
+
+ );
+});
+```
+
## Forwarding HTML Attributes
In addition to the options below in the [component's API](#api) section, you