본문으로 건너뛰기
언어

Questionnairestandard

질문을 한 번에 하나씩 보여 주고 선택지나 직접 입력으로 답을 받는 질문지(questionnaire)입니다. shadcn base-nova 의 stock 아이템이며 `@shadcn/react` 의 `Questionnaire` 부품 위에 서 있습니다. 한 화면에 여러 입력을 모두 늘어놓는 양식에는 `field` 를 씁니다.

설치

pnpm
pnpm dlx shadcn@4.23.0 add @uikit/questionnaire

의존 아이템button

Default

questionnaire-example.tsx
"use client"
import * as React from "react"import { toast } from "sonner"
import {  Example,  ExampleWrapper,} from "@/components/example"import { Button } from "@/components/ui/button"import {  Card,  CardAction,  CardContent,  CardDescription,  CardFooter,  CardHeader,  CardTitle,} from "@/components/ui/card"import {  Dialog,  DialogContent,  DialogDescription,  DialogFooter,  DialogHeader,  DialogTitle,  DialogTrigger,} from "@/components/ui/dialog"import {  Questionnaire,  QuestionnaireActions,  QuestionnaireChoice,  QuestionnaireChoiceDescription,  QuestionnaireChoices,  QuestionnaireDescription,  QuestionnaireError,  QuestionnaireInput,  QuestionnaireItem,  QuestionnaireNext,  QuestionnairePrevious,  QuestionnaireProgress,  QuestionnaireSkip,  QuestionnaireSubmit,  QuestionnaireTitle,} from "@/components/ui/questionnaire"
const questionnaireItems = [  {    choices: [      { value: "delegation" },      { value: "questions" },      { value: "both" },    ],    name: "direction",    required: true,  },  {    choices: [      { value: "progress" },      { value: "decisions" },      { value: "risks" },    ],    name: "signals",  },  {    choices: [{ value: "week" }, { value: "cycle" }, { value: "later" }],    name: "timing",    required: true,  },] as const
const taskItems = [  {    choices: [      { value: "inspect" },      { value: "implement" },      { value: "review" },    ],    name: "task",    required: true,  },] as const
const planItems = [  {    choices: [      { value: "plus" },      { value: "pro" },      { value: "enterprise", disabled: true },    ],    name: "plan",    required: true,  },] as const
export default function QuestionnaireExample() {  return (    <ExampleWrapper>      <QuestionnaireStandalone />      <QuestionnaireCard />      <QuestionnaireDialog />      <QuestionnaireNoDescription />      <QuestionnaireDisabled />    </ExampleWrapper>  )}
function QuestionnaireDisabled() {  return (    <Example title="Disabled" containerClassName="md:col-span-2">      <Questionnaire        className="mx-auto max-w-lg"        defaultItem="plan"        items={planItems}        onSubmit={handleSubmit}      >        <QuestionnaireItem name="plan" required>          <QuestionnaireTitle>Choose a plan</QuestionnaireTitle>          <QuestionnaireDescription>            Enterprise is not available on your account.          </QuestionnaireDescription>          <QuestionnaireChoices>            <QuestionnaireChoice value="plus">              <span className="font-medium">Plus</span>              <QuestionnaireChoiceDescription>                For individuals and small teams              </QuestionnaireChoiceDescription>            </QuestionnaireChoice>            <QuestionnaireChoice value="pro">              <span className="font-medium">Pro</span>              <QuestionnaireChoiceDescription>                For growing businesses              </QuestionnaireChoiceDescription>            </QuestionnaireChoice>            <QuestionnaireChoice value="enterprise" disabled>              <span className="font-medium">Enterprise</span>              <QuestionnaireChoiceDescription>                For large teams and enterprises              </QuestionnaireChoiceDescription>            </QuestionnaireChoice>          </QuestionnaireChoices>          <QuestionnaireError />        </QuestionnaireItem>        <QuestionnaireNavigation />      </Questionnaire>    </Example>  )}
function QuestionnaireNoDescription() {  return (    <Example title="No description" containerClassName="md:col-span-2">      <Questionnaire        className="mx-auto max-w-lg"        defaultItem="task"        items={taskItems}        shortcuts="letters"        onSubmit={handleSubmit}      >        <QuestionnaireProgress />        <QuestionnaireItem name="task" required>          <QuestionnaireTitle>            What should the agent do next?          </QuestionnaireTitle>          <QuestionnaireChoices>            <QuestionnaireChoice value="inspect">              Inspect the codebase            </QuestionnaireChoice>            <QuestionnaireChoice value="implement">              Implement the change            </QuestionnaireChoice>            <QuestionnaireChoice value="review">              Review the result            </QuestionnaireChoice>          </QuestionnaireChoices>          <QuestionnaireError />        </QuestionnaireItem>        <QuestionnaireNavigation />      </Questionnaire>    </Example>  )}
function QuestionnaireStandalone() {  return (    <Example title="Standalone" containerClassName="md:col-span-2">      <Questionnaire        className="mx-auto max-w-lg"        defaultItem="direction"        items={questionnaireItems}        shortcuts="letters"        onSubmit={handleSubmit}      >        <QuestionnaireProgress />        <QuestionnaireQuestions />        <QuestionnaireNavigation />      </Questionnaire>    </Example>  )}
function QuestionnaireCard() {  return (    <Example title="Card" containerClassName="md:col-span-2">      <Questionnaire        defaultItem="direction"        items={questionnaireItems}        shortcuts="numbers"        onSubmit={handleSubmit}      >        <QuestionnaireCardQuestions />      </Questionnaire>    </Example>  )}
function QuestionnaireDialog() {  return (    <Example      title="Dialog"      className="items-center justify-center"      containerClassName="md:col-span-2"    >      <Dialog>        <DialogTrigger render={<Button variant="outline" />}>          Open questionnaire        </DialogTrigger>        <DialogContent>          <Questionnaire            defaultItem="direction"            items={questionnaireItems}            onSubmit={handleSubmit}          >            <DialogHeader>              <DialogTitle className="sr-only">                Plan an agent interface              </DialogTitle>              <DialogDescription className="sr-only">                Answer three questions to shape the next prototype.              </DialogDescription>              <QuestionnaireProgress                className="font-semibold tracking-widest text-foreground uppercase"                render={(props, state) => (                  <span {...props}>                    Question {state.current} of {state.total}                  </span>                )}              />            </DialogHeader>            <QuestionnaireQuestions />            <DialogFooter>              <QuestionnaireNavigation />            </DialogFooter>          </Questionnaire>        </DialogContent>      </Dialog>    </Example>  )}
function QuestionnaireCardQuestions() {  const directionTitleId = React.useId()  const signalsTitleId = React.useId()  const timingTitleId = React.useId()
  return (    <>      <QuestionnaireItem        aria-labelledby={directionTitleId}        name="direction"        required      >        <Card className="mx-auto w-full max-w-lg">          <CardHeader>            <QuestionnaireTitle id={directionTitleId} render={<CardTitle />}>              What should we prototype next?            </QuestionnaireTitle>            <QuestionnaireDescription render={<CardDescription />}>              Choose one direction or write another answer.            </QuestionnaireDescription>            <CardAction>              <QuestionnaireProgress />            </CardAction>          </CardHeader>          <CardContent>            <QuestionnaireChoices>              <QuestionnaireChoice value="delegation">                <span className="font-medium">Sub-agent delegation</span>                <QuestionnaireChoiceDescription>                  Show when work is delegated and what comes back.                </QuestionnaireChoiceDescription>              </QuestionnaireChoice>              <QuestionnaireChoice value="questions">                <span className="font-medium">Question prompts</span>                <QuestionnaireChoiceDescription>                  Show choices while the agent waits for input.                </QuestionnaireChoiceDescription>              </QuestionnaireChoice>              <QuestionnaireChoice value="both">                <span className="font-medium">Both together</span>                <QuestionnaireChoiceDescription>                  Explore one unified interaction pattern.                </QuestionnaireChoiceDescription>              </QuestionnaireChoice>              <QuestionnaireInput                aria-label="Another direction"                placeholder="Type another direction…"              />            </QuestionnaireChoices>            <QuestionnaireError />          </CardContent>          <CardFooter>            <QuestionnaireNavigation />          </CardFooter>        </Card>      </QuestionnaireItem>      <QuestionnaireItem        aria-labelledby={signalsTitleId}        name="signals"        multiple      >        <Card className="mx-auto w-full max-w-lg">          <CardHeader>            <QuestionnaireTitle id={signalsTitleId} render={<CardTitle />}>              What should every progress update include?            </QuestionnaireTitle>            <QuestionnaireDescription render={<CardDescription />}>              Select all that apply, or skip this question.            </QuestionnaireDescription>            <CardAction>              <QuestionnaireProgress />            </CardAction>          </CardHeader>          <CardContent>            <QuestionnaireChoices>              <QuestionnaireChoice value="progress">                Progress              </QuestionnaireChoice>              <QuestionnaireChoice value="decisions">                Decisions              </QuestionnaireChoice>              <QuestionnaireChoice value="risks">Risks</QuestionnaireChoice>            </QuestionnaireChoices>            <QuestionnaireError />          </CardContent>          <CardFooter>            <QuestionnaireNavigation />          </CardFooter>        </Card>      </QuestionnaireItem>      <QuestionnaireItem aria-labelledby={timingTitleId} name="timing" required>        <Card className="mx-auto w-full max-w-lg">          <CardHeader>            <QuestionnaireTitle id={timingTitleId} render={<CardTitle />}>              When should this be revisited?            </QuestionnaireTitle>            <QuestionnaireDescription render={<CardDescription />}>              Choose when this should be revisited.            </QuestionnaireDescription>            <CardAction>              <QuestionnaireProgress />            </CardAction>          </CardHeader>          <CardContent>            <QuestionnaireChoices>              <QuestionnaireChoice value="week">This week</QuestionnaireChoice>              <QuestionnaireChoice value="cycle">                Next cycle              </QuestionnaireChoice>              <QuestionnaireChoice value="later">                Revisit later              </QuestionnaireChoice>            </QuestionnaireChoices>            <QuestionnaireError />          </CardContent>          <CardFooter>            <QuestionnaireNavigation />          </CardFooter>        </Card>      </QuestionnaireItem>    </>  )}
function QuestionnaireQuestions() {  return (    <>      <QuestionnaireItem name="direction" required>        <QuestionnaireTitle>What should we prototype next?</QuestionnaireTitle>        <QuestionnaireDescription>          Choose one direction or write another answer.        </QuestionnaireDescription>        <QuestionnaireChoices>          <QuestionnaireChoice value="delegation">            <span className="font-medium">Sub-agent delegation</span>            <QuestionnaireChoiceDescription>              Show when work is delegated and what comes back.            </QuestionnaireChoiceDescription>          </QuestionnaireChoice>          <QuestionnaireChoice value="questions">            <span className="font-medium">Question prompts</span>            <QuestionnaireChoiceDescription>              Show choices while the agent waits for input.            </QuestionnaireChoiceDescription>          </QuestionnaireChoice>          <QuestionnaireChoice value="both">            <span className="font-medium">Both together</span>            <QuestionnaireChoiceDescription>              Explore one unified interaction pattern.            </QuestionnaireChoiceDescription>          </QuestionnaireChoice>          <QuestionnaireInput            aria-label="Another direction"            placeholder="Type another direction…"          />        </QuestionnaireChoices>        <QuestionnaireError />      </QuestionnaireItem>      <QuestionnaireItem name="signals" multiple>        <QuestionnaireTitle>          What should every progress update include?        </QuestionnaireTitle>        <QuestionnaireDescription>          Select all that apply, or skip this question.        </QuestionnaireDescription>        <QuestionnaireChoices>          <QuestionnaireChoice value="progress">Progress</QuestionnaireChoice>          <QuestionnaireChoice value="decisions">Decisions</QuestionnaireChoice>          <QuestionnaireChoice value="risks">Risks</QuestionnaireChoice>        </QuestionnaireChoices>        <QuestionnaireError />      </QuestionnaireItem>      <QuestionnaireItem name="timing" required>        <QuestionnaireTitle>When should this be revisited?</QuestionnaireTitle>        <QuestionnaireDescription>          Choose when this should be revisited.        </QuestionnaireDescription>        <QuestionnaireChoices>          <QuestionnaireChoice value="week">This week</QuestionnaireChoice>          <QuestionnaireChoice value="cycle">Next cycle</QuestionnaireChoice>          <QuestionnaireChoice value="later">Revisit later</QuestionnaireChoice>        </QuestionnaireChoices>        <QuestionnaireError />      </QuestionnaireItem>    </>  )}
function QuestionnaireNavigation() {  return (    <QuestionnaireActions className="w-full">      <QuestionnairePrevious />      <QuestionnaireSkip />      <QuestionnaireNext>Next</QuestionnaireNext>      <QuestionnaireSubmit>Save answers</QuestionnaireSubmit>    </QuestionnaireActions>  )}
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {  event.preventDefault()
  const formData = new FormData(event.currentTarget)  const values = {    direction: formData.get("direction"),    signals: formData.getAll("signals"),    timing: formData.get("timing"),  }
  toast("Questionnaire submitted", {    description: `Direction: ${values.direction ?? "None"} · Progress signals: ${values.signals.join(", ") || "None"} · Timing: ${values.timing ?? "None"}`,  })}