Installs the copy of vela-ui that veni-builder serves to Venizia apps; veni-builder serves the Radix UI copy only, so this tab exists for that base alone.
import * as React from "react";import { DocTypewriter } from "@/components/ui/doc-typewriter";const REPLY = "Here is the plan: freeze the schema on Thursday, run the dry migration on Friday morning, and cut over once finance signs off.";export default function Example() { const [run, setRun] = React.useState(0); return ( <div className="w-full max-w-xl space-y-3 p-6"> <DocTypewriter key={run} value={REPLY} /> <button type="button" className="text-xs text-text-muted underline" onClick={() => setRun(run + 1)}> Write again </button> </div> );}
Props
Prop
Type
Default
onDone
() => void
—
speed
number
28
valuerequired
string
—
Takes these and every prop of DocTypewriterProps.
Dependencies
npm
none besides React
Installs with it
nothing else
Source
components/ui/doc-typewriter.tsx
import * as React from "react";import { cn } from "@/lib/utils";export interface DocTypewriterProps extends Omit<React.ComponentProps<"p">, "children"> { /** The full text. When it grows, the new tail is typed on from where it stood. */ value: string; /** Milliseconds per character. */ speed?: number; onDone?: () => void;}/** Text that types itself in, with a caret while it runs; shown at once for reduced motion. */function DocTypewriter({ value, speed = 28, onDone, className, ...props }: DocTypewriterProps) { const [shown, setShown] = React.useState(0); const done = React.useRef(onDone); done.current = onDone; const still = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches; const length = still ? value.length : Math.min(shown, value.length); React.useEffect(() => { if (still || shown >= value.length) { if (value.length > 0 && shown >= value.length) done.current?.(); return; } const t = setTimeout(() => setShown(shown + 1), speed); return () => clearTimeout(t); }, [shown, value, speed, still]); // A rewritten value starts over; an appended one keeps typing. const last = React.useRef(value); React.useEffect(() => { if (!value.startsWith(last.current)) setShown(0); last.current = value; }, [value]); return ( <p className={cn("text-sm leading-[1.6] text-text-strong", className)} {...props}> <span className="sr-only">{value}</span> <span aria-hidden>{value.slice(0, length)}</span> {length < value.length && <span aria-hidden className="ml-px inline-block h-[1.1em] w-px translate-y-[0.2em] animate-pulse bg-current" />} </p> );}export { DocTypewriter };