Transition

Transition manages enter/exit animations for a single child element, driven by named "variants" (or a custom callback) that read the element's measured DOMRect and drive inline styles frame-by-frame. It's a thin declarative wrapper around the useTransition hook. A separate useTransitionGroup hook handles staggered animations for lists of keyed items.

<Transition>

Props

PropTypeDefaultDescription
childrenReactElementExactly|one|React|element.|Transition|clones|it|and|attaches|its|own|ref|(merged|with|any|ref|the|child|already|has).
openbooleanRequired.|true|animates|toward|the|variant's|to|state,|false|animates|back|toward|from.
variantkeyof|typeof|variants|||TransitionVariantCallback"fade"A|built-in|variant|name|(see|table|below)|or|a|callback|(el:|HTMLElement,|rect:|DOMRect)|=>|{|from,|to,|onUpdate,|onEntered?,|onExited?|}.
durationnumber|(ms)450Transition|duration,|forwarded|to|the|internal|animate()|call.
delaynumber|(ms)Transition|delay.
easingkeyof|Easing"default"Name|of|one|of|the|built-in|easing|functions|(see|Easing|below).
initialTransitionbooleantrueWhen|false,|the|very|first|mount|jumps|straight|to|its|state|instead|of|animating|(subsequent|open|changes|still|animate).
exitOnUnmountbooleanfalseWhen|true,|Transition|renders|nothing|(undefined)|once|the|exit|animation|finishes|(status|===|"exited"),|effectively|unmounting|the|child.
onEnter()|=>|voidFired|when|an|enter|animation|starts.
onEntered()|=>|voidFired|when|an|enter|animation|finishes.
onExit()|=>|voidFired|when|an|exit|animation|starts.
onExited()|=>|voidFired|when|an|exit|animation|finishes.
onUpdate(value:|Record<string,|number>,|progress:|number)|=>|voidFired|on|every|animation|frame|with|the|current|interpolated|values|and|0..1|progress.
onDone()|=>|voidFired|once|the|animation|completes,|regardless|of|direction.

Notes:

  • If children isn't exactly one valid React element, Transition throws.
  • Passing a different variant remounts the internal transition state machine (it's used as the React key).

Built-in variants

Defined in src/Transition/variants.ts. Each one computes from/to values and an onUpdate that writes directly to el.style:

NameBehavior
fadeCross-fades|opacity|between|0|and|1.
fadeUp|/|fadeDown|/|fadeLeft|/|fadeRightFades|opacity|0|->|1|while|translating|40px|along|the|named|axis|and|scaling|from|0.98|to|1.
slideUp|/|slideDown|/|slideLeft|/|slideRightTranslates|the|element|40px|along|the|named|axis|(no|opacity/scale|change).
zoom|/|zoomOverScales|from|0.8|(zoom)|or|1.2|(zoomOver)|to|1|while|fading|in.
growScales|scaleX/scaleY|from|0.8/0.6|to|1/1|while|fading|in.
collapseVertical|/|collapseHorizontalAnimates|max-height/width|from|0|to|the|element's|measured|size|(removes|the|inline|max-height|once|entered).

You can also pass a custom TransitionVariantCallback:

tsx

const scaleY = (el: HTMLElement, rect: DOMRect) => ({
from: { scale: , opacity: },
to: { scale: , opacity: },
onUpdate: ({ scale, opacity }: any) => {
el.style.transform = `scaleY(${scale})`
el.style.transformOrigin = 'top'
el.style.opacity = String(opacity)
},
})

Usage

tsx

import { Transition, Tag } from 'xanui-core'
export const Toast = ({ open, children }) => (
<Transition open={open} variant="fade" duration={}>
<Tag
px={}
py={}
radius={}
shadow="md"
bgcolor="brand.primary"
color="brand.contrast"
>
{children}
</Tag>
</Transition>
)

tsx

<Transition
open={isOpen}
variant="fadeUp"
duration={}
easing="smooth"
onEntered={() => console.log('panel expanded')}
onExited={() => console.log('panel collapsed')}
>
<Tag component="section" px={} py={} radius={} shadow="sm">
{children}
</Tag>
</Transition>

useTransition

The hook Transition is built on. Runs a single enter/exit animation over an arbitrary set of numeric values.

ts

import { useTransition, Easing } from 'xanui-core'
const trans = useTransition({
from: { opacity: },
to: { opacity: },
duration: ,
easing: Easing.smooth,
onUpdate: (value) => { el.style.opacity = String(value.opacity) },
onEntered: () => console.log('fully visible'),
})
trans.enter() // animate toward `to`
trans.exit() // animate toward `from`
trans.toggle() // flip direction
trans.enter(false) // jump instantly, no animation
trans.status // "entering" | "entered" | "exiting" | "exited"
trans.isEntered // boolean
trans.state // ref holding the current interpolated value
trans.isReady // whether `from`/`to` have been resolved (post-mount)

UseTransitionProps<T>

Extends AnimateOptions<T> (see animate below) with:

PropTypeDefaultDescription
initialStatus"entered"|||"exited""exited"Starting|status|before|any|enter()/exit()|call.
onEnter()|=>|voidFired|when|enter()|starts|an|animation.
onEntered()|=>|voidFired|when|the|enter|animation|completes.
onExit()|=>|voidFired|when|exit()|starts|an|animation.
onExited()|=>|voidFired|when|the|exit|animation|completes.

useTransitionGroup

Staggered enter/exit animation for a list of keyed items, with optional mount-on-enter / unmount-on-exit support.

ts

import { useTransitionGroup } from 'xanui-core'
const group = useTransitionGroup({
items: list.map((item) => ({ key: item.id, from: { y: , opacity: }, to: { y: , opacity: } })),
stagger: ,
duration: ,
mountOnEnter: true,
unmountOnExit: true,
onUpdate: (value, key, progress) => { /* apply per-item styles */ },
})
group.enter()
group.exit()
group.toggle()
group.statuses[item.key] // "entering" | "entered" | "exiting" | "exited"
group.mounted[item.key] // boolean, useful with mountOnEnter/unmountOnExit

UseTransitionGroupProps<T>

PropTypeDefaultDescription
items{|key:|string|||number;|from:|T;|to:|T|}[]Required.|The|set|of|items|to|animate.
durationnumber|(ms)400Per-item|animation|duration.
staggernumber|(ms)100Delay|applied|per|item|index|(index|*|stagger).
mountOnEnterbooleanIf|true,|items|start|"exited"/unmounted|and|only|mount|once|enter()|runs.
unmountOnExitbooleanIf|true,|items|are|marked|unmounted|once|their|exit|animation|finishes.
onUpdate(value:|T,|key,|progress:|number)|=>|voidFired|per|frame,|per|item.
onEnter|/|onEntered|/|onExit|/|onExited(key:|string|||number)|=>|voidPer-item|lifecycle|callbacks.

animate / Easing

Both useTransition and useTransitionGroup are built on the low-level animate() function, also exported directly:

ts

import { animate, Easing } from 'xanui-core'
const cancel = animate({
from: { x: },
to: { x: },
duration: ,
easing: Easing.smooth,
onUpdate: (value) => { el.style.transform = `translateX(${value.x}px)` },
onDone: () => console.log('done'),
})
cancel() // stop the animation early

Built-in Easing functions: default (cubic ease-out), standard, fast, smooth, linear, bounceBezier (cubic-bezier curves), plus cubicInOut, easeOutBounce, and spring.

AnimateOptions<T> also supports repeat (number of extra cycles), repeatBack (ping-pong direction on repeat), and breakpoints (per-key value thresholds that fire a callback() once when crossed during the animation).