{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "inline-feedback",
  "title": "Inline Feedback",
  "description": "Confirm or reject a local action in a polite live region without interrupting the workflow.",
  "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/inline-feedback.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { Check, LoaderCircle, X } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type MouseEvent,\n  useContext,\n  useEffect,\n  useEffectEvent,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const INLINE_FEEDBACK_VARIANTS = [\"text\", \"pill\", \"outlined\"] as const;\nexport type InlineFeedbackVariant = (typeof INLINE_FEEDBACK_VARIANTS)[number];\nexport type InlineFeedbackStatus = \"idle\" | \"pending\" | \"success\" | \"error\";\nexport type InlineFeedbackProps = ComponentProps<\"div\"> & {\n  variant?: InlineFeedbackVariant;\n  status?: InlineFeedbackStatus;\n  defaultStatus?: InlineFeedbackStatus;\n  onStatusChange?: (status: InlineFeedbackStatus) => void;\n  /** Milliseconds before success or error returns to idle. Omit to keep the message. */\n  duration?: number;\n};\ntype FeedbackContext = {\n  variant: InlineFeedbackVariant;\n  status: InlineFeedbackStatus;\n  change: (status: InlineFeedbackStatus) => void;\n};\nconst Context = createContext<FeedbackContext | null>(null);\nfunction useFeedback(part: string) {\n  const context = useContext(Context);\n  if (!context) throw new Error(`${part} must be used within InlineFeedback`);\n  return context;\n}\nconst inlineFeedbackVariants = cva(\n  \"inline-flex min-w-0 flex-wrap items-center gap-2.5 text-[13px]/[18px] text-foreground\",\n  { variants: { variant: { text: \"\", pill: \"\", outlined: \"\" } } },\n);\n\nexport function InlineFeedback({\n  variant = \"text\",\n  status,\n  defaultStatus = \"idle\",\n  onStatusChange,\n  duration,\n  className,\n  children,\n  ...props\n}: InlineFeedbackProps) {\n  const [internal, setInternal] = useState(defaultStatus);\n  const current = status ?? internal;\n  const change = (next: InlineFeedbackStatus) => {\n    if (status === undefined) setInternal(next);\n    onStatusChange?.(next);\n  };\n  const reset = useEffectEvent(() => change(\"idle\"));\n  useEffect(() => {\n    if (duration === undefined || (current !== \"success\" && current !== \"error\")) return;\n    const timer = window.setTimeout(reset, duration);\n    return () => window.clearTimeout(timer);\n  }, [current, duration]);\n  return (\n    <Context.Provider value={{ variant, status: current, change }}>\n      <div\n        data-slot=\"inline-feedback\"\n        className={cn(inlineFeedbackVariants({ variant }), className)}\n        {...props}\n        data-variant={variant}\n        data-status={current}\n      >\n        {children}\n      </div>\n    </Context.Provider>\n  );\n}\n\nexport type InlineFeedbackActionProps = Omit<ComponentProps<\"button\">, \"onClick\"> & {\n  /** Runs the local action. Resolve for success, throw or reject for error. */\n  onAction?: (event: MouseEvent<HTMLButtonElement>) => unknown;\n};\n\nexport function InlineFeedbackAction({\n  onAction,\n  disabled,\n  className,\n  ...props\n}: InlineFeedbackActionProps) {\n  const context = useFeedback(\"InlineFeedbackAction\");\n  const pending = context.status === \"pending\";\n  return (\n    <Button\n      type=\"button\"\n      variant=\"secondary\"\n      data-slot=\"inline-feedback-action\"\n      className={cn(\n        \"h-7 cursor-pointer rounded-full px-3 py-0 text-[12.5px]/4 has-[>svg]:px-3 [transition:background-color_120ms_ease-out,scale_140ms_var(--ease-out-quint)] hover:bg-[color-mix(in_oklab,var(--secondary)_85%,var(--foreground))] 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] aria-busy:cursor-progress motion-reduce:transition-none motion-reduce:active:scale-100\",\n        className,\n      )}\n      {...props}\n      disabled={disabled}\n      aria-disabled={pending || undefined}\n      aria-busy={pending || undefined}\n      onClick={async (event) => {\n        if (pending || !onAction) return;\n        context.change(\"pending\");\n        try {\n          await onAction(event);\n          context.change(\"success\");\n        } catch {\n          context.change(\"error\");\n        }\n      }}\n    />\n  );\n}\n\nexport function InlineFeedbackStatus({ className, children, ...props }: ComponentProps<\"span\">) {\n  const context = useFeedback(\"InlineFeedbackStatus\");\n  const visible = context.status !== \"idle\";\n  const { status, variant } = context;\n  return (\n    <span\n      role=\"status\"\n      aria-live=\"polite\"\n      data-slot=\"inline-feedback-status\"\n      className={cn(\n        \"inline-flex min-h-6 items-center gap-1.5 text-[12.5px]\",\n        variant === \"text\" ? \"font-normal\" : \"font-medium\",\n        status === \"error\"\n          ? \"text-destructive-ink\"\n          : status === \"success\" && variant === \"pill\"\n            ? \"text-success-ink\"\n            : \"text-muted-foreground\",\n        visible &&\n          variant === \"pill\" &&\n          cn(\n            \"rounded-full py-0.5 pr-2.5 pl-2\",\n            status === \"error\"\n              ? \"bg-destructive/14\"\n              : status === \"success\"\n                ? \"bg-success/14\"\n                : \"bg-muted\",\n          ),\n        visible &&\n          variant === \"outlined\" &&\n          cn(\n            \"rounded-full border py-px pr-2.25 pl-1.75\",\n            status === \"error\"\n              ? \"border-destructive/32\"\n              : status === \"success\"\n                ? \"border-success/32\"\n                : \"border-border\",\n          ),\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </span>\n  );\n}\n\nexport function InlineFeedbackMessage({\n  status,\n  children,\n  className,\n  ...props\n}: ComponentProps<\"span\"> & { status: Exclude<InlineFeedbackStatus, \"idle\"> }) {\n  const context = useFeedback(\"InlineFeedbackMessage\");\n  if (context.status !== status) return null;\n  const Icon = status === \"success\" ? Check : status === \"error\" ? X : LoaderCircle;\n  const pending = status === \"pending\";\n  return (\n    <span\n      data-slot=\"inline-feedback-message\"\n      className={cn(\n        \"inline-flex origin-left animate-in items-center gap-1.5 duration-180 ease-[cubic-bezier(0.16,1,0.3,1)] fade-in-0 zoom-in-96 fill-mode-both motion-reduce:animate-none\",\n        className,\n      )}\n      {...props}\n    >\n      <Icon\n        size={14}\n        strokeWidth={2}\n        aria-hidden=\"true\"\n        className={cn(\n          \"size-3.5\",\n          \"flex-none\",\n          status === \"success\" && \"text-success-ink\",\n          pending &&\n            \"animate-[spin_900ms_linear_infinite] text-subtle-foreground motion-reduce:animate-none\",\n        )}\n      />\n      <span className={pending ? \"shimmer-text motion-reduce:text-inherit!\" : undefined}>\n        {children}\n      </span>\n    </span>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/inline-feedback.tsx"
    }
  ],
  "categories": [
    "feedback",
    "status"
  ],
  "type": "registry:ui"
}