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": "prerelease",
"comment": "feat: add motion for Drawer",
"packageName": "@fluentui/react-drawer",
"email": "[email protected]",
"dependentChangeType": "patch"
}
17 changes: 10 additions & 7 deletions packages/react-components/react-drawer/etc/react-drawer.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { DialogSurfaceProps } from '@fluentui/react-dialog';
import { DialogSurfaceSlots } from '@fluentui/react-dialog';
import { DialogTitleSlots } from '@fluentui/react-dialog';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import { MotionShorthand } from '@fluentui/react-motion-preview';
import { MotionState } from '@fluentui/react-motion-preview';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';
Expand Down Expand Up @@ -126,7 +128,7 @@ export type DrawerInlineSlots = {
};

// @public
export type DrawerInlineState = ComponentState<DrawerInlineSlots> & DrawerInlineProps;
export type DrawerInlineState = Required<ComponentState<DrawerInlineSlots> & DrawerBaseState & Pick<DrawerInlineProps, 'separator'>>;

// @public
export const DrawerOverlay: ForwardRefComponent<DrawerOverlayProps>;
Expand All @@ -143,12 +145,13 @@ export type DrawerOverlaySlots = DialogSurfaceSlots & {
};

// @public
export type DrawerOverlayState = ComponentState<DrawerOverlaySlots> & DrawerBaseProps & {
export type DrawerOverlayState = Required<Omit<ComponentState<DrawerOverlaySlots>, 'backdrop'> & DrawerBaseState & {
dialog: DialogProps;
};
backdropMotion: MotionState<HTMLDivElement>;
}>;

// @public
export type DrawerProps = ComponentProps<Partial<DrawerSlots>> & {
export type DrawerProps = ComponentProps<DrawerSlots> & {
type?: 'inline' | 'overlay';
};

Expand Down Expand Up @@ -182,7 +185,7 @@ export const renderDrawerHeaderTitle_unstable: (state: DrawerHeaderTitleState) =
export const renderDrawerInline_unstable: (state: DrawerInlineState) => JSX.Element | null;

// @public
export const renderDrawerOverlay_unstable: (state: DrawerOverlayState) => JSX.Element;
export const renderDrawerOverlay_unstable: (state: DrawerOverlayState) => JSX.Element | null;

// @public
export const useDrawer_unstable: (props: DrawerProps, ref: React_2.Ref<HTMLElement>) => DrawerState;
Expand Down Expand Up @@ -218,13 +221,13 @@ export const useDrawerHeaderTitle_unstable: (props: DrawerHeaderTitleProps, ref:
export const useDrawerHeaderTitleStyles_unstable: (state: DrawerHeaderTitleState) => DrawerHeaderTitleState;

// @public
export const useDrawerInline_unstable: (props: DrawerInlineProps, ref: React_2.Ref<HTMLElement>) => DrawerInlineState;
export const useDrawerInline_unstable: (props: DrawerInlineProps, ref: React_2.Ref<HTMLDivElement>) => DrawerInlineState;

// @public
export const useDrawerInlineStyles_unstable: (state: DrawerInlineState) => DrawerInlineState;

// @public
export const useDrawerOverlay_unstable: (props: DrawerOverlayProps, ref: React_2.Ref<HTMLElement>) => DrawerOverlayState;
export const useDrawerOverlay_unstable: (props: DrawerOverlayProps, ref: React_2.Ref<HTMLDivElement>) => DrawerOverlayState;

// @public
export const useDrawerOverlayStyles_unstable: (state: DrawerOverlayState) => DrawerOverlayState;
Expand Down
1 change: 1 addition & 0 deletions packages/react-components/react-drawer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"dependencies": {
"@fluentui/react-dialog": "^9.6.0",
"@fluentui/react-jsx-runtime": "^9.0.3",
"@fluentui/react-motion-preview": "^0.1.0",
"@fluentui/react-shared-contexts": "^9.7.2",
"@fluentui/react-theme": "^9.1.11",
"@fluentui/react-utilities": "^9.13.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type DrawerSlots = {
/**
* Drawer Props
*/
export type DrawerProps = ComponentProps<Partial<DrawerSlots>> & {
export type DrawerProps = ComponentProps<DrawerSlots> & {
/**
* Type of the drawer.
* @default overlay
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@ import { DrawerInline } from '../DrawerInline/DrawerInline';
export const useDrawer_unstable = (props: DrawerProps, ref: React.Ref<HTMLElement>): DrawerState => {
const { type = 'overlay' } = props;

const elementType = type === 'overlay' ? DrawerOverlay : DrawerInline;

return {
components: {
root: type === 'overlay' ? DrawerOverlay : DrawerInline,
root: elementType,
},

root: slot.always(props, {
defaultProps: {
root: slot.always<DrawerProps>(
slot.resolveShorthand({
ref,
} as DrawerProps,
elementType: type === 'overlay' ? DrawerOverlay : DrawerInline,
}),
...props,
}),
{
elementType,
},
),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ describe('DrawerInline', () => {
requiredProps: {
open: true,
},
// Disabled as this component returns null when not open by default
disabledTests: ['make-styles-overrides-win'],
});

// TODO add more tests here, and create visual regression tests in /apps/vr-tests
Expand All @@ -19,6 +21,11 @@ describe('DrawerInline', () => {
expect(result.container).toMatchInlineSnapshot(`<div />`);
});

it('renders an closed inline drawer', () => {
const result = render(<DrawerInline>Default Drawer</DrawerInline>);
expect(result.container).toMatchInlineSnapshot(`<div />`);
});

it('renders an open inline drawer', () => {
const result = render(<DrawerInline open>Default Drawer</DrawerInline>);
expect(result.container).toMatchInlineSnapshot(`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { DrawerInlineProps } from './DrawerInline.types';
import type { ForwardRefComponent } from '@fluentui/react-utilities';

/**
* DrawerInline is often used for navigation that is not dissmissable. As it is on the same level as
* DrawerInline is often used for navigation that is not dismissible. As it is on the same level as
* the main surface, users can still interact with other UI elements.
*/
export const DrawerInline: ForwardRefComponent<DrawerInlineProps> = React.forwardRef((props, ref) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { DrawerBaseProps } from '../../util/DrawerBase.types';
import { DrawerBaseProps, DrawerBaseState } from '../../util/DrawerBase.types';

export type DrawerInlineSlots = {
root: Slot<'div'>;
Expand All @@ -21,4 +21,6 @@ export type DrawerInlineProps = ComponentProps<DrawerInlineSlots> &
/**
* State used in rendering DrawerInline
*/
export type DrawerInlineState = ComponentState<DrawerInlineSlots> & DrawerInlineProps;
export type DrawerInlineState = Required<
ComponentState<DrawerInlineSlots> & DrawerBaseState & Pick<DrawerInlineProps, 'separator'>
>;
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import type { DrawerInlineState, DrawerInlineSlots } from './DrawerInline.types'
* Render the final JSX of DrawerInline
*/
export const renderDrawerInline_unstable = (state: DrawerInlineState) => {
assertSlots<DrawerInlineSlots>(state);

if (!state.open) {
if (!state.motion.canRender) {
return null;
}

assertSlots<DrawerInlineSlots>(state);

return <state.root />;
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as React from 'react';
import { getNativeElementProps, useControllableState, slot } from '@fluentui/react-utilities';
import { getNativeElementProps, useControllableState, slot, useMergedRefs } from '@fluentui/react-utilities';
import { useMotion } from '@fluentui/react-motion-preview';

import type { DrawerInlineProps, DrawerInlineState } from './DrawerInline.types';
import { getDefaultDrawerProps } from '../../util/getDefaultDrawerProps';
import { useDrawerDefaultProps } from '../../util/useDrawerDefaultProps';

/**
* Create the state required to render DrawerInline.
Expand All @@ -12,32 +14,37 @@ import { getDefaultDrawerProps } from '../../util/getDefaultDrawerProps';
* @param props - props from this instance of DrawerInline
* @param ref - reference to root HTMLElement of DrawerInline
*/
export const useDrawerInline_unstable = (props: DrawerInlineProps, ref: React.Ref<HTMLElement>): DrawerInlineState => {
const { open: initialOpen, defaultOpen, size, position } = getDefaultDrawerProps(props);
export const useDrawerInline_unstable = (
props: DrawerInlineProps,
ref: React.Ref<HTMLDivElement>,
): DrawerInlineState => {
const { size, position, ...defaultProps } = useDrawerDefaultProps(props);
const { separator = false } = props;

const [open] = useControllableState({
state: initialOpen,
defaultState: defaultOpen,
state: defaultProps.open,
defaultState: defaultProps.defaultOpen,
initialState: false,
});

const motion = useMotion<HTMLDivElement>(open);

return {
components: {
root: 'div',
},

root: slot.always(
getNativeElementProps('div', {
ref,
...props,
ref: useMergedRefs(ref, motion.ref),
}),
{ elementType: 'div' },
),

size,
position,
open,
separator,
motion,
};
};
Original file line number Diff line number Diff line change
@@ -1,53 +1,72 @@
import * as React from 'react';
import { makeStyles, mergeClasses, shorthands } from '@griffel/react';
import type { DrawerInlineSlots, DrawerInlineState } from './DrawerInline.types';
import type { SlotClassNames } from '@fluentui/react-utilities';
import { useDrawerBaseStyles } from '../../util/useDrawerBaseStyles.styles';
import { tokens } from '@fluentui/react-theme';

import type { DrawerInlineSlots, DrawerInlineState } from './DrawerInline.types';
import { drawerCSSVars, useDrawerBaseClassNames } from '../../util/useDrawerBaseStyles.styles';

export const drawerInlineClassNames: SlotClassNames<DrawerInlineSlots> = {
root: 'fui-DrawerInline',
};

/**
* Styles for the root slot
*/
const useStyles = makeStyles({
const separatorValues = ['1px', 'solid', tokens.colorNeutralBackground3] as const;
const useDrawerRootStyles = makeStyles({
root: {
position: 'relative',
opacity: 0,
transitionProperty: 'opacity, transform',
willChange: 'opacity, transform',
},

/* Separator */
separatorStart: {
...shorthands.borderRight('1px', 'solid', tokens.colorNeutralBackground3),
...shorthands.borderRight(...separatorValues),
},
separatorEnd: {
...shorthands.borderLeft('1px', 'solid', tokens.colorNeutralBackground3),
...shorthands.borderLeft(...separatorValues),
},

/* Positioning */
start: {
transform: `translate3D(calc(var(${drawerCSSVars.drawerSizeVar}) * -1), 0, 0)`,
},
end: {
transform: `translate3D(var(${drawerCSSVars.drawerSizeVar}), 0, 0)`,
},

/* Visible */
visible: {
opacity: 1,
transform: `translate3D(0, 0, 0)`,
},
});

/**
* Apply styling to the DrawerInline slots based on the state
*/
export const useDrawerInlineStyles_unstable = (state: DrawerInlineState): DrawerInlineState => {
const baseStyles = useDrawerBaseStyles();
const styles = useStyles();
const baseClassNames = useDrawerBaseClassNames(state);
const rootStyles = useDrawerRootStyles();

const separatorClass = React.useMemo(() => {
if (!state.separator) {
return undefined;
}

return state.position === 'start' ? styles.separatorStart : styles.separatorEnd;
}, [state.position, state.separator, styles.separatorEnd, styles.separatorStart]);
return state.position === 'start' ? rootStyles.separatorStart : rootStyles.separatorEnd;
}, [state.position, state.separator, rootStyles.separatorEnd, rootStyles.separatorStart]);

state.root.className = mergeClasses(
drawerInlineClassNames.root,
baseStyles.root,
styles.root,
state.size && baseStyles[state.size],
state.position && baseStyles[state.position],
baseClassNames,
rootStyles.root,
separatorClass,
rootStyles[state.position],
state.motion.active && rootStyles.visible,
state.root.className,
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DialogProps, DialogSurfaceProps, DialogSurfaceSlots } from '@fluentui/react-dialog';
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import { DrawerBaseProps } from '../../util/DrawerBase.types';
import type { MotionState } from '@fluentui/react-motion-preview';
import type { DrawerBaseProps, DrawerBaseState } from '../../util/DrawerBase.types';

export type DrawerOverlaySlots = DialogSurfaceSlots & {
root: Slot<DialogSurfaceProps>;
Expand All @@ -16,7 +17,10 @@ export type DrawerOverlayProps = ComponentProps<DrawerOverlaySlots> &
/**
* State used in rendering DrawerOverlay
*/
export type DrawerOverlayState = ComponentState<DrawerOverlaySlots> &
DrawerBaseProps & {
dialog: DialogProps;
};
export type DrawerOverlayState = Required<
Omit<ComponentState<DrawerOverlaySlots>, 'backdrop'> &
DrawerBaseState & {
dialog: DialogProps;
backdropMotion: MotionState<HTMLDivElement>;
}
>;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import { Dialog } from '@fluentui/react-dialog';
* Render the final JSX of DrawerOverlay
*/
export const renderDrawerOverlay_unstable = (state: DrawerOverlayState) => {
if (!state.motion.canRender) {
return null;
}

assertSlots<DrawerOverlaySlots>(state);

return (
Expand Down
Loading