{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-question",
  "title": "Agent Question",
  "description": "Lets an agent ask for a decision: one to five single or multiple choice questions, a recommended option, Something else, number-key answers, and a record of the answers.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "button",
    "checkbox",
    "collapsible",
    "input",
    "radio-group",
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/components/agent-question.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { Check, ChevronDown, X } from \"lucide-react\";\nimport {\n  Children,\n  type ComponentProps,\n  createContext,\n  isValidElement,\n  type KeyboardEvent,\n  type ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from \"@/components/ui/collapsible\";\nimport { Input } from \"@/components/ui/input\";\nimport { RadioGroup, RadioGroupItem } from \"@/components/ui/radio-group\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const AGENT_QUESTION_VARIANTS = [\"card\", \"inline\", \"compact\"] as const;\nexport const AGENT_QUESTION_STATUSES = [\"asking\", \"submitting\", \"answered\", \"dismissed\"] as const;\n\nexport type AgentQuestionVariant = (typeof AGENT_QUESTION_VARIANTS)[number];\nexport type AgentQuestionStatus = (typeof AGENT_QUESTION_STATUSES)[number];\nexport type AgentQuestionType = \"single\" | \"multiple\";\n/**\n * Answers by question id. A single choice is a string, a multiple choice a string array.\n * Choosing \"Something else\" records the value `other`, and its text sits under `\"<id>:other\"`.\n * A skipped question has no entry.\n */\nexport type AgentQuestionAnswers = Record<string, string | string[]>;\n\n/** The key that holds a question's \"Something else\" text. */\nexport function agentQuestionOtherKey(questionId: string) {\n  return `${questionId}:other`;\n}\n\ntype RegisteredItem = {\n  id: string;\n  type: AgentQuestionType;\n  required: boolean;\n  element: HTMLElement;\n};\n\ntype AgentQuestionContextValue = {\n  variant: AgentQuestionVariant;\n  status: AgentQuestionStatus;\n  busy: boolean;\n  items: readonly RegisteredItem[];\n  step: number;\n  direction: 1 | -1;\n  leaving: number | null;\n  answers: AgentQuestionAnswers;\n  error: ReactNode;\n  focusOnStep: React.RefObject<boolean>;\n  titles: React.RefObject<Map<string, string>>;\n  labels: React.RefObject<Map<string, Map<string, string>>>;\n  register: (item: RegisteredItem) => () => void;\n  setAnswer: (id: string, value: string | string[] | undefined) => void;\n  setOther: (id: string, text: string) => void;\n  isAnswered: (item: RegisteredItem | undefined) => boolean;\n  next: () => void;\n  back: () => void;\n  skip: () => void;\n  dismiss: (() => void) | undefined;\n  primaryRef: React.RefObject<HTMLButtonElement | null>;\n};\n\nconst AgentQuestionContext = createContext<AgentQuestionContextValue | null>(null);\n\nfunction useAgentQuestion(name: string) {\n  const context = useContext(AgentQuestionContext);\n  if (!context) throw new Error(`${name} must be used within AgentQuestion`);\n  return context;\n}\n\ntype ItemContextValue = {\n  id: string;\n  type: AgentQuestionType;\n  index: number;\n  current: boolean;\n  legendId: string;\n  descriptionId: string;\n  legendRef: React.RefObject<HTMLLegendElement | null>;\n};\n\nconst ItemContext = createContext<ItemContextValue | null>(null);\n\nfunction useItem(name: string) {\n  const context = useContext(ItemContext);\n  if (!context) throw new Error(`${name} must be used within AgentQuestionItem`);\n  return context;\n}\n\ntype OptionsContextValue = { optionCount: number };\nconst OptionsContext = createContext<OptionsContextValue>({ optionCount: 0 });\n\nfunction hasValue(value: string | string[] | undefined) {\n  return Array.isArray(value) ? value.length > 0 : Boolean(value);\n}\n\nconst rootVariants = cva(\n  \"group/aq grid w-full min-w-0 animate-in text-[13px]/[18px] text-card-foreground duration-240 ease-out-quint fade-in-0 slide-in-from-bottom-1 motion-reduce:animate-none\",\n  {\n    variants: {\n      variant: {\n        card: \"max-w-[420px] rounded-[14px] border border-border bg-card\",\n        inline: \"max-w-[420px]\",\n        compact: \"max-w-[300px] rounded-[12px] border border-border bg-card\",\n      },\n    },\n  },\n);\n\nconst bodyInnerVariants = cva(\"grid min-w-0\", {\n  variants: {\n    variant: {\n      card: \"gap-3.5 p-4\",\n      inline: \"ml-1 gap-3.5 border-l border-border py-1 pl-4\",\n      compact: \"gap-2.5 p-3\",\n    },\n  },\n});\n\nexport type AgentQuestionProps = Omit<ComponentProps<\"section\">, \"onSubmit\" | \"defaultValue\"> & {\n  variant?: AgentQuestionVariant;\n  /** Controlled status. Leave it unset to let the component move to submitting and answered. */\n  status?: AgentQuestionStatus;\n  step?: number;\n  defaultStep?: number;\n  onStepChange?: (step: number) => void;\n  value?: AgentQuestionAnswers;\n  defaultValue?: AgentQuestionAnswers;\n  onValueChange?: (answers: AgentQuestionAnswers) => void;\n  /** Called with every answer. Return a promise to show the submitting state until it settles. */\n  onSubmit?: (answers: AgentQuestionAnswers) => unknown;\n  /** Called with the question id when an optional question is skipped. */\n  onSkip?: (questionId: string) => void;\n  /** Shows the dismiss button in the header. */\n  onDismiss?: () => void;\n  /** A danger-ink line above the footer. The answers are kept. */\n  error?: ReactNode;\n};\n\n/**\n * A decision an agent asks for: one to five questions, single or multiple choice. The questions\n * collapse into `AgentQuestionSummary` once answered.\n */\nexport function AgentQuestion({\n  variant = \"card\",\n  status: statusProp,\n  step: stepProp,\n  defaultStep = 0,\n  onStepChange,\n  value: valueProp,\n  defaultValue = {},\n  onValueChange,\n  onSubmit,\n  onSkip,\n  onDismiss,\n  error: errorProp,\n  className,\n  children,\n  onKeyDown,\n  ...props\n}: AgentQuestionProps) {\n  const [internalStatus, setInternalStatus] = useState<AgentQuestionStatus>(\"asking\");\n  const status = statusProp ?? internalStatus;\n  const [internalStep, setInternalStep] = useState(defaultStep);\n  const step = stepProp ?? internalStep;\n  const [internalValue, setInternalValue] = useState<AgentQuestionAnswers>(defaultValue);\n  const answers = valueProp ?? internalValue;\n  const [internalError, setInternalError] = useState<ReactNode>(null);\n  const error = errorProp ?? internalError;\n  const [items, setItems] = useState<RegisteredItem[]>([]);\n  const [direction, setDirection] = useState<1 | -1>(1);\n  const [leaving, setLeaving] = useState<number | null>(null);\n  const focusOnStep = useRef(false);\n  const titles = useRef(new Map<string, string>());\n  const labels = useRef(new Map<string, Map<string, string>>());\n  const primaryRef = useRef<HTMLButtonElement | null>(null);\n  const sectionRef = useRef<HTMLElement | null>(null);\n  const summaryHeadingRef = useRef<HTMLHeadingElement | null>(null);\n  const answersRef = useRef(answers);\n  answersRef.current = answers;\n\n  const register = useCallback((item: RegisteredItem) => {\n    setItems((current) =>\n      [...current.filter((entry) => entry.id !== item.id), item].sort((a, b) =>\n        a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1,\n      ),\n    );\n    return () => setItems((current) => current.filter((entry) => entry.id !== item.id));\n  }, []);\n\n  const commit = useCallback(\n    (next: AgentQuestionAnswers) => {\n      answersRef.current = next;\n      if (valueProp === undefined) setInternalValue(next);\n      onValueChange?.(next);\n    },\n    [valueProp, onValueChange],\n  );\n\n  const setAnswer = useCallback(\n    (id: string, value: string | string[] | undefined) => {\n      const next = { ...answersRef.current };\n      if (value === undefined || (Array.isArray(value) && value.length === 0)) delete next[id];\n      else next[id] = value;\n      commit(next);\n    },\n    [commit],\n  );\n\n  const setOther = useCallback(\n    (id: string, text: string) => {\n      const next = { ...answersRef.current };\n      if (text) next[agentQuestionOtherKey(id)] = text;\n      else delete next[agentQuestionOtherKey(id)];\n      commit(next);\n    },\n    [commit],\n  );\n\n  const isAnswered = useCallback(\n    (item: RegisteredItem | undefined) => {\n      if (!item) return false;\n      const value = answers[item.id];\n      if (!hasValue(value)) return false;\n      const picked = Array.isArray(value) ? value : [value as string];\n      if (picked.includes(\"other\") && !answers[agentQuestionOtherKey(item.id)]?.toString().trim())\n        return picked.length > 1;\n      return true;\n    },\n    [answers],\n  );\n\n  const goTo = useCallback(\n    (nextStep: number) => {\n      setDirection(nextStep > step ? 1 : -1);\n      setLeaving(step);\n      focusOnStep.current = true;\n      if (stepProp === undefined) setInternalStep(nextStep);\n      onStepChange?.(nextStep);\n    },\n    [step, stepProp, onStepChange],\n  );\n\n  useEffect(() => {\n    if (leaving === null) return;\n    const timer = window.setTimeout(() => setLeaving(null), 200);\n    return () => window.clearTimeout(timer);\n  }, [leaving]);\n\n  const submit = useCallback(() => {\n    const result = onSubmit?.(answersRef.current);\n    if (statusProp !== undefined) return;\n    if (result && typeof (result as Promise<unknown>).then === \"function\") {\n      setInternalStatus(\"submitting\");\n      setInternalError(null);\n      (result as Promise<unknown>).then(\n        () => setInternalStatus(\"answered\"),\n        (reason: unknown) => {\n          setInternalStatus(\"asking\");\n          setInternalError(reason instanceof Error ? reason.message : \"Couldn’t send. Try again.\");\n        },\n      );\n    } else {\n      setInternalStatus(\"answered\");\n    }\n  }, [onSubmit, statusProp]);\n\n  const next = useCallback(() => {\n    if (step < items.length - 1) goTo(step + 1);\n    else submit();\n  }, [step, items.length, goTo, submit]);\n\n  const back = useCallback(() => {\n    if (step > 0) goTo(step - 1);\n  }, [step, goTo]);\n\n  const skip = useCallback(() => {\n    const item = items[step];\n    if (!item) return;\n    const nextAnswers = { ...answersRef.current };\n    delete nextAnswers[item.id];\n    delete nextAnswers[agentQuestionOtherKey(item.id)];\n    commit(nextAnswers);\n    onSkip?.(item.id);\n    next();\n  }, [items, step, commit, onSkip, next]);\n\n  const dismiss = onDismiss\n    ? () => {\n        onDismiss();\n        if (statusProp === undefined) setInternalStatus(\"dismissed\");\n      }\n    : undefined;\n\n  // Move focus to the summary heading once answered, but only if focus was inside.\n  const previousStatus = useRef(status);\n  useEffect(() => {\n    const before = previousStatus.current;\n    previousStatus.current = status;\n    if (before === status) return;\n    if (status !== \"answered\" && status !== \"dismissed\") return;\n    const section = sectionRef.current;\n    const active = document.activeElement;\n    if (section && (active === document.body || (active && section.contains(active)))) {\n      summaryHeadingRef.current?.focus();\n    }\n  }, [status]);\n\n  const collapsed = status === \"answered\" || status === \"dismissed\";\n  const busy = status === \"submitting\";\n\n  const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n    onKeyDown?.(event);\n    const target = event.target as HTMLElement;\n    // Radio and checkbox primitives prevent Enter themselves; it still continues here.\n    const choice = target.closest(\"[role=radio], [role=checkbox]\");\n    if ((event.defaultPrevented && !choice) || collapsed || busy) return;\n    if (event.metaKey || event.ctrlKey || event.altKey) return;\n    const typing = target.closest(\"input, textarea, [contenteditable='true']\");\n    if (/^[1-9]$/.test(event.key) && !typing) {\n      const item = items[step];\n      if (!item) return;\n      const options = Array.from(\n        item.element.querySelectorAll<HTMLElement>(\n          \"[data-slot=agent-question-option] [data-index]\",\n        ),\n      );\n      const control = options[Number(event.key) - 1];\n      if (!control) return;\n      event.preventDefault();\n      control.click();\n      control.focus();\n      return;\n    }\n    if (event.key === \"Enter\") {\n      const activates = target.closest(\n        \"button:not([role=radio]):not([role=checkbox]), a, summary, [role=button]\",\n      );\n      if (activates) return;\n      event.preventDefault();\n      if (primaryRef.current && !primaryRef.current.disabled) primaryRef.current.click();\n    }\n  };\n\n  const all = Children.toArray(children);\n  const summaryIndex = all.findIndex(\n    (child) => isValidElement(child) && child.type === AgentQuestionSummary,\n  );\n  const summary = summaryIndex >= 0 ? all[summaryIndex] : <AgentQuestionSummary />;\n  const rest = summaryIndex >= 0 ? all.filter((_, index) => index !== summaryIndex) : all;\n\n  const value: AgentQuestionContextValue = {\n    variant,\n    status,\n    busy,\n    items,\n    step,\n    direction,\n    leaving,\n    answers,\n    error,\n    focusOnStep,\n    titles,\n    labels,\n    register,\n    setAnswer,\n    setOther,\n    isAnswered,\n    next,\n    back,\n    skip,\n    dismiss,\n    primaryRef,\n  };\n\n  return (\n    <AgentQuestionContext.Provider value={value}>\n      <SummaryHeadingContext.Provider value={summaryHeadingRef}>\n        {/* biome-ignore lint/a11y/noStaticElementInteractions: number keys and Enter are delegated from the options and the input. */}\n        <section\n          ref={sectionRef}\n          data-slot=\"agent-question\"\n          data-variant={variant}\n          data-status={status}\n          aria-busy={busy || undefined}\n          className={cn(\n            rootVariants({ variant }),\n            collapsed && \"border-0 bg-transparent\",\n            className,\n          )}\n          onKeyDown={handleKeyDown}\n          {...props}\n        >\n          <div\n            data-slot=\"agent-question-body\"\n            inert={collapsed || undefined}\n            className={cn(\n              \"grid min-w-0 [transition:grid-template-rows_300ms_cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none\",\n              collapsed ? \"grid-rows-[0fr]\" : \"grid-rows-[1fr]\",\n            )}\n          >\n            <div className=\"min-h-0 min-w-0 overflow-hidden\">\n              <div className={bodyInnerVariants({ variant })}>{rest}</div>\n            </div>\n          </div>\n          <div\n            data-slot=\"agent-question-record\"\n            className={cn(\n              \"grid min-w-0 [transition:grid-template-rows_300ms_cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none\",\n              collapsed ? \"grid-rows-[1fr]\" : \"grid-rows-[0fr]\",\n            )}\n          >\n            <div className=\"min-h-0 min-w-0 overflow-hidden\">{collapsed ? summary : null}</div>\n          </div>\n          <span role=\"status\" className=\"sr-only\">\n            {status === \"answered\" ? \"Answers sent\" : \"\"}\n          </span>\n        </section>\n      </SummaryHeadingContext.Provider>\n    </AgentQuestionContext.Provider>\n  );\n}\n\nconst SummaryHeadingContext = createContext<React.RefObject<HTMLHeadingElement | null> | null>(\n  null,\n);\n\n/* -------------------------------------------------------------------------------------------------\n * Header\n * -----------------------------------------------------------------------------------------------*/\n\nexport function AgentQuestionHeader({ className, children, ...props }: ComponentProps<\"div\">) {\n  const context = useAgentQuestion(\"AgentQuestionHeader\");\n  const total = context.items.length;\n  const compact = context.variant === \"compact\";\n  return (\n    <div\n      data-slot=\"agent-question-header\"\n      className={cn(\"flex min-w-0 items-center justify-between gap-2\", className)}\n      {...props}\n    >\n      <span className=\"flex min-w-0 items-center gap-2 text-[11.5px]/4 text-subtle-foreground tabular-nums\">\n        {total > 1 ? (\n          <span aria-hidden=\"true\" className=\"flex items-center gap-1\">\n            {context.items.map((item, index) =>\n              index === context.step ? (\n                <span\n                  key={`${item.id}-${context.step}`}\n                  className=\"h-1 w-4 origin-left rounded-full bg-foreground motion-safe:animate-[grow-x_240ms_cubic-bezier(0.23,1,0.32,1)_backwards]\"\n                />\n              ) : (\n                <span\n                  key={item.id}\n                  className={cn(\n                    \"size-1 rounded-full\",\n                    index < context.step ? \"bg-muted-foreground\" : \"bg-border-strong\",\n                  )}\n                />\n              ),\n            )}\n          </span>\n        ) : null}\n        {total > 0\n          ? compact\n            ? `${context.step + 1} of ${total}`\n            : `Question ${context.step + 1} of ${total}`\n          : null}\n      </span>\n      <span className=\"flex shrink-0 items-center gap-1.5\">\n        {children ?? (compact ? null : <AgentQuestionWaiting />)}\n        {context.dismiss ? (\n          <Button\n            type=\"button\"\n            variant=\"ghost\"\n            size=\"icon\"\n            aria-label=\"Dismiss question\"\n            onClick={context.dismiss}\n            className={cn(\n              \"rounded-lg text-muted-foreground [transition:background-color_120ms_ease-out,color_120ms_ease-out,scale_140ms_cubic-bezier(0.23,1,0.32,1)] hover:bg-muted hover:text-foreground active:scale-[0.94] motion-reduce:transition-none motion-reduce:active:scale-100\",\n              compact ? \"size-6 rounded-md\" : \"size-7\",\n            )}\n          >\n            <X className=\"size-3.5\" strokeWidth={1.75} aria-hidden=\"true\" />\n          </Button>\n        ) : null}\n      </span>\n    </div>\n  );\n}\n\n/** The \"Waiting for you\" badge. Not live: the agent's message announces the question. */\nexport function AgentQuestionWaiting({ className, children, ...props }: ComponentProps<\"span\">) {\n  const context = useAgentQuestion(\"AgentQuestionWaiting\");\n  if (context.status !== \"asking\") return null;\n  return (\n    <Badge\n      variant=\"secondary\"\n      data-slot=\"agent-question-waiting\"\n      className={cn(\n        \"h-6 gap-1.5 rounded-full border-0 bg-primary/14 px-2.5 py-0 text-[11.5px] font-medium text-primary-ink\",\n        className,\n      )}\n      {...props}\n    >\n      <span\n        aria-hidden=\"true\"\n        className=\"size-1.5 rounded-full bg-current motion-safe:animate-pulse\"\n      />\n      {children ?? \"Waiting for you\"}\n    </Badge>\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Item\n * -----------------------------------------------------------------------------------------------*/\n\nexport type AgentQuestionItemProps = Omit<ComponentProps<\"fieldset\">, \"id\"> & {\n  id: string;\n  type?: AgentQuestionType;\n  required?: boolean;\n};\n\n/** One question, a fieldset whose legend is the title. Only the current one shows. */\nexport function AgentQuestionItem({\n  id,\n  type = \"single\",\n  required = false,\n  className,\n  children,\n  ...props\n}: AgentQuestionItemProps) {\n  const context = useAgentQuestion(\"AgentQuestionItem\");\n  const ref = useRef<HTMLFieldSetElement | null>(null);\n  const legendRef = useRef<HTMLLegendElement | null>(null);\n  const legendId = useId();\n  const descriptionId = useId();\n  const { register } = context;\n\n  useLayoutEffect(() => {\n    if (!ref.current) return;\n    return register({ id, type, required, element: ref.current });\n  }, [register, id, type, required]);\n\n  const index = context.items.findIndex((item) => item.id === id);\n  const current = index === context.step;\n  const leaving = index === context.leaving && !current;\n\n  useEffect(() => {\n    if (!current || !context.focusOnStep.current) return;\n    context.focusOnStep.current = false;\n    legendRef.current?.focus();\n  }, [current, context.focusOnStep]);\n\n  const itemValue = useMemo<ItemContextValue>(\n    () => ({ id, type, index, current, legendId, descriptionId, legendRef }),\n    [id, type, index, current, legendId, descriptionId],\n  );\n\n  return (\n    <ItemContext.Provider value={itemValue}>\n      <fieldset\n        ref={ref}\n        data-slot=\"agent-question-item\"\n        data-current={current || undefined}\n        aria-describedby={descriptionId}\n        hidden={!current && !leaving}\n        inert={leaving || undefined}\n        aria-hidden={leaving || undefined}\n        disabled={context.busy}\n        className={cn(\n          \"m-0 grid min-w-0 gap-3 border-0 p-0\",\n          context.variant === \"compact\" && \"gap-2\",\n          current &&\n            (context.direction === 1\n              ? \"animate-in duration-200 ease-out-quint fade-in-0 slide-in-from-right-2 motion-reduce:animate-none\"\n              : \"animate-in duration-200 ease-out-quint fade-in-0 slide-in-from-left-2 motion-reduce:animate-none\"),\n          leaving &&\n            (context.direction === 1\n              ? \"pointer-events-none animate-out duration-200 ease-out-quint fade-out-0 slide-out-to-left-2 fill-mode-forwards motion-reduce:hidden\"\n              : \"pointer-events-none animate-out duration-200 ease-out-quint fade-out-0 slide-out-to-right-2 fill-mode-forwards motion-reduce:hidden\"),\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </fieldset>\n    </ItemContext.Provider>\n  );\n}\n\n/** Groups the items so the outgoing and incoming questions cross over in place. */\nexport function AgentQuestionItems({ className, ...props }: ComponentProps<\"div\">) {\n  useAgentQuestion(\"AgentQuestionItems\");\n  return (\n    <div\n      data-slot=\"agent-question-items\"\n      className={cn(\"grid min-w-0 *:[grid-area:1/1]\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function AgentQuestionTitle({ className, children, ...props }: ComponentProps<\"legend\">) {\n  const context = useAgentQuestion(\"AgentQuestionTitle\");\n  const item = useItem(\"AgentQuestionTitle\");\n  useLayoutEffect(() => {\n    if (typeof children === \"string\") context.titles.current.set(item.id, children);\n  }, [children, context.titles, item.id]);\n  return (\n    <legend\n      ref={item.legendRef}\n      id={item.legendId}\n      tabIndex={-1}\n      data-slot=\"agent-question-title\"\n      className={cn(\n        \"float-left m-0 w-full p-0 font-semibold tracking-[-0.005em] text-foreground outline-none [&+*]:clear-left\",\n        context.variant === \"compact\" ? \"text-[13px]/[18px]\" : \"text-[14px]/5\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </legend>\n  );\n}\n\n/** 12.5/18 muted. Hidden in compact. */\nexport function AgentQuestionDescription({ className, ...props }: ComponentProps<\"p\">) {\n  const context = useAgentQuestion(\"AgentQuestionDescription\");\n  const item = useItem(\"AgentQuestionDescription\");\n  if (context.variant === \"compact\") return null;\n  return (\n    <p\n      id={item.descriptionId}\n      data-slot=\"agent-question-description\"\n      className={cn(\"m-0 -mt-2.5 text-[12.5px]/[18px] text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Options\n * -----------------------------------------------------------------------------------------------*/\n\nconst STAGGER = [\n  \"\",\n  \"[&>*:nth-child(2)]:delay-[40ms]\",\n  \"[&>*:nth-child(3)]:delay-[80ms]\",\n  \"[&>*:nth-child(4)]:delay-[120ms]\",\n  \"[&>*:nth-child(5)]:delay-[160ms]\",\n  \"[&>*:nth-child(6)]:delay-[200ms]\",\n].join(\" \");\n\nexport type AgentQuestionOptionsProps = ComponentProps<\"div\">;\n\n/** A RadioGroup for single choice, or a labelled group of checkboxes for multiple choice. */\nexport function AgentQuestionOptions({ className, children, ...props }: AgentQuestionOptionsProps) {\n  const context = useAgentQuestion(\"AgentQuestionOptions\");\n  const item = useItem(\"AgentQuestionOptions\");\n  const count = Math.min(9, Children.toArray(children).filter(isValidElement).length);\n  const shortcuts = Array.from({ length: count }, (_, index) => String(index + 1)).join(\" \");\n  const classes = cn(\n    \"grid min-w-0 gap-1.5 *:animate-in *:fade-in-0 *:slide-in-from-bottom-1 *:duration-240 *:ease-out-quint *:fill-mode-backwards motion-reduce:*:animate-none\",\n    STAGGER,\n    context.variant === \"compact\" && \"gap-1\",\n    className,\n  );\n  const value = context.answers[item.id];\n  return (\n    <OptionsContext.Provider value={{ optionCount: count }}>\n      {item.type === \"single\" ? (\n        <RadioGroup\n          data-slot=\"agent-question-options\"\n          aria-labelledby={item.legendId}\n          aria-keyshortcuts={shortcuts || undefined}\n          value={typeof value === \"string\" ? value : \"\"}\n          onValueChange={(next: unknown) => {\n            if (typeof next === \"string\") context.setAnswer(item.id, next);\n          }}\n          disabled={context.busy}\n          className={classes}\n          {...(props as Omit<ComponentProps<typeof RadioGroup>, \"value\" | \"onValueChange\">)}\n        >\n          {children}\n        </RadioGroup>\n      ) : (\n        // biome-ignore lint/a11y/useSemanticElements: the item is already the fieldset; this groups its checkboxes.\n        <div\n          role=\"group\"\n          data-slot=\"agent-question-options\"\n          aria-labelledby={item.legendId}\n          aria-keyshortcuts={shortcuts || undefined}\n          className={classes}\n          {...props}\n        >\n          {children}\n        </div>\n      )}\n    </OptionsContext.Provider>\n  );\n}\n\nconst optionVariants = cva(\n  \"group/option grid min-w-0 rounded-[10px] bg-background [transition:background-color_120ms_ease-out,box-shadow_120ms_ease-out] hover:shadow-[inset_0_0_0_1px_var(--border-strong)] has-data-[state=checked]:bg-muted has-data-checked:bg-muted has-data-[state=checked]:shadow-[inset_0_0_0_1px_var(--foreground)] has-data-checked:shadow-[inset_0_0_0_1px_var(--foreground)] has-focus-visible:outline-2 has-focus-visible:-outline-offset-2 has-focus-visible:outline-ring has-focus-visible:outline-solid has-disabled:opacity-55 motion-reduce:transition-none\",\n  {\n    variants: {\n      variant: {\n        card: \"\",\n        inline: \"\",\n        compact: \"\",\n      },\n    },\n  },\n);\n\nconst controlClass =\n  \"mt-px size-4 shrink-0 border-0 bg-transparent shadow-[inset_0_0_0_1.5px_var(--subtle-foreground)] transition-[background-color,box-shadow] duration-[140ms] ease-out-quint focus-visible:ring-0 data-[state=checked]:bg-primary data-checked:bg-primary data-[state=checked]:shadow-none data-checked:shadow-none disabled:opacity-100 motion-reduce:transition-none dark:bg-transparent dark:data-[state=checked]:bg-primary dark:data-checked:bg-primary\";\n\ntype OptionBaseProps = {\n  value: string;\n  label: string;\n  description?: ReactNode;\n  recommended?: boolean;\n  disabled?: boolean;\n  /** Rendered under the label when the option is selected (used by \"Something else\"). */\n  reveal?: (selected: boolean) => ReactNode;\n};\n\nexport type AgentQuestionOptionProps = Omit<ComponentProps<\"div\">, \"children\"> & {\n  value: string;\n  label: string;\n  description?: ReactNode;\n  recommended?: boolean;\n  disabled?: boolean;\n};\n\nfunction OptionRow({\n  value,\n  label,\n  description,\n  recommended = false,\n  disabled = false,\n  reveal,\n  className,\n  ...props\n}: OptionBaseProps & Omit<ComponentProps<\"div\">, \"children\">) {\n  const context = useAgentQuestion(\"AgentQuestionOption\");\n  const item = useItem(\"AgentQuestionOption\");\n  const descriptionId = useId();\n  const rowRef = useRef<HTMLDivElement | null>(null);\n  const [index, setIndex] = useState(-1);\n  const compact = context.variant === \"compact\";\n\n  useLayoutEffect(() => {\n    let map = context.labels.current.get(item.id);\n    if (!map) {\n      map = new Map();\n      context.labels.current.set(item.id, map);\n    }\n    map.set(value, label);\n    const row = rowRef.current;\n    const parent = row?.parentElement;\n    if (row && parent) {\n      setIndex(\n        Array.from(parent.children)\n          .filter((child) => child.getAttribute(\"data-slot\") === \"agent-question-option\")\n          .indexOf(row),\n      );\n    }\n  }, [context.labels, item.id, value, label]);\n\n  const answer = context.answers[item.id];\n  const selected = Array.isArray(answer) ? answer.includes(value) : answer === value;\n  const shortcut = index >= 0 && index < 9 ? String(index + 1) : null;\n  const showDescription = !compact && description && !(reveal && selected);\n  const name = recommended ? `${label}, recommended` : label;\n\n  const control =\n    item.type === \"single\" ? (\n      <RadioGroupItem\n        value={value}\n        disabled={disabled || context.busy}\n        aria-label={name}\n        aria-describedby={showDescription ? descriptionId : undefined}\n        data-index={index}\n        className={cn(\n          controlClass,\n          \"[&_svg]:size-1.5 [&_svg]:fill-primary-foreground [&_svg]:stroke-0 [&_svg]:animate-in [&_svg]:zoom-in-0 [&_svg]:duration-150 [&_svg]:ease-out-quint motion-reduce:[&_svg]:animate-none\",\n        )}\n      />\n    ) : (\n      <Checkbox\n        checked={selected}\n        disabled={disabled || context.busy}\n        onCheckedChange={(checked: unknown) => {\n          const current = Array.isArray(answer) ? answer : [];\n          const nextValue = checked\n            ? [...current.filter((entry) => entry !== value), value]\n            : current.filter((entry) => entry !== value);\n          context.setAnswer(item.id, nextValue);\n        }}\n        aria-label={name}\n        aria-describedby={showDescription ? descriptionId : undefined}\n        data-index={index}\n        className={cn(\n          controlClass,\n          \"rounded-[5px] text-primary-foreground [&_svg]:size-3 [&_svg]:stroke-[3] [&_svg]:animate-in [&_svg]:zoom-in-50 [&_svg]:duration-150 [&_svg]:ease-out-quint motion-reduce:[&_svg]:animate-none\",\n        )}\n      />\n    );\n\n  return (\n    <div\n      ref={rowRef}\n      data-slot=\"agent-question-option\"\n      data-selected={selected || undefined}\n      className={cn(optionVariants({ variant: context.variant }), className)}\n      {...props}\n    >\n      {/* biome-ignore lint/a11y/noLabelWithoutControl: the control is the first labelable child. */}\n      <label\n        className={cn(\n          \"grid cursor-pointer grid-cols-[16px_minmax(0,1fr)_auto] items-start gap-2.5\",\n          compact ? \"py-[7px] pr-2 pl-2.5\" : \"py-2.5 pr-2.5 pl-3\",\n          (disabled || context.busy) && \"cursor-not-allowed\",\n        )}\n      >\n        {control}\n        <span className=\"grid min-w-0 gap-0.5\">\n          <span\n            className={cn(\n              \"flex min-w-0 flex-wrap items-center gap-1.5 font-medium text-foreground\",\n              compact ? \"text-[12.5px]/[18px]\" : \"text-[13px]/[18px]\",\n            )}\n          >\n            {label}\n            {recommended ? (\n              <span\n                aria-hidden=\"true\"\n                className=\"inline-flex h-[18px] items-center rounded-full bg-muted px-1.5 text-[10.5px] font-medium text-muted-foreground group-has-data-checked/option:bg-background group-has-data-[state=checked]/option:bg-background\"\n              >\n                Recommended\n              </span>\n            ) : null}\n          </span>\n          {showDescription ? (\n            <span id={descriptionId} className=\"text-[12px]/4 text-subtle-foreground\">\n              {description}\n            </span>\n          ) : null}\n        </span>\n        {shortcut ? (\n          <span aria-hidden=\"true\" className=\"flex\">\n            <kbd className=\"min-w-5 rounded-md bg-muted px-1.5 text-center font-mono text-[11px]/[18px] font-medium text-subtle-foreground group-has-data-checked/option:bg-background group-has-data-[state=checked]/option:bg-background\">\n              {shortcut}\n            </kbd>\n          </span>\n        ) : (\n          <span />\n        )}\n      </label>\n      {reveal ? reveal(selected) : null}\n    </div>\n  );\n}\n\n/** One choice: a 16px control, a label, an optional Recommended chip and description, and its number key. */\nexport function AgentQuestionOption(props: AgentQuestionOptionProps) {\n  return <OptionRow {...props} />;\n}\n\nexport type AgentQuestionOtherProps = Omit<\n  ComponentProps<\"div\">,\n  \"children\" | \"onChange\" | \"defaultValue\"\n> & {\n  label?: string;\n  description?: ReactNode;\n  placeholder?: string;\n};\n\n/** \"Something else\": selecting it reveals an inline text input and moves focus into it. */\nexport function AgentQuestionOther({\n  label = \"Something else\",\n  description = \"Describe what you want instead\",\n  placeholder = \"Type your answer\",\n  ...props\n}: AgentQuestionOtherProps) {\n  const context = useAgentQuestion(\"AgentQuestionOther\");\n  const item = useItem(\"AgentQuestionOther\");\n  const inputRef = useRef<HTMLInputElement | null>(null);\n  const answer = context.answers[item.id];\n  const selected = Array.isArray(answer) ? answer.includes(\"other\") : answer === \"other\";\n  const wasSelected = useRef(selected);\n  useEffect(() => {\n    if (selected && !wasSelected.current && item.current) inputRef.current?.focus();\n    wasSelected.current = selected;\n  }, [selected, item.current]);\n  const text = context.answers[agentQuestionOtherKey(item.id)];\n  const compact = context.variant === \"compact\";\n  return (\n    <OptionRow\n      value=\"other\"\n      label={label}\n      description={description}\n      reveal={(open) => (\n        <div\n          className={cn(\n            \"grid [transition:grid-template-rows_300ms_cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none\",\n            open ? \"grid-rows-[1fr]\" : \"grid-rows-[0fr]\",\n          )}\n          inert={!open || undefined}\n        >\n          <div className=\"min-h-0 overflow-hidden\">\n            <div className={compact ? \"px-2.5 pb-2\" : \"pr-2.5 pb-2.5 pl-[38px]\"}>\n              <Input\n                ref={inputRef}\n                aria-label={label}\n                placeholder={placeholder}\n                value={typeof text === \"string\" ? text : \"\"}\n                disabled={context.busy}\n                onChange={(event) => context.setOther(item.id, event.target.value)}\n                className=\"h-[30px] rounded-lg border-border-strong bg-card px-2.5 text-[12.5px] shadow-none focus-visible:ring-[3px] focus-visible:ring-primary/24 md:text-[12.5px] dark:bg-card\"\n              />\n            </div>\n          </div>\n        </div>\n      )}\n      {...props}\n    />\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Footer\n * -----------------------------------------------------------------------------------------------*/\n\nconst pillClass =\n  \"rounded-full border-0 px-3.5 font-medium [transition:background-color_120ms_ease-out,scale_140ms_cubic-bezier(0.23,1,0.32,1)] focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid active:scale-[0.97] motion-reduce:transition-none motion-reduce:active:scale-100\";\n\n/** Key hints or the selected count, then Back, Skip, and Continue (Send answers on the last step). */\nexport function AgentQuestionFooter({ className, children, ...props }: ComponentProps<\"div\">) {\n  const context = useAgentQuestion(\"AgentQuestionFooter\");\n  const item = context.items[context.step];\n  const compact = context.variant === \"compact\";\n  const last = context.step >= context.items.length - 1;\n  const answered = context.isAnswered(item);\n  const canContinue = item ? !item.required || answered : false;\n  const value = item ? context.answers[item.id] : undefined;\n  const selectedCount = Array.isArray(value) ? value.length : 0;\n  const optionCount = item\n    ? Math.min(9, item.element.querySelectorAll(\"[data-slot=agent-question-option]\").length)\n    : 0;\n  const size = compact ? \"h-7 text-[12px]\" : \"h-8 text-[13px]\";\n  return (\n    <div\n      data-slot=\"agent-question-footer\"\n      className={cn(\"grid min-w-0 gap-2.5\", className)}\n      {...props}\n    >\n      {context.error ? (\n        <p role=\"alert\" className=\"m-0 text-[12px]/4 text-destructive-ink\">\n          {context.error}\n        </p>\n      ) : null}\n      <div className=\"flex min-w-0 items-center justify-between gap-2\">\n        {children ??\n          (compact ? (\n            <span />\n          ) : item?.type === \"multiple\" && selectedCount > 0 ? (\n            <span className=\"text-[11.5px]/4 text-subtle-foreground tabular-nums\">\n              {selectedCount} selected\n            </span>\n          ) : optionCount > 0 ? (\n            <span\n              aria-hidden=\"true\"\n              className=\"flex min-w-0 items-center gap-1 text-[11.5px]/4 text-subtle-foreground max-[380px]:hidden\"\n            >\n              <Kbd>1</Kbd>–<Kbd>{optionCount}</Kbd> choose · <Kbd>↵</Kbd> continue\n            </span>\n          ) : (\n            <span />\n          ))}\n        <span className=\"flex shrink-0 items-center gap-2\">\n          {context.step > 0 ? (\n            <Button\n              type=\"button\"\n              variant=\"secondary\"\n              disabled={context.busy}\n              onClick={context.back}\n              className={cn(pillClass, size, \"bg-secondary hover:bg-accent\")}\n            >\n              Back\n            </Button>\n          ) : null}\n          {item && !item.required ? (\n            <Button\n              type=\"button\"\n              variant=\"secondary\"\n              disabled={context.busy}\n              onClick={context.skip}\n              className={cn(pillClass, size, \"bg-secondary hover:bg-accent\")}\n            >\n              Skip\n            </Button>\n          ) : null}\n          <Button\n            ref={context.primaryRef}\n            type=\"button\"\n            data-slot=\"agent-question-continue\"\n            disabled={context.busy || !canContinue}\n            onClick={context.next}\n            className={cn(pillClass, size, \"disabled:opacity-50\")}\n          >\n            {context.busy ? (\n              <span className=\"shimmer-text\">Sending…</span>\n            ) : last ? (\n              \"Send answers\"\n            ) : (\n              \"Continue\"\n            )}\n          </Button>\n        </span>\n      </div>\n    </div>\n  );\n}\n\nfunction Kbd({ children }: { children: ReactNode }) {\n  return (\n    <kbd className=\"min-w-[18px] rounded-[5px] bg-muted px-1 text-center font-mono text-[10.5px]/4 font-medium text-subtle-foreground\">\n      {children}\n    </kbd>\n  );\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Summary\n * -----------------------------------------------------------------------------------------------*/\n\nexport type AgentQuestionSummaryProps = Omit<ComponentProps<\"div\">, \"children\"> & {\n  /** When the answers went out, already formatted, such as \"14:02\". */\n  time?: string;\n  defaultOpen?: boolean;\n};\n\n/**\n * The record left in the transcript: a check tile, \"Answered 3 questions\", the time, and a\n * disclosure with each question and its answer. Rendered by the root once answered or dismissed.\n */\nexport function AgentQuestionSummary({\n  time,\n  defaultOpen = false,\n  className,\n  ...props\n}: AgentQuestionSummaryProps) {\n  const context = useAgentQuestion(\"AgentQuestionSummary\");\n  const headingRef = useContext(SummaryHeadingContext);\n  const [open, setOpen] = useState(defaultOpen);\n  const listId = useId();\n  if (context.status !== \"answered\" && context.status !== \"dismissed\") return null;\n  const dismissed = context.status === \"dismissed\";\n  const total = context.items.length;\n  const chrome =\n    context.variant === \"inline\"\n      ? \"rounded-[14px] border border-border bg-card\"\n      : context.variant === \"compact\"\n        ? \"rounded-[12px] border border-border bg-card\"\n        : \"rounded-[14px] border border-border bg-card\";\n  const heading = dismissed\n    ? \"Question dismissed\"\n    : `Answered ${total} ${total === 1 ? \"question\" : \"questions\"}`;\n\n  const answerText = (id: string) => {\n    const value = context.answers[id];\n    if (!hasValue(value)) return null;\n    const labels = context.labels.current.get(id);\n    const other = context.answers[agentQuestionOtherKey(id)];\n    return (Array.isArray(value) ? value : [value as string])\n      .map((entry) =>\n        entry === \"other\" && typeof other === \"string\" && other\n          ? other\n          : (labels?.get(entry) ?? entry),\n      )\n      .join(\" · \");\n  };\n\n  return (\n    <Collapsible open={open} onOpenChange={setOpen}>\n      <div data-slot=\"agent-question-summary\" className={cn(chrome, className)} {...props}>\n        <div\n          className={cn(\n            \"flex min-w-0 items-center gap-2.5\",\n            context.variant === \"compact\" ? \"px-3 py-2.5\" : \"px-3 py-2.5\",\n          )}\n        >\n          <span\n            aria-hidden=\"true\"\n            className={cn(\n              \"grid size-6 shrink-0 place-items-center rounded-full\",\n              dismissed\n                ? \"bg-muted text-muted-foreground\"\n                : \"animate-in bg-success/14 text-success-ink duration-200 ease-[cubic-bezier(0.16,1,0.3,1)] zoom-in-50 motion-reduce:animate-none\",\n            )}\n          >\n            {dismissed ? (\n              <X className=\"size-3.5\" strokeWidth={2} />\n            ) : (\n              <Check className=\"size-3.5\" strokeWidth={2.2} />\n            )}\n          </span>\n          <div className=\"grid min-w-0 flex-1\">\n            <h3\n              ref={headingRef}\n              tabIndex={-1}\n              className={cn(\n                \"m-0 truncate text-[13px]/[18px] font-medium outline-none\",\n                dismissed ? \"text-muted-foreground\" : \"text-foreground\",\n              )}\n            >\n              {heading}\n            </h3>\n            {!dismissed ? (\n              <p className=\"m-0 text-[12px]/4 text-subtle-foreground tabular-nums\">\n                Sent to the agent{time ? ` · ${time}` : \"\"}\n              </p>\n            ) : null}\n          </div>\n          {!dismissed && total > 0 ? (\n            <CollapsibleTrigger asChild>\n              <Button\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon\"\n                aria-label={open ? \"Hide answers\" : \"Show answers\"}\n                aria-controls={listId}\n                className=\"size-7 rounded-lg text-muted-foreground [transition:background-color_120ms_ease-out,color_120ms_ease-out,scale_140ms_cubic-bezier(0.23,1,0.32,1)] hover:bg-muted hover:text-foreground active:scale-[0.94] motion-reduce:transition-none motion-reduce:active:scale-100\"\n              >\n                <ChevronDown\n                  className={cn(\n                    \"size-4 [transition:rotate_200ms_cubic-bezier(0.23,1,0.32,1)] motion-reduce:transition-none\",\n                    open && \"rotate-180\",\n                  )}\n                  strokeWidth={1.75}\n                  aria-hidden=\"true\"\n                />\n              </Button>\n            </CollapsibleTrigger>\n          ) : null}\n        </div>\n        {!dismissed ? (\n          <CollapsibleContent id={listId}>\n            <ul className=\"m-0 grid list-none gap-2 border-t border-border px-3.5 pt-3 pb-3.5\">\n              {context.items.map((item) => {\n                const answer = answerText(item.id);\n                return (\n                  <li key={item.id} className=\"grid gap-0.5 text-[12px]/4\">\n                    <span className=\"text-subtle-foreground\">\n                      {context.titles.current.get(item.id) ?? item.id}\n                    </span>\n                    <span\n                      className={cn(\n                        answer ? \"font-medium text-foreground\" : \"text-subtle-foreground\",\n                      )}\n                    >\n                      {answer ?? \"Skipped\"}\n                    </span>\n                  </li>\n                );\n              })}\n            </ul>\n          </CollapsibleContent>\n        ) : null}\n      </div>\n    </Collapsible>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/agent-question.tsx"
    }
  ],
  "categories": [
    "ai",
    "agents"
  ],
  "type": "registry:ui"
}