{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "status-dot",
  "title": "Status dot",
  "author": "Tori Bryan",
  "description": "A small mark for whether someone is present, away or offline, told apart by shape.",
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/status-dot.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype StatusDotStatus = \"present\" | \"away\" | \"offline\"\n\nconst STATUS_LABELS: Record<StatusDotStatus, string> = {\n  present: \"Present\",\n  away: \"Away\",\n  offline: \"Offline\",\n}\n\n// Each status keeps the semantic token it means everywhere else in fibo.\nconst MARK: Record<StatusDotStatus, string> = {\n  present: \"group-data-[variant=color]/status-dot:fill-success\",\n  away: \"group-data-[variant=color]/status-dot:fill-warning\",\n  offline: \"group-data-[variant=color]/status-dot:stroke-muted-foreground\",\n}\n\nconst statusDotVariants = cva(\n  \"group/status-dot relative inline-block shrink-0 align-middle\",\n  {\n    variants: {\n      variant: {\n        color: \"\",\n        mono: \"\",\n      },\n      size: {\n        xs: \"size-2\",\n        sm: \"size-2.5\",\n        default: \"size-3\",\n        lg: \"size-4\",\n      },\n    },\n    defaultVariants: {\n      variant: \"color\",\n      size: \"default\",\n    },\n  }\n)\n\n/*\n * A part that draws its own accessible name, such as Sticker avatar with\n * role=\"img\", can't hear text inside it. It provides this context, the dot\n * gives its label the host's id and says it is there, and the host adds the\n * id to its aria-labelledby only while a dot is mounted.\n */\ntype StatusDotHost = { labelId: string; register: () => () => void }\n\nconst StatusDotHostContext = React.createContext<StatusDotHost | null>(null)\n\n/** For parts that compose a StatusDot into their own accessible name. */\nfunction useStatusDotHost() {\n  const labelId = React.useId()\n  const [count, setCount] = React.useState(0)\n  const host = React.useMemo<StatusDotHost>(\n    () => ({\n      labelId,\n      register: () => {\n        setCount((n) => n + 1)\n        return () => setCount((n) => n - 1)\n      },\n    }),\n    [labelId]\n  )\n  return {\n    /** The dot's label id while a dot is mounted, for aria-labelledby. */\n    labelledBy: count > 0 ? labelId : undefined,\n    host,\n  }\n}\n\nfunction StatusDotHostProvider({\n  host,\n  children,\n}: {\n  host: StatusDotHost\n  children?: React.ReactNode\n}) {\n  return (\n    <StatusDotHostContext.Provider value={host}>\n      {children}\n    </StatusDotHostContext.Provider>\n  )\n}\n\ntype StatusDotProps = Omit<React.ComponentProps<\"span\">, \"children\"> &\n  VariantProps<typeof statusDotVariants> & {\n    /** Present, away or offline: a dot, a crescent or a ring, so status reads without color. */\n    status: StatusDotStatus\n    /** The spoken status, for translation. Defaults to Present, Away or Offline; `null` makes the dot decorative when the text beside it already says it. */\n    label?: string | null\n  }\n\nfunction StatusDot({\n  status,\n  label,\n  variant = \"color\",\n  size = \"default\",\n  className,\n  ...props\n}: StatusDotProps) {\n  const host = React.useContext(StatusDotHostContext)\n  const text = label === null ? null : (label ?? STATUS_LABELS[status])\n\n  React.useLayoutEffect(() => {\n    if (host && text) return host.register()\n  }, [host, text])\n\n  return (\n    <span\n      data-slot=\"status-dot\"\n      data-status={status}\n      data-variant={variant}\n      data-size={size}\n      className={cn(statusDotVariants({ variant, size }), className)}\n      {...props}\n    >\n      {/* The disc is the page color, so the dot keeps a clear edge on a\n          photo or a sticker, and fibo's status tokens keep their contrast,\n          which they are tuned for against the page in each theme. */}\n      <svg\n        aria-hidden=\"true\"\n        viewBox=\"0 0 20 20\"\n        className=\"block size-full overflow-visible\"\n      >\n        <circle cx=\"10\" cy=\"10\" r=\"10\" className=\"fill-background\" />\n        {status === \"offline\" ? (\n          <circle\n            cx=\"10\"\n            cy=\"10\"\n            r=\"4.75\"\n            strokeWidth=\"3.5\"\n            className={cn(\"fill-none stroke-foreground\", MARK.offline)}\n          />\n        ) : (\n          <circle\n            cx=\"10\"\n            cy=\"10\"\n            r=\"6.5\"\n            className={cn(\"fill-foreground\", MARK[status])}\n          />\n        )}\n        {status === \"away\" ? (\n          <circle cx=\"6.5\" cy=\"6.5\" r=\"5\" className=\"fill-background\" />\n        ) : null}\n      </svg>\n      {text ? (\n        <span id={host?.labelId} className=\"sr-only\">\n          {text}\n        </span>\n      ) : null}\n    </span>\n  )\n}\n\nexport {\n  StatusDot,\n  StatusDotHostProvider,\n  statusDotVariants,\n  useStatusDotHost,\n  type StatusDotProps,\n  type StatusDotStatus,\n}\n",
      "type": "registry:ui"
    }
  ],
  "cssVars": {
    "theme": {
      "color-success": "var(--success)",
      "color-warning": "var(--warning)",
      "fill-success": "var(--success-icon)",
      "fill-warning": "var(--warning-icon)"
    },
    "light": {
      "success": "var(--color-green-700)",
      "warning": "var(--color-amber-700)",
      "success-icon": "var(--success)",
      "warning-icon": "var(--warning)"
    },
    "dark": {
      "success": "var(--color-green-400)",
      "warning": "var(--color-amber-400)",
      "success-icon": "var(--success)",
      "warning-icon": "var(--warning)"
    }
  },
  "meta": {
    "tier": "base-components",
    "group": "Display",
    "docs": "https://fibo.toribryan.com/?path=/docs/base-components-display-status-dot--docs"
  },
  "docs": "Docs and live examples: https://fibo.toribryan.com/?path=/docs/base-components-display-status-dot--docs",
  "categories": [
    "base-components",
    "display"
  ],
  "type": "registry:ui"
}