Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
171 commits
Select commit Hold shift + click to select a range
971b6f9
Shared referenceProps
michaldudak Jul 17, 2025
fc2138c
Update Floating UI to work with multiple triggers
michaldudak Jul 21, 2025
3a56526
Create a working demo
michaldudak Jul 21, 2025
6d6d22a
Do not animate initial appearance
michaldudak Jul 21, 2025
d9152e6
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Jul 21, 2025
4088d34
Suppress TS error
michaldudak Jul 21, 2025
f65cc32
Cleanup
michaldudak Jul 21, 2025
bfee582
Open on hover setting
michaldudak Jul 21, 2025
d9d1905
Remove unnecessary data prop
michaldudak Jul 21, 2025
7a4f3bf
Animate popups' dimensions
michaldudak Jul 22, 2025
6740f73
Fix autoresize
michaldudak Jul 23, 2025
3b03622
Update Popover implementation to use the Store
michaldudak Jul 23, 2025
6543ba0
Fix hover triggers
michaldudak Jul 23, 2025
b0de7c8
Remove unnecessary props
michaldudak Jul 23, 2025
d8ee5ac
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Jul 24, 2025
e6c5e96
Fix height transitions when anchored on top
michaldudak Jul 25, 2025
4a45232
Remove separate DetachedTriggerComponent
michaldudak Jul 25, 2025
6531171
Fix some of the issues
michaldudak Jul 25, 2025
b46d7fe
Update the experiment
michaldudak Jul 28, 2025
a1a8eee
Cleanup
michaldudak Jul 28, 2025
a8a6656
Payload type safety
michaldudak Jul 29, 2025
4b3fd46
Fix `stickIfOpen`
michaldudak Jul 29, 2025
ee73f22
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Jul 29, 2025
c10b27f
API docs
michaldudak Jul 29, 2025
80f56d3
Fix build errors
michaldudak Jul 29, 2025
c7eb92a
Fix modality, set initial state
michaldudak Jul 29, 2025
3bc5d73
Update tests
michaldudak Jul 29, 2025
4d0a555
Controlled mode WIP
michaldudak Jul 30, 2025
03f81f8
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Jul 30, 2025
1a55d07
lint, api docs
michaldudak Jul 30, 2025
4498f3b
dedupe
michaldudak Jul 30, 2025
bd331f4
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Jul 30, 2025
fd17f93
Fix controlled mode
michaldudak Jul 31, 2025
f10279d
Make a Trigger mandatory
michaldudak Jul 31, 2025
b58ea65
Change onOpenChange signature
michaldudak Jul 31, 2025
0205efc
docs:api
michaldudak Jul 31, 2025
323ca10
comment
michaldudak Jul 31, 2025
ef7084d
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Jul 31, 2025
7d99d16
dedupe
michaldudak Jul 31, 2025
fff7da9
Fix controlled popups not appearing
michaldudak Jul 31, 2025
2d84030
Update demos
michaldudak Jul 31, 2025
5bbcc7e
Set open state only for active triggers
michaldudak Aug 1, 2025
cc26415
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Aug 1, 2025
a3e3a26
Correct a11y attributes for inactive triggers
michaldudak Aug 1, 2025
d09bd98
Recreate lockfile
michaldudak Aug 1, 2025
dc4822b
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Aug 12, 2025
b41b0e8
Remove gaps between triggers
michaldudak Aug 12, 2025
1e9dc60
Support transitions while hovering
michaldudak Aug 13, 2025
c8e98bf
Somewhat working content transitions
michaldudak Aug 13, 2025
63d00b0
Fix
michaldudak Aug 14, 2025
87fcc2f
Fix opening the wrong popover on hover with delay
michaldudak Aug 14, 2025
50ceda6
TransitionContainer -> Viewport
michaldudak Aug 14, 2025
c6de7d2
API docs
michaldudak Aug 14, 2025
682766b
Restore closeOnFocusOut
michaldudak Aug 14, 2025
482a2b3
Hint what component should stay stable
michaldudak Aug 14, 2025
4261b0b
Recreate the old content using innerHTML instead of JSX tree
michaldudak Aug 14, 2025
8e467f0
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Aug 19, 2025
e7f5357
Docs and demos
michaldudak Aug 19, 2025
6a9bc64
Fix a mistake in the example
michaldudak Aug 19, 2025
94f35e8
Fix docs
michaldudak Aug 19, 2025
4cd22af
JSDocs, cleanup, fixes
michaldudak Aug 20, 2025
b5672eb
Add tests
michaldudak Aug 20, 2025
f5a8901
Fix tests
michaldudak Aug 20, 2025
f91b5bf
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Aug 20, 2025
ef0bf76
Do not run tests on JSDOM
michaldudak Aug 20, 2025
7610fde
Do not show shared state in experiments
michaldudak Aug 20, 2025
4637d11
Do not include demo files without default export in regression tests
michaldudak Aug 20, 2025
027c64d
Cleanup
michaldudak Aug 20, 2025
176e2fc
Do not modify old trigger maps
michaldudak Aug 21, 2025
6d8627a
Detect aborted animations
michaldudak Aug 21, 2025
f347f42
Remove unnecessary positionerRef
michaldudak Aug 21, 2025
48c0615
Revert
michaldudak Aug 21, 2025
8234bec
Fix layout shifting during content transitions
michaldudak Aug 25, 2025
3440cb1
Prettier
michaldudak Aug 25, 2025
d482c8a
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Aug 25, 2025
2fa33ac
Do not use `innerHTML`/`dangerouslySetInnerHTML`
michaldudak Aug 25, 2025
56e59c5
Fix ref cleanup
michaldudak Aug 25, 2025
25062e3
Keep the `open` prop a boolean and add the `triggerId` prop
michaldudak Aug 26, 2025
dba3a53
Add close to imperative actions
michaldudak Aug 26, 2025
feb1f03
Fix TS and API docs
michaldudak Aug 26, 2025
89bc14a
Dedupe
michaldudak Aug 26, 2025
26f4079
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Aug 27, 2025
25b7191
Recreate the lockfile
michaldudak Aug 27, 2025
d468eab
Fix disappearing arrow
michaldudak Aug 27, 2025
ff5b413
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Aug 27, 2025
69197a5
API docs
michaldudak Aug 27, 2025
64c6f82
Merge eventDetails and options parameters
michaldudak Aug 27, 2025
37c2423
Fix hover with delay=0
michaldudak Aug 28, 2025
6791a00
Fix controlled input warning
michaldudak Aug 28, 2025
f58b76e
Viewport tests
michaldudak Aug 28, 2025
bc9a03f
Tailwind demos
michaldudak Aug 28, 2025
449eebf
Fix `inert` value on R18
michaldudak Aug 28, 2025
246dd66
Refactor usePreviousRenderValue
michaldudak Aug 28, 2025
47c4c82
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Sep 8, 2025
576421c
Add missing paren
michaldudak Sep 8, 2025
21748a0
Update demos and docs
michaldudak Sep 8, 2025
e858dbe
Pass trigger to onOpenChange
michaldudak Sep 8, 2025
551ff08
Update `useFloating` to recognize triggers
michaldudak Sep 9, 2025
0bbe272
Make transitions consistent
michaldudak Sep 9, 2025
5b07b1a
dedupe
michaldudak Sep 9, 2025
51acf6b
Improve the fully featured demo
michaldudak Sep 9, 2025
821fae6
prettier
michaldudak Sep 9, 2025
2473531
Use state in `usePreviousRenderValue`
michaldudak Sep 9, 2025
48366c9
Tests and cleanup
michaldudak Sep 9, 2025
f7a8579
Transition improvements
michaldudak Sep 10, 2025
46264ca
Almost there
michaldudak Sep 10, 2025
fff09c6
fix WIP
michaldudak Sep 12, 2025
f642d11
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Sep 15, 2025
f9a8f8b
Fix size calculations
michaldudak Sep 15, 2025
a95e936
Remove debug code
michaldudak Sep 15, 2025
6cfc0af
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Sep 15, 2025
d3c18b2
Fix sizing issue and update Tailwind demo
michaldudak Sep 15, 2025
404b6c0
Inline demo icons
michaldudak Sep 16, 2025
c5a4b89
Update the docs
michaldudak Sep 16, 2025
b148fb8
Fix animation restarting when toggling the open state repeatedly
michaldudak Sep 16, 2025
23cdb6c
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Sep 16, 2025
ae9e720
Fix layout glitch
michaldudak Sep 16, 2025
64948de
Docs
michaldudak Sep 17, 2025
a4c1607
Revert useStoreControlled
michaldudak Sep 17, 2025
7f0b948
contains
michaldudak Sep 17, 2025
b49358f
Add trigger to BaseUIEventDetails
michaldudak Sep 17, 2025
9048abd
Remove `innerHTML`
michaldudak Sep 17, 2025
bdec6fe
Fix NavigationMenu regression
michaldudak Sep 17, 2025
b4da865
Fix layout shift when closing the popover during content transitions
michaldudak Sep 18, 2025
b69104a
Fix tests
michaldudak Sep 18, 2025
2013b72
dedupe
michaldudak Sep 18, 2025
483c10e
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Sep 18, 2025
b3172e9
Fix focus management
michaldudak Sep 19, 2025
6eb6cdb
Fixes
michaldudak Sep 19, 2025
c6e9a58
Fix focus restoration on esc
michaldudak Sep 19, 2025
cbe8e11
Do not close the popup on shift+tab
michaldudak Sep 19, 2025
d1f4520
Cleanup
michaldudak Sep 19, 2025
60522c0
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Sep 19, 2025
41178e4
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Sep 22, 2025
ee0a1be
Focus issues
michaldudak Sep 22, 2025
c089f6a
More focus fixes
michaldudak Sep 22, 2025
6f04c99
Fixing R18 tests
michaldudak Sep 22, 2025
2affba5
Fix the `key` spread issue
michaldudak Sep 23, 2025
16354d2
Formatting
michaldudak Sep 23, 2025
c414231
Testing leftover
michaldudak Sep 23, 2025
54e0297
Use `resolveRef` elsewhere
michaldudak Sep 23, 2025
ac67488
Fix
michaldudak Sep 23, 2025
5385539
Set data-instant on focusout
michaldudak Sep 23, 2025
b8f2769
Fix iOS size calculation
michaldudak Sep 23, 2025
846b34b
Capitalize titles
michaldudak Sep 23, 2025
fe45dea
Typo
michaldudak Sep 23, 2025
b1b2983
Controlled mode docs and demo
michaldudak Sep 23, 2025
f7a7d26
Use CSS transitions in the demo
michaldudak Sep 24, 2025
cc6ffe8
API docs
michaldudak Sep 24, 2025
a5f4cb6
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Sep 24, 2025
379ac3a
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Sep 29, 2025
556dc6a
Use new methods from the ReactStore
michaldudak Sep 29, 2025
946eb93
Use `store.useControlledProp`
michaldudak Sep 30, 2025
a547bec
Add Calendar experiment
michaldudak Oct 2, 2025
0901f32
Ignore available width/height when measuring popup size
michaldudak Oct 2, 2025
a851cb0
Remove starting-style when measuring content
michaldudak Oct 2, 2025
6d16361
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Oct 2, 2025
9609029
Lint, dedupe
michaldudak Oct 3, 2025
c5e58c4
API docs
michaldudak Oct 3, 2025
d59a259
Fix exploding layout in controlled demo
michaldudak Oct 3, 2025
aa2d8f5
Revert ignoring starting style
michaldudak Oct 3, 2025
610eb03
Fix transition issue in the deployed docs
michaldudak Oct 3, 2025
800730b
Fixing React 18
michaldudak Oct 5, 2025
a8c54a7
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Oct 5, 2025
edf8d4e
Dedupe
michaldudak Oct 5, 2025
9f594cf
Remove redundant ref
michaldudak Oct 5, 2025
0027f01
Move refs to store's context
michaldudak Oct 6, 2025
bf7a6b6
Merge remote-tracking branch 'upstream/master' into popover-detached-…
michaldudak Oct 6, 2025
7529cc3
Experiment updates
michaldudak Oct 6, 2025
15109b5
Cleanup
michaldudak Oct 6, 2025
feb8909
Prettier
michaldudak Oct 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 254 additions & 0 deletions docs/src/app/(private)/experiments/popover-detached-trigger.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
.Container {
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
}

.IconButton {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
padding: 0;
margin: 0;
outline: 0;
border: 1px solid var(--color-gray-200);
border-radius: 0.375rem;
background-color: var(--color-gray-50);
color: var(--color-gray-900);
user-select: none;

@media (hover: hover) {
&:hover {
background-color: var(--color-gray-100);
}
}

&:active {
background-color: var(--color-gray-100);
}

&[data-popup-open] {
background-color: var(--color-gray-100);
}

&:focus-visible {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
}

.Icon {
width: 1.25rem;
height: 1.25rem;
}

.Positioner {
transition: transform 0.5s;

&[data-instant] {
transition: none;
}
}

.Popup {
box-sizing: border-box;
padding: 1rem 1.5rem;
border-radius: 0.5rem;
background-color: canvas;
color: var(--color-gray-900);
transform-origin: var(--transform-origin);

width: var(--popup-width, auto);
height: var(--popup-height, auto);

overflow: hidden;

transition:
width 0.2s cubic-bezier(0.165, 0.84, 0.44, 1),
height 0.2s cubic-bezier(0.165, 0.84, 0.44, 1);

&[data-starting-style],
&[data-ending-style] {
opacity: 0;
transform: scale(0.9);
}

@media (prefers-color-scheme: light) {
outline: 1px solid var(--color-gray-200);
box-shadow:
0 10px 15px -3px var(--color-gray-200),
0 4px 6px -4px var(--color-gray-200);
}

@media (prefers-color-scheme: dark) {
outline: 1px solid var(--color-gray-300);
outline-offset: -1px;
}
}

.Arrow {
display: flex;

&[data-side='top'] {
bottom: -8px;
rotate: 180deg;
}

&[data-side='bottom'] {
top: -8px;
rotate: 0deg;
}

&[data-side='left'] {
right: -13px;
rotate: 90deg;
}

&[data-side='right'] {
left: -13px;
rotate: -90deg;
}
}

.ArrowFill {
fill: canvas;
}

.ArrowOuterStroke {
@media (prefers-color-scheme: light) {
fill: var(--color-gray-200);
}
}

.ArrowInnerStroke {
@media (prefers-color-scheme: dark) {
fill: var(--color-gray-300);
}
}

.Title {
margin: 0;
font-size: 1rem;
line-height: 1.5rem;
font-weight: 500;
}

.Description {
margin: 0;
font-size: 1rem;
line-height: 1.5rem;
color: var(--color-gray-600);
}

.Actions {
display: flex;
justify-content: end;
gap: 1rem;
}

.PopoverSection {
margin-top: 0.5rem;
margin-bottom: 0.5rem;

&:not(:last-child) {
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--color-gray-200);
}

& button {
padding: 0.25rem 0.75rem;
background-color: var(--color-gray-200);
border-radius: 4px;
margin-top: 0.5rem;

&:hover {
background-color: var(--color-gray-300);
}
}
}

.content-switcher {
position: relative;
width: 100%;
height: 100%;
overflow: hidden; /* This is the viewport */
}

.slide-item {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translateX(0); /* Start in the center */
}

/* --- Forward Animation --- */
.slide-item.exit-forward {
animation: slideOutToLeft 0.4s ease-in-out forwards;
}

.slide-item.enter-forward {
animation: slideInFromRight 0.4s ease-in-out forwards;
}

@keyframes slideOutToLeft {
from {
transform: translateX(0);
opacity: 1;
}

to {
transform: translateX(-100%);
opacity: 0;
}
}

@keyframes slideInFromRight {
from {
transform: translateX(100%);
opacity: 0;
}

to {
transform: translateX(0);
opacity: 1;
}
}

/* --- Backward Animation --- */
.slide-item.exit-backward {
animation: slideOutToRight 0.4s ease-in-out forwards;
}

.slide-item.enter-backward {
animation: slideInFromLeft 0.4s ease-in-out forwards;
}

@keyframes slideOutToRight {
from {
transform: translateX(0);
opacity: 1;
}

to {
transform: translateX(100%);
opacity: 0;
}
}

@keyframes slideInFromLeft {
from {
transform: translateX(-100%);
opacity: 0;
}

to {
transform: translateX(0);
opacity: 1;
}
}
133 changes: 133 additions & 0 deletions docs/src/app/(private)/experiments/popover-detached-trigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
'use client';
import * as React from 'react';
import { Popover } from '@base-ui-components/react/popover';

// eslint-disable-next-line no-restricted-imports
import { PopoverHandle } from '@base-ui-components/react/popover/handle/createPopover';
import {
SettingsMetadata,
useExperimentSettings,
} from 'docs/src/components/Experiments/SettingsPanel';
import styles from './popover-detached-trigger.module.css';

const popover1 = new PopoverHandle<unknown>();

interface Settings {
openOnHover: boolean;
}

export default function PopoverDetachedTrigger() {
return (
<div className={styles.Container}>
<StyledPopover handle={popover1} />
<StyledTrigger handle={popover1} payload={1} />
<StyledTrigger handle={popover1} payload={2} />
<StyledTrigger handle={popover1} payload={3} />
<StyledTrigger handle={popover1} payload={4} />
<StyledTrigger handle={popover1} payload={5} />
</div>
);
}

interface StyledPopoverProps {
handle: PopoverHandle<unknown>;
}

function StyledTrigger<Payload>(props: { handle: PopoverHandle<Payload>; payload: Payload }) {
const { settings } = useExperimentSettings<Settings>();
return (
<Popover.DetachedTrigger
className={styles.IconButton}
handle={props.handle as PopoverHandle<unknown>}
payload={props.payload}
openOnHover={settings.openOnHover}
delay={50}
>
<PopupIcon aria-label="Notifications" className={styles.Icon} />
</Popover.DetachedTrigger>
);
}

function StyledPopover(props: StyledPopoverProps) {
const { handle } = props;

return (
<Popover.Root handle={handle}>
{({ payload }) => (
<Popover.Portal>
<Popover.Positioner sideOffset={8} className={styles.Positioner}>
<Popover.Popup className={styles.Popup}>
<Popover.Arrow className={styles.Arrow}>
<ArrowSvg />
</Popover.Arrow>
<Popover.Title className={styles.Title}>Popover {payload}</Popover.Title>
<Content payload={payload as number} />
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
)}
</Popover.Root>
);
}

function Content({ payload }: { payload: number }) {
const [localState, setLocalState] = React.useState(0);

return (
<div>
<div className={styles.PopoverSection}>
{Array.from({ length: payload }).map((_, i) => (
<p key={i}>Lorem ipsum dolor sit amet.</p>
))}
</div>

<div className={styles.PopoverSection}>
<p>Local state: {localState}</p>
<button type="button" onClick={() => setLocalState((s) => s + 1)}>
Increment local state
</button>
</div>
</div>
);
}

export const settingsMetadata: SettingsMetadata<Settings> = {
openOnHover: {
type: 'boolean',
label: 'Open on hover',
},
};

function ArrowSvg(props: React.ComponentProps<'svg'>) {
return (
<svg width="20" height="10" viewBox="0 0 20 10" fill="none" {...props}>
<path
d="M9.66437 2.60207L4.80758 6.97318C4.07308 7.63423 3.11989 8 2.13172 8H0V10H20V8H18.5349C17.5468 8 16.5936 7.63423 15.8591 6.97318L11.0023 2.60207C10.622 2.2598 10.0447 2.25979 9.66437 2.60207Z"
className={styles.ArrowFill}
/>
<path
d="M8.99542 1.85876C9.75604 1.17425 10.9106 1.17422 11.6713 1.85878L16.5281 6.22989C17.0789 6.72568 17.7938 7.00001 18.5349 7.00001L15.89 7L11.0023 2.60207C10.622 2.2598 10.0447 2.2598 9.66436 2.60207L4.77734 7L2.13171 7.00001C2.87284 7.00001 3.58774 6.72568 4.13861 6.22989L8.99542 1.85876Z"
className={styles.ArrowOuterStroke}
/>
<path
d="M10.3333 3.34539L5.47654 7.71648C4.55842 8.54279 3.36693 9 2.13172 9H0V8H2.13172C3.11989 8 4.07308 7.63423 4.80758 6.97318L9.66437 2.60207C10.0447 2.25979 10.622 2.2598 11.0023 2.60207L15.8591 6.97318C16.5936 7.63423 17.5468 8 18.5349 8H20V9H18.5349C17.2998 9 16.1083 8.54278 15.1901 7.71648L10.3333 3.34539Z"
className={styles.ArrowInnerStroke}
/>
</svg>
);
}

function PopupIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
{...props}
height="24px"
viewBox="0 -960 960 960"
width="24px"
fill="#606060"
>
<path d="M480-80 373-240H160q-33 0-56.5-23.5T80-320v-480q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H587L480-80Zm0-144 64-96h256v-480H160v480h256l64 96Zm0-336Z" />
</svg>
);
}
Loading