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
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: adds motion to DialogSurface",
"packageName": "@fluentui/react-dialog",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ export type DialogSurfaceSlots = {
};

// @public
export type DialogSurfaceState = ComponentState<DialogSurfaceSlots> & Partial<Pick<DialogContextValue, 'isNestedDialog'>> & Pick<PortalProps, 'mountNode'>;
export type DialogSurfaceState = ComponentState<DialogSurfaceSlots> & Pick<DialogContextValue, 'isNestedDialog'> & Pick<PortalProps, 'mountNode'> & {
transitionStatus?: 'entering' | 'entered' | 'idle' | 'exiting' | 'exited' | 'unmounted';
};

// @public
export const DialogTitle: ForwardRefComponent<DialogTitleProps>;
Expand Down
1 change: 1 addition & 0 deletions packages/react-components/react-dialog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"dependencies": {
"@fluentui/react-utilities": "^9.15.1",
"@fluentui/react-jsx-runtime": "^9.0.18",
"react-transition-group": "^4.4.1",
"@fluentui/keyboard-keys": "^9.0.6",
"@fluentui/react-context-selector": "^9.1.41",
"@fluentui/react-shared-contexts": "^9.10.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/** @jsxRuntime automatic */
/** @jsxImportSource @fluentui/react-jsx-runtime */

import { Transition } from 'react-transition-group';
import { DialogProvider, DialogSurfaceProvider } from '../../contexts';
import type { DialogState, DialogContextValues } from './Dialog.types';
import { DialogTransitionProvider } from '../../contexts/dialogTransitionContext';

/**
* Render the final JSX of Dialog
Expand All @@ -14,7 +16,16 @@ export const renderDialog_unstable = (state: DialogState, contextValues: DialogC
<DialogProvider value={contextValues.dialog}>
<DialogSurfaceProvider value={contextValues.dialogSurface}>
{trigger}
{content}
<Transition
mountOnEnter
unmountOnExit
in={state.open}
nodeRef={state.dialogRef}
// FIXME: this should not be hardcoded tokens.durationGentle
timeout={250}
>
{status => <DialogTransitionProvider value={status}>{content}</DialogTransitionProvider>}
</Transition>
</DialogSurfaceProvider>
</DialogProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const useDialog_unstable = (props: DialogProps): DialogState => {
inertTrapFocus,
open,
modalType,
content: open ? content : null,
content,
trigger,
requestOpenChange,
dialogTitleId: useId('dialog-title-'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ export type DialogSurfaceContextValues = {
*/
export type DialogSurfaceState = ComponentState<DialogSurfaceSlots> &
// This is only partial to avoid breaking changes, it should be mandatory and in fact it is always defined internally.
Partial<Pick<DialogContextValue, 'isNestedDialog'>> &
Pick<PortalProps, 'mountNode'>;
Pick<DialogContextValue, 'isNestedDialog'> &
Pick<PortalProps, 'mountNode'> & {
// This is only optional to avoid breaking changes, it should be mandatory and in fact it is always defined internally.
transitionStatus?: 'entering' | 'entered' | 'idle' | 'exiting' | 'exited' | 'unmounted';
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import type { DialogSurfaceElement, DialogSurfaceProps, DialogSurfaceState } from './DialogSurface.types';
import { useDialogContext_unstable } from '../../contexts';
import { Escape } from '@fluentui/keyboard-keys';
import { useDialogTransitionContext_unstable } from '../../contexts/dialogTransitionContext';

/**
* Create the state required to render DialogSurface.
Expand All @@ -25,9 +26,9 @@ export const useDialogSurface_unstable = (
): DialogSurfaceState => {
const modalType = useDialogContext_unstable(ctx => ctx.modalType);
const isNestedDialog = useDialogContext_unstable(ctx => ctx.isNestedDialog);
const transitionStatus = useDialogTransitionContext_unstable();
const modalAttributes = useDialogContext_unstable(ctx => ctx.modalAttributes);
const dialogRef = useDialogContext_unstable(ctx => ctx.dialogRef);
const open = useDialogContext_unstable(ctx => ctx.open);
const requestOpenChange = useDialogContext_unstable(ctx => ctx.requestOpenChange);
const dialogTitleID = useDialogContext_unstable(ctx => ctx.dialogTitleId);

Expand Down Expand Up @@ -60,7 +61,7 @@ export const useDialogSurface_unstable = (
});

const backdrop = slot.optional(props.backdrop, {
renderByDefault: open && modalType !== 'non-modal',
renderByDefault: modalType !== 'non-modal',
defaultProps: {
'aria-hidden': 'true',
},
Expand All @@ -73,6 +74,7 @@ export const useDialogSurface_unstable = (
components: { backdrop: 'div', root: 'div' },
backdrop,
isNestedDialog,
transitionStatus,
mountNode: props.mountNode,
root: slot.always(
getIntrinsicElementProps('div', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const dialogSurfaceClassNames: SlotClassNames<DialogSurfaceSlots> = {
/**
* Styles for the root slot
*/
const useRootResetStyles = makeResetStyles({
const useRootBaseStyle = makeResetStyles({
...createFocusOutlineStyle(),
...shorthands.inset(0),
...shorthands.padding(0),
Expand All @@ -33,46 +33,96 @@ const useRootResetStyles = makeResetStyles({
maxWidth: '600px',
maxHeight: '100vh',
boxSizing: 'border-box',
boxShadow: tokens.shadow64,
backgroundColor: tokens.colorNeutralBackground1,
color: tokens.colorNeutralForeground1,

[MEDIA_QUERY_BREAKPOINT_SELECTOR]: {
maxWidth: '100vw',
},

// initial style before animation:
opacity: 0,
transitionDuration: tokens.durationGentle,
transitionProperty: 'opacity, transform, box-shadow',
// // FIXME: https://github.com/microsoft/fluentui/issues/29473
transitionTimingFunction: tokens.curveDecelerateMid,
boxShadow: '0px 0px 0px 0px rgba(0, 0, 0, 0.1)',
transform: 'scale(0.85) translateZ(0)',
});

const useBackdropStyles = makeStyles({
nestedDialogBackdrop: {
backgroundColor: tokens.colorTransparentBackground,
const useRootStyles = makeStyles({
unmounted: {},
entering: {},
entered: {
boxShadow: tokens.shadow64,
transform: 'scale(1) translateZ(0)',
opacity: 1,
},
idle: {},
exiting: {
transitionTimingFunction: tokens.curveAccelerateMin,
},
exited: {},
});

/**
* Styles for the backdrop slot
*/
const useBackdropResetStyles = makeResetStyles({
const useBackdropBaseStyle = makeResetStyles({
...shorthands.inset('0px'),
backgroundColor: 'rgba(0, 0, 0, 0.4)',
position: 'fixed',

// initial style before animation:
transitionDuration: tokens.durationGentle,
transitionTimingFunction: tokens.curveLinear,
transitionProperty: 'opacity',
willChange: 'opacity',
opacity: 0,
});

const useBackdropStyles = makeStyles({
nestedDialogBackdrop: {
backgroundColor: tokens.colorTransparentBackground,
},
unmounted: {},
entering: {},
entered: {
opacity: 1,
},
idle: {},
exiting: {
transitionTimingFunction: tokens.curveAccelerateMin,
},
exited: {},
});

/**
* Apply styling to the DialogSurface slots based on the state
*/
export const useDialogSurfaceStyles_unstable = (state: DialogSurfaceState): DialogSurfaceState => {
const surfaceResetStyles = useRootResetStyles();
const styles = useBackdropStyles();
const backdropResetStyles = useBackdropResetStyles();
const { isNestedDialog, root, backdrop, transitionStatus = 'unmounted' } = state;

const rootBaseStyle = useRootBaseStyle();
const rootStyles = useRootStyles();

const backdropBaseStyle = useBackdropBaseStyle();
const backdropStyles = useBackdropStyles();

state.root.className = mergeClasses(dialogSurfaceClassNames.root, surfaceResetStyles, state.root.className);
root.className = mergeClasses(
dialogSurfaceClassNames.root,
rootBaseStyle,
rootStyles[transitionStatus],
root.className,
);

if (state.backdrop) {
state.backdrop.className = mergeClasses(
if (backdrop) {
backdrop.className = mergeClasses(
dialogSurfaceClassNames.backdrop,
backdropResetStyles,
state.isNestedDialog && styles.nestedDialogBackdrop,
state.backdrop.className,
backdropBaseStyle,
isNestedDialog && backdropStyles.nestedDialogBackdrop,
backdropStyles[transitionStatus],
backdrop.className,
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';
import { TransitionStatus } from 'react-transition-group';

/**
* @internal
*/
export type DialogTransitionContextValue = TransitionStatus;

/**
* @internal
*/
const defaultContextValue: DialogTransitionContextValue = 'unmounted';

// Contexts should default to undefined
/**
* @internal
*/
export const DialogTransitionContext: React.Context<DialogTransitionContextValue | undefined> = React.createContext<
DialogTransitionContextValue | undefined
>(undefined);

/**
* @internal
*/
export const DialogTransitionProvider = DialogTransitionContext.Provider;

/**
* @internal
*/
export const useDialogTransitionContext_unstable = (): DialogTransitionContextValue => {
return React.useContext(DialogTransitionContext) ?? defaultContextValue;
};