{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "status-dot",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": [],
  "devDependencies": [],
  "files": [
    {
      "path": "status-dot.tsx",
      "content": "\"use client\";\n\nimport {\n  type CSSProperties,\n  createElement,\n  forwardRef,\n  type HTMLAttributes,\n  useLayoutEffect,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] [--status-dot-neutral:var(--ic-muted-foreground)] [--status-dot-queued:#d1d5db] [--status-dot-canceled:#9ca3af] [--status-dot-active:var(--ic-brand)] [--status-dot-success:#14b8a6] [--status-dot-warning:#f59e0b] [--status-dot-error:var(--ic-destructive)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)] dark:[--status-dot-success:#2dd4bf] dark:[--status-dot-queued:#9ca3af] dark:[--status-dot-canceled:#6b7280]\";\n\nconst STYLE_ID = \"iconiq-status-dot-styles\";\nconst RIPPLE_DURATION = 2.25;\nconst RIPPLE_DELAYS = [0, RIPPLE_DURATION / 2] as const;\n\nconst STATUS_DOT_CSS = `\n@keyframes iconiq-status-dot-ripple {\n  0% {\n    box-shadow: 0 0 0 0 var(--status-dot-ripple-mid);\n  }\n  72% {\n    box-shadow: 0 0 0 var(--status-dot-ripple-spread) var(--status-dot-ripple-end);\n  }\n  100% {\n    box-shadow: 0 0 0 0 var(--status-dot-ripple-end);\n  }\n}\n\n.iconiq-status-dot-ripple {\n  --status-dot-ripple-mid: color-mix(in srgb, var(--status-dot-color) 48%, transparent);\n  --status-dot-ripple-end: transparent;\n  animation: iconiq-status-dot-ripple 2.25s ease-out infinite;\n}\n\n.dark .iconiq-status-dot-ripple {\n  --status-dot-ripple-mid: color-mix(in srgb, var(--status-dot-color) 58%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .iconiq-status-dot-ripple {\n    animation: none;\n  }\n}\n`;\n\ntype StatusDotTone = \"neutral\" | \"active\" | \"success\" | \"warning\" | \"error\";\n\ntype StatusDotState = \"QUEUED\" | \"BUILDING\" | \"ERROR\" | \"READY\" | \"CANCELED\";\n\ntype StatusDotSize = \"sm\" | \"md\" | \"lg\";\n\nconst statusDotStates = [\n  \"QUEUED\",\n  \"BUILDING\",\n  \"ERROR\",\n  \"READY\",\n  \"CANCELED\",\n] as const satisfies readonly StatusDotState[];\n\nconst statusDotTones = [\n  \"neutral\",\n  \"active\",\n  \"success\",\n  \"warning\",\n  \"error\",\n] as const satisfies readonly StatusDotTone[];\n\nconst statusDotToneConfig = {\n  neutral: {\n    colorVar: \"var(--status-dot-neutral)\",\n    label: \"Neutral\",\n  },\n  active: {\n    colorVar: \"var(--status-dot-active)\",\n    label: \"Active\",\n  },\n  success: {\n    colorVar: \"var(--status-dot-success)\",\n    label: \"Success\",\n  },\n  warning: {\n    colorVar: \"var(--status-dot-warning)\",\n    label: \"Warning\",\n  },\n  error: {\n    colorVar: \"var(--status-dot-error)\",\n    label: \"Error\",\n  },\n} as const satisfies Record<StatusDotTone, { colorVar: string; label: string }>;\n\nconst statusDotStateConfig = {\n  QUEUED: {\n    tone: \"neutral\",\n    label: \"Queued\",\n    animate: false,\n    colorVar: \"var(--status-dot-queued)\",\n  },\n  BUILDING: { tone: \"warning\", label: \"Building\", animate: true },\n  ERROR: { tone: \"error\", label: \"Error\", animate: false },\n  READY: { tone: \"success\", label: \"Ready\", animate: false },\n  CANCELED: {\n    tone: \"neutral\",\n    label: \"Canceled\",\n    animate: false,\n    colorVar: \"var(--status-dot-canceled)\",\n  },\n} as const satisfies Record<\n  StatusDotState,\n  {\n    tone: StatusDotTone;\n    label: string;\n    animate: boolean;\n    colorVar?: string;\n  }\n>;\n\nconst statusDotSizeConfig = {\n  sm: { dot: 6, container: 14, rippleSpread: \"7px\" },\n  md: { dot: 8, container: 20, rippleSpread: \"9px\" },\n  lg: { dot: 10, container: 26, rippleSpread: \"11px\" },\n} as const satisfies Record<\n  StatusDotSize,\n  { dot: number; container: number; rippleSpread: string }\n>;\n\ntype StatusDotBaseProps = {\n  animate?: boolean;\n  inline?: boolean;\n  label?: string;\n  labelClassName?: string;\n  showLabel?: boolean;\n  size?: StatusDotSize;\n} & HTMLAttributes<HTMLElement>;\n\ntype StatusDotProps = StatusDotBaseProps &\n  (\n    | { state: StatusDotState; tone?: StatusDotTone }\n    | { state?: StatusDotState; tone: StatusDotTone }\n  );\n\ntype ResolvedStatusDotConfig = {\n  animate: boolean;\n  colorVar: string;\n  defaultLabel: string;\n  tone: StatusDotTone;\n};\n\nfunction ensureStatusDotStyles() {\n  if (typeof document === \"undefined\") {\n    return;\n  }\n\n  let style = document.getElementById(STYLE_ID) as HTMLStyleElement | null;\n\n  if (!style) {\n    style = document.createElement(\"style\");\n    style.id = STYLE_ID;\n    document.head.appendChild(style);\n  }\n\n  style.textContent = STATUS_DOT_CSS;\n}\n\nfunction getStatusDotConfig({\n  animate,\n  state,\n  tone,\n}: {\n  animate?: boolean;\n  state?: StatusDotState;\n  tone?: StatusDotTone;\n}): ResolvedStatusDotConfig {\n  if (state) {\n    const stateEntry = statusDotStateConfig[state];\n    const toneEntry = statusDotToneConfig[stateEntry.tone];\n\n    return {\n      animate: animate ?? stateEntry.animate,\n      colorVar:\n        \"colorVar\" in stateEntry ? stateEntry.colorVar : toneEntry.colorVar,\n      defaultLabel: stateEntry.label,\n      tone: stateEntry.tone,\n    };\n  }\n\n  const resolvedTone = tone ?? \"neutral\";\n  const toneEntry = statusDotToneConfig[resolvedTone];\n\n  return {\n    animate: animate ?? resolvedTone === \"active\",\n    colorVar: toneEntry.colorVar,\n    defaultLabel: toneEntry.label,\n    tone: resolvedTone,\n  };\n}\n\nfunction resolveStatusDotAccessibleName(defaultLabel: string, label?: string) {\n  return label && label.length > 0 ? label : defaultLabel;\n}\n\nfunction resolveStatusDotLabel({\n  defaultLabel,\n  label,\n  showLabel = false,\n}: {\n  defaultLabel: string;\n  label?: string;\n  showLabel?: boolean;\n}) {\n  const accessibleName = resolveStatusDotAccessibleName(defaultLabel, label);\n\n  if (!showLabel) {\n    return {\n      accessibleName,\n      displayLabel: null,\n    };\n  }\n\n  return {\n    accessibleName,\n    displayLabel: accessibleName,\n  };\n}\n\nfunction resolveStatusDotA11yProps({\n  accessibleName,\n  showVisibleLabel,\n}: {\n  accessibleName: string;\n  showVisibleLabel: boolean;\n}) {\n  if (showVisibleLabel) {\n    return {\n      \"aria-live\": \"polite\" as const,\n      role: \"status\" as const,\n    };\n  }\n\n  return {\n    \"aria-label\": accessibleName,\n    \"aria-live\": \"polite\" as const,\n    role: \"status\" as const,\n  };\n}\n\nfunction RippleRing({\n  delay,\n  dotSize,\n  rippleSpread,\n}: {\n  delay: number;\n  dotSize: number;\n  rippleSpread: string;\n}) {\n  return (\n    <span\n      aria-hidden\n      className=\"iconiq-status-dot-ripple pointer-events-none absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full motion-reduce:hidden\"\n      style={{\n        width: dotSize,\n        height: dotSize,\n        animationDelay: `${delay}s`,\n        [\"--status-dot-ripple-spread\" as string]: rippleSpread,\n      }}\n    />\n  );\n}\n\nconst StatusDot = forwardRef<HTMLElement, StatusDotProps>(function StatusDot(\n  {\n    animate,\n    className,\n    inline = true,\n    label,\n    labelClassName,\n    showLabel = false,\n    size = \"md\",\n    state,\n    style,\n    tone,\n    ...props\n  },\n  ref\n) {\n  const config = getStatusDotConfig({ animate, state, tone });\n  const sizeConfig = statusDotSizeConfig[size];\n  const { accessibleName, displayLabel } = resolveStatusDotLabel({\n    defaultLabel: config.defaultLabel,\n    label,\n    showLabel,\n  });\n  const a11yProps = resolveStatusDotA11yProps({\n    accessibleName,\n    showVisibleLabel: Boolean(displayLabel),\n  });\n  const shouldAnimate = config.animate;\n  const Root = inline ? \"span\" : \"div\";\n\n  useLayoutEffect(() => {\n    ensureStatusDotStyles();\n  }, []);\n\n  const colorStyle = {\n    [\"--status-dot-color\" as string]: config.colorVar,\n  } as CSSProperties;\n\n  const rootStyle = {\n    ...style,\n    ...colorStyle,\n  } as CSSProperties;\n\n  return createElement(\n    Root,\n    {\n      className: cn(\n        componentThemeClassName,\n        inline ? \"inline-flex\" : \"flex\",\n        \"items-center gap-2\",\n        className\n      ),\n      ref,\n      style: rootStyle,\n      ...props,\n      ...a11yProps,\n    },\n    <span\n      aria-hidden\n      className=\"relative shrink-0 overflow-visible\"\n      style={{\n        width: sizeConfig.container,\n        height: sizeConfig.container,\n        ...colorStyle,\n      }}\n    >\n      {shouldAnimate\n        ? RIPPLE_DELAYS.map((delay) => (\n            <RippleRing\n              delay={delay}\n              dotSize={sizeConfig.dot}\n              key={delay}\n              rippleSpread={sizeConfig.rippleSpread}\n            />\n          ))\n        : null}\n      <span\n        className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full\"\n        style={{\n          width: sizeConfig.dot,\n          height: sizeConfig.dot,\n          backgroundColor: \"var(--status-dot-color)\",\n        }}\n      />\n    </span>,\n    displayLabel ? (\n      <span\n        className={cn(\n          \"font-medium text-[color:var(--ic-muted-foreground)] text-sm\",\n          labelClassName\n        )}\n      >\n        {displayLabel}\n      </span>\n    ) : null\n  );\n});\n\nStatusDot.displayName = \"StatusDot\";\n\nexport {\n  getStatusDotConfig,\n  resolveStatusDotAccessibleName,\n  resolveStatusDotA11yProps,\n  resolveStatusDotLabel,\n  StatusDot,\n  statusDotSizeConfig,\n  statusDotStateConfig,\n  statusDotStates,\n  statusDotToneConfig,\n  statusDotTones,\n};\nexport default StatusDot;\nexport type {\n  ResolvedStatusDotConfig,\n  StatusDotProps,\n  StatusDotSize,\n  StatusDotState,\n  StatusDotTone,\n};\n",
      "type": "registry:ui"
    }
  ],
  "title": "Status Dot",
  "description": "Rippling inline status dot with deployment presets, generic tones, sizing, and dot-only accessibility defaults."
}
