跳到正文
目录与搜索
⌘K

原语 / React Aria

Questionnaire · React Aria

多步问卷,支持单选、多选、自由填写与跳过。

安装

React Aria 的原语保持上游 API 不变,安装只需要三步:加包、加组件、把 import 换掉。

终端
bun add @animate-ui/react motion
bunx shadcn@latest add questionnaire
bunx @animate-ui/cli@latest animate questionnaire

用法

从 Animate UI 引入原语,其余结构、属性与 data 属性都保持 React Aria 的写法。也可以按子路径单独引入:

tsx
import { Questionnaire } from '@animate-ui/react/primitives/questionnaire';
questionnaire.tsx
import { Questionnaire } from '@animate-ui/react/primitives/questionnaire';
questionnaire.tsx
import {
  Questionnaire,
  QuestionnaireActions,
  QuestionnaireChoice,
  QuestionnaireChoices,
  QuestionnaireDescription,
  QuestionnaireError,
  QuestionnaireInput,
  QuestionnaireItem,
  QuestionnaireNext,
  QuestionnairePrevious,
  QuestionnaireProgress,
  QuestionnaireSkip,
  QuestionnaireSubmit,
  QuestionnaireTitle,
} from '@/components/ui/questionnaire';

const items = [
  {
    name: 'direction',
    required: true,
    prompt: 'What should we prototype next?',
    description: 'Choose a direction or write your own.',
    choices: [
      {
        value: 'delegation',
        label: 'Delegation',
        description: 'Show how work moves to a specialist.',
      },
      {
        value: 'questions',
        label: 'Question prompts',
        description: 'Show choices while the interface waits.',
      },
      { value: 'both', label: 'Both together' },
    ],
    input: { label: 'Another answer', placeholder: 'Type another answer…' },
  },
  {
    name: 'detail',
    required: false,
    prompt: 'How much detail should it include?',
    description: 'Skip this if you are not sure yet.',
    choices: [
      { value: 'focused', label: 'Focused' },
      { value: 'complete', label: 'Complete flow' },
    ],
  },
] as const;

export function PrototypeQuestionnaire() {
  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const answers = new FormData(event.currentTarget);
    // answers.get("direction"), answers.getAll(...) for multiple items.
  }

  return (
    <Questionnaire items={items} onSubmit={handleSubmit}>
      <QuestionnaireProgress />
      {items.map((question) => (
        <QuestionnaireItem
          key={question.name}
          name={question.name}
          required={question.required}
        >
          <QuestionnaireTitle>{question.prompt}</QuestionnaireTitle>
          <QuestionnaireDescription>
            {question.description}
          </QuestionnaireDescription>
          <QuestionnaireChoices>
            {question.choices.map((choice) => (
              <QuestionnaireChoice key={choice.value} value={choice.value}>
                <span className="font-medium">{choice.label}</span>
                {'description' in choice ? (
                  <span className="text-muted-foreground">
                    {choice.description}
                  </span>
                ) : null}
              </QuestionnaireChoice>
            ))}
            {'input' in question ? (
              <QuestionnaireInput
                aria-label={question.input.label}
                placeholder={question.input.placeholder}
              />
            ) : null}
          </QuestionnaireChoices>
          <QuestionnaireError />
        </QuestionnaireItem>
      ))}
      <QuestionnaireActions>
        <QuestionnairePrevious />
        <QuestionnaireSkip />
        <QuestionnaireNext />
        <QuestionnaireSubmit />
      </QuestionnaireActions>
    </Questionnaire>
  );
}

动效

  • 前进时当前题向起始方向滑出 24px 并淡入 5px 模糊(150ms),下一题从另一侧 24px 进入(300ms)。
  • 高度在旧题离场后以 450ms 布局曲线滑向新高度,下方操作区不跳变。

所有时长都会乘以 AnimateUIProvider 的 tempo,并遵守系统的减少动效设置。

API

动效相关的 transition 与 exitTransition 是 Animate UI 唯一新增的属性;其余 props 与 React Aria 完全一致。

这一页没有额外的动画属性,API 与 React Aria 相同。