{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "questionnaire",
  "title": "Questionnaire",
  "description": "Multi-step form, one question at a time: choices, free text, skip, validation, progress.",
  "dependencies": [
    "lucide-react",
    "radix-ui"
  ],
  "registryDependencies": [
    "@ikas/button",
    "@ikas/utils"
  ],
  "files": [
    {
      "path": "registry/ui/questionnaire.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CheckIcon } from \"lucide-react\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants, type ButtonColor, type ButtonProps, type ButtonVariant } from \"@/components/ui/button\"\n\n/*\n * Same parts and props as the shadcn/ui Questionnaire, without @shadcn/react: that\n * package only declares its entry points through `exports`, which TypeScript's\n * \"moduleResolution\": \"node\" (used by many ikas apps) cannot read.\n */\n\ntype QuestionnaireStatus = \"unanswered\" | \"answered\" | \"skipped\"\ntype QuestionnaireShortcuts = \"letters\" | \"numbers\"\n\ntype QuestionnaireItemDefinition = {\n  name: string\n  required?: boolean\n  disabled?: boolean\n  choices?: readonly { value: string; disabled?: boolean }[]\n}\n\n/** Text input answers share one key in the item's selection. */\nconst INPUT_ANSWER = \"\\u0000input\"\n\nconst shortcutKeys = (mode: QuestionnaireShortcuts | undefined) =>\n  mode === \"letters\"\n    ? Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i))\n    : mode === \"numbers\"\n      ? Array.from({ length: 9 }, (_, i) => String(i + 1))\n      : []\n\nconst isTextField = (el: EventTarget | null) =>\n  el instanceof HTMLTextAreaElement ||\n  el instanceof HTMLSelectElement ||\n  (el instanceof HTMLInputElement && ![\"button\", \"checkbox\", \"radio\", \"reset\", \"submit\"].includes(el.type)) ||\n  (el instanceof HTMLElement && el.isContentEditable)\n\ntype ItemHandle = {\n  name: string\n  element: HTMLFieldSetElement\n  disabled: boolean\n  required: boolean\n  /** Marks the item as attempted; false when it still needs an answer. */\n  validate: () => boolean\n  focus: () => void\n  focusInvalid: () => void\n  skip: () => void\n  reset: () => void\n  answerByShortcut: (key: string) => HTMLInputElement | null\n}\n\ntype RootContextValue = {\n  activeName: string | null\n  activeRequired: boolean\n  activeStatus: QuestionnaireStatus | null\n  current: number\n  total: number\n  first: boolean\n  last: boolean\n  shortcuts: QuestionnaireShortcuts | undefined\n  definitions: Map<string, QuestionnaireItemDefinition> | null\n  registerItem: (handle: ItemHandle) => () => void\n  reportStatus: (name: string, status: QuestionnaireStatus) => void\n  goNext: () => void\n  goPrevious: () => void\n  skipCurrent: () => void\n}\n\nconst RootContext = React.createContext<RootContextValue | null>(null)\n\nfunction useRoot(part: string) {\n  const context = React.useContext(RootContext)\n  if (!context) throw new Error(`${part} must be used inside <Questionnaire>.`)\n  return context\n}\n\ntype QuestionnaireProps = Omit<React.ComponentProps<\"form\">, \"defaultValue\"> & {\n  /** Item order and settings. Without it, rendered items are used in DOM order. */\n  items?: readonly QuestionnaireItemDefinition[]\n  /** Item shown first when uncontrolled. */\n  defaultItem?: string\n  /** Active item name, for controlled navigation. */\n  item?: string\n  /** Called with the name of the item that becomes active. */\n  onItemChange?: (item: string) => void\n  /** Letter or number keys that pick a choice in the active item. */\n  shortcuts?: QuestionnaireShortcuts\n}\n\n/**\n * Multi-step form, one question at a time: single or multiple choice, free text,\n * skippable items, validation and progress. The host owns closing, saving and branching.\n * Read the answers in onSubmit with `new FormData(event.currentTarget)`.\n */\nfunction Questionnaire({\n  className,\n  items,\n  defaultItem,\n  item,\n  onItemChange,\n  shortcuts,\n  onSubmit,\n  onReset,\n  onKeyDown,\n  children,\n  ...props\n}: QuestionnaireProps) {\n  const [handles, setHandles] = React.useState<ReadonlyMap<string, ItemHandle>>(() => new Map())\n  const [statuses, setStatuses] = React.useState<Record<string, QuestionnaireStatus>>({})\n  const [internal, setInternal] = React.useState<string | null>(defaultItem ?? null)\n  const pendingFocus = React.useRef<{ name: string; invalid: boolean } | null>(null)\n\n  const definitions = React.useMemo(() => (items ? new Map(items.map((d) => [d.name, d])) : null), [items])\n\n  const order = React.useMemo(() => {\n    // With `items` the order is known before any item mounts, so the server render\n    // already shows the first question.\n    if (items) return items.filter((d) => !d.disabled && !handles.get(d.name)?.disabled).map((d) => d.name)\n    return [...handles.values()]\n      .filter((h) => !h.disabled)\n      .sort((a, b) => (a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1))\n      .map((h) => h.name)\n  }, [items, handles])\n\n  const requested = item !== undefined ? item : internal\n  const activeName = requested && order.includes(requested) ? requested : (order[0] ?? null)\n  const index = activeName ? order.indexOf(activeName) : -1\n  const total = order.length\n  const active = activeName ? handles.get(activeName) : undefined\n\n  const go = React.useCallback(\n    (name: string, invalid = false) => {\n      pendingFocus.current = { name, invalid }\n      if (item === undefined) setInternal(name)\n      onItemChange?.(name)\n      // Same item (e.g. failed validation) does not re-render; focus right away.\n      if (name === activeName) {\n        const handle = handles.get(name)\n        if (invalid) handle?.focusInvalid()\n        else handle?.focus()\n        pendingFocus.current = null\n      }\n    },\n    [item, onItemChange, activeName, handles]\n  )\n\n  React.useLayoutEffect(() => {\n    const pending = pendingFocus.current\n    if (!pending || pending.name !== activeName) return\n    const handle = handles.get(pending.name)\n    if (pending.invalid) handle?.focusInvalid()\n    else handle?.focus()\n    pendingFocus.current = null\n  }, [activeName, handles])\n\n  const formRef = React.useRef<HTMLFormElement>(null)\n\n  const goNext = React.useCallback(() => {\n    if (!active || index < 0) return\n    if (!active.validate()) return go(active.name, true)\n    if (index === total - 1) formRef.current?.requestSubmit()\n    else go(order[index + 1])\n  }, [active, index, total, order, go])\n\n  const goPrevious = React.useCallback(() => {\n    if (index > 0) go(order[index - 1])\n  }, [index, order, go])\n\n  const skipCurrent = React.useCallback(() => {\n    if (!active || active.required) return\n    active.skip()\n    if (index === total - 1) queueMicrotask(() => formRef.current?.requestSubmit())\n    else go(order[index + 1])\n  }, [active, index, total, order, go])\n\n  const registerItem = React.useCallback((handle: ItemHandle) => {\n    setHandles((map) => new Map(map).set(handle.name, handle))\n    return () =>\n      setHandles((map) => {\n        if (map.get(handle.name) !== handle) return map\n        const next = new Map(map)\n        next.delete(handle.name)\n        return next\n      })\n  }, [])\n\n  const reportStatus = React.useCallback((name: string, status: QuestionnaireStatus) => {\n    setStatuses((s) => (s[name] === status ? s : { ...s, [name]: status }))\n  }, [])\n\n  const handleSubmit: NonNullable<React.ComponentProps<\"form\">[\"onSubmit\"]> = (event) => {\n    const invalid = order.map((name) => handles.get(name)).find((h) => h && !h.validate())\n    if (invalid) {\n      event.preventDefault()\n      go(invalid.name, true)\n      return\n    }\n    onSubmit?.(event)\n  }\n\n  function handleReset(event: React.FormEvent<HTMLFormElement>) {\n    onReset?.(event)\n    if (event.defaultPrevented) return\n    for (const handle of handles.values()) handle.reset()\n    const start = defaultItem && order.includes(defaultItem) ? defaultItem : order[0]\n    if (start) go(start)\n  }\n\n  function handleKeyDown(event: React.KeyboardEvent<HTMLFormElement>) {\n    onKeyDown?.(event)\n    if (event.defaultPrevented || event.nativeEvent.isComposing || !active) return\n    const target = event.target\n    if (event.key === \"Enter\" && (event.metaKey || event.ctrlKey)) {\n      event.preventDefault()\n      if (!event.repeat) goNext()\n      return\n    }\n    if (event.metaKey || event.ctrlKey || event.altKey) return\n    const inAnswer = target instanceof HTMLInputElement && active.element.contains(target)\n    if (event.key === \"Enter\" && inAnswer) {\n      event.preventDefault()\n      if (!event.repeat) goNext()\n      return\n    }\n    if ((event.key === \"ArrowUp\" || event.key === \"ArrowDown\") && inAnswer && target.type === \"checkbox\") {\n      const answers = [...active.element.querySelectorAll<HTMLInputElement>(\"input:not(:disabled)\")]\n      const next = answers[(answers.indexOf(target) + (event.key === \"ArrowDown\" ? 1 : -1) + answers.length) % answers.length]\n      event.preventDefault()\n      next?.focus()\n      return\n    }\n    const radio = target instanceof HTMLInputElement && target.type === \"radio\"\n    if ((event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") && !isTextField(target) && !radio) {\n      event.preventDefault()\n      if (event.repeat) return\n      if (event.key === \"ArrowLeft\") goPrevious()\n      else if (statuses[active.name] !== \"unanswered\") goNext()\n      return\n    }\n    if (!shortcuts || isTextField(target)) return\n    const answer = active.answerByShortcut(shortcuts === \"letters\" ? event.key.toUpperCase() : event.key)\n    if (answer) {\n      event.preventDefault()\n      answer.focus()\n      answer.click()\n    }\n  }\n\n  const context: RootContextValue = {\n    activeName,\n    activeRequired: active?.required ?? false,\n    activeStatus: activeName ? (statuses[activeName] ?? \"unanswered\") : null,\n    current: index + 1,\n    total,\n    first: index === 0,\n    last: total > 0 && index === total - 1,\n    shortcuts,\n    definitions,\n    registerItem,\n    reportStatus,\n    goNext,\n    goPrevious,\n    skipCurrent,\n  }\n\n  return (\n    <RootContext.Provider value={context}>\n      <form\n        ref={formRef}\n        data-slot=\"questionnaire\"\n        data-shortcuts={shortcuts}\n        noValidate\n        onSubmit={handleSubmit}\n        onReset={handleReset}\n        onKeyDown={handleKeyDown}\n        className={cn(\"flex w-full min-w-0 flex-col gap-6\", className)}\n        {...props}\n      >\n        {children}\n      </form>\n    </RootContext.Provider>\n  )\n}\n\ntype QuestionnaireProgressProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** Text beside the bar. Receives the 1-based current step and the total. */\n  label?: (current: number, total: number) => React.ReactNode\n}\n\n/** Step counter with a thin bar that fills as the questionnaire advances. */\nfunction QuestionnaireProgress({\n  className,\n  label = (current, total) => `Soru ${current} / ${total}`,\n  ...props\n}: QuestionnaireProgressProps) {\n  const { current, total } = useRoot(\"QuestionnaireProgress\")\n  return (\n    <div\n      data-slot=\"questionnaire-progress\"\n      role=\"progressbar\"\n      aria-label=\"Anket ilerlemesi\"\n      aria-live=\"polite\"\n      aria-valuemin={total ? 1 : undefined}\n      aria-valuemax={total || undefined}\n      aria-valuenow={total ? current : undefined}\n      aria-valuetext={total ? `Soru ${current} / ${total}` : undefined}\n      className={cn(\"flex items-center gap-3 text-xs font-medium text-muted-foreground tabular-nums\", className)}\n      {...props}\n    >\n      <span className=\"relative h-1 flex-1 overflow-hidden rounded-full bg-muted\">\n        <span\n          className=\"absolute inset-y-0 left-0 rounded-full bg-primary transition-[width] duration-300 ease-(--ease-out) motion-reduce:transition-none\"\n          style={{ width: total ? `${(current / total) * 100}%` : \"0%\" }}\n        />\n      </span>\n      <span className=\"shrink-0\">{total ? label(current, total) : null}</span>\n    </div>\n  )\n}\n\ntype ItemContextValue = {\n  name: string\n  multiple: boolean\n  disabled: boolean\n  invalid: boolean\n  skipped: boolean\n  selected: string[]\n  select: (key: string, on: boolean) => void\n  addDefault: (key: string) => void\n  registerChoice: (value: string, element: HTMLElement) => () => void\n  shortcutFor: (value: string) => string | null\n  registerDescription: (id: string) => () => void\n  errorId: string\n}\n\nconst ItemContext = React.createContext<ItemContextValue | null>(null)\n\nfunction useItem(part: string) {\n  const context = React.useContext(ItemContext)\n  if (!context) throw new Error(`${part} must be used inside <QuestionnaireItem>.`)\n  return context\n}\n\ntype QuestionnaireItemProps = Omit<React.ComponentProps<\"fieldset\">, \"name\"> & {\n  /** Field name of the answers in FormData. Unique per questionnaire. */\n  name: string\n  /** Blocks Next until answered and hides Skip. */\n  required?: boolean\n  /** Checkboxes instead of radios. */\n  multiple?: boolean\n  /** Forces the error state, e.g. from your own schema validation. */\n  invalid?: boolean\n  /** Called when the item becomes unanswered, answered or skipped. */\n  onStatusChange?: (status: QuestionnaireStatus) => void\n}\n\n/** One question. Renders a fieldset; only the active one is visible. */\nfunction QuestionnaireItem({\n  className,\n  name,\n  required = false,\n  multiple = false,\n  disabled = false,\n  invalid: invalidProp = false,\n  onStatusChange,\n  children,\n  \"aria-describedby\": ariaDescribedBy,\n  ...props\n}: QuestionnaireItemProps) {\n  const root = useRoot(\"QuestionnaireItem\")\n  const ref = React.useRef<HTMLFieldSetElement>(null)\n  const defaults = React.useRef<string[]>([])\n  const [selected, setSelected] = React.useState<string[]>([])\n  const [skipped, setSkipped] = React.useState(false)\n  const [attempted, setAttempted] = React.useState(false)\n  const [choices, setChoices] = React.useState<{ value: string; element: HTMLElement }[]>([])\n  const [descriptions, setDescriptions] = React.useState<string[]>([])\n  const errorId = React.useId()\n\n  const answered = selected.length > 0\n  const status: QuestionnaireStatus = skipped ? \"skipped\" : answered ? \"answered\" : \"unanswered\"\n  const invalid = !disabled && (invalidProp || (attempted && !answered && !skipped))\n  const active = root.activeName === name && !disabled\n\n  const select = React.useCallback(\n    (key: string, on: boolean) => {\n      setSkipped(false)\n      setSelected((current) => {\n        if (!on) return current.filter((k) => k !== key)\n        if (!multiple) return [key]\n        return current.includes(key) ? current : [...current, key]\n      })\n    },\n    [multiple]\n  )\n\n  const addDefault = React.useCallback(\n    (key: string) => {\n      if (defaults.current.includes(key)) return\n      defaults.current = multiple ? [...defaults.current, key] : [key]\n      setSelected((current) => (multiple ? (current.includes(key) ? current : [...current, key]) : current.length ? current : [key]))\n    },\n    [multiple]\n  )\n\n  const registerChoice = React.useCallback((value: string, element: HTMLElement) => {\n    const entry = { value, element }\n    setChoices((list) =>\n      [...list.filter((c) => c.value !== value), entry].sort((a, b) =>\n        a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1\n      )\n    )\n    return () => setChoices((list) => list.filter((c) => c !== entry))\n  }, [])\n\n  const registerDescription = React.useCallback((id: string) => {\n    setDescriptions((ids) => [...ids, id])\n    return () => setDescriptions((ids) => ids.filter((i) => i !== id))\n  }, [])\n\n  const shortcuts = React.useMemo(() => {\n    const keys = shortcutKeys(root.shortcuts)\n    const definition = root.definitions?.get(name)\n    const values = definition?.choices\n      ? definition.choices.filter((c) => !c.disabled).map((c) => c.value)\n      : choices.filter((c) => !c.element.hasAttribute(\"data-disabled\")).map((c) => c.value)\n    return new Map(values.slice(0, keys.length).map((value, i) => [value, keys[i]]))\n  }, [root.shortcuts, root.definitions, name, choices])\n\n  const { registerItem, reportStatus } = root\n  React.useLayoutEffect(() => {\n    const element = ref.current\n    if (!element) return\n    const inputs = () => [...element.querySelectorAll<HTMLInputElement>(\"input:not(:disabled), textarea:not(:disabled)\")]\n    return registerItem({\n      name,\n      element,\n      disabled,\n      required,\n      validate: () => {\n        setAttempted(true)\n        return !invalidProp && (answered || skipped)\n      },\n      focus: () => element.focus(),\n      focusInvalid: () => (inputs().find((i) => i.checked) ?? inputs()[0] ?? element).focus(),\n      skip: () => {\n        setSelected([])\n        setSkipped(true)\n        setAttempted(false)\n      },\n      reset: () => {\n        setSelected(defaults.current)\n        setSkipped(false)\n        setAttempted(false)\n      },\n      answerByShortcut: (key) => {\n        const value = [...shortcuts.entries()].find(([, k]) => k === key)?.[0]\n        const choice = choices.find((c) => c.value === value)\n        return choice?.element.querySelector<HTMLInputElement>(\"input:not(:disabled)\") ?? null\n      },\n    })\n  }, [registerItem, name, disabled, required, shortcuts, choices, answered, skipped, invalidProp])\n\n  const lastStatus = React.useRef(status)\n  React.useEffect(() => {\n    reportStatus(name, status)\n    if (lastStatus.current === status) return\n    lastStatus.current = status\n    onStatusChange?.(status)\n  }, [reportStatus, name, status, onStatusChange])\n\n  const describedBy = [...descriptions, invalid ? errorId : null, ariaDescribedBy].filter(Boolean).join(\" \") || undefined\n\n  const context: ItemContextValue = {\n    name,\n    multiple,\n    disabled,\n    invalid,\n    skipped,\n    selected,\n    select,\n    addDefault,\n    registerChoice,\n    shortcutFor: (value) => shortcuts.get(value) ?? null,\n    registerDescription,\n    errorId,\n  }\n\n  return (\n    <ItemContext.Provider value={context}>\n      <fieldset\n        ref={ref}\n        data-slot=\"questionnaire-item\"\n        data-active={active ? \"\" : undefined}\n        data-status={status}\n        data-invalid={invalid ? \"\" : undefined}\n        data-disabled={disabled ? \"\" : undefined}\n        aria-invalid={invalid || undefined}\n        aria-describedby={describedBy}\n        disabled={disabled}\n        hidden={!active}\n        inert={!active}\n        tabIndex={-1}\n        className={cn(\n          \"flex min-w-0 flex-col gap-4 border-0 p-0 outline-none data-active:animate-in data-active:fade-in-0 data-active:slide-in-from-right-2 data-active:duration-300 motion-reduce:data-active:animate-none\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </fieldset>\n    </ItemContext.Provider>\n  )\n}\n\ntype QuestionnaireTitleProps = React.ComponentProps<\"legend\"> & {\n  /** Render the child element instead of a legend, Radix style. */\n  asChild?: boolean\n}\n\nfunction QuestionnaireTitle({ className, asChild = false, ...props }: QuestionnaireTitleProps) {\n  useItem(\"QuestionnaireTitle\")\n  const Comp = asChild ? Slot.Root : \"legend\"\n  return (\n    <Comp\n      data-slot=\"questionnaire-title\"\n      className={cn(\n        \"font-heading mb-4 text-base font-semibold tracking-[-0.01em] text-pretty [&:has(~[data-slot=questionnaire-description])]:mb-1\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction QuestionnaireDescription({ className, id: idProp, ...props }: React.ComponentProps<\"p\">) {\n  const { registerDescription } = useItem(\"QuestionnaireDescription\")\n  const fallback = React.useId()\n  const id = idProp ?? fallback\n  React.useLayoutEffect(() => registerDescription(id), [registerDescription, id])\n  return (\n    <p id={id} data-slot=\"questionnaire-description\" className={cn(\"text-sm text-pretty text-muted-foreground\", className)} {...props} />\n  )\n}\n\nfunction QuestionnaireChoices({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { shortcuts } = useRoot(\"QuestionnaireChoices\")\n  return (\n    <div\n      data-slot=\"questionnaire-choices\"\n      data-shortcuts={shortcuts}\n      className={cn(\"group/questionnaire-choices grid min-w-0 gap-2\", className)}\n      {...props}\n    />\n  )\n}\n\ntype QuestionnaireChoiceProps = Omit<React.ComponentProps<\"label\">, \"onChange\"> & {\n  /** Submitted value of this answer. */\n  value: string\n  /** Selected when the questionnaire starts or resets. */\n  defaultChecked?: boolean\n  disabled?: boolean\n  /** Change event of the native radio or checkbox. */\n  onChange?: React.ChangeEventHandler<HTMLInputElement>\n}\n\n/** One fixed answer: a raised card with a native radio or checkbox inside. */\nfunction QuestionnaireChoice({\n  children,\n  className,\n  value,\n  defaultChecked = false,\n  disabled: disabledProp = false,\n  onChange,\n  ...props\n}: QuestionnaireChoiceProps) {\n  const item = useItem(\"QuestionnaireChoice\")\n  const ref = React.useRef<HTMLLabelElement>(null)\n  const checked = item.selected.includes(value)\n  const disabled = item.disabled || disabledProp\n  const type = item.multiple ? \"checkbox\" : \"radio\"\n  const shortcut = item.shortcutFor(value)\n\n  const { registerChoice, addDefault } = item\n  React.useLayoutEffect(() => (ref.current ? registerChoice(value, ref.current) : undefined), [registerChoice, value])\n  React.useLayoutEffect(() => {\n    if (defaultChecked) addDefault(value)\n  }, [addDefault, defaultChecked, value])\n\n  return (\n    <label\n      ref={ref}\n      data-slot=\"questionnaire-choice\"\n      data-type={type}\n      data-checked={checked ? \"\" : undefined}\n      data-unchecked={checked ? undefined : \"\"}\n      data-invalid={item.invalid ? \"\" : undefined}\n      data-disabled={disabled ? \"\" : undefined}\n      data-shortcut={shortcut ?? undefined}\n      className={cn(\n        \"group/questionnaire-choice relative flex min-h-11 cursor-pointer items-start gap-3 rounded-lg bg-card px-3.5 py-3 text-start text-sm shadow-raised transition-[box-shadow,background-color] outline-none select-none hover:shadow-raised-hover has-[>input:focus-visible]:ring-3 has-[>input:focus-visible]:ring-ring/30\",\n        \"data-checked:bg-primary-subtle data-checked:shadow-[inset_0_0_0_1.5px_var(--primary)] data-invalid:shadow-[inset_0_0_0_1px_var(--destructive)]\",\n        \"data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      <input\n        data-slot=\"questionnaire-choice-input\"\n        type={type}\n        name={item.skipped ? undefined : item.name}\n        value={value}\n        checked={checked}\n        disabled={disabled}\n        aria-invalid={item.invalid || undefined}\n        aria-keyshortcuts={shortcut ?? undefined}\n        onChange={(event) => {\n          onChange?.(event)\n          if (!event.defaultPrevented) item.select(value, event.target.checked)\n        }}\n        className=\"absolute inset-0 z-10 size-full cursor-pointer opacity-0\"\n      />\n      <span\n        aria-hidden=\"true\"\n        data-slot=\"questionnaire-choice-indicator\"\n        className=\"pointer-events-none relative mt-px flex size-4 shrink-0 items-center justify-center rounded-[5px] border border-border-strong bg-card shadow-inset transition-colors group-data-[type=radio]/questionnaire-choice:rounded-full group-data-checked/questionnaire-choice:border-primary group-data-checked/questionnaire-choice:bg-primary group-data-checked/questionnaire-choice:text-primary-foreground dark:bg-input/30 dark:group-data-checked/questionnaire-choice:bg-primary\"\n      >\n        <span className=\"hidden size-1.5 rounded-full bg-primary-foreground group-data-[type=radio]/questionnaire-choice:group-data-checked/questionnaire-choice:block\" />\n        <CheckIcon className=\"hidden size-3 group-data-[type=checkbox]/questionnaire-choice:group-data-checked/questionnaire-choice:block\" strokeWidth={3} />\n      </span>\n      <span data-slot=\"questionnaire-choice-label\" className=\"flex min-w-0 flex-1 flex-col gap-0.5 leading-snug font-medium\">\n        {children}\n      </span>\n      {shortcut && (\n        <span\n          aria-hidden\n          data-slot=\"questionnaire-choice-shortcut\"\n          className=\"pointer-events-none ms-auto inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-md bg-muted px-1 font-mono text-[10px] leading-none font-medium text-muted-foreground\"\n        >\n          {shortcut}\n        </span>\n      )}\n    </label>\n  )\n}\n\nfunction QuestionnaireChoiceDescription({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span data-slot=\"questionnaire-choice-description\" className={cn(\"font-normal text-muted-foreground\", className)} {...props} />\n  )\n}\n\n/** Free text answer, styled like Input. Give it a label or aria-label. */\nfunction QuestionnaireInput({ className, onChange, defaultValue, ...props }: React.ComponentProps<\"input\">) {\n  const item = useItem(\"QuestionnaireInput\")\n  const filled = item.selected.includes(INPUT_ANSWER)\n  const { addDefault } = item\n  React.useLayoutEffect(() => {\n    if (String(defaultValue ?? \"\").trim()) addDefault(INPUT_ANSWER)\n  }, [addDefault, defaultValue])\n\n  return (\n    <input\n      data-slot=\"questionnaire-input\"\n      type=\"text\"\n      // Only an answer while it is the selection: typing replaces a single choice.\n      name={filled && !item.skipped ? item.name : undefined}\n      defaultValue={defaultValue}\n      disabled={item.disabled || props.disabled}\n      aria-invalid={item.invalid || undefined}\n      data-filled={filled ? \"\" : undefined}\n      data-empty={filled ? undefined : \"\"}\n      onChange={(event) => {\n        onChange?.(event)\n        if (!event.defaultPrevented) item.select(INPUT_ANSWER, event.target.value.trim().length > 0)\n      }}\n      className={cn(\n        \"h-9 w-full min-w-0 rounded-lg border border-input bg-card px-3 py-1 text-base shadow-inset transition-[border-color,box-shadow] outline-none placeholder:text-muted-foreground hover:border-border-strong focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/20 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/** Error of the active item, shown after Next is pressed without an answer. */\nfunction QuestionnaireError({ className, children = \"Devam etmek için bir yanıt seçin.\", ...props }: React.ComponentProps<\"p\">) {\n  const item = useItem(\"QuestionnaireError\")\n  return (\n    <p\n      id={item.errorId}\n      data-slot=\"questionnaire-error\"\n      role={item.invalid ? \"alert\" : undefined}\n      hidden={!item.invalid}\n      className={cn(\"text-[13px] text-destructive\", className)}\n      {...props}\n    >\n      {children}\n    </p>\n  )\n}\n\n/** Row for the navigation buttons: Back on the left, Skip and Next or Submit on the right. */\nfunction QuestionnaireActions({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"questionnaire-actions\"\n      className={cn(\"grid w-full grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-2 border-t pt-4\", className)}\n      {...props}\n    />\n  )\n}\n\ntype QuestionnaireButtonProps = React.ComponentProps<\"button\"> & {\n  size?: ButtonProps[\"size\"]\n  /** Button variant. Next and Submit are solid, Skip outline, Back ghost. */\n  variant?: ButtonVariant\n  color?: ButtonColor\n  /** Render the child element instead of a button, Radix style. */\n  asChild?: boolean\n}\n\nfunction NavButton({\n  slot,\n  visible,\n  column,\n  className,\n  size = \"default\",\n  variant,\n  color,\n  asChild = false,\n  disabled,\n  type = \"button\",\n  tabIndex,\n  ...props\n}: QuestionnaireButtonProps & { slot: string; visible: boolean; column: string }) {\n  const Comp = asChild ? Slot.Root : \"button\"\n  return (\n    <Comp\n      data-slot={slot}\n      data-visible={visible ? \"\" : undefined}\n      data-hidden={visible ? undefined : \"\"}\n      type={asChild ? undefined : type}\n      hidden={!visible}\n      inert={!visible}\n      aria-hidden={!visible || undefined}\n      tabIndex={visible ? tabIndex : -1}\n      disabled={disabled}\n      className={cn(buttonVariants({ size, variant, color }), column, className)}\n      {...props}\n    />\n  )\n}\n\nfunction QuestionnairePrevious({ children = \"Geri\", variant = \"ghost\", onClick, ...props }: QuestionnaireButtonProps) {\n  const root = useRoot(\"QuestionnairePrevious\")\n  return (\n    <NavButton\n      slot=\"questionnaire-previous\"\n      visible={root.total > 1 && !root.first}\n      column=\"col-start-1 row-start-1 justify-self-start\"\n      variant={variant}\n      onClick={(event) => {\n        onClick?.(event)\n        if (!event.defaultPrevented) root.goPrevious()\n      }}\n      {...props}\n    >\n      {children}\n    </NavButton>\n  )\n}\n\nfunction QuestionnaireSkip({ children = \"Atla\", variant = \"outline\", onClick, ...props }: QuestionnaireButtonProps) {\n  const root = useRoot(\"QuestionnaireSkip\")\n  return (\n    <NavButton\n      slot=\"questionnaire-skip\"\n      visible={root.activeName !== null && !root.activeRequired}\n      column=\"col-start-2 row-start-1 justify-self-end\"\n      variant={variant}\n      onClick={(event) => {\n        onClick?.(event)\n        if (!event.defaultPrevented) root.skipCurrent()\n      }}\n      {...props}\n    >\n      {children}\n    </NavButton>\n  )\n}\n\nfunction QuestionnaireNext({ children = \"İleri\", variant = \"solid\", onClick, ...props }: QuestionnaireButtonProps) {\n  const root = useRoot(\"QuestionnaireNext\")\n  return (\n    <NavButton\n      slot=\"questionnaire-next\"\n      visible={root.total > 1 && !root.last}\n      column=\"col-start-3 row-start-1 justify-self-end\"\n      variant={variant}\n      aria-keyshortcuts=\"Enter\"\n      onClick={(event) => {\n        onClick?.(event)\n        if (!event.defaultPrevented) root.goNext()\n      }}\n      {...props}\n    >\n      {children}\n    </NavButton>\n  )\n}\n\nfunction QuestionnaireSubmit({ children = \"Gönder\", variant = \"solid\", ...props }: QuestionnaireButtonProps) {\n  const root = useRoot(\"QuestionnaireSubmit\")\n  return (\n    <NavButton\n      slot=\"questionnaire-submit\"\n      visible={root.last}\n      column=\"col-start-3 row-start-1 justify-self-end\"\n      variant={variant}\n      type=\"submit\"\n      aria-keyshortcuts=\"Enter\"\n      {...props}\n    >\n      {children}\n    </NavButton>\n  )\n}\n\nexport {\n  Questionnaire,\n  QuestionnaireActions,\n  QuestionnaireChoice,\n  QuestionnaireChoiceDescription,\n  QuestionnaireChoices,\n  QuestionnaireDescription,\n  QuestionnaireError,\n  QuestionnaireInput,\n  QuestionnaireItem,\n  QuestionnaireNext,\n  QuestionnairePrevious,\n  QuestionnaireProgress,\n  QuestionnaireSkip,\n  QuestionnaireSubmit,\n  QuestionnaireTitle,\n  type QuestionnaireButtonProps,\n  type QuestionnaireChoiceProps,\n  type QuestionnaireItemDefinition,\n  type QuestionnaireItemProps,\n  type QuestionnaireProgressProps,\n  type QuestionnaireProps,\n  type QuestionnaireStatus,\n  type QuestionnaireTitleProps,\n}\n",
      "type": "registry:ui"
    }
  ],
  "docs": "https://builders.ikas.com/tr/docs/app-development/ui-kit/components/questionnaire",
  "categories": [
    "primitives",
    "forms"
  ],
  "type": "registry:ui"
}