/** * Shared animation tokens. Reanimated uses spring configs and easing curves * that mirror the original web easing values. */ import { Easing, LinearTransition } from 'react-native-reanimated'; /** The spring subset a layout transition needs to mirror a Moti spring. */ type SpringLayoutConfig = { stiffness: number; damping: number; mass: number }; // Cubic bezier control points (mirrored from web). Single source of truth so the // Moti-facing factory and the plain JS-thread function stay on the same curve. const EASE_OUT_POINTS = [0.16, 1, 0.3, 1] as const; const EASE_IN_OUT_POINTS = [0.77, 0, 0.175, 1] as const; const EASE_DRAWER_POINTS = [0.32, 0.72, 0, 1] as const; // Moti transitions consume these as the `easing` field — Reanimated's `bezier` // returns an EasingFunctionFactory that runs as a worklet on the UI thread. export const EASE_OUT = Easing.bezier(...EASE_OUT_POINTS); export const EASE_IN_OUT = Easing.bezier(...EASE_IN_OUT_POINTS); export const EASE_DRAWER = Easing.bezier(...EASE_DRAWER_POINTS); // Plain (t) => number form of EASE_OUT for JS-thread interpolation // (TextNumberTicker's requestAnimationFrame loop), where a factory isn't callable. export const EASE_OUT_FN = Easing.bezierFn(...EASE_OUT_POINTS); /** Press feedback on buttons and other tappable surfaces. */ export const SPRING_PRESS = { type: 'spring' as const, stiffness: 500, damping: 30, mass: 0.6, }; /** Content swaps — label/icon slots trading places inside a control. */ export const SPRING_SWAP = { type: 'spring' as const, stiffness: 460, damping: 30, mass: 0.55, }; /** Dock item icon/label scale — the `showLabels` toggle. Very stiff and * critically damped so the icon scale-up and caption reveal settle near-instantly * with no overshoot, reading as a fast, smooth size glide rather than a spring. */ export const SPRING_DOCK_SCALE = { type: 'spring' as const, stiffness: 800, damping: 36, mass: 0.4, }; /** Overlay panel entrances — modals and sheets summoned by pointer. */ export const SPRING_PANEL = { type: 'spring' as const, stiffness: 420, damping: 40, mass: 0.5, }; /** Shared-layout glides — pills, indicators and panels morphing between positions. */ export const SPRING_LAYOUT = { type: 'spring' as const, stiffness: 360, damping: 32, mass: 0.6, }; /** The content of an edge panel landing after it unrolls open (ActivityIsland's * bar). The one spring here tuned to overshoot — about 5% — because the size * change *is* the gesture: a damped glide reads as a panel being resized, while * a little bounce reads as something inflating and settling. It rides the * panel's *content*, not its height: layout transitions are keyframed rather * than sprung on web, so a spring only survives on style props. Entrances only — * an exit springs away from the screen and has nothing to settle onto. */ export const SPRING_UNROLL = { type: 'spring' as const, stiffness: 420, damping: 22, mass: 0.6, }; /** Cursor-follow physics for decorative mouse tracking (magnetic, tilt, dock). */ export const SPRING_MOUSE = { stiffness: 200, damping: 15, mass: 0.3, }; /** Drawer / sheet glide: a long, fully-damped tween reads smoother than a spring. */ export const DRAWER = { duration: 500, easing: EASE_DRAWER } as const; /** Stack spring for toast / list item layout animations. */ export const STACK_SPRING = { type: 'spring' as const, stiffness: 420, damping: 34, mass: 0.75, }; /** Heavy, deliberate thumb spring (switch toggles). */ export const THUMB_SPRING = { type: 'spring' as const, stiffness: 800, damping: 80, mass: 4, }; /** Content transitions. */ export const CONTENT_TRANSITION = { duration: 280, easing: EASE_OUT, } as const; /** * Builds a Fabric-safe Reanimated layout transition that mirrors a Moti spring * config. * * On the new architecture (Fabric), animating layout props (`width`, `height`, * `top`, `left`, `margin`, `padding`) through Moti's `animate`/`from`/`exit` * pipeline does not round-trip Yoga — a size morph collapses to 0×0 or snaps * instead of animating. Give the shell a static size and drive the change with * `layout={springLayout(...)}`; style props (`opacity`, `transform`, * `borderRadius`, color) keep animating through Moti. * * Pass one of the shared spring tokens above (e.g. `SPRING_LAYOUT`) so the * layout glide and any parallel Moti spring stay on the same physics instead of * carrying a second, hand-kept copy of the numbers. */ export function springLayout(spring: SpringLayoutConfig) { return LinearTransition.springify().damping(spring.damping).stiffness(spring.stiffness).mass(spring.mass); }