Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
DialogTitle,
DialogTrigger,
} from '@fluentui/react-dialog';
import { OverlayDrawer, DrawerBody, DrawerHeader, DrawerHeaderTitle } from '@fluentui/react-drawer';
import { Button } from '@fluentui/react-button';
import { Combobox, Option } from '@fluentui/react-combobox';
import { Rocket24Regular } from '@fluentui/react-icons';
import { Rocket24Regular, Dismiss24Regular } from '@fluentui/react-icons';
import type { Meta } from '@storybook/react-webpack5';
import { getStoryVariant, DARK_MODE, HIGH_CONTRAST, RTL } from '../../utilities';

Expand Down Expand Up @@ -528,3 +529,149 @@ export const IntegrationComboboxInline = () => {
</Dialog>
);
};

export const DialogInsideDrawerDefault = () => (
<OverlayDrawer open position="start">
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} />}>
Drawer with Dialog
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<Dialog open>
<DialogTrigger>
<Button>Open dialog</Button>
</DialogTrigger>
<DialogSurface>
<DialogBody>
<DialogTitle>Dialog inside Drawer</DialogTitle>
<DialogContent>This dialog has the default transparent backdrop when nested inside a Drawer.</DialogContent>
<DialogActions>
<DialogTrigger>
<Button appearance="secondary">Close</Button>
</DialogTrigger>
<Button appearance="primary">Do Something</Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
</DrawerBody>
</OverlayDrawer>
);

DialogInsideDrawerDefault.storyName = 'dialog inside drawer default';

export const DialogInsideDrawerDefaultDarkMode = getStoryVariant(DialogInsideDrawerDefault, DARK_MODE);
export const DialogInsideDrawerDefaultHighContrast = getStoryVariant(DialogInsideDrawerDefault, HIGH_CONTRAST);
export const DialogInsideDrawerDefaultRTL = getStoryVariant(DialogInsideDrawerDefault, RTL);

export const DialogInsideDrawerDimmed = () => (
<OverlayDrawer open position="start">
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} />}>
Drawer with Dialog
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<Dialog open>
<DialogTrigger>
<Button>Open dialog</Button>
</DialogTrigger>
<DialogSurface backdrop={{ appearance: 'dimmed' }}>
<DialogBody>
<DialogTitle>Dialog inside Drawer</DialogTitle>
<DialogContent>This dialog has a dimmed backdrop even though it is nested inside a Drawer.</DialogContent>
<DialogActions>
<DialogTrigger>
<Button appearance="secondary">Close</Button>
</DialogTrigger>
<Button appearance="primary">Do Something</Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
</DrawerBody>
</OverlayDrawer>
);

DialogInsideDrawerDimmed.storyName = 'dialog inside drawer dimmed';

export const DialogInsideDrawerDimmedDarkMode = getStoryVariant(DialogInsideDrawerDimmed, DARK_MODE);
export const DialogInsideDrawerDimmedHighContrast = getStoryVariant(DialogInsideDrawerDimmed, HIGH_CONTRAST);
export const DialogInsideDrawerDimmedRTL = getStoryVariant(DialogInsideDrawerDimmed, RTL);

export const DialogInsideDrawerTransparent = () => (
<OverlayDrawer open position="start">
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} />}>
Drawer with Dialog
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<Dialog open>
<DialogTrigger>
<Button>Open dialog</Button>
</DialogTrigger>
<DialogSurface backdrop={{ appearance: 'transparent' }}>
<DialogBody>
<DialogTitle>Dialog inside Drawer</DialogTitle>
<DialogContent>This dialog explicitly sets transparent backdrop appearance.</DialogContent>
<DialogActions>
<DialogTrigger>
<Button appearance="secondary">Close</Button>
</DialogTrigger>
<Button appearance="primary">Do Something</Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
</DrawerBody>
</OverlayDrawer>
);

DialogInsideDrawerTransparent.storyName = 'dialog inside drawer transparent';

export const DialogInsideDrawerTransparentDarkMode = getStoryVariant(DialogInsideDrawerTransparent, DARK_MODE);
export const DialogInsideDrawerTransparentHighContrast = getStoryVariant(DialogInsideDrawerTransparent, HIGH_CONTRAST);
export const DialogInsideDrawerTransparentRTL = getStoryVariant(DialogInsideDrawerTransparent, RTL);

export const NestedDialogDimmed = () => (
<Dialog open>
<DialogTrigger>
<Button>Open nested dialog</Button>
</DialogTrigger>
<DialogSurface as="div">
<DialogBody>
<DialogTitle>Outer Dialog</DialogTitle>
<DialogContent>This is the outer dialog.</DialogContent>
<DialogActions>
<Dialog open>
<DialogTrigger>
<Button appearance="primary">Open inner dialog</Button>
</DialogTrigger>
<DialogSurface as="div" backdrop={{ appearance: 'dimmed' }}>
<DialogBody>
<DialogTitle>Inner Dialog with dimmed backdrop</DialogTitle>
<DialogContent>
This inner dialog explicitly sets dimmed backdrop to override the default transparent behavior for
nested dialogs.
</DialogContent>
<DialogActions>
<DialogTrigger>
<Button appearance="primary">Close</Button>
</DialogTrigger>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
);

NestedDialogDimmed.storyName = 'nested dialog dimmed';

export const NestedDialogDimmedDarkMode = getStoryVariant(NestedDialogDimmed, DARK_MODE);
export const NestedDialogDimmedHighContrast = getStoryVariant(NestedDialogDimmed, HIGH_CONTRAST);
export const NestedDialogDimmedRTL = getStoryVariant(NestedDialogDimmed, RTL);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: add DialogBackdropProvider to react-dialog",
"packageName": "@fluentui/react-components",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: add appearance to backdrop slot",
"packageName": "@fluentui/react-dialog",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: use DialogBackdropProvider to fix no dimmed nested dialogs inside OverlayDrawer'",
"packageName": "@fluentui/react-drawer",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ import { DialogActionsPosition } from '@fluentui/react-dialog';
import { DialogActionsProps } from '@fluentui/react-dialog';
import { DialogActionsSlots } from '@fluentui/react-dialog';
import { DialogActionsState } from '@fluentui/react-dialog';
import { DialogBackdropContextValue } from '@fluentui/react-dialog';
import { DialogBody } from '@fluentui/react-dialog';
import { dialogBodyClassNames } from '@fluentui/react-dialog';
import { DialogBodyProps } from '@fluentui/react-dialog';
Expand Down Expand Up @@ -1642,6 +1643,7 @@ import { useDataGridStyles_unstable } from '@fluentui/react-table';
import { useDialog_unstable } from '@fluentui/react-dialog';
import { useDialogActions_unstable } from '@fluentui/react-dialog';
import { useDialogActionsStyles_unstable } from '@fluentui/react-dialog';
import { useDialogBackdropContext_unstable } from '@fluentui/react-dialog';
import { useDialogBody_unstable } from '@fluentui/react-dialog';
import { useDialogBodyStyles_unstable } from '@fluentui/react-dialog';
import { useDialogContent_unstable } from '@fluentui/react-dialog';
Expand Down Expand Up @@ -2701,6 +2703,8 @@ export { DialogActionsSlots }

export { DialogActionsState }

export { DialogBackdropContextValue }

export { DialogBody }

export { dialogBodyClassNames }
Expand Down Expand Up @@ -5301,6 +5305,8 @@ export { useDialogActions_unstable }

export { useDialogActionsStyles_unstable }

export { useDialogBackdropContext_unstable }

export { useDialogBody_unstable }

export { useDialogBodyStyles_unstable }
Expand Down
2 changes: 2 additions & 0 deletions packages/react-components/react-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,7 @@ export {
useDialogContent_unstable,
renderDialogContent_unstable,
useDialogContext_unstable,
useDialogBackdropContext_unstable,
useDialogSurfaceContext_unstable,
useDialogSurfaceContextValues_unstable,
DialogProvider,
Expand Down Expand Up @@ -914,6 +915,7 @@ export type {
DialogContextValue,
DialogSurfaceContextValue,
DialogSurfaceContextValues,
DialogBackdropContextValue,
} from '@fluentui/react-dialog';

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ARIAButtonType } from '@fluentui/react-aria';
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import { ContextSelector } from '@fluentui/react-context-selector';
import type { ExtractSlotProps } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { JSXElement } from '@fluentui/react-utilities';
import type { PortalProps } from '@fluentui/react-portal';
Expand Down Expand Up @@ -54,6 +55,17 @@ export type DialogActionsSlots = {
// @public
export type DialogActionsState = ComponentState<DialogActionsSlots> & Pick<Required<DialogActionsProps>, 'position' | 'fluid'>;

// @public (undocumented)
export type DialogBackdropContextValue = boolean;

// @public (undocumented)
export const DialogBackdropProvider: React_2.Provider<boolean | undefined>;

// @public
export type DialogBackdropSlotProps = ExtractSlotProps<Slot<'div'> & {
appearance?: 'dimmed' | 'transparent';
}>;

// @public
export const DialogBody: ForwardRefComponent<DialogBodyProps>;

Expand Down Expand Up @@ -180,7 +192,7 @@ export const DialogSurfaceProvider: React_2.Provider<boolean | undefined>;

// @public (undocumented)
export type DialogSurfaceSlots = {
backdrop?: Slot<'div'>;
backdrop?: Slot<DialogBackdropSlotProps>;
root: Slot<'div'>;
backdropMotion: Slot<PresenceMotionSlotProps>;
};
Expand All @@ -189,7 +201,9 @@ export type DialogSurfaceSlots = {
export type DialogSurfaceState = ComponentState<DialogSurfaceSlots> & Pick<DialogContextValue, 'isNestedDialog'> & Pick<PortalProps, 'mountNode'> & {
open?: boolean;
unmountOnClose?: boolean;
treatBackdropAsNested: boolean;
transitionStatus?: 'entering' | 'entered' | 'idle' | 'exiting' | 'exited' | 'unmounted';
backdropAppearance?: DialogBackdropSlotProps['appearance'];
};

// @public
Expand Down Expand Up @@ -262,6 +276,9 @@ export const useDialogActions_unstable: (props: DialogActionsProps, ref: React_2
// @public
export const useDialogActionsStyles_unstable: (state: DialogActionsState) => DialogActionsState;

// @public (undocumented)
export const useDialogBackdropContext_unstable: () => DialogBackdropContextValue | undefined;

// @public
export const useDialogBody_unstable: (props: DialogBodyProps, ref: React_2.Ref<HTMLElement>) => DialogBodyState;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type {
DialogBackdropSlotProps,
DialogSurfaceContextValues,
DialogSurfaceElement,
DialogSurfaceProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const useDialog_unstable = (props: DialogProps): DialogState => {
trapFocus: modalType !== 'non-modal',
legacyTrapFocus: !inertTrapFocus,
});

const isNestedDialog = useHasParentContext(DialogContext);

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
import type { PresenceMotionSlotProps } from '@fluentui/react-motion';
import type { PortalProps } from '@fluentui/react-portal';
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import type { ComponentProps, ComponentState, Slot, ExtractSlotProps } from '@fluentui/react-utilities';

import { DialogContextValue, DialogSurfaceContextValue } from '../../contexts';

/**
* Custom slot props for the backdrop slot.
*/
export type DialogBackdropSlotProps = ExtractSlotProps<
Slot<'div'> & {
/**
* Controls the backdrop appearance.
* - 'dimmed': Shows a dimmed backdrop (default for standalone dialogs)
* - 'transparent': Shows a transparent backdrop (default for nested dialogs)
*/
appearance?: 'dimmed' | 'transparent';
}
>;

export type DialogSurfaceSlots = {
/**
* Dimmed background of dialog.
* The default backdrop is rendered as a `<div>` with styling.
* This slot expects a `<div>` element which will replace the default backdrop.
* The backdrop should have `aria-hidden="true"`.
*
* Accepts an `appearance` prop to control backdrop visibility:
* - `'dimmed'`: Always shows a dimmed backdrop, regardless of nesting.
* - `'transparent'`: Always shows a transparent backdrop.
*
* @example
* ```tsx
* <DialogSurface backdrop={{ appearance: 'dimmed' }} />
* ```
*/
backdrop?: Slot<'div'>;
backdrop?: Slot<DialogBackdropSlotProps>;
root: Slot<'div'>;
/**
* For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs).
Expand Down Expand Up @@ -44,12 +66,18 @@ export type DialogSurfaceState = ComponentState<DialogSurfaceSlots> &
Pick<PortalProps, 'mountNode'> & {
open?: boolean;
unmountOnClose?: boolean;

/**
* Whether the backdrop should be treated as nested (transparent).
* When inside an OverlayDrawer, this is `false` even though `isNestedDialog` may be `true`,
* preventing the false-positive transparent backdrop.
*/
treatBackdropAsNested: boolean;
/**
* Transition status for animation.
* In test environment, this is always `undefined`.
*
* @deprecated Will be always `undefined`.
*/
transitionStatus?: 'entering' | 'entered' | 'idle' | 'exiting' | 'exited' | 'unmounted';
backdropAppearance?: DialogBackdropSlotProps['appearance'];
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { DialogSurface } from './DialogSurface';
export type {
DialogBackdropSlotProps,
DialogSurfaceContextValues,
DialogSurfaceElement,
DialogSurfaceProps,
Expand Down
Loading