@fluentui/react-motion-components-preview 0.15.4 → 0.15.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -2
- package/lib/choreography/Stagger/useStaggerItemsVisibility.js +1 -0
- package/lib/choreography/Stagger/useStaggerItemsVisibility.js.map +1 -1
- package/lib-commonjs/choreography/Stagger/useStaggerItemsVisibility.js +1 -0
- package/lib-commonjs/choreography/Stagger/useStaggerItemsVisibility.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# @fluentui/react-motion-components-preview
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Pre-built Motion Components for [Fluent UI React](https://react.fluentui.dev/)**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> ⚠️ **Preview Package**: These components are in beta and APIs may change before stable release.
|
|
6
|
+
|
|
7
|
+
Ready-to-use presence components for common UI animation patterns, built on top of `@fluentui/react-motion`.
|
|
8
|
+
|
|
9
|
+
## Components
|
|
10
|
+
|
|
11
|
+
| Component | Description |
|
|
12
|
+
| ------------ | ---------------------------------------------------------- |
|
|
13
|
+
| **Fade** | Opacity transitions for tooltips, notifications, overlays |
|
|
14
|
+
| **Scale** | Size animations for popovers, menus, emphasis |
|
|
15
|
+
| **Collapse** | Height/width expansion for accordions, expandable sections |
|
|
16
|
+
| **Slide** | Directional movement for drawers, panels, carousels |
|
|
17
|
+
| **Blur** | Focus/defocus effects for backgrounds, depth |
|
|
18
|
+
| **Rotate** | 3D rotation for card flips, reveals |
|
|
19
|
+
| **Stagger** | Choreography for sequential list animations |
|
|
20
|
+
|
|
21
|
+
Each component (except Blur and Rotate) comes with **Snappy** (150ms) and **Relaxed** (250ms) timing variants.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @fluentui/react-motion-components-preview
|
|
27
|
+
# or
|
|
28
|
+
yarn add @fluentui/react-motion-components-preview
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { Fade, Scale, Slide, Collapse } from '@fluentui/react-motion-components-preview';
|
|
35
|
+
|
|
36
|
+
// Simple fade
|
|
37
|
+
function Tooltip({ visible, children }) {
|
|
38
|
+
return (
|
|
39
|
+
<Fade visible={visible}>
|
|
40
|
+
{children}
|
|
41
|
+
</Fade>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Slide from the right
|
|
46
|
+
function Drawer({ open, children }) {
|
|
47
|
+
return (
|
|
48
|
+
<Slide visible={open} outX="20px">
|
|
49
|
+
{children}
|
|
50
|
+
</Slide>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Use timing variants
|
|
55
|
+
import { FadeSnappy, ScaleRelaxed } from '@fluentui/react-motion-components-preview';
|
|
56
|
+
|
|
57
|
+
<FadeSnappy visible={show}>Quick feedback</FadeSnappy>
|
|
58
|
+
<ScaleRelaxed visible={show}>Smooth entrance</ScaleRelaxed>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### The `.In` and `.Out` Pattern
|
|
62
|
+
|
|
63
|
+
Every presence component includes one-way sub-components:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// One-way enter animation (plays on mount)
|
|
67
|
+
<Fade.In>
|
|
68
|
+
<div>Fades in once</div>
|
|
69
|
+
</Fade.In>
|
|
70
|
+
|
|
71
|
+
// One-way exit animation (plays on mount)
|
|
72
|
+
<Fade.Out>
|
|
73
|
+
<div>Fades out once</div>
|
|
74
|
+
</Fade.Out>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
📚 **[Full documentation](https://react.fluentui.dev/?path=/docs/motion-components-preview-introduction--docs)**
|
|
80
|
+
|
|
81
|
+
- [Introduction](https://react.fluentui.dev/?path=/docs/motion-components-preview-introduction--docs) — Overview of all components
|
|
82
|
+
- [Fade](https://react.fluentui.dev/?path=/docs/motion-components-preview-fade--docs)
|
|
83
|
+
- [Scale](https://react.fluentui.dev/?path=/docs/motion-components-preview-scale--docs)
|
|
84
|
+
- [Collapse](https://react.fluentui.dev/?path=/docs/motion-components-preview-collapse--docs)
|
|
85
|
+
- [Slide](https://react.fluentui.dev/?path=/docs/motion-components-preview-slide--docs)
|
|
86
|
+
- [Blur](https://react.fluentui.dev/?path=/docs/motion-components-preview-blur--docs)
|
|
87
|
+
- [Rotate](https://react.fluentui.dev/?path=/docs/motion-components-preview-rotate--docs)
|
|
88
|
+
- [Stagger](https://react.fluentui.dev/?path=/docs/motion-choreography-preview-stagger--docs)
|
|
89
|
+
- [Motion Atoms](https://react.fluentui.dev/?path=/docs/motion-components-preview-atoms--docs) — Building blocks for custom components
|
|
90
|
+
|
|
91
|
+
## Related
|
|
92
|
+
|
|
93
|
+
- **[@fluentui/react-motion](https://www.npmjs.com/package/@fluentui/react-motion)** — Core motion APIs for creating custom animations
|
|
@@ -58,6 +58,7 @@ import { staggerItemsVisibilityAtTime, DEFAULT_ITEM_DURATION } from './utils';
|
|
|
58
58
|
});
|
|
59
59
|
// Update visibility mapping when childMapping changes
|
|
60
60
|
React.useEffect(()=>{
|
|
61
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
61
62
|
setItemsVisibility((prev)=>{
|
|
62
63
|
const next = {};
|
|
63
64
|
const targetState = direction === 'enter';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/choreography/Stagger/useStaggerItemsVisibility.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useAnimationFrame, useEventCallback } from '@fluentui/react-utilities';\nimport type { StaggerProps } from './stagger-types';\nimport {\n staggerItemsVisibilityAtTime,\n type StaggerItemsVisibilityAtTimeParams,\n DEFAULT_ITEM_DURATION,\n type StaggerChildMapping,\n} from './utils';\n\nexport interface UseStaggerItemsVisibilityParams\n extends Pick<StaggerProps, 'onMotionFinish'>,\n Omit<StaggerItemsVisibilityAtTimeParams, 'elapsed' | 'itemCount'> {\n hideMode: StaggerProps['hideMode'];\n childMapping: StaggerChildMapping;\n}\n\n/**\n * Hook that tracks the visibility of a staggered sequence of items as time progresses.\n *\n * Behavior summary for all hide modes:\n * - On the first render, items are placed in their final state (enter => visible, exit => hidden)\n * and no animation runs.\n * - On subsequent renders when direction changes, items animate from the opposite state\n * to the final state over the stagger timeline.\n * - Changes to the `reversed` prop do not trigger re-animation; they only affect the order\n * during the next direction change animation.\n *\n * This hook uses child key mapping instead of item count to track individual items.\n * This allows it to correctly handle:\n * - Items being added and removed simultaneously (when count stays the same)\n * - Items being reordered\n * - Individual item identity across renders\n *\n * @param childMapping - Mapping of child keys to elements and indices\n * @param itemDelay - Milliseconds between the start of each item's animation\n * @param itemDuration - Milliseconds each item's animation lasts\n * @param direction - 'enter' (show items) or 'exit' (hide items)\n * @param reversed - Whether to reverse the stagger order (last item first)\n * @param onMotionFinish - Callback fired when the full stagger sequence completes\n * @param hideMode - How children's visibility is managed: 'visibleProp', 'visibilityStyle', or 'unmount'\n *\n * @returns An object with `itemsVisibility: Record<string, boolean>` indicating which items are currently visible by key\n */\nexport function useStaggerItemsVisibility({\n childMapping,\n itemDelay,\n itemDuration = DEFAULT_ITEM_DURATION,\n direction,\n reversed = false,\n onMotionFinish,\n hideMode = 'visibleProp',\n}: UseStaggerItemsVisibilityParams): { itemsVisibility: Record<string, boolean> } {\n const [requestAnimationFrame, cancelAnimationFrame] = useAnimationFrame();\n\n // Stabilize the callback reference to avoid re-triggering effects on every render\n const handleMotionFinish = useEventCallback(\n onMotionFinish ??\n (() => {\n return;\n }),\n );\n\n // Track animation state independently of child changes\n const [animationKey, setAnimationKey] = React.useState(0);\n const prevDirection = React.useRef(direction);\n\n // Only trigger new animation when direction actually changes, not when children change\n React.useEffect(() => {\n if (prevDirection.current !== direction) {\n setAnimationKey(prev => prev + 1);\n prevDirection.current = direction;\n }\n }, [direction]);\n\n // State: visibility mapping for all items by key\n const [itemsVisibility, setItemsVisibility] = React.useState<Record<string, boolean>>(() => {\n const initial: Record<string, boolean> = {};\n // All hide modes start in final state: visible for 'enter', hidden for 'exit'\n const initialState = direction === 'enter';\n Object.keys(childMapping).forEach(key => {\n initial[key] = initialState;\n });\n return initial;\n });\n\n // Update visibility mapping when childMapping changes\n React.useEffect(() => {\n setItemsVisibility(prev => {\n const next: Record<string, boolean> = {};\n const targetState = direction === 'enter';\n\n // Add or update items from new mapping\n Object.keys(childMapping).forEach(key => {\n if (key in prev) {\n // Existing item - preserve its visibility state\n next[key] = prev[key];\n } else {\n // New item - set to target state\n next[key] = targetState;\n }\n });\n\n // Note: Items that were in prev but not in childMapping are automatically removed\n // because we only iterate over keys in childMapping\n\n return next;\n });\n }, [childMapping, direction]);\n\n // Refs: animation timing and control\n const startTimeRef = React.useRef<number | null>(null);\n const frameRef = React.useRef<number | null>(null);\n const finishedRef = React.useRef(false);\n const isFirstRender = React.useRef(true);\n\n // Use ref to avoid re-running the animation when child mapping changes\n const childMappingRef = React.useRef(childMapping);\n\n // Update childMapping ref whenever it changes\n React.useEffect(() => {\n childMappingRef.current = childMapping;\n }, [childMapping]);\n\n // Use ref for reversed to avoid re-running animation when it changes\n const reversedRef = React.useRef(reversed);\n\n // Update reversed ref whenever it changes\n React.useEffect(() => {\n reversedRef.current = reversed;\n }, [reversed]);\n\n // ====== ANIMATION EFFECT ======\n\n React.useEffect(() => {\n let cancelled = false;\n startTimeRef.current = null;\n finishedRef.current = false;\n\n // All hide modes skip animation on first render - items are already in their final state\n if (isFirstRender.current) {\n isFirstRender.current = false;\n // Items are already in their final state from useState, no animation needed\n handleMotionFinish();\n return; // No cleanup needed for first render\n }\n\n // For animations after first render, start from the opposite of the final state\n // - Enter animation: start hidden (false), animate to visible (true)\n // - Exit animation: start visible (true), animate to hidden (false)\n const startState = direction === 'exit';\n // Use childMappingRef.current to avoid adding childMapping to dependencies\n const initialVisibility: Record<string, boolean> = {};\n Object.keys(childMappingRef.current).forEach(key => {\n initialVisibility[key] = startState;\n });\n setItemsVisibility(initialVisibility);\n\n // Animation loop: update visibility on each frame until complete\n const tick = (now: number) => {\n if (cancelled) {\n return;\n }\n if (startTimeRef.current === null) {\n startTimeRef.current = now;\n }\n const elapsed = now - (startTimeRef.current as number);\n\n const childKeys = Object.keys(childMappingRef.current);\n const itemCount = childKeys.length;\n\n const result = staggerItemsVisibilityAtTime({\n itemCount,\n elapsed,\n itemDelay,\n itemDuration,\n direction,\n reversed: reversedRef.current,\n });\n\n // Convert boolean array to keyed object\n const nextVisibility: Record<string, boolean> = {};\n childKeys.forEach((key, idx) => {\n nextVisibility[key] = result.itemsVisibility[idx];\n });\n\n setItemsVisibility(nextVisibility);\n\n if (elapsed < result.totalDuration) {\n frameRef.current = requestAnimationFrame(tick);\n } else if (!finishedRef.current) {\n finishedRef.current = true;\n handleMotionFinish();\n }\n };\n\n frameRef.current = requestAnimationFrame(tick);\n return () => {\n cancelled = true;\n if (frameRef.current) {\n cancelAnimationFrame();\n }\n };\n }, [\n animationKey,\n itemDelay,\n itemDuration,\n direction,\n requestAnimationFrame,\n cancelAnimationFrame,\n handleMotionFinish,\n ]);\n\n return { itemsVisibility };\n}\n"],"names":["React","useAnimationFrame","useEventCallback","staggerItemsVisibilityAtTime","DEFAULT_ITEM_DURATION","useStaggerItemsVisibility","childMapping","itemDelay","itemDuration","direction","reversed","onMotionFinish","hideMode","requestAnimationFrame","cancelAnimationFrame","handleMotionFinish","animationKey","setAnimationKey","useState","prevDirection","useRef","useEffect","current","prev","itemsVisibility","setItemsVisibility","initial","initialState","Object","keys","forEach","key","next","targetState","startTimeRef","frameRef","finishedRef","isFirstRender","childMappingRef","reversedRef","cancelled","startState","initialVisibility","tick","now","elapsed","childKeys","itemCount","length","result","nextVisibility","idx","totalDuration"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/choreography/Stagger/useStaggerItemsVisibility.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useAnimationFrame, useEventCallback } from '@fluentui/react-utilities';\nimport type { StaggerProps } from './stagger-types';\nimport {\n staggerItemsVisibilityAtTime,\n type StaggerItemsVisibilityAtTimeParams,\n DEFAULT_ITEM_DURATION,\n type StaggerChildMapping,\n} from './utils';\n\nexport interface UseStaggerItemsVisibilityParams\n extends Pick<StaggerProps, 'onMotionFinish'>,\n Omit<StaggerItemsVisibilityAtTimeParams, 'elapsed' | 'itemCount'> {\n hideMode: StaggerProps['hideMode'];\n childMapping: StaggerChildMapping;\n}\n\n/**\n * Hook that tracks the visibility of a staggered sequence of items as time progresses.\n *\n * Behavior summary for all hide modes:\n * - On the first render, items are placed in their final state (enter => visible, exit => hidden)\n * and no animation runs.\n * - On subsequent renders when direction changes, items animate from the opposite state\n * to the final state over the stagger timeline.\n * - Changes to the `reversed` prop do not trigger re-animation; they only affect the order\n * during the next direction change animation.\n *\n * This hook uses child key mapping instead of item count to track individual items.\n * This allows it to correctly handle:\n * - Items being added and removed simultaneously (when count stays the same)\n * - Items being reordered\n * - Individual item identity across renders\n *\n * @param childMapping - Mapping of child keys to elements and indices\n * @param itemDelay - Milliseconds between the start of each item's animation\n * @param itemDuration - Milliseconds each item's animation lasts\n * @param direction - 'enter' (show items) or 'exit' (hide items)\n * @param reversed - Whether to reverse the stagger order (last item first)\n * @param onMotionFinish - Callback fired when the full stagger sequence completes\n * @param hideMode - How children's visibility is managed: 'visibleProp', 'visibilityStyle', or 'unmount'\n *\n * @returns An object with `itemsVisibility: Record<string, boolean>` indicating which items are currently visible by key\n */\nexport function useStaggerItemsVisibility({\n childMapping,\n itemDelay,\n itemDuration = DEFAULT_ITEM_DURATION,\n direction,\n reversed = false,\n onMotionFinish,\n hideMode = 'visibleProp',\n}: UseStaggerItemsVisibilityParams): { itemsVisibility: Record<string, boolean> } {\n const [requestAnimationFrame, cancelAnimationFrame] = useAnimationFrame();\n\n // Stabilize the callback reference to avoid re-triggering effects on every render\n const handleMotionFinish = useEventCallback(\n onMotionFinish ??\n (() => {\n return;\n }),\n );\n\n // Track animation state independently of child changes\n const [animationKey, setAnimationKey] = React.useState(0);\n const prevDirection = React.useRef(direction);\n\n // Only trigger new animation when direction actually changes, not when children change\n React.useEffect(() => {\n if (prevDirection.current !== direction) {\n setAnimationKey(prev => prev + 1);\n prevDirection.current = direction;\n }\n }, [direction]);\n\n // State: visibility mapping for all items by key\n const [itemsVisibility, setItemsVisibility] = React.useState<Record<string, boolean>>(() => {\n const initial: Record<string, boolean> = {};\n // All hide modes start in final state: visible for 'enter', hidden for 'exit'\n const initialState = direction === 'enter';\n Object.keys(childMapping).forEach(key => {\n initial[key] = initialState;\n });\n return initial;\n });\n\n // Update visibility mapping when childMapping changes\n React.useEffect(() => {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setItemsVisibility(prev => {\n const next: Record<string, boolean> = {};\n const targetState = direction === 'enter';\n\n // Add or update items from new mapping\n Object.keys(childMapping).forEach(key => {\n if (key in prev) {\n // Existing item - preserve its visibility state\n next[key] = prev[key];\n } else {\n // New item - set to target state\n next[key] = targetState;\n }\n });\n\n // Note: Items that were in prev but not in childMapping are automatically removed\n // because we only iterate over keys in childMapping\n\n return next;\n });\n }, [childMapping, direction]);\n\n // Refs: animation timing and control\n const startTimeRef = React.useRef<number | null>(null);\n const frameRef = React.useRef<number | null>(null);\n const finishedRef = React.useRef(false);\n const isFirstRender = React.useRef(true);\n\n // Use ref to avoid re-running the animation when child mapping changes\n const childMappingRef = React.useRef(childMapping);\n\n // Update childMapping ref whenever it changes\n React.useEffect(() => {\n childMappingRef.current = childMapping;\n }, [childMapping]);\n\n // Use ref for reversed to avoid re-running animation when it changes\n const reversedRef = React.useRef(reversed);\n\n // Update reversed ref whenever it changes\n React.useEffect(() => {\n reversedRef.current = reversed;\n }, [reversed]);\n\n // ====== ANIMATION EFFECT ======\n\n React.useEffect(() => {\n let cancelled = false;\n startTimeRef.current = null;\n finishedRef.current = false;\n\n // All hide modes skip animation on first render - items are already in their final state\n if (isFirstRender.current) {\n isFirstRender.current = false;\n // Items are already in their final state from useState, no animation needed\n handleMotionFinish();\n return; // No cleanup needed for first render\n }\n\n // For animations after first render, start from the opposite of the final state\n // - Enter animation: start hidden (false), animate to visible (true)\n // - Exit animation: start visible (true), animate to hidden (false)\n const startState = direction === 'exit';\n // Use childMappingRef.current to avoid adding childMapping to dependencies\n const initialVisibility: Record<string, boolean> = {};\n Object.keys(childMappingRef.current).forEach(key => {\n initialVisibility[key] = startState;\n });\n setItemsVisibility(initialVisibility);\n\n // Animation loop: update visibility on each frame until complete\n const tick = (now: number) => {\n if (cancelled) {\n return;\n }\n if (startTimeRef.current === null) {\n startTimeRef.current = now;\n }\n const elapsed = now - (startTimeRef.current as number);\n\n const childKeys = Object.keys(childMappingRef.current);\n const itemCount = childKeys.length;\n\n const result = staggerItemsVisibilityAtTime({\n itemCount,\n elapsed,\n itemDelay,\n itemDuration,\n direction,\n reversed: reversedRef.current,\n });\n\n // Convert boolean array to keyed object\n const nextVisibility: Record<string, boolean> = {};\n childKeys.forEach((key, idx) => {\n nextVisibility[key] = result.itemsVisibility[idx];\n });\n\n setItemsVisibility(nextVisibility);\n\n if (elapsed < result.totalDuration) {\n frameRef.current = requestAnimationFrame(tick);\n } else if (!finishedRef.current) {\n finishedRef.current = true;\n handleMotionFinish();\n }\n };\n\n frameRef.current = requestAnimationFrame(tick);\n return () => {\n cancelled = true;\n if (frameRef.current) {\n cancelAnimationFrame();\n }\n };\n }, [\n animationKey,\n itemDelay,\n itemDuration,\n direction,\n requestAnimationFrame,\n cancelAnimationFrame,\n handleMotionFinish,\n ]);\n\n return { itemsVisibility };\n}\n"],"names":["React","useAnimationFrame","useEventCallback","staggerItemsVisibilityAtTime","DEFAULT_ITEM_DURATION","useStaggerItemsVisibility","childMapping","itemDelay","itemDuration","direction","reversed","onMotionFinish","hideMode","requestAnimationFrame","cancelAnimationFrame","handleMotionFinish","animationKey","setAnimationKey","useState","prevDirection","useRef","useEffect","current","prev","itemsVisibility","setItemsVisibility","initial","initialState","Object","keys","forEach","key","next","targetState","startTimeRef","frameRef","finishedRef","isFirstRender","childMappingRef","reversedRef","cancelled","startState","initialVisibility","tick","now","elapsed","childKeys","itemCount","length","result","nextVisibility","idx","totalDuration"],"mappings":"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"}
|
|
@@ -43,6 +43,7 @@ function useStaggerItemsVisibility({ childMapping, itemDelay, itemDuration = _ut
|
|
|
43
43
|
});
|
|
44
44
|
// Update visibility mapping when childMapping changes
|
|
45
45
|
_react.useEffect(()=>{
|
|
46
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
46
47
|
setItemsVisibility((prev)=>{
|
|
47
48
|
const next = {};
|
|
48
49
|
const targetState = direction === 'enter';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/choreography/Stagger/useStaggerItemsVisibility.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useAnimationFrame, useEventCallback } from '@fluentui/react-utilities';\nimport type { StaggerProps } from './stagger-types';\nimport {\n staggerItemsVisibilityAtTime,\n type StaggerItemsVisibilityAtTimeParams,\n DEFAULT_ITEM_DURATION,\n type StaggerChildMapping,\n} from './utils';\n\nexport interface UseStaggerItemsVisibilityParams\n extends Pick<StaggerProps, 'onMotionFinish'>,\n Omit<StaggerItemsVisibilityAtTimeParams, 'elapsed' | 'itemCount'> {\n hideMode: StaggerProps['hideMode'];\n childMapping: StaggerChildMapping;\n}\n\n/**\n * Hook that tracks the visibility of a staggered sequence of items as time progresses.\n *\n * Behavior summary for all hide modes:\n * - On the first render, items are placed in their final state (enter => visible, exit => hidden)\n * and no animation runs.\n * - On subsequent renders when direction changes, items animate from the opposite state\n * to the final state over the stagger timeline.\n * - Changes to the `reversed` prop do not trigger re-animation; they only affect the order\n * during the next direction change animation.\n *\n * This hook uses child key mapping instead of item count to track individual items.\n * This allows it to correctly handle:\n * - Items being added and removed simultaneously (when count stays the same)\n * - Items being reordered\n * - Individual item identity across renders\n *\n * @param childMapping - Mapping of child keys to elements and indices\n * @param itemDelay - Milliseconds between the start of each item's animation\n * @param itemDuration - Milliseconds each item's animation lasts\n * @param direction - 'enter' (show items) or 'exit' (hide items)\n * @param reversed - Whether to reverse the stagger order (last item first)\n * @param onMotionFinish - Callback fired when the full stagger sequence completes\n * @param hideMode - How children's visibility is managed: 'visibleProp', 'visibilityStyle', or 'unmount'\n *\n * @returns An object with `itemsVisibility: Record<string, boolean>` indicating which items are currently visible by key\n */\nexport function useStaggerItemsVisibility({\n childMapping,\n itemDelay,\n itemDuration = DEFAULT_ITEM_DURATION,\n direction,\n reversed = false,\n onMotionFinish,\n hideMode = 'visibleProp',\n}: UseStaggerItemsVisibilityParams): { itemsVisibility: Record<string, boolean> } {\n const [requestAnimationFrame, cancelAnimationFrame] = useAnimationFrame();\n\n // Stabilize the callback reference to avoid re-triggering effects on every render\n const handleMotionFinish = useEventCallback(\n onMotionFinish ??\n (() => {\n return;\n }),\n );\n\n // Track animation state independently of child changes\n const [animationKey, setAnimationKey] = React.useState(0);\n const prevDirection = React.useRef(direction);\n\n // Only trigger new animation when direction actually changes, not when children change\n React.useEffect(() => {\n if (prevDirection.current !== direction) {\n setAnimationKey(prev => prev + 1);\n prevDirection.current = direction;\n }\n }, [direction]);\n\n // State: visibility mapping for all items by key\n const [itemsVisibility, setItemsVisibility] = React.useState<Record<string, boolean>>(() => {\n const initial: Record<string, boolean> = {};\n // All hide modes start in final state: visible for 'enter', hidden for 'exit'\n const initialState = direction === 'enter';\n Object.keys(childMapping).forEach(key => {\n initial[key] = initialState;\n });\n return initial;\n });\n\n // Update visibility mapping when childMapping changes\n React.useEffect(() => {\n setItemsVisibility(prev => {\n const next: Record<string, boolean> = {};\n const targetState = direction === 'enter';\n\n // Add or update items from new mapping\n Object.keys(childMapping).forEach(key => {\n if (key in prev) {\n // Existing item - preserve its visibility state\n next[key] = prev[key];\n } else {\n // New item - set to target state\n next[key] = targetState;\n }\n });\n\n // Note: Items that were in prev but not in childMapping are automatically removed\n // because we only iterate over keys in childMapping\n\n return next;\n });\n }, [childMapping, direction]);\n\n // Refs: animation timing and control\n const startTimeRef = React.useRef<number | null>(null);\n const frameRef = React.useRef<number | null>(null);\n const finishedRef = React.useRef(false);\n const isFirstRender = React.useRef(true);\n\n // Use ref to avoid re-running the animation when child mapping changes\n const childMappingRef = React.useRef(childMapping);\n\n // Update childMapping ref whenever it changes\n React.useEffect(() => {\n childMappingRef.current = childMapping;\n }, [childMapping]);\n\n // Use ref for reversed to avoid re-running animation when it changes\n const reversedRef = React.useRef(reversed);\n\n // Update reversed ref whenever it changes\n React.useEffect(() => {\n reversedRef.current = reversed;\n }, [reversed]);\n\n // ====== ANIMATION EFFECT ======\n\n React.useEffect(() => {\n let cancelled = false;\n startTimeRef.current = null;\n finishedRef.current = false;\n\n // All hide modes skip animation on first render - items are already in their final state\n if (isFirstRender.current) {\n isFirstRender.current = false;\n // Items are already in their final state from useState, no animation needed\n handleMotionFinish();\n return; // No cleanup needed for first render\n }\n\n // For animations after first render, start from the opposite of the final state\n // - Enter animation: start hidden (false), animate to visible (true)\n // - Exit animation: start visible (true), animate to hidden (false)\n const startState = direction === 'exit';\n // Use childMappingRef.current to avoid adding childMapping to dependencies\n const initialVisibility: Record<string, boolean> = {};\n Object.keys(childMappingRef.current).forEach(key => {\n initialVisibility[key] = startState;\n });\n setItemsVisibility(initialVisibility);\n\n // Animation loop: update visibility on each frame until complete\n const tick = (now: number) => {\n if (cancelled) {\n return;\n }\n if (startTimeRef.current === null) {\n startTimeRef.current = now;\n }\n const elapsed = now - (startTimeRef.current as number);\n\n const childKeys = Object.keys(childMappingRef.current);\n const itemCount = childKeys.length;\n\n const result = staggerItemsVisibilityAtTime({\n itemCount,\n elapsed,\n itemDelay,\n itemDuration,\n direction,\n reversed: reversedRef.current,\n });\n\n // Convert boolean array to keyed object\n const nextVisibility: Record<string, boolean> = {};\n childKeys.forEach((key, idx) => {\n nextVisibility[key] = result.itemsVisibility[idx];\n });\n\n setItemsVisibility(nextVisibility);\n\n if (elapsed < result.totalDuration) {\n frameRef.current = requestAnimationFrame(tick);\n } else if (!finishedRef.current) {\n finishedRef.current = true;\n handleMotionFinish();\n }\n };\n\n frameRef.current = requestAnimationFrame(tick);\n return () => {\n cancelled = true;\n if (frameRef.current) {\n cancelAnimationFrame();\n }\n };\n }, [\n animationKey,\n itemDelay,\n itemDuration,\n direction,\n requestAnimationFrame,\n cancelAnimationFrame,\n handleMotionFinish,\n ]);\n\n return { itemsVisibility };\n}\n"],"names":["useStaggerItemsVisibility","childMapping","itemDelay","itemDuration","DEFAULT_ITEM_DURATION","direction","reversed","onMotionFinish","hideMode","requestAnimationFrame","cancelAnimationFrame","useAnimationFrame","handleMotionFinish","useEventCallback","animationKey","setAnimationKey","React","useState","prevDirection","useRef","useEffect","current","prev","itemsVisibility","setItemsVisibility","initial","initialState","Object","keys","forEach","key","next","targetState","startTimeRef","frameRef","finishedRef","isFirstRender","childMappingRef","reversedRef","cancelled","startState","initialVisibility","tick","now","elapsed","childKeys","itemCount","length","result","staggerItemsVisibilityAtTime","nextVisibility","idx","totalDuration"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/choreography/Stagger/useStaggerItemsVisibility.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useAnimationFrame, useEventCallback } from '@fluentui/react-utilities';\nimport type { StaggerProps } from './stagger-types';\nimport {\n staggerItemsVisibilityAtTime,\n type StaggerItemsVisibilityAtTimeParams,\n DEFAULT_ITEM_DURATION,\n type StaggerChildMapping,\n} from './utils';\n\nexport interface UseStaggerItemsVisibilityParams\n extends Pick<StaggerProps, 'onMotionFinish'>,\n Omit<StaggerItemsVisibilityAtTimeParams, 'elapsed' | 'itemCount'> {\n hideMode: StaggerProps['hideMode'];\n childMapping: StaggerChildMapping;\n}\n\n/**\n * Hook that tracks the visibility of a staggered sequence of items as time progresses.\n *\n * Behavior summary for all hide modes:\n * - On the first render, items are placed in their final state (enter => visible, exit => hidden)\n * and no animation runs.\n * - On subsequent renders when direction changes, items animate from the opposite state\n * to the final state over the stagger timeline.\n * - Changes to the `reversed` prop do not trigger re-animation; they only affect the order\n * during the next direction change animation.\n *\n * This hook uses child key mapping instead of item count to track individual items.\n * This allows it to correctly handle:\n * - Items being added and removed simultaneously (when count stays the same)\n * - Items being reordered\n * - Individual item identity across renders\n *\n * @param childMapping - Mapping of child keys to elements and indices\n * @param itemDelay - Milliseconds between the start of each item's animation\n * @param itemDuration - Milliseconds each item's animation lasts\n * @param direction - 'enter' (show items) or 'exit' (hide items)\n * @param reversed - Whether to reverse the stagger order (last item first)\n * @param onMotionFinish - Callback fired when the full stagger sequence completes\n * @param hideMode - How children's visibility is managed: 'visibleProp', 'visibilityStyle', or 'unmount'\n *\n * @returns An object with `itemsVisibility: Record<string, boolean>` indicating which items are currently visible by key\n */\nexport function useStaggerItemsVisibility({\n childMapping,\n itemDelay,\n itemDuration = DEFAULT_ITEM_DURATION,\n direction,\n reversed = false,\n onMotionFinish,\n hideMode = 'visibleProp',\n}: UseStaggerItemsVisibilityParams): { itemsVisibility: Record<string, boolean> } {\n const [requestAnimationFrame, cancelAnimationFrame] = useAnimationFrame();\n\n // Stabilize the callback reference to avoid re-triggering effects on every render\n const handleMotionFinish = useEventCallback(\n onMotionFinish ??\n (() => {\n return;\n }),\n );\n\n // Track animation state independently of child changes\n const [animationKey, setAnimationKey] = React.useState(0);\n const prevDirection = React.useRef(direction);\n\n // Only trigger new animation when direction actually changes, not when children change\n React.useEffect(() => {\n if (prevDirection.current !== direction) {\n setAnimationKey(prev => prev + 1);\n prevDirection.current = direction;\n }\n }, [direction]);\n\n // State: visibility mapping for all items by key\n const [itemsVisibility, setItemsVisibility] = React.useState<Record<string, boolean>>(() => {\n const initial: Record<string, boolean> = {};\n // All hide modes start in final state: visible for 'enter', hidden for 'exit'\n const initialState = direction === 'enter';\n Object.keys(childMapping).forEach(key => {\n initial[key] = initialState;\n });\n return initial;\n });\n\n // Update visibility mapping when childMapping changes\n React.useEffect(() => {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setItemsVisibility(prev => {\n const next: Record<string, boolean> = {};\n const targetState = direction === 'enter';\n\n // Add or update items from new mapping\n Object.keys(childMapping).forEach(key => {\n if (key in prev) {\n // Existing item - preserve its visibility state\n next[key] = prev[key];\n } else {\n // New item - set to target state\n next[key] = targetState;\n }\n });\n\n // Note: Items that were in prev but not in childMapping are automatically removed\n // because we only iterate over keys in childMapping\n\n return next;\n });\n }, [childMapping, direction]);\n\n // Refs: animation timing and control\n const startTimeRef = React.useRef<number | null>(null);\n const frameRef = React.useRef<number | null>(null);\n const finishedRef = React.useRef(false);\n const isFirstRender = React.useRef(true);\n\n // Use ref to avoid re-running the animation when child mapping changes\n const childMappingRef = React.useRef(childMapping);\n\n // Update childMapping ref whenever it changes\n React.useEffect(() => {\n childMappingRef.current = childMapping;\n }, [childMapping]);\n\n // Use ref for reversed to avoid re-running animation when it changes\n const reversedRef = React.useRef(reversed);\n\n // Update reversed ref whenever it changes\n React.useEffect(() => {\n reversedRef.current = reversed;\n }, [reversed]);\n\n // ====== ANIMATION EFFECT ======\n\n React.useEffect(() => {\n let cancelled = false;\n startTimeRef.current = null;\n finishedRef.current = false;\n\n // All hide modes skip animation on first render - items are already in their final state\n if (isFirstRender.current) {\n isFirstRender.current = false;\n // Items are already in their final state from useState, no animation needed\n handleMotionFinish();\n return; // No cleanup needed for first render\n }\n\n // For animations after first render, start from the opposite of the final state\n // - Enter animation: start hidden (false), animate to visible (true)\n // - Exit animation: start visible (true), animate to hidden (false)\n const startState = direction === 'exit';\n // Use childMappingRef.current to avoid adding childMapping to dependencies\n const initialVisibility: Record<string, boolean> = {};\n Object.keys(childMappingRef.current).forEach(key => {\n initialVisibility[key] = startState;\n });\n setItemsVisibility(initialVisibility);\n\n // Animation loop: update visibility on each frame until complete\n const tick = (now: number) => {\n if (cancelled) {\n return;\n }\n if (startTimeRef.current === null) {\n startTimeRef.current = now;\n }\n const elapsed = now - (startTimeRef.current as number);\n\n const childKeys = Object.keys(childMappingRef.current);\n const itemCount = childKeys.length;\n\n const result = staggerItemsVisibilityAtTime({\n itemCount,\n elapsed,\n itemDelay,\n itemDuration,\n direction,\n reversed: reversedRef.current,\n });\n\n // Convert boolean array to keyed object\n const nextVisibility: Record<string, boolean> = {};\n childKeys.forEach((key, idx) => {\n nextVisibility[key] = result.itemsVisibility[idx];\n });\n\n setItemsVisibility(nextVisibility);\n\n if (elapsed < result.totalDuration) {\n frameRef.current = requestAnimationFrame(tick);\n } else if (!finishedRef.current) {\n finishedRef.current = true;\n handleMotionFinish();\n }\n };\n\n frameRef.current = requestAnimationFrame(tick);\n return () => {\n cancelled = true;\n if (frameRef.current) {\n cancelAnimationFrame();\n }\n };\n }, [\n animationKey,\n itemDelay,\n itemDuration,\n direction,\n requestAnimationFrame,\n cancelAnimationFrame,\n handleMotionFinish,\n ]);\n\n return { itemsVisibility };\n}\n"],"names":["useStaggerItemsVisibility","childMapping","itemDelay","itemDuration","DEFAULT_ITEM_DURATION","direction","reversed","onMotionFinish","hideMode","requestAnimationFrame","cancelAnimationFrame","useAnimationFrame","handleMotionFinish","useEventCallback","animationKey","setAnimationKey","React","useState","prevDirection","useRef","useEffect","current","prev","itemsVisibility","setItemsVisibility","initial","initialState","Object","keys","forEach","key","next","targetState","startTimeRef","frameRef","finishedRef","isFirstRender","childMappingRef","reversedRef","cancelled","startState","initialVisibility","tick","now","elapsed","childKeys","itemCount","length","result","staggerItemsVisibilityAtTime","nextVisibility","idx","totalDuration"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluentui/react-motion-components-preview",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.5",
|
|
4
4
|
"description": "A preview package for Fluent UI motion components, providing a collection of components",
|
|
5
5
|
"main": "lib-commonjs/index.js",
|
|
6
6
|
"module": "lib/index.js",
|