Questionnaire
Questionnaire dựng khảo sát hoặc form nhiều bước từ một mảng cấu hình — mỗi màn một câu như Typeform, hoặc tất cả trên một trang.
Khảo sát nhanh
6 câu · khoảng 1 phút
Bạn dùng Yugen UI cho việc gì? (bắt buộc)
Nhấn 1–3 để chọn
Import
Cài từ npm, chép mã nguồn vào dự án bằng @yugen-ui/cli hoặc shadcn CLI, hoặc import thẳng khi làm việc trong repo này.
// npm install yugen-uiimport { Questionnaire, type Question } from "yugen-ui";Biến thể
Mỗi ô là một mẫu độc lập: hover để Copy, hoặc bấm Code để xem mã nguồn.
Chế độ list + validate
mode="list": mọi câu trên một trang, mỗi câu bọc trong Field; email kiểm tra bằng validate.
Đã trả lời 0 /2 câu
Hôm nay của bạn thế nào? (bắt buộc)
Nhấn 1–5 để chấm điểm
Controlled & màn hoàn tất
value / onChange để đồng bộ với state bên ngoài; completion là render-prop nhận answers và restart.
Cách dùng
- Giữ mỗi câu một ý; mô tả ngắn chỉ khi cần giải thích.
- Chỉ đánh dấu required khi thật sự cần — câu tuỳ chọn giúp người dùng đi nhanh.
- Bật bước xem lại cho khảo sát dài hoặc khi câu trả lời khó sửa sau khi gửi.
- Không dùng stepper cho form 2–3 ô đơn giản — dùng mode="list" hoặc Field thường.
- Không quá 9 lựa chọn trong một câu (phím số chỉ tới 9, và khó đọc).
- Không dùng validate để chặn câu tuỳ chọn còn trống — validate chỉ chạy khi đã có câu trả lời.
Props
| Prop · Kiểu | Mặc định | Mô tả |
|---|---|---|
| questionsQuestion[] | Mặc định: — | Cấu hình câu hỏi: { id, type, title, description?, required?, validate?, …theo loại }. |
| mode"stepper" | "list" | Mặc định: "stepper" | Mỗi màn một câu, hoặc tất cả trên một trang. |
| value / defaultValueQuestionnaireAnswers | Mặc định: — | Record<id, string | string[] | number | boolean>. |
| onChange(answers, questionId) => void | Mặc định: — | Gọi mỗi khi một câu trả lời đổi. |
| onSubmit(answers) => void | Promise<void> | Mặc định: — | Gọi khi mọi câu hợp lệ; Promise → nút Gửi hiện “Đang gửi…”, lỗi → thông báo thử lại. |
| reviewboolean | Mặc định: true | Thêm bước xem lại trước khi gửi. |
| completionReactNode | (answers, restart) => ReactNode | Mặc định: — | Thay màn hoàn tất mặc định. |
| title / description / submitLabelReactNode / ReactNode / string | Mặc định: — / — / "Submit" | Phần đầu và nhãn nút gửi. |
| appearance"outline" | "subtle" | Mặc định: "outline" | Có khung (Card) hay không. |
| SingleQuestion / MultipleQuestion{ options, min?, max? } | Mặc định: — | Thẻ lựa chọn; multiple giới hạn bằng min / max. |
| RatingQuestion / ScaleQuestion{ max? } / { min?, max?, minLabel?, maxLabel? } | Mặc định: — | Sao (Rating) hoặc thang điểm phân đoạn (mặc định 0–10). |
| TextQuestion / LongTextQuestion{ placeholder?, inputType?, maxLength?, rows? } | Mặc định: — | Input một dòng / Textarea có bộ đếm. |
| YesNoQuestion{ yesLabel?, noLabel? } | Mặc định: — | Có / Không — phím Y, N (hoặc C, K). |
Messages
Chữ có sẵn của component: nhãn, aria-label, thông báo cho trình đọc màn hình.
Mặc định là tiếng Anh. Đổi qua prop messages của YugenProvider, theo namespace bên dưới; messagesVi chứa bản tiếng Việt. Xem Ngôn ngữ giao diện.
messages.questionnaire
| Khoá · Kiểu | Mặc định (tiếng Anh) | messagesVi |
|---|---|---|
| requiredTextstring Lỗi khi câu bắt buộc chưa trả lời (text / longText · rating / scale · còn lại). | Mặc định (tiếng Anh)"Enter an answer." | messagesVi"Vui lòng nhập câu trả lời." |
| requiredScorestring | Mặc định (tiếng Anh)"Select a rating." | messagesVi"Vui lòng chọn một mức điểm." |
| requiredChoicestring | Mặc định (tiếng Anh)"Select an answer." | messagesVi"Vui lòng chọn một câu trả lời." |
| minSelected(min: number) => string Lỗi số mục đã chọn của câu | Mặc định (tiếng Anh)(min) => (min === 1 ? "Select at least 1 item." : `Select at least ${min} items.`) | messagesVi(min) => `Chọn ít nhất ${min} mục.` |
| maxSelected(max: number) => string | Mặc định (tiếng Anh)(max) => (max === 1 ? "Select up to 1 item." : `Select up to ${max} items.`) | messagesVi(max) => `Chọn tối đa ${max} mục.` |
| selectedCount(count: number, max: number) => string Đếm số mục đã chọn của câu | Mặc định (tiếng Anh)(count, max) => `${count}/${max} selected` | messagesVi(count, max) => `Đã chọn ${count}/${max}` |
| unansweredstring Trang xem lại: câu chưa trả lời, điểm rating. | Mặc định (tiếng Anh)"Not answered" | messagesVi"Chưa trả lời" |
| ratingAnswer(value: number, max: number) => string | Mặc định (tiếng Anh)(value, max) => `${value}/${max} stars` | messagesVi(value, max) => `${value}/${max} sao` |
| yesstring Nhãn mặc định của câu | Mặc định (tiếng Anh)"Yes" | messagesVi"Có" |
| nostring | Mặc định (tiếng Anh)"No" | messagesVi"Không" |
| requiredstring Chữ ẩn (sr-only) sau tiêu đề câu bắt buộc. | Mặc định (tiếng Anh)"(required)" | messagesVi"(bắt buộc)" |
| keyHintChoice(count: number) => string Gợi ý phím tắt (chế độ stepper). | Mặc định (tiếng Anh)(count) => `Press 1–${count} to select` | messagesVi(count) => `Nhấn 1–${count} để chọn` |
| keyHintYesNostring | Mặc định (tiếng Anh)"Press Y or N to select" | messagesVi"Nhấn Y hoặc N để chọn" |
| keyHintRating(max: number) => string | Mặc định (tiếng Anh)(max) => `Press 1–${max} to rate` | messagesVi(max) => `Nhấn 1–${max} để chấm điểm` |
| keyHintScalestring | Mặc định (tiếng Anh)"Press a number key to select" | messagesVi"Nhấn phím số để chọn" |
| questionOf(current: number, total: number) => string Dòng tiến độ và thanh tiến độ (chế độ stepper); | Mặc định (tiếng Anh)(current, total) => `Question ${current} of ${total}` | messagesVi(current, total) => `Câu ${current}/${total}` |
| progressLabelstring | Mặc định (tiếng Anh)"Survey progress" | messagesVi"Tiến độ khảo sát" |
| progressValue(current: number, total: number) => string | Mặc định (tiếng Anh)(current, total) => `Question ${current} of ${total}` | messagesVi(current, total) => `Câu ${current} trên ${total}` |
| reviewTitlestring Trang xem lại. | Mặc định (tiếng Anh)"Review your answers" | messagesVi"Xem lại câu trả lời" |
| reviewDescriptionstring | Mặc định (tiếng Anh)"Check your answers before you submit. Select “Edit” to make changes." | messagesVi"Kiểm tra lần cuối trước khi gửi. Chọn “Sửa” để thay đổi." |
| editQuestion(index: number, title: string) => string aria-label nút sửa một câu; | Mặc định (tiếng Anh)(index, title) => `Edit question ${index}: ${title}` | messagesVi(index, title) => `Sửa câu ${index}: ${title}` |
| doneTitlestring Màn hình hoàn tất mặc định. | Mặc định (tiếng Anh)"Thank you!" | messagesVi"Cảm ơn bạn!" |
| doneDescriptionstring | Mặc định (tiếng Anh)"Your answers have been recorded." | messagesVi"Câu trả lời của bạn đã được ghi nhận." |
| restartstring | Mặc định (tiếng Anh)"Start over" | messagesVi"Làm lại" |
| submitstring Nút và trạng thái. | Mặc định (tiếng Anh)"Submit" | messagesVi"Gửi" |
| submittingstring | Mặc định (tiếng Anh)"Submitting…" | messagesVi"Đang gửi…" |
| submitErrorstring | Mặc định (tiếng Anh)"Couldn't submit your answers. Try again." | messagesVi"Không gửi được câu trả lời. Vui lòng thử lại." |
| reviewstring | Mặc định (tiếng Anh)"Review" | messagesVi"Xem lại" |
| backstring | Mặc định (tiếng Anh)"Back" | messagesVi"Quay lại" |
| backToEditstring | Mặc định (tiếng Anh)"Back to editing" | messagesVi"Quay lại chỉnh sửa" |
| nextstring | Mặc định (tiếng Anh)"Next" | messagesVi"Tiếp" |
| orPressstring Chữ trước phím Enter cạnh nút Tiếp. | Mặc định (tiếng Anh)"or press" | messagesVi"hoặc nhấn" |