Split Flap
loomui/split-flapFreeComponent
A departure board that flaps through its glyphs, one cell behind the last, until it lands on the value.
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/split-flap.jsonSource
1"use client"23import * as React from "react"45import { cn } from "@/lib/utils"67export interface SplitFlapProps extends Omit<8 React.ComponentProps<"div">,9 "children"10> {11 /** The string the board settles on. Matched against the charset in caps. */12 value: string13 /** Every glyph a cell can show, in the order it flaps through them. */14 charset?: string15 /** Milliseconds per flap. One flap is also this long, so the board never gaps. */16 interval?: number17 /** Flaps of head start each cell gives the one after it. */18 stagger?: number19 /** Keep at least this many cells, so a shorter value does not resize the board. */20 padTo?: number21}2223/** The glyphs a departure board actually carries, in board order. */24const DEFAULT_CHARSET = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,:'-?!"2526export function SplitFlap({27 value,28 charset = DEFAULT_CHARSET,29 interval = 62,30 stagger = 2,31 padTo,32 className,33 ...props34}: SplitFlapProps) {35 const target = React.useMemo(() => {36 const text = value.toUpperCase()37 const width = Math.max(padTo ?? 0, text.length)38 return text.padEnd(width, " ").split("")39 }, [value, padTo])4041 const blank = charset[0] ?? " "42 const charsRef = React.useRef<string[]>(target.map(() => blank))43 const [chars, setChars] = React.useState(charsRef.current)4445 // The half falling away carries the glyph from the previous commit, so the46 // flap has something to show on its way out. Read during render, written47 // after it, which is the only ordering that gives the outgoing face.48 const previousRef = React.useRef(chars)49 const previous = previousRef.current50 React.useEffect(() => {51 previousRef.current = chars52 }, [chars])5354 React.useEffect(() => {55 const commit = (next: string[]) => {56 charsRef.current = next57 setChars(next)58 }5960 // A board that grew gets blank cells to flap up from rather than popping.61 const from = target.map((_, index) => charsRef.current[index] ?? blank)6263 // Flaps forward through the charset, per cell. A cell whose target is not64 // in the charset takes none and is set outright.65 const steps = from.map((char, index) => {66 const start = charset.indexOf(char)67 const end = charset.indexOf(target[index])68 if (start < 0 || end < 0) {69 return 070 }71 return (end - start + charset.length) % charset.length72 })7374 const total = steps.reduce(75 (longest, count, index) =>76// … 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 |
