Repository navigation
Overlays inside other libraries' modals, and in RTL pages - #216
Merged
Merged
Conversation
Popovers (Select, ComboBox, MultiSelect, Dropdown, DatePicker, DateRangePicker, nav account menus) and modals (Modal, SlideoutMenu) opened from inside another library's modal layer (Radix/Base UI: shadcn/ui Dialog, Sheet, AlertDialog, Drawer) portalled to document.body, where Radix's pointer-events: none, focus trap and document-level Escape made them unusable. They now render into that layer (utils/overlay-host.tsx, React Aria's UNSTABLE_portalContainer); full-screen overlays are offset back over the viewport when the layer is transformed, and Escape in them closes only them. Menus of other libraries opened from our Modal or SlideoutMenu are marked as React Aria top layers so they stay clickable and focusable. Without a foreign layer nothing changes: same portal, same DOM. React Aria takes direction from its locale, not the page: when the page sets dir and the browser-default locale disagrees (no I18nProvider), popovers get a locale for the page's direction (the page's lang when it matches). Physical popover placements become logical (bottom end, end top), identical in LTR.
…nent in overlay-popover.tsx Keeps Vite's fast refresh rule (only-export-components) quiet in users' projects.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes our overlays usable inside other libraries' modals (shadcn/ui Dialog, Sheet, AlertDialog, Drawer on Radix or Base UI), keeps other libraries' menus usable inside ours, and makes popovers follow an RTL page.
Problems (found by the shadcn compatibility matrix)
document.body. A Radix modal setspointer-events: noneon<body>, traps focus and handles Escape in a capture listener ondocument, so our Select/ComboBox/Dropdown/DatePicker options could not be clicked inside their Dialog or Sheet, our Modal opened from their Dialog was unclickable, and Escape in our Select closed their whole dialog.<body>inert, so a shadcn DropdownMenu opened from our Modal could not be clicked (Radix and Base UI), and Escape in it closed our Modal too (Radix: focus was pulled back into our dialog).<html dir>, so our popovers rendered LTR in RTL pages.Fix
One helper,
utils/overlay-host.ts(hooks), plusutils/overlay-popover.tsx(a drop-in for React Aria'sPopover):[role=dialog|alertdialog]without React Aria'sdata-rac), the overlay renders into that dialog (UNSTABLE_portalContainer). Modals use the dialog that holds focus when they open, and are offset back over the viewport when that dialog is transformed (Tailwind'stranslatemakes it the containing block of fixed children). A window capture listener marks Escape in our hosted overlays as handled, so Radix leaves its dialog open.<body>getdata-react-aria-top-layer(React Aria's own escape hatch for toasts): not inert, focus and clicks allowed. Marked on insert and on focus-in.dirand the browser-default locale disagrees (noI18nProvider), popovers get anI18nProviderwith the page'slang(or a locale of that direction). Apps that set their ownI18nProviderare left alone.bottom end,end top,bottom start): identical in LTR, mirrored in RTL. Sidebar popovers stay physical (our sidebars don't mirror).Existing users
No change without a foreign dialog or an RTL page: same portal (
document.body), no new attribute. Verified by building a normal Untitled UI Next.js project with the published components and the same project with this branch: all #ours items and interactions (focus ring, open Select, open Dropdown, Tooltip, open Modal) pixel-identical in light and dark; in an RTL page only the open Select/Dropdown change (they are now RTL).Evidence and matrix results: see the website PR / compat/interop branch.
🤖 Generated with Claude Code