{"$schema":"https://ui.shadcn.com/schema/registry-item.json","dependencies":[],"description":"AI-powered question component.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport type {\n  ChangeEvent,\n  ComponentProps,\n  FormEvent,\n  HTMLAttributes,\n  MouseEvent,\n  ReactNode,\n} from \"react\";\n\nimport { Button } from \"@/registry/default/ui/button\";\nimport { Textarea } from \"@/registry/default/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n} from \"react\";\n\nexport interface QuestionValue {\n  selectedValues: readonly string[];\n  text: string;\n}\n\nexport interface QuestionResponse {\n  selectedValues: readonly string[];\n  text?: string;\n}\n\ntype SelectionMode = \"multiple\" | \"single\";\n\ninterface QuestionContextValue {\n  disabled: boolean;\n  selectedValues: readonly string[];\n  selectionMode: SelectionMode;\n  setText: (text: string) => void;\n  text: string;\n  toggleValue: (value: string) => void;\n}\n\nconst QuestionContext = createContext<QuestionContextValue | null>(null);\n\nconst useQuestion = () => {\n  const context = useContext(QuestionContext);\n\n  if (!context) {\n    throw new Error(\"Question components must be used within Question\");\n  }\n\n  return context;\n};\n\nexport type QuestionProps = Omit<\n  ComponentProps<\"form\">,\n  \"defaultValue\" | \"onSubmit\" | \"value\"\n> & {\n  defaultValue?: QuestionValue;\n  disabled?: boolean;\n  onSubmit?: (\n    response: QuestionResponse,\n    event: FormEvent<HTMLFormElement>\n  ) => void | Promise<void>;\n  onValueChange?: (value: QuestionValue) => void;\n  selectionMode?: SelectionMode;\n  value?: QuestionValue;\n};\n\nconst EMPTY_VALUE: QuestionValue = { selectedValues: [], text: \"\" };\n\nconst getSelectedValues = (\n  currentValues: readonly string[],\n  optionValue: string,\n  selectionMode: SelectionMode\n): readonly string[] => {\n  const isSelected = currentValues.includes(optionValue);\n\n  if (selectionMode === \"single\") {\n    return isSelected ? [] : [optionValue];\n  }\n\n  if (isSelected) {\n    return currentValues.filter((item) => item !== optionValue);\n  }\n\n  return [...currentValues, optionValue];\n};\n\nexport const Question = ({\n  children,\n  className,\n  defaultValue = EMPTY_VALUE,\n  disabled = false,\n  onSubmit,\n  onValueChange,\n  selectionMode = \"single\",\n  value: controlledValue,\n  ...props\n}: QuestionProps) => {\n  const [internalValue, setInternalValue] = useState(defaultValue);\n  const value = controlledValue ?? internalValue;\n\n  const setValue = useCallback(\n    (nextValue: QuestionValue) => {\n      if (controlledValue === undefined) {\n        setInternalValue(nextValue);\n      }\n      onValueChange?.(nextValue);\n    },\n    [controlledValue, onValueChange]\n  );\n\n  const setText = useCallback(\n    (text: string) => {\n      setValue({ ...value, text });\n    },\n    [setValue, value]\n  );\n\n  const toggleValue = useCallback(\n    (optionValue: string) => {\n      const selectedValues = getSelectedValues(\n        value.selectedValues,\n        optionValue,\n        selectionMode\n      );\n      setValue({ ...value, selectedValues });\n    },\n    [selectionMode, setValue, value]\n  );\n\n  const contextValue = useMemo(\n    () => ({\n      disabled,\n      selectedValues: value.selectedValues,\n      selectionMode,\n      setText,\n      text: value.text,\n      toggleValue,\n    }),\n    [disabled, selectionMode, setText, toggleValue, value]\n  );\n\n  const handleSubmit = useCallback(\n    async (event: FormEvent<HTMLFormElement>) => {\n      event.preventDefault();\n      if (disabled) {\n        return;\n      }\n\n      const text = value.text.trim();\n      if (value.selectedValues.length === 0 && text.length === 0) {\n        return;\n      }\n\n      await onSubmit?.(\n        {\n          selectedValues: value.selectedValues,\n          text: text.length > 0 ? text : undefined,\n        },\n        event\n      );\n    },\n    [disabled, onSubmit, value]\n  );\n\n  return (\n    <QuestionContext.Provider value={contextValue}>\n      <form\n        className={cn(\n          \"space-y-4 rounded-lg border bg-background p-4\",\n          className\n        )}\n        onSubmit={handleSubmit}\n        {...props}\n      >\n        {children}\n      </form>\n    </QuestionContext.Provider>\n  );\n};\n\nexport type QuestionPromptProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const QuestionPrompt = ({\n  className,\n  ...props\n}: QuestionPromptProps) => (\n  <p className={cn(\"font-medium text-sm\", className)} {...props} />\n);\n\nexport type QuestionDescriptionProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const QuestionDescription = ({\n  className,\n  ...props\n}: QuestionDescriptionProps) => (\n  <p className={cn(\"text-muted-foreground text-sm\", className)} {...props} />\n);\n\nexport type QuestionOptionsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const QuestionOptions = ({\n  className,\n  ...props\n}: QuestionOptionsProps) => {\n  const { selectionMode } = useQuestion();\n\n  return (\n    <div\n      className={cn(\"flex flex-wrap gap-2\", className)}\n      role={selectionMode === \"single\" ? \"radiogroup\" : \"group\"}\n      {...props}\n    />\n  );\n};\n\nexport type QuestionOptionProps = Omit<\n  ComponentProps<typeof Button>,\n  \"value\"\n> & {\n  value: string;\n};\n\nexport const QuestionOption = ({\n  children,\n  className,\n  disabled,\n  onClick,\n  value,\n  variant,\n  ...props\n}: QuestionOptionProps) => {\n  const question = useQuestion();\n  const isSelected = question.selectedValues.includes(value);\n  const role = question.selectionMode === \"single\" ? \"radio\" : \"checkbox\";\n  const handleClick = useCallback(\n    (event: MouseEvent<HTMLButtonElement>) => {\n      question.toggleValue(value);\n      onClick?.(event);\n    },\n    [onClick, question, value]\n  );\n\n  return (\n    <Button\n      aria-checked={isSelected}\n      className={cn(\"h-auto whitespace-normal\", className)}\n      disabled={question.disabled || disabled}\n      onClick={handleClick}\n      role={role}\n      type=\"button\"\n      variant={variant ?? (isSelected ? \"default\" : \"outline\")}\n      {...props}\n    >\n      {children ?? value}\n    </Button>\n  );\n};\n\nexport type QuestionInputProps = Omit<\n  ComponentProps<typeof Textarea>,\n  \"defaultValue\" | \"value\"\n>;\n\nexport const QuestionInput = ({\n  className,\n  disabled,\n  onChange,\n  ...props\n}: QuestionInputProps) => {\n  const question = useQuestion();\n  const handleChange = useCallback(\n    (event: ChangeEvent<HTMLTextAreaElement>) => {\n      question.setText(event.currentTarget.value);\n      onChange?.(event);\n    },\n    [onChange, question]\n  );\n\n  return (\n    <Textarea\n      className={cn(\"min-h-20\", className)}\n      disabled={question.disabled || disabled}\n      onChange={handleChange}\n      value={question.text}\n      {...props}\n    />\n  );\n};\n\nexport type QuestionActionsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const QuestionActions = ({\n  className,\n  ...props\n}: QuestionActionsProps) => (\n  <div\n    className={cn(\"flex items-center justify-end gap-2\", className)}\n    {...props}\n  />\n);\n\nexport type QuestionSubmitProps = ComponentProps<typeof Button> & {\n  children?: ReactNode;\n};\n\nexport const QuestionSubmit = ({\n  children = \"Submit\",\n  disabled,\n  ...props\n}: QuestionSubmitProps) => {\n  const question = useQuestion();\n  const hasResponse =\n    question.selectedValues.length > 0 || question.text.trim().length > 0;\n\n  return (\n    <Button\n      disabled={question.disabled || disabled || !hasResponse}\n      type=\"submit\"\n      {...props}\n    >\n      {children}\n    </Button>\n  );\n};\n","path":"registry/default/ai-elements/question.tsx","target":"components/ai-elements/question.tsx","type":"registry:component"}],"name":"question","registryDependencies":["button","textarea"],"title":"Question","type":"registry:component"}