diff --git a/apps/public-docsite-v9/src/DocsComponents/FluentDocsPage.stories.tsx b/apps/public-docsite-v9/src/DocsComponents/FluentDocsPage.stories.tsx index aaf9bf8cc8b023..7313f5f22f428d 100644 --- a/apps/public-docsite-v9/src/DocsComponents/FluentDocsPage.stories.tsx +++ b/apps/public-docsite-v9/src/DocsComponents/FluentDocsPage.stories.tsx @@ -10,6 +10,7 @@ import { Stories, type DocsContextProps, } from '@storybook/addon-docs'; +import type { PreparedStory, Renderer } from '@storybook/types'; import type { SBEnumType } from '@storybook/csf'; import { makeStyles, shorthands, tokens, Link, Text } from '@fluentui/react-components'; import { InfoFilled } from '@fluentui/react-icons'; @@ -18,6 +19,8 @@ import { DirSwitch } from './DirSwitch.stories'; import { ThemePicker } from './ThemePicker.stories'; import { Toc, nameToHash } from './Toc.stories'; +type PrimaryStory = PreparedStory; + const useStyles = makeStyles({ divider: { height: '1px', @@ -130,6 +133,49 @@ const getNativeElementsList = (elements: SBEnumType['value']): JSX.Element => { ); }; +const RenderArgsTable = ({ hideArgsTable, primaryStory }: { primaryStory: PrimaryStory; hideArgsTable: boolean }) => { + const styles = useStyles(); + return hideArgsTable ? null : ( + <> + + {primaryStory.argTypes.as && primaryStory.argTypes.as?.type?.name === 'enum' && ( +
+ +
+ + Native props are supported 🙌 + + + All HTML attributes native to the {getNativeElementsList(primaryStory.argTypes.as.type.value)}, including + all aria-* and data-* attributes, can be applied as native props on this + component. + +
+
+ )} + + ); +}; + +const RenderPrimaryStory = ({ + primaryStory, + skipPrimaryStory, +}: { + primaryStory: PrimaryStory; + skipPrimaryStory: boolean; +}) => { + const styles = useStyles(); + return skipPrimaryStory ? null : ( + <> +
+ + {primaryStory.name} + + + + ); +}; + export const FluentDocsPage = () => { const context = React.useContext(DocsContext); const stories = context.componentStories(); @@ -141,6 +187,9 @@ export const FluentDocsPage = () => { const dir = primaryStoryContext.parameters?.dir ?? primaryStoryContext.globals?.[DIR_ID] ?? 'ltr'; const selectedTheme = themes.find(theme => theme.id === primaryStoryContext.globals![THEME_ID]); + const hideArgsTable = Boolean(primaryStoryContext.parameters?.docs?.hideArgsTable); + const skipPrimaryStory = Boolean(primaryStoryContext.parameters?.docs?.skipPrimaryStory); + const videos = primaryStoryContext.parameters?.videos ?? null; const styles = useStyles(); // DEBUG @@ -169,27 +218,8 @@ export const FluentDocsPage = () => { {videos && } -
- - {primaryStory.name} - - - - {primaryStory.argTypes.as && primaryStory.argTypes.as?.type?.name === 'enum' && ( -
- -
- - Native props are supported 🙌 - - - All HTML attributes native to the {getNativeElementsList(primaryStory.argTypes.as.type.value)}, - including all aria-* and data-* attributes, can be applied as native props - on this component. - -
-
- )} + +
diff --git a/change/@fluentui-react-dialog-8ba14dae-2316-4b9a-b2f6-eb0d73be7cf3.json b/change/@fluentui-react-dialog-8ba14dae-2316-4b9a-b2f6-eb0d73be7cf3.json new file mode 100644 index 00000000000000..4ad2ad19a9bc47 --- /dev/null +++ b/change/@fluentui-react-dialog-8ba14dae-2316-4b9a-b2f6-eb0d73be7cf3.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "docs: add link to motion docs page", + "packageName": "@fluentui/react-dialog", + "email": "viktorgenaev@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-drawer-5d3bdcc4-6781-480e-9d49-f441ee95112e.json b/change/@fluentui-react-drawer-5d3bdcc4-6781-480e-9d49-f441ee95112e.json new file mode 100644 index 00000000000000..4f51f5bb71943d --- /dev/null +++ b/change/@fluentui-react-drawer-5d3bdcc4-6781-480e-9d49-f441ee95112e.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "docs: add link to motion docs page", + "packageName": "@fluentui/react-drawer", + "email": "viktorgenaev@microsoft.com", + "dependentChangeType": "none" +} diff --git a/packages/react-components/react-dialog/library/src/components/Dialog/Dialog.types.ts b/packages/react-components/react-dialog/library/src/components/Dialog/Dialog.types.ts index 3629d78884e4ec..503494ae64ac65 100644 --- a/packages/react-components/react-dialog/library/src/components/Dialog/Dialog.types.ts +++ b/packages/react-components/react-dialog/library/src/components/Dialog/Dialog.types.ts @@ -6,11 +6,14 @@ import type { DialogContextValue, DialogSurfaceContextValue } from '../../contex import type { DialogSurfaceElement } from '../DialogSurface/DialogSurface.types'; export type DialogSlots = { + /** + * For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs). + * + */ surfaceMotion: Slot; }; export type InternalDialogSlots = { - // motion slots cannot be nullable surfaceMotion: NonNullable>; }; diff --git a/packages/react-components/react-dialog/library/src/components/DialogSurface/DialogSurface.types.ts b/packages/react-components/react-dialog/library/src/components/DialogSurface/DialogSurface.types.ts index 21934a8c3d2b45..691cf9d222c7df 100644 --- a/packages/react-components/react-dialog/library/src/components/DialogSurface/DialogSurface.types.ts +++ b/packages/react-components/react-dialog/library/src/components/DialogSurface/DialogSurface.types.ts @@ -14,7 +14,10 @@ export type DialogSurfaceSlots = { */ backdrop?: Slot<'div'>; root: Slot<'div'>; - + /** + * For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs). + * + */ backdropMotion: Slot; }; diff --git a/packages/react-components/react-drawer/library/src/components/OverlayDrawer/OverlayDrawer.types.ts b/packages/react-components/react-drawer/library/src/components/OverlayDrawer/OverlayDrawer.types.ts index 23fad795bdb83c..c40d6e979bef5b 100644 --- a/packages/react-components/react-drawer/library/src/components/OverlayDrawer/OverlayDrawer.types.ts +++ b/packages/react-components/react-drawer/library/src/components/OverlayDrawer/OverlayDrawer.types.ts @@ -14,8 +14,15 @@ export type OverlayDrawerSlots = { * Slot for the root element. */ root: Slot; - + /** + * For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs). + * + */ backdropMotion?: Slot>; + /** + * For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs). + * + */ surfaceMotion?: Slot>; }; diff --git a/packages/react-components/react-motion/stories/src/CreateMotionComponent/CreateMotionComponent.stories.tsx b/packages/react-components/react-motion/stories/src/CreateMotionComponent/CreateMotionComponent.stories.tsx index 71fe3babea91b1..22bffe3199a9a7 100644 --- a/packages/react-components/react-motion/stories/src/CreateMotionComponent/CreateMotionComponent.stories.tsx +++ b/packages/react-components/react-motion/stories/src/CreateMotionComponent/CreateMotionComponent.stories.tsx @@ -1,4 +1,4 @@ -import { createMotionComponent, makeStyles, tokens } from '@fluentui/react-components'; +import { createMotionComponent, MotionComponentProps, makeStyles, tokens } from '@fluentui/react-components'; import * as React from 'react'; import description from './CreateMotionComponent.stories.md'; @@ -47,7 +47,7 @@ const DropIn = createMotionComponent({ iterations: Infinity, }); -export const CreateMotionComponent = () => { +export const CreateMotionComponent = (props: MotionComponentProps) => { const classes = useClasses(); return ( diff --git a/packages/react-components/react-motion/stories/src/CreateMotionComponent/index.stories.ts b/packages/react-components/react-motion/stories/src/CreateMotionComponent/index.stories.ts index bc1173d4430a6a..0790663b551ca8 100644 --- a/packages/react-components/react-motion/stories/src/CreateMotionComponent/index.stories.ts +++ b/packages/react-components/react-motion/stories/src/CreateMotionComponent/index.stories.ts @@ -1,5 +1,6 @@ import type { Meta } from '@storybook/react'; import CreateMotionComponentDescription from './CreateMotionComponentDescription.md'; +import { CreateMotionComponent } from './CreateMotionComponent.stories'; export { CreateMotionComponentDefault as Default } from './CreateMotionComponentDefault.stories'; @@ -15,6 +16,7 @@ export { MotionFunctionParams as functionParams } from './MotionFunctionParams.s export default { title: 'Motion/APIs/createMotionComponent', + component: CreateMotionComponent, parameters: { docs: { description: { diff --git a/packages/react-components/react-motion/stories/src/CreatePresenceComponent/CreatePresenceComponent.stories.tsx b/packages/react-components/react-motion/stories/src/CreatePresenceComponent/CreatePresenceComponent.stories.tsx index e723865a17cfa7..5b596c0d203927 100644 --- a/packages/react-components/react-motion/stories/src/CreatePresenceComponent/CreatePresenceComponent.stories.tsx +++ b/packages/react-components/react-motion/stories/src/CreatePresenceComponent/CreatePresenceComponent.stories.tsx @@ -3,6 +3,7 @@ import { Field, makeStyles, type MotionImperativeRef, + PresenceComponentProps, tokens, Switch, } from '@fluentui/react-components'; @@ -67,7 +68,7 @@ const DropIn = createPresenceComponent({ }, }); -export const CreatePresenceComponent = () => { +export const CreatePresenceComponent = (props: PresenceComponentProps) => { const classes = useClasses(); const motionRef = React.useRef(); diff --git a/packages/react-components/react-motion/stories/src/CreatePresenceComponent/index.stories.ts b/packages/react-components/react-motion/stories/src/CreatePresenceComponent/index.stories.ts index 1eed397e33eb4b..8b2c27e148a1f6 100644 --- a/packages/react-components/react-motion/stories/src/CreatePresenceComponent/index.stories.ts +++ b/packages/react-components/react-motion/stories/src/CreatePresenceComponent/index.stories.ts @@ -1,5 +1,6 @@ import type { Meta } from '@storybook/react'; import CreatePresenceComponentDescription from './CreatePresenceComponentDescription.md'; +import { CreatePresenceComponent } from './CreatePresenceComponent.stories'; export { CreatePresenceComponentDefault as Default } from './CreatePresenceComponentDefault.stories'; @@ -15,6 +16,7 @@ export { PresenceMotionFunctionParams as functionParams } from './PresenceMotion export default { title: 'Motion/APIs/createPresenceComponent', + component: CreatePresenceComponent, parameters: { docs: { description: { diff --git a/packages/react-components/react-motion/stories/src/Motion/CustomMotion.stories.tsx b/packages/react-components/react-motion/stories/src/Motion/CustomMotion.stories.tsx new file mode 100644 index 00000000000000..624c097e228942 --- /dev/null +++ b/packages/react-components/react-motion/stories/src/Motion/CustomMotion.stories.tsx @@ -0,0 +1,137 @@ +import * as React from 'react'; +import { + Dialog, + tokens, + DialogTrigger, + DialogSurface, + makeStyles, + DialogTitle, + DialogBody, + DialogActions, + DialogContent, + Button, + Link, + createPresenceComponent, + motionTokens, +} from '@fluentui/react-components'; + +const useStyles = makeStyles({ + wrapper: { + display: 'flex', + gap: '20px', + }, +}); + +const SlideDialogMotion = createPresenceComponent(() => { + const keyframes = [ + { opacity: 0, transform: 'translateX(-100%)', boxShadow: '0px 0px 0px 0px rgba(0, 0, 0, 0.1)' }, + { opacity: 1, transform: 'translateX(0)', boxShadow: tokens.shadow64 }, + ]; + + return { + enter: { + keyframes, + easing: motionTokens.curveDecelerateMax, + duration: motionTokens.durationGentle, + }, + exit: { + keyframes: [...keyframes].reverse(), + easing: motionTokens.curveAccelerateMid, + duration: motionTokens.durationGentle, + }, + }; +}); + +const RadialBackdropMotion = createPresenceComponent(() => { + const keyframes = [ + { + opacity: 0, + background: 'radial-gradient(circle at center, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%)', + transform: 'scale(0)', + }, + { + opacity: 1, + background: 'radial-gradient(circle at center, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%)', + transform: 'scale(1)', + }, + ]; + + return { + enter: { + keyframes: keyframes, + easing: motionTokens.curveDecelerateMax, + duration: motionTokens.durationGentle, + }, + exit: { + keyframes: [...keyframes].reverse(), + easing: motionTokens.curveAccelerateMax, + duration: motionTokens.durationGentle, + }, + }; +}); + +export const CustomMotion = () => { + const classes = useStyles(); + + return ( +
+ {props.children}, + }} + > + + + + + + Custom surface motion + + If you need to use a custom animation, you can use a render function inside surfaceMotion slot and + apply your custom animation built with{' '} + + createPresenceComponent + + + + + + + + + + + + + + + + {props.children}, + }} + > + + Custom backdrop + Customized dimmed background motion + + + + + + + + + +
+ ); +}; + +CustomMotion.parameters = { + docs: { + description: { + story: `If you need to use a custom animation, you can use a render function inside a slot and +apply your custom animation built with [createPresenceComponent](https://react.fluentui.dev/?path=/docs/motion-apis-createpresencecomponent--docs").`, + }, + }, +}; diff --git a/packages/react-components/react-motion/stories/src/Motion/DisableMotion.stories.tsx b/packages/react-components/react-motion/stories/src/Motion/DisableMotion.stories.tsx new file mode 100644 index 00000000000000..6f1dcb145dbce5 --- /dev/null +++ b/packages/react-components/react-motion/stories/src/Motion/DisableMotion.stories.tsx @@ -0,0 +1,68 @@ +import * as React from 'react'; +import { + Dialog, + DialogTrigger, + DialogSurface, + makeStyles, + DialogTitle, + DialogBody, + DialogActions, + DialogContent, + Button, +} from '@fluentui/react-components'; + +const useStyles = makeStyles({ + wrapper: { + display: 'flex', + gap: '20px', + }, +}); + +export const DisableMotion = () => { + const classes = useStyles(); + + return ( +
+ + + + + + + Surface motion disabled + Dialog has no surface scale animation. + + + + + + + + + + + + + + + + Dialog has no animation and backdrop + Backdrop (dimmed background of dialog) can also be disabled. + + + + + + + + + +
+ ); +}; + +DisableMotion.parameters = { + docs: { + description: { story: 'In order to disable motion you can pass `null` to appropriate slot.' }, + }, +}; diff --git a/packages/react-components/react-motion/stories/src/Motion/MotionDescription.md b/packages/react-components/react-motion/stories/src/Motion/MotionDescription.md new file mode 100644 index 00000000000000..e9ba7e8d1ec6f2 --- /dev/null +++ b/packages/react-components/react-motion/stories/src/Motion/MotionDescription.md @@ -0,0 +1,9 @@ +Fluent components leverages the `@fluentui/react-motion` to handle animations. All components that expose a motion slot, can be configured in a similar way. For detailed guidance on creating animations using `@fluentui/react-motion`, refer to the [motion API](https://react.fluentui.dev/?path=/docs/motion-apis-createpresencecomponent--docs) documentation. + +### Components using motion API + +- Dialog +- Drawer +- Toast (_internally uses the motion API but does not expose a slot_) + +For components that provide a slot to control animation, you can disable or customize the motion by configuring these slots. diff --git a/packages/react-components/react-motion/stories/src/Motion/index.stories.tsx b/packages/react-components/react-motion/stories/src/Motion/index.stories.tsx new file mode 100644 index 00000000000000..db375664142e45 --- /dev/null +++ b/packages/react-components/react-motion/stories/src/Motion/index.stories.tsx @@ -0,0 +1,20 @@ +import type { Meta } from '@storybook/react'; + +import description from './MotionDescription.md'; + +export { DisableMotion } from './DisableMotion.stories'; +export { CustomMotion } from './CustomMotion.stories'; + +export default { + title: 'Motion/Motion Slot', + parameters: { + layout: 'centered', + docs: { + description: { + component: description, + }, + hideArgsTable: true, + skipPrimaryStory: true, + }, + }, +} satisfies Meta; diff --git a/packages/react-components/react-motion/stories/src/PresenceGroup/index.stories.ts b/packages/react-components/react-motion/stories/src/PresenceGroup/index.stories.ts index ac4894f5b70478..184c519dd0eee1 100644 --- a/packages/react-components/react-motion/stories/src/PresenceGroup/index.stories.ts +++ b/packages/react-components/react-motion/stories/src/PresenceGroup/index.stories.ts @@ -10,6 +10,7 @@ export default { description: { component: PresenceGroupDescription, }, + hideArgsTable: true, }, }, } satisfies Meta;