Spool
loomui/spoolFreeComponent
A container that changes shape to fit whatever it is showing, on a spring that carries its velocity through an interruption.
Live preview
live · rendered by the registry
Rendered by loomui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://loomui.design/r/spool.jsonSource
1"use client"23import * as React from "react"45import { cn } from "@/lib/utils"6import { useSpring, type SpringOptions } from "@/registry/lib/use-spring"78export interface SpoolProps extends Omit<9 React.ComponentProps<"div">,10 "children"11> {12 /** Which state is showing. */13 value: string14 /** One entry per state. Whatever is not `value` is not in the DOM. */15 children: React.ReactNode16 /** How the shape travels between states. */17 spring?: SpringOptions18 /** Corner radius in pixels. Kept circular through the morph. */19 radius?: number20 /** Milliseconds the outgoing state stays mounted for. */21 fade?: number22}2324export interface SpoolItemProps extends React.ComponentProps<"div"> {25 /** Matches the `value` on the parent. */26 value: string27}2829/** Milliseconds the outgoing state takes to leave. */30const EXIT_MS = 12031/** Milliseconds a piece takes to arrive once the old state has gone. */32const ENTER_MS = 17033/** Milliseconds between one piece landing and the next. */34const STAGGER_MS = 453536/**37 * One state's contents. Sized by whatever is inside it: the shape follows the38 * content rather than the content being poured into a shape.39 */40export function SpoolItem({41 value: _value,42 className,43 ...props44}: SpoolItemProps) {45 return (46 <div47 data-slot="spool-item"48 className={cn("flex items-center gap-2.5 px-4 py-2.5", className)}49 {...props}50 />51 )52}5354function findItem(children: React.ReactNode, value: string) {55 let found: React.ReactElement<SpoolItemProps> | null = null5657 React.Children.forEach(children, (child) => {58 if (59 !found &&60 React.isValidElement<SpoolItemProps>(child) &&61 child.props.value === value62 ) {63 found = child64 }65 })6667 return found as React.ReactElement<SpoolItemProps> | null68}6970/**71 * A shape that changes to fit what it holds.72 *73 * The size is never animated. The box is set once, in the commit that swaps the74 * contents, and the difference replays as a transform: start at the ratio75 * between old and new, spring to 1. Layout runs once per change.76 */77export function Spool({78 value,79 children,80 spring,81 radius = 999,82 fade = 200,83 className,84 style,85 ...props86}: SpoolProps) {87 const rootRef = React.useRef<HTMLDivElement>(null)88 const wrapRef = React.useRef<HTMLDivElement>(null)89 // The root is never laid out at its animated size, so its own offset box is90 // always the natural size of the contents. That is what makes a remeasure91// … truncated
What it pulls in
Other registry items
Files it writes
More from loomui
All 91 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Aurora Backdropaurora-backdrop | loomui | Components | Free | no deps | |
| Bento Gridbento-grid | loomui | Components | Free | no deps | |
| Card Stackcard-stack | loomui | Components | Free | no deps | |
| Compare Slidercompare-slider | loomui | Components | Free | no deps | |
| Confetti Buttonconfetti-button | loomui | Components | Free | no deps | |
| Count Upcount-up | loomui | Components | Free | no deps | |
| Credit Cardcredit-card | loomui | Components | Free | no deps | |
| Drawerdrawer | loomui | Components | Free | 1 dep |
