From 3a72995628e093c869fac088e408006791d0e767 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 13 Jan 2025 19:32:00 +0000 Subject: [PATCH 01/40] 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 0000000000000..1d6d5211cc578 --- /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 9a66a5133cae3dd1235450bc96c383981b8eb7e6 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 13 Jan 2025 19:34:23 +0000 Subject: [PATCH 02/40] 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 96b7382655f3b..f49d16ca667ed 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 3d9de89c1ed17..d41a488ad436a 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 a35a005760f707596c38864382dbdea2b121e816 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 13 Jan 2025 19:38:20 +0000 Subject: [PATCH 03/40] 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 9fe504b3cedff..67d3775528c3e 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-linear' }); + 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 19094c629041c..e8185ed88019a 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 751067413d4b2..cf31b28b342ae 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 db0a382594867a9cc54382fe66daf029846632b8 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Wed, 10 Sep 2025 18:20:47 +0000 Subject: [PATCH 04/40] 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 8340382888894..b110a6db33987 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 @@ -118,8 +118,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 b90a6d73255de..cd5ae7fbb67d9 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'; /** * A private symbol to store the motion definition on the component for variants. @@ -85,8 +85,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 a350faa3c8a049c6dbd1e75500164f03d4b658bb Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Wed, 10 Sep 2025 18:23:13 +0000 Subject: [PATCH 05/40] 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 1d6d5211cc578..a81e65ad2f858 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 f6cdbb80929174526d89f9ec3592a03c4606c58b Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Wed, 10 Sep 2025 19:01:02 +0000 Subject: [PATCH 06/40] 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 2e52f1b2f4520..8a13838ce5b2e 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 67d3775528c3e..d3e843cba6628 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 f49d16ca667ed..f568f10935b79 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 d41a488ad436a..6ab8397f7066e 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 172d733f8e55a367c18d08811f23cef11b219fa4 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Wed, 10 Sep 2025 19:10:39 +0000 Subject: [PATCH 07/40] 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 f568f10935b79..e8b8219229874 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 a81e65ad2f858..8234166240bf6 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 405f336ad0526e8de27428f49985203040d1eaa0 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 14:19:01 +0000 Subject: [PATCH 08/40] 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 d3e843cba6628..68bb33587b668 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 e8b8219229874..e5bcc43567ced 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 8234166240bf6..0000000000000 --- 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 0000000000000..14a96b505a6bb --- /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 c24ba3786878fe744836e26ca62dec9b6e1d22a5 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 14:28:27 +0000 Subject: [PATCH 09/40] 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 0000000000000..dec3716fecb3f --- /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 0000000000000..4cadad1a7ef0a --- /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 a19dddfc6c8d5cf2d3a368449982689e2a249fc9 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 14:57:03 +0000 Subject: [PATCH 10/40] 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 6a45c738aa1d8..1672f54f074f5 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 14a96b505a6bb..27efbc7c83781 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 23917205a9d9001bc37b29144eb0649c6c920122 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 15:17:49 +0000 Subject: [PATCH 11/40] 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 68bb33587b668..752888c2f2888 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 e5bcc43567ced..7b7f5ae3bd7a1 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 42559c0730b9b210caf811dba65c4ec98cdc346e Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 15:23:18 +0000 Subject: [PATCH 12/40] 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 e8185ed88019a..19094c629041c 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 cf31b28b342ae..45b01bc7763a0 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 7b7f5ae3bd7a1..8e67b548c06c1 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 6d912e82a860f6dbca3bf93f526efe9f6b255918 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 11 Sep 2025 15:36:46 +0000 Subject: [PATCH 13/40] 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 45b01bc7763a0..eb705cf694714 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 6ab8397f7066e..35ea98ad798c2 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 11b64d4529ac5e5c60c237343c22efb6a02fc44e Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 3 Nov 2025 19:07:25 +0000 Subject: [PATCH 14/40] 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 8a13838ce5b2e..cce90fc28e6f7 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 752888c2f2888..45f69ff6a0606 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 8e67b548c06c1..75a970313c5ec 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 35ea98ad798c2..9794b8c21cb08 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 ddab24e736e77c490c515b3f5783ede3bb9874c7 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 00:04:40 +0000 Subject: [PATCH 15/40] 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 0000000000000..95007c4635b35 --- /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 89272bd3fa686910cfca1f971e6e97125d2becd5 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 00:52:51 +0000 Subject: [PATCH 16/40] 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 95007c4635b35..cd5c40b5588cb 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 2ab2bf5df0143c586da6c056730968f6335c4303 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 00:58:12 +0000 Subject: [PATCH 17/40] 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 27efbc7c83781..aaf372dcbbe91 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 4a684c4e55a459f86c1938c6809a6f43aa36e539 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:13:43 +0000 Subject: [PATCH 18/40] 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 19094c629041c..9aa2dddd80c53 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 dfc0ef44ec70b39da5fdefe7b398507426ec29e7 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:14:00 +0000 Subject: [PATCH 19/40] 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 45f69ff6a0606..541e29c051ac8 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 be7438441ed444c04d4b3739661d0f6bbc23e2eb Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:14:20 +0000 Subject: [PATCH 20/40] 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 eb705cf694714..c277be8c66690 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 1eff6366cdcb9c5a5e54075a5d42edfe4d235692 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:14:40 +0000 Subject: [PATCH 21/40] 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 1672f54f074f5..371c40d64a2ee 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 ad0e8f47f4ac6827a8648f1f51c94e6905c3851b Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:48:39 +0000 Subject: [PATCH 22/40] 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 --- .../react-components/react-calendar-compat/library/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-components/react-calendar-compat/library/package.json b/packages/react-components/react-calendar-compat/library/package.json index 3d91899efe4ca..bd3eafe0557f4 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.4.1", + "@fluentui/react-motion": "^9.11.5", + "@fluentui/react-motion-components-preview": "^0.14.2", "@fluentui/react-shared-contexts": "^9.26.2", "@fluentui/react-tabster": "^9.26.13", "@fluentui/react-theme": "^9.2.1", From b00bf02618ee8976eda03fd6e9e2eedfe31e0989 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:48:56 +0000 Subject: [PATCH 23/40] 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 9aa2dddd80c53..3f4daba5f849f 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 f0c0748f611f4467a0902d4c83ddf1a7b65eaa60 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:49:09 +0000 Subject: [PATCH 24/40] 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 8c0190f04fd6c..d6159be30c8a3 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 7baac934fab0d6dbd5af784e39c960c9175e6bff Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:49:18 +0000 Subject: [PATCH 25/40] 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 9794b8c21cb08..3967e130eac27 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 a4c55530e352646454525a694f62735b53514321 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 01:49:26 +0000 Subject: [PATCH 26/40] 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 371c40d64a2ee..f6b6baf122b08 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 2cdf76e5e0414..d021332767de7 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 a0dc05a82053214aa99ef768637f7c09293500ae Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:00 +0000 Subject: [PATCH 27/40] 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 aaf372dcbbe91..7b87e2d006b2d 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 234be5dce7f0541e58a24ea5199f25fcf1276cb6 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:12 +0000 Subject: [PATCH 28/40] 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 cce90fc28e6f7..b02f2c20c0095 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 5c89a0db90425..92fd87630bcf8 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 3ac61788c9088a69491a72e171a2b7f8b143fb3e Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:19 +0000 Subject: [PATCH 29/40] 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 75a970313c5ec..2cf7b58dbecac 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 dc07cb3ddb91a15971de9ffd1069f54c527bcca7 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:35 +0000 Subject: [PATCH 31/40] 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 3967e130eac27..8d3b817bcc8a9 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 420514a067bcf4f2c6b4f9eb5d1232ba9fed36a1 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:18:45 +0000 Subject: [PATCH 32/40] 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 cd5c40b5588cb..00501fecaa226 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 354d9ed0003598fcff4d742838d658c839602e00 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Fri, 16 Jan 2026 03:35:07 +0000 Subject: [PATCH 33/40] 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 f6b6baf122b08..9e7f486c46442 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%': { From f291d4a382a2d20592d16b02f2fde2b499bed106 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 12 Mar 2026 06:53:54 +0000 Subject: [PATCH 34/40] feat(react-calendar-compat): migrate CalendarDayGrid weekday labels to Fade.In motion component - Wrap day label elements in Fade.In in CalendarMonthHeaderRow.tsx - Remove CSS fade animation from useCalendarDayGridStyles.styles.ts - Mark FADE_IN, DURATION_2, EASING_FUNCTION_2 as @deprecated in animations.ts - Update migration plan to mark Phase 3 complete --- .../MOTION_MIGRATION_PLAN.md | 10 +++++++- .../CalendarMonthHeaderRow.tsx | 23 +++++++++++-------- .../useCalendarDayGridStyles.styles.ts | 6 +---- .../library/src/utils/animations.ts | 8 +++---- 4 files changed, 27 insertions(+), 20 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 00501fecaa226..59b59f5a7c2db 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 2 complete (all animations migrated to motion components) +**Status:** Phase 3 complete (all animations migrated to motion components) --- @@ -29,6 +29,13 @@ Migration of Calendar animations from CSS keyframe animations to Fluent UI v9 mo - ✅ Removed CSS fade animations from `useCalendarPickerStyles.styles.ts` - ✅ Marked animation constants as `@deprecated` in `animations.ts` +### Completed (Phase 3: Final Cleanup) + +- ✅ Migrated `CalendarDayGrid` weekDayLabelCell fade to `Fade.In` motion component in `CalendarMonthHeaderRow.tsx` +- ✅ Removed CSS fade animation from `useCalendarDayGridStyles.styles.ts` +- ✅ Marked remaining constants (`FADE_IN`, `DURATION_2`, `EASING_FUNCTION_2`) as `@deprecated` +- ✅ All animation constants in `animations.ts` are now deprecated + --- ## Phase 2: Remaining CSS Animation Migration (COMPLETED) @@ -98,6 +105,7 @@ All CSS animations have been migrated to motion components: | `CalendarDay.tsx` | ✅ | Added `HeaderFade` for header | | `useCalendarDayStyles.styles.ts` | ✅ | Removed CSS fade animation | | `animations.ts` | ✅ | All exports marked `@deprecated` | +| `CalendarMonthHeaderRow.tsx` | ✅ | Wrapped `` with `Fade.In` motion component | --- diff --git a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarMonthHeaderRow.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarMonthHeaderRow.tsx index fbe0a3cb479f6..157fd9fd59651 100644 --- a/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarMonthHeaderRow.tsx +++ b/packages/react-components/react-calendar-compat/library/src/components/CalendarDayGrid/CalendarMonthHeaderRow.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; import { mergeClasses } from '@griffel/react'; +import { motionTokens } from '@fluentui/react-motion'; +import { Fade } from '@fluentui/react-motion-components-preview'; import { DAYS_IN_WEEK } from '../../utils'; import type { CalendarDayGridProps, CalendarDayGridStyles } from './CalendarDayGrid.types'; import type { DayInfo } from './CalendarDayGrid'; @@ -41,16 +43,17 @@ export const CalendarMonthHeaderRow: React.FunctionComponent - {dayLabels[i]} - + + + {dayLabels[i]} + + ); })} 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 c277be8c66690..ee4db3533075e 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,7 +2,6 @@ import { tokens } from '@fluentui/react-theme'; import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; -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'; @@ -173,10 +172,7 @@ const useWeekRowStyles = makeStyles({ const useWeekDayLabelCellStyles = makeStyles({ base: { userSelect: 'none', - animationDuration: DURATION_2, - animationFillMode: 'both', - animationName: FADE_IN, - animationTimingFunction: EASING_FUNCTION_2, + // CSS fade animation removed - now handled by Fade.In motion component in CalendarMonthHeaderRow }, }); 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 9e7f486c46442..52f9ea73fd5a9 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 @@ -5,15 +5,15 @@ import { motionTokens } from '@fluentui/react-motion'; /** @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 +/** @deprecated Fade animations now use Fade.In motion component. No exact motion token equivalent. */ +export const EASING_FUNCTION_2 = 'cubic-bezier(.1,.25,.75,.9)'; // === DURATIONS === /** @deprecated No longer used internally. */ export const DURATION_1 = `${motionTokens.durationFast}ms`; -// Used in header fade animations (CalendarDay, CalendarPicker currentItemButton) +/** @deprecated Fade animations now use Fade.In motion component with motionTokens.durationGentle. */ export const DURATION_2 = `${motionTokens.durationGentle}ms`; /** @deprecated Slide animations now use motion components with motionTokens.durationSlower. */ @@ -24,7 +24,7 @@ export const DURATION_4 = `${motionTokens.durationUltraSlow}ms`; // === FADE ANIMATIONS === -// Used in header fade animations (CalendarDay, CalendarPicker currentItemButton) +/** @deprecated Fade animations now use Fade.In motion component. */ export const FADE_IN = { from: { opacity: 0, From 0579a4cc1209933260f3fa8af3e285c1e1a027f7 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 12 Mar 2026 07:56:00 +0000 Subject: [PATCH 35/40] chore: update yarn.lock --- yarn.lock | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/yarn.lock b/yarn.lock index 2a0964696b419..1a5c93d165ed4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1882,6 +1882,15 @@ "@griffel/react" "^1.0.0" tslib "^2.1.0" +"@fluentui/react-motion-components-preview@^0.14.2": + version "0.14.2" + resolved "https://registry.yarnpkg.com/@fluentui/react-motion-components-preview/-/react-motion-components-preview-0.14.2.tgz#9fd0ae8bd27bd43423ca4d9eca6b913d01ecdfbf" + integrity sha512-QbdbgzcM02AvYCN4PbBMZCw10vMh9AvPK8kK2kbMdNWXolbRau2ndNVfXpXvZxY9KZFc2lJlYUBLWJTLDINQXA== + dependencies: + "@fluentui/react-motion" "*" + "@fluentui/react-utilities" "*" + "@swc/helpers" "^0.5.1" + "@fluentui/react-northstar-fela-renderer@^0.66.5": version "0.66.5" resolved "https://registry.yarnpkg.com/@fluentui/react-northstar-fela-renderer/-/react-northstar-fela-renderer-0.66.5.tgz#ceffe843a535886f318ae42012e8bb0c800c3e83" From 2b20e6f471cc85de2a4b755c62fa7be8ed51e8ae Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 12 Mar 2026 08:12:37 +0000 Subject: [PATCH 36/40] chore: update version of react-motion-components-preview in Calendar package.json --- .../react-calendar-compat/library/package.json | 2 +- yarn.lock | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/react-components/react-calendar-compat/library/package.json b/packages/react-components/react-calendar-compat/library/package.json index bd3eafe0557f4..560980418e24b 100644 --- a/packages/react-components/react-calendar-compat/library/package.json +++ b/packages/react-components/react-calendar-compat/library/package.json @@ -16,7 +16,7 @@ "@fluentui/react-icons": "^2.0.245", "@fluentui/react-jsx-runtime": "^9.4.1", "@fluentui/react-motion": "^9.11.5", - "@fluentui/react-motion-components-preview": "^0.14.2", + "@fluentui/react-motion-components-preview": "^0.15.2", "@fluentui/react-shared-contexts": "^9.26.2", "@fluentui/react-tabster": "^9.26.13", "@fluentui/react-theme": "^9.2.1", diff --git a/yarn.lock b/yarn.lock index 1a5c93d165ed4..2a0964696b419 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1882,15 +1882,6 @@ "@griffel/react" "^1.0.0" tslib "^2.1.0" -"@fluentui/react-motion-components-preview@^0.14.2": - version "0.14.2" - resolved "https://registry.yarnpkg.com/@fluentui/react-motion-components-preview/-/react-motion-components-preview-0.14.2.tgz#9fd0ae8bd27bd43423ca4d9eca6b913d01ecdfbf" - integrity sha512-QbdbgzcM02AvYCN4PbBMZCw10vMh9AvPK8kK2kbMdNWXolbRau2ndNVfXpXvZxY9KZFc2lJlYUBLWJTLDINQXA== - dependencies: - "@fluentui/react-motion" "*" - "@fluentui/react-utilities" "*" - "@swc/helpers" "^0.5.1" - "@fluentui/react-northstar-fela-renderer@^0.66.5": version "0.66.5" resolved "https://registry.yarnpkg.com/@fluentui/react-northstar-fela-renderer/-/react-northstar-fela-renderer-0.66.5.tgz#ceffe843a535886f318ae42012e8bb0c800c3e83" From 82814eb8671288f488e44eef1ed123fb079ec675 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Thu, 12 Mar 2026 08:31:10 +0000 Subject: [PATCH 37/40] chore: delete migration plan doc --- .../MOTION_MIGRATION_PLAN.md | 115 ------------------ 1 file changed, 115 deletions(-) delete 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 deleted file mode 100644 index 59b59f5a7c2db..0000000000000 --- a/packages/react-components/react-calendar-compat/MOTION_MIGRATION_PLAN.md +++ /dev/null @@ -1,115 +0,0 @@ -# Calendar Motion Migration Plan - -## Overview - -Migration of Calendar animations from CSS keyframe animations to Fluent UI v9 motion components. - -**Last Updated:** January 2026 -**Status:** Phase 3 complete (all animations migrated to motion components) - ---- - -## Current State - -### Completed (Phase 1: Slide Animations) - -- ✅ 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` - -### Completed (Phase 2: CSS Animation Migration) - -- ✅ 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` - -### Completed (Phase 3: Final Cleanup) - -- ✅ Migrated `CalendarDayGrid` weekDayLabelCell fade to `Fade.In` motion component in `CalendarMonthHeaderRow.tsx` -- ✅ Removed CSS fade animation from `useCalendarDayGridStyles.styles.ts` -- ✅ Marked remaining constants (`FADE_IN`, `DURATION_2`, `EASING_FUNCTION_2`) as `@deprecated` -- ✅ All animation constants in `animations.ts` are now deprecated - ---- - -## Phase 2: Remaining CSS Animation Migration (COMPLETED) - -### Summary - -All CSS animations have been migrated to motion components: - -#### 1. CalendarYear/CalendarPicker Slide Animations ✅ - -- Added `DirectionalSlide` wrappers around year rows in `CalendarYear.tsx` -- Removed CSS slide animation styles from `useCalendarPickerStyles.styles.ts` -- Uses same pattern as CalendarMonth - -#### 2. Header Fade Animations ✅ - -- 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 - -#### 3. Animation Constants ✅ - -- All animation constants in `animations.ts` marked as `@deprecated` -- Constants retained for backwards compatibility only - -### Migration Order (COMPLETED) - -1. **CalendarYear slide animations** ✅ - - - Same pattern as CalendarMonth - - Removed `SLIDE_*_IN20` and `DURATION_3` usage - -2. **Header fade animations** ✅ - - - Created `HeaderFade` component with `navigationKey` for triggering - - Used standard motion tokens (slight deviation from original easing accepted) - -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`) -- [ ] Cross-browser validation (Chrome, Firefox, Safari) - ---- - -## Files Modified - -| 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` | -| `CalendarMonthHeaderRow.tsx` | ✅ | Wrapped `` with `Fade.In` motion component | - ---- - -## References - -- [Fluent UI Motion Documentation](https://react.fluentui.dev/?path=/docs/motion-introduction--docs) -- [react-motion-components-preview](../../react-motion-components-preview/) From 44740c26978e85f93b793ae940a50d667786e3a4 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 16 Mar 2026 15:47:46 +0000 Subject: [PATCH 38/40] revert(react-motion): remove PresenceComponent type change for separate PR Move the MotionComponent type change on .In/.Out to a dedicated PR as requested in code review, since it affects a shared package. --- ...-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json | 7 ------- .../react-motion/library/etc/react-motion.api.md | 4 ++-- .../library/src/factories/createPresenceComponent.ts | 6 +++--- 3 files changed, 5 insertions(+), 12 deletions(-) delete mode 100644 change/@fluentui-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json diff --git a/change/@fluentui-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json b/change/@fluentui-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json deleted file mode 100644 index 4cadad1a7ef0a..0000000000000 --- a/change/@fluentui-react-motion-bbaaafd1-d86c-4b82-99bd-49a92507f93e.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "minor", - "comment": "fix(react-motion): apply MotionComponent type to presence definition", - "packageName": "@fluentui/react-motion", - "email": "robertpenner@microsoft.com", - "dependentChangeType": "patch" -} 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 b110a6db33987..8340382888894 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 @@ -118,8 +118,8 @@ export const motionTokens: { export type PresenceComponent = {}> = React_2.FC & { (props: PresenceComponentProps & MotionParams): JSXElement | null; [PRESENCE_MOTION_DEFINITION]: PresenceMotionFn; - In: MotionComponent; - Out: MotionComponent; + In: React_2.FC; + Out: React_2.FC; }; // @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 cd5ae7fbb67d9..b90a6d73255de 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, MotionComponent } from './createMotionComponent'; +import { createMotionComponent, MotionComponentProps } from './createMotionComponent'; /** * A private symbol to store the motion definition on the component for variants. @@ -85,8 +85,8 @@ export type PresenceComponent = > & { (props: PresenceComponentProps & MotionParams): JSXElement | null; [PRESENCE_MOTION_DEFINITION]: PresenceMotionFn; - In: MotionComponent; - Out: MotionComponent; + In: React.FC; + Out: React.FC; }; const INTERRUPTABLE_MOTION_SYMBOL = Symbol.for('interruptablePresence'); From dc927200d7d337ec464187028a6664902c1fb4a1 Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 16 Mar 2026 15:47:53 +0000 Subject: [PATCH 39/40] chore(react-calendar-compat): remove redundant CSS animation removal comments --- .../useCalendarDayStyles.styles.ts | 4 --- .../useCalendarDayGridStyles.styles.ts | 25 +++---------------- .../useCalendarPickerStyles.styles.ts | 13 +--------- 3 files changed, 4 insertions(+), 38 deletions(-) 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 92fd87630bcf8..62682c31ba561 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 @@ -5,8 +5,6 @@ import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; 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 */ @@ -65,7 +63,6 @@ const useMonthAndYearStyles = makeStyles({ textOverflow: 'ellipsis', whiteSpace: 'nowrap', }, - // CSS animation removed - now handled by HeaderFade motion component headerIsClickable: { '&:hover': { backgroundColor: tokens.colorBrandBackgroundInvertedHover, @@ -162,7 +159,6 @@ export const useCalendarDayStyles_unstable = (props: CalendarDayStyleProps): Cal monthAndYear: mergeClasses( calendarDayClassNames.monthAndYear, monthAndYearStyles.base, - // CSS animation removed - now handled by HeaderFade motion component headerIsClickable && monthAndYearStyles.headerIsClickable, ), monthComponents: mergeClasses(calendarDayClassNames.monthComponents, monthComponentsStyles.base), 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 ee4db3533075e..a96df5f6681ab 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 @@ -7,8 +7,6 @@ import { createFocusOutlineStyle } from '@fluentui/react-tabster'; import type { SlotClassNames } from '@fluentui/react-utilities'; import type { CalendarDayGridStyles, CalendarDayGridStyleProps } from './CalendarDayGrid.types'; -// Note: DURATION_3, EASING_FUNCTION_1, and SLIDE_* animations removed - now handled by motion components (DirectionalSlide) - /** * @internal */ @@ -166,13 +164,11 @@ const useWeekRowStyles = makeStyles({ zIndex: 1, }, }, - // CSS slide animations removed - now handled by motion components (DirectionalSlide) }); const useWeekDayLabelCellStyles = makeStyles({ base: { userSelect: 'none', - // CSS fade animation removed - now handled by Fade.In motion component in CalendarMonthHeaderRow }, }); @@ -288,7 +284,6 @@ const useFirstTransitionWeekStyles = makeStyles({ position: 'absolute', width: 0, }, - // CSS animations removed - now handled by motion components (DirectionalSlide) }); const useLastTransitionWeekStyles = makeStyles({ @@ -300,7 +295,6 @@ const useLastTransitionWeekStyles = makeStyles({ position: 'absolute', width: 0, }, - // CSS animations removed - now handled by motion components (DirectionalSlide) }); const useDayMarkerStyles = makeStyles({ @@ -366,7 +360,6 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro const cornerBorderAndRadiusStyles = useCornerBorderAndRadiusStyles(); const dayTodayMarkerStyles = useDayTodayMarkerStyles(); - // Note: animateBackwards and animationDirection no longer used here - handled by motion components const { lightenDaysOutsideNavigatedMonth, showWeekNumbers } = props; return { @@ -384,11 +377,7 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro ), daySelected: mergeClasses(calendarDayGridClassNames.daySelected, daySelectedStyles.base), daySingleSelected: mergeClasses(calendarDayGridClassNames.daySingleSelected, daySingleSelectedStyles.base), - weekRow: mergeClasses( - calendarDayGridClassNames.weekRow, - weekRowStyles.base, - // CSS animations removed - now handled by motion components (DirectionalSlide) - ), + weekRow: mergeClasses(calendarDayGridClassNames.weekRow, weekRowStyles.base), weekDayLabelCell: mergeClasses(calendarDayGridClassNames.weekDayLabelCell, weekDayLabelCellStyles.base), weekNumberCell: mergeClasses(calendarDayGridClassNames.weekNumberCell, weekNumberCellStyles.base), dayOutsideBounds: mergeClasses(calendarDayGridClassNames.dayOutsideBounds, dayOutsideBoundsStyles.base), @@ -398,16 +387,8 @@ export const useCalendarDayGridStyles_unstable = (props: CalendarDayGridStylePro ), dayButton: mergeClasses(calendarDayGridClassNames.dayButton, dayButtonStyles.base), dayIsToday: mergeClasses(calendarDayGridClassNames.dayIsToday, dayIsTodayStyles.base), - firstTransitionWeek: mergeClasses( - calendarDayGridClassNames.firstTransitionWeek, - firstTransitionWeekStyles.base, - // CSS animations removed - now handled by motion components (DirectionalSlide) - ), - lastTransitionWeek: mergeClasses( - calendarDayGridClassNames.lastTransitionWeek, - lastTransitionWeekStyles.base, - // CSS animations removed - now handled by motion components (DirectionalSlide) - ), + firstTransitionWeek: mergeClasses(calendarDayGridClassNames.firstTransitionWeek, firstTransitionWeekStyles.base), + lastTransitionWeek: mergeClasses(calendarDayGridClassNames.lastTransitionWeek, lastTransitionWeekStyles.base), 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 8d3b817bcc8a9..2d77d1cf1774a 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,12 +2,9 @@ import { tokens } from '@fluentui/react-theme'; import { makeStyles, mergeClasses, shorthands } from '@griffel/react'; -// 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'; -// Note: DURATION_3, EASING_FUNCTION_1, and SLIDE_* animations removed - now handled by motion components (DirectionalSlide) - /** * @internal */ @@ -60,7 +57,6 @@ const useCurrentItemButtonStyles = makeStyles({ padding: '0 4px 0 10px', textAlign: 'left', }, - // CSS animation removed - now handled by HeaderFade motion component hasHeaderClickCallback: { // If this is updated, make sure to update headerIsClickable in useCalendarDayStyles as well '&:hover': { @@ -131,7 +127,6 @@ const useButtonRowStyles = makeStyles({ marginBottom: 0, }, }, - // CSS slide animations removed - now handled by motion components (DirectionalSlide) }); const useItemButtonStyles = makeStyles({ @@ -272,7 +267,6 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps const selectedStyles = useSelectedStyles(); const disabledStyles = useDisabledStyles(); - // Note: animateBackwards and animationDirection no longer used - handled by motion components const { className, hasHeaderClickCallback, highlightCurrent, highlightSelected } = props; return { @@ -281,7 +275,6 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps currentItemButton: mergeClasses( calendarPickerClassNames.currentItemButton, currentItemButtonStyles.base, - // CSS animation removed - now handled by HeaderFade motion component hasHeaderClickCallback && currentItemButtonStyles.hasHeaderClickCallback, ), navigationButtonsContainer: mergeClasses( @@ -290,11 +283,7 @@ export const useCalendarPickerStyles_unstable = (props: CalendarPickerStyleProps ), navigationButton: mergeClasses(calendarPickerClassNames.navigationButton, navigationButtonStyles.base), gridContainer: mergeClasses(calendarPickerClassNames.gridContainer, gridContainerStyles.base), - buttonRow: mergeClasses( - calendarPickerClassNames.buttonRow, - buttonRowStyles.base, - // CSS animations removed - now handled by motion components (DirectionalSlide) - ), + buttonRow: mergeClasses(calendarPickerClassNames.buttonRow, buttonRowStyles.base), itemButton: mergeClasses(calendarPickerClassNames.itemButton, itemButtonStyles.base), selected: mergeClasses(calendarPickerClassNames.selected, highlightSelected && selectedStyles.highlightSelected), current: mergeClasses(calendarPickerClassNames.current, highlightCurrent && currentStyles.highlightCurrent), From 73903d6bfa35fc1df93edb772f3c174a4a5cd72c Mon Sep 17 00:00:00 2001 From: Robert Penner Date: Mon, 16 Mar 2026 16:14:25 +0000 Subject: [PATCH 40/40] refactor(react-calendar-compat): remove HeaderFade component and redundant div keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove HeaderFade — the header fade animation was not present on master, so it was adding new behavior rather than migrating existing animations. Also remove redundant key on inner div inside DirectionalSlide wrappers. --- .../components/CalendarDay/CalendarDay.tsx | 9 +++----- .../CalendarMonth/CalendarMonth.tsx | 12 +++++----- .../components/CalendarYear/CalendarYear.tsx | 20 +++++++---------- .../library/src/utils/calendarMotions.tsx | 22 +------------------ 4 files changed, 17 insertions(+), 46 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 b02f2c20c0095..cce90fc28e6f7 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,7 +6,6 @@ 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'; @@ -71,11 +70,9 @@ 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/CalendarMonth/CalendarMonth.tsx b/packages/react-components/react-calendar-compat/library/src/components/CalendarMonth/CalendarMonth.tsx index 2cf7b58dbecac..0b70bbdaf3928 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, HeaderFade } from '../../utils/calendarMotions'; +import { DirectionalSlide } from '../../utils/calendarMotions'; import { AnimationDirection } from '../../Calendar'; const MONTHS_PER_ROW = 4; @@ -212,11 +212,9 @@ export const CalendarMonth: React.FunctionComponent = props tabIndex={!!onUserHeaderSelect || !yearPickerHidden ? 0 : -1} type="button" > - - - {yearString} - - + + {yearString} +
); } return (
- - - {onRenderYear(fromYear)} - {onRenderYear(toYear)} - - + + {onRenderYear(fromYear)} - {onRenderYear(toYear)} +
); }; 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 7b87e2d006b2d..aaf372dcbbe91 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 { Fade, Slide } from '@fluentui/react-motion-components-preview'; +import { Slide } from '@fluentui/react-motion-components-preview'; import * as React from 'react'; import { AnimationDirection } from '../Calendar'; import { JSXElement } from '@fluentui/react-utilities'; @@ -33,23 +33,3 @@ 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} - - ); -};