Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9a71e4c
refactor(Calendar): add motion components for slide and fade
robertpenner Jan 13, 2025
03ffd99
refactor(Calendar): migrate month row slide to motion components
robertpenner Jan 13, 2025
762f410
refactor(Calendar): start migrating week row slide to motion components
robertpenner Jan 13, 2025
c19f933
fix(motion): update presence .In and .Out to use MotionComponent type
robertpenner Sep 10, 2025
fa474cf
refactor(Calendar): migrate to variants of Slide motion component
robertpenner Sep 10, 2025
9f90c30
TEMP: default animationDirection to Horizontal for testing
robertpenner Sep 10, 2025
ff8a7d3
reactor(Calendar): create horizontal slide motion components for mont…
robertpenner Sep 10, 2025
34e87fc
refactor(Calendar): simplify conditional motion; consolidate to Direc…
robertpenner Sep 11, 2025
8561a19
chore: yarn change
robertpenner Sep 11, 2025
2d3c0b4
refactor: document Calendar motion constants & plans to migrate to to…
robertpenner Sep 11, 2025
298c737
refactor: rename motions.ts to calendarMotions.ts
robertpenner Sep 11, 2025
8349321
chore: remove unused code
robertpenner Sep 11, 2025
07903c2
chore: uncomment CSS animation code
robertpenner Sep 11, 2025
9a9ed25
chore(Calendar): change default animationDirection back to Vertical
robertpenner Nov 3, 2025
4b690ad
docs(Calendar): add migration plan for transitioning to Fluent UI v9 …
robertpenner Jan 16, 2026
68356ff
docs(react-calendar-compat): Update MOTION_MIGRATION_PLAN.md
robertpenner Jan 16, 2026
beb9313
refactor(react-calendar-compat): Use motion tokens in DirectionalSlide
robertpenner Jan 16, 2026
bf37628
refactor(react-calendar-compat): Convert CalendarGridRow to forwardRef
robertpenner Jan 16, 2026
9ce8066
refactor(react-calendar-compat): Remove wrapper divs from Directional…
robertpenner Jan 16, 2026
f677705
refactor(react-calendar-compat): Remove CSS slide animations from Cal…
robertpenner Jan 16, 2026
14b0eef
refactor(react-calendar-compat): Update animation duration constants
robertpenner Jan 16, 2026
1133129
deps(react-calendar-compat): Add react-motion dependencies
robertpenner Jan 16, 2026
1d83032
refactor(react-calendar-compat): Add 'use client' directive to Calend…
robertpenner Jan 16, 2026
7d20000
feat(react-calendar-compat): Add DirectionalSlide to CalendarYear gri…
robertpenner Jan 16, 2026
358fb4c
refactor(react-calendar-compat): Remove CSS slide animations from Cal…
robertpenner Jan 16, 2026
7628ef8
refactor(react-calendar-compat): Mark unused animation constants as d…
robertpenner Jan 16, 2026
0d87207
feat(react-calendar-compat): add HeaderFade motion component
robertpenner Jan 16, 2026
151f592
feat(react-calendar-compat): migrate CalendarDay header to HeaderFade
robertpenner Jan 16, 2026
206ce85
feat(react-calendar-compat): migrate CalendarMonth header to HeaderFade
robertpenner Jan 16, 2026
df70260
feat(react-calendar-compat): migrate CalendarYear header to HeaderFade
robertpenner Jan 16, 2026
190ddb9
refactor(react-calendar-compat): remove CSS fade animation from Calen…
robertpenner Jan 16, 2026
2d23e7f
docs(react-calendar-compat): update migration plan to mark Phase 2 co…
robertpenner Jan 16, 2026
cf73532
refactor(react-calendar-compat): update animation constants to reflec…
robertpenner Jan 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# 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 2 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`

---

## 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` |

---

## References

- [Fluent UI Motion Documentation](https://react.fluentui.dev/?path=/docs/motion-introduction--docs)
- [react-motion-components-preview](../../react-motion-components-preview/)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"@fluentui/keyboard-keys": "^9.0.8",
"@fluentui/react-icons": "^2.0.245",
"@fluentui/react-jsx-runtime": "^9.3.4",
"@fluentui/react-motion": "^9.11.5",
"@fluentui/react-motion-components-preview": "^0.14.2",
"@fluentui/react-shared-contexts": "^9.26.0",
"@fluentui/react-tabster": "^9.26.11",
"@fluentui/react-theme": "^9.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ 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';
import { AnimationDirection } from '../../Calendar';

/**
* @internal
Expand Down Expand Up @@ -40,7 +42,7 @@ export const CalendarDay: React.FunctionComponent<CalendarDayProps> = props => {
onNavigateDate,
showWeekNumbers,
dateRangeType,
animationDirection,
animationDirection = AnimationDirection.Vertical,
} = props;

const classNames = useCalendarDayStyles_unstable({
Expand Down Expand Up @@ -69,9 +71,11 @@ export const CalendarDay: React.FunctionComponent<CalendarDayProps> = props => {
onKeyDown={onButtonKeyDown(onHeaderSelect)}
type="button"
>
<span aria-live="polite" aria-atomic="true">
{monthAndYear}
</span>
<HeaderFade navigationKey={monthAndYear}>
<span aria-live="polite" aria-atomic="true">
{monthAndYear}
</span>
</HeaderFade>
</HeaderButtonComponentType>
<CalendarDayNavigationButtons {...props} classNames={classNames} />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ 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/calendarMotions';
import { AnimationDirection } from '../../Calendar';

export interface DayInfo extends Day {
onSelected: () => void;
Expand Down Expand Up @@ -75,6 +77,7 @@ export const CalendarDayGrid: React.FunctionComponent<CalendarDayGridProps> = pr

const weeks = useWeeks(props, onSelectDate, getSetRefCallback);
const animateBackwards = useAnimateBackwards(weeks);

const [getWeekCornerStyles, calculateRoundedStyles] = useWeekCornerStyles(props);

React.useImperativeHandle(
Expand Down Expand Up @@ -130,7 +133,7 @@ export const CalendarDayGrid: React.FunctionComponent<CalendarDayGridProps> = pr
showWeekNumbers,
labelledBy,
lightenDaysOutsideNavigatedMonth,
animationDirection,
animationDirection = AnimationDirection.Vertical,
} = props;

const classNames = useCalendarDayGridStyles_unstable({
Expand All @@ -156,6 +159,8 @@ export const CalendarDayGrid: React.FunctionComponent<CalendarDayGridProps> = pr
} as const;

const arrowNavigationAttributes = useArrowNavigationGroup({ axis: 'grid' });
const firstWeek = weeks[0];
const finalWeek = weeks![weeks!.length - 1];

return (
<table
Expand All @@ -169,34 +174,40 @@ export const CalendarDayGrid: React.FunctionComponent<CalendarDayGridProps> = pr
>
<tbody>
<CalendarMonthHeaderRow {...props} classNames={classNames} weeks={weeks} />
<CalendarGridRow
{...props}
{...partialWeekProps}
week={weeks[0]}
weekIndex={-1}
rowClassName={classNames.firstTransitionWeek}
aria-role="presentation"
ariaHidden={true}
/>
{weeks!.slice(1, weeks!.length - 1).map((week: DayInfo[], weekIndex: number) => (
<DirectionalSlide key={'firstTransitionWeek_' + firstWeek[0].key} {...{ animationDirection, animateBackwards }}>
<CalendarGridRow
{...props}
{...partialWeekProps}
key={weekIndex}
week={week}
weekIndex={weekIndex}
rowClassName={classNames.weekRow}
week={firstWeek}
weekIndex={-1}
rowClassName={classNames.firstTransitionWeek}
aria-role="presentation"
ariaHidden={true}
/>
</DirectionalSlide>
{weeks!.slice(1, weeks!.length - 1).map((week: DayInfo[], weekIndex: number) => (
<DirectionalSlide key={weekIndex + '_' + week[0].key} {...{ animationDirection, animateBackwards }}>
<CalendarGridRow
{...props}
{...partialWeekProps}
key={weekIndex}
week={week}
weekIndex={weekIndex}
rowClassName={classNames.weekRow}
/>
</DirectionalSlide>
))}
<CalendarGridRow
{...props}
{...partialWeekProps}
week={weeks![weeks!.length - 1]}
weekIndex={-2}
rowClassName={classNames.lastTransitionWeek}
aria-role="presentation"
ariaHidden={true}
/>
<DirectionalSlide key={'lastTransitionWeek_' + finalWeek[0].key} {...{ animationDirection, animateBackwards }}>
<CalendarGridRow
{...props}
{...partialWeekProps}
week={finalWeek}
weekIndex={-2}
rowClassName={classNames.lastTransitionWeek}
aria-role="presentation"
ariaHidden={true}
/>
</DirectionalSlide>
</tbody>
</table>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import * as React from 'react';
import { getWeekNumbersInMonth } from '../../utils';
import { CalendarGridDayCell } from './CalendarGridDayCell';
Expand Down Expand Up @@ -28,7 +30,7 @@ export interface CalendarGridRowProps extends CalendarDayGridProps {
/**
* @internal
*/
export const CalendarGridRow: React.FunctionComponent<CalendarGridRowProps> = props => {
export const CalendarGridRow = React.forwardRef<HTMLTableRowElement, CalendarGridRowProps>((props, ref) => {
const {
ariaHidden,
classNames,
Expand All @@ -52,7 +54,7 @@ export const CalendarGridRow: React.FunctionComponent<CalendarGridRowProps> = pr
: '';

return (
<tr role={ariaRole} aria-hidden={ariaHidden} className={rowClassName} key={weekIndex + '_' + week[0].key}>
<tr ref={ref} role={ariaRole} aria-hidden={ariaHidden} className={rowClassName} key={weekIndex + '_' + week[0].key}>
{showWeekNumbers && weekNumbers && (
<th
className={classNames.weekNumberCell}
Expand All @@ -69,4 +71,6 @@ export const CalendarGridRow: React.FunctionComponent<CalendarGridRowProps> = pr
))}
</tr>
);
};
});

CalendarGridRow.displayName = 'CalendarGridRow';
Loading
Loading