From 630755520995cb901909cb0a9a58803da0bc60d7 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Mon, 27 Mar 2023 09:45:39 +0200 Subject: [PATCH 01/17] add initial proposal --- ...mponent-transitions-on-mount-or-unmount.md | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md new file mode 100644 index 00000000000000..a3954e3efe112f --- /dev/null +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -0,0 +1,124 @@ +# RFC: Component CSS Transitions on mount/unmount + + + +--- + +@marcosmoura + +## Summary + +This RFC describes how CSS transitions in Fluent UI components can be applied. + +## Background + +Currently it is not possible to add CSS transition to components on mount/unmount. A usage of this pattern would be transitions to components that has a open/close toggle, such as Drawer, Dialog and more. + +## Problem statement + +In order to display that a content is showing or hiding from screen, CSS transitions should be applied. Due to React's way of rendering, there is no builtin support for this, therefore we need to create a solution that can provide with the status of component on screen. + +## Detailed Design or Proposal + +### A `useAnimationState` hook based solution + +To differentiate the animation state of a component, a hook could be created and be part of the react-utilities. + +Usage in the component: + +```ts +import * as React from 'react'; +import { getNativeElementProps, useAnimationState } from '@fluentui/react-utilities'; + +import type { SampleProps, SampleState } from './Sample.types'; + +export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref): SampleState => { + const { visible, mounted, animating, entering, exiting } = useAnimationState(open, { + enterDuration: 200, + exitDuration: 250, + }); + + return { + components: { + root: 'div', + }, + + root: getNativeElementProps('div'), + + visible, + mounted, + animating, + entering, + exiting, + }; +}; +``` + +Applying styles: + +```ts +import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; +import type { SampleSlots, SampleState } from './Sample.types'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { tokens } from '@fluentui/react-theme'; + +export const SampleClassNames: SlotClassNames = { + root: 'fui-Sample', +}; + +/** + * Styles for the root slot + */ +const useStyles = makeStyles({ + root: { + transitionTimingFunction: 'ease-out', + transitionProperty: 'opacity', + }, + + entering: { + transitionDuration: '200ms', + }, + + exiting: { + transitionDuration: '250ms', + }, + + visible: { + opacity: 1, + }, +}); + +export const useSampleStyles_unstable = (state: SampleState): SampleState => { + const styles = useStyles(); + + state.root.className = mergeClasses( + SampleClassNames.root, + state.visible && styles.visible, + state.entering && styles.entering, + state.exiting && styles.exiting, + styles.root, + ); + + return state; +}; +``` + +#### Pros + +- 👍 Simple hook. +- 👍 Easy to style components based on state. +- 👍 Freedom to declare transitions as well as animations. + +#### Cons + +- 👎 Need to sync the duration values in styles and hook parameter. +- 👎 In order to use tokens to express the durations, a change to the [tokens package](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/durations.ts) would be needed to export the values as numbers, so they could be used in JS. From 23ea8d5633d2ebb010725d46e5937a1eda53b70d Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Mon, 27 Mar 2023 09:49:44 +0200 Subject: [PATCH 02/17] add note about rerender --- .../convergence/component-transitions-on-mount-or-unmount.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index a3954e3efe112f..a1dfe69f4db3b3 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -31,7 +31,7 @@ In order to display that a content is showing or hiding from screen, CSS transit ### A `useAnimationState` hook based solution -To differentiate the animation state of a component, a hook could be created and be part of the react-utilities. +To differentiate the animation state of a component, a hook could be created and be part of the react-utilities. Note that every time a status change (from not visible to entering for example) the component will rerender. Usage in the component: From 8d0e21c0902b1fcbcfd3c5df9e023bb8c7682668 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Mon, 27 Mar 2023 10:02:38 +0200 Subject: [PATCH 03/17] add reference to initial implementation --- .../convergence/component-transitions-on-mount-or-unmount.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index a1dfe69f4db3b3..3f978c8fce8eeb 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -31,7 +31,7 @@ In order to display that a content is showing or hiding from screen, CSS transit ### A `useAnimationState` hook based solution -To differentiate the animation state of a component, a hook could be created and be part of the react-utilities. Note that every time a status change (from not visible to entering for example) the component will rerender. +To differentiate the animation state of a component, a hook could be created and be part of the react-utilities. Note that every time a status change (from not visible to entering for example) the component will rerender. A draft implementation of the hook: [useAnimationState](https://github.com/marcosmoura/fluentui/blob/marcosmoura/feat/drawer-component/packages/react-components/react-drawer/src/components/Drawer/useAnimationState.ts) Usage in the component: From 48748a65902867bab69c305ec092087c5f3f12d4 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 29 Mar 2023 14:48:29 +0200 Subject: [PATCH 04/17] add griffel mention as a "pro" --- .../convergence/component-transitions-on-mount-or-unmount.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 3f978c8fce8eeb..5a6d326983d7d0 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -115,6 +115,7 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { #### Pros - 👍 Simple hook. +- 👍 Use Griffel styles to create transitions - 👍 Easy to style components based on state. - 👍 Freedom to declare transitions as well as animations. From 9666773239bfa43aa6169e0c701a4033e50c4030 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Thu, 22 Jun 2023 18:28:22 +0200 Subject: [PATCH 05/17] docs: recreate RFC based on a more refined approach --- ...mponent-transitions-on-mount-or-unmount.md | 160 ++++++++++++++---- 1 file changed, 127 insertions(+), 33 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 5a6d326983d7d0..01c1c3c9e9591e 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -1,15 +1,4 @@ -# RFC: Component CSS Transitions on mount/unmount - - +# RFC: Component CSS Animations/Transitions on mount/unmount --- @@ -17,53 +6,79 @@ Tips for writing a successful RFC: ## Summary -This RFC describes how CSS transitions in Fluent UI components can be applied. +This RFC outlines the implementation details for effectively tracking and applying CSS animations/transitions within Fluent UI components, with a particular focus on the mount/unmount state. ## Background -Currently it is not possible to add CSS transition to components on mount/unmount. A usage of this pattern would be transitions to components that has a open/close toggle, such as Drawer, Dialog and more. +Currently, there is a limitation in incorporating motion effects to components during their mounting or unmounting. This restricts the ability to apply CSS transitions/animations for components featuring an open/close behavior, such as Drawer, Dialog, and various others. ## Problem statement -In order to display that a content is showing or hiding from screen, CSS transitions should be applied. Due to React's way of rendering, there is no builtin support for this, therefore we need to create a solution that can provide with the status of component on screen. +In order to display that a content is showing or hiding from screen, CSS transitions or animations should be applied. React lacks built-in support for applying CSS animations or transitions specifically during the mount and unmount phases of a component's lifecycle and we need to develop a solution that can determine the on-screen status of a component. Even though there are existing packages available that could solve this issue, they either would increase bundle size (e.g., [Framer Motion](https://www.framer.com/motion/) and [React Spring](https://www.react-spring.dev/)) or lacking flexibility and better integration with Griffel ([React Transition Group](https://reactcommunity.org/react-transition-group/)). ## Detailed Design or Proposal -### A `useAnimationState` hook based solution +### A `useMotionPresence` hook based solution + +To determine the motion state of a component, a hook could be created as part of the @fluentui/react-utilities package. A preliminary implementation of this hook can be found here: [useMotionPresence](https://github.com/marcosmoura/fluentui/blob/feat/drawer-motion/packages/react-components/react-utilities/src/hooks/useMotionPresence.ts). -To differentiate the animation state of a component, a hook could be created and be part of the react-utilities. Note that every time a status change (from not visible to entering for example) the component will rerender. A draft implementation of the hook: [useAnimationState](https://github.com/marcosmoura/fluentui/blob/marcosmoura/feat/drawer-component/packages/react-components/react-drawer/src/components/Drawer/useAnimationState.ts) +#### What is it? -Usage in the component: +A tracker hook, that monitors the state of animations and transitions for a particular element. A hook to track the state of animations/transitions. This hook does not directly create animations but instead synchronizes with CSS properties to determine the rendering status, visibility, entering, leaving, and ongoing animation of a component. If any CSS changes or properties are overridden, this hook will automatically adjust and stay synchronized. + +#### Usage + +State: ```ts import * as React from 'react'; -import { getNativeElementProps, useAnimationState } from '@fluentui/react-utilities'; +import { getNativeElementProps, useMotionPresence, useMergedRefs } from '@fluentui/react-utilities'; import type { SampleProps, SampleState } from './Sample.types'; export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref): SampleState => { - const { visible, mounted, animating, entering, exiting } = useAnimationState(open, { - enterDuration: 200, - exitDuration: 250, - }); + const { ref: componentRef, shouldRender, visible, entering, exiting, animating } = useMotionPresence(open); return { components: { root: 'div', }, - root: getNativeElementProps('div'), + root: getNativeElementProps('div', { + ref: useMergedRefs(ref, componentRef), + }), + shouldRender, visible, - mounted, - animating, entering, exiting, + animating, }; }; ``` -Applying styles: +Renderization: + +```tsx +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utilities'; +import type { SampleState, SampleSlots } from './Sample.types'; + +/** + * Render the final JSX of Sample + */ +export const renderSample_unstable = (state: SampleState) => { + const { slots, slotProps } = getSlots(state); + + if (!state.shouldRender) { + return null; + } + + return ; +}; +``` + +Styles: ```ts import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; @@ -80,8 +95,10 @@ export const SampleClassNames: SlotClassNames = { */ const useStyles = makeStyles({ root: { + opacity: 0, transitionTimingFunction: 'ease-out', transitionProperty: 'opacity', + willChange: 'opacity', }, entering: { @@ -112,14 +129,91 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { }; ``` +#### How it works? + +The hook accepts a boolean value that represents the "presence" state. It internally tracks the animation/transition and return boolean values related to its motion state and a ref that needs to be assigned to the element we are tracking. + +```ts +const { + /* + * Ref used to track the target element that requires animation. It should + * be passed to the element responsible for performing the animation. + */ + ref, + + /* + * Determines whether the component should be displayed on the screen. + * + * This property will evaluate to `true` when the provided presence value is true. + * Once the provided value changes to false, the hook will monitor the transition/animation + * completion and subsequently set this property to false. + */ + shouldRender, + + /* + * Indicates whether the component is currently rendered and visible. + * + * This flag will be set to `true` one frame after the shouldRender value becomes true. + * It will be set to `false` when the specified presence value changes to false. + */ + visible, + + /* + * Indicates whether the component is currently entering the screen. + * + * This will be `true` when the element is entering the screen. This happens + * one frame after `visible` value becomes true. + * It becomes `false` when the transition/animation ends. + */ + entering, + + /* + * Whether the component is leaving the screen + * + * This property is set to `true` one frame after the `visible` prop becomes true, + * indicating that the element is in the process of entering the screen. + * It becomes `false` when the transition/animation ends. + * It is set to false once the transition or animation for the component has completed. + */ + exiting, + + /* + * Indicates whether the component is currently undergoing animation. + * This property will be set to `true` when the component is either entering or exiting. + */ + animating, +} = useMotionPresence(open); +``` + +Events can be provided as a second argument of the hook.: + +```js + const presence = useMotionPresence(open, { + onEntered: () => { + // Called when the element finished the "enter" animation/transition + }; + onExited: () => { + /// Called when the element finished the "leave" animation/transition + }; + }) +``` + +Other events can be implemented, like `onBeforeEnter` or `onBeforeLeave`. + +#### Background research + +- Vue.js [Transition component](https://vuejs.org/guide/built-ins/transition.html#javascript-hooks). The [implementation](https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/components/Transition.ts) CSS transitions and animations and provides classes to the components based on the detected transitions. +- Radix UI has an internal package called [Presence](https://www.npmjs.com/package/@radix-ui/react-presence), which addresses the same issue. To handle this, they offer a [`usePresence` hook and a Presence component](https://github.com/radix-ui/primitives/blob/main/packages/react/presence/src/Presence.tsx). The implementation relies on animations only so they only need to determine whether the element should be present or not on the screen. + #### Pros -- 👍 Simple hook. -- 👍 Use Griffel styles to create transitions -- 👍 Easy to style components based on state. -- 👍 Freedom to declare transitions as well as animations. +- 👍 Hook based solution. +- 👍 Griffel styles can be used normally to create motion. +- 👍 Easy styling of components based on their state. +- 👍 Offers the flexibility to declare both transitions and animations. +- 👍 Can be applied to multiple elements simultaneously. +- 👍 Animations/Transitions can be easily overridden on the application side. #### Cons -- 👎 Need to sync the duration values in styles and hook parameter. -- 👎 In order to use tokens to express the durations, a change to the [tokens package](https://github.com/microsoft/fluentui/blob/master/packages/tokens/src/global/durations.ts) would be needed to export the values as numbers, so they could be used in JS. +- 👎 Lacks support for sequential animation playback, similar to React Transition Group. From cd22d10aa74d513b69b199b06bbc11cc77ee82a4 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Thu, 22 Jun 2023 18:53:57 +0200 Subject: [PATCH 06/17] docs: add example of transition override --- ...mponent-transitions-on-mount-or-unmount.md | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 01c1c3c9e9591e..cf0d20940ffc79 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -129,6 +129,26 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { }; ``` +Overriding the transition on the Application side: + +```tsx +import * as React from 'react'; +import { Drawer } from '@fluentui/react-drawer'; +import { makeStyles } from '@fluentui/react-components'; + +const useStyles = makeStyles({ + customDuration: { + transitionDuration: '500ms', + }, +}); + +export const CustomDuration = () => { + const styles = useStyles(); + + return ; +}; +``` + #### How it works? The hook accepts a boolean value that represents the "presence" state. It internally tracks the animation/transition and return boolean values related to its motion state and a ref that needs to be assigned to the element we are tracking. @@ -193,7 +213,7 @@ Events can be provided as a second argument of the hook.: // Called when the element finished the "enter" animation/transition }; onExited: () => { - /// Called when the element finished the "leave" animation/transition + // Called when the element finished the "leave" animation/transition }; }) ``` @@ -212,7 +232,7 @@ Other events can be implemented, like `onBeforeEnter` or `onBeforeLeave`. - 👍 Easy styling of components based on their state. - 👍 Offers the flexibility to declare both transitions and animations. - 👍 Can be applied to multiple elements simultaneously. -- 👍 Animations/Transitions can be easily overridden on the application side. +- 👍 Users can easily override Animations/Transitions just by changing CSS. #### Cons From 8ca8c1993698300251e7d7d250c2501d6e191b43 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Thu, 22 Jun 2023 18:54:20 +0200 Subject: [PATCH 07/17] fix: add mention about animations --- .../convergence/component-transitions-on-mount-or-unmount.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index cf0d20940ffc79..21da306eba1422 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -129,7 +129,7 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { }; ``` -Overriding the transition on the Application side: +Overriding the transition/animation on the Application side: ```tsx import * as React from 'react'; From 97216b62bd60a51139a3aff0599d053597645254 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Mon, 31 Jul 2023 16:55:00 +0200 Subject: [PATCH 08/17] fix: update doc to reflect latest changes to implementation --- ...mponent-transitions-on-mount-or-unmount.md | 63 +++++++------------ 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 21da306eba1422..f7fadbc5bca13d 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -37,7 +37,7 @@ import { getNativeElementProps, useMotionPresence, useMergedRefs } from '@fluent import type { SampleProps, SampleState } from './Sample.types'; export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref): SampleState => { - const { ref: componentRef, shouldRender, visible, entering, exiting, animating } = useMotionPresence(open); + const { ref: componentRef, shouldRender, visible, motionState } = useMotionPresence(open); return { components: { @@ -50,9 +50,7 @@ export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref shouldRender, visible, - entering, - exiting, - animating, + motionState, }; }; ``` @@ -120,8 +118,8 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { state.root.className = mergeClasses( SampleClassNames.root, state.visible && styles.visible, - state.entering && styles.entering, - state.exiting && styles.exiting, + state.motionState === 'entering' && styles.entering, + state.motionState === 'exiting' && styles.exiting, styles.root, ); @@ -155,13 +153,13 @@ The hook accepts a boolean value that represents the "presence" state. It intern ```ts const { - /* + /** * Ref used to track the target element that requires animation. It should * be passed to the element responsible for performing the animation. */ ref, - /* + /** * Determines whether the component should be displayed on the screen. * * This property will evaluate to `true` when the provided presence value is true. @@ -170,7 +168,7 @@ const { */ shouldRender, - /* + /** * Indicates whether the component is currently rendered and visible. * * This flag will be set to `true` one frame after the shouldRender value becomes true. @@ -178,48 +176,33 @@ const { */ visible, - /* - * Indicates whether the component is currently entering the screen. + /** + * Current state of the tracked element * - * This will be `true` when the element is entering the screen. This happens - * one frame after `visible` value becomes true. - * It becomes `false` when the transition/animation ends. + * Can return the following values: + * - `entering` - The element is entering the DOM. + * - `exiting` - The element is exiting the DOM. + * - `resting` - The element is currently not animating. This is the final and initial state of the element. + * - `unmounted` - The element is not rendered in the DOM. */ - entering, - - /* - * Whether the component is leaving the screen - * - * This property is set to `true` one frame after the `visible` prop becomes true, - * indicating that the element is in the process of entering the screen. - * It becomes `false` when the transition/animation ends. - * It is set to false once the transition or animation for the component has completed. - */ - exiting, - - /* - * Indicates whether the component is currently undergoing animation. - * This property will be set to `true` when the component is either entering or exiting. - */ - animating, + motionState, } = useMotionPresence(open); ``` -Events can be provided as a second argument of the hook.: +Options can be provided as a second argument of the hook.: ```js const presence = useMotionPresence(open, { - onEntered: () => { - // Called when the element finished the "enter" animation/transition - }; - onExited: () => { - // Called when the element finished the "leave" animation/transition - }; + /** + * Whether to animate the element on first mount. Useful when the element + * is visible on first render, but still have the options to be toggled off + * + * @default false + */ + animateOnFirstMount: false; }) ``` -Other events can be implemented, like `onBeforeEnter` or `onBeforeLeave`. - #### Background research - Vue.js [Transition component](https://vuejs.org/guide/built-ins/transition.html#javascript-hooks). The [implementation](https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/components/Transition.ts) CSS transitions and animations and provides classes to the components based on the detected transitions. From d809b7b673085b3dbcae519985fd27bd3f70bb2b Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Mon, 31 Jul 2023 23:25:07 +0200 Subject: [PATCH 09/17] docs: add example of CSS Animations --- ...mponent-transitions-on-mount-or-unmount.md | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index f7fadbc5bca13d..6fd1a360502d7d 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -76,7 +76,7 @@ export const renderSample_unstable = (state: SampleState) => { }; ``` -Styles: +Styles, in case of CSS transitions: ```ts import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; @@ -127,6 +127,71 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { }; ``` +Styles, in case of CSS animations: + +```ts +import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; +import type { SampleSlots, SampleState } from './Sample.types'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { tokens } from '@fluentui/react-theme'; + +export const SampleClassNames: SlotClassNames = { + root: 'fui-Sample', +}; + +/** + * Styles for the root slot + */ +const visibleKeyframe = { + ...shorthands.borderRadius(0), + opacity: 1, + transform: 'translate3D(0, 0, 0) scale(1)', +}; + +const hiddenKeyframe = { + ...shorthands.borderRadius('36px'), + opacity: 0, + transform: 'translate3D(-100%, 0, 0) scale(0.9)', +}; + +const useStyles = makeStyles({ + root: { + willChange: 'opacity, transform, border-radius', + }, + + entering: { + animationDuration: '500ms', + animationTimingFunction: tokens.curveDecelerateMid, + animationName: { + '0%': hiddenKeyframe, + '100%': visibleKeyframe, + }, + }, + + exiting: { + animationDuration: '750ms', + animationTimingFunction: tokens.curveAccelerateMin, + animationName: { + '0%': visibleKeyframe, + '100%': hiddenKeyframe, + }, + }, +}); + +export const useSampleStyles_unstable = (state: SampleState): SampleState => { + const styles = useStyles(); + + state.root.className = mergeClasses( + SampleClassNames.root, + state.motionState === 'entering' && styles.entering, + state.motionState === 'exiting' && styles.exiting, + styles.root, + ); + + return state; +}; +``` + Overriding the transition/animation on the Application side: ```tsx @@ -137,6 +202,7 @@ import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ customDuration: { transitionDuration: '500ms', + animationDuration: '750ms', }, }); @@ -195,7 +261,7 @@ Options can be provided as a second argument of the hook.: const presence = useMotionPresence(open, { /** * Whether to animate the element on first mount. Useful when the element - * is visible on first render, but still have the options to be toggled off + * is visible on first render, but still can be to be toggled off * * @default false */ From 9b17a25b30942ca180fc6ff49a40efc53b1955dd Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Tue, 1 Aug 2023 14:26:04 +0200 Subject: [PATCH 10/17] fix: improve descriptions --- .../component-transitions-on-mount-or-unmount.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 6fd1a360502d7d..12fe5c39196ee1 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -6,7 +6,7 @@ ## Summary -This RFC outlines the implementation details for effectively tracking and applying CSS animations/transitions within Fluent UI components, with a particular focus on the mount/unmount state. +This RFC outlines the implementation details for effectively tracking CSS animations/transitions within Fluent UI components, with a particular focus on the mount/unmount state. ## Background @@ -245,10 +245,10 @@ const { /** * Current state of the tracked element * - * Can return the following values: + * Can return one of the following states: * - `entering` - The element is entering the DOM. * - `exiting` - The element is exiting the DOM. - * - `resting` - The element is currently not animating. This is the final and initial state of the element. + * - `resting` - The element is currently not animating, but rendered on screen. * - `unmounted` - The element is not rendered in the DOM. */ motionState, From 389d72de6f6854c6100e88729f86a3d875146459 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Mon, 7 Aug 2023 18:27:44 +0200 Subject: [PATCH 11/17] feat: simplify API by removing redundant prop --- ...mponent-transitions-on-mount-or-unmount.md | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 12fe5c39196ee1..8e078d20d13ecc 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -37,7 +37,7 @@ import { getNativeElementProps, useMotionPresence, useMergedRefs } from '@fluent import type { SampleProps, SampleState } from './Sample.types'; export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref): SampleState => { - const { ref: componentRef, shouldRender, visible, motionState } = useMotionPresence(open); + const { ref: componentRef, active, motionState } = useMotionPresence(open); return { components: { @@ -48,8 +48,7 @@ export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref ref: useMergedRefs(ref, componentRef), }), - shouldRender, - visible, + active, motionState, }; }; @@ -68,7 +67,7 @@ import type { SampleState, SampleSlots } from './Sample.types'; export const renderSample_unstable = (state: SampleState) => { const { slots, slotProps } = getSlots(state); - if (!state.shouldRender) { + if (state.motionState === 'unmounted') { return null; } @@ -117,7 +116,7 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { state.root.className = mergeClasses( SampleClassNames.root, - state.visible && styles.visible, + state.active && styles.visible, state.motionState === 'entering' && styles.entering, state.motionState === 'exiting' && styles.exiting, styles.root, @@ -225,22 +224,14 @@ const { */ ref, - /** - * Determines whether the component should be displayed on the screen. - * - * This property will evaluate to `true` when the provided presence value is true. - * Once the provided value changes to false, the hook will monitor the transition/animation - * completion and subsequently set this property to false. - */ - shouldRender, - /** * Indicates whether the component is currently rendered and visible. * - * This flag will be set to `true` one frame after the shouldRender value becomes true. + * This flag will be set to `true` one frame after the motionState changes from 'unmounted' to 'entering' or 'resting' * It will be set to `false` when the specified presence value changes to false. + * Useful to apply CSS transitions only when the element is active. */ - visible, + active, /** * Current state of the tracked element @@ -260,8 +251,8 @@ Options can be provided as a second argument of the hook.: ```js const presence = useMotionPresence(open, { /** - * Whether to animate the element on first mount. Useful when the element - * is visible on first render, but still can be to be toggled off + * Whether to animate the element on first mount. Useful when the animation/transition + * should be played if the element is already rendered on screen. * * @default false */ From c700cdf30e3edb20ef7a5dd338dca4996340a3c0 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Thu, 17 Aug 2023 19:18:35 +0200 Subject: [PATCH 12/17] docs: upgrade description for new spec --- ...mponent-transitions-on-mount-or-unmount.md | 212 +++++++++++++----- 1 file changed, 155 insertions(+), 57 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 8e078d20d13ecc..8b311470330278 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -14,47 +14,117 @@ Currently, there is a limitation in incorporating motion effects to components d ## Problem statement -In order to display that a content is showing or hiding from screen, CSS transitions or animations should be applied. React lacks built-in support for applying CSS animations or transitions specifically during the mount and unmount phases of a component's lifecycle and we need to develop a solution that can determine the on-screen status of a component. Even though there are existing packages available that could solve this issue, they either would increase bundle size (e.g., [Framer Motion](https://www.framer.com/motion/) and [React Spring](https://www.react-spring.dev/)) or lacking flexibility and better integration with Griffel ([React Transition Group](https://reactcommunity.org/react-transition-group/)). +In order to display that a content is showing or hiding from screen, CSS transitions or animations should be applied. React lacks built-in support for applying CSS animations or transitions specifically during the mount and unmount phases of a component's lifecycle and we need to develop a solution that can determine the on-screen status of a component. Even though there are existing packages available that could solve this issue, they either would increase bundle size (e.g., [Framer Motion](https://www.framer.com/motion/) and [React Spring](https://www.react-spring.dev/)) or lacking flexibility and better integration with how we create styles ([React Transition Group](https://reactcommunity.org/react-transition-group/)). ## Detailed Design or Proposal -### A `useMotionPresence` hook based solution +### A `useMotion` hook based solution -To determine the motion state of a component, a hook could be created as part of the @fluentui/react-utilities package. A preliminary implementation of this hook can be found here: [useMotionPresence](https://github.com/marcosmoura/fluentui/blob/feat/drawer-motion/packages/react-components/react-utilities/src/hooks/useMotionPresence.ts). +To determine the motion state of a component, a hook could be created as part of the @fluentui/react-utilities package. A preliminary implementation of this hook can be found here: [useMotion](https://github.com/marcosmoura/fluentui/blob/feat/use-motion-presence-hook/packages/react-components/react-motion-preview/src/hooks/useMotion.ts). #### What is it? -A tracker hook, that monitors the state of animations and transitions for a particular element. A hook to track the state of animations/transitions. This hook does not directly create animations but instead synchronizes with CSS properties to determine the rendering status, visibility, entering, leaving, and ongoing animation of a component. If any CSS changes or properties are overridden, this hook will automatically adjust and stay synchronized. +A tracker hook, that monitors the state of animations and transitions for a particular element. This hook does not directly create animations but instead synchronizes with CSS properties to determine the rendering status, visibility, entering, leaving, and ongoing animation of a component. If any CSS changes or properties are overridden, this hook will automatically adjust and stay synchronized. + +#### API + +The hook accepts a `MotionProps` param and a `MotionOptions`: + +```tsx +// Types +type MotionProps = { + /** + * Whether the element should be present in the DOM. + */ + presence: boolean; + + /** + * Ref used to track the target element that requires animation. It should + * be passed to the element responsible for performing the animation. + */ + ref; + + /** + * Indicates whether the component is currently rendered and visible. + * + * This flag will be set to `true` one frame after the motionState changes from 'unmounted' to 'entering' or 'resting' + * It will be set to `false` when the specified presence value changes to false. + * Useful to apply CSS transitions only when the element is active. + */ + active; + + /** + * Current state of the element. + * + * - `unmounted` - The element is not yet rendered or can be safely removed from the DOM. + * - `entering` - The element is performing enter animation. + * - `entered` - The element has finished enter animation. + * - `idle` - The element is currently not animating, but rendered on screen. + * - `exiting` - The element is performing exit animation. + * - `exited` - The element has finished exit animation. + */ + state: 'unmounted' | 'entering' | 'entered' | 'idle' | 'exiting' | 'exited'; +}; + +type MotionOptions = { + /** + * Whether to animate the element on first mount. Useful when the animation/transition + * should be played if the element is already rendered on screen. + * + * @default false + */ + animateOnFirstMount: false; +}; + +// Usage +const props = { + presence: true, +}; +const options = { + animateOnFirstMount: false, +}; +const { ref, presence, active, state } = useMotion(props, options); +``` + +The hook also outputs the same `MotionProps`. This is for cases when the motion props don't need to be recalculated or are passed by another component. See the **Usage** section on this document. #### Usage -State: +##### State: ```ts import * as React from 'react'; -import { getNativeElementProps, useMotionPresence, useMergedRefs } from '@fluentui/react-utilities'; +import { getNativeElementProps, slot } from '@fluentui/react-utilities'; +import { useMotion } from '@fluentui/react-motion-preview'; import type { SampleProps, SampleState } from './Sample.types'; export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref): SampleState => { - const { ref: componentRef, active, motionState } = useMotionPresence(open); + const motion = useMotion({ + presence: open, + ref, + }); return { components: { root: 'div', }, - root: getNativeElementProps('div', { - ref: useMergedRefs(ref, componentRef), - }), + root: slot.always( + getNativeElementProps('div', { + ref: motion.ref, + ...props, + }), + { elementType: 'div' }, + ), - active, - motionState, + active: motion.active, + motionState: motion.state, }; }; ``` -Renderization: +##### Renderization: ```tsx import * as React from 'react'; @@ -75,7 +145,7 @@ export const renderSample_unstable = (state: SampleState) => { }; ``` -Styles, in case of CSS transitions: +##### Styles: ```ts import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; @@ -126,7 +196,7 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { }; ``` -Styles, in case of CSS animations: +##### Styles, in case of CSS animations: ```ts import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; @@ -191,7 +261,9 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { }; ``` -Overriding the transition/animation on the Application side: +#### Overriding the transition/animation + +To override motion on another component, only a CSS change is needed: ```tsx import * as React from 'react'; @@ -201,7 +273,6 @@ import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ customDuration: { transitionDuration: '500ms', - animationDuration: '750ms', }, }); @@ -212,52 +283,79 @@ export const CustomDuration = () => { }; ``` -#### How it works? +Fluent components can also accept a `motion` prop, that can be used to receive `useMotion` values called from another component. This is very useful for cases when a completely custom animation/transition is needed. In this case, we enable full control to override animations/transitions while improving performance by not computing the values twice: -The hook accepts a boolean value that represents the "presence" state. It internally tracks the animation/transition and return boolean values related to its motion state and a ref that needs to be assigned to the element we are tracking. +##### Application side: -```ts -const { - /** - * Ref used to track the target element that requires animation. It should - * be passed to the element responsible for performing the animation. - */ - ref, +```tsx +import * as React from 'react'; +import { makeStyles, mergeClasses, Drawer, Button } from '@fluentui/react-components'; +import { useMotion } from '@fluentui/react-motion-preview'; - /** - * Indicates whether the component is currently rendered and visible. - * - * This flag will be set to `true` one frame after the motionState changes from 'unmounted' to 'entering' or 'resting' - * It will be set to `false` when the specified presence value changes to false. - * Useful to apply CSS transitions only when the element is active. - */ - active, +const useStyles = makeStyles({ + drawer: { + opacity: 0, + transitionDuration: '3s', + transitionProperty: 'opacity', + }, - /** - * Current state of the tracked element - * - * Can return one of the following states: - * - `entering` - The element is entering the DOM. - * - `exiting` - The element is exiting the DOM. - * - `resting` - The element is currently not animating, but rendered on screen. - * - `unmounted` - The element is not rendered in the DOM. - */ - motionState, -} = useMotionPresence(open); + drawerVisible: { + opacity: 1, + }, +}); + +export const CustomAnimation = () => { + const styles = useStyles(); + + const [open, setOpen] = React.useState(false); + const motion = useMotion({ + presence: open, + }); + + const onClick = () => setOpen(!open); + + return ( +
+ + ; +
+ ); +}; ``` -Options can be provided as a second argument of the hook.: - -```js - const presence = useMotionPresence(open, { - /** - * Whether to animate the element on first mount. Useful when the animation/transition - * should be played if the element is already rendered on screen. - * - * @default false - */ - animateOnFirstMount: false; - }) +##### Fluent Component: + +```tsx +export const useDrawer_unstable = (props: DrawerInlineProps, ref: React.Ref): DrawerInlineState => { + const { open, motion: motionProp } = props; + + // Call useMotion with given motion values or create a new one + const motion = useMotion( + motionProp || { + presence: open, + ref, + }, + ); + + return { + components: { + root: 'div', + }, + + root: slot.always( + getNativeElementProps('div', { + ref: motion.ref, + ...props, + }), + { elementType: 'div' }, + ), + + active: motion.active, + motionState: motion.state, + }; +}; ``` #### Background research From f93f841f6cf9fff55873e86da7e2c56dd0437e33 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Mon, 21 Aug 2023 14:33:58 +0200 Subject: [PATCH 13/17] docs: useMotion now has a much more simplified API to allow overrides --- ...mponent-transitions-on-mount-or-unmount.md | 99 ++++++++----------- 1 file changed, 43 insertions(+), 56 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 8b311470330278..914e14007b9e30 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -28,30 +28,17 @@ A tracker hook, that monitors the state of animations and transitions for a part #### API -The hook accepts a `MotionProps` param and a `MotionOptions`: +The hook accepts a `MotionShorthand` param and a `MotionOptions`: ```tsx // Types -type MotionProps = { - /** - * Whether the element should be present in the DOM. - */ - presence: boolean; +export type MotionType = 'unmounted' | 'entering' | 'entered' | 'idle' | 'exiting' | 'exited'; +export type MotionState = { /** - * Ref used to track the target element that requires animation. It should - * be passed to the element responsible for performing the animation. + * Ref to the element. */ - ref; - - /** - * Indicates whether the component is currently rendered and visible. - * - * This flag will be set to `true` one frame after the motionState changes from 'unmounted' to 'entering' or 'resting' - * It will be set to `false` when the specified presence value changes to false. - * Useful to apply CSS transitions only when the element is active. - */ - active; + ref: React.Ref; /** * Current state of the element. @@ -63,9 +50,25 @@ type MotionProps = { * - `exiting` - The element is performing exit animation. * - `exited` - The element has finished exit animation. */ - state: 'unmounted' | 'entering' | 'entered' | 'idle' | 'exiting' | 'exited'; + type: MotionType; + + /** + * Indicates whether the component is currently rendered and visible. + * Useful to apply CSS transitions only when the element is active. + */ + isActive(): boolean; + + /** + * Indicates whether the component can be rendered. + * This can be used to avoid rendering the component when it is not visible anymore. + */ + canRender(): boolean; }; +export type MotionShorthandValue = boolean; + +export type MotionShorthand = MotionShorthandValue | MotionState; + type MotionOptions = { /** * Whether to animate the element on first mount. Useful when the animation/transition @@ -77,16 +80,14 @@ type MotionOptions = { }; // Usage -const props = { - presence: true, -}; +const [open, setOpen] = React.useState(false); const options = { animateOnFirstMount: false, }; -const { ref, presence, active, state } = useMotion(props, options); +const { ref, type, isActive, canRender } = useMotion(open, options); ``` -The hook also outputs the same `MotionProps`. This is for cases when the motion props don't need to be recalculated or are passed by another component. See the **Usage** section on this document. +The hook always returns a `MotionState`. The received `MotionShorthand` parameter can be either a `boolean` or a `MotionState`. This flexibility is extremely useful for cases when an override on another component is needed, and with that a double calculation is avoided. See the **Usage** section on this document. #### Usage @@ -100,10 +101,7 @@ import { useMotion } from '@fluentui/react-motion-preview'; import type { SampleProps, SampleState } from './Sample.types'; export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref): SampleState => { - const motion = useMotion({ - presence: open, - ref, - }); + const motion = useMotion(open); return { components: { @@ -112,14 +110,13 @@ export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref root: slot.always( getNativeElementProps('div', { - ref: motion.ref, + ref: useMergedRefs(ref, motion.ref), ...props, }), { elementType: 'div' }, ), - active: motion.active, - motionState: motion.state, + motion, }; }; ``` @@ -137,7 +134,7 @@ import type { SampleState, SampleSlots } from './Sample.types'; export const renderSample_unstable = (state: SampleState) => { const { slots, slotProps } = getSlots(state); - if (state.motionState === 'unmounted') { + if (state.motion.canRender()) { return null; } @@ -186,9 +183,9 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { state.root.className = mergeClasses( SampleClassNames.root, - state.active && styles.visible, - state.motionState === 'entering' && styles.entering, - state.motionState === 'exiting' && styles.exiting, + state.motion.isActive() && styles.visible, + state.motion.type === 'entering' && styles.entering, + state.motion.type === 'exiting' && styles.exiting, styles.root, ); @@ -252,8 +249,8 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { state.root.className = mergeClasses( SampleClassNames.root, - state.motionState === 'entering' && styles.entering, - state.motionState === 'exiting' && styles.exiting, + state.motion.type === 'entering' && styles.entering, + state.motion.type === 'exiting' && styles.exiting, styles.root, ); @@ -283,7 +280,7 @@ export const CustomDuration = () => { }; ``` -Fluent components can also accept a `motion` prop, that can be used to receive `useMotion` values called from another component. This is very useful for cases when a completely custom animation/transition is needed. In this case, we enable full control to override animations/transitions while improving performance by not computing the values twice: +In order to enable overrides, Fluent components can also accept a prop that can be used to receive `useMotion` values coming from another component. This is extremely useful for cases when a completely custom animation/transition is needed. In this case, we enable full control to override animations/transitions while improving performance by not computing the values twice. In the following example, the `open` prop for the Drawer can receive either a `boolean` or a `MotionState`: ##### Application side: @@ -308,9 +305,7 @@ export const CustomAnimation = () => { const styles = useStyles(); const [open, setOpen] = React.useState(false); - const motion = useMotion({ - presence: open, - }); + const motion = useMotion(open); const onClick = () => setOpen(!open); @@ -319,7 +314,7 @@ export const CustomAnimation = () => { - ; + ; ); }; @@ -328,16 +323,9 @@ export const CustomAnimation = () => { ##### Fluent Component: ```tsx -export const useDrawer_unstable = (props: DrawerInlineProps, ref: React.Ref): DrawerInlineState => { - const { open, motion: motionProp } = props; - - // Call useMotion with given motion values or create a new one - const motion = useMotion( - motionProp || { - presence: open, - ref, - }, - ); +export const useDrawer_unstable = ({ open }: DrawerInlineProps, ref: React.Ref): DrawerInlineState => { + // Call useMotion with given motion values + const motion = useMotion(open); return { components: { @@ -346,14 +334,13 @@ export const useDrawer_unstable = (props: DrawerInlineProps, ref: React.Ref Date: Tue, 22 Aug 2023 17:27:33 +0200 Subject: [PATCH 14/17] Update rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md Co-authored-by: Bernardo Sunderhus --- .../convergence/component-transitions-on-mount-or-unmount.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 914e14007b9e30..2d884e51c074be 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -125,7 +125,7 @@ export const useSample_unstable = ({ open = false }: SampleProps, ref: React.Ref ```tsx import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; +import { assertSlots } from '@fluentui/react-utilities'; import type { SampleState, SampleSlots } from './Sample.types'; /** From 6c418a1d374b1284023dc192e9616b210e51c43e Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Tue, 22 Aug 2023 17:28:24 +0200 Subject: [PATCH 15/17] Update rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md Co-authored-by: Bernardo Sunderhus --- .../component-transitions-on-mount-or-unmount.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 2d884e51c074be..50ed92df787fe3 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -132,13 +132,14 @@ import type { SampleState, SampleSlots } from './Sample.types'; * Render the final JSX of Sample */ export const renderSample_unstable = (state: SampleState) => { - const { slots, slotProps } = getSlots(state); if (state.motion.canRender()) { return null; } - - return ; + + assertSlots(state); + + return ; }; ``` From 9e329874c91bd2a3d82320bb78f733cf7c73da6d Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 23 Aug 2023 18:10:25 +0200 Subject: [PATCH 16/17] Update component-transitions-on-mount-or-unmount.md --- .../component-transitions-on-mount-or-unmount.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index 50ed92df787fe3..f4f6c95bcd2fda 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -56,13 +56,13 @@ export type MotionState = { * Indicates whether the component is currently rendered and visible. * Useful to apply CSS transitions only when the element is active. */ - isActive(): boolean; + active: boolean; /** * Indicates whether the component can be rendered. * This can be used to avoid rendering the component when it is not visible anymore. */ - canRender(): boolean; + canRender: boolean; }; export type MotionShorthandValue = boolean; @@ -84,7 +84,7 @@ const [open, setOpen] = React.useState(false); const options = { animateOnFirstMount: false, }; -const { ref, type, isActive, canRender } = useMotion(open, options); +const { ref, type, active, canRender } = useMotion(open, options); ``` The hook always returns a `MotionState`. The received `MotionShorthand` parameter can be either a `boolean` or a `MotionState`. This flexibility is extremely useful for cases when an override on another component is needed, and with that a double calculation is avoided. See the **Usage** section on this document. @@ -133,7 +133,7 @@ import type { SampleState, SampleSlots } from './Sample.types'; */ export const renderSample_unstable = (state: SampleState) => { - if (state.motion.canRender()) { + if (state.motion.canRender) { return null; } @@ -184,7 +184,7 @@ export const useSampleStyles_unstable = (state: SampleState): SampleState => { state.root.className = mergeClasses( SampleClassNames.root, - state.motion.isActive() && styles.visible, + state.motion.active && styles.visible, state.motion.type === 'entering' && styles.entering, state.motion.type === 'exiting' && styles.exiting, styles.root, @@ -315,7 +315,7 @@ export const CustomAnimation = () => { - ; + ; ); }; From 17f04063d4360843bf44dde93ccb7abc34826053 Mon Sep 17 00:00:00 2001 From: Marcos Moura Date: Wed, 23 Aug 2023 21:02:06 +0200 Subject: [PATCH 17/17] fix: lint issues with code examples --- .../convergence/component-transitions-on-mount-or-unmount.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md index f4f6c95bcd2fda..db6505fa33f559 100644 --- a/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md +++ b/rfcs/react-components/convergence/component-transitions-on-mount-or-unmount.md @@ -132,13 +132,12 @@ import type { SampleState, SampleSlots } from './Sample.types'; * Render the final JSX of Sample */ export const renderSample_unstable = (state: SampleState) => { - if (state.motion.canRender) { return null; } - + assertSlots(state); - + return ; }; ```