{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "response-status",
  "title": "Response Status",
  "description": "Queued, streaming, stopped, complete, and failed response states with stop and retry actions.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/components/response-status.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { Check, CircleAlert, CircleDashed, RotateCw, Square } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  useContext,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const RESPONSE_STATUS_VARIANTS = [\"inline\", \"pill\", \"bar\"] as const;\nexport const RESPONSE_STATUSES = [\"queued\", \"streaming\", \"stopped\", \"complete\", \"failed\"] as const;\nexport type ResponseStatusVariant = (typeof RESPONSE_STATUS_VARIANTS)[number];\nexport type ResponseStatusValue = (typeof RESPONSE_STATUSES)[number];\nexport type ResponseStatusProps = ComponentProps<\"div\"> & {\n  variant?: ResponseStatusVariant;\n  status?: ResponseStatusValue;\n};\n\ntype ActionKind = \"stop\" | \"retry\";\ntype ResponseStatusContextValue = {\n  id: string;\n  variant: ResponseStatusVariant;\n  status: ResponseStatusValue;\n  active: boolean;\n  register: (kind: ActionKind, node: HTMLButtonElement | null) => void;\n  setFocused: (kind: ActionKind | null) => void;\n};\n\nconst ResponseStatusContext = createContext<ResponseStatusContextValue | null>(null);\n\nfunction useResponseStatus(part: string) {\n  const context = useContext(ResponseStatusContext);\n  if (!context) throw new Error(`${part} must be used within ResponseStatus`);\n  return context;\n}\n\nconst useIsomorphicLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\nconst dangerText = \"text-destructive-ink\";\nconst defaultLabels: Record<ResponseStatusValue, string> = {\n  queued: \"Waiting to start…\",\n  streaming: \"Generating response…\",\n  stopped: \"Response stopped\",\n  complete: \"Response complete\",\n  failed: \"Response failed\",\n};\n\nconst responseStatusVariants = cva(\n  \"max-w-full min-w-0 flex-wrap items-center text-[13px]/[18px] text-foreground outline-none\",\n  {\n    variants: {\n      variant: {\n        inline: \"inline-flex min-h-7 gap-2\",\n        pill: \"inline-flex min-h-8 gap-2 rounded-full bg-muted py-0.5 pr-0.5 pl-2.5 inset-ring-1 inset-ring-foreground/5\",\n        bar: \"flex min-h-11 gap-2.5 rounded-[14px] border bg-card py-1.5 pr-1.5 pl-3.5\",\n      },\n      failed: { true: \"\", false: \"\" },\n    },\n    compoundVariants: [\n      {\n        variant: \"bar\",\n        failed: true,\n        className: \"border-[color-mix(in_oklab,var(--destructive)_45%,var(--border))]\",\n      },\n    ],\n  },\n);\n\nconst responseStatusActionVariants = cva(\n  \"h-7 cursor-pointer gap-1.5 rounded-full px-3 text-[12.5px] text-foreground has-[>svg]:px-3 [transition:background-color_120ms_ease-out,scale_140ms_cubic-bezier(0.23,1,0.32,1)] animate-in duration-200 ease-out-quint fade-in-0 zoom-in-95 focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-2 focus-visible:outline-ring active:scale-[0.97] motion-reduce:animate-none motion-reduce:transition-none motion-reduce:active:scale-100\",\n  {\n    variants: {\n      variant: {\n        inline:\n          \"bg-secondary hover:bg-[color-mix(in_oklab,var(--secondary)_85%,var(--foreground))]\",\n        pill: \"bg-card hover:bg-accent\",\n        bar: \"bg-secondary hover:bg-[color-mix(in_oklab,var(--secondary)_85%,var(--foreground))]\",\n      },\n    },\n  },\n);\n\nfunction prefersReducedMotion() {\n  return (\n    typeof window.matchMedia === \"function\" &&\n    window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n  );\n}\n\nexport function ResponseStatus({\n  variant = \"inline\",\n  status = \"queued\",\n  children,\n  className,\n  ...props\n}: ResponseStatusProps) {\n  const id = useId();\n  const rootRef = useRef<HTMLDivElement>(null);\n  const actions = useRef<Partial<Record<ActionKind, HTMLButtonElement>>>({});\n  const focused = useRef<ActionKind | null>(null);\n  const active = status === \"queued\" || status === \"streaming\";\n  // When the focused Stop or Retry action unmounts after a status change, keep focus nearby.\n  useIsomorphicLayoutEffect(() => {\n    const kind = focused.current;\n    const lost = !document.activeElement || document.activeElement === document.body;\n    if (!kind || actions.current[kind]?.isConnected || !lost) return;\n    focused.current = null;\n    const next = actions.current[kind === \"stop\" ? \"retry\" : \"stop\"];\n    if (next?.isConnected) {\n      focused.current = kind === \"stop\" ? \"retry\" : \"stop\";\n      next.focus();\n    } else rootRef.current?.focus();\n  }, [status]);\n  return (\n    <ResponseStatusContext.Provider\n      value={{\n        id,\n        variant,\n        status,\n        active,\n        register: (kind, node) => {\n          if (node) actions.current[kind] = node;\n          else delete actions.current[kind];\n        },\n        setFocused: (kind) => {\n          focused.current = kind;\n        },\n      }}\n    >\n      <div\n        tabIndex={-1}\n        data-slot=\"response-status\"\n        {...props}\n        ref={rootRef}\n        aria-busy={active || undefined}\n        data-variant={variant}\n        data-status={status}\n        className={cn(responseStatusVariants({ variant, failed: status === \"failed\" }), className)}\n      >\n        {children}\n      </div>\n    </ResponseStatusContext.Provider>\n  );\n}\n\nfunction StreamingDots() {\n  const ref = useRef<HTMLSpanElement>(null);\n  useEffect(() => {\n    if (prefersReducedMotion()) return;\n    const dots = Array.from(ref.current?.children ?? []);\n    const animations = dots.map((dot, index) =>\n      dot.animate?.([{ opacity: 0.25 }, { opacity: 1 }, { opacity: 0.25 }], {\n        duration: 1100,\n        delay: index * 160,\n        iterations: Number.POSITIVE_INFINITY,\n        easing: \"ease-in-out\",\n      }),\n    );\n    return () => {\n      for (const animation of animations) animation?.cancel();\n    };\n  }, []);\n  return (\n    <span ref={ref} className=\"inline-flex gap-0.75\">\n      {[0, 1, 2].map((dot) => (\n        <span key={dot} className=\"size-1 rounded-full bg-current opacity-60\" />\n      ))}\n    </span>\n  );\n}\n\nexport function ResponseStatusIndicator({ children, className, ...props }: ComponentProps<\"span\">) {\n  const context = useResponseStatus(\"ResponseStatusIndicator\");\n  const icon = {\n    queued: <CircleDashed size={14} className=\"size-3.5\" />,\n    streaming: <StreamingDots />,\n    stopped: <Square size={11} className=\"size-[11px]\" fill=\"currentColor\" strokeWidth={0} />,\n    complete: <Check size={14} className=\"size-3.5\" />,\n    failed: <CircleAlert size={14} className=\"size-3.5\" />,\n  }[context.status];\n  const color = {\n    queued: \"text-subtle-foreground\",\n    streaming: \"text-foreground\",\n    stopped: \"text-subtle-foreground\",\n    complete: \"text-success-ink\",\n    failed: dangerText,\n  }[context.status];\n  return (\n    <span\n      aria-hidden=\"true\"\n      data-slot=\"response-status-indicator\"\n      className={cn(\n        \"grid size-[18px] flex-none place-items-center transition-[color] duration-200 ease-out\",\n        color,\n        className,\n      )}\n      {...props}\n    >\n      <span\n        key={context.status}\n        className=\"grid place-items-center animate-in duration-200 ease-out-quint fade-in-0 zoom-in-50 motion-reduce:animate-none\"\n      >\n        {children ?? icon}\n      </span>\n    </span>\n  );\n}\n\nexport function ResponseStatusLabel({ children, className, ...props }: ComponentProps<\"span\">) {\n  const context = useResponseStatus(\"ResponseStatusLabel\");\n  return (\n    <span\n      role=\"status\"\n      aria-live=\"polite\"\n      aria-atomic=\"true\"\n      data-slot=\"response-status-label\"\n      className={cn(\n        \"min-w-0 font-medium transition-[color] duration-200 ease-out\",\n        context.active\n          ? \"shimmer-text\"\n          : context.status === \"failed\"\n            ? dangerText\n            : \"text-foreground\",\n        className,\n      )}\n      {...props}\n      id={`${context.id}-label`}\n    >\n      {children ?? defaultLabels[context.status]}\n    </span>\n  );\n}\n\nexport function ResponseStatusDetail({ className, ...props }: ComponentProps<\"span\">) {\n  useResponseStatus(\"ResponseStatusDetail\");\n  return (\n    <span\n      data-slot=\"response-status-detail\"\n      className={cn(\"min-w-0 text-[12px] text-subtle-foreground tabular-nums\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function ResponseStatusActions({ className, ...props }: ComponentProps<\"div\">) {\n  useResponseStatus(\"ResponseStatusActions\");\n  return (\n    <div\n      data-slot=\"response-status-actions\"\n      className={cn(\"ml-auto flex items-center gap-1\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction useAction(kind: ActionKind, part: string) {\n  const context = useResponseStatus(part);\n  return {\n    context,\n    ref: (node: HTMLButtonElement | null) => context.register(kind, node),\n    onFocus: () => context.setFocused(kind),\n    // A blur caused by unmounting keeps the record so the root can restore focus.\n    onBlur: (node: HTMLButtonElement) =>\n      queueMicrotask(() => {\n        if (node.isConnected) context.setFocused(null);\n      }),\n  };\n}\n\nexport function ResponseStatusStop({\n  children,\n  onFocus,\n  onBlur,\n  className,\n  ...props\n}: ComponentProps<\"button\">) {\n  const { context, ref, onFocus: track, onBlur: untrack } = useAction(\"stop\", \"ResponseStatusStop\");\n  if (!context.active) return null;\n  return (\n    <Button\n      type=\"button\"\n      variant=\"secondary\"\n      size=\"sm\"\n      aria-describedby={`${context.id}-label`}\n      data-slot=\"response-status-stop\"\n      className={cn(responseStatusActionVariants({ variant: context.variant }), className)}\n      {...props}\n      ref={ref}\n      onFocus={(event) => {\n        onFocus?.(event);\n        track();\n      }}\n      onBlur={(event) => {\n        onBlur?.(event);\n        untrack(event.currentTarget);\n      }}\n    >\n      {children ?? (\n        <>\n          <Square className=\"size-2.25\" fill=\"currentColor\" strokeWidth={0} aria-hidden=\"true\" />\n          Stop\n        </>\n      )}\n    </Button>\n  );\n}\n\nexport function ResponseStatusRetry({\n  children,\n  onFocus,\n  onBlur,\n  className,\n  ...props\n}: ComponentProps<\"button\">) {\n  const {\n    context,\n    ref,\n    onFocus: track,\n    onBlur: untrack,\n  } = useAction(\"retry\", \"ResponseStatusRetry\");\n  if (context.status !== \"failed\" && context.status !== \"stopped\") return null;\n  return (\n    <Button\n      type=\"button\"\n      variant=\"secondary\"\n      size=\"sm\"\n      aria-describedby={`${context.id}-label`}\n      data-slot=\"response-status-retry\"\n      className={cn(responseStatusActionVariants({ variant: context.variant }), className)}\n      {...props}\n      ref={ref}\n      onFocus={(event) => {\n        onFocus?.(event);\n        track();\n      }}\n      onBlur={(event) => {\n        onBlur?.(event);\n        untrack(event.currentTarget);\n      }}\n    >\n      {children ?? (\n        <>\n          <RotateCw className=\"size-3\" aria-hidden=\"true\" />\n          {context.status === \"failed\" ? \"Retry\" : \"Regenerate\"}\n        </>\n      )}\n    </Button>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/response-status.tsx"
    }
  ],
  "categories": [
    "ai",
    "status"
  ],
  "type": "registry:ui"
}