Particular.Tech
Feedbackui

Status indicator

Dot and label for deployment and service states.

ReadyBuildingQueuedErrorCanceledDegradedMaintenanceOffline

Installation

npx shadcn@latest add @particular/status-indicator
npm dependencies
  • class-variance-authority@^0.7.1
  • cn@^0.4.0

Source

ui/status-indicator.tsx
import * as React from "react"
import { cva } from "class-variance-authority"
import { cn } from "cn"

const statuses = {
  ready: "Ready",
  building: "Building",
  queued: "Queued",
  error: "Error",
  canceled: "Canceled",
  degraded: "Degraded",
  maintenance: "Maintenance",
  offline: "Offline",
} as const

type Status = keyof typeof statuses

const dotVariants = cva("relative inline-flex size-2 shrink-0 rounded-full", {
  variants: {
    status: {
      ready: "bg-success",
      building: "bg-warning",
      queued: "bg-muted-foreground/60",
      error: "bg-destructive",
      canceled: "bg-muted-foreground/40",
      degraded: "bg-warning",
      maintenance: "bg-info",
      offline: "bg-muted-foreground/40",
    },
  },
})

const pulsing = new Set<Status>(["building", "queued"])

function StatusDot({
  status,
  className,
  ...props
}: React.ComponentProps<"span"> & { status: Status }) {
  return (
    <span
      aria-hidden
      data-slot="status-dot"
      data-status={status}
      className={cn(dotVariants({ status }), className)}
      {...props}
    >
      {pulsing.has(status) && (
        <span className="absolute inset-0 animate-ping rounded-full bg-inherit opacity-75 motion-reduce:animate-none" />
      )}
    </span>
  )
}

function StatusIndicator({
  status,
  label,
  className,
  ...props
}: Omit<React.ComponentProps<"span">, "children"> & {
  status: Status
  /** Visible text. Defaults to the status name; `false` keeps it for screen readers only. */
  label?: React.ReactNode | false
}) {
  const text = label === false || label === undefined ? statuses[status] : label

  return (
    <span
      data-slot="status-indicator"
      data-status={status}
      className={cn("inline-flex items-center gap-2 text-sm", className)}
      {...props}
    >
      <StatusDot status={status} />
      <span className={cn(label === false && "sr-only")}>{text}</span>
    </span>
  )
}

export { StatusIndicator, StatusDot, statuses, type Status }