From 9a71e4cfb1418ea85aca590538ad17ca7b84aa02 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 13 Jan 2025 19:32:00 +0000 Subject: [PATCH 01/33] refactor(Calendar): add motion components for slide and fade - Copied from MessageBarGroup.motions.tsx for now --- .../library/src/utils/motions.ts | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 packages/react-components/react-calendar-compat/library/src/utils/motions.ts diff --git a/packages/react-components/react-calendar-compat/library/src/utils/motions.ts b/packages/react-components/react-calendar-compat/library/src/utils/motions.ts new file mode 100644 index 00000000000000..1d6d5211cc578f --- /dev/null +++ b/packages/react-components/react-calendar-compat/library/src/utils/motions.ts @@ -0,0 +1,110 @@ +import { Vertical } from './../../../../../../apps/vr-tests-react-components/src/stories/Tabs.stories'; +import { createMotionComponent, PresenceDirection, motionTokens, AtomMotion } from '@fluentui/react-motion'; + +interface FadeAtomParams { + direction: PresenceDirection; + duration: number; + easing?: string; + fromValue?: number; +} + +// For now, this is copied from MessageBarGroup.motions.tsx +// TODO: move to a centralized location and import from there +/** + * Generates a motion atom object for a fade in or fade out. + * @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 fromValue - The starting opacity value. Defaults to 0. + * @returns A motion atom object with opacity keyframes and the supplied duration and easing. + */ +const fadeAtom = ({ + direction, + duration, + easing = motionTokens.curveLinear, + fromValue = 0, +}: FadeAtomParams): AtomMotion => { + const keyframes = [{ opacity: fromValue }, { opacity: 1 }]; + if (direction === 'exit') { + keyframes.reverse(); + } + return { + keyframes, + duration, + easing, + }; +}; + +// For now, this is copied from MessageBarGroup.motions.tsx +// TODO: move to a centralized location and import from there +/** + * Generates a motion atom object for an X or Y translation, from a specified distance to zero. + * @param direction - The functional direction of the motion: 'enter' or 'exit'. + * @param axis - The axis of the translation: 'X' or 'Y'. + * @param fromValue - The starting position of the slide; it can be a percentage or pixels. + * @param duration - The duration of the motion in milliseconds. + * @param easing - The easing curve for the motion. Defaults to `motionTokens.curveDecelerateMid`. + */ +const slideAtom = ({ + direction, + axis, + fromValue, + duration, + easing = motionTokens.curveDecelerateMid, +}: { + direction: PresenceDirection; + axis: 'X' | 'Y'; + fromValue: string; + duration: number; + easing?: string; +}): AtomMotion => { + const keyframes = [{ transform: `translate${axis}(${fromValue})` }, { transform: `translate${axis}(0)` }]; + if (direction === 'exit') { + keyframes.reverse(); + } + return { + keyframes, + duration, + easing, + }; +}; + +export const createVerticalSlideFadeMotion = ({ + distance = '20px', + reverse = false, + duration = 367, + easing = motionTokens.curveDecelerateMax, +} = {}) => { + // TODO: animateOpacity runtime param? + return createMotionComponent(() => { + return [ + slideAtom({ direction: 'enter', axis: 'Y', fromValue: reverse ? `-${distance}` : distance, duration, easing }), + fadeAtom({ direction: 'enter', duration, easing }), + ]; + }); +}; + +// An alternate approach where both directions of the the slide are in a single motion component, +// with up vs. down controlled by a runtime param `reverse`. +export const createVerticalSlideFadeMotion_combined = ({ + distance = '20px', // TODO: make this a runtime param? + duration = 367, + easing = motionTokens.curveDecelerateMax, +} = {}) => { + return createMotionComponent( + ({ reverse = false, animateOpacity = true }: { reverse?: boolean; animateOpacity?: boolean }) => { + const atoms = [ + slideAtom({ direction: 'enter', axis: 'Y', fromValue: reverse ? `-${distance}` : distance, duration, easing }), + ]; + if (animateOpacity) { + atoms.push(fadeAtom({ direction: 'enter', duration, easing })); + } + return atoms; + }, + ); +}; + +export const SlideUp = createVerticalSlideFadeMotion(); +export const SlideDown = createVerticalSlideFadeMotion({ reverse: true }); +// TODO: test using this instead of SlideUp/SlideDown +export const VerticalSlide = createVerticalSlideFadeMotion_combined(); From 03ffd999f12ad9e3e8adff09c864ad10288f9ac0 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 13 Jan 2025 19:34:23 +0000 Subject: [PATCH 02/33] refactor(Calendar): migrate month row slide to motion components --- .../CalendarMonth/CalendarMonth.tsx | 81 ++++++++++--------- .../useCalendarPickerStyles.styles.ts | 18 ++--- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx index 96b7382655f3b4..f49d16ca667ed1 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx @@ -18,6 +18,7 @@ import { CalendarYear } from '../CalendarYear/CalendarYear'; import { useCalendarMonthStyles_unstable } from './useCalendarMonthStyles.styles'; import type { CalendarMonthProps } from './CalendarMonth.types'; import type { CalendarYearRange, ICalendarYear } from '../CalendarYear/CalendarYear.types'; +import { SlideDown, SlideUp } from '../../utils/motions'; const MONTHS_PER_ROW = 4; @@ -94,6 +95,9 @@ export const CalendarMonth: React.FunctionComponent = props const [isYearPickerVisible, setIsYearPickerVisible] = React.useState(false); const animateBackwards = useAnimateBackwards({ navigatedDate }); + // TODO: consider replacing SlideDown/SlideUp with a single motion component + // that receives animateBackwards in a prop, so the component type isn't changing back and forth. + const SlideMotion = animateBackwards ? SlideDown : SlideUp; const selectMonthCallback = (newMonth: number): (() => void) => { return () => onSelectMonth(newMonth); @@ -250,44 +254,47 @@ export const CalendarMonth: React.FunctionComponent = props
{rowIndexes.map((rowNum: number) => { const monthsForRow = strings!.shortMonths.slice(rowNum * MONTHS_PER_ROW, (rowNum + 1) * MONTHS_PER_ROW); + const rowKey = 'monthRow_' + rowNum + navigatedDate.getFullYear(); return ( -
- {monthsForRow.map((month: string, index: number) => { - const monthIndex = rowNum * MONTHS_PER_ROW + index; - const indexedMonth = setMonth(navigatedDate, monthIndex); - const isNavigatedMonth = navigatedDate.getMonth() === monthIndex; - const isSelectedMonth = selectedDate.getMonth() === monthIndex; - const isSelectedYear = selectedDate.getFullYear() === navigatedDate.getFullYear(); - const isInBounds = - (minDate ? compareDatePart(minDate, getMonthEnd(indexedMonth)) < 1 : true) && - (maxDate ? compareDatePart(getMonthStart(indexedMonth), maxDate) < 1 : true); - - return ( - - ); - })} -
+ +
+ {monthsForRow.map((month: string, index: number) => { + const monthIndex = rowNum * MONTHS_PER_ROW + index; + const indexedMonth = setMonth(navigatedDate, monthIndex); + const isNavigatedMonth = navigatedDate.getMonth() === monthIndex; + const isSelectedMonth = selectedDate.getMonth() === monthIndex; + const isSelectedYear = selectedDate.getFullYear() === navigatedDate.getFullYear(); + const isInBounds = + (minDate ? compareDatePart(minDate, getMonthEnd(indexedMonth)) < 1 : true) && + (maxDate ? compareDatePart(getMonthStart(indexedMonth), maxDate) < 1 : true); + + return ( + + ); + })} +
+
); })}
diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts index 9aa3967f718b9e..f816e63f3192f4 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts @@ -329,15 +329,15 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps buttonRow: mergeClasses( calendarPickerClassNames.buttonRow, buttonRowStyles.base, - buttonRowStyles.animation, - animateBackwards !== undefined && - (animationDirection === AnimationDirection.Horizontal - ? animateBackwards - ? buttonRowStyles.horizontalBackward - : buttonRowStyles.horizontalForward - : animateBackwards - ? buttonRowStyles.verticalBackward - : buttonRowStyles.verticalForward), + // buttonRowStyles.animation, + // animateBackwards !== undefined && + // (animationDirection === AnimationDirection.Horizontal + // ? animateBackwards + // ? buttonRowStyles.horizontalBackward + // : buttonRowStyles.horizontalForward + // : animateBackwards + // ? buttonRowStyles.verticalBackward + // : buttonRowStyles.verticalForward), ), itemButton: mergeClasses(calendarPickerClassNames.itemButton, itemButtonStyles.base), selected: mergeClasses(calendarPickerClassNames.selected, highlightSelected && selectedStyles.highlightSelected), From 762f41017c2f4021c920ba082ff108463166515d Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 13 Jan 2025 19:38:20 +0000 Subject: [PATCH 03/33] refactor(Calendar): start migrating week row slide to motion components --- .../CalendarDayGrid/CalendarDayGrid.tsx | 69 ++++++++++++------- .../CalendarDayGrid/CalendarGridRow.tsx | 3 + .../useCalendarDayGridStyles.styles.ts | 34 ++++----- 3 files changed, 63 insertions(+), 43 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx index 83747833b1ff30..a67a3599412c57 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx @@ -12,6 +12,7 @@ import { useWeekCornerStyles, WeekCorners } from './useWeekCornerStyles.styles'; import { mergeClasses } from '@griffel/react'; import type { Day } from '../../utils'; import type { CalendarDayGridProps } from './CalendarDayGrid.types'; +import { SlideDown, SlideUp } from '../../utils/motions'; export interface DayInfo extends Day { onSelected: () => void; @@ -75,6 +76,8 @@ export const CalendarDayGrid: React.FunctionComponent = pr const weeks = useWeeks(props, onSelectDate, getSetRefCallback); const animateBackwards = useAnimateBackwards(weeks); + const SlideMotion = animateBackwards ? SlideDown : SlideUp; + const [getWeekCornerStyles, calculateRoundedStyles] = useWeekCornerStyles(props); React.useImperativeHandle( @@ -156,6 +159,8 @@ export const CalendarDayGrid: React.FunctionComponent = pr } as const; const arrowNavigationAttributes = useArrowNavigationGroup({ axis: 'grid' }); + const firstWeek = weeks[0]; + const finalWeek = weeks![weeks!.length - 1]; return ( = pr > - + +
+ +
+
{weeks!.slice(1, weeks!.length - 1).map((week: DayInfo[], weekIndex: number) => ( - + +
+ +
+
))} - + +
+ +
+
); diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx index 19094c629041c6..e8185ed88019a9 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx @@ -4,6 +4,7 @@ import { CalendarGridDayCell } from './CalendarGridDayCell'; import type { CalendarDayGridProps, CalendarDayGridStyles } from './CalendarDayGrid.types'; import type { DayInfo } from './CalendarDayGrid'; import type { WeekCorners } from './useWeekCornerStyles.styles'; +import { SlideUp } from '../../utils/motions'; /** * @internal @@ -52,6 +53,7 @@ export const CalendarGridRow: React.FunctionComponent = pr : ''; return ( + // {showWeekNumbers && weekNumbers && ( = pr ))} + // ); }; diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts index febe85c7539797..9a6c0ee9324ffb 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts @@ -430,15 +430,15 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro weekRow: mergeClasses( calendarDayGridClassNames.weekRow, weekRowStyles.base, - animateBackwards !== undefined && weekRowStyles.animation, - animateBackwards !== undefined && - (animationDirection === AnimationDirection.Horizontal - ? animateBackwards - ? weekRowStyles.horizontalBackward - : weekRowStyles.horizontalForward - : animateBackwards - ? weekRowStyles.verticalBackward - : weekRowStyles.verticalForward), + // animateBackwards !== undefined && weekRowStyles.animation, + // animateBackwards !== undefined && + // (animationDirection === AnimationDirection.Horizontal + // ? animateBackwards + // ? weekRowStyles.horizontalBackward + // : weekRowStyles.horizontalForward + // : animateBackwards + // ? weekRowStyles.verticalBackward + // : weekRowStyles.verticalForward), ), weekDayLabelCell: mergeClasses(calendarDayGridClassNames.weekDayLabelCell, weekDayLabelCellStyles.base), weekNumberCell: mergeClasses(calendarDayGridClassNames.weekNumberCell, weekNumberCellStyles.base), @@ -452,18 +452,18 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro firstTransitionWeek: mergeClasses( calendarDayGridClassNames.firstTransitionWeek, firstTransitionWeekStyles.base, - animateBackwards !== undefined && - animationDirection !== AnimationDirection.Horizontal && - !animateBackwards && - firstTransitionWeekStyles.verticalForward, + // animateBackwards !== undefined && + // animationDirection !== AnimationDirection.Horizontal && + // !animateBackwards && + // firstTransitionWeekStyles.verticalForward, ), lastTransitionWeek: mergeClasses( calendarDayGridClassNames.lastTransitionWeek, lastTransitionWeekStyles.base, - animateBackwards !== undefined && - animationDirection !== AnimationDirection.Horizontal && - animateBackwards && - lastTransitionWeekStyles.verticalBackward, + // animateBackwards !== undefined && + // animationDirection !== AnimationDirection.Horizontal && + // animateBackwards && + // lastTransitionWeekStyles.verticalBackward, ), dayMarker: mergeClasses(calendarDayGridClassNames.dayMarker, dayMarkerStyles.base), dayTodayMarker: mergeClasses(calendarDayGridClassNames.dayTodayMarker, dayTodayMarkerStyles.base), From c19f93380cad8e75445b3f3acc53d6f912b7b338 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Wed, 10 Sep 2025 18:20:47 +0000 Subject: [PATCH 04/33] fix(motion): update presence .In and .Out to use MotionComponent type --- .../react-motion/library/etc/react-motion.api.md | 4 ++-- .../library/src/factories/createPresenceComponent.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-motion/library/etc/react-motion.api.md b/packages/react-components/react-motion/library/etc/react-motion.api.md index f8d084538c43da..b3f56b01d84d84 100644 --- a/packages/react-components/react-motion/library/etc/react-motion.api.md +++ b/packages/react-components/react-motion/library/etc/react-motion.api.md @@ -108,8 +108,8 @@ export const motionTokens: { export type PresenceComponent = {}> = React_2.FC & { (props: PresenceComponentProps & MotionParams): JSXElement | null; [PRESENCE_MOTION_DEFINITION]: PresenceMotionFn; - In: React_2.FC; - Out: React_2.FC; + In: MotionComponent; + Out: MotionComponent; }; // @public (undocumented) diff --git a/packages/react-components/react-motion/library/src/factories/createPresenceComponent.ts b/packages/react-components/react-motion/library/src/factories/createPresenceComponent.ts index ea0d3b2fac32bb..c4262501305139 100644 --- a/packages/react-components/react-motion/library/src/factories/createPresenceComponent.ts +++ b/packages/react-components/react-motion/library/src/factories/createPresenceComponent.ts @@ -19,7 +19,7 @@ import type { AnimationHandle, } from '../types'; import { useMotionBehaviourContext } from '../contexts/MotionBehaviourContext'; -import { createMotionComponent, MotionComponentProps } from './createMotionComponent'; +import { createMotionComponent, MotionComponent } from './createMotionComponent'; /** * @internal A private symbol to store the motion definition on the component for variants. @@ -83,8 +83,8 @@ export type PresenceComponent = > & { (props: PresenceComponentProps & MotionParams): JSXElement | null; [PRESENCE_MOTION_DEFINITION]: PresenceMotionFn; - In: React.FC; - Out: React.FC; + In: MotionComponent; + Out: MotionComponent; }; const INTERRUPTABLE_MOTION_SYMBOL = Symbol.for('interruptablePresence'); From fa474cfefe43723026fe873a931d7818db145df2 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Wed, 10 Sep 2025 18:23:13 +0000 Subject: [PATCH 05/33] refactor(Calendar): migrate to variants of Slide motion component --- .../library/src/utils/motions.ts | 124 ++---------------- 1 file changed, 14 insertions(+), 110 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/utils/motions.ts b/packages/react-components/react-calendar-compat/library/src/utils/motions.ts index 1d6d5211cc578f..a81e65ad2f858a 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/motions.ts +++ b/packages/react-components/react-calendar-compat/library/src/utils/motions.ts @@ -1,110 +1,14 @@ -import { Vertical } from './../../../../../../apps/vr-tests-react-components/src/stories/Tabs.stories'; -import { createMotionComponent, PresenceDirection, motionTokens, AtomMotion } from '@fluentui/react-motion'; - -interface FadeAtomParams { - direction: PresenceDirection; - duration: number; - easing?: string; - fromValue?: number; -} - -// For now, this is copied from MessageBarGroup.motions.tsx -// TODO: move to a centralized location and import from there -/** - * Generates a motion atom object for a fade in or fade out. - * @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 fromValue - The starting opacity value. Defaults to 0. - * @returns A motion atom object with opacity keyframes and the supplied duration and easing. - */ -const fadeAtom = ({ - direction, - duration, - easing = motionTokens.curveLinear, - fromValue = 0, -}: FadeAtomParams): AtomMotion => { - const keyframes = [{ opacity: fromValue }, { opacity: 1 }]; - if (direction === 'exit') { - keyframes.reverse(); - } - return { - keyframes, - duration, - easing, - }; -}; - -// For now, this is copied from MessageBarGroup.motions.tsx -// TODO: move to a centralized location and import from there -/** - * Generates a motion atom object for an X or Y translation, from a specified distance to zero. - * @param direction - The functional direction of the motion: 'enter' or 'exit'. - * @param axis - The axis of the translation: 'X' or 'Y'. - * @param fromValue - The starting position of the slide; it can be a percentage or pixels. - * @param duration - The duration of the motion in milliseconds. - * @param easing - The easing curve for the motion. Defaults to `motionTokens.curveDecelerateMid`. - */ -const slideAtom = ({ - direction, - axis, - fromValue, - duration, - easing = motionTokens.curveDecelerateMid, -}: { - direction: PresenceDirection; - axis: 'X' | 'Y'; - fromValue: string; - duration: number; - easing?: string; -}): AtomMotion => { - const keyframes = [{ transform: `translate${axis}(${fromValue})` }, { transform: `translate${axis}(0)` }]; - if (direction === 'exit') { - keyframes.reverse(); - } - return { - keyframes, - duration, - easing, - }; -}; - -export const createVerticalSlideFadeMotion = ({ - distance = '20px', - reverse = false, - duration = 367, - easing = motionTokens.curveDecelerateMax, -} = {}) => { - // TODO: animateOpacity runtime param? - return createMotionComponent(() => { - return [ - slideAtom({ direction: 'enter', axis: 'Y', fromValue: reverse ? `-${distance}` : distance, duration, easing }), - fadeAtom({ direction: 'enter', duration, easing }), - ]; - }); -}; - -// An alternate approach where both directions of the the slide are in a single motion component, -// with up vs. down controlled by a runtime param `reverse`. -export const createVerticalSlideFadeMotion_combined = ({ - distance = '20px', // TODO: make this a runtime param? - duration = 367, - easing = motionTokens.curveDecelerateMax, -} = {}) => { - return createMotionComponent( - ({ reverse = false, animateOpacity = true }: { reverse?: boolean; animateOpacity?: boolean }) => { - const atoms = [ - slideAtom({ direction: 'enter', axis: 'Y', fromValue: reverse ? `-${distance}` : distance, duration, easing }), - ]; - if (animateOpacity) { - atoms.push(fadeAtom({ direction: 'enter', duration, easing })); - } - return atoms; - }, - ); -}; - -export const SlideUp = createVerticalSlideFadeMotion(); -export const SlideDown = createVerticalSlideFadeMotion({ reverse: true }); -// TODO: test using this instead of SlideUp/SlideDown -export const VerticalSlide = createVerticalSlideFadeMotion_combined(); +import { motionTokens, createMotionComponentVariant } from '@fluentui/react-motion'; +import { Slide } from '@fluentui/react-motion-components-preview'; + +export const SlideUp = createMotionComponentVariant(Slide.In, { + fromY: '20px', + duration: 367, + easing: motionTokens.curveDecelerateMax, +}); + +export const SlideDown = createMotionComponentVariant(Slide.In, { + fromY: '-20px', + duration: 367, + easing: motionTokens.curveDecelerateMax, +}); From 9f90c308ed5d6436a9e04195da342c881f68a520 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Wed, 10 Sep 2025 19:01:02 +0000 Subject: [PATCH 06/33] TEMP: default animationDirection to Horizontal for testing --- .../library/src/components/CalendarDay/CalendarDay.tsx | 3 ++- .../library/src/components/CalendarDayGrid/CalendarDayGrid.tsx | 3 ++- .../library/src/components/CalendarMonth/CalendarMonth.tsx | 3 ++- .../CalendarPicker/useCalendarPickerStyles.styles.ts | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx index 2e52f1b2f45207..8a13838ce5b2eb 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx @@ -9,6 +9,7 @@ import { useCalendarDayStyles_unstable } from './useCalendarDayStyles.styles'; import type { ICalendarDayGrid } from '../CalendarDayGrid/CalendarDayGrid.types'; import type { CalendarDayProps, CalendarDayStyles } from './CalendarDay.types'; import type { JSXElement } from '@fluentui/react-utilities'; +import { AnimationDirection } from '../../Calendar'; /** * @internal @@ -40,7 +41,7 @@ export const CalendarDay: React.FunctionComponent = props => { onNavigateDate, showWeekNumbers, dateRangeType, - animationDirection, + animationDirection = AnimationDirection.Horizontal, } = props; const classNames = useCalendarDayStyles_unstable({ diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx index a67a3599412c57..b24baf9213e674 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx @@ -13,6 +13,7 @@ import { mergeClasses } from '@griffel/react'; import type { Day } from '../../utils'; import type { CalendarDayGridProps } from './CalendarDayGrid.types'; import { SlideDown, SlideUp } from '../../utils/motions'; +import { AnimationDirection } from '../../Calendar'; export interface DayInfo extends Day { onSelected: () => void; @@ -133,7 +134,7 @@ export const CalendarDayGrid: React.FunctionComponent = pr showWeekNumbers, labelledBy, lightenDaysOutsideNavigatedMonth, - animationDirection, + animationDirection = AnimationDirection.Horizontal, } = props; const classNames = useCalendarDayGridStyles_unstable({ diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx index f49d16ca667ed1..f568f10935b79c 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx @@ -19,6 +19,7 @@ import { useCalendarMonthStyles_unstable } from './useCalendarMonthStyles.styles import type { CalendarMonthProps } from './CalendarMonth.types'; import type { CalendarYearRange, ICalendarYear } from '../CalendarYear/CalendarYear.types'; import { SlideDown, SlideUp } from '../../utils/motions'; +import { AnimationDirection } from '../../Calendar'; const MONTHS_PER_ROW = 4; @@ -73,7 +74,7 @@ function useFocusLogic({ componentRef }: { componentRef: CalendarMonthProps['com export const CalendarMonth: React.FunctionComponent = props => { const { allFocusable, - animationDirection, + animationDirection = AnimationDirection.Horizontal, className, componentRef, dateTimeFormatter = DEFAULT_DATE_FORMATTING, diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts index f816e63f3192f4..890b84395633d5 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts @@ -304,7 +304,7 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps const { animateBackwards, - animationDirection, + animationDirection = AnimationDirection.Horizontal, className, hasHeaderClickCallback, highlightCurrent, From ff8a7d321a370bbb096fa98849c54c097eec2c98 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Wed, 10 Sep 2025 19:10:39 +0000 Subject: [PATCH 07/33] reactor(Calendar): create horizontal slide motion components for month navigation --- .../components/CalendarMonth/CalendarMonth.tsx | 7 +++++-- .../library/src/utils/motions.ts | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx index f568f10935b79c..e8b8219229874f 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx @@ -18,7 +18,7 @@ import { CalendarYear } from '../CalendarYear/CalendarYear'; import { useCalendarMonthStyles_unstable } from './useCalendarMonthStyles.styles'; import type { CalendarMonthProps } from './CalendarMonth.types'; import type { CalendarYearRange, ICalendarYear } from '../CalendarYear/CalendarYear.types'; -import { SlideDown, SlideUp } from '../../utils/motions'; +import { SlideDown, SlideUp, SlideLeft, SlideRight } from '../../utils/motions'; import { AnimationDirection } from '../../Calendar'; const MONTHS_PER_ROW = 4; @@ -98,7 +98,10 @@ export const CalendarMonth: React.FunctionComponent = props const animateBackwards = useAnimateBackwards({ navigatedDate }); // TODO: consider replacing SlideDown/SlideUp with a single motion component // that receives animateBackwards in a prop, so the component type isn't changing back and forth. - const SlideMotion = animateBackwards ? SlideDown : SlideUp; + let SlideMotion = animateBackwards ? SlideDown : SlideUp; + if (animationDirection === AnimationDirection.Horizontal) { + SlideMotion = animateBackwards ? SlideRight : SlideLeft; + } const selectMonthCallback = (newMonth: number): (() => void) => { return () => onSelectMonth(newMonth); diff --git a/packages/react-components/react-calendar-compat/library/src/utils/motions.ts b/packages/react-components/react-calendar-compat/library/src/utils/motions.ts index a81e65ad2f858a..8234166240bf6e 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/motions.ts +++ b/packages/react-components/react-calendar-compat/library/src/utils/motions.ts @@ -12,3 +12,19 @@ export const SlideDown = createMotionComponentVariant(Slide.In, { duration: 367, easing: motionTokens.curveDecelerateMax, }); + +export const SlideLeft = createMotionComponentVariant(Slide.In, { + fromX: '20px', + fromY: '0px', + duration: 367, + easing: motionTokens.curveDecelerateMax, +}); + +export const SlideRight = createMotionComponentVariant(Slide.In, { + fromX: '-20px', + fromY: '0px', + duration: 367, + easing: motionTokens.curveDecelerateMax, +}); + +// export const CalendarSlide = From 34e87fca2deab29ce2e4c1f8f76a36e6e2026078 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 14:19:01 +0000 Subject: [PATCH 08/33] refactor(Calendar): simplify conditional motion; consolidate to DirectionalSlide --- .../CalendarDayGrid/CalendarDayGrid.tsx | 15 +++++----- .../CalendarMonth/CalendarMonth.tsx | 10 ++----- .../library/src/utils/motions.ts | 30 ------------------- .../library/src/utils/motions.tsx | 29 ++++++++++++++++++ 4 files changed, 39 insertions(+), 45 deletions(-) delete mode 100644 packages/react-components/react-calendar-compat/library/src/utils/motions.ts create mode 100644 packages/react-components/react-calendar-compat/library/src/utils/motions.tsx diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx index b24baf9213e674..a9e7514cabbc75 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx @@ -12,7 +12,7 @@ import { useWeekCornerStyles, WeekCorners } from './useWeekCornerStyles.styles'; import { mergeClasses } from '@griffel/react'; import type { Day } from '../../utils'; import type { CalendarDayGridProps } from './CalendarDayGrid.types'; -import { SlideDown, SlideUp } from '../../utils/motions'; +import { DirectionalSlide } from '../../utils/motions'; import { AnimationDirection } from '../../Calendar'; export interface DayInfo extends Day { @@ -77,7 +77,6 @@ export const CalendarDayGrid: React.FunctionComponent = pr const weeks = useWeeks(props, onSelectDate, getSetRefCallback); const animateBackwards = useAnimateBackwards(weeks); - const SlideMotion = animateBackwards ? SlideDown : SlideUp; const [getWeekCornerStyles, calculateRoundedStyles] = useWeekCornerStyles(props); @@ -175,7 +174,7 @@ export const CalendarDayGrid: React.FunctionComponent = pr > - +
= pr ariaHidden={true} />
-
+ {weeks!.slice(1, weeks!.length - 1).map((week: DayInfo[], weekIndex: number) => ( - +
= pr rowClassName={classNames.weekRow} />
-
+ ))} - +
= pr ariaHidden={true} />
-
+ ); diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx index e8b8219229874f..e5bcc43567cedf 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx @@ -18,7 +18,7 @@ import { CalendarYear } from '../CalendarYear/CalendarYear'; import { useCalendarMonthStyles_unstable } from './useCalendarMonthStyles.styles'; import type { CalendarMonthProps } from './CalendarMonth.types'; import type { CalendarYearRange, ICalendarYear } from '../CalendarYear/CalendarYear.types'; -import { SlideDown, SlideUp, SlideLeft, SlideRight } from '../../utils/motions'; +import { DirectionalSlide } from '../../utils/motions'; import { AnimationDirection } from '../../Calendar'; const MONTHS_PER_ROW = 4; @@ -98,10 +98,6 @@ export const CalendarMonth: React.FunctionComponent = props const animateBackwards = useAnimateBackwards({ navigatedDate }); // TODO: consider replacing SlideDown/SlideUp with a single motion component // that receives animateBackwards in a prop, so the component type isn't changing back and forth. - let SlideMotion = animateBackwards ? SlideDown : SlideUp; - if (animationDirection === AnimationDirection.Horizontal) { - SlideMotion = animateBackwards ? SlideRight : SlideLeft; - } const selectMonthCallback = (newMonth: number): (() => void) => { return () => onSelectMonth(newMonth); @@ -260,7 +256,7 @@ export const CalendarMonth: React.FunctionComponent = props const monthsForRow = strings!.shortMonths.slice(rowNum * MONTHS_PER_ROW, (rowNum + 1) * MONTHS_PER_ROW); const rowKey = 'monthRow_' + rowNum + navigatedDate.getFullYear(); return ( - +
{monthsForRow.map((month: string, index: number) => { const monthIndex = rowNum * MONTHS_PER_ROW + index; @@ -298,7 +294,7 @@ export const CalendarMonth: React.FunctionComponent = props ); })}
-
+ ); })} diff --git a/packages/react-components/react-calendar-compat/library/src/utils/motions.ts b/packages/react-components/react-calendar-compat/library/src/utils/motions.ts deleted file mode 100644 index 8234166240bf6e..00000000000000 --- a/packages/react-components/react-calendar-compat/library/src/utils/motions.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { motionTokens, createMotionComponentVariant } from '@fluentui/react-motion'; -import { Slide } from '@fluentui/react-motion-components-preview'; - -export const SlideUp = createMotionComponentVariant(Slide.In, { - fromY: '20px', - duration: 367, - easing: motionTokens.curveDecelerateMax, -}); - -export const SlideDown = createMotionComponentVariant(Slide.In, { - fromY: '-20px', - duration: 367, - easing: motionTokens.curveDecelerateMax, -}); - -export const SlideLeft = createMotionComponentVariant(Slide.In, { - fromX: '20px', - fromY: '0px', - duration: 367, - easing: motionTokens.curveDecelerateMax, -}); - -export const SlideRight = createMotionComponentVariant(Slide.In, { - fromX: '-20px', - fromY: '0px', - duration: 367, - easing: motionTokens.curveDecelerateMax, -}); - -// export const CalendarSlide = diff --git a/packages/react-components/react-calendar-compat/library/src/utils/motions.tsx b/packages/react-components/react-calendar-compat/library/src/utils/motions.tsx new file mode 100644 index 00000000000000..14a96b505a6bb9 --- /dev/null +++ b/packages/react-components/react-calendar-compat/library/src/utils/motions.tsx @@ -0,0 +1,29 @@ +import { motionTokens } from '@fluentui/react-motion'; +import { Slide } from '@fluentui/react-motion-components-preview'; +import * as React from 'react'; +import { AnimationDirection } from '../Calendar'; +import { JSXElement } from '@fluentui/react-utilities'; + +export const DirectionalSlide: React.FC<{ + duration?: number; + easing?: string; + animationDirection?: AnimationDirection; + animateBackwards?: boolean; + children: JSXElement; +}> = ({ + duration = 367, + easing = motionTokens.curveDecelerateMax, + animationDirection = AnimationDirection.Vertical, + animateBackwards = false, + children, +}) => { + let fromX = '0px'; + let fromY = '0px'; + if (animationDirection === AnimationDirection.Horizontal) { + fromX = animateBackwards ? '-20px' : '20px'; + } else { + // default to vertical + fromY = animateBackwards ? '-20px' : '20px'; + } + return {children}; +}; From 8561a19c667158674b45e43306053e830f474717 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 14:28:27 +0000 Subject: [PATCH 09/33] chore: yarn change --- ...lendar-compat-51c1ee36-d9f0-4d16-9a35-8e1fda9424e3.json | 7 +++++++ ...-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 change/@fluentui-react-calendar-compat-51c1ee36-d9f0-4d16-9a35-8e1fda9424e3.json create mode 100644 change/@fluentui-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json diff --git a/change/@fluentui-react-calendar-compat-51c1ee36-d9f0-4d16-9a35-8e1fda9424e3.json b/change/@fluentui-react-calendar-compat-51c1ee36-d9f0-4d16-9a35-8e1fda9424e3.json new file mode 100644 index 00000000000000..dec3716fecb3f9 --- /dev/null +++ b/change/@fluentui-react-calendar-compat-51c1ee36-d9f0-4d16-9a35-8e1fda9424e3.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "refactor(react-calendar): migrate to motion components", + "packageName": "@fluentui/react-calendar-compat", + "email": "robertpenner@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json b/change/@fluentui-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json new file mode 100644 index 00000000000000..4cadad1a7ef0a5 --- /dev/null +++ b/change/@fluentui-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "fix(react-motion): apply MotionComponent type to presence definition", + "packageName": "@fluentui/react-motion", + "email": "robertpenner@microsoft.com", + "dependentChangeType": "patch" +} From 2d3c0b44ac4b075cf97fc90cf0c50009d6af9412 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 14:57:03 +0000 Subject: [PATCH 10/33] refactor: document Calendar motion constants & plans to migrate to tokens --- .../library/src/utils/animations.ts | 18 +++++++++++++----- .../library/src/utils/motions.tsx | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/utils/animations.ts b/packages/react-components/react-calendar-compat/library/src/utils/animations.ts index 6a45c738aa1d89..1672f54f074f53 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/animations.ts +++ b/packages/react-components/react-calendar-compat/library/src/utils/animations.ts @@ -1,9 +1,17 @@ -export const EASING_FUNCTION_1 = 'cubic-bezier(.1,.9,.2,1)'; +import { motionTokens } from '@fluentui/react-motion'; + +export const EASING_FUNCTION_1 = motionTokens.curveDecelerateMax; +// This is nearly linear easing export const EASING_FUNCTION_2 = 'cubic-bezier(.1,.25,.75,.9)'; -export const DURATION_1 = '0.167s'; -export const DURATION_2 = '0.267s'; -export const DURATION_3 = '0.367s'; -export const DURATION_4 = '0.467s'; +// TODO: migrate to the closest duration tokens +// These constants are strings in seconds for CSS, but WAAPI expects numbers in milliseconds +// DURATION_2 is used in CalendarDay for the fade animation +// DURATION_3 is used in CalendarDayGrid for the slide animation +// DURATION_1 and DURATION_4 are not currently used +export const DURATION_1 = '0.167s'; // motionTokens.durationFast = 150 +export const DURATION_2 = '0.267s'; // motionTokens.durationGentle = 250 +export const DURATION_3 = '0.367s'; // motionTokens.durationSlower = 400 +export const DURATION_4 = '0.467s'; // motionTokens.durationUltraSlow = 500 export const FADE_IN = { from: { diff --git a/packages/react-components/react-calendar-compat/library/src/utils/motions.tsx b/packages/react-components/react-calendar-compat/library/src/utils/motions.tsx index 14a96b505a6bb9..27efbc7c83781b 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/motions.tsx +++ b/packages/react-components/react-calendar-compat/library/src/utils/motions.tsx @@ -11,6 +11,7 @@ export const DirectionalSlide: React.FC<{ animateBackwards?: boolean; children: JSXElement; }> = ({ + // The DURATION_3 constant is a string in seconds, but WAAPI expects a number in ms duration = 367, easing = motionTokens.curveDecelerateMax, animationDirection = AnimationDirection.Vertical, From 298c737ae538f0298af200409fdbc6304eb96f2d Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 15:17:49 +0000 Subject: [PATCH 11/33] refactor: rename motions.ts to calendarMotions.ts --- .../library/src/components/CalendarDayGrid/CalendarDayGrid.tsx | 2 +- .../library/src/components/CalendarMonth/CalendarMonth.tsx | 2 +- .../library/src/utils/{motions.tsx => calendarMotions.tsx} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/react-components/react-calendar-compat/library/src/utils/{motions.tsx => calendarMotions.tsx} (100%) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx index a9e7514cabbc75..f3bd0155f61d30 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx @@ -12,7 +12,7 @@ import { useWeekCornerStyles, WeekCorners } from './useWeekCornerStyles.styles'; import { mergeClasses } from '@griffel/react'; import type { Day } from '../../utils'; import type { CalendarDayGridProps } from './CalendarDayGrid.types'; -import { DirectionalSlide } from '../../utils/motions'; +import { DirectionalSlide } from '../../utils/calendarMotions'; import { AnimationDirection } from '../../Calendar'; export interface DayInfo extends Day { diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx index e5bcc43567cedf..7b7f5ae3bd7a1b 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx @@ -18,7 +18,7 @@ import { CalendarYear } from '../CalendarYear/CalendarYear'; import { useCalendarMonthStyles_unstable } from './useCalendarMonthStyles.styles'; import type { CalendarMonthProps } from './CalendarMonth.types'; import type { CalendarYearRange, ICalendarYear } from '../CalendarYear/CalendarYear.types'; -import { DirectionalSlide } from '../../utils/motions'; +import { DirectionalSlide } from '../../utils/calendarMotions'; import { AnimationDirection } from '../../Calendar'; const MONTHS_PER_ROW = 4; diff --git a/packages/react-components/react-calendar-compat/library/src/utils/motions.tsx b/packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx similarity index 100% rename from packages/react-components/react-calendar-compat/library/src/utils/motions.tsx rename to packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx From 834932130d0466f6bfefd3d67b547fdea15a97e9 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 15:23:18 +0000 Subject: [PATCH 12/33] chore: remove unused code --- .../library/src/components/CalendarDayGrid/CalendarGridRow.tsx | 3 --- .../CalendarDayGrid/useCalendarDayGridStyles.styles.ts | 1 - .../library/src/components/CalendarMonth/CalendarMonth.tsx | 2 -- 3 files changed, 6 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx index e8185ed88019a9..19094c629041c6 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx @@ -4,7 +4,6 @@ import { CalendarGridDayCell } from './CalendarGridDayCell'; import type { CalendarDayGridProps, CalendarDayGridStyles } from './CalendarDayGrid.types'; import type { DayInfo } from './CalendarDayGrid'; import type { WeekCorners } from './useWeekCornerStyles.styles'; -import { SlideUp } from '../../utils/motions'; /** * @internal @@ -53,7 +52,6 @@ export const CalendarGridRow: React.FunctionComponent = pr : ''; return ( - // {showWeekNumbers && weekNumbers && ( = pr ))} - // ); }; diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts index 9a6c0ee9324ffb..e9e7f61d123421 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts @@ -17,7 +17,6 @@ import { SLIDE_UP_OUT20, TRANSITION_ROW_DISAPPEARANCE, } from '../../utils'; -import { AnimationDirection } from '../Calendar/Calendar.types'; import { weekCornersClassNames } from './useWeekCornerStyles.styles'; import { createFocusOutlineStyle } from '@fluentui/react-tabster'; import type { SlotClassNames } from '@fluentui/react-utilities'; diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx index 7b7f5ae3bd7a1b..8e67b548c06c13 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx @@ -96,8 +96,6 @@ export const CalendarMonth: React.FunctionComponent = props const [isYearPickerVisible, setIsYearPickerVisible] = React.useState(false); const animateBackwards = useAnimateBackwards({ navigatedDate }); - // TODO: consider replacing SlideDown/SlideUp with a single motion component - // that receives animateBackwards in a prop, so the component type isn't changing back and forth. const selectMonthCallback = (newMonth: number): (() => void) => { return () => onSelectMonth(newMonth); From 07903c2915fab885714d156439f472d633e24b01 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 15:36:46 +0000 Subject: [PATCH 13/33] chore: uncomment CSS animation code --- .../useCalendarDayGridStyles.styles.ts | 36 +++++++++---------- .../useCalendarPickerStyles.styles.ts | 18 +++++----- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts index e9e7f61d123421..b05cb15b55e551 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts @@ -21,6 +21,7 @@ import { weekCornersClassNames } from './useWeekCornerStyles.styles'; import { createFocusOutlineStyle } from '@fluentui/react-tabster'; import type { SlotClassNames } from '@fluentui/react-utilities'; import type { CalendarDayGridStyles, CalendarDayGridStyleProps } from './CalendarDayGrid.types'; +import { AnimationDirection } from '../../Calendar'; /** * @internal @@ -317,7 +318,6 @@ const useFirstTransitionWeekStyles = makeStyles({ height: 0, opacity: 0, overflow: 'hidden', - position: 'absolute', width: 0, }, @@ -429,15 +429,15 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro weekRow: mergeClasses( calendarDayGridClassNames.weekRow, weekRowStyles.base, - // animateBackwards !== undefined && weekRowStyles.animation, - // animateBackwards !== undefined && - // (animationDirection === AnimationDirection.Horizontal - // ? animateBackwards - // ? weekRowStyles.horizontalBackward - // : weekRowStyles.horizontalForward - // : animateBackwards - // ? weekRowStyles.verticalBackward - // : weekRowStyles.verticalForward), + animateBackwards !== undefined && weekRowStyles.animation, + animateBackwards !== undefined && + (animationDirection === AnimationDirection.Horizontal + ? animateBackwards + ? weekRowStyles.horizontalBackward + : weekRowStyles.horizontalForward + : animateBackwards + ? weekRowStyles.verticalBackward + : weekRowStyles.verticalForward), ), weekDayLabelCell: mergeClasses(calendarDayGridClassNames.weekDayLabelCell, weekDayLabelCellStyles.base), weekNumberCell: mergeClasses(calendarDayGridClassNames.weekNumberCell, weekNumberCellStyles.base), @@ -451,18 +451,18 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro firstTransitionWeek: mergeClasses( calendarDayGridClassNames.firstTransitionWeek, firstTransitionWeekStyles.base, - // animateBackwards !== undefined && - // animationDirection !== AnimationDirection.Horizontal && - // !animateBackwards && - // firstTransitionWeekStyles.verticalForward, + animateBackwards !== undefined && + animationDirection !== AnimationDirection.Horizontal && + !animateBackwards && + firstTransitionWeekStyles.verticalForward, ), lastTransitionWeek: mergeClasses( calendarDayGridClassNames.lastTransitionWeek, lastTransitionWeekStyles.base, - // animateBackwards !== undefined && - // animationDirection !== AnimationDirection.Horizontal && - // animateBackwards && - // lastTransitionWeekStyles.verticalBackward, + animateBackwards !== undefined && + animationDirection !== AnimationDirection.Horizontal && + animateBackwards && + lastTransitionWeekStyles.verticalBackward, ), dayMarker: mergeClasses(calendarDayGridClassNames.dayMarker, dayMarkerStyles.base), dayTodayMarker: mergeClasses(calendarDayGridClassNames.dayTodayMarker, dayTodayMarkerStyles.base), diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts index 890b84395633d5..822dc35fc6f100 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts @@ -329,15 +329,15 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps buttonRow: mergeClasses( calendarPickerClassNames.buttonRow, buttonRowStyles.base, - // buttonRowStyles.animation, - // animateBackwards !== undefined && - // (animationDirection === AnimationDirection.Horizontal - // ? animateBackwards - // ? buttonRowStyles.horizontalBackward - // : buttonRowStyles.horizontalForward - // : animateBackwards - // ? buttonRowStyles.verticalBackward - // : buttonRowStyles.verticalForward), + buttonRowStyles.animation, + animateBackwards !== undefined && + (animationDirection === AnimationDirection.Horizontal + ? animateBackwards + ? buttonRowStyles.horizontalBackward + : buttonRowStyles.horizontalForward + : animateBackwards + ? buttonRowStyles.verticalBackward + : buttonRowStyles.verticalForward), ), itemButton: mergeClasses(calendarPickerClassNames.itemButton, itemButtonStyles.base), selected: mergeClasses(calendarPickerClassNames.selected, highlightSelected && selectedStyles.highlightSelected), From 9a9ed250ca56c350537d9bffd21fa5afa4d7f700 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 3 Nov 2025 19:07:25 +0000 Subject: [PATCH 14/33] chore(Calendar): change default animationDirection back to Vertical --- .../library/src/components/CalendarDay/CalendarDay.tsx | 2 +- .../library/src/components/CalendarDayGrid/CalendarDayGrid.tsx | 2 +- .../library/src/components/CalendarMonth/CalendarMonth.tsx | 2 +- .../components/CalendarPicker/useCalendarPickerStyles.styles.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx index 8a13838ce5b2eb..cce90fc28e6f72 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx @@ -41,7 +41,7 @@ export const CalendarDay: React.FunctionComponent = props => { onNavigateDate, showWeekNumbers, dateRangeType, - animationDirection = AnimationDirection.Horizontal, + animationDirection = AnimationDirection.Vertical, } = props; const classNames = useCalendarDayStyles_unstable({ diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx index f3bd0155f61d30..4a735d68c74749 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx @@ -133,7 +133,7 @@ export const CalendarDayGrid: React.FunctionComponent = pr showWeekNumbers, labelledBy, lightenDaysOutsideNavigatedMonth, - animationDirection = AnimationDirection.Horizontal, + animationDirection = AnimationDirection.Vertical, } = props; const classNames = useCalendarDayGridStyles_unstable({ diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx index 8e67b548c06c13..75a970313c5ec5 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx @@ -74,7 +74,7 @@ function useFocusLogic({ componentRef }: { componentRef: CalendarMonthProps['com export const CalendarMonth: React.FunctionComponent = props => { const { allFocusable, - animationDirection = AnimationDirection.Horizontal, + animationDirection = AnimationDirection.Vertical, className, componentRef, dateTimeFormatter = DEFAULT_DATE_FORMATTING, diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts index 822dc35fc6f100..dbd22b9667d2bc 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts @@ -304,7 +304,7 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps const { animateBackwards, - animationDirection = AnimationDirection.Horizontal, + animationDirection = AnimationDirection.Vertical, className, hasHeaderClickCallback, highlightCurrent, From 4b690adc49b24d104d18827ebe62b4f32db93048 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 00:04:40 +0000 Subject: [PATCH 15/33] docs(Calendar): add migration plan for transitioning to Fluent UI v9 motion components --- .../MOTION_MIGRATION_PLAN.md | 356 ++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md diff --git a/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md b/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md new file mode 100644 index 00000000000000..95007c4635b359 --- /dev/null +++ b/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md @@ -0,0 +1,356 @@ +# Calendar Motion Migration Plan + +## Overview + +This document outlines the plan to complete the migration of Calendar animations from CSS keyframe animations to Fluent UI v9 motion components. + +**Branch:** `refactor/calendar-slide` +**Started:** Several months ago +**Status:** In progress, with blocking issues + +--- + +## Current State + +### What's Done + +- [x] Created `DirectionalSlide` component in `calendarMotions.tsx` +- [x] Updated `EASING_FUNCTION_1` to use `motionTokens.curveDecelerateMax` +- [x] Wrapped `CalendarMonth` rows with `DirectionalSlide` +- [x] Wrapped `CalendarDayGrid` rows with `DirectionalSlide` +- [x] Fixed `PresenceComponent.In` and `.Out` types in `react-motion` +- [x] Documented duration constant mappings to motion tokens +- [x] Created beachball change files + +### What's Broken + +- [ ] **Critical:** Invalid DOM nesting (`
` inside ``) +- [ ] CSS animations still applied alongside motion components (double animation) +- [ ] Hardcoded duration instead of motion tokens + +--- + +## Blocking Issue: Invalid DOM Nesting + +### Problem + +The `Slide.In` motion component wraps its children in a `
` element. When used inside a ``, this creates invalid HTML: + +```html + + + ... + +
+ +
+ + ... + +
+
+ +``` + +### Error Message + +``` + cannot contain a nested
. +``` + +### Why This Matters + +- Browsers may render content incorrectly or unpredictably +- Accessibility tools may not interpret the structure correctly +- React logs errors in development mode +- Some browsers silently move misplaced elements, breaking layout + +--- + +## Solution Options + +### Option A: Restructure Calendar Grid to Use CSS Grid (Recommended) + +**Approach:** Replace `` with CSS Grid layout using `
` elements. + +**Pros:** + +- Full compatibility with motion components +- More flexible layout possibilities +- Modern approach used by other v9 components +- `transform` animations work reliably on `
` elements + +**Cons:** + +- Larger refactor +- May need to update accessibility attributes +- Potential visual regression testing needed + +**Implementation:** + +1. Replace `
` → `
` +2. Replace `
` → `
` (no role needed) +3. Replace `
` → `
` +4. Replace `
` elements +- Quick to implement + +**Cons:** + +- Inconsistent with v9 motion system +- No access to motion behavior context (e.g., reduced motion preferences handled differently) +- Doesn't advance the migration goal + +**Implementation:** + +1. Remove `DirectionalSlide` wrappers from `CalendarDayGrid.tsx` +2. Remove `DirectionalSlide` wrappers from `CalendarMonth.tsx` +3. Keep CSS animation classes applied via `useCalendarDayGridStyles_unstable` +4. Document why CSS is used for this specific case + +**Estimated effort:** 0.5 days + +--- + +### Option C: Apply Motion to Table Wrapper Instead of Rows + +**Approach:** Animate the entire table or tbody as a single unit when navigating. + +**Pros:** + +- Valid DOM structure +- Uses motion components + +**Cons:** + +- Different visual effect (whole grid slides vs. individual rows) +- May not match original design intent +- Could feel less refined + +**Implementation:** + +1. Move `DirectionalSlide` to wrap the entire `
` → `
` +5. Apply CSS Grid styles for layout +6. Wrap rows with `DirectionalSlide` + +**Estimated effort:** 2-3 days + +--- + +### Option B: Keep CSS Animations for Row Transitions + +**Approach:** Revert the row animation changes and keep using CSS `@keyframes` for table rows. + +**Pros:** + +- Minimal changes +- CSS animations work correctly on `
` element +2. Use key prop on wrapper to trigger animation on navigation +3. Remove row-level motion wrappers + +**Estimated effort:** 0.5 days + +--- + +### Option D: Create a Table-Compatible Motion Component + +**Approach:** Create a new motion component that doesn't wrap children in `
`. + +**Pros:** + +- Keeps table structure +- Uses WAAPI for animations + +**Cons:** + +- Requires changes to `react-motion` package +- Complex to implement correctly +- May have browser compatibility issues (transforms on `
`) + +**Implementation:** + +1. Investigate if `react-motion` can support a "no-wrapper" mode +2. Create `TableRowMotion` component that applies animation directly +3. Handle the fact that `` doesn't reliably support transforms in all browsers + +**Estimated effort:** 3-5 days (with research) + +--- + +## Recommended Approach + +**Option A (CSS Grid restructure)** is recommended for the following reasons: + +1. **Future-proof:** Aligns with modern layout practices +2. **Full motion support:** Works seamlessly with motion components +3. **Consistency:** Matches patterns used in other v9 components +4. **Flexibility:** Enables future enhancements to the calendar layout + +If time is constrained, **Option B (keep CSS)** is acceptable as a fallback, with documentation explaining the limitation. + +--- + +## Implementation Plan + +### Phase 1: Fix Blocking Issues (Priority: High) + +#### Task 1.1: Restructure CalendarDayGrid to CSS Grid + +- [ ] Create new styles using CSS Grid in `useCalendarDayGridStyles.styles.ts` +- [ ] Update `CalendarDayGrid.tsx` to use `
` with appropriate ARIA roles +- [ ] Update `CalendarGridRow.tsx` to render `
` instead of `
` +- [ ] Ensure keyboard navigation still works with `useArrowNavigationGroup` +- [ ] Verify visual appearance matches current implementation + +#### Task 1.2: Restructure CalendarMonth Grid + +- [ ] Update month button grid to use CSS Grid if using table structure +- [ ] Verify `DirectionalSlide` works correctly after restructure + +#### Task 1.3: Remove Duplicate CSS Animations + +- [ ] Remove CSS animation class applications from `useCalendarDayGridStyles_unstable` +- [ ] Clean up unused animation styles (or keep for fallback) +- [ ] Verify only motion component animations are running + +### Phase 2: Complete Token Migration (Priority: Medium) + +#### Task 2.1: Migrate Duration Constants + +- [ ] Replace hardcoded `367` in `DirectionalSlide` with appropriate motion token +- [ ] Decision: Use `motionTokens.durationSlower` (400ms) or create custom duration +- [ ] Update `animations.ts` to export token-based values for any remaining CSS usage + +#### Task 2.2: Migrate Remaining Easing Functions + +- [ ] Evaluate `EASING_FUNCTION_2` usage and migrate if applicable +- [ ] Document any intentional deviations from motion tokens + +### Phase 3: Testing & Validation (Priority: High) + +#### Task 3.1: Unit Tests + +- [ ] Verify all existing tests pass without DOM nesting warnings +- [ ] Add tests for animation direction (vertical/horizontal) +- [ ] Add tests for backwards animation + +#### Task 3.2: Visual Regression Tests + +- [ ] Run VR tests: `yarn nx run vr-tests-react-components:test` +- [ ] Review any visual differences +- [ ] Update snapshots if changes are intentional + +#### Task 3.3: Accessibility Testing + +- [ ] Test keyboard navigation after restructure +- [ ] Verify screen reader announces grid correctly +- [ ] Test with reduced motion preference enabled + +#### Task 3.4: Manual Testing + +- [ ] Test month navigation (left/right arrows) +- [ ] Test year navigation +- [ ] Test in different themes +- [ ] Test in RTL mode + +### Phase 4: Cleanup (Priority: Low) + +#### Task 4.1: Remove Dead Code + +- [ ] Remove unused CSS animation keyframes if fully migrated +- [ ] Remove unused style variants (e.g., `verticalForward` CSS classes) +- [ ] Clean up any temporary comments or TODOs + +#### Task 4.2: Documentation + +- [ ] Update component documentation if API changed +- [ ] Add inline comments explaining motion implementation +- [ ] Update change file description if needed + +--- + +## Files to Modify + +| File | Changes Needed | +| ------------------------------------ | ------------------------------------------------ | +| `CalendarDayGrid.tsx` | Restructure to CSS Grid, update JSX | +| `CalendarGridRow.tsx` | Change from `` to `
` | +| `useCalendarDayGridStyles.styles.ts` | Add CSS Grid styles, remove duplicate animations | +| `CalendarMonth.tsx` | Verify motion works after any shared changes | +| `calendarMotions.tsx` | Update duration to use motion tokens | +| `animations.ts` | Complete token migration or document exceptions | + +--- + +## Risk Mitigation + +### Visual Regression Risk + +- Run VR tests before and after each phase +- Keep screenshots of current behavior for comparison +- Have design review if significant visual changes occur + +### Accessibility Risk + +- Test with axe-core after restructure +- Verify ARIA roles are correctly applied +- Test keyboard navigation thoroughly + +### Performance Risk + +- Monitor bundle size changes +- Check animation performance on lower-end devices +- Ensure no unnecessary re-renders from key changes + +--- + +## Success Criteria + +1. ✅ No DOM nesting warnings in tests or console +2. ✅ Animations match original visual design +3. ✅ All existing unit tests pass +4. ✅ Visual regression tests pass (or changes approved) +5. ✅ Keyboard navigation works correctly +6. ✅ Screen reader announces calendar grid properly +7. ✅ Reduced motion preference is respected +8. ✅ Duration and easing use motion tokens (or documented exceptions) + +--- + +## Open Questions + +1. **Should we use `durationSlower` (400ms) or keep the original 367ms?** + + - Recommendation: Use token for consistency, the 33ms difference is imperceptible + +2. **Should the fade animation in CalendarDay also migrate to motion components?** + + - Currently only slide is migrated; fade uses CSS + +3. **Is the "first transition week" / "last transition week" animation still needed?** + + - These appear to be edge case animations; evaluate if they're necessary + +4. **Should we support a CSS fallback for browsers that don't support WAAPI?** + - Modern browsers all support it; evaluate based on browser support requirements + +--- + +## Timeline Estimate + +| Phase | Estimated Duration | +| --------------------------------- | ------------------ | +| Phase 1: Fix Blocking Issues | 2-3 days | +| Phase 2: Complete Token Migration | 0.5 days | +| Phase 3: Testing & Validation | 1-2 days | +| Phase 4: Cleanup | 0.5 days | +| **Total** | **4-6 days** | + +--- + +## References + +- [Fluent UI Motion Documentation](https://react.fluentui.dev/?path=/docs/motion-introduction--docs) +- [react-motion package](../../react-motion/) +- [react-motion-components-preview](../../react-motion-components-preview/) +- [CSS Grid Layout Guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout) +- [ARIA Grid Role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/grid_role) From 68356ff9ee07cb3ec1d7701c06dc2d2c43f8d79a Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 00:52:51 +0000 Subject: [PATCH 16/33] docs(react-calendar-compat): Update MOTION_MIGRATION_PLAN.md - Simplified document to reflect completed Phase 1 (slide animations) - Documented Phase 2 remaining work (CalendarPicker button rows and header fade animations) - Clarified migration status with table of completed vs remaining CSS animations - Updated file status table to show Phase 1 completion - Added specific migration approach for Phase 2 tasks --- .../MOTION_MIGRATION_PLAN.md | 389 ++++-------------- 1 file changed, 91 insertions(+), 298 deletions(-) diff --git a/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md b/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md index 95007c4635b359..cd5c40b5588cba 100644 --- a/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md +++ b/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md @@ -2,355 +2,148 @@ ## Overview -This document outlines the plan to complete the migration of Calendar animations from CSS keyframe animations to Fluent UI v9 motion components. +Migration of Calendar animations from CSS keyframe animations to Fluent UI v9 motion components. -**Branch:** `refactor/calendar-slide` -**Started:** Several months ago -**Status:** In progress, with blocking issues +**Last Updated:** January 2026 +**Status:** Phase 1 complete (slide animations migrated), Phase 2 in progress (remaining CSS animations) --- ## Current State -### What's Done +### Completed (Phase 1: Slide Animations) -- [x] Created `DirectionalSlide` component in `calendarMotions.tsx` -- [x] Updated `EASING_FUNCTION_1` to use `motionTokens.curveDecelerateMax` -- [x] Wrapped `CalendarMonth` rows with `DirectionalSlide` -- [x] Wrapped `CalendarDayGrid` rows with `DirectionalSlide` -- [x] Fixed `PresenceComponent.In` and `.Out` types in `react-motion` -- [x] Documented duration constant mappings to motion tokens -- [x] Created beachball change files +- ✅ Created `DirectionalSlide` component using `Slide.In` from `@fluentui/react-motion-components-preview` +- ✅ Migrated `CalendarDayGrid` row animations to motion components +- ✅ Migrated `CalendarMonth` row animations to motion components +- ✅ Updated `CalendarGridRow` to use `React.forwardRef` (required for motion ref) +- ✅ Using `motionTokens.durationSlower` (400ms) and `motionTokens.curveDecelerateMax` -### What's Broken +### Remaining CSS Animations (Phase 2) -- [ ] **Critical:** Invalid DOM nesting (`
` inside `
`) -- [ ] CSS animations still applied alongside motion components (double animation) -- [ ] Hardcoded duration instead of motion tokens +The following CSS animations in `animations.ts` are still actively used: ---- - -## Blocking Issue: Invalid DOM Nesting - -### Problem - -The `Slide.In` motion component wraps its children in a `
` element. When used inside a `
`, this creates invalid HTML: - -```html - - - ... - -
- -
-
- ... - - - - -``` - -### Error Message - -``` - cannot contain a nested
. -``` - -### Why This Matters - -- Browsers may render content incorrectly or unpredictably -- Accessibility tools may not interpret the structure correctly -- React logs errors in development mode -- Some browsers silently move misplaced elements, breaking layout - ---- - -## Solution Options - -### Option A: Restructure Calendar Grid to Use CSS Grid (Recommended) - -**Approach:** Replace `
` with CSS Grid layout using `
` elements. - -**Pros:** - -- Full compatibility with motion components -- More flexible layout possibilities -- Modern approach used by other v9 components -- `transform` animations work reliably on `
` elements - -**Cons:** - -- Larger refactor -- May need to update accessibility attributes -- Potential visual regression testing needed - -**Implementation:** - -1. Replace `
` → `
` -2. Replace `
` → `
` (no role needed) -3. Replace `
` → `
` -4. Replace `
` elements -- Quick to implement - -**Cons:** - -- Inconsistent with v9 motion system -- No access to motion behavior context (e.g., reduced motion preferences handled differently) -- Doesn't advance the migration goal - -**Implementation:** - -1. Remove `DirectionalSlide` wrappers from `CalendarDayGrid.tsx` -2. Remove `DirectionalSlide` wrappers from `CalendarMonth.tsx` -3. Keep CSS animation classes applied via `useCalendarDayGridStyles_unstable` -4. Document why CSS is used for this specific case - -**Estimated effort:** 0.5 days - ---- - -### Option C: Apply Motion to Table Wrapper Instead of Rows - -**Approach:** Animate the entire table or tbody as a single unit when navigating. - -**Pros:** - -- Valid DOM structure -- Uses motion components - -**Cons:** - -- Different visual effect (whole grid slides vs. individual rows) -- May not match original design intent -- Could feel less refined - -**Implementation:** +| Animation | Used In | Purpose | +| ------------------- | ------------------------------- | ------------------------------------ | +| `FADE_IN` | `CalendarDay`, `CalendarPicker` | Header/button fade on navigation | +| `SLIDE_*_IN20` | `CalendarPicker` | Button row slide (month/year picker) | +| `DURATION_2` | `CalendarDay`, `CalendarPicker` | Fade animation duration | +| `DURATION_3` | `CalendarPicker` | Slide animation duration | +| `EASING_FUNCTION_1` | `CalendarPicker` | Slide timing (migrated to token) | +| `EASING_FUNCTION_2` | `CalendarDay`, `CalendarPicker` | Fade timing (no exact token) | -1. Move `DirectionalSlide` to wrap the entire `
` → `
` -5. Apply CSS Grid styles for layout -6. Wrap rows with `DirectionalSlide` - -**Estimated effort:** 2-3 days - ---- - -### Option B: Keep CSS Animations for Row Transitions - -**Approach:** Revert the row animation changes and keep using CSS `@keyframes` for table rows. - -**Pros:** - -- Minimal changes -- CSS animations work correctly on `
` element -2. Use key prop on wrapper to trigger animation on navigation -3. Remove row-level motion wrappers +### Unused (Can Be Removed) -**Estimated effort:** 0.5 days +- `FADE_OUT`, `SLIDE_DOWN_OUT20`, `SLIDE_UP_OUT20`, `TRANSITION_ROW_DISAPPEARANCE` +- `DURATION_1`, `DURATION_4` --- -### Option D: Create a Table-Compatible Motion Component +## Phase 2: Remaining CSS Animation Migration -**Approach:** Create a new motion component that doesn't wrap children in `
`. +### Analysis -**Pros:** +There are two categories of remaining CSS animations: -- Keeps table structure -- Uses WAAPI for animations +#### 1. CalendarPicker Button Row Animations -**Cons:** +**Location:** `useCalendarPickerStyles.styles.ts` (lines 141-163) +**Current implementation:** CSS `@keyframes` with `FADE_IN` + `SLIDE_*_IN20` -- Requires changes to `react-motion` package -- Complex to implement correctly -- May have browser compatibility issues (transforms on `
`) +**Migration approach:** Wrap button rows with `DirectionalSlide` (same pattern as CalendarMonth) -**Implementation:** - -1. Investigate if `react-motion` can support a "no-wrapper" mode -2. Create `TableRowMotion` component that applies animation directly -3. Handle the fact that `` doesn't reliably support transforms in all browsers - -**Estimated effort:** 3-5 days (with research) - ---- - -## Recommended Approach - -**Option A (CSS Grid restructure)** is recommended for the following reasons: - -1. **Future-proof:** Aligns with modern layout practices -2. **Full motion support:** Works seamlessly with motion components -3. **Consistency:** Matches patterns used in other v9 components -4. **Flexibility:** Enables future enhancements to the calendar layout - -If time is constrained, **Option B (keep CSS)** is acceptable as a fallback, with documentation explaining the limitation. - ---- - -## Implementation Plan - -### Phase 1: Fix Blocking Issues (Priority: High) - -#### Task 1.1: Restructure CalendarDayGrid to CSS Grid - -- [ ] Create new styles using CSS Grid in `useCalendarDayGridStyles.styles.ts` -- [ ] Update `CalendarDayGrid.tsx` to use `
` with appropriate ARIA roles -- [ ] Update `CalendarGridRow.tsx` to render `
` instead of `
` -- [ ] Ensure keyboard navigation still works with `useArrowNavigationGroup` -- [ ] Verify visual appearance matches current implementation - -#### Task 1.2: Restructure CalendarMonth Grid - -- [ ] Update month button grid to use CSS Grid if using table structure -- [ ] Verify `DirectionalSlide` works correctly after restructure - -#### Task 1.3: Remove Duplicate CSS Animations - -- [ ] Remove CSS animation class applications from `useCalendarDayGridStyles_unstable` -- [ ] Clean up unused animation styles (or keep for fallback) -- [ ] Verify only motion component animations are running - -### Phase 2: Complete Token Migration (Priority: Medium) - -#### Task 2.1: Migrate Duration Constants - -- [ ] Replace hardcoded `367` in `DirectionalSlide` with appropriate motion token -- [ ] Decision: Use `motionTokens.durationSlower` (400ms) or create custom duration -- [ ] Update `animations.ts` to export token-based values for any remaining CSS usage - -#### Task 2.2: Migrate Remaining Easing Functions - -- [ ] Evaluate `EASING_FUNCTION_2` usage and migrate if applicable -- [ ] Document any intentional deviations from motion tokens - -### Phase 3: Testing & Validation (Priority: High) - -#### Task 3.1: Unit Tests - -- [ ] Verify all existing tests pass without DOM nesting warnings -- [ ] Add tests for animation direction (vertical/horizontal) -- [ ] Add tests for backwards animation - -#### Task 3.2: Visual Regression Tests - -- [ ] Run VR tests: `yarn nx run vr-tests-react-components:test` -- [ ] Review any visual differences -- [ ] Update snapshots if changes are intentional - -#### Task 3.3: Accessibility Testing - -- [ ] Test keyboard navigation after restructure -- [ ] Verify screen reader announces grid correctly -- [ ] Test with reduced motion preference enabled - -#### Task 3.4: Manual Testing - -- [ ] Test month navigation (left/right arrows) -- [ ] Test year navigation -- [ ] Test in different themes -- [ ] Test in RTL mode +```tsx +// CalendarMonth.tsx (line ~255) - already using this pattern: + +
+ {/* month buttons */} +
+
+``` -### Phase 4: Cleanup (Priority: Low) +**Files to modify:** -#### Task 4.1: Remove Dead Code +- `CalendarPicker.tsx` - Add `DirectionalSlide` wrapper around button rows +- `useCalendarPickerStyles.styles.ts` - Remove CSS animation styles from `buttonRow` -- [ ] Remove unused CSS animation keyframes if fully migrated -- [ ] Remove unused style variants (e.g., `verticalForward` CSS classes) -- [ ] Clean up any temporary comments or TODOs +**Complexity:** Low - follows existing pattern from CalendarMonth -#### Task 4.2: Documentation +#### 2. Header Fade Animations -- [ ] Update component documentation if API changed -- [ ] Add inline comments explaining motion implementation -- [ ] Update change file description if needed +**Location:** `useCalendarDayStyles.styles.ts` (line 67-71), `useCalendarPickerStyles.styles.ts` (line 72-76) +**Current implementation:** CSS `@keyframes` with `FADE_IN`, `DURATION_2`, `EASING_FUNCTION_2` ---- +**Migration approach:** Use `Fade.In` from `@fluentui/react-motion-components-preview` -## Files to Modify +```tsx +import { Fade } from '@fluentui/react-motion-components-preview'; -| File | Changes Needed | -| ------------------------------------ | ------------------------------------------------ | -| `CalendarDayGrid.tsx` | Restructure to CSS Grid, update JSX | -| `CalendarGridRow.tsx` | Change from `` to `
` | -| `useCalendarDayGridStyles.styles.ts` | Add CSS Grid styles, remove duplicate animations | -| `CalendarMonth.tsx` | Verify motion works after any shared changes | -| `calendarMotions.tsx` | Update duration to use motion tokens | -| `animations.ts` | Complete token migration or document exceptions | +// Wrap the header text that animates + + {yearString} +; +``` ---- +**Challenge:** `EASING_FUNCTION_2` (`cubic-bezier(.1,.25,.75,.9)`) has no exact motion token equivalent. Options: -## Risk Mitigation +1. Use closest token (`motionTokens.curveEasyEase` or similar) +2. Keep custom easing value (acceptable deviation) +3. Accept slight visual difference with standard token -### Visual Regression Risk +**Files to modify:** -- Run VR tests before and after each phase -- Keep screenshots of current behavior for comparison -- Have design review if significant visual changes occur +- `CalendarDay.tsx` - Wrap month/year header with `Fade.In` +- `CalendarPicker.tsx` - Wrap current item button text with `Fade.In` +- `useCalendarDayStyles.styles.ts` - Remove CSS animation styles +- `useCalendarPickerStyles.styles.ts` - Remove CSS animation styles -### Accessibility Risk +**Complexity:** Medium - needs state management to trigger fade on navigation -- Test with axe-core after restructure -- Verify ARIA roles are correctly applied -- Test keyboard navigation thoroughly +### Recommended Migration Order -### Performance Risk +1. **CalendarPicker button rows** (Low effort, high impact) -- Monitor bundle size changes -- Check animation performance on lower-end devices -- Ensure no unnecessary re-renders from key changes + - Same pattern already proven in CalendarMonth + - Removes `SLIDE_*_IN20` and `DURATION_3` usage ---- +2. **Header fade animations** (Medium effort) -## Success Criteria + - Requires adding state to track navigation changes + - May need to accept `EASING_FUNCTION_2` deviation -1. ✅ No DOM nesting warnings in tests or console -2. ✅ Animations match original visual design -3. ✅ All existing unit tests pass -4. ✅ Visual regression tests pass (or changes approved) -5. ✅ Keyboard navigation works correctly -6. ✅ Screen reader announces calendar grid properly -7. ✅ Reduced motion preference is respected -8. ✅ Duration and easing use motion tokens (or documented exceptions) +3. **Cleanup animations.ts** (After above complete) + - Remove all unused exports + - Consider removing file entirely if everything migrated --- -## Open Questions - -1. **Should we use `durationSlower` (400ms) or keep the original 367ms?** - - - Recommendation: Use token for consistency, the 33ms difference is imperceptible +## Validation Tasks -2. **Should the fade animation in CalendarDay also migrate to motion components?** - - - Currently only slide is migrated; fade uses CSS - -3. **Is the "first transition week" / "last transition week" animation still needed?** - - - These appear to be edge case animations; evaluate if they're necessary - -4. **Should we support a CSS fallback for browsers that don't support WAAPI?** - - Modern browsers all support it; evaluate based on browser support requirements +- [ ] Run VR tests: `yarn nx run vr-tests-react-components:test-vr` +- [ ] Test keyboard navigation +- [ ] Test reduced motion preference (`prefers-reduced-motion`) +- [ ] Cross-browser validation (Chrome, Firefox, Safari) --- -## Timeline Estimate - -| Phase | Estimated Duration | -| --------------------------------- | ------------------ | -| Phase 1: Fix Blocking Issues | 2-3 days | -| Phase 2: Complete Token Migration | 0.5 days | -| Phase 3: Testing & Validation | 1-2 days | -| Phase 4: Cleanup | 0.5 days | -| **Total** | **4-6 days** | +## Files Modified + +| File | Status | Changes | +| ------------------------------------ | ------ | ----------------------------------------------- | +| `CalendarDayGrid.tsx` | ✅ | `DirectionalSlide` wrappers for day rows | +| `CalendarGridRow.tsx` | ✅ | Added `React.forwardRef` | +| `CalendarMonth.tsx` | ✅ | `DirectionalSlide` wrappers for month rows | +| `calendarMotions.tsx` | ✅ | Created `DirectionalSlide` component | +| `useCalendarDayGridStyles.styles.ts` | ✅ | Removed CSS slide animations | +| `CalendarPicker.tsx` | ⏳ | Pending: Add `DirectionalSlide` for button rows | +| `useCalendarPickerStyles.styles.ts` | ⏳ | Pending: Remove CSS animations | +| `CalendarDay.tsx` | ⏳ | Pending: Add `Fade.In` for header | +| `useCalendarDayStyles.styles.ts` | ⏳ | Pending: Remove CSS fade animation | +| `animations.ts` | ⏳ | Pending: Remove unused exports | --- ## References - [Fluent UI Motion Documentation](https://react.fluentui.dev/?path=/docs/motion-introduction--docs) -- [react-motion package](../../react-motion/) - [react-motion-components-preview](../../react-motion-components-preview/) -- [CSS Grid Layout Guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout) -- [ARIA Grid Role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/grid_role) From beb9313420bef674edf64776327959f0bd121b9c Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 00:58:12 +0000 Subject: [PATCH 17/33] refactor(react-calendar-compat): Use motion tokens in DirectionalSlide - Use motionTokens.durationSlower (400ms) instead of hardcoded 367ms - Rename fromX/fromY props to outX/outY to match Slide.In API - Simplify distance calculation logic - Add JSDoc comment explaining duration token choice --- .../library/src/utils/calendarMotions.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx b/packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx index 27efbc7c83781b..aaf372dcbbe91f 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx +++ b/packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx @@ -11,20 +11,25 @@ export const DirectionalSlide: React.FC<{ animateBackwards?: boolean; children: JSXElement; }> = ({ - // The DURATION_3 constant is a string in seconds, but WAAPI expects a number in ms - duration = 367, + // Using durationSlower (400ms) as the closest token to the original 367ms + duration = motionTokens.durationSlower, easing = motionTokens.curveDecelerateMax, animationDirection = AnimationDirection.Vertical, animateBackwards = false, children, }) => { - let fromX = '0px'; - let fromY = '0px'; + let outX = '0px'; + let outY = '0px'; + const distance = animateBackwards ? '-20px' : '20px'; if (animationDirection === AnimationDirection.Horizontal) { - fromX = animateBackwards ? '-20px' : '20px'; + outX = distance; } else { // default to vertical - fromY = animateBackwards ? '-20px' : '20px'; + outY = distance; } - return {children}; + return ( + + {children} + + ); }; From bf37628ced8a99dc5071a9977a904bf38c2081de Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:13:43 +0000 Subject: [PATCH 18/33] refactor(react-calendar-compat): Convert CalendarGridRow to forwardRef - Change from FunctionComponent to forwardRef to enable ref support - Required for motion components to properly animate table rows - Add displayName for better debugging --- .../src/components/CalendarDayGrid/CalendarGridRow.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx index 19094c629041c6..9aa2dddd80c53b 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx @@ -28,7 +28,7 @@ export interface CalendarGridRowProps extends CalendarDayGridProps { /** * @internal */ -export const CalendarGridRow: React.FunctionComponent = props => { +export const CalendarGridRow = React.forwardRef((props, ref) => { const { ariaHidden, classNames, @@ -52,7 +52,7 @@ export const CalendarGridRow: React.FunctionComponent = pr : ''; return ( -
+ {showWeekNumbers && weekNumbers && ( ); -}; +}); + +CalendarGridRow.displayName = 'CalendarGridRow'; From 9ce8066604926fae67d54f46bdb88c9987030f79 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:14:00 +0000 Subject: [PATCH 19/33] refactor(react-calendar-compat): Remove wrapper divs from DirectionalSlide - Remove unnecessary
wrappers inside DirectionalSlide components - CalendarGridRow now accepts refs directly via forwardRef - Fixes invalid DOM nesting (div inside tbody) - Maintains slide animation functionality --- .../CalendarDayGrid/CalendarDayGrid.tsx | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx index 4a735d68c74749..8eb46efb90a9f0 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarDayGrid.tsx @@ -175,44 +175,38 @@ export const CalendarDayGrid: React.FunctionComponent = pr
-
- -
+
{weeks!.slice(1, weeks!.length - 1).map((week: DayInfo[], weekIndex: number) => ( -
- -
-
- ))} - -
-
+
+ ))} + +
= pr ))}
From f6777052843ddc8482cbff1053299edf353e7e86 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:14:20 +0000 Subject: [PATCH 20/33] refactor(react-calendar-compat): Remove CSS slide animations from CalendarDayGrid - Remove CSS keyframe animations for row sliding (now handled by DirectionalSlide) - Remove unused imports (SLIDE_*, DURATION_3, EASING_FUNCTION_1, TRANSITION_ROW_DISAPPEARANCE) - Remove animation-related style classes from weekRow, firstTransitionWeek, lastTransitionWeek - Remove animateBackwards and animationDirection logic from style application - Add comments explaining migration to motion components - Keeps FADE_IN, DURATION_2, EASING_FUNCTION_2 for header animations (not yet migrated) --- .../useCalendarDayGridStyles.styles.ts | 74 +++---------------- 1 file changed, 11 insertions(+), 63 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts index b05cb15b55e551..81f70e792a9a30 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/useCalendarDayGridStyles.styles.ts @@ -2,26 +2,13 @@ import { tokens } from '@fluentui/react-theme'; import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; -import { - DURATION_2, - DURATION_3, - EASING_FUNCTION_1, - EASING_FUNCTION_2, - FADE_IN, - FADE_OUT, - SLIDE_DOWN_IN20, - SLIDE_DOWN_OUT20, - SLIDE_LEFT_IN20, - SLIDE_RIGHT_IN20, - SLIDE_UP_IN20, - SLIDE_UP_OUT20, - TRANSITION_ROW_DISAPPEARANCE, -} from '../../utils'; +import { DURATION_2, EASING_FUNCTION_2, FADE_IN } from '../../utils'; import { weekCornersClassNames } from './useWeekCornerStyles.styles'; import { createFocusOutlineStyle } from '@fluentui/react-tabster'; import type { SlotClassNames } from '@fluentui/react-utilities'; import type { CalendarDayGridStyles, CalendarDayGridStyleProps } from './CalendarDayGrid.types'; -import { AnimationDirection } from '../../Calendar'; + +// Note: DURATION_3, EASING_FUNCTION_1, and SLIDE_* animations removed - now handled by motion components (DirectionalSlide) /** * @internal @@ -180,23 +167,7 @@ const useWeekRowStyles = makeStyles({ zIndex: 1, }, }, - animation: { - animationDuration: DURATION_3, - animationFillMode: 'both', - animationTimingFunction: EASING_FUNCTION_1, - }, - horizontalBackward: { - animationName: [FADE_IN, SLIDE_RIGHT_IN20], - }, - horizontalForward: { - animationName: [FADE_IN, SLIDE_LEFT_IN20], - }, - verticalBackward: { - animationName: [FADE_IN, SLIDE_DOWN_IN20], - }, - verticalForward: { - animationName: [FADE_IN, SLIDE_UP_IN20], - }, + // CSS slide animations removed - now handled by motion components (DirectionalSlide) }); const useWeekDayLabelCellStyles = makeStyles({ @@ -321,12 +292,7 @@ const useFirstTransitionWeekStyles = makeStyles({ position: 'absolute', width: 0, }, - verticalForward: { - animationDuration: DURATION_3, - animationFillMode: 'both', - animationName: [FADE_OUT, SLIDE_UP_OUT20, TRANSITION_ROW_DISAPPEARANCE], - animationTimingFunction: EASING_FUNCTION_1, - }, + // CSS animations removed - now handled by motion components (DirectionalSlide) }); const useLastTransitionWeekStyles = makeStyles({ @@ -338,12 +304,7 @@ const useLastTransitionWeekStyles = makeStyles({ position: 'absolute', width: 0, }, - verticalBackward: { - animationDuration: DURATION_3, - animationFillMode: 'both', - animationName: [FADE_OUT, SLIDE_DOWN_OUT20, TRANSITION_ROW_DISAPPEARANCE], - animationTimingFunction: EASING_FUNCTION_1, - }, + // CSS animations removed - now handled by motion components (DirectionalSlide) }); const useDayMarkerStyles = makeStyles({ @@ -409,7 +370,8 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro const cornerBorderAndRadiusStyles = useCornerBorderAndRadiusStyles(); const dayTodayMarkerStyles = useDayTodayMarkerStyles(); - const { animateBackwards, animationDirection, lightenDaysOutsideNavigatedMonth, showWeekNumbers } = props; + // Note: animateBackwards and animationDirection no longer used here - handled by motion components + const { lightenDaysOutsideNavigatedMonth, showWeekNumbers } = props; return { wrapper: mergeClasses(calendarDayGridClassNames.wrapper, wrapperStyles.base), @@ -429,15 +391,7 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro weekRow: mergeClasses( calendarDayGridClassNames.weekRow, weekRowStyles.base, - animateBackwards !== undefined && weekRowStyles.animation, - animateBackwards !== undefined && - (animationDirection === AnimationDirection.Horizontal - ? animateBackwards - ? weekRowStyles.horizontalBackward - : weekRowStyles.horizontalForward - : animateBackwards - ? weekRowStyles.verticalBackward - : weekRowStyles.verticalForward), + // CSS animations removed - now handled by motion components (DirectionalSlide) ), weekDayLabelCell: mergeClasses(calendarDayGridClassNames.weekDayLabelCell, weekDayLabelCellStyles.base), weekNumberCell: mergeClasses(calendarDayGridClassNames.weekNumberCell, weekNumberCellStyles.base), @@ -451,18 +405,12 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro firstTransitionWeek: mergeClasses( calendarDayGridClassNames.firstTransitionWeek, firstTransitionWeekStyles.base, - animateBackwards !== undefined && - animationDirection !== AnimationDirection.Horizontal && - !animateBackwards && - firstTransitionWeekStyles.verticalForward, + // CSS animations removed - now handled by motion components (DirectionalSlide) ), lastTransitionWeek: mergeClasses( calendarDayGridClassNames.lastTransitionWeek, lastTransitionWeekStyles.base, - animateBackwards !== undefined && - animationDirection !== AnimationDirection.Horizontal && - animateBackwards && - lastTransitionWeekStyles.verticalBackward, + // CSS animations removed - now handled by motion components (DirectionalSlide) ), dayMarker: mergeClasses(calendarDayGridClassNames.dayMarker, dayMarkerStyles.base), dayTodayMarker: mergeClasses(calendarDayGridClassNames.dayTodayMarker, dayTodayMarkerStyles.base), From 14b0eef746a0b93305b6cdad8eef7932850ccc1a Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:14:40 +0000 Subject: [PATCH 21/33] refactor(react-calendar-compat): Update animation duration constants - Convert DURATION_* from string seconds to millisecond numbers using motion tokens - Update comments to clarify which animations still use CSS vs motion components - Document that DURATION_3 is no longer used (slide animations now use motion) - Note that EASING_FUNCTION_2 has no exact motion token equivalent --- .../library/src/utils/animations.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/utils/animations.ts b/packages/react-components/react-calendar-compat/library/src/utils/animations.ts index 1672f54f074f53..371c40d64a2ee7 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/animations.ts +++ b/packages/react-components/react-calendar-compat/library/src/utils/animations.ts @@ -1,17 +1,16 @@ import { motionTokens } from '@fluentui/react-motion'; export const EASING_FUNCTION_1 = motionTokens.curveDecelerateMax; -// This is nearly linear easing +// This is nearly linear easing - no exact motion token equivalent export const EASING_FUNCTION_2 = 'cubic-bezier(.1,.25,.75,.9)'; -// TODO: migrate to the closest duration tokens -// These constants are strings in seconds for CSS, but WAAPI expects numbers in milliseconds -// DURATION_2 is used in CalendarDay for the fade animation -// DURATION_3 is used in CalendarDayGrid for the slide animation -// DURATION_1 and DURATION_4 are not currently used -export const DURATION_1 = '0.167s'; // motionTokens.durationFast = 150 -export const DURATION_2 = '0.267s'; // motionTokens.durationGentle = 250 -export const DURATION_3 = '0.367s'; // motionTokens.durationSlower = 400 -export const DURATION_4 = '0.467s'; // motionTokens.durationUltraSlow = 500 + +// Duration constants for CSS animations +// DURATION_2 is used in CalendarDay/CalendarPicker for the fade animation (CSS) +// DURATION_3 is no longer used - slide animations now use motion components directly +export const DURATION_1 = `${motionTokens.durationFast}ms`; +export const DURATION_2 = `${motionTokens.durationGentle}ms`; +export const DURATION_3 = `${motionTokens.durationSlower}ms`; +export const DURATION_4 = `${motionTokens.durationUltraSlow}ms`; export const FADE_IN = { from: { From 113312970e23d79ffb39744c6824dbc48168e05f Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:48:39 +0000 Subject: [PATCH 22/33] deps(react-calendar-compat): Add react-motion dependencies - Add @fluentui/react-motion ^9.11.5 - Add @fluentui/react-motion-components-preview ^0.14.2 - Required for DirectionalSlide motion component --- .../library/package.json | 2 ++ yarn.lock | 32 ++----------------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/package.json b/packages/react-components/react-calendar-compat/library/package.json index d4240ac0feb0e5..0ec18e8ffa2722 100644 --- a/packages/react-components/react-calendar-compat/library/package.json +++ b/packages/react-components/react-calendar-compat/library/package.json @@ -15,6 +15,8 @@ "@fluentui/keyboard-keys": "^9.0.8", "@fluentui/react-icons": "^2.0.245", "@fluentui/react-jsx-runtime": "^9.3.4", + "@fluentui/react-motion": "^9.11.5", + "@fluentui/react-motion-components-preview": "^0.14.2", "@fluentui/react-shared-contexts": "^9.26.0", "@fluentui/react-tabster": "^9.26.11", "@fluentui/react-theme": "^9.2.0", diff --git a/yarn.lock b/yarn.lock index c7ad81f786ea91..f8a9b4091e368e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18683,7 +18683,7 @@ string-length@^6.0.0: dependencies: strip-ansi "^7.1.0" -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -18719,15 +18719,6 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -18829,8 +18820,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - name strip-ansi-cjs +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -18865,13 +18855,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -20871,7 +20854,7 @@ workspace-tools@^0.27.0: js-yaml "^4.1.0" micromatch "^4.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -20907,15 +20890,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 1d8303256b28bd97af66ab026d2f4efd15bb76b1 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:48:56 +0000 Subject: [PATCH 23/33] refactor(react-calendar-compat): Add 'use client' directive to CalendarGridRow Required for server-side rendering compatibility with React.forwardRef --- .../library/src/components/CalendarDayGrid/CalendarGridRow.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx index 9aa2dddd80c53b..3f4daba5f849f4 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarGridRow.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import { getWeekNumbersInMonth } from '../../utils'; import { CalendarGridDayCell } from './CalendarGridDayCell'; From 7d20000d032c22f6a1bb798d065d318ee329fc3f Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:49:09 +0000 Subject: [PATCH 24/33] feat(react-calendar-compat): Add DirectionalSlide to CalendarYear grid rows - Wrap year picker rows with DirectionalSlide motion component - Consistent animation behavior with CalendarMonth button rows - Part of Phase 2 motion migration --- .../src/components/CalendarYear/CalendarYear.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarYear/CalendarYear.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarYear/CalendarYear.tsx index da9fb0b5705b37..e7703ffd7abf9c 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarYear/CalendarYear.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarYear/CalendarYear.tsx @@ -5,6 +5,7 @@ import { Enter, Space } from '@fluentui/keyboard-keys'; import { useArrowNavigationGroup } from '@fluentui/react-tabster'; import { mergeClasses } from '@griffel/react'; import { useCalendarYearStyles_unstable } from './useCalendarYearStyles.styles'; +import { DirectionalSlide } from '../../utils/calendarMotions'; import type { CalendarYearStrings, CalendarYearProps, @@ -176,10 +177,13 @@ const CalendarYearGrid: React.FunctionComponent = props = return (
{cells.map((cellRow: React.ReactNode[], index: number) => { + const rowKey = 'yearPickerRow_' + index + '_' + fromYear; return ( -
- {cellRow} -
+ +
+ {cellRow} +
+
); })}
From 358fb4c87178718294203fb357f8a99015a208f9 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:49:18 +0000 Subject: [PATCH 25/33] refactor(react-calendar-compat): Remove CSS slide animations from CalendarPicker - Remove CSS keyframe animations for button row sliding - Now handled by DirectionalSlide motion component in CalendarMonth/CalendarYear - Remove unused imports (SLIDE_*, DURATION_3, EASING_FUNCTION_1) - Keep FADE_IN, DURATION_2, EASING_FUNCTION_2 for header fade animation --- .../useCalendarPickerStyles.styles.ts | 53 +++---------------- 1 file changed, 7 insertions(+), 46 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts index dbd22b9667d2bc..c740e4b726488c 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts @@ -2,21 +2,12 @@ import { tokens } from '@fluentui/react-theme'; import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; -import { - DURATION_2, - DURATION_3, - EASING_FUNCTION_1, - EASING_FUNCTION_2, - FADE_IN, - SLIDE_DOWN_IN20, - SLIDE_LEFT_IN20, - SLIDE_RIGHT_IN20, - SLIDE_UP_IN20, -} from '../../utils/animations'; -import { AnimationDirection } from '../Calendar/Calendar.types'; +import { DURATION_2, EASING_FUNCTION_2, FADE_IN } from '../../utils/animations'; import type { SlotClassNames } from '@fluentui/react-utilities'; import type { CalendarPickerStyles, CalendarPickerStyleProps } from './CalendarPicker.types'; +// Note: DURATION_3, EASING_FUNCTION_1, and SLIDE_* animations removed - now handled by motion components (DirectionalSlide) + /** * @internal */ @@ -145,23 +136,7 @@ const useButtonRowStyles = makeStyles({ marginBottom: 0, }, }, - animation: { - animationDuration: DURATION_3, - animationFillMode: 'both', - animationTimingFunction: EASING_FUNCTION_1, - }, - horizontalBackward: { - animationName: [FADE_IN, SLIDE_RIGHT_IN20], - }, - horizontalForward: { - animationName: [FADE_IN, SLIDE_LEFT_IN20], - }, - verticalBackward: { - animationName: [FADE_IN, SLIDE_DOWN_IN20], - }, - verticalForward: { - animationName: [FADE_IN, SLIDE_UP_IN20], - }, + // CSS slide animations removed - now handled by motion components (DirectionalSlide) }); const useItemButtonStyles = makeStyles({ @@ -302,14 +277,8 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps const selectedStyles = useSelectedStyles(); const disabledStyles = useDisabledStyles(); - const { - animateBackwards, - animationDirection = AnimationDirection.Vertical, - className, - hasHeaderClickCallback, - highlightCurrent, - highlightSelected, - } = props; + // Note: animateBackwards and animationDirection no longer used for buttonRow - handled by motion components + const { animateBackwards, className, hasHeaderClickCallback, highlightCurrent, highlightSelected } = props; return { root: mergeClasses(calendarPickerClassNames.root, rootStyles.normalize, rootStyles.base, className), @@ -329,15 +298,7 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps buttonRow: mergeClasses( calendarPickerClassNames.buttonRow, buttonRowStyles.base, - buttonRowStyles.animation, - animateBackwards !== undefined && - (animationDirection === AnimationDirection.Horizontal - ? animateBackwards - ? buttonRowStyles.horizontalBackward - : buttonRowStyles.horizontalForward - : animateBackwards - ? buttonRowStyles.verticalBackward - : buttonRowStyles.verticalForward), + // CSS animations removed - now handled by motion components (DirectionalSlide) ), itemButton: mergeClasses(calendarPickerClassNames.itemButton, itemButtonStyles.base), selected: mergeClasses(calendarPickerClassNames.selected, highlightSelected && selectedStyles.highlightSelected), From 7628ef8988d06e8121e2b9d0de95728504215af1 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:49:26 +0000 Subject: [PATCH 26/33] refactor(react-calendar-compat): Mark unused animation constants as deprecated - Reorganize animations.ts with used vs deprecated sections - Add @deprecated JSDoc tags to unused animation constants - Add eslint-disable for deprecated re-exports in index.ts - Keep FADE_IN, DURATION_2, EASING_FUNCTION_2 (still used for header fade) - Maintain backwards compatibility for external consumers --- .../library/src/utils/animations.ts | 44 ++++++++++++++----- .../library/src/utils/index.ts | 2 + 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/utils/animations.ts b/packages/react-components/react-calendar-compat/library/src/utils/animations.ts index 371c40d64a2ee7..f6b6baf122b084 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/animations.ts +++ b/packages/react-components/react-calendar-compat/library/src/utils/animations.ts @@ -1,17 +1,9 @@ import { motionTokens } from '@fluentui/react-motion'; -export const EASING_FUNCTION_1 = motionTokens.curveDecelerateMax; -// This is nearly linear easing - no exact motion token equivalent -export const EASING_FUNCTION_2 = 'cubic-bezier(.1,.25,.75,.9)'; - -// Duration constants for CSS animations -// DURATION_2 is used in CalendarDay/CalendarPicker for the fade animation (CSS) -// DURATION_3 is no longer used - slide animations now use motion components directly -export const DURATION_1 = `${motionTokens.durationFast}ms`; +// === USED CONSTANTS === +// Used in header fade animations (CalendarDay, CalendarPicker currentItemButton) +export const EASING_FUNCTION_2 = 'cubic-bezier(.1,.25,.75,.9)'; // No exact motion token equivalent export const DURATION_2 = `${motionTokens.durationGentle}ms`; -export const DURATION_3 = `${motionTokens.durationSlower}ms`; -export const DURATION_4 = `${motionTokens.durationUltraSlow}ms`; - export const FADE_IN = { from: { opacity: 0, @@ -20,6 +12,22 @@ export const FADE_IN = { opacity: 1, }, }; + +// === DEPRECATED - Kept for backward compatibility === +// The following constants are no longer used internally (slide animations now use motion components) +// but are kept as exports in case external consumers depend on them + +/** @deprecated Slide animations now use motion components. Use motionTokens.curveDecelerateMax instead. */ +export const EASING_FUNCTION_1 = motionTokens.curveDecelerateMax; + +/** @deprecated No longer used internally. */ +export const DURATION_1 = `${motionTokens.durationFast}ms`; +/** @deprecated Slide animations now use motion components with motionTokens.durationSlower. */ +export const DURATION_3 = `${motionTokens.durationSlower}ms`; +/** @deprecated No longer used internally. */ +export const DURATION_4 = `${motionTokens.durationUltraSlow}ms`; + +/** @deprecated Slide animations now use motion components. */ export const FADE_OUT = { from: { opacity: 1, @@ -29,6 +37,8 @@ export const FADE_OUT = { visibility: 'hidden' as const, }, }; + +/** @deprecated Slide animations now use DirectionalSlide motion component. */ export const SLIDE_DOWN_IN20 = { from: { pointerEvents: 'none' as const, @@ -39,6 +49,8 @@ export const SLIDE_DOWN_IN20 = { transform: 'translate3d(0, 0, 0)', }, }; + +/** @deprecated Slide animations now use DirectionalSlide motion component. */ export const SLIDE_LEFT_IN20 = { from: { pointerEvents: 'none' as const, @@ -49,6 +61,8 @@ export const SLIDE_LEFT_IN20 = { transform: 'translate3d(0, 0, 0)', }, }; + +/** @deprecated Slide animations now use DirectionalSlide motion component. */ export const SLIDE_RIGHT_IN20 = { from: { pointerEvents: 'none' as const, @@ -59,6 +73,8 @@ export const SLIDE_RIGHT_IN20 = { transform: 'translate3d(0, 0, 0)', }, }; + +/** @deprecated Slide animations now use DirectionalSlide motion component. */ export const SLIDE_UP_IN20 = { from: { pointerEvents: 'none' as const, @@ -69,6 +85,8 @@ export const SLIDE_UP_IN20 = { transform: 'translate3d(0, 0, 0)', }, }; + +/** @deprecated Slide animations now use DirectionalSlide motion component. */ export const SLIDE_DOWN_OUT20 = { from: { transform: 'translate3d(0, 0, 0)', @@ -77,6 +95,8 @@ export const SLIDE_DOWN_OUT20 = { transform: 'translate3d(0, 20px, 0)', }, }; + +/** @deprecated Slide animations now use DirectionalSlide motion component. */ export const SLIDE_UP_OUT20 = { from: { transform: 'translate3d(0, 0, 0)', @@ -86,11 +106,11 @@ export const SLIDE_UP_OUT20 = { }, }; +/** @deprecated No longer used internally. */ export const TRANSITION_ROW_DISAPPEARANCE = { '100%': { height: '0px', overflow: 'hidden', - width: '0px', }, '99.9%': { diff --git a/packages/react-components/react-calendar-compat/library/src/utils/index.ts b/packages/react-components/react-calendar-compat/library/src/utils/index.ts index 2cdf76e5e0414c..d021332767de7e 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/index.ts +++ b/packages/react-components/react-calendar-compat/library/src/utils/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-deprecated -- Re-exporting deprecated animations for backwards compatibility */ export { DURATION_1, DURATION_2, @@ -15,6 +16,7 @@ export { SLIDE_UP_OUT20, TRANSITION_ROW_DISAPPEARANCE, } from './animations'; +/* eslint-enable @typescript-eslint/no-deprecated */ export { DAYS_IN_WEEK, DateRangeType, DayOfWeek, FirstWeekOfYear, MonthOfYear, TimeConstants } from './constants'; export type { CalendarStrings, DateFormatting, DateGridStrings } from './dateFormatting'; export { From 0d872070fde007e8d806ea85a8cca34b76c1fe99 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:00 +0000 Subject: [PATCH 27/33] feat(react-calendar-compat): add HeaderFade motion component Add HeaderFade component that wraps Fade.In from react-motion-components-preview. The component uses navigationKey prop to trigger fade animation when the value changes. - Uses motionTokens.durationGentle (~250ms) matching original DURATION_2 (267ms) - Replaces CSS keyframe fade animations with motion component --- .../library/src/utils/calendarMotions.tsx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx b/packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx index aaf372dcbbe91f..7b87e2d006b2d7 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx +++ b/packages/react-components/react-calendar-compat/library/src/utils/calendarMotions.tsx @@ -1,5 +1,5 @@ import { motionTokens } from '@fluentui/react-motion'; -import { Slide } from '@fluentui/react-motion-components-preview'; +import { Fade, Slide } from '@fluentui/react-motion-components-preview'; import * as React from 'react'; import { AnimationDirection } from '../Calendar'; import { JSXElement } from '@fluentui/react-utilities'; @@ -33,3 +33,23 @@ export const DirectionalSlide: React.FC<{ ); }; + +/** + * A wrapper component that fades in its children when the navigationKey changes. + * Used for header text that should fade when navigating between months/years. + * + * Note: Using motionTokens.durationGentle (250ms) which closely matches DURATION_2 (267ms). + * The original EASING_FUNCTION_2 (cubic-bezier(.1,.25,.75,.9)) has no exact token equivalent, + * using the default motion easing which provides a similar smooth fade effect. + */ +export const HeaderFade: React.FC<{ + /** Key that changes when navigation occurs, triggering the fade animation */ + navigationKey: string | number; + children: JSXElement; +}> = ({ navigationKey, children }) => { + return ( + + {children} + + ); +}; From 151f592ad62107e2f7fb2207bee40cb46c1da4d7 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:12 +0000 Subject: [PATCH 28/33] feat(react-calendar-compat): migrate CalendarDay header to HeaderFade Replace CSS fade animation on month/year header with HeaderFade motion component. - Add HeaderFade wrapper around monthAndYear span in CalendarDay.tsx - Remove CSS animation imports and styles from useCalendarDayStyles.styles.ts --- .../src/components/CalendarDay/CalendarDay.tsx | 9 ++++++--- .../CalendarDay/useCalendarDayStyles.styles.ts | 12 ++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx index cce90fc28e6f72..b02f2c20c0095a 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/CalendarDay.tsx @@ -6,6 +6,7 @@ import { mergeClasses } from '@griffel/react'; import { addMonths, compareDatePart, getMonthEnd, getMonthStart } from '../../utils'; import { CalendarDayGrid } from '../CalendarDayGrid/CalendarDayGrid'; import { useCalendarDayStyles_unstable } from './useCalendarDayStyles.styles'; +import { HeaderFade } from '../../utils/calendarMotions'; import type { ICalendarDayGrid } from '../CalendarDayGrid/CalendarDayGrid.types'; import type { CalendarDayProps, CalendarDayStyles } from './CalendarDay.types'; import type { JSXElement } from '@fluentui/react-utilities'; @@ -70,9 +71,11 @@ export const CalendarDay: React.FunctionComponent = props => { onKeyDown={onButtonKeyDown(onHeaderSelect)} type="button" > - - {monthAndYear} - + + + {monthAndYear} + +
diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/useCalendarDayStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/useCalendarDayStyles.styles.ts index 192901462de3d9..6d2c284988c87a 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/useCalendarDayStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDay/useCalendarDayStyles.styles.ts @@ -2,10 +2,11 @@ import { tokens } from '@fluentui/react-theme'; import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; -import { DURATION_2, EASING_FUNCTION_2, FADE_IN } from '../../utils/animations'; import type { SlotClassNames } from '@fluentui/react-utilities'; import type { CalendarDayStyles, CalendarDayStyleProps } from './CalendarDay.types'; +// Note: FADE_IN, DURATION_2, EASING_FUNCTION_2 animations removed - now handled by HeaderFade motion component + /** * @internal */ @@ -64,12 +65,7 @@ const useMonthAndYearStyles = makeStyles({ textOverflow: 'ellipsis', whiteSpace: 'nowrap', }, - animation: { - animationDuration: DURATION_2, - animationFillMode: 'both', - animationName: FADE_IN, - animationTimingFunction: EASING_FUNCTION_2, - }, + // CSS animation removed - now handled by HeaderFade motion component headerIsClickable: { '&:hover': { backgroundColor: tokens.colorBrandBackgroundInvertedHover, @@ -166,7 +162,7 @@ export const useCalendarDayStyles_unstable = (props: CalendarDayStyleProps): Cal monthAndYear: mergeClasses( calendarDayClassNames.monthAndYear, monthAndYearStyles.base, - monthAndYearStyles.animation, + // CSS animation removed - now handled by HeaderFade motion component headerIsClickable && monthAndYearStyles.headerIsClickable, ), monthComponents: mergeClasses(calendarDayClassNames.monthComponents, monthComponentsStyles.base), From 206ce85e7bb8aa890da941711aceb0ad5021437a Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:19 +0000 Subject: [PATCH 29/33] feat(react-calendar-compat): migrate CalendarMonth header to HeaderFade Replace CSS fade animation on year string header with HeaderFade motion component. - Add HeaderFade wrapper around yearString span in CalendarMonth.tsx --- .../src/components/CalendarMonth/CalendarMonth.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx index 75a970313c5ec5..2cf7b58dbecacc 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx @@ -18,7 +18,7 @@ import { CalendarYear } from '../CalendarYear/CalendarYear'; import { useCalendarMonthStyles_unstable } from './useCalendarMonthStyles.styles'; import type { CalendarMonthProps } from './CalendarMonth.types'; import type { CalendarYearRange, ICalendarYear } from '../CalendarYear/CalendarYear.types'; -import { DirectionalSlide } from '../../utils/calendarMotions'; +import { DirectionalSlide, HeaderFade } from '../../utils/calendarMotions'; import { AnimationDirection } from '../../Calendar'; const MONTHS_PER_ROW = 4; @@ -212,9 +212,11 @@ export const CalendarMonth: React.FunctionComponent = props tabIndex={!!onUserHeaderSelect || !yearPickerHidden ? 0 : -1} type="button" > - - {yearString} - + + + {yearString} + +
); } return (
- {onRenderYear(fromYear)} - {onRenderYear(toYear)} + + + {onRenderYear(fromYear)} - {onRenderYear(toYear)} + +
); }; From 190ddb9c03ccf9f30d2f43f796c081fa9a4dc9d4 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:35 +0000 Subject: [PATCH 31/33] refactor(react-calendar-compat): remove CSS fade animation from CalendarPicker styles Remove CSS fade animation from useCalendarPickerStyles.styles.ts now that header fade is handled by HeaderFade motion component. - Remove DURATION_2, EASING_FUNCTION_2, FADE_IN imports - Remove animation block from useCurrentItemButtonStyles - Remove animateBackwards destructuring (no longer used) --- .../useCalendarPickerStyles.styles.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts index c740e4b726488c..4c1e3bc681a652 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarPicker/useCalendarPickerStyles.styles.ts @@ -2,7 +2,7 @@ import { tokens } from '@fluentui/react-theme'; import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; -import { DURATION_2, EASING_FUNCTION_2, FADE_IN } from '../../utils/animations'; +// CSS animations (DURATION_2, EASING_FUNCTION_2, FADE_IN) removed - now handled by HeaderFade motion component import type { SlotClassNames } from '@fluentui/react-utilities'; import type { CalendarPickerStyles, CalendarPickerStyleProps } from './CalendarPicker.types'; @@ -60,12 +60,7 @@ const useCurrentItemButtonStyles = makeStyles({ padding: '0 4px 0 10px', textAlign: 'left', }, - animation: { - animationDuration: DURATION_2, - animationFillMode: 'both', - animationName: FADE_IN, - animationTimingFunction: EASING_FUNCTION_2, - }, + // CSS animation removed - now handled by HeaderFade motion component hasHeaderClickCallback: { // If this is updated, make sure to update headerIsClickable in useCalendarDayStyles as well '&:hover': { @@ -277,8 +272,8 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps const selectedStyles = useSelectedStyles(); const disabledStyles = useDisabledStyles(); - // Note: animateBackwards and animationDirection no longer used for buttonRow - handled by motion components - const { animateBackwards, className, hasHeaderClickCallback, highlightCurrent, highlightSelected } = props; + // Note: animateBackwards and animationDirection no longer used - handled by motion components + const { className, hasHeaderClickCallback, highlightCurrent, highlightSelected } = props; return { root: mergeClasses(calendarPickerClassNames.root, rootStyles.normalize, rootStyles.base, className), @@ -286,7 +281,7 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps currentItemButton: mergeClasses( calendarPickerClassNames.currentItemButton, currentItemButtonStyles.base, - animateBackwards !== undefined && currentItemButtonStyles.animation, + // CSS animation removed - now handled by HeaderFade motion component hasHeaderClickCallback && currentItemButtonStyles.hasHeaderClickCallback, ), navigationButtonsContainer: mergeClasses( From 2d23e7f3a8962473a147a9a96fe7d55e951d7c71 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:45 +0000 Subject: [PATCH 32/33] docs(react-calendar-compat): update migration plan to mark Phase 2 complete Update MOTION_MIGRATION_PLAN.md to reflect completed migration: - All CSS animations migrated to motion components - DirectionalSlide for slide animations - HeaderFade for header fade animations - Animation constants marked as deprecated - Build and tests passing --- .../MOTION_MIGRATION_PLAN.md | 138 ++++++------------ 1 file changed, 48 insertions(+), 90 deletions(-) diff --git a/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md b/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md index cd5c40b5588cba..00501fecaa226d 100644 --- a/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md +++ b/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md @@ -5,7 +5,7 @@ Migration of Calendar animations from CSS keyframe animations to Fluent UI v9 motion components. **Last Updated:** January 2026 -**Status:** Phase 1 complete (slide animations migrated), Phase 2 in progress (remaining CSS animations) +**Status:** Phase 2 complete (all animations migrated to motion components) --- @@ -19,106 +19,64 @@ Migration of Calendar animations from CSS keyframe animations to Fluent UI v9 mo - ✅ Updated `CalendarGridRow` to use `React.forwardRef` (required for motion ref) - ✅ Using `motionTokens.durationSlower` (400ms) and `motionTokens.curveDecelerateMax` -### Remaining CSS Animations (Phase 2) +### Completed (Phase 2: CSS Animation Migration) -The following CSS animations in `animations.ts` are still actively used: - -| Animation | Used In | Purpose | -| ------------------- | ------------------------------- | ------------------------------------ | -| `FADE_IN` | `CalendarDay`, `CalendarPicker` | Header/button fade on navigation | -| `SLIDE_*_IN20` | `CalendarPicker` | Button row slide (month/year picker) | -| `DURATION_2` | `CalendarDay`, `CalendarPicker` | Fade animation duration | -| `DURATION_3` | `CalendarPicker` | Slide animation duration | -| `EASING_FUNCTION_1` | `CalendarPicker` | Slide timing (migrated to token) | -| `EASING_FUNCTION_2` | `CalendarDay`, `CalendarPicker` | Fade timing (no exact token) | - -### Unused (Can Be Removed) - -- `FADE_OUT`, `SLIDE_DOWN_OUT20`, `SLIDE_UP_OUT20`, `TRANSITION_ROW_DISAPPEARANCE` -- `DURATION_1`, `DURATION_4` +- ✅ Migrated `CalendarYear` row animations to `DirectionalSlide` motion components +- ✅ Removed CSS slide animations from `CalendarPicker` styles +- ✅ Created `HeaderFade` component using `Fade.In` from `@fluentui/react-motion-components-preview` +- ✅ Migrated header fade animations in `CalendarDay`, `CalendarMonth`, `CalendarYear` +- ✅ Removed CSS fade animations from `useCalendarDayStyles.styles.ts` +- ✅ Removed CSS fade animations from `useCalendarPickerStyles.styles.ts` +- ✅ Marked animation constants as `@deprecated` in `animations.ts` --- -## Phase 2: Remaining CSS Animation Migration - -### Analysis - -There are two categories of remaining CSS animations: - -#### 1. CalendarPicker Button Row Animations - -**Location:** `useCalendarPickerStyles.styles.ts` (lines 141-163) -**Current implementation:** CSS `@keyframes` with `FADE_IN` + `SLIDE_*_IN20` - -**Migration approach:** Wrap button rows with `DirectionalSlide` (same pattern as CalendarMonth) - -```tsx -// CalendarMonth.tsx (line ~255) - already using this pattern: - -
- {/* month buttons */} -
-
-``` - -**Files to modify:** - -- `CalendarPicker.tsx` - Add `DirectionalSlide` wrapper around button rows -- `useCalendarPickerStyles.styles.ts` - Remove CSS animation styles from `buttonRow` - -**Complexity:** Low - follows existing pattern from CalendarMonth - -#### 2. Header Fade Animations - -**Location:** `useCalendarDayStyles.styles.ts` (line 67-71), `useCalendarPickerStyles.styles.ts` (line 72-76) -**Current implementation:** CSS `@keyframes` with `FADE_IN`, `DURATION_2`, `EASING_FUNCTION_2` +## Phase 2: Remaining CSS Animation Migration (COMPLETED) -**Migration approach:** Use `Fade.In` from `@fluentui/react-motion-components-preview` +### Summary -```tsx -import { Fade } from '@fluentui/react-motion-components-preview'; +All CSS animations have been migrated to motion components: -// Wrap the header text that animates - - {yearString} -; -``` +#### 1. CalendarYear/CalendarPicker Slide Animations ✅ -**Challenge:** `EASING_FUNCTION_2` (`cubic-bezier(.1,.25,.75,.9)`) has no exact motion token equivalent. Options: +- Added `DirectionalSlide` wrappers around year rows in `CalendarYear.tsx` +- Removed CSS slide animation styles from `useCalendarPickerStyles.styles.ts` +- Uses same pattern as CalendarMonth -1. Use closest token (`motionTokens.curveEasyEase` or similar) -2. Keep custom easing value (acceptable deviation) -3. Accept slight visual difference with standard token +#### 2. Header Fade Animations ✅ -**Files to modify:** +- Created `HeaderFade` component using `Fade.In` from `@fluentui/react-motion-components-preview` +- Uses `motionTokens.durationGentle` (~250ms) for timing +- Component uses `navigationKey` prop to trigger animation on value change +- Migrated in `CalendarDay.tsx`, `CalendarMonth.tsx`, `CalendarYear.tsx` +- Removed CSS fade animations from style files -- `CalendarDay.tsx` - Wrap month/year header with `Fade.In` -- `CalendarPicker.tsx` - Wrap current item button text with `Fade.In` -- `useCalendarDayStyles.styles.ts` - Remove CSS animation styles -- `useCalendarPickerStyles.styles.ts` - Remove CSS animation styles +#### 3. Animation Constants ✅ -**Complexity:** Medium - needs state management to trigger fade on navigation +- All animation constants in `animations.ts` marked as `@deprecated` +- Constants retained for backwards compatibility only -### Recommended Migration Order +### Migration Order (COMPLETED) -1. **CalendarPicker button rows** (Low effort, high impact) +1. **CalendarYear slide animations** ✅ - - Same pattern already proven in CalendarMonth - - Removes `SLIDE_*_IN20` and `DURATION_3` usage + - Same pattern as CalendarMonth + - Removed `SLIDE_*_IN20` and `DURATION_3` usage -2. **Header fade animations** (Medium effort) +2. **Header fade animations** ✅ - - Requires adding state to track navigation changes - - May need to accept `EASING_FUNCTION_2` deviation + - Created `HeaderFade` component with `navigationKey` for triggering + - Used standard motion tokens (slight deviation from original easing accepted) -3. **Cleanup animations.ts** (After above complete) - - Remove all unused exports - - Consider removing file entirely if everything migrated +3. **Animation constants deprecated** ✅ + - All exports marked `@deprecated` for backwards compatibility --- ## Validation Tasks +- [x] Build passes: `yarn nx run react-calendar-compat:build` +- [x] Unit tests pass: `yarn nx run react-calendar-compat:test` - [ ] Run VR tests: `yarn nx run vr-tests-react-components:test-vr` - [ ] Test keyboard navigation - [ ] Test reduced motion preference (`prefers-reduced-motion`) @@ -128,18 +86,18 @@ import { Fade } from '@fluentui/react-motion-components-preview'; ## Files Modified -| File | Status | Changes | -| ------------------------------------ | ------ | ----------------------------------------------- | -| `CalendarDayGrid.tsx` | ✅ | `DirectionalSlide` wrappers for day rows | -| `CalendarGridRow.tsx` | ✅ | Added `React.forwardRef` | -| `CalendarMonth.tsx` | ✅ | `DirectionalSlide` wrappers for month rows | -| `calendarMotions.tsx` | ✅ | Created `DirectionalSlide` component | -| `useCalendarDayGridStyles.styles.ts` | ✅ | Removed CSS slide animations | -| `CalendarPicker.tsx` | ⏳ | Pending: Add `DirectionalSlide` for button rows | -| `useCalendarPickerStyles.styles.ts` | ⏳ | Pending: Remove CSS animations | -| `CalendarDay.tsx` | ⏳ | Pending: Add `Fade.In` for header | -| `useCalendarDayStyles.styles.ts` | ⏳ | Pending: Remove CSS fade animation | -| `animations.ts` | ⏳ | Pending: Remove unused exports | +| File | Status | Changes | +| ------------------------------------ | ------ | ------------------------------------------------------ | +| `CalendarDayGrid.tsx` | ✅ | `DirectionalSlide` wrappers for day rows | +| `CalendarGridRow.tsx` | ✅ | Added `React.forwardRef` | +| `CalendarMonth.tsx` | ✅ | `DirectionalSlide` wrappers for month rows, HeaderFade | +| `CalendarYear.tsx` | ✅ | `DirectionalSlide` wrappers for year rows, HeaderFade | +| `calendarMotions.tsx` | ✅ | Created `DirectionalSlide` and `HeaderFade` components | +| `useCalendarDayGridStyles.styles.ts` | ✅ | Removed CSS slide animations | +| `useCalendarPickerStyles.styles.ts` | ✅ | Removed CSS slide and fade animations | +| `CalendarDay.tsx` | ✅ | Added `HeaderFade` for header | +| `useCalendarDayStyles.styles.ts` | ✅ | Removed CSS fade animation | +| `animations.ts` | ✅ | All exports marked `@deprecated` | --- From cf735323d174eef3b484eca2fd44914ce8276504 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:35:07 +0000 Subject: [PATCH 33/33] refactor(react-calendar-compat): update animation constants to reflect deprecations and improve organization --- .../library/src/utils/animations.ts | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/src/utils/animations.ts b/packages/react-components/react-calendar-compat/library/src/utils/animations.ts index f6b6baf122b084..9e7f486c46442a 100644 --- a/packages/react-components/react-calendar-compat/library/src/utils/animations.ts +++ b/packages/react-components/react-calendar-compat/library/src/utils/animations.ts @@ -1,9 +1,30 @@ import { motionTokens } from '@fluentui/react-motion'; -// === USED CONSTANTS === +// === EASING FUNCTIONS === + +/** @deprecated Slide animations now use motion components. Use motionTokens.curveDecelerateMax instead. */ +export const EASING_FUNCTION_1 = motionTokens.curveDecelerateMax; + // Used in header fade animations (CalendarDay, CalendarPicker currentItemButton) export const EASING_FUNCTION_2 = 'cubic-bezier(.1,.25,.75,.9)'; // No exact motion token equivalent + +// === DURATIONS === + +/** @deprecated No longer used internally. */ +export const DURATION_1 = `${motionTokens.durationFast}ms`; + +// Used in header fade animations (CalendarDay, CalendarPicker currentItemButton) export const DURATION_2 = `${motionTokens.durationGentle}ms`; + +/** @deprecated Slide animations now use motion components with motionTokens.durationSlower. */ +export const DURATION_3 = `${motionTokens.durationSlower}ms`; + +/** @deprecated No longer used internally. */ +export const DURATION_4 = `${motionTokens.durationUltraSlow}ms`; + +// === FADE ANIMATIONS === + +// Used in header fade animations (CalendarDay, CalendarPicker currentItemButton) export const FADE_IN = { from: { opacity: 0, @@ -13,20 +34,6 @@ export const FADE_IN = { }, }; -// === DEPRECATED - Kept for backward compatibility === -// The following constants are no longer used internally (slide animations now use motion components) -// but are kept as exports in case external consumers depend on them - -/** @deprecated Slide animations now use motion components. Use motionTokens.curveDecelerateMax instead. */ -export const EASING_FUNCTION_1 = motionTokens.curveDecelerateMax; - -/** @deprecated No longer used internally. */ -export const DURATION_1 = `${motionTokens.durationFast}ms`; -/** @deprecated Slide animations now use motion components with motionTokens.durationSlower. */ -export const DURATION_3 = `${motionTokens.durationSlower}ms`; -/** @deprecated No longer used internally. */ -export const DURATION_4 = `${motionTokens.durationUltraSlow}ms`; - /** @deprecated Slide animations now use motion components. */ export const FADE_OUT = { from: { @@ -38,6 +45,8 @@ export const FADE_OUT = { }, }; +// === SLIDE ANIMATIONS === + /** @deprecated Slide animations now use DirectionalSlide motion component. */ export const SLIDE_DOWN_IN20 = { from: { @@ -51,10 +60,10 @@ export const SLIDE_DOWN_IN20 = { }; /** @deprecated Slide animations now use DirectionalSlide motion component. */ -export const SLIDE_LEFT_IN20 = { +export const SLIDE_UP_IN20 = { from: { pointerEvents: 'none' as const, - transform: 'translate3d(20px, 0, 0)', + transform: 'translate3d(0, 20px, 0)', }, to: { pointerEvents: 'auto' as const, @@ -63,10 +72,10 @@ export const SLIDE_LEFT_IN20 = { }; /** @deprecated Slide animations now use DirectionalSlide motion component. */ -export const SLIDE_RIGHT_IN20 = { +export const SLIDE_LEFT_IN20 = { from: { pointerEvents: 'none' as const, - transform: 'translate3d(-20px, 0, 0)', + transform: 'translate3d(20px, 0, 0)', }, to: { pointerEvents: 'auto' as const, @@ -75,10 +84,10 @@ export const SLIDE_RIGHT_IN20 = { }; /** @deprecated Slide animations now use DirectionalSlide motion component. */ -export const SLIDE_UP_IN20 = { +export const SLIDE_RIGHT_IN20 = { from: { pointerEvents: 'none' as const, - transform: 'translate3d(0, 20px, 0)', + transform: 'translate3d(-20px, 0, 0)', }, to: { pointerEvents: 'auto' as const, @@ -106,6 +115,8 @@ export const SLIDE_UP_OUT20 = { }, }; +// === OTHER TRANSITIONS === + /** @deprecated No longer used internally. */ export const TRANSITION_ROW_DISAPPEARANCE = { '100%': {