import * as React from "react"
import { cn } from "@/lib/utils"
export type LivePulseTone = "success" | "warning" | "danger" | "info" | "neutral"
// Literal class names, one per tone: Tailwind reads this file's source text, so
// a name built by interpolation would type-check and then render colourless.
const DOT: Record<LivePulseTone, string> = {
success: "bg-success",
warning: "bg-warning",
danger: "bg-danger",
info: "bg-info",
neutral: "bg-muted-foreground",
}
const LABEL: Record<LivePulseTone, string> = {
success: "text-foreground",
warning: "text-foreground",
danger: "text-foreground",
info: "text-foreground",
neutral: "text-muted-foreground",
}
export type LivePulseProps = React.ComponentProps<"span"> & {
/** What the dot is reporting; it is the element's accessible name, so keep it short and true. */
label: React.ReactNode
tone?: LivePulseTone
/** False parks the dot: it keeps its colour and stops ringing. */
pulsing?: boolean
}
/**
* A status dot with a ring pulsing out of it, and the word for what it means.
*
* A live region, so a reader hears the switch between live and parked as it
* happens; the colour is never the only signal, because the label says the
* same thing in words. The ring's period is four times `--duration-slow`,
* which means it stops of its own accord when a reader has asked for less
* motion — the token is 0ms then — as well as under the media query.
*/
function LivePulse({
className,
label,
tone = "success",
pulsing = true,
...props
}: LivePulseProps) {
return (
<span
data-slot="live-pulse"
data-tone={tone}
data-pulsing={pulsing || undefined}
role="status"
className={cn("inline-flex w-fit items-center gap-2 text-xs font-medium", className)}
{...props}
>
<span aria-hidden="true" className="relative flex size-2 shrink-0">
{pulsing ? (
<span
data-slot="live-pulse-ring"
className={cn(
"absolute inline-flex size-full animate-ping rounded-full opacity-70 [animation-duration:calc(var(--duration-slow,320ms)*4)] motion-reduce:hidden in-data-[motion=reduced]:hidden",
DOT[tone]
)}
/>
) : null}
<span
data-slot="live-pulse-dot"
className={cn("relative inline-flex size-full rounded-full", DOT[tone])}
/>
</span>
<span data-slot="live-pulse-label" className={LABEL[tone]}>
{label}
</span>
</span>
)
}
export { LivePulse }