GitHub

Framer Motion for the octane UI framework.

Motion separates a framework-agnostic animation engine (animate) and gesture primitives (hover, press) from its React components (motion.div, AnimatePresence). This package reuses the engine + gestures verbatim and reimplements the components on octane.

// before
import { motion, AnimatePresence } from 'motion/react';
// after
import { motion, AnimatePresence } from '@octanejs/motion';
function Card() @{
  <motion.div
    className="card"
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ duration: 0.3 }}
    whileHover={{ scale: 1.05 }}
    whileTap={{ scale: 0.95 }}
  >
    {'hello'}
  </motion.div>
}
function List(props) @{
  <AnimatePresence>
    @if (props.show) {
      <motion.div exit={{ opacity: 0 }}>{'I fade out when removed'}</motion.div>
    }
  </AnimatePresence>
}

What's bound

  • motion.<tag>initial, animate, transition, whileHover, whileTap, whileFocus, whileInView (+ viewport), exit, drag (+ dragConstraints, onDrag*), layout, layoutId, variants, plus any DOM props (className, style, events, …) and style MotionValues spread/bound onto the element.
  • AnimatePresence — exit animations on removal.
  • MotionConfig — global transition / reducedMotion defaults via context.
  • useReducedMotion() — a live prefers-reduced-motion subscription; operating-system setting changes update mounted consumers.
  • LayoutGroup — namespaces layoutId values so independent shared-layout surfaces do not cross-animate.
  • LazyMotion + domAnimation / domMax + m — feature-gated hosts. The ./react-m entry exposes every HTML/SVG host as a named export.
  • variants — label resolution (animate="visible") + parent→child propagation + staggerChildren / delayChildren (number or stagger() function) / staggerDirection.
  • useMotionValue(), useScroll(), useAnimate() — MotionValues, scroll-linked values, and imperative scoped animation.
  • useTransform(), useSpring(), useMotionValueEvent() — MotionValue composition: derive a value (range-map / transformer / multi-input combiner), spring toward a value or source, and subscribe to a value's events.
  • Motion's framework-agnostic helpers (animate, stagger, value types, …), re-exported.

How it works

octane had no public way for a runtime-proxy component to render a host element wrapping children, nor to provide context from plain-TS — so this package added two runtime primitives: hostComponent and provideContext. motion.<tag> renders a real <tag> through hostComponent, captures the node, and drives:

  • Animations from layout effects calling motion's animate(); gestures via hover() / press() / inView(); MotionValues (from useMotionValue / useScroll) by subscribing in style and writing the element directly.
  • MotionConfig + variants through provideContext: a plain-TS component stamps context for its children (config defaults, active variant labels).
  • drag with pointer events (axis lock + dragConstraints).
  • Exit without any deferred-deletion machinery: octane fires cleanups before detaching the DOM, so a leaving element's unmount cleanup clones it (outside the range octane is about to remove), animates the exit on the clone, and removes it when it finishes.
  • layout / layoutId via FLIP: measure the box, and if it moved/resized — vs the previous commit (layout) or a same-id element that just unmounted (layoutId) — apply the inverse transform then animate it back to identity. The same cleanup-before-detach ordering lets a leaving layoutId element record its box for a same-commit replacement; unused boxes expire after that commit. layout="position" applies only translation, layout="size" only scaling, and layout animations use transition.layout when provided.

Not yet ported

The full layout projection tree — nested projection, child scale correction, and continuous shared-layout during drag (the layout/layoutId here are single-element FLIPs). Also drag momentum/elastic physics and useTransform's output-map form (useTransform(mv, [0, 100], { opacity: [0, 1] })).

Stagger specifics: when: 'beforeChildren' | 'afterChildren' parent/child sequencing is not implemented, and a child's stagger index is fixed at registration order (a keyed reorder does not re-stagger).

Status

Current scope, known divergences, and verification status are tracked in the generated bindings status table, sourced from this package's status.json.

Read the original on github.com ↗