代码演示
如何引入
import { Feedback } from '@fibrous/semi-ui-react';
基本使用
通过 visible 设置是否显示。默认反馈展示内容是 emoji 形式。 可通过 onValueChange 获取当前选择的内容。
代码
import { useState, useCallback } from 'react';
import { Feedback, Button, type FeedbackValue } from '@fibrous/semi-ui-react';
export default function FeedbackDemo() {
const [visible, setVisible] = useState(false);
const handleOk = useCallback(() => setVisible(false), []);
const handleCancel = useCallback(() => setVisible(false), []);
const onValueChange = useCallback((value: FeedbackValue) => {
console.log('emoji value', value);
}, []);
return (
<>
<Button onClick={() => setVisible(!visible)}>展示反馈: Popup, emoji</Button>
<Feedback title="您对本产品的评分是?" visible={visible} onOk={handleOk} onCancel={handleCancel} onValueChange={onValueChange} />
</>
);
}文字类型
设置 type 为 text 可获得多行输入框形式的 feedback,可通过 textAreaProps 设置多行输入框的参数。
代码
import { useState, useCallback } from 'react';
import { Feedback, Button } from '@fibrous/semi-ui-react';
export default function FeedbackDemo() {
const [visible, setVisible] = useState(false);
const handleOk = useCallback(() => setVisible(false), []);
const handleCancel = useCallback(() => setVisible(false), []);
return (
<>
<Button onClick={() => setVisible(!visible)}>展示反馈: Popup, text</Button>
<Feedback type="text" textAreaProps={{ maxCount: 200 }} title="您对本产品的建议是?" visible={visible} onOk={handleOk} onCancel={handleCancel} />
</>
);
}单选反馈
设置 type 为 radio 可获得单选形式的 feedback,可通过 radioGroupProps 设置单选的参数。选项组默认没有可访问名称,可通过 radioGroupProps 的 aria-label 命名。
代码
import { useState, useCallback } from 'react';
import { Feedback, Button } from '@fibrous/semi-ui-react';
export default function FeedbackDemo() {
const [visible, setVisible] = useState(false);
const handleOk = useCallback(() => setVisible(false), []);
const handleCancel = useCallback(() => setVisible(false), []);
return (
<>
<Button onClick={() => setVisible(!visible)}>展示反馈: Popup, radio</Button>
<Feedback
type="radio"
radioGroupProps={{
options: ['访客', '开发者', '维护者'],
}}
title="您的身份是"
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
/>
</>
);
}多选反馈
设置 type 为 checkbox 可获得多选形式的 feedback,可通过 checkboxGroupProps 设置多选的参数。选项组默认没有可访问名称,可通过 checkboxGroupProps 的 aria-label 命名。
代码
import { useState, useCallback } from 'react';
import { Feedback, Button } from '@fibrous/semi-ui-react';
export default function FeedbackDemo() {
const [visible, setVisible] = useState(false);
const handleOk = useCallback(() => setVisible(false), []);
const handleCancel = useCallback(() => setVisible(false), []);
return (
<>
<Button onClick={() => setVisible(!visible)}>展示反馈: Popup, checkbox</Button>
<Feedback
type="checkbox"
checkboxGroupProps={{
options: ['抖音', '火山', '豆包'],
}}
title="您最常使用以下哪些产品?"
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
/>
</>
);
}自定义反馈内容
设置 type 为 custom 可获得自定义内容的 feedback,可通过 renderContent 设置反馈的内容。使用自定义反馈时候,需自行控制提交按钮的禁用与否状态,用户可通过 okButtonProps 设置。
代码
import { useState, useCallback } from 'react';
import { Feedback, Button, TextArea } from '@fibrous/semi-ui-react';
export default function FeedbackDemo() {
const [visible, setVisible] = useState(false);
const [value, setValue] = useState('');
const handleOk = useCallback(() => setVisible(false), []);
const handleCancel = useCallback(() => setVisible(false), []);
const onTextAreaChange = useCallback((value: string) => {
setValue(value);
}, []);
const renderContent = useCallback(() => {
return (
<>
<span>这是一段自定义的内容</span>
<TextArea onChange={onTextAreaChange} />
</>
);
}, [onTextAreaChange]);
return (
<>
<Button onClick={() => setVisible(!visible)}>展示反馈: Popup, custom</Button>
<Feedback type="custom" okButtonProps={{ disabled: !value }} title="您对本产品的建议是?" visible={visible} onOk={handleOk} onCancel={handleCancel} renderContent={renderContent} />
</>
);
}模态对话框形式
可通过 mode 反馈的形式,默认是 popup, 设置为 modal 可获得模态对话框形式的展示。
代码
import { useState, useCallback } from 'react';
import { Feedback, Button, type FeedbackValue } from '@fibrous/semi-ui-react';
export default function FeedbackDemo() {
const [visible, setVisible] = useState(false);
const handleOk = useCallback(() => setVisible(false), []);
const handleCancel = useCallback(() => setVisible(false), []);
const onValueChange = useCallback((value: FeedbackValue) => {
console.log('emoji value', value);
}, []);
return (
<>
<Button onClick={() => setVisible(!visible)}>展示反馈: Modal, emoji</Button>
<Feedback mode="modal" title="Why did you choose this rating?" visible={visible} onOk={handleOk} onCancel={handleCancel} onValueChange={onValueChange} />
</>
);
}反馈完成提示
反馈完成后,可以切换展示信息提示用户本次反馈已经完成。
代码
import { useState, useCallback, useMemo } from 'react';
import { IllustrationSuccess, IllustrationSuccessDark } from '@fibrous/semi-illustrations-react';
import { Feedback, Button, Empty, TextArea } from '@fibrous/semi-ui-react';
export default function FeedbackDemo() {
const [visible1, setVisible1] = useState(false);
const [value1, setValue1] = useState('');
const [showThanks1, setShowThanks1] = useState(false);
const [visible2, setVisible2] = useState(false);
const [value2, setValue2] = useState('');
const [showThanks2, setShowThanks2] = useState(false);
const handleOk1 = useCallback(() => {
setShowThanks1(true);
setTimeout(() => {
setVisible1(false);
setTimeout(() => {
setShowThanks1(false);
}, 200);
}, 1500);
}, []);
const hideFeedback1 = useCallback(() => {
setVisible1(false);
}, []);
const onTextAreaChange1 = useCallback((value: string) => {
setValue1(value);
}, []);
const handleOk2 = useCallback(() => {
setShowThanks2(true);
setTimeout(() => {
setVisible2(false);
setTimeout(() => {
setShowThanks2(false);
}, 200);
}, 1500);
}, []);
const hideFeedback2 = useCallback(() => {
setVisible2(false);
}, []);
const onTextAreaChange2 = useCallback((value: string) => {
setValue2(value);
}, []);
const showThankProps = useMemo(() => ({ title: ' ', footer: null }), []);
return (
<div>
<Button onClick={() => setVisible1(!visible1)}>Open Feedback: Popup, Custom</Button>
<Feedback
visible={visible1}
onOk={handleOk1}
onCancel={hideFeedback1}
okButtonProps={{
disabled: !value1,
}}
title="What is your feedback on this product?"
type="custom"
{...(showThanks1 ? showThankProps : {})}
renderContent={() => {
return showThanks1 ? (
<>
<Empty image={<IllustrationSuccess style={{ width: 150, height: 150 }} />} darkModeImage={<IllustrationSuccessDark style={{ width: 150, height: 150 }} />} description="感谢您的反馈" style={{ padding: 30 }} />
</>
) : (
<>
<span>这是一段自定义的内容</span>
<TextArea onChange={onTextAreaChange1} />
</>
);
}}
/>
<br />
<br />
<Button onClick={() => setVisible2(!visible2)}>Open Feedback: Modal, Custom</Button>
<Feedback
visible={visible2}
onOk={handleOk2}
onCancel={hideFeedback2}
okButtonProps={{
disabled: !value2,
}}
title="What is your feedback on this product?"
type="custom"
mode="modal"
{...(showThanks2 ? showThankProps : {})}
renderContent={() => {
return showThanks2 ? (
<>
<Empty image={<IllustrationSuccess style={{ width: 150, height: 150 }} />} darkModeImage={<IllustrationSuccessDark style={{ width: 150, height: 150 }} />} description="感谢您的反馈" style={{ padding: 30 }} />
</>
) : (
<>
<span>这是一段自定义的内容</span>
<TextArea onChange={onTextAreaChange2} />
</>
);
}}
/>
</div>
);
}API 参考
FeedbackProps
除去下面参数列表所列参数外,当 mode 为 modal 时, FeedbackProps 还支持 ModalProps 中的参数,当 mode 为 popup 时, FeedbackProps 还支持 SideSheetProps 中的参数
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| afterClose | 弹层完全关闭后的回调,两种模式都会调用。 | Type() => void | - |
| bodyStyle | 内容区域的样式,透传给当前模式的 Modal 或 SideSheet。 | CSSProperties | - |
| cancelButtonProps | 取消按钮的属性。popup 模式下 loading 取内置挂起态与使用方的 loading 任一为真,onClick 先于内置处理调用。 | ButtonProps | - |
| checkboxGroupProps | type 为 checkbox 时多选组的属性,写法同 CheckboxGroup 的属性。选项组默认没有可访问名称,请用 aria-label 命名。 | CheckboxGroupProps | - |
| children | type 为 custom 时的反馈内容。 | React.ReactNode | - |
| className | 弹层根元素的类名,与内置类名合并。 | string | - |
| closable | 是否显示关闭按钮,透传给当前模式的 Modal 或 SideSheet。 | boolean | - |
| closeIcon | 关闭按钮中的图标,透传给当前模式的 Modal 或 SideSheet。 | ReactNode | - |
| closeOnEsc | 按 Esc 是否关闭弹层,透传给当前模式的 Modal 或 SideSheet。popup 模式需设为 true 才会在按 Esc 时触发 onCancel。 | boolean | - |
| footer | 替换默认的取消、提交按钮所在的页脚,透传给当前模式的 Modal 或 SideSheet。传入 null 时不渲染页脚。 | ReactNode | - |
| getPopupContainer | 弹层挂载的容器,透传给当前模式的 Modal 或 SideSheet。 | Type() => HTMLElement | - |
| height | 弹层高度,仅 popup 模式生效,透传给 SideSheet。popup 模式内置为 auto,给出后以使用方的值为准。 | string | number | - |
| keepDOM | 关闭后是否保留 DOM,透传给当前模式的 Modal 或 SideSheet。 | boolean | - |
| mask | 是否显示遮罩,透传给当前模式的 Modal 或 SideSheet。popup 模式内置为不显示,给出后以使用方的值为准。 | boolean | - |
| maskClosable | 点击遮罩是否关闭,透传给当前模式的 Modal 或 SideSheet。 | boolean | - |
| maskStyle | 遮罩的样式,透传给当前模式的 Modal 或 SideSheet。 | CSSProperties | - |
| mode | 展示模式:popup 是底部无遮罩的非模态面板(SideSheet),modal 是模态对话框(Modal)。未声明的属性按模式透传给对应的 SideSheet 或 Modal。 | 'modal' | 'popup' | "popup" |
| motion | 是否启用动画,透传给当前模式的 Modal 或 SideSheet。 | boolean | - |
| okButtonProps | 提交按钮的属性。未设置 disabled 时,内容为空则禁用;type 为 custom 时用它的 disabled 控制能否提交。loading 取内置挂起态与使用方的 loading 任一为真;onClick 先于内置处理调用,内置提交仍会执行。 | ButtonProps | - |
| onCancel | 取消按钮、关闭按钮、Esc 触发时调用,按 Esc 时参数为原生 KeyboardEvent。popup 模式返回 Promise 时取消按钮保持加载,resolve 后才清空已填写的值,reject 时保留。关闭由 visible 控制,组件不会自行关闭。 | Type(e: React.MouseEvent | KeyboardEvent) => void | Promise<any> | - |
| onOk | 点击提交按钮时调用,参数为点击事件。popup 模式返回 Promise 时提交按钮保持加载,resolve 后才清空已填写的值,reject 时保留;其他返回值立即清空。关闭由 visible 控制,组件不会自行关闭。 | Type(e: React.MouseEvent) => void | Promise<any> | - |
| onValueChange | 反馈内容变化时的回调,参数为当前值:text 为字符串,emoji 为 { emoji, text }(text 是选了「差」后填写的原因),radio 为所选项的值,checkbox 为所选值的数组。 | Type(value: FeedbackValue) => void | - |
| radioGroupProps | type 为 radio 时单选组的属性,写法同 RadioGroup 的属性。选项组默认没有可访问名称,请用 aria-label 命名。 | RadioGroupProps | - |
| ref | 弹层在 portal 中最外层容器的引用,popup 模式为 SideSheet 的最外层元素,modal 模式为 Modal 的最外层元素;未渲染时为 null。 | TypeReact.Ref<HTMLDivElement> | - |
| renderContent | 包裹反馈内容:参数为内置内容(type 为 custom 时是 children),返回包裹后的节点。 | Type(content: React.ReactNode) => React.ReactNode | - |
| size | 弹层尺寸,透传给当前模式的 Modal 或 SideSheet。 | Size | - |
| style | 内联样式,落点随模式不同:popup 模式在 role="dialog" 元素上,modal 模式在 .semi-modal 对话框盒子上。 | CSSProperties | - |
| textAreaProps | type 为 text 与 emoji 时多行输入框的属性,写法同 TextArea 的属性。输入的 onChange 由 Feedback 接管,使用方的 onChange 仍会被调用。 | TextAreaProps | - |
| title | 弹层标题。 | ReactNode | - |
| type | 反馈内容的类型,可选 emoji、text、radio、checkbox 或 custom。custom 时内容写在 children 中,或由 renderContent 生成。 | 'text' | 'emoji' | 'radio' | 'checkbox' | 'custom' | "emoji" |
| visible | 是否显示。组件不会自行关闭,由使用方在 onOk、onCancel 中把它设为 false。 | boolean | - |
| width | 弹层宽度,透传给当前模式的 Modal 或 SideSheet。 | string | number | - |
| zIndex | 弹层的层级,透传给当前模式的 Modal 或 SideSheet。 | number | - |
未声明的属性按 mode 透传给 Modal 或 SideSheet,落点与它们相同:aria-* 属性落在 role="dialog" 元素上,其余未声明的属性和原生事件监听落在 portal 内最外层元素上。
Accessibility
ARIA
- emoji 类型的表情是
role="radiogroup"(名称来自语言包Feedback.rating)中的原生input type="radio"(名称来自Feedback.bad、Feedback.normal、Feedback.good),选中状态由原生checked表达,遵循 APG Radio Group - 单选、多选类型的选项组默认没有可访问名称,可通过
radioGroupProps、checkboxGroupProps的aria-label命名 modal模式遵循 APG Dialog (Modal),细节同 Modal;popup模式是不带遮罩的 SideSheet,按非模态对话框处理,不设置aria-modal
键盘和焦点
- emoji 类型的表情组只有一个 Tab 停靠点,方向键切换选中的表情,键盘聚焦时表情显示主题焦点环。单选、多选类型的键盘操作沿用 RadioGroup 与 CheckboxGroup
popup模式打开时焦点移入反馈面板,但不限制 Tab,页面其余部分保持可用;按 Esc 触发取消。关闭时只在焦点仍在面板内或已落到 body 时归还给打开前的元素,用户已移到页面其他位置的焦点不动modal模式打开时焦点移入对话框,Tab 在对话框内循环,背后的页面不可交互;关闭后焦点回到打开前的元素,按 Esc 触发取消