Playback Ghost

Superposition translucide indiquant ou se trouvait un objet du canvas a un horodatage precedent pendant la lecture. Installez Playback Ghost depuis le registre VLLNT UI avec la CLI shadcn.

Signaler un bug

Superposition translucide indiquant ou se trouvait un objet du canvas a un horodatage precedent pendant la lecture. Fait partie de la famille utility dans VLLNT UI, il est publie comme entree de registre lisible par machine — copiez la source directement dans votre application avec la CLI shadcn et possedez-la, sans dependance d'execution a une bibliotheque de composants.

Apercu

Basculez entre clair et sombre pour inspecter l'apercu Storybook integre.

Installation

Ajoutez Playback Ghost a votre projet avec la CLI shadcn. La source arrive dans votre code, prete a etre adaptee :

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/playback-ghost.json

Source

"use client"; import type { ComponentPropsWithoutRef, ReactNode } from "react"; import { cn } from "../../lib/utils"; /** * Object kind — drives the ghost glyph. * * @public */ export type PlaybackGhostKind = | "agent" | "artifact" | "input" | "output" | "run" | "task"; const KIND_GLYPH: Record<PlaybackGhostKind, string> = { agent: "◇", artifact: "◌", input: "↘", output: "↗", run: "▶", task: "▢", }; const KIND_LABEL: Record<PlaybackGhostKind, string> = { agent: "Agent", artifact: "Artifact", input: "Input", output: "Output", run: "Run", task: "Task", }; /** * Localizable strings. * * @public */ export type PlaybackGhostLabels = { /** Aria-label override. Defaults to `"Playback ghost"`. */ region?: string; }; const DEFAULT_LABELS = { region: "Playback ghost", } as const satisfies Required<PlaybackGhostLabels>; /** * Props for {@link PlaybackGhost}. * * @public */ export type PlaybackGhostProps = { /** Optional kind glyph + tooltip. */ kind?: PlaybackGhostKind; /** Optional label rendered next to the glyph (e.g. id, run name). */ label?: ReactNode; /** Localizable strings. */ labels?: PlaybackGhostLabels; /** Ghost opacity `0..1`. Defaults to `0.4`. */ opacity?: number; /** Ghost size in pixels. Defaults to `40`. */ size?: number; /** Center X in canvas pixels. */ x: number; /** Center Y in canvas pixels. */ y: number; } & ComponentPropsWithoutRef<"div">; const clamp01 = (v: number): number => { if (v < 0) { return 0; } if (v > 1) { return 1; } return v; }; /** * Translucent overlay marking where a canvas object was at a previous * timestamp during state playback. Renders a kind glyph (and optional * label) at the historical position so the user can compare the * present canvas against earlier state without losing context. * * Pure presentation; the host computes the historical position from the * scrubbed timestamp. The wrapper is `pointer-events: none` so host * gestures pass through. * * @example * ```tsx * <div className="relative h-screen w-screen"> * <Canvas /> * <PlaybackGhost x={420} y={180} kind="run" label="research-2025" /> * </div> * ``` * * @public */ export const PlaybackGhost = ({ ref, ...props }: PlaybackGhostProps & { ref?: React.Ref<HTMLDivElement> }) => { const { className, kind, label, labels, opacity = 0.4, size = 40, x, y, ...rest } = props; const resolvedLabels = { ...DEFAULT_LABELS, ...labels }; const safeOpacity = clamp01(opacity); const safeSize = size < 16 ? 16 : size; const ariaLabel = kind ? `${resolvedLabels.region}: ${KIND_LABEL[kind]}` : resolvedLabels.region; return ( <div aria-label={ariaLabel} className={cn( "pointer-events-none absolute z-10 inline-flex -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-1.5 rounded-md border border-dashed border-border/70 bg-background/40 px-2 py-1 text-xs text-foreground backdrop-blur-sm", className, )} data-playback-ghost data-playback-kind={kind ?? "unknown"} ref={ref} role="img" style={{ left: x, minHeight: safeSize, minWidth: safeSize, opacity: safeOpacity, top: y, }} {...rest} > {kind ? ( <span aria-hidden="true" className="text-muted-foreground"> {KIND_GLYPH[kind]} </span> ) : null} {label ? ( <span className="truncate" data-playback-ghost-label> {label} </span> ) : null} </div> ); }; PlaybackGhost.displayName = "PlaybackGhost";

Stories

Explorez chaque variante et etat dans le Storybook interactif :

Apercu

Basculez entre clair et sombre pour inspecter l'apercu Storybook integre.

Dependances

  • @vllnt/ui@^0.3.0