{"$schema":"https://ui.logic2b.com/schema/registry-item.json","name":"motion","type":"registry:ui","title":"Motion","description":"Enter/exit/hover animation recipes on tokens, built on tw-animate-css (zero runtime deps). Ships the <Motion> primitive (plays an enter preset on mount, with duration/delay/hover props and asChild) plus the recipe maps and helpers (motionEnter, motionExit, motionHover) to drop the same effects onto any element or a Radix data-[state] node. Presets: fade, fade-up/down/left/right, scale, blur. Respects prefers-reduced-motion.","dependencies":["radix-ui"],"registryDependencies":["utils"],"files":[{"path":"ui/motion.tsx","type":"registry:ui","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Motion presets — enter/exit/hover recipes expressed as token-driven class\n * strings on top of `tw-animate-css` (zero runtime deps). The `<Motion>`\n * primitive plays an enter recipe once on mount; the exported maps and helpers\n * let you drop the same recipes onto anything else (a Radix `data-[state]`\n * element, a hand-rolled transition).\n *\n * Timing rides the CSS custom properties tw-animate-css reads:\n *  - default duration/easing come from the static `duration-*`/`ease-*` classes\n *    baked into `motionEnter`,\n *  - the `duration`/`delay` props override them per-instance via\n *    `--tw-animation-duration` / `--tw-animation-delay` (no arbitrary Tailwind\n *    class is generated at runtime, so it works with any value).\n *\n * Everything degrades under `prefers-reduced-motion`: the enter animation is\n * dropped (content shows immediately) and hover transitions are disabled.\n */\n\nexport type MotionPreset =\n  | \"fade\"\n  | \"fade-up\"\n  | \"fade-down\"\n  | \"fade-left\"\n  | \"fade-right\"\n  | \"scale\"\n  | \"blur\"\n\nexport type MotionHover = \"lift\" | \"sink\" | \"scale\" | \"glow\"\n\n/** Enter recipes: tw-animate-css `animate-in` sets the keyframe `from` state,\n *  the element animates to its natural rendered state. */\nexport const motionEnterPresets: Record<MotionPreset, string> = {\n  fade: \"fade-in\",\n  \"fade-up\": \"fade-in slide-in-from-bottom-3\",\n  \"fade-down\": \"fade-in slide-in-from-top-3\",\n  \"fade-left\": \"fade-in slide-in-from-right-3\",\n  \"fade-right\": \"fade-in slide-in-from-left-3\",\n  scale: \"fade-in zoom-in-95\",\n  blur: \"fade-in blur-in-8\",\n}\n\n/** Exit recipes: the mirror of each enter preset, for `animate-out` on a\n *  Radix `data-[state=closed]` element. */\nexport const motionExitPresets: Record<MotionPreset, string> = {\n  fade: \"fade-out\",\n  \"fade-up\": \"fade-out slide-out-to-top-3\",\n  \"fade-down\": \"fade-out slide-out-to-bottom-3\",\n  \"fade-left\": \"fade-out slide-out-to-left-3\",\n  \"fade-right\": \"fade-out slide-out-to-right-3\",\n  scale: \"fade-out zoom-out-95\",\n  blur: \"fade-out blur-out-8\",\n}\n\n/** Hover recipes: plain Tailwind transitions, no keyframes. */\nexport const motionHoverPresets: Record<MotionHover, string> = {\n  lift: \"transition-transform duration-200 ease-out hover:-translate-y-1\",\n  sink: \"transition-transform duration-200 ease-out hover:translate-y-0.5\",\n  scale: \"transition-transform duration-200 ease-out hover:scale-[1.03]\",\n  glow: \"transition-shadow duration-200 ease-out hover:shadow-lg\",\n}\n\n/** Build the class string for an enter recipe (plays on mount). */\nexport function motionEnter(preset: MotionPreset, className?: string) {\n  return cn(\n    \"animate-in fill-mode-both duration-500 ease-out motion-reduce:animate-none\",\n    motionEnterPresets[preset],\n    className\n  )\n}\n\n/** Build the class string for an exit recipe (pair with `animate-out`). */\nexport function motionExit(preset: MotionPreset, className?: string) {\n  return cn(\n    \"animate-out fill-mode-both duration-300 ease-in motion-reduce:animate-none\",\n    motionExitPresets[preset],\n    className\n  )\n}\n\n/** Build the class string for a hover recipe. */\nexport function motionHover(hover: MotionHover, className?: string) {\n  return cn(\"motion-reduce:transition-none\", motionHoverPresets[hover], className)\n}\n\n/** Per-instance timing overrides, applied through the custom properties\n *  tw-animate-css reads so any numeric value works without a Tailwind class. */\nfunction motionVars(duration?: number, delay?: number): React.CSSProperties {\n  const style: Record<string, string> = {}\n  if (duration != null) style[\"--tw-animation-duration\"] = `${duration}ms`\n  if (delay != null) style[\"--tw-animation-delay\"] = `${delay}ms`\n  return style as React.CSSProperties\n}\n\nexport interface MotionProps extends React.ComponentProps<\"div\"> {\n  /** Enter recipe to play on mount. Default `\"fade\"`. */\n  preset?: MotionPreset\n  /** Optional hover recipe applied to the same element. */\n  hover?: MotionHover\n  /** Enter duration in milliseconds. Overrides the 500ms default. */\n  duration?: number\n  /** Enter delay in milliseconds — the knob for staggering a list. */\n  delay?: number\n  /** Merge props onto the single child instead of rendering a wrapper. */\n  asChild?: boolean\n}\n\nfunction Motion({\n  className,\n  preset = \"fade\",\n  hover,\n  duration,\n  delay,\n  asChild = false,\n  style,\n  ...props\n}: MotionProps) {\n  const Comp = asChild ? Slot.Root : \"div\"\n\n  return (\n    <Comp\n      data-slot=\"motion\"\n      className={cn(motionEnter(preset), hover && motionHover(hover), className)}\n      style={{ ...motionVars(duration, delay), ...style }}\n      {...props}\n    />\n  )\n}\n\nexport { Motion }\n"}],"categories":["motion"],"accessibility":{"support":"consumer","pattern":"animated semantic wrapper","primitive":"Motion for React","keyboard":[],"aria":["Preserves the semantics and focus behavior of the rendered child element."],"consumer":["Respect prefers-reduced-motion and avoid animating focus, reading order or critical status changes."],"limitations":["Motion safety depends on the selected transition and consumer composition."]},"api":{"source":"src/ui/motion.tsx","exports":[{"name":"MotionPreset","kind":"type","description":"Motion presets — enter/exit/hover recipes expressed as token-driven class strings on top of `tw-animate-css` (zero runtime deps). The `<Motion>` primitive plays an enter recipe once on mount; the exported maps and helpers let you drop the same recipes onto anything else (a Radix `data-[state]` element, a hand-rolled transition). Timing rides the CSS custom properties tw-animate-css reads: - default duration/easing come from the static `duration-*`/`ease-*` classes baked into `motionEnter`, - the `duration`/`delay` props override them per-instance via `--tw-animation-duration` / `--tw-animation-delay` (no arbitrary Tailwind class is generated at runtime, so it works with any value). Everything degrades under `prefers-reduced-motion`: the enter animation is dropped (content shows immediately) and hover transitions are disabled.","definition":"type MotionPreset = | \"fade\" | \"fade-up\" | \"fade-down\" | \"fade-left\" | \"fade-right\" | \"scale\" | \"blur\""},{"name":"MotionHover","kind":"type","definition":"type MotionHover = \"lift\" | \"sink\" | \"scale\" | \"glow\""},{"name":"motionEnterPresets","kind":"utility","description":"Enter recipes: tw-animate-css `animate-in` sets the keyframe `from` state, the element animates to its natural rendered state."},{"name":"motionExitPresets","kind":"utility","description":"Exit recipes: the mirror of each enter preset, for `animate-out` on a Radix `data-[state=closed]` element."},{"name":"motionHoverPresets","kind":"utility","description":"Hover recipes: plain Tailwind transitions, no keyframes."},{"name":"motionEnter","kind":"utility","description":"Build the class string for an enter recipe (plays on mount).","signature":"(preset: MotionPreset, className?: string): string"},{"name":"motionExit","kind":"utility","description":"Build the class string for an exit recipe (pair with `animate-out`).","signature":"(preset: MotionPreset, className?: string): string"},{"name":"motionHover","kind":"utility","description":"Build the class string for a hover recipe.","signature":"(hover: MotionHover, className?: string): string"},{"name":"MotionProps","kind":"type","definition":"interface MotionProps extends React.ComponentProps<\"div\"> { /** Enter recipe to play on mount. Default `\"fade\"`. */ preset?: MotionPreset /** Optional hover recipe applied to the same element. */ hover?: MotionHover /** Enter duration in milliseconds. Overrides the 500ms default. */ duration?: number /** Enter delay in milliseconds — the knob for staggering a list. */ delay?: number /** Merge props onto the single child instead of rendering a wrapper. */ asChild?: boolean }"},{"name":"Motion","kind":"component","propsType":"MotionProps","props":[{"name":"preset","type":"MotionPreset","required":false,"default":"\"fade\"","description":"Enter recipe to play on mount. Default `\"fade\"`."},{"name":"hover","type":"MotionHover","required":false,"description":"Optional hover recipe applied to the same element."},{"name":"duration","type":"number","required":false,"description":"Enter duration in milliseconds. Overrides the 500ms default."},{"name":"delay","type":"number","required":false,"description":"Enter delay in milliseconds — the knob for staggering a list."},{"name":"asChild","type":"boolean","required":false,"default":"false","description":"Merge props onto the single child instead of rendering a wrapper."}]}]}}