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

feat(motion): add extended support for reduced motion #33353

Merged
merged 2 commits into from
Jan 14, 2025

Conversation

layershifter
Copy link
Member

@layershifter layershifter commented Nov 27, 2024

Previous Behavior

No way to customize motions' behavior when reduced motion is enabled.

New Behavior

  • Adds reducedMotion option to AtomMotion type.
  • Tests added
  • Story added

By default, when reduced motion is enabled the duration of the animation is set to 1ms (current behavior).

reducedMotion allows to customize a reduced motion version of the animation:

const Motion = createPresenceComponent({
  enter: {
    keyframes: [
      { opacity: 0, transform: 'scale(0)' },
      { opacity: 1, transform: 'scale(1)' },
    ],
    duration: 300,

    /** 💡reduced motion will not have scale animation with a different duration */
    reducedMotion: {
      keyframes: [{ opacity: 0 }, { opacity: 1 }],
      duration: 1000,
    },
  },
  exit: {
    /* ... */
  },
});

💡Note, if keyframes are provided, they will be used instead of the regular keyframes.

This will also works with arrays of atoms as reducedMotion definitions are collocated to its atoms:

const Motion = createPresenceComponent({
  enter: [
    /** Atom A
        Has a definition for "reducedMotion" */
    {
      keyframes: [
        /* ... */
      ],
      reducedMotion: {
        /* some options or keyframes*/
      },
    },
    /** Atom B
        This atom _may_ declare "reducedMotion" (it's optional) */
    {
      keyframes: [
        /* ... */
      ],
    },
  ],
  exit: [
    /* ... */
  ],
});

Rejected options

Second argument to createMotionComponent() & createPresenceComponent()

Note: examples are based on createPresenceComponent() as its syntax is more verbose and highlights challenges better.

We have already an array syntax for multiple atoms (see createPresenceComponent() and arrays):

// 💡 We already have this API implemented
createPresenceComponent({
  enter: [
    { keyframes: [] /* Atom A: enter */},
    { keyframes: [] /* Atom B: enter */},
  ],
  exit: [/* ... */],
});

The proposal is two have a second argument:

// ⚠️ Proposed API (rejected option)
createPresenceComponent([
  {
    enter: [
      { keyframes: [] /* Atom A: enter */ },
      { keyframes: [] /* Atom B: enter */ },
    ],
    exit: [/* ... */],
  },
  {
    enter: [
      { keyframes: [] /* Atom A: enter & reduced motion */ },
      { keyframes: [] /* Atom B: enter & reduced motion */ },
    ],
    exit: [/* ... */],
  },
]);

However, it will be hard to distinguish between atoms and reduced motion declarations - you have to know the order of arguments in the functions to understand it.

This implementation also brings a challenge of collocation: reduced options should be optional, however we will need to maintain the order to make it work. The example below shows the requirement:

// ⚠️ Proposed API (rejected option)
createPresenceComponent([
  {
    enter: [
      { keyframes: [] /* Atom A: enter */ },
      { keyframes: [] /* Atom B: enter */ },
    ],
    exit: [/* ... */],
  },
  {
    enter: [
      { keyframes: [] /* Atom B: enter & reduced motion */ },
      // 💥 💥 💥 
      // There are two atoms in the motion declaration, however only one in reduced options
      // The order will break and options will be wrongly applied
    ],
    exit: [/* ... */],
  },
]);

Consumers will be forced to pass null (or something else?) in this case:

// ⚠️ Proposed API (rejected option)
createPresenceComponent([
  {
    enter: [
      { keyframes: [] /* Atom A: enter */ },
      { keyframes: [] /* Atom B: enter */ },
    ],
    exit: [
      { keyframes: [] /* Atom A: exit */ },
      { keyframes: [] /* Atom B: enter */ },
    ],
  },
  {
    enter: [
      null, /* Atom A: enter & reduced motion */,
      { keyframes: [] /* Atom B: enter & reduced motion */ },
    ],
    exit: [/* ... */],
  },
]);

Nest atoms

That's very similar to previous option, but instead we will have syntax similar to fallback values in Griffel.

// ⚠️ Proposed API (rejected option)
createPresenceComponent([
  {
    enter: [
      [
        { keyframes: [], duration: 1000 /* Atom A: enter */ },
        { keyframes: [], duration: 300 /* Atom A: enter, reduced options */ },
        // 👆 reduced options are collocated there
      ],
      [{ keyframes: [] /* Atom B: enter */ }],
    ],
    exit: [/* ... */],
  },
]);

In this case we collocate reduced options with the atom they are related to, so we don't need to maintain the order like in the previous option 👍

However, this option has the same issue with being implicit and hard to understand: which array stands for atoms and which for reduced options?

Related issues

Fixes #33358.

Copy link

github-actions bot commented Nov 27, 2024

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-accordion
Accordion (including children components)
107.106 kB
32.792 kB
107.176 kB
32.824 kB
70 B
32 B
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
222.729 kB
64.426 kB
222.799 kB
64.464 kB
70 B
38 B
react-components
react-components: entire library
1.164 MB
291.241 kB
1.164 MB
291.304 kB
70 B
63 B
react-dialog
Dialog (including children components)
100.443 kB
30.105 kB
100.513 kB
30.131 kB
70 B
26 B
react-motion
@fluentui/react-motion - createMotionComponent()
4.434 kB
1.935 kB
4.506 kB
1.967 kB
72 B
32 B
react-motion
@fluentui/react-motion - createPresenceComponent()
5.165 kB
2.263 kB
5.231 kB
2.296 kB
66 B
33 B
react-toast
Toast (including Toaster)
101.397 kB
30.485 kB
101.467 kB
30.514 kB
70 B
29 B
react-tree
FlatTree
145.488 kB
41.779 kB
145.568 kB
41.82 kB
80 B
41 B
react-tree
PersonaFlatTree
146.176 kB
41.89 kB
146.256 kB
41.924 kB
80 B
34 B
react-tree
PersonaTree
142.407 kB
40.711 kB
142.477 kB
40.753 kB
70 B
42 B
react-tree
Tree
141.719 kB
40.611 kB
141.789 kB
40.648 kB
70 B
37 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-components
react-components: Button, FluentProvider & webLightTheme
69.236 kB
20.182 kB
react-components
react-components: FluentProvider & webLightTheme
44.473 kB
14.597 kB
react-message-bar
MessageBar (all components)
24.851 kB
9.276 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
108.551 kB
36.094 kB
🤖 This report was generated against 9ed5fce8dda03bb85d583a23478ed298e51d305d

Copy link

Pull request demo site: URL

@layershifter layershifter force-pushed the fix/create-motion-reduced branch 3 times, most recently from 2f16fd0 to 07ad3b3 Compare November 27, 2024 16:52
@layershifter layershifter changed the title fix(motion): handle reduced motion in createMotionComponent() feat(motion): add extended support for reduced motion Nov 27, 2024
@layershifter layershifter force-pushed the fix/create-motion-reduced branch from 07ad3b3 to 7649e34 Compare November 27, 2024 16:54
@layershifter layershifter marked this pull request as ready for review November 27, 2024 17:03
@layershifter layershifter requested a review from a team as a code owner November 27, 2024 17:03
@robertpenner
Copy link
Collaborator

The feature motivation is solid; the feature design needs to be fleshed out a bit more like an RFC (if it's not going to be an actual RFC). For example:

  • The alternative reducedMotion implementation conceivably could be placed in a number of different spots. How do we know this spot is the best design? Comparing alternatives would be instructive.
  • How might reducedMotion look with other cases, e.g. when enter is an array of atoms?

@layershifter
Copy link
Member Author

the feature design needs to be fleshed out a bit more like an RFC (if it's not going to be an actual RFC).

FYI, it's was mentioned in the original RFC as a proposal: https://github.com/microsoft/fluentui/blob/master/docs/react-v9/contributing/rfcs/react-components/convergence/motion-definition-n-apis.md#advanced-reduced-motion-support


  • The alternative reducedMotion implementation conceivably could be placed in a number of different spots. How do we know this spot is the best design? Comparing alternatives would be instructive.

I updated the PR description to list other two options, is there anything that comes to your mind? Did I miss something?

  • How might reducedMotion look with other cases, e.g. when enter is an array of atoms?

As the atom definition changes, it will work transparently. I added an example to the PR description.

@robertpenner
Copy link
Collaborator

  • The alternative reducedMotion implementation conceivably could be placed in a number of different spots. How do we know this spot is the best design? Comparing alternatives would be instructive.

I updated the PR description to list other two options, is there anything that comes to your mind? Did I miss something?

@layershifter I think you've covered it well.

  • How might reducedMotion look with other cases, e.g. when enter is an array of atoms?

As the atom definition changes, it will work transparently. I added an example to the PR description.

Excellent additions, thanks.

@layershifter layershifter force-pushed the fix/create-motion-reduced branch from 7128361 to 4b6d027 Compare January 14, 2025 15:28
@github-actions github-actions bot removed this from the December Project Cycle Q4 2024 milestone Jan 14, 2025
@layershifter layershifter force-pushed the fix/create-motion-reduced branch 2 times, most recently from e86b2df to 1d7e3a8 Compare January 14, 2025 15:29
@layershifter layershifter force-pushed the fix/create-motion-reduced branch from 1d7e3a8 to 72b8029 Compare January 14, 2025 16:16
@layershifter layershifter merged commit ab6a302 into microsoft:master Jan 14, 2025
16 checks passed
@layershifter layershifter deleted the fix/create-motion-reduced branch January 14, 2025 16:38
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.

[Bug]: createMotionComponent() does not respect motion in settings of OS
5 participants