Transition + Animation Editor
ridiculous/transition-editorFreeComponent
Ridiculously typed CSS transition + animation editor (mode prop) — a comma-separated layer list with per-kind token typing and cardinality caps. Live preview that actually animates, with an embedded easing picker and duration scrubber.
Install it
npx shadcn@latest add http://ridiculous.turtlesocks.dev/r/transition-editor.jsonSource
1"use client"23import { useEffect, useRef, useState } from "react"4import { Button } from "@/components/ui/button"5import {6 Popover,7 PopoverContent,8 PopoverTrigger,9} from "@/components/ui/popover"10import { cn } from "@/lib/utils"11import { AddLayerButton } from "./controls"12import { AnimationFields, TransitionFields } from "./fields"13import { TransitionPreview } from "./preview"14import {15 defaultAnimationLayer,16 defaultTransitionLayer,17 formatAnimation,18 formatTransition,19 layerCount,20 parseAnimation,21 parseTransition,22} from "./transition-editor.helpers"23import type {24 AnimationLayer,25 AnimationString,26 EditorMode,27 TransitionEditorState,28 TransitionEditorStringMap,29 TransitionLayer,30 TransitionString,31} from "./transition-editor.types"3233// ---------------------------------------------------------------------------34// Mode-keyed helper indirection35// ---------------------------------------------------------------------------3637type AnyLayer = TransitionLayer | AnimationLayer3839/**40 * Parse a value into a discriminated `{ mode, layers }` state, or `null` on a41 * parse error. The `mode` literal tags the layer list so downstream `format`42 * narrows the element type without a cast.43 */44function parseState(45 mode: EditorMode,46 value: string,47): TransitionEditorState | null {48 if (mode === "transition") {49 const layers = parseTransition(value)50 return layers === null ? null : { mode, layers }51 }52 const layers = parseAnimation(value)53 return layers === null ? null : { mode, layers }54}5556/** Serialize a discriminated editor state. The `mode` tag narrows `layers`. */57function formatState(state: TransitionEditorState): string {58 return state.mode === "transition"59 ? formatTransition(state.layers)60 : formatAnimation(state.layers)61}6263/** A fresh default layer for the mode. */64function defaultLayerFor(mode: EditorMode): AnyLayer {65 return mode === "transition"66 ? defaultTransitionLayer()67 : defaultAnimationLayer()68}6970/**71 * Re-tag a working `AnyLayer[]` as a discriminated `{ mode, layers }` state.72 *73 * This is the ONE place the editor asserts the layer kind matches the mode —74 * unavoidable because the public `TransitionLayerRow` exposes an untagged75 * `layer: AnyLayer` alongside a separate `mode` prop, so the type system cannot76 * prove their correspondence. The editor only ever stores layers of the77 * matching kind (parse + defaults + per-mode fields all produce them), so the78// … truncated
What it pulls in
Other registry items
Files it writes
More from ridiculous
All 22 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| All ridiculous componentsall | ridiculous | Components | Free | no deps | |
| Anchor Position Editoranchor-position-editor | ridiculous | Components | Free | no deps · 9 files | |
| Background Editorbackground-editor | ridiculous | Components | Free | no deps · 9 files | |
| Box Shadow Editorbox-shadow-editor | ridiculous | Components | Free | no deps · 7 files | |
| Calc Editorcalc-editor | ridiculous | Components | Free | no deps · 4 files | |
| Clip Path Editorclip-path-editor | ridiculous | Components | Free | no deps · 20 files | |
| Color Functioncolor-function | ridiculous | Components | Free | no deps · 10 files | |
| Color Pickercolor-picker | ridiculous | Components | Free | no deps · 11 files |
