气泡/容器
Feedback
Composed feedback form for reporting issues and collecting feedback
反馈组件示例
"use client";
import * as React from "react";
import { FeedbackComposed } from "@/components/composed/feedback/feedback";
export function FeedbackDemo() {
const [selectedOption, setSelectedOption] = React.useState<string>("");
const [inputValue, setInputValue] = React.useState("");
const feedbackOptions = [
{ id: "harmful", label: "有害/不安全" },
{ id: "false", label: "信息虚假" },
{ id: "inappropriate", label: "内容不当" },
{ id: "other", label: "其他" },
];
const handleSubmit = () => {
console.log("提交反馈:", {
option: selectedOption,
input: inputValue,
});
// 这里可以添加提交逻辑
};
return (
<div className="flex flex-col gap-6 w-full max-w-md">
<div>
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)] mb-4">
反馈组件示例
</h3>
<FeedbackComposed
title="有什么问题?"
options={feedbackOptions}
selectedId={selectedOption}
onSelect={setSelectedOption}
inputValue={inputValue}
onInputChange={setInputValue}
onClose={() => console.log("关闭")}
onSubmit={handleSubmit}
/>
</div>
</div>
);
}
Feedback 反馈表单组件用于收集用户反馈,支持多选项按钮、详细描述输入和表单提交,适用于内容举报、问题反馈、满意度调查等场景。
概述
- 反馈选项:支持多个反馈类型选项,用户可快速选择问题类型(默认为多选模式)
- 详细描述:提供输入框收集详细的反馈信息,可配置
showInputWhenSelected实现仅选中指定选项(如「其他」)时才显示 - 表单提交:内置提交按钮和表单处理逻辑
- 多选/单选:支持多选和单选两种模式,默认为多选模式
- 受控/非受控:支持受控和非受控两种使用模式
- 可关闭:提供关闭按钮,方便用户退出反馈界面
- 灵活定制:可自定义标题、选项、占位符和提交按钮文本
- 按需显示输入框:
showInputWhenSelected可配置仅在选中指定选项时显示输入框
快速开始
多选模式(默认):用户可以选择多个反馈选项。
import { FeedbackComposed } from "@/registry/wuhan/composed/feedback/feedback";
export function Example() {
return (
<FeedbackComposed
options={[
{ id: "harmful", label: "有害/不安全" },
{ id: "false", label: "信息虚假" },
{ id: "inappropriate", label: "内容不当" },
{ id: "other", label: "其他" },
]}
onSubmit={(data) => {
console.log("反馈数据:", data);
// data.selectedIds: 选中的选项 ID 数组
// data.inputValue: 输入框内容
}}
/>
);
}单选模式:用户只能选择一个选项。
import { FeedbackComposed } from "@/registry/wuhan/composed/feedback/feedback";
export function Example() {
return (
<FeedbackComposed
multiple={false}
title="有什么问题?"
options={[
{ id: "harmful", label: "有害/不安全" },
{ id: "false", label: "信息虚假" },
]}
onSubmit={(data) => {
console.log("反馈数据:", data);
// data.selectedId: 选中的选项 ID(单选模式)
// data.inputValue: 输入框内容
}}
/>
);
}特性
- 多种反馈类型:通过选项按钮快速分类问题
- 多选支持:默认为多选模式,用户可选择多个选项(单选模式需设置
multiple={false}) - 详细信息收集:输入框支持用户补充详细描述
- 双模式支持:受控模式用于复杂表单,非受控模式用于简单场景
- 表单验证:内置表单提交处理,可扩展验证逻辑
- 无障碍支持:包含适当的 ARIA 标签
- 响应式设计:自适应不同屏幕尺寸
安装
代码演示
基本
基础的反馈组件示例,包含完整的反馈流程。
反馈组件示例
"use client";
import * as React from "react";
import { FeedbackComposed } from "@/components/composed/feedback/feedback";
export function FeedbackDemo() {
const [selectedOption, setSelectedOption] = React.useState<string>("");
const [inputValue, setInputValue] = React.useState("");
const feedbackOptions = [
{ id: "harmful", label: "有害/不安全" },
{ id: "false", label: "信息虚假" },
{ id: "inappropriate", label: "内容不当" },
{ id: "other", label: "其他" },
];
const handleSubmit = () => {
console.log("提交反馈:", {
option: selectedOption,
input: inputValue,
});
// 这里可以添加提交逻辑
};
return (
<div className="flex flex-col gap-6 w-full max-w-md">
<div>
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)] mb-4">
反馈组件示例
</h3>
<FeedbackComposed
title="有什么问题?"
options={feedbackOptions}
selectedId={selectedOption}
onSelect={setSelectedOption}
inputValue={inputValue}
onInputChange={setInputValue}
onClose={() => console.log("关闭")}
onSubmit={handleSubmit}
/>
</div>
</div>
);
}
受控模式
使用受控模式管理反馈状态,适合需要外部状态管理的场景。
受控模式反馈表单
当前选择: 无
输入内容: 空
"use client";
import * as React from "react";
import { FeedbackComposed } from "@/components/composed/feedback/feedback";
export function FeedbackControlled() {
const [selectedOption, setSelectedOption] = React.useState<string>("");
const [inputValue, setInputValue] = React.useState("");
const [submitted, setSubmitted] = React.useState(false);
const feedbackOptions = [
{ id: "bug", label: "Bug 报告" },
{ id: "feature", label: "功能建议" },
{ id: "question", label: "使用问题" },
{ id: "other", label: "其他" },
];
const handleSubmit = (data: { selectedId: string; inputValue: string }) => {
console.log("提交反馈:", data);
setSubmitted(true);
// 模拟提交成功后重置
setTimeout(() => {
setSelectedOption("");
setInputValue("");
setSubmitted(false);
}, 2000);
};
return (
<div className="flex flex-col gap-6 w-full max-w-md">
<div>
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)] mb-4">
受控模式反馈表单
</h3>
{submitted ? (
<div className="p-4 bg-green-50 text-green-700 rounded-lg">
提交成功!感谢您的反馈。
</div>
) : (
<FeedbackComposed
title="反馈问题"
options={feedbackOptions}
selectedId={selectedOption}
onSelect={setSelectedOption}
inputValue={inputValue}
onInputChange={setInputValue}
placeholder="请详细描述您遇到的问题..."
submitLabel="提交反馈"
onSubmit={handleSubmit}
onClose={() => console.log("关闭反馈")}
/>
)}
</div>
{/* 显示当前状态 */}
<div className="text-xs text-[var(--Text-text-tertiary)] space-y-1">
<div>当前选择: {selectedOption || "无"}</div>
<div>输入内容: {inputValue || "空"}</div>
</div>
</div>
);
}
自定义选项
自定义反馈选项,支持不同的业务场景。
自定义选项(带图标)
"use client";
import * as React from "react";
import { FeedbackComposed } from "@/components/composed/feedback/feedback";
import { ThumbsUp, ThumbsDown, Meh, Star } from "lucide-react";
export function FeedbackCustomOptions() {
const handleSubmit = (data: { selectedId: string; inputValue: string }) => {
console.log("满意度调查:", data);
};
// 自定义选项,包含图标和文本
const satisfactionOptions = [
{
id: "very-satisfied",
label: (
<span className="flex items-center gap-1.5">
<Star className="w-4 h-4" />
非常满意
</span>
),
},
{
id: "satisfied",
label: (
<span className="flex items-center gap-1.5">
<ThumbsUp className="w-4 h-4" />
满意
</span>
),
},
{
id: "neutral",
label: (
<span className="flex items-center gap-1.5">
<Meh className="w-4 h-4" />
一般
</span>
),
},
{
id: "dissatisfied",
label: (
<span className="flex items-center gap-1.5">
<ThumbsDown className="w-4 h-4" />
不满意
</span>
),
},
];
return (
<div className="flex flex-col gap-6 w-full max-w-md">
<div>
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)] mb-4">
自定义选项(带图标)
</h3>
<FeedbackComposed
title="对我们的服务满意吗?"
options={satisfactionOptions}
defaultSelectedId=""
placeholder="请告诉我们您的想法和建议..."
submitLabel="提交评价"
onSubmit={handleSubmit}
/>
</div>
</div>
);
}
定义内容
内容举报场景,包含常见的举报理由选项。
内容举报场景
💡 提示:举报信息将严格保密,我们会在24小时内处理。
"use client";
import * as React from "react";
import { FeedbackComposed } from "@/components/composed/feedback/feedback";
import { AlertTriangle, Flag, MessageSquareWarning, Ban } from "lucide-react";
export function FeedbackReport() {
const [isReported, setIsReported] = React.useState(false);
const reportOptions = [
{
id: "harmful",
label: (
<span className="flex items-center gap-1.5">
<AlertTriangle className="w-4 h-4" />
有害/不安全
</span>
),
},
{
id: "false-info",
label: (
<span className="flex items-center gap-1.5">
<Flag className="w-4 h-4" />
虚假信息
</span>
),
},
{
id: "spam",
label: (
<span className="flex items-center gap-1.5">
<Ban className="w-4 h-4" />
垃圾内容
</span>
),
},
{
id: "inappropriate",
label: (
<span className="flex items-center gap-1.5">
<MessageSquareWarning className="w-4 h-4" />
内容不当
</span>
),
},
];
const handleSubmit = (data: { selectedId: string; inputValue: string }) => {
console.log("举报内容:", data);
setIsReported(true);
};
const handleClose = () => {
console.log("取消举报");
setIsReported(false);
};
if (isReported) {
return (
<div className="w-full max-w-md p-6 bg-green-50 border border-green-200 rounded-lg">
<div className="flex flex-col items-center gap-3 text-center">
<div className="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center">
<Flag className="w-6 h-6 text-green-600" />
</div>
<h3 className="text-base font-medium text-green-900">举报已提交</h3>
<p className="text-sm text-green-700">
感谢您的举报,我们会尽快审核处理。
</p>
<button
onClick={() => setIsReported(false)}
className="mt-2 text-sm text-green-600 hover:text-green-700 underline"
>
返回
</button>
</div>
</div>
);
}
return (
<div className="flex flex-col gap-6 w-full max-w-md">
<div>
<h3 className="text-sm font-medium text-[var(--Text-text-secondary)] mb-4">
内容举报场景
</h3>
<FeedbackComposed
title="举报此内容"
options={reportOptions}
placeholder="请详细说明举报理由(必填)..."
submitLabel="提交举报"
onSubmit={handleSubmit}
onClose={handleClose}
/>
</div>
<div className="text-xs text-[var(--Text-text-tertiary)] p-3 bg-[var(--Container-bg-container-secondary)] rounded">
<p>💡 提示:举报信息将严格保密,我们会在24小时内处理。</p>
</div>
</div>
);
}
带验证的反馈表单
多选模式下的表单验证示例。
"use client";
import { FeedbackComposed } from "@/components/composed/feedback/feedback";
import { useState } from "react";
export function FeedbackValidated() {
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [inputValue, setInputValue] = useState("");
const [error, setError] = useState("");
const handleSubmit = (data: {
selectedIds: string[];
inputValue: string;
}) => {
// 验证
if (data.selectedIds.length === 0) {
setError("请至少选择一个反馈类型");
return;
}
if (!data.inputValue.trim()) {
setError("请输入详细描述");
return;
}
// 提交
console.log("提交反馈:", data);
setError("");
};
return (
<div>
<FeedbackComposed
options={[
{ id: "bug", label: "Bug" },
{ id: "feature", label: "功能" },
{ id: "other", label: "其他" },
]}
selectedIds={selectedIds}
onSelectChange={setSelectedIds}
inputValue={inputValue}
onInputChange={setInputValue}
onSubmit={handleSubmit}
/>
{error && <p className="text-red-500 text-sm mt-2">{error}</p>}
</div>
);
}
异步提交
异步提交反馈数据的示例。
"use client";
import { FeedbackComposed } from "@/components/composed/feedback/feedback";
import { useState } from "react";
export function FeedbackAsync() {
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const handleSubmit = async (data: {
selectedIds: string[];
inputValue: string;
}) => {
setLoading(true);
try {
// 模拟 API 调用
await new Promise((resolve) => setTimeout(resolve, 1500));
console.log("提交反馈:", data);
setSuccess(true);
} catch (error) {
console.error("提交失败:", error);
} finally {
setLoading(false);
}
};
if (success) {
return <div className="text-green-600 p-4 text-center">感谢您的反馈!</div>;
}
return (
<FeedbackComposed
options={[
{ id: "good", label: "👍 很好" },
{ id: "helpful", label: "有帮助" },
{ id: "bad", label: "👎 不好" },
]}
submitLabel={loading ? "提交中..." : "提交"}
onSubmit={handleSubmit}
/>
);
}
按需显示输入框
仅当选中「其他」选项时才显示输入框,适用于希望用户在选定预设选项后补充说明的场景。
import { FeedbackComposed } from "@/registry/wuhan/composed/feedback/feedback";
import { useRef } from "react";
export function Example() {
const feedbackRef = useRef<HTMLFormElement>(null);
return (
<FeedbackComposed
ref={feedbackRef}
title="有什么问题?"
options={[
{ id: "harmful", label: "有害/不安全" },
{ id: "false", label: "信息虚假" },
{ id: "other", label: "其他" },
]}
submitLabel="确认"
showInputWhenSelected={["other"]}
onInputShown={() => {
feedbackRef.current?.scrollIntoView?.({ behavior: "smooth", block: "end" });
}}
onSubmit={(data) => console.log("反馈数据:", data)}
/>
);
}带验证的反馈表单
多选模式下的表单验证示例。
"use client";
import { FeedbackComposed } from "@/components/composed/feedback/feedback";
import { useState } from "react";
export function FeedbackValidated() {
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [inputValue, setInputValue] = useState("");
const [error, setError] = useState("");
const handleSubmit = (data: {
selectedIds: string[];
inputValue: string;
}) => {
// 验证
if (data.selectedIds.length === 0) {
setError("请至少选择一个反馈类型");
return;
}
if (!data.inputValue.trim()) {
setError("请输入详细描述");
return;
}
// 提交
console.log("提交反馈:", data);
setError("");
};
return (
<div>
<FeedbackComposed
options={[
{ id: "bug", label: "Bug" },
{ id: "feature", label: "功能" },
{ id: "other", label: "其他" },
]}
selectedIds={selectedIds}
onSelectChange={setSelectedIds}
inputValue={inputValue}
onInputChange={setInputValue}
onSubmit={handleSubmit}
/>
{error && <p className="text-red-500 text-sm mt-2">{error}</p>}
</div>
);
}
异步提交
支持异步提交的反馈表单。
"use client";
import { FeedbackComposed } from "@/components/composed/feedback/feedback";
import { useState } from "react";
export function FeedbackAsync() {
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const handleSubmit = async (data: {
selectedIds: string[];
inputValue: string;
}) => {
setLoading(true);
try {
// 模拟 API 调用
await new Promise((resolve) => setTimeout(resolve, 1500));
console.log("提交反馈:", data);
setSuccess(true);
} catch (error) {
console.error("提交失败:", error);
} finally {
setLoading(false);
}
};
if (success) {
return <div className="text-green-600 p-4 text-center">感谢您的反馈!</div>;
}
return (
<FeedbackComposed
options={[
{ id: "good", label: "👍 很好" },
{ id: "helpful", label: "有帮助" },
{ id: "bad", label: "👎 不好" },
]}
submitLabel={loading ? "提交中..." : "提交"}
onSubmit={handleSubmit}
/>
);
}
API
FeedbackComposed
反馈表单主组件,提供完整的反馈收集界面。
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | "有什么问题?" | 反馈表单标题 |
options | FeedbackOption[] | - | 反馈选项列表(必填) |
multiple | boolean | true | 是否支持多选模式 |
selectedId | string | - | 单选模式:受控模式当前选中的选项 ID |
selectedIds | string[] | - | 多选模式:受控模式当前选中的选项 ID 列表 |
defaultSelectedId | string | "" | 单选模式:非受控模式默认选中的选项 ID |
defaultSelectedIds | string[] | [] | 多选模式:非受控模式默认选中的选项 ID 列表 |
onSelect | (id: string) => void | - | 单选模式:选项选择变化时的回调 |
onSelectChange | (ids: string[]) => void | - | 多选模式:选项选择变化时的回调 |
inputValue | string | - | 受控模式:输入框的当前值 |
defaultInputValue | string | "" | 非受控模式:输入框的默认值 |
onInputChange | (value: string) => void | - | 输入框内容变化时的回调 |
placeholder | string | "请输入详细描述..." | 输入框占位符 |
submitLabel | ReactNode | "提交" | 提交按钮文本 |
showInputWhenSelected | string | string[] | - | 仅当选中指定选项时显示输入框,如 ["other"] 表示选中「其他」时才显示,未配置时始终显示 |
onInputShown | () => void | - | 输入框由隐藏变为显示时调用,可用于滚动到可视区域等 |
onSubmit | (payload: { selectedId: string; selectedIds: string[]; inputValue: string }) => void | - | 表单提交时的回调 |
onClose | () => void | - | 关闭按钮点击时的回调 |
Example
多选模式(默认):
import { FeedbackComposed } from "@/registry/wuhan/composed/feedback/feedback";
function FeedbackExample() {
return (
<FeedbackComposed
options={[
{ id: "bug", label: "Bug 报告" },
{ id: "feature", label: "功能建议" },
{ id: "question", label: "使用问题" },
]}
onSubmit={(data) => {
console.log("提交:", data);
// data.selectedIds: 选中的选项 ID 数组
// data.selectedId: 空字符串(多选模式)
// data.inputValue: 输入框内容
}}
/>
);
}单选模式:
import { FeedbackComposed } from "@/registry/wuhan/composed/feedback/feedback";
import { useState } from "react";
function FeedbackExample() {
const [selectedId, setSelectedId] = useState("");
const [inputValue, setInputValue] = useState("");
return (
<FeedbackComposed
multiple={false}
title="反馈问题"
options={[
{ id: "bug", label: "Bug 报告" },
{ id: "feature", label: "功能建议" },
{ id: "question", label: "使用问题" },
]}
// 单选模式受控
selectedId={selectedId}
onSelect={setSelectedId}
inputValue={inputValue}
onInputChange={setInputValue}
// 提交处理
onSubmit={(data) => {
console.log("提交:", data);
// data.selectedId: 选中的选项 ID
// data.selectedIds: 空数组(单选模式)
// data.inputValue: 输入框内容
}}
onClose={() => {
console.log("关闭反馈");
}}
/>
);
}FeedbackOption
反馈选项类型定义。
interface FeedbackOption {
id: string; // 选项的唯一标识符
label: ReactNode; // 选项显示的标签
}使用场景
- 内容举报:用户举报不当内容、虚假信息、有害内容等
- 问题反馈:收集用户遇到的问题和 Bug 报告
- 功能建议:收集用户的产品改进建议和新功能需求
- 满意度调查:收集用户对服务、产品的满意度反馈
- 客服咨询:用户提交咨询问题的表单
- 质量评价:对内容、服务质量的评价和意见收集
最佳实践
- 选项设计:反馈选项应涵盖常见问题类型,并提供"其他"选项
- 输入引导:使用清晰的占位符文本引导用户输入详细信息
- 提交反馈:提交成功后给予明确的反馈提示
- 数据验证:提交前验证必填项,确保收集到有效信息
- 隐私保护:明确告知用户数据用途,保护用户隐私
- 响应及时:收到反馈后及时响应,提升用户体验
注意事项
options是必填属性,至少需要提供一个选项- 多选模式(默认):使用
selectedIds/defaultSelectedIds和onSelectChange - 单选模式:使用
selectedId/defaultSelectedId和onSelect - 受控模式下需要同时提供
selectedId/selectedIds和对应的回调函数、inputValue和onInputChange - 非受控模式下可以使用
defaultSelectedId/defaultSelectedIds和defaultInputValue设置初始值 onSubmit回调会接收包含selectedId、selectedIds和inputValue的对象- 多选模式下:
selectedIds为选中的 ID 数组,selectedId为空字符串 - 单选模式下:
selectedId为选中的 ID,selectedIds为空数组
- 多选模式下:
- 表单提交会自动调用
preventDefault(),需要在onSubmit中处理实际的提交逻辑 showInputWhenSelected未配置时输入框始终显示;配置后仅当选中指定选项时显示,如["other"]表示选中「其他」时才显示onInputShown在输入框由隐藏变为显示时触发,可用于滚动到可视区域等场景
样式定制
组件使用 Tailwind CSS,可以通过原语组件进行完全定制:
import {
FeedbackContainerPrimitive,
FeedbackHeaderPrimitive,
FeedbackInputPrimitive,
FeedbackSubmitButtonPrimitive,
} from "@/registry/wuhan/blocks/feedback/feedback-01";
function CustomFeedback() {
return (
<FeedbackContainerPrimitive className="bg-gray-50 rounded-lg">
<FeedbackHeaderPrimitive
title="自定义反馈"
className="border-b border-gray-200"
/>
<FeedbackInputPrimitive
placeholder="输入您的反馈..."
className="border-2 border-blue-200 focus:border-blue-500"
/>
<FeedbackSubmitButtonPrimitive className="bg-purple-500 hover:bg-purple-600">
发送反馈
</FeedbackSubmitButtonPrimitive>
</FeedbackContainerPrimitive>
);
}原语组件
Feedback 组件基于以下原语组件构建,可用于需要完全自定义的场景。
FeedbackContainerPrimitive
反馈容器组件,包含所有反馈内容。
| 参数 | 类型 | 说明 |
|---|---|---|
children | ReactNode | 子元素 |
onClose | () => void | 关闭回调 |
className | string | 自定义样式类名 |
FeedbackHeaderPrimitive
反馈头部组件,包含标题和关闭按钮。
| 参数 | 类型 | 说明 |
|---|---|---|
title | ReactNode | 标题内容 |
onClose | () => void | 关闭回调 |
className | string | 自定义样式类名 |
FeedbackInputContainerPrimitive
输入框容器组件。
| 参数 | 类型 | 说明 |
|---|---|---|
children | ReactNode | 子元素 |
className | string | 自定义样式类名 |
FeedbackInputPrimitive
反馈输入框组件,复用 sidebar 搜索框样式。
| 参数 | 类型 | 说明 |
|---|---|---|
placeholder | string | 占位符文本 |
value | string | 输入框值 |
onChange | (e: ChangeEvent) => void | 变化回调 |
className | string | 自定义样式类名 |
FeedbackSubmitButtonPrimitive
提交按钮组件,品牌色样式。
| 参数 | 类型 | 说明 |
|---|---|---|
children | ReactNode | 按钮文本 |
type | "submit" | "button" | 按钮类型 |
className | string | 自定义样式类名 |
已废弃的原语
注意:以下组件已废弃,建议使用
toggle-buttonblock 中的组件。
- FeedbackButtonPrimitive - 已废弃,请使用
ToggleButtonPrimitive - FeedbackButtonGroupPrimitive - 已废弃,请使用
ToggleButtonGroupPrimitive
原语组件可以在 registry/wuhan/blocks/feedback/feedback-01.tsx 中找到。