Ai Approval
smoothui-registry/ai-approvalFreeComponent
Human-in-the-loop approval card where the chosen option expands to fill the card while the alternatives collapse away.
Install it
npx shadcn@latest add https://www.smoothui.dev/r/ai-approval.jsonSource
1"use client";23import { cn } from "@/lib/utils";4import { Check } from "lucide-react";5import { AnimatePresence, motion, useReducedMotion } from "motion/react";6import { type ReactNode, useState } from "react";78const SPRING_DEFAULT = {9 bounce: 0.1,10 duration: 0.25,11 type: "spring" as const,12};13const EASE_OUT = [0.23, 1, 0.32, 1] as const;14const CHOICE_STAGGER = 0.03;1516export type AIApprovalOption = {17 /** Marks the option as the destructive one, e.g. "Delete everything". */18 destructive?: boolean;19 /** Secondary line under the label. */20 detail?: string;21 id: string;22 label: string;23};2425export type AIApprovalProps = {26 className?: string;27 /** Extra context under the question. */28 children?: ReactNode;29 /** Called once, with the chosen option. */30 onDecide?: (option: AIApprovalOption) => void;31 options: AIApprovalOption[];32 /** What the agent needs a human to settle. */33 question: string;34 /** Render already-resolved, e.g. when replaying a transcript. */35 resolvedId?: string;36};3738/**39 * The card an agent puts up before it acts.40 *41 * Choosing does not tick a radio button — the chosen option expands to fill the42 * card and the alternatives collapse out of existence. The decision should look43 * as irreversible as it is; leaving the rejected options sitting there greyed out44 * invites a second look at something that already happened.45 */46const AIApproval = ({47 className,48 children,49 onDecide,50 options,51 question,52 resolvedId,53}: AIApprovalProps) => {54 const shouldReduceMotion = useReducedMotion();55 const [chosenId, setChosenId] = useState<string | null>(resolvedId ?? null);5657 const chosen = options.find((option) => option.id === chosenId) ?? null;5859 const decide = (option: AIApprovalOption) => {60 if (chosenId) {61 return;62 }63 setChosenId(option.id);64 onDecide?.(option);65 };6667 return (68 <motion.div69 className={cn(70 "w-full rounded-xl border border-border bg-background p-3.5",71 className72 )}73 layout={!shouldReduceMotion}74 transition={shouldReduceMotion ? { duration: 0 } : SPRING_DEFAULT}75 >76 <motion.div layout={!shouldReduceMotion}>77 <p className="font-medium text-foreground text-sm">{question}</p>78 {children ? (79 <div className="mt-1 text-muted-foreground text-xs leading-relaxed">80 {children}81 </div>82 ) : null}83 </motion.div>8485 <div className="mt-3">86// … truncated
What it pulls in
npm packages
Files it writes
More from SmoothUI Registry
All 178 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Agent Avataragent-avatar | SmoothUI Registry | Components | Free | no deps | |
| Ai Artifactai-artifact | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Branchai-branch | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Citationai-citation | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Context Meterai-context-meter | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Conversationai-conversation | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Coreai-core | SmoothUI Registry | Components | Free | 1 dep | |
| Ai Diffai-diff | SmoothUI Registry | Components | Free | 2 deps |
