{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"c-questionnaire-2","type":"registry:block","title":"Multi-select answers","description":"A checkbox question that accepts several answers, with a live count and a chip summary.","dependencies":[],"registryDependencies":["@reui/badge","button","card","questionnaire"],"files":[{"path":"c-questionnaire-2.tsx","type":"registry:block","content":"\"use client\"\n\nimport { useState, type FormEvent } from \"react\"\nimport { Badge } from \"@/components/reui/badge\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\"\nimport {\n  Questionnaire,\n  QuestionnaireActions,\n  QuestionnaireChoice,\n  QuestionnaireChoiceDescription,\n  QuestionnaireChoices,\n  QuestionnaireError,\n  QuestionnaireItem,\n  QuestionnaireNext,\n  QuestionnairePrevious,\n  QuestionnaireProgress,\n  QuestionnaireSubmit,\n  QuestionnaireTitle,\n} from \"@/components/ui/questionnaire\"\n\ntype Option = { value: string; label: string; hint?: string }\n\nconst WORKFLOWS: Option[] = [\n  {\n    value: \"digest\",\n    label: \"Daily digest\",\n    hint: \"One summary of yesterday, sent at 08:00\",\n  },\n  {\n    value: \"releases\",\n    label: \"Release notes\",\n    hint: \"Posted to the channel whenever a version ships\",\n  },\n  {\n    value: \"incidents\",\n    label: \"Incident alerts\",\n    hint: \"Raised the moment a service starts degrading\",\n  },\n  {\n    value: \"rollup\",\n    label: \"Weekly roll-up\",\n    hint: \"Team activity, every Friday afternoon\",\n  },\n  {\n    value: \"billing\",\n    label: \"Billing reminders\",\n    hint: \"Three working days before an invoice falls due\",\n  },\n  {\n    value: \"access\",\n    label: \"Access reviews\",\n    hint: \"A quarterly check on who can see what\",\n  },\n]\n\nconst REVIEWERS: Option[] = [\n  { value: \"author\", label: \"Whoever wrote the change\" },\n  { value: \"lead\", label: \"A named team lead\" },\n  { value: \"rota\", label: \"The reviewer on rota that week\" },\n]\n\nconst CADENCES: Option[] = [\n  { value: \"daily\", label: \"Every weekday morning\" },\n  { value: \"weekly\", label: \"Once a week\" },\n  { value: \"monthly\", label: \"Once a month\" },\n]\n\n// Every entry spells out its choice values because the letter shortcuts are\n// handed out from this list. An items entry without choices leaves each\n// shortcut badge unrendered even while shortcuts is set on the root.\n//\n// required on a multiple item means at least one box, not all of them. The\n// primitive keeps the attribute off the individual inputs in that case, since\n// a native required checkbox would insist on that one box in particular.\nconst ITEMS = [\n  {\n    choices: WORKFLOWS.map((option) => ({ value: option.value })),\n    name: \"workflows\",\n    required: true,\n  },\n  {\n    choices: REVIEWERS.map((option) => ({ value: option.value })),\n    name: \"reviewer\",\n    required: true,\n  },\n  {\n    choices: CADENCES.map((option) => ({ value: option.value })),\n    name: \"cadence\",\n    required: true,\n  },\n]\n\ntype Result = { workflows: string[]; reviewer: string; cadence: string }\n\nfunction labelOf(options: Option[], value: FormDataEntryValue | null) {\n  return (\n    options.find((option) => option.value === value)?.label ?? \"Not answered\"\n  )\n}\n\nexport function Pattern() {\n  // The running count lives in host state because item status only reports\n  // answered, unanswered or skipped. Nothing the primitive exposes counts how\n  // many answers are currently ticked.\n  const [selected, setSelected] = useState<string[]>([])\n  const [result, setResult] = useState<Result | null>(null)\n\n  // getAll is the counterpart to get. A multiple item writes one FormData\n  // entry per checked value, so the answer stays a real string array instead\n  // of arriving as one joined string that would have to be split back apart.\n  //\n  // Submit re-checks every enabled item, not only the last one, and jumps back\n  // to the first that fails, so this handler is only reached once all three\n  // questions hold an answer.\n  function handleSubmit(event: FormEvent<HTMLFormElement>) {\n    event.preventDefault()\n\n    const formData = new FormData(event.currentTarget)\n    const values = formData.getAll(\"workflows\")\n\n    setResult({\n      workflows: WORKFLOWS.filter((workflow) =>\n        values.includes(workflow.value)\n      ).map((workflow) => workflow.label),\n      reviewer: labelOf(REVIEWERS, formData.get(\"reviewer\")),\n      cadence: labelOf(CADENCES, formData.get(\"cadence\")),\n    })\n  }\n\n  // The flow unmounts while the summary is on screen, so the running count has\n  // to be cleared alongside it. Leaving it would reopen an empty question\n  // under a badge still claiming three workflows were picked.\n  function startOver() {\n    setResult(null)\n    setSelected([])\n  }\n\n  if (result) {\n    return (\n      <Card className=\"mx-auto w-full max-w-md\">\n        <CardHeader>\n          <CardTitle>Notifications switched on</CardTitle>\n          <CardDescription>\n            {result.workflows.length} of {WORKFLOWS.length} workflows will start\n            sending from tomorrow.\n          </CardDescription>\n        </CardHeader>\n        <CardContent>\n          <div className=\"grid gap-3\">\n            <div className=\"flex flex-wrap gap-1.5\">\n              {result.workflows.map((workflow) => (\n                <Badge key={workflow} variant=\"secondary\">\n                  {workflow}\n                </Badge>\n              ))}\n            </div>\n            <p className=\"text-muted-foreground text-sm\">\n              Reviewed by {result.reviewer.toLowerCase()}, delivered{\" \"}\n              {result.cadence.toLowerCase()}.\n            </p>\n          </div>\n        </CardContent>\n        <CardFooter>\n          <Button size=\"sm\" variant=\"outline\" onClick={startOver}>\n            Change answers\n          </Button>\n        </CardFooter>\n      </Card>\n    )\n  }\n\n  return (\n    // No Card on this one: the root already lays its children out in a column\n    // with a gap, and a bare flow reads as a different surface to its\n    // neighbours.\n    //\n    // The progress line below carries role=\"progressbar\" and announces itself\n    // politely on each step, so the badge beside it needs no live region.\n    <Questionnaire\n      className=\"mx-auto w-full max-w-md\"\n      defaultItem=\"workflows\"\n      items={ITEMS}\n      shortcuts=\"letters\"\n      onSubmit={handleSubmit}\n    >\n      <div className=\"flex items-center justify-between gap-2\">\n        <QuestionnaireProgress />\n        <Badge variant=\"primary-light\">\n          {selected.length} of {WORKFLOWS.length} selected\n        </Badge>\n      </div>\n\n      <QuestionnaireItem name=\"workflows\" multiple required>\n        <QuestionnaireTitle>\n          Which workflows should we switch on?\n        </QuestionnaireTitle>\n        <QuestionnaireChoices>\n          {WORKFLOWS.map((workflow) => (\n            // multiple on the item is the entire switch for checkboxes. The\n            // indicator swaps on the choice's own data-type attribute, so this\n            // markup is byte for byte what a single-answer question uses.\n            <QuestionnaireChoice\n              key={workflow.value}\n              value={workflow.value}\n              onChange={(event) =>\n                setSelected((current) =>\n                  event.target.checked\n                    ? [...current, workflow.value]\n                    : current.filter((value) => value !== workflow.value)\n                )\n              }\n            >\n              {workflow.label}\n              <QuestionnaireChoiceDescription>\n                {workflow.hint}\n              </QuestionnaireChoiceDescription>\n            </QuestionnaireChoice>\n          ))}\n        </QuestionnaireChoices>\n        <QuestionnaireError />\n      </QuestionnaireItem>\n\n      <QuestionnaireItem name=\"reviewer\" required>\n        <QuestionnaireTitle>\n          Who reviews changes before they ship?\n        </QuestionnaireTitle>\n        <QuestionnaireChoices>\n          {REVIEWERS.map((reviewer) => (\n            <QuestionnaireChoice key={reviewer.value} value={reviewer.value}>\n              {reviewer.label}\n            </QuestionnaireChoice>\n          ))}\n        </QuestionnaireChoices>\n        <QuestionnaireError />\n      </QuestionnaireItem>\n\n      <QuestionnaireItem name=\"cadence\" required>\n        <QuestionnaireTitle>\n          How often should the digest arrive?\n        </QuestionnaireTitle>\n        <QuestionnaireChoices>\n          {CADENCES.map((cadence) => (\n            <QuestionnaireChoice key={cadence.value} value={cadence.value}>\n              {cadence.label}\n            </QuestionnaireChoice>\n          ))}\n        </QuestionnaireChoices>\n        <QuestionnaireError />\n      </QuestionnaireItem>\n\n      <QuestionnaireActions>\n        <QuestionnairePrevious />\n        <QuestionnaireNext>Next</QuestionnaireNext>\n        <QuestionnaireSubmit>Turn these on</QuestionnaireSubmit>\n      </QuestionnaireActions>\n    </Questionnaire>\n  )\n}","target":"components/examples/c-questionnaire-2.tsx"}],"meta":{"order":2,"gridSize":2}}