Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(motion): simplify variant creation, starting with Collapse #32939

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

robertpenner
Copy link
Collaborator

@robertpenner robertpenner commented Sep 27, 2024

Related Issue(s)

Motivation

The current mechanism for creating variants of motion components, createPresenceComponentVariant, has several shortcomings:

  1. It doesn't support motion groups, where enter and exit are arrays of motion atoms.
    a. It's also not clear what the solution would look like, to target an array item in the existed override object.
  2. The override object puts variant params in a nested object structure:
    a. duration and easing are nested inside enter: { duration: ... }, exit: { duration: ... }, and optionally all: { duration: ...} as a shortcut.
    b. This now looks less maintainable and more complicated to teach, compared to a flat structure, where all properties are on the same level, e.g. { enterEasing: ..., enterDuration: ..., exitEasing: ..., exitDuration: ... }.
  3. Knowing which variant properties can be overridden on a motion component is not that straightforward to discover. One has to retrieve an object from the motion function and decide which ones to poke at. It doesn't have good encapsulation.
  4. The createPresenceComponentVariant(Fade, overrides) mechanism relies on the motion component Fade to store a reference to the motion molecule from which it was created.
    a. This reference is accessed through a special Symbol lookup (MOTION_DEFINITION), which is set by createPresenceComponent.
    b. In hindsight, this feels a bit clunky and magical, if there can be a simpler way.

Solution

There is a simpler way to create variants using a more functional approach:

  1. Now there is an outer factory function createCollapsePresence that accepts variant parameters (e.g. duration, easing), which are locked into the variant.
  2. createCollapsePresence returns a motion function which accepts runtime parameters (e.g. animateOpacity).
  3. The result of createCollapsePresence can be passed to createPresenceComponent directly, eliminating the need for createPresenceComponentVariant:
export const Collapse = createPresenceComponent(createCollapsePresence());

export const CollapseSnappy = createPresenceComponent(
  createCollapsePresence({ enterDuration: durationFast }),
);
  1. We then can delete the createPresenceComponentVariant function and its helper function overridePresenceMotion:
export function overridePresenceMotion<MotionParams extends Record<string, MotionParam> = {}>(
  presenceMotion: PresenceMotionFn<MotionParams>,
  override: PresenceOverride,
): PresenceMotionFn<MotionParams> {
  return (...args: Parameters<PresenceMotionFn<MotionParams>>) => {
    const { enter, exit } = presenceMotion(...args);

    return {
      enter: { ...enter, ...override.all, ...override.enter },
      exit: { ...exit, ...override.all, ...override.exit },
    };
  };
}
  1. We will no longer need to explain the nested override object structure or have logic to validate the levels. All variant params are in a flat object.
  2. Using types, variant parameters and runtime parameters for Collapse are transparently visible and validated:
type CollapseVariantParams = {
  /** Time (ms) for the enter transition (expand). Defaults to the `durationNormal` value (200 ms). */
  enterDuration?: number;

  /** Easing curve for the enter transition (expand). Defaults to the `easeEaseMax` value.  */
  enterEasing?: string;

  /** Time (ms) for the exit transition (collapse). Defaults to the `enterDuration` param for symmetry. */
  exitDuration?: number;

  /** Easing curve for the exit transition (collapse). Defaults to the `enterEasing` param for symmetry.  */
  exitEasing?: string;
};

type CollapseRuntimeParams = {
  /** Whether to animate the opacity. Defaults to `true`. */
  animateOpacity?: boolean;
};
  1. The variant parameters are cleanly encapsulated by the factory function, exposing to the developer what can be overridden, but retaining full control over the resulting motion definition object.

Future Work

  • Migrate the variant definitions for other motion components like Fade.
  • Give Collapse an orientation runtime param and horizontal option to collapse width.

@robertpenner robertpenner changed the title refactor(motion): simplify variant creation w/functional approach, starting with Collapse refactor(motion): simplify variant creation, starting with Collapse Sep 27, 2024
@fabricteam
Copy link
Collaborator

fabricteam commented Sep 27, 2024

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-accordion
Accordion (including children components)
104.529 kB
32.097 kB
104.34 kB
32.078 kB
-189 B
-19 B
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
218.317 kB
63.258 kB
218.128 kB
63.227 kB
-189 B
-31 B
react-components
react-components: entire library
1.1 MB
272.081 kB
1.1 MB
272.111 kB
-5 B
30 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-components
react-components: Button, FluentProvider & webLightTheme
69.14 kB
20.137 kB
react-components
react-components: FluentProvider & webLightTheme
44.447 kB
14.59 kB
react-dialog
Dialog (including children components)
99.724 kB
29.912 kB
react-motion
@fluentui/react-motion - createMotionComponent()
4.303 kB
1.899 kB
react-motion
@fluentui/react-motion - createPresenceComponent()
5.038 kB
2.229 kB
react-motion
@fluentui/react-motion - PresenceGroup
1.714 kB
819 B
react-portal-compat
PortalCompatProvider
8.39 kB
2.64 kB
react-timepicker-compat
TimePicker
107.387 kB
35.758 kB
react-toast
Toast (including Toaster)
97.816 kB
29.438 kB
🤖 This report was generated against 5bea3fa901c841e41438fff4b7ae6b38bf30b982

@fabricteam
Copy link
Collaborator

fabricteam commented Sep 27, 2024

Perf Analysis (@fluentui/react-components)

No significant results to display.

All results

Scenario Render type Master Ticks PR Ticks Iterations Status
Avatar mount 659 651 5000
Button mount 310 293 5000
Field mount 1151 1139 5000
FluentProvider mount 721 737 5000
FluentProviderWithTheme mount 80 89 10
FluentProviderWithTheme virtual-rerender 36 36 10
FluentProviderWithTheme virtual-rerender-with-unmount 89 77 10
MakeStyles mount 883 866 50000
Persona mount 1759 1736 5000
SpinButton mount 1389 1387 5000
SwatchPicker mount 1623 1708 5000

createPresenceComponent,
createPresenceComponentVariant,
} from '@fluentui/react-motion';
import { motionTokens, createPresenceComponent } from '@fluentui/react-motion';
Copy link
Collaborator

@fabricteam fabricteam Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual regressions to review in the fluentuiv9 Visual Regression Report

Avatar Converged 2 screenshots
Image Name Diff(in Pixels) Image Type
Avatar Converged.Badge Mask RTL.chromium.png 1 Changed
Avatar Converged.badgeMask.chromium.png 12 Changed
Drawer 1 screenshots
Image Name Diff(in Pixels) Image Type
Drawer.overlay drawer full.chromium.png 1155 Changed

@robertpenner robertpenner marked this pull request as ready for review October 2, 2024 21:42
@robertpenner robertpenner requested a review from a team as a code owner October 2, 2024 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants