-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat(motion): add Rotate presence motion component #34928
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
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
6cffffe
feat(Rotate): start implementing Rotate motion component
robertpenner c8153b4
feat(Rotate): enhance Rotate component with 3D axis control and add s…
robertpenner 4fc8b7f
feat(Rotate): implement rotateAtom for single-axis rotation and updat…
robertpenner c86c9f9
feat(Rotate): refactor Rotate types
robertpenner 9a707b9
refactor(Rotate): rename enterAngle to angle
robertpenner 1519a42
feat(Rotate): update Rotate patterns with new parameters and easing f…
robertpenner 3f3c487
chore(Rotate): yarn change
robertpenner c305a53
chore(Rotate): update .api.md
robertpenner eaf9301
feat(Rotate): update RotateCommonPatterns to include exit easing and …
robertpenner 97f4152
test(Rotate): add unit tests
robertpenner 74f7dc8
feat(Rotate): rename story
robertpenner 90f3843
feat(Rotate): adjust perspective settings and enhance card wrapper st…
robertpenner 15939c4
docs(Rotate): remove extra shadow from cards
robertpenner 2b4e706
docs(Rotate): adjust card perspective spring easing
robertpenner 6c9998e
feat(Rotate): enhance design token usage and improve accessibility in…
robertpenner 9b7f4fa
docs(Rotate): improve layout of animation controls
robertpenner 37ee76f
feat(Rotate): remove background shadows for improved 3D rotation effect
robertpenner f201884
docs(Rotate): update transition effects and toggle button text for im…
robertpenner 40663de
refactor(Rotate): reorganize type exports and improve import structur…
robertpenner 0b82abd
refactor(Rotate): remove autoplay functionality and associated contro…
robertpenner 6850bab
refactor(Rotate): replace Card with CompoundButton
robertpenner b390132
refactor(Rotate): update slider label styles and introduce slider hea…
robertpenner 29437b7
chore(Rotate): update .api.md
robertpenner 330ab71
refactor(Rotate): remove Axis3D type export and use RotateParams['axi…
robertpenner b09358f
docs(Rotate): update usage of CompoundButton in RotateCardFlip story
robertpenner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@fluentui-react-motion-components-preview-32aac0a2-6998-44d1-a90e-ea35ad4f9e92.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "minor", | ||
"comment": "feat(motion): add Rotate presence motion component", | ||
"packageName": "@fluentui/react-motion-components-preview", | ||
"email": "[email protected]", | ||
"dependentChangeType": "patch" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
...es/react-components/react-motion-components-preview/library/src/atoms/rotate-atom.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { motionTokens } from '@fluentui/react-motion'; | ||
import { rotateAtom } from './rotate-atom'; | ||
|
||
describe('rotateAtom', () => { | ||
it('creates enter keyframes with rotation from angle to 0', () => { | ||
const atom = rotateAtom({ | ||
direction: 'enter', | ||
duration: 300, | ||
easing: motionTokens.curveEasyEase, | ||
angle: -90, | ||
}); | ||
|
||
expect(atom).toMatchObject({ | ||
duration: 300, | ||
easing: motionTokens.curveEasyEase, | ||
keyframes: [{ rotate: 'y -90deg' }, { rotate: 'y 0deg' }], | ||
}); | ||
}); | ||
|
||
it('creates exit keyframes with rotation from 0 to exitAngle', () => { | ||
const atom = rotateAtom({ | ||
direction: 'exit', | ||
duration: 250, | ||
easing: motionTokens.curveAccelerateMin, | ||
exitAngle: 90, | ||
}); | ||
|
||
expect(atom).toMatchObject({ | ||
duration: 250, | ||
easing: motionTokens.curveAccelerateMin, | ||
keyframes: [{ rotate: 'y 0deg' }, { rotate: 'y 90deg' }], | ||
}); | ||
}); | ||
|
||
it('uses default angle when not provided', () => { | ||
const atom = rotateAtom({ | ||
direction: 'enter', | ||
duration: 300, | ||
}); | ||
|
||
expect(atom.keyframes).toEqual([{ rotate: 'y -90deg' }, { rotate: 'y 0deg' }]); | ||
}); | ||
|
||
it('uses default easing when not provided', () => { | ||
const atom = rotateAtom({ | ||
direction: 'enter', | ||
duration: 300, | ||
angle: 45, | ||
}); | ||
|
||
expect(atom.easing).toBe(motionTokens.curveLinear); | ||
}); | ||
|
||
it('uses default axis when not provided', () => { | ||
const atom = rotateAtom({ | ||
direction: 'enter', | ||
duration: 300, | ||
angle: 180, | ||
}); | ||
|
||
expect(atom.keyframes[0]).toEqual({ rotate: 'y 180deg' }); | ||
expect(atom.keyframes[1]).toEqual({ rotate: 'y 0deg' }); | ||
}); | ||
|
||
it('handles different rotation axes', () => { | ||
const atomX = rotateAtom({ | ||
direction: 'enter', | ||
duration: 300, | ||
axis: 'x', | ||
angle: 45, | ||
}); | ||
|
||
const atomY = rotateAtom({ | ||
direction: 'enter', | ||
duration: 300, | ||
axis: 'y', | ||
angle: 45, | ||
}); | ||
|
||
const atomZ = rotateAtom({ | ||
direction: 'enter', | ||
duration: 300, | ||
axis: 'z', | ||
angle: 45, | ||
}); | ||
|
||
expect(atomX.keyframes[0]).toEqual({ rotate: 'x 45deg' }); | ||
expect(atomY.keyframes[0]).toEqual({ rotate: 'y 45deg' }); | ||
expect(atomZ.keyframes[0]).toEqual({ rotate: 'z 45deg' }); | ||
}); | ||
|
||
it('uses exitAngle when direction is exit', () => { | ||
const atom = rotateAtom({ | ||
direction: 'exit', | ||
duration: 300, | ||
angle: -90, | ||
exitAngle: 45, | ||
}); | ||
|
||
expect(atom.keyframes).toEqual([{ rotate: 'y 0deg' }, { rotate: 'y 45deg' }]); | ||
}); | ||
|
||
it('uses negated angle as default exitAngle', () => { | ||
const atom = rotateAtom({ | ||
direction: 'exit', | ||
duration: 300, | ||
angle: -90, | ||
}); | ||
|
||
expect(atom.keyframes).toEqual([{ rotate: 'y 0deg' }, { rotate: 'y 90deg' }]); | ||
}); | ||
|
||
it('handles positive and negative angle values', () => { | ||
const atomPositive = rotateAtom({ | ||
direction: 'enter', | ||
duration: 300, | ||
angle: 90, | ||
}); | ||
|
||
const atomNegative = rotateAtom({ | ||
direction: 'enter', | ||
duration: 300, | ||
angle: -45, | ||
}); | ||
|
||
expect(atomPositive.keyframes[0]).toEqual({ rotate: 'y 90deg' }); | ||
expect(atomNegative.keyframes[0]).toEqual({ rotate: 'y -45deg' }); | ||
}); | ||
}); |
51 changes: 51 additions & 0 deletions
51
packages/react-components/react-motion-components-preview/library/src/atoms/rotate-atom.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { AtomMotion, PresenceDirection, motionTokens } from '@fluentui/react-motion'; | ||
import type { RotateParams } from '../components/Rotate/rotate-types'; | ||
|
||
type Axis3D = NonNullable<RotateParams['axis']>; | ||
|
||
interface RotateAtomParams { | ||
direction: PresenceDirection; | ||
duration: number; | ||
easing?: string; | ||
axis?: Axis3D; | ||
angle?: number; | ||
exitAngle?: number; | ||
} | ||
|
||
const createRotateValue = (axis: Axis3D, angle: number): string => { | ||
return `${axis.toLowerCase()} ${angle}deg`; | ||
}; | ||
|
||
/** | ||
* Generates a motion atom object for a rotation around a single axis. | ||
* @param direction - The functional direction of the motion: 'enter' or 'exit'. | ||
* @param duration - The duration of the motion in milliseconds. | ||
* @param easing - The easing curve for the motion. Defaults to `motionTokens.curveLinear`. | ||
* @param axis - The axis of rotation: 'x', 'y', or 'z'. Defaults to 'y'. | ||
* @param angle - The starting rotation angle in degrees. Defaults to -90. | ||
* @param exitAngle - The ending rotation angle in degrees. Defaults to the negation of `angle`. | ||
* @returns A motion atom object with rotate keyframes and the supplied duration and easing. | ||
*/ | ||
export const rotateAtom = ({ | ||
direction, | ||
duration, | ||
easing = motionTokens.curveLinear, | ||
axis = 'y', | ||
angle = -90, | ||
exitAngle = -angle, | ||
}: RotateAtomParams): AtomMotion => { | ||
let fromAngle = angle; | ||
let toAngle = 0; | ||
|
||
if (direction === 'exit') { | ||
fromAngle = 0; | ||
toAngle = exitAngle; | ||
} | ||
const keyframes = [{ rotate: createRotateValue(axis, fromAngle) }, { rotate: createRotateValue(axis, toAngle) }]; | ||
|
||
return { | ||
keyframes, | ||
duration, | ||
easing, | ||
}; | ||
}; |
12 changes: 12 additions & 0 deletions
12
...t-components/react-motion-components-preview/library/src/components/Rotate/Rotate.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { expectPresenceMotionFunction, expectPresenceMotionArray } from '../../testing/testUtils'; | ||
import { Rotate } from './Rotate'; | ||
|
||
describe('Rotate', () => { | ||
it('stores its motion definition as a static function', () => { | ||
expectPresenceMotionFunction(Rotate); | ||
}); | ||
|
||
it('generates a motion definition from the static function', () => { | ||
expectPresenceMotionArray(Rotate); | ||
}); | ||
}); |
62 changes: 62 additions & 0 deletions
62
.../react-components/react-motion-components-preview/library/src/components/Rotate/Rotate.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { AtomMotion, createPresenceComponent, motionTokens, PresenceMotionFn } from '@fluentui/react-motion'; | ||
import { fadeAtom } from '../../atoms/fade-atom'; | ||
import { rotateAtom } from '../../atoms/rotate-atom'; | ||
import { RotateParams } from './rotate-types'; | ||
|
||
/** | ||
* Define a presence motion for rotate in/out | ||
* | ||
* @param duration - Time (ms) for the enter transition (rotate-in). Defaults to the `durationGentle` value. | ||
* @param easing - Easing curve for the enter transition (rotate-in). Defaults to the `curveDecelerateMax` value. | ||
* @param exitDuration - Time (ms) for the exit transition (rotate-out). Defaults to the `duration` param for symmetry. | ||
* @param exitEasing - Easing curve for the exit transition (rotate-out). Defaults to the `curveAccelerateMax` value. | ||
* @param axis - The axis of rotation: 'x', 'y', or 'z'. Defaults to 'y'. | ||
* @param angle - The starting rotation angle in degrees. Defaults to -90. | ||
* @param exitAngle - The ending rotation angle in degrees. Defaults to the negation of `angle`. | ||
* @param animateOpacity - Whether to animate the opacity during the rotation. Defaults to `true`. | ||
*/ | ||
const rotatePresenceFn: PresenceMotionFn<RotateParams> = ({ | ||
axis = 'y', | ||
angle = -90, | ||
exitAngle = -angle, | ||
duration = motionTokens.durationGentle, | ||
exitDuration = duration, | ||
easing = motionTokens.curveDecelerateMax, | ||
exitEasing = motionTokens.curveAccelerateMax, | ||
animateOpacity = true, | ||
}: RotateParams) => { | ||
const enterAtoms: AtomMotion[] = [ | ||
rotateAtom({ | ||
direction: 'enter', | ||
duration, | ||
easing, | ||
axis, | ||
angle, | ||
exitAngle, | ||
}), | ||
]; | ||
|
||
const exitAtoms: AtomMotion[] = [ | ||
rotateAtom({ | ||
direction: 'exit', | ||
duration: exitDuration, | ||
easing: exitEasing, | ||
axis, | ||
angle, | ||
exitAngle, | ||
}), | ||
]; | ||
|
||
if (animateOpacity) { | ||
enterAtoms.push(fadeAtom({ direction: 'enter', duration, easing })); | ||
exitAtoms.push(fadeAtom({ direction: 'exit', duration: exitDuration, easing: exitEasing })); | ||
} | ||
|
||
return { | ||
enter: enterAtoms, | ||
exit: exitAtoms, | ||
}; | ||
}; | ||
|
||
// Create a presence motion component to rotate an element around a single axis (x, y, or z). | ||
export const Rotate = createPresenceComponent(rotatePresenceFn); |
2 changes: 2 additions & 0 deletions
2
...s/react-components/react-motion-components-preview/library/src/components/Rotate/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { Rotate } from './Rotate'; | ||
export type { RotateParams } from './rotate-types'; |
25 changes: 25 additions & 0 deletions
25
...-components/react-motion-components-preview/library/src/components/Rotate/rotate-types.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import type { PresenceDuration, PresenceEasing, AnimateOpacity } from '../../types'; | ||
|
||
type Axis3D = 'x' | 'y' | 'z'; | ||
|
||
export type RotateParams = PresenceDuration & | ||
PresenceEasing & | ||
AnimateOpacity & { | ||
/** | ||
* The axis of rotation: 'x', 'y', or 'z'. | ||
* Defaults to 'y'. | ||
*/ | ||
axis?: Axis3D; | ||
|
||
/** | ||
* The starting rotation angle in degrees. | ||
* Defaults to -90. | ||
*/ | ||
angle?: number; | ||
|
||
/** | ||
* The ending rotation angle in degrees. | ||
* Defaults to the negation of `angle`. | ||
*/ | ||
exitAngle?: number; | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.