{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "questionnaire-story",
  "title": "Questionnaire Story",
  "author": "Lloyd Richards <lloyd.d.richards@gmail.com>",
  "description": "Interactive Storybook stories demonstrating multi-step questionnaire flows, validation, and navigation",
  "registryDependencies": [
    "questionnaire"
  ],
  "files": [
    {
      "path": "registry/ui/questionnaire-story/questionnaire-radix.stories.tsx",
      "content": "import type { Meta, StoryObj } from \"@storybook/nextjs-vite\";\nimport { expect, fn, userEvent } from \"storybook/test\";\n\nimport {\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} from \"@/components/ui/questionnaire\";\n\ntype QuestionnaireAnswers = {\n  approach: FormDataEntryValue | null;\n  checks: FormDataEntryValue[];\n  timing: FormDataEntryValue | null;\n};\n\ntype QuestionnaireStoryArgs = React.ComponentProps<typeof Questionnaire> & {\n  onAnswersSubmit: (answers: QuestionnaireAnswers) => void;\n};\n\nconst items = [\n  {\n    name: \"approach\",\n    required: true,\n    choices: [\n      { value: \"smallest\" },\n      { value: \"incremental\" },\n      { value: \"replace\" },\n    ],\n  },\n  {\n    name: \"checks\",\n    choices: [{ value: \"tests\" }, { value: \"types\" }, { value: \"visual\" }],\n  },\n  {\n    name: \"timing\",\n    required: true,\n    choices: [{ value: \"now\" }, { value: \"cycle\" }, { value: \"backlog\" }],\n  },\n] as const;\n\nconst submissionItems = [items[2]] as const;\n\n/**\n * A multi-step questionnaire with fixed, multiple-choice, and freeform answers.\n */\nconst meta = {\n  title: \"ui/radix/Questionnaire\",\n  component: Questionnaire,\n  tags: [\"autodocs\"],\n  argTypes: {\n    className: { control: false },\n    items: { control: false },\n    onAnswersSubmit: { table: { disable: true } },\n    onSubmit: { control: false },\n    shortcuts: {\n      control: \"inline-radio\",\n      options: [\"letters\", \"numbers\"],\n    },\n  },\n  parameters: { layout: \"centered\" },\n  decorators: (Story) => (\n    <div className=\"w-full min-w-sm max-w-lg\">\n      <Story />\n    </div>\n  ),\n  args: { onAnswersSubmit: fn(), shortcuts: \"letters\" },\n  render: ({ onAnswersSubmit, shortcuts = \"letters\", ...props }) => {\n    function handleSubmit(event: React.FormEvent<HTMLFormElement>) {\n      event.preventDefault();\n      const formData = new FormData(event.currentTarget);\n\n      onAnswersSubmit({\n        approach: formData.get(\"approach\"),\n        checks: formData.getAll(\"checks\"),\n        timing: formData.get(\"timing\"),\n      });\n    }\n\n    return (\n      <Questionnaire\n        {...props}\n        className=\"w-full max-w-lg\"\n        items={items}\n        shortcuts={shortcuts}\n        onSubmit={handleSubmit}\n      >\n        <QuestionnaireProgress />\n        <QuestionnaireItem name=\"approach\" required>\n          <QuestionnaireTitle>\n            How should we approach this change?\n          </QuestionnaireTitle>\n          <QuestionnaireDescription>\n            Choose a strategy or describe a more specific approach.\n          </QuestionnaireDescription>\n          <QuestionnaireChoices>\n            <QuestionnaireChoice value=\"smallest\">\n              <span className=\"font-medium\">Make the smallest safe change</span>\n              <QuestionnaireChoiceDescription>\n                Keep the implementation focused on the requested behavior.\n              </QuestionnaireChoiceDescription>\n            </QuestionnaireChoice>\n            <QuestionnaireChoice value=\"incremental\">\n              Refactor one module at a time\n            </QuestionnaireChoice>\n            <QuestionnaireChoice value=\"replace\">\n              Replace the implementation completely\n            </QuestionnaireChoice>\n            <QuestionnaireInput\n              aria-label=\"Another approach\"\n              placeholder=\"Describe another approach…\"\n            />\n          </QuestionnaireChoices>\n          <QuestionnaireError />\n        </QuestionnaireItem>\n        <QuestionnaireItem name=\"checks\" multiple>\n          <QuestionnaireTitle>\n            What should be checked before handoff?\n          </QuestionnaireTitle>\n          <QuestionnaireDescription>\n            Select every relevant check, or skip this optional question.\n          </QuestionnaireDescription>\n          <QuestionnaireChoices>\n            <QuestionnaireChoice value=\"tests\">Tests</QuestionnaireChoice>\n            <QuestionnaireChoice value=\"types\">\n              Type checking\n            </QuestionnaireChoice>\n            <QuestionnaireChoice value=\"visual\">\n              Visual review\n            </QuestionnaireChoice>\n          </QuestionnaireChoices>\n          <QuestionnaireError />\n        </QuestionnaireItem>\n        <QuestionnaireItem name=\"timing\" required>\n          <QuestionnaireTitle>When should work begin?</QuestionnaireTitle>\n          <QuestionnaireDescription>\n            Choose when the implementation should start.\n          </QuestionnaireDescription>\n          <QuestionnaireChoices>\n            <QuestionnaireChoice value=\"now\">Start now</QuestionnaireChoice>\n            <QuestionnaireChoice value=\"cycle\">\n              Next development cycle\n            </QuestionnaireChoice>\n            <QuestionnaireChoice value=\"backlog\">\n              Add it to the backlog\n            </QuestionnaireChoice>\n          </QuestionnaireChoices>\n          <QuestionnaireError />\n        </QuestionnaireItem>\n        <QuestionnaireActions>\n          <QuestionnairePrevious />\n          <QuestionnaireSkip />\n          <QuestionnaireNext />\n          <QuestionnaireSubmit>Save plan</QuestionnaireSubmit>\n        </QuestionnaireActions>\n      </Questionnaire>\n    );\n  },\n} satisfies Meta<QuestionnaireStoryArgs>;\n\nexport default meta;\ntype Story = StoryObj<typeof meta>;\n\n/** Combines required and optional questions in one navigable flow. */\nexport const Default: Story = {};\n\n/** Starts on the optional step to demonstrate multiple selection and skipping. */\nexport const MultipleChoice: Story = {\n  args: {\n    defaultItem: \"checks\",\n  },\n};\n\n/** Demonstrates required validation and submission in a focused one-step flow. */\nexport const Submission: Story = {\n  render: ({ onAnswersSubmit, shortcuts = \"letters\" }) => {\n    function handleSubmit(event: React.FormEvent<HTMLFormElement>) {\n      event.preventDefault();\n      const formData = new FormData(event.currentTarget);\n\n      onAnswersSubmit({\n        approach: null,\n        checks: [],\n        timing: formData.get(\"timing\"),\n      });\n    }\n\n    return (\n      <Questionnaire\n        className=\"w-full max-w-lg\"\n        items={submissionItems}\n        shortcuts={shortcuts}\n        onSubmit={handleSubmit}\n      >\n        <QuestionnaireProgress />\n        <QuestionnaireItem name=\"timing\" required>\n          <QuestionnaireTitle>When should work begin?</QuestionnaireTitle>\n          <QuestionnaireDescription>\n            Choose when the implementation should start.\n          </QuestionnaireDescription>\n          <QuestionnaireChoices>\n            <QuestionnaireChoice value=\"now\">Start now</QuestionnaireChoice>\n            <QuestionnaireChoice value=\"cycle\">\n              Next development cycle\n            </QuestionnaireChoice>\n            <QuestionnaireChoice value=\"backlog\">\n              Add it to the backlog\n            </QuestionnaireChoice>\n          </QuestionnaireChoices>\n          <QuestionnaireError />\n        </QuestionnaireItem>\n        <QuestionnaireActions>\n          <QuestionnaireSubmit>Save plan</QuestionnaireSubmit>\n        </QuestionnaireActions>\n      </Questionnaire>\n    );\n  },\n};\n\n/** Verifies validation, navigation, answer preservation, and submission. */\nexport const ShouldValidateNavigateAndSubmit: Story = {\n  name: \"when completing a questionnaire, should preserve and submit answers\",\n  tags: [\"!dev\", \"!autodocs\"],\n  play: async ({ args, canvas, step }) => {\n    const nextButton = canvas.getByRole(\"button\", { name: \"Next\" });\n\n    await step(\"validate the required first question\", async () => {\n      await userEvent.click(nextButton);\n      await expect(canvas.getByRole(\"alert\")).toHaveTextContent(\n        \"Choose an answer to continue.\",\n      );\n    });\n\n    await step(\"enter a freeform answer and continue with Enter\", async () => {\n      const input = canvas.getByRole(\"textbox\", { name: \"Another approach\" });\n      await userEvent.type(input, \"Keep the public API stable\");\n      await userEvent.keyboard(\"{Enter}\");\n      await expect(\n        canvas.getByRole(\"group\", {\n          name: \"What should be checked before handoff?\",\n        }),\n      ).toBeVisible();\n    });\n\n    await step(\"select multiple answers\", async () => {\n      await userEvent.click(canvas.getByRole(\"checkbox\", { name: \"Tests\" }));\n      await userEvent.click(\n        canvas.getByRole(\"checkbox\", { name: \"Visual review\" }),\n      );\n    });\n\n    await step(\"navigate back and preserve every answer\", async () => {\n      await userEvent.click(canvas.getByRole(\"button\", { name: \"Previous\" }));\n      await expect(\n        canvas.getByRole(\"textbox\", { name: \"Another approach\" }),\n      ).toHaveValue(\"Keep the public API stable\");\n      await userEvent.click(nextButton);\n      await expect(\n        canvas.getByRole(\"checkbox\", { name: \"Tests\" }),\n      ).toBeChecked();\n      await expect(\n        canvas.getByRole(\"checkbox\", { name: \"Visual review\" }),\n      ).toBeChecked();\n      await userEvent.click(nextButton);\n    });\n\n    await step(\"submit the collected answers\", async () => {\n      await userEvent.click(canvas.getByRole(\"radio\", { name: \"Start now\" }));\n      await userEvent.click(canvas.getByRole(\"button\", { name: \"Save plan\" }));\n      await expect(args.onAnswersSubmit).toHaveBeenCalledWith({\n        approach: \"Keep the public API stable\",\n        checks: [\"tests\", \"visual\"],\n        timing: \"now\",\n      });\n    });\n  },\n};\n\n/** Verifies keyboard selection and explicitly skipping an optional question. */\nexport const ShouldSkipOptionalQuestion: Story = {\n  name: \"when skipping an optional question, should submit no answer for it\",\n  tags: [\"!dev\", \"!autodocs\"],\n  play: async ({ args, canvas, step }) => {\n    await step(\"answer the required first question\", async () => {\n      const firstQuestion = canvas.getByRole(\"group\", {\n        name: \"How should we approach this change?\",\n      });\n      const firstAnswer = canvas.getByRole(\"radio\", {\n        name: /Make the smallest safe change/,\n      });\n\n      firstQuestion.focus();\n      await userEvent.keyboard(\"a\");\n      await expect(firstAnswer).toBeChecked();\n      await userEvent.click(canvas.getByRole(\"button\", { name: \"Next\" }));\n    });\n\n    await step(\"skip the optional question\", async () => {\n      await userEvent.click(canvas.getByRole(\"button\", { name: \"Skip\" }));\n      await expect(\n        canvas.getByRole(\"group\", { name: \"When should work begin?\" }),\n      ).toBeVisible();\n    });\n\n    await step(\"submit without an optional answer\", async () => {\n      await userEvent.click(\n        canvas.getByRole(\"radio\", { name: \"Next development cycle\" }),\n      );\n      await userEvent.click(canvas.getByRole(\"button\", { name: \"Save plan\" }));\n      await expect(args.onAnswersSubmit).toHaveBeenCalledWith({\n        approach: \"smallest\",\n        checks: [],\n        timing: \"cycle\",\n      });\n    });\n  },\n};\n",
      "type": "registry:component",
      "target": "@ui/questionnaire.stories.tsx"
    }
  ],
  "categories": [
    "ui",
    "storybook",
    "questionnaire",
    "form"
  ],
  "type": "registry:component"
}