Alert Pulse

Pulsing ring overlay for alerting canvas objects, with severity tones. Install Alert Pulse from the VLLNT UI registry with the shadcn CLI.

Report a bug

Pulsing ring overlay for alerting canvas objects, with severity tones. Part of the utility family in VLLNT UI, it ships as a machine-readable registry entry — copy the source directly into your app with the shadcn CLI and own it, no runtime dependency on a component library.

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

Add Alert Pulse to your project with the shadcn CLI. The source lands in your codebase, ready to adapt:

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

Source

"use client"; import type { ComponentPropsWithoutRef } from "react"; import { cn } from "../../lib/utils"; /** * Severity tone of an alert pulse — drives the ring color. * * @public */ export type AlertPulseSeverity = "error" | "info" | "warn"; const SEVERITY_STROKE: Record<AlertPulseSeverity, string> = { error: "stroke-red-500", info: "stroke-blue-500", warn: "stroke-amber-500", }; const SEVERITY_FILL: Record<AlertPulseSeverity, string> = { error: "fill-red-500/20", info: "fill-blue-500/20", warn: "fill-amber-500/20", }; const SEVERITY_LABEL: Record<AlertPulseSeverity, string> = { error: "Error", info: "Info", warn: "Warning", }; /** * Localizable strings. * * @public */ export type AlertPulseLabels = { /** Override for the aria-label. Defaults to severity name. */ region?: string; }; /** * Props for {@link AlertPulse}. * * @public */ export type AlertPulseProps = { /** Center X of the pulse in canvas pixels. */ cx: number; /** Center Y of the pulse in canvas pixels. */ cy: number; /** Localizable strings. */ labels?: AlertPulseLabels; /** Outer ring radius in pixels. Defaults to `36`. */ radius?: number; /** Disable the pulse animation. Useful for snapshots. Defaults to `false`. */ reducedMotion?: boolean; /** Severity tone. Defaults to `"warn"`. */ severity?: AlertPulseSeverity; } & ComponentPropsWithoutRef<"svg">; const safeRadius = (value: number): number => (value < 6 ? 6 : value); /** * Pulsing ring overlay drawn around an alerting canvas object. The * outer ring expands and fades to communicate "attention here"; * the inner ring stays put so the object remains anchored. Pure * presentation; the host computes the center + severity from the * runtime alert stream. * * Render inside a `position: relative` parent that shares the canvas * pixel coordinate space; the SVG is `pointer-events: none` so host * gestures pass through. * * @example * ```tsx * <div className="relative h-screen w-screen"> * <Canvas /> * <AlertPulse cx={480} cy={320} severity="error" /> * </div> * ``` * * @public */ export const AlertPulse = ({ ref, ...props }: AlertPulseProps & { ref?: React.Ref<SVGSVGElement> }) => { const { className, cx, cy, labels, radius = 36, reducedMotion = false, severity = "warn", ...rest } = props; const r = safeRadius(radius); const ariaLabel = labels?.region ?? SEVERITY_LABEL[severity]; const size = r * 2 + 24; return ( <svg aria-label={ariaLabel} className={cn( "pointer-events-none absolute z-20 overflow-visible", className, )} data-alert-pulse data-alert-severity={severity} height={size} ref={ref} role="img" style={{ left: cx - size / 2, top: cy - size / 2, }} width={size} {...rest} > <circle className={cn("origin-center", SEVERITY_STROKE[severity])} cx={size / 2} cy={size / 2} fill="none" r={r} strokeOpacity={0.7} strokeWidth={2} /> <circle className={cn( "origin-center", SEVERITY_STROKE[severity], SEVERITY_FILL[severity], reducedMotion ? null : "animate-ping", )} cx={size / 2} cy={size / 2} data-alert-pulse-ring r={r} strokeOpacity={0.4} strokeWidth={2} /> </svg> ); }; AlertPulse.displayName = "AlertPulse";

Stories

Explore every variant and state in the interactive Storybook:

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Dependencies

  • @vllnt/ui@^0.3.0