反馈类

反馈

快速定义各类型反馈

代码演示

如何引入

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-
checkboxGroupPropstype 为 checkbox 时多选组的属性,写法同 CheckboxGroup 的属性。选项组默认没有可访问名称,请用 aria-label 命名。CheckboxGroupProps-
childrentype 为 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-
radioGroupPropstype 为 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-
textAreaPropstype 为 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 触发取消