Skip to content

Commit e014564

Browse files
authored
[Menu, Select, Dialog] Use internal backdrop for pointer modality (#1161)
1 parent 46dba37 commit e014564

File tree

20 files changed

+441
-208
lines changed

20 files changed

+441
-208
lines changed

docs/src/app/(private)/experiments/popups-in-popups.tsx

+36-39
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@ export default function PopupsInPopups() {
3131

3232
{withBackdrop && <Dialog.Backdrop render={<Backdrop />} />}
3333

34-
<DialogPopup>
35-
<div style={{ display: 'flex', gap: '10px' }}>
36-
<SelectDemo modal={modal} />
37-
<MenuDemo modal={modal} />
38-
</div>
39-
<DialogControls>
40-
<DialogCloseButton>Cancel</DialogCloseButton>
41-
</DialogControls>
42-
</DialogPopup>
34+
<Dialog.Portal>
35+
<DialogPopup>
36+
<div style={{ display: 'flex', gap: '10px' }}>
37+
<SelectDemo modal={modal} />
38+
<MenuDemo modal={modal} />
39+
</div>
40+
<DialogControls>
41+
<DialogCloseButton>Cancel</DialogCloseButton>
42+
</DialogControls>
43+
</DialogPopup>
44+
</Dialog.Portal>
4345
</Dialog.Root>
4446
</div>
4547
</div>
@@ -48,38 +50,40 @@ export default function PopupsInPopups() {
4850

4951
function SelectDemo({ modal }: Props) {
5052
return (
51-
<Select.Root defaultValue="system" modal={modal} alignItemToTrigger={false}>
53+
<Select.Root modal={modal} defaultValue="system" alignItemToTrigger={false}>
5254
<Tooltip.Root>
5355
<Select.Trigger
5456
aria-label="Select font"
5557
render={<Tooltip.Trigger render={<Trigger />} />}
5658
>
5759
<Select.Value placeholder="System font" />
5860
<SelectDropdownArrow />
59-
<Tooltip.Portal>
60-
<Tooltip.Positioner sideOffset={10} render={<TooltipPositioner />}>
61-
<Tooltip.Popup render={<TooltipPopup />}>Choose a font</Tooltip.Popup>
62-
</Tooltip.Positioner>
63-
</Tooltip.Portal>
6461
</Select.Trigger>
62+
<Tooltip.Portal>
63+
<Tooltip.Positioner sideOffset={10} render={<TooltipPositioner />}>
64+
<Tooltip.Popup render={<TooltipPopup />}>Choose a font</Tooltip.Popup>
65+
</Tooltip.Positioner>
66+
</Tooltip.Portal>
6567
</Tooltip.Root>
6668

67-
<Select.Positioner sideOffset={5} render={<Positioner />}>
68-
<SelectPopup>
69-
<SelectItem value="system">
70-
<SelectItemIndicator render={<CheckIcon />} />
71-
<Select.ItemText>System font</Select.ItemText>
72-
</SelectItem>
73-
<SelectItem value="arial">
74-
<SelectItemIndicator render={<CheckIcon />} />
75-
<Select.ItemText>Arial</Select.ItemText>
76-
</SelectItem>
77-
<SelectItem value="roboto">
78-
<SelectItemIndicator render={<CheckIcon />} />
79-
<Select.ItemText>Roboto</Select.ItemText>
80-
</SelectItem>
81-
</SelectPopup>
82-
</Select.Positioner>
69+
<Select.Portal>
70+
<Select.Positioner sideOffset={5} render={<Positioner />}>
71+
<SelectPopup>
72+
<SelectItem value="system">
73+
<SelectItemIndicator render={<CheckIcon />} />
74+
<Select.ItemText>System font</Select.ItemText>
75+
</SelectItem>
76+
<SelectItem value="arial">
77+
<SelectItemIndicator render={<CheckIcon />} />
78+
<Select.ItemText>Arial</Select.ItemText>
79+
</SelectItem>
80+
<SelectItem value="roboto">
81+
<SelectItemIndicator render={<CheckIcon />} />
82+
<Select.ItemText>Roboto</Select.ItemText>
83+
</SelectItem>
84+
</SelectPopup>
85+
</Select.Positioner>
86+
</Select.Portal>
8387
</Select.Root>
8488
);
8589
}
@@ -283,8 +287,6 @@ const SelectDropdownArrow = styled(Select.Icon)`
283287
`;
284288

285289
const Positioner = styled('div')`
286-
z-index: 2900;
287-
288290
&:focus-visible {
289291
outline: 0;
290292
}
@@ -364,7 +366,6 @@ const MenuPopup = styled(Menu.Popup)(
364366
border: 1px solid ${theme.palette.mode === 'dark' ? grey[700] : grey[200]};
365367
color: ${theme.palette.mode === 'dark' ? grey[300] : grey[900]};
366368
box-shadow: 0px 4px 30px ${theme.palette.mode === 'dark' ? grey[900] : grey[200]};
367-
z-index: 1;
368369
transform-origin: var(--transform-origin);
369370
opacity: 1;
370371
transform: scale(1, 1);
@@ -460,7 +461,6 @@ const DialogPopup = styled(Dialog.Popup)(
460461
font-family: "IBM Plex Sans", sans-serif;
461462
transform: translate(-50%, -50%);
462463
padding: 16px;
463-
z-index: 2100;
464464
`,
465465
);
466466

@@ -491,9 +491,7 @@ const DialogCloseButton = styled(Dialog.Close)(
491491
`,
492492
);
493493

494-
const TooltipPositioner = styled('div')`
495-
z-index: 3000;
496-
`;
494+
const TooltipPositioner = styled('div')``;
497495

498496
const TooltipPopup = styled('div')`
499497
box-sizing: border-box;
@@ -537,5 +535,4 @@ const Backdrop = styled('div')`
537535
position: fixed;
538536
inset: 0;
539537
backdrop-filter: blur(4px);
540-
z-index: 2000;
541538
`;

packages/react/src/alert-dialog/popup/AlertDialogPopup.tsx

+15-10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useForkRef } from '../../utils/useForkRef';
1414
import { InteractionType } from '../../utils/useEnhancedClickHandler';
1515
import { transitionStatusMapping } from '../../utils/styleHookMapping';
1616
import { AlertDialogPopupDataAttributes } from './AlertDialogPopupDataAttributes';
17+
import { InternalBackdrop } from '../../utils/InternalBackdrop';
1718

1819
const customStyleHookMapping: CustomStyleHookMapping<AlertDialogPopup.State> = {
1920
...baseMapping,
@@ -50,6 +51,7 @@ const AlertDialogPopup = React.forwardRef(function AlertDialogPopup(
5051
setPopupElementId,
5152
titleElementId,
5253
transitionStatus,
54+
modal,
5355
} = useAlertDialogRootContext();
5456

5557
const mergedRef = useForkRef(forwardedRef, popupRef);
@@ -101,16 +103,19 @@ const AlertDialogPopup = React.forwardRef(function AlertDialogPopup(
101103
}
102104

103105
return (
104-
<FloatingFocusManager
105-
context={floatingContext}
106-
modal={open}
107-
disabled={!mounted}
108-
initialFocus={resolvedInitialFocus}
109-
returnFocus={finalFocus}
110-
outsideElementsInert
111-
>
112-
{renderElement()}
113-
</FloatingFocusManager>
106+
<React.Fragment>
107+
{mounted && modal && <InternalBackdrop />}
108+
<FloatingFocusManager
109+
context={floatingContext}
110+
modal={open}
111+
disabled={!mounted}
112+
initialFocus={resolvedInitialFocus}
113+
returnFocus={finalFocus}
114+
outsideElementsInert
115+
>
116+
{renderElement()}
117+
</FloatingFocusManager>
118+
</React.Fragment>
114119
);
115120
});
116121

packages/react/src/dialog/popup/DialogPopup.tsx

+15-11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { InteractionType } from '../../utils/useEnhancedClickHandler';
1515
import { transitionStatusMapping } from '../../utils/styleHookMapping';
1616
import { DialogPopupCssVars } from './DialogPopupCssVars';
1717
import { DialogPopupDataAttributes } from './DialogPopupDataAttributes';
18+
import { InternalBackdrop } from '../../utils/InternalBackdrop';
1819

1920
const customStyleHookMapping: CustomStyleHookMapping<DialogPopup.State> = {
2021
...baseMapping,
@@ -98,17 +99,20 @@ const DialogPopup = React.forwardRef(function DialogPopup(
9899
}
99100

100101
return (
101-
<FloatingFocusManager
102-
context={floatingContext}
103-
modal={open}
104-
disabled={!mounted}
105-
closeOnFocusOut={dismissible}
106-
initialFocus={resolvedInitialFocus}
107-
returnFocus={finalFocus}
108-
outsideElementsInert={modal}
109-
>
110-
{renderElement()}
111-
</FloatingFocusManager>
102+
<React.Fragment>
103+
{mounted && modal && <InternalBackdrop />}
104+
<FloatingFocusManager
105+
context={floatingContext}
106+
modal={open}
107+
disabled={!mounted}
108+
closeOnFocusOut={dismissible}
109+
initialFocus={resolvedInitialFocus}
110+
returnFocus={finalFocus}
111+
outsideElementsInert={modal}
112+
>
113+
{renderElement()}
114+
</FloatingFocusManager>
115+
</React.Fragment>
112116
);
113117
});
114118

packages/react/src/dialog/popup/useDialogPopup.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function useDialogPopup(parameters: useDialogPopup.Parameters): useDialog
5555
const id = useBaseUiId(idParam);
5656
const handleRef = useForkRef(ref, popupRef, setPopupElement);
5757

58-
useScrollLock(modal && open, elements.floating);
58+
useScrollLock(open && modal, elements.floating);
5959

6060
// Default initial focus logic:
6161
// If opened by touch, focus the popup element to prevent the virtual keyboard from opening
@@ -91,7 +91,7 @@ export function useDialogPopup(parameters: useDialogPopup.Parameters): useDialog
9191
mergeReactProps<'div'>(externalProps, {
9292
'aria-labelledby': titleElementId ?? undefined,
9393
'aria-describedby': descriptionElementId ?? undefined,
94-
'aria-modal': open && modal ? true : undefined,
94+
'aria-modal': mounted && modal ? true : undefined,
9595
role: 'dialog',
9696
tabIndex: -1,
9797
...getPopupProps(),

0 commit comments

Comments
 (0)