展示类

用户引导

用于页面对新用户进行功能引导

代码演示

如何引入

import { UserGuide } from '@fibrous/semi-ui-solid';

基本用法



Default Tag
代码
import { createSignal } from 'solid-js';
import { Button, Space, Switch, Tag, type UserGuideStepItem, UserGuide } from '@fibrous/semi-ui-solid';

const steps = [
    {
        target: () => document.querySelector('#basic-demo-1'),
        title: '新手引导',
        description: 'Hello ByteDancer!',
        position: 'bottom',
    },
    {
        target: () => document.querySelector('#basic-demo-2'),
        title: 'Switch',
        description: 'This is a Semi Switch',
        position: 'bottom',
    },
    {
        target: () => document.querySelector('#basic-demo-3'),
        title: 'Button',
        description: 'This is a Semi Button',
        position: 'bottom',
    },
] satisfies UserGuideStepItem[];

export default function UserGuideDemo() {
    const [visible, setVisible] = createSignal(false);
    const showDialog = () => {
        setVisible(true);
    };
    return (
        <div>
            <Button onClick={showDialog}>开始引导</Button>
            <br />
            <br />
            <Space>
                <Switch aria-label="示例开关" id="basic-demo-1" defaultChecked />
                <Tag id="basic-demo-2"> Default Tag </Tag>
                <Button id="basic-demo-3">确定</Button>
            </Space>
            <UserGuide
                mode="popup"
                mask
                visible={visible()}
                steps={steps}
                onChange={(current) => {
                    console.log('当前引导步骤', current);
                }}
                onNext={() => {
                    console.log('下一步引导');
                }}
                onPrev={() => {
                    console.log('上一步引导');
                }}
                onFinish={() => {
                    setVisible(false);
                    console.log('引导完成');
                }}
                onSkip={() => {
                    setVisible(false);
                    console.log('跳过引导');
                }}
            />
        </div>
    );
}

主题

popup 气泡卡片模式下提供两种主题 default 和 primary,通过 theme 属性设置。



Default Tag
代码
import { createSignal } from 'solid-js';
import { Button, Space, Switch, Tag, type UserGuideStepItem, UserGuide } from '@fibrous/semi-ui-solid';

const steps = [
    {
        target: () => document.querySelector('#theme-demo-1'),
        title: '新手引导',
        description: 'Hello ByteDancer!',
        position: 'bottom',
    },
    {
        target: () => document.querySelector('#theme-demo-2'),
        title: 'Switch',
        description: 'This is a Semi Switch',
        position: 'bottom',
    },
    {
        target: () => document.querySelector('#theme-demo-3'),
        title: 'Button',
        description: 'This is a Semi Button',
        position: 'bottom',
    },
] satisfies UserGuideStepItem[];

export default function UserGuideDemo() {
    const [visible, setVisible] = createSignal(false);
    const showDialog = () => {
        setVisible(true);
    };
    return (
        <div>
            <Button onClick={showDialog}>开始引导</Button>
            <br />
            <br />
            <Space>
                <Switch aria-label="示例开关" id="theme-demo-1" defaultChecked />
                <Tag id="theme-demo-2"> Default Tag </Tag>
                <Button id="theme-demo-3">确定</Button>
            </Space>
            <UserGuide
                mode="popup"
                mask
                visible={visible()}
                theme="primary"
                steps={steps}
                onFinish={() => {
                    setVisible(false);
                    console.log('引导完成');
                }}
                onSkip={() => {
                    setVisible(false);
                    console.log('跳过引导');
                }}
            />
        </div>
    );
}

气泡卡片弹出位置

popup 气泡卡片模式下提供 16 种弹出位置,可选值有 top, topLeft, topRight, left, leftTop, leftBottom, right, rightTop, rightBottom, bottom, bottomLeft, bottomRight, leftTopOver, rightTopOver, leftBottomOver, rightBottomOver,还可以通过 showArrow 属性设置是否显示箭头。

代码
import { createSignal } from 'solid-js';
import { Button, type UserGuideStepItem, UserGuide } from '@fibrous/semi-ui-solid';

const steps = [
    {
        target: () => document.querySelector('#position-demo'),
        title: '新手引导',
        description: 'Hello ByteDancer!',
        position: 'top',
    },
    {
        target: () => document.querySelector('#position-demo'),
        title: 'New Position',
        description: 'This is Right Position',
        position: 'right',
    },
    {
        target: () => document.querySelector('#position-demo'),
        title: 'Hide Arrow',
        description: 'We hide the arrow',
        position: 'bottom',
        showArrow: false,
    },
] satisfies UserGuideStepItem[];

export default function UserGuideDemo() {
    const [visible, setVisible] = createSignal(false);
    const showDialog = () => {
        setVisible(true);
    };
    return (
        <div>
            <Button id="position-demo" onClick={showDialog}>
                开始引导
            </Button>
            <UserGuide
                mode="popup"
                mask
                visible={visible()}
                steps={steps}
                onFinish={() => {
                    setVisible(false);
                    console.log('引导完成');
                }}
                onSkip={() => {
                    setVisible(false);
                    console.log('跳过引导');
                }}
            />
        </div>
    );
}

设置高亮区域大小

通过 spotlightPadding 属性设置。



Default Tag
代码
import { createSignal } from 'solid-js';
import { Button, Space, Switch, Tag, type UserGuideStepItem, UserGuide } from '@fibrous/semi-ui-solid';

const steps = [
    {
        target: () => document.querySelector('#padding-demo-1'),
        title: '新手引导',
        description: 'Hello ByteDancer!',
    },
    {
        target: () => document.querySelector('#padding-demo-2'),
        title: 'New Padding',
        description: 'This is 10px padding',
    },
    {
        target: () => document.querySelector('#padding-demo-3'),
        title: 'Change Padding',
        spotlightPadding: 15,
        description: 'We change the Padding to 15px',
    },
] satisfies UserGuideStepItem[];

export default function UserGuideDemo() {
    const [visible, setVisible] = createSignal(false);
    const showDialog = () => {
        setVisible(true);
    };
    return (
        <div>
            <Button onClick={showDialog}>开始引导</Button>
            <br />
            <br />
            <Space>
                <Switch aria-label="示例开关" id="padding-demo-1" defaultChecked />
                <Tag id="padding-demo-2"> Default Tag </Tag>
                <Button id="padding-demo-3">确定</Button>
            </Space>
            <UserGuide
                mode="popup"
                mask
                visible={visible()}
                spotlightPadding={10}
                steps={steps}
                onFinish={() => {
                    setVisible(false);
                    console.log('引导完成');
                }}
                onSkip={() => {
                    setVisible(false);
                    console.log('跳过引导');
                }}
            />
        </div>
    );
}

定制按钮

通过 nextButtonProps 和 prevButtonProps 属性设置按钮的样式。

按钮文本通过 nextButtonProps 与 prevButtonProps 的 children 设置;最后一步的按钮文本由 finishText 决定。



Default Tag
代码
import { createSignal } from 'solid-js';
import { Button, type ButtonProps, Space, Switch, Tag, type UserGuideStepItem, UserGuide } from '@fibrous/semi-ui-solid';

const nextButtonProps = { children: 'Next' } satisfies ButtonProps;
const prevButtonProps = { children: 'Prev', theme: 'borderless' } satisfies ButtonProps;

const steps = [
    {
        target: () => document.querySelector('#button-demo-1'),
        title: '新手引导',
        description: 'Hello ByteDancer!',
    },
    {
        target: () => document.querySelector('#button-demo-2'),
        title: 'New Button Style',
        description: 'Button text is Next',
    },
    {
        target: () => document.querySelector('#button-demo-3'),
        title: 'New finish button text',
        description: 'Button text is I know',
    },
] satisfies UserGuideStepItem[];

export default function UserGuideDemo() {
    const [visible, setVisible] = createSignal(false);
    const showDialog = () => {
        setVisible(true);
    };
    return (
        <div>
            <Button onClick={showDialog}>开始引导</Button>
            <br />
            <br />
            <Space>
                <Switch aria-label="示例开关" id="button-demo-1" defaultChecked />
                <Tag id="button-demo-2"> Default Tag </Tag>
                <Button id="button-demo-3">确定</Button>
            </Space>
            <UserGuide
                mode="popup"
                mask
                visible={visible()}
                nextButtonProps={nextButtonProps}
                prevButtonProps={prevButtonProps}
                finishText="我知道啦!"
                steps={steps}
                onFinish={() => {
                    setVisible(false);
                    console.log('引导完成');
                }}
                onSkip={() => {
                    setVisible(false);
                    console.log('跳过引导');
                }}
            />
        </div>
    );
}

受控

通过 current 属性设置当前引导步骤。

用 current 绑定当前步骤,并在 onChange 中回写;用 visible 绑定显示状态,在 onSkip、onFinish 中关闭引导。



Default Tag
代码
import { createSignal } from 'solid-js';
import { Button, Space, Switch, Tag, type UserGuideStepItem, UserGuide } from '@fibrous/semi-ui-solid';

const steps = [
    {
        target: () => document.querySelector('#controlled-demo-1'),
        title: '新手引导',
        description: 'Hello ByteDancer!',
        position: 'bottom',
    },
    {
        target: () => document.querySelector('#controlled-demo-2'),
        title: 'Switch',
        description: 'This is a Semi Switch',
        position: 'bottom',
    },
    {
        target: () => document.querySelector('#controlled-demo-3'),
        title: 'Button',
        description: 'This is a Semi Button',
        position: 'bottom',
    },
] satisfies UserGuideStepItem[];

export default function UserGuideDemo() {
    const [visible, setVisible] = createSignal(false);
    const [current, setCurrent] = createSignal(0);

    const showDialog = () => {
        setVisible(true);
    };
    return (
        <div>
            <Button onClick={showDialog}>开始引导</Button>
            <br />
            <br />
            <Space>
                <Switch aria-label="示例开关" id="controlled-demo-1" defaultChecked />
                <Tag id="controlled-demo-2"> Default Tag </Tag>
                <Button id="controlled-demo-3">确定</Button>
            </Space>
            <UserGuide
                mode="popup"
                mask
                visible={visible()}
                current={current()}
                steps={steps}
                onChange={(next) => {
                    setCurrent(next);
                }}
                onFinish={() => {
                    setVisible(false);
                    setCurrent(0);
                    console.log('引导完成');
                }}
                onSkip={() => {
                    setVisible(false);
                    setCurrent(0);
                    console.log('跳过引导');
                }}
            />
        </div>
    );
}

弹窗式引导

通过 mode 属性设置为 modal 开启弹窗式引导。

封面使用 renderCover,富文本描述使用 renderDescription,它们只为当前步骤调用。

代码
import { createSignal } from 'solid-js';
import { Button, Image, Typography, type UserGuideStepItem, UserGuide } from '@fibrous/semi-ui-solid';

const steps: UserGuideStepItem[] = [
    { title: '欢迎使用 Semi DSM!', position: 'bottom' },
    {
        title: '高可用的色盘',
        description: '选取主色后,我们的颜色算法会为你生成一套高可用的色盘',
        position: 'bottom',
    },
    { title: '自由定制', description: '开始定制属于你的设计系统吧!', position: 'bottom' },
];

const covers = ['/assets/root-web-sites/sky.jpg', '/assets/root-web-sites/greenleaf.jpg', '/assets/root-web-sites/colorful.jpg'];

export default function UserGuideDemo() {
    const [visible, setVisible] = createSignal(false);
    const showDialog = () => {
        setVisible(true);
    };
    return (
        <div>
            <Button onClick={showDialog}>开始引导</Button>
            <UserGuide
                mode="modal"
                mask
                visible={visible()}
                steps={steps}
                renderCover={({ index }) => <Image width="600px" height="100%" src={covers[index]} />}
                renderDescription={({ index }) =>
                    index === 0 ? (
                        <div>
                            你可以从已发布的主题出发,或者选择<Typography.Text strong>立即创造</Typography.Text>
                            来创造一个新的主题
                        </div>
                    ) : undefined
                }
                onChange={(current) => {
                    console.log('当前引导步骤', current);
                }}
                onNext={() => {
                    console.log('下一步引导');
                }}
                onPrev={() => {
                    console.log('上一步引导');
                }}
                onFinish={() => {
                    setVisible(false);
                    console.log('引导完成');
                }}
                onSkip={() => {
                    setVisible(false);
                    console.log('跳过引导');
                }}
            />
        </div>
    );
}

无遮罩

通过 mask 属性设置为 false 开启无遮罩引导。

代码
import { createSignal } from 'solid-js';
import { Button, type UserGuideStepItem, UserGuide } from '@fibrous/semi-ui-solid';

const steps = [
    {
        target: () => document.querySelector('#mask-demo'),
        title: 'No Mask',
        description: 'Hello ByteDancer!',
    },
] satisfies UserGuideStepItem[];

export default function UserGuideDemo() {
    const [visible, setVisible] = createSignal(false);
    const showDialog = () => {
        setVisible(true);
    };
    return (
        <div>
            <Button id="mask-demo" onClick={showDialog}>
                开始引导
            </Button>
            <UserGuide
                mode="popup"
                mask={false}
                visible={visible()}
                steps={steps}
                onFinish={() => {
                    setVisible(false);
                    console.log('引导完成');
                }}
                onSkip={() => {
                    setVisible(false);
                    console.log('跳过引导');
                }}
            />
        </div>
    );
}

API 参考


Props

属性说明类型默认值
class自定义类名。popup 模式下落在气泡卡片上,modal 模式下落在弹窗的外层元素上。string-
current当前步骤的索引,从 0 开始。设置后由外部控制,配合 onChange 回写;不设置时组件自己保存步骤。number-
finishText最后一步完成按钮的文本;为空时使用语言包中的文案。string-
getPopupContainermodal 模式下弹窗渲染至该 DOM 中;popup 模式下指向 body 以外的元素时不锁定 body 滚动。气泡卡片位于 top layer,不受此属性影响。Type() => HTMLElement-
mask是否显示蒙层,可被步骤的 mask 覆盖。booleantrue
mode引导模式:popup 为气泡卡片,modal 为弹窗。'popup' | 'modal'"popup"
nextButtonProps下一步按钮的属性,可含 class、style 与 onClick;onClick 先于内置处理调用。按钮文本使用 children,最后一步的文本由 finishText 决定。ButtonProps-
onChange步骤改变时的回调,参数为新的索引。Type(current: number) => void-
onFinish点击最后一步的完成按钮时的回调。Type() => void-
onNext点击下一步按钮时的回调,参数为新的索引;最后一步不触发,由 onFinish 处理。Type(current: number) => void-
onPrev点击上一步按钮时的回调,参数为新的索引。Type(current: number) => void-
onSkip点击跳过按钮或按 Esc 时的回调。Type() => void-
position气泡卡片相对于目标元素的位置,可被步骤的 position 覆盖。Position"bottom"
prevButtonProps上一步按钮的属性,可含 class、style 与 onClick;onClick 先于内置处理调用。按钮文本使用 children。ButtonProps-
renderCover渲染当前步骤的封面,参数为 { item, index };渲染为空时该步骤没有封面区域。Type(props: StepRenderProps) => JSX.Element-
renderDescription渲染当前步骤的描述,参数为 { item, index };渲染为空时回落到步骤的 description。Type(props: StepRenderProps) => JSX.Element-
renderTitle渲染当前步骤的标题,参数为 { item, index };渲染为空时回落到步骤的 title。Type(props: StepRenderProps) => JSX.Element-
showPrevButton是否显示上一步按钮,第一步不显示。booleantrue
showSkipButton是否显示跳过按钮,最后一步不显示。booleantrue
spotlightPadding高亮区域的外扩距离,单位为像素,可被步骤的 spotlightPadding 覆盖;都未设置时为 5。number-
steps引导步骤的数据,只放数据;自定义内容由 renderTitle、renderDescription、renderCover 渲染。StepItem[]-
style自定义样式。popup 模式下落在气泡卡片上,modal 模式下落在 .semi-modal 上。JSX.CSSProperties | string-
themepopup 模式的主题,可被步骤的 theme 覆盖。'default' | 'primary'"default"
visible是否显示引导。booleanfalse
zIndexpopup 模式下遮罩的层级,modal 模式下弹窗的层级。气泡卡片位于 top layer,始终在遮罩之上。number1030

UserGuide 没有根元素:popup 模式下 class、style 落在气泡卡片上;modal 模式下 class、style 交给 Modal,落点与 Modal 一致。未声明的属性(包括 ref、aria-* 与原生事件)会被丢弃。对话框的可访问名称只来自当前步骤的 title。

StepItem

steps 的每一项(类型 UserGuideStepItem)只放数据,自定义内容通过 renderTitle、renderDescription、renderCover 按 { item, index }(类型 UserGuideStepRenderProps)渲染。

属性说明类型默认值
class步骤的自定义类名string-
target目标元素,高亮区域会聚焦到这个元素上;函数形式在测量步骤时调用,目标尚未出现时可返回 null 或 undefinedElement | null | (() => Element | null | undefined)-
title步骤标题的文本;renderTitle 渲染出内容时以它为准string-
description步骤描述的文本;renderDescription 渲染出内容时以它为准string-
mask是否显示此步骤的蒙层,会覆盖全局配置boolean-
showArrow是否显示箭头(仅在 mode=popup 时有效)booleantrue
spotlightPadding此步骤高亮区域的内边距,会覆盖全局配置number-
theme此步骤的主题,会覆盖全局配置default | primary-
position此步骤弹出层的位置,会覆盖全局配置Position-

Accessibility

  • 每一步是以步骤 title 命名的对话框,按 Esc 等同点击「跳过」。
  • 两种模式下每一步出现时,焦点都移到主按钮(「下一步」或「完成」)上。popup 模式下 Tab 在卡片内循环,引导关闭时焦点回到打开前的元素;modal 模式的焦点循环与归还由 Modal 管理。

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$transition_duration-userGuide_spotLight200msspotLight 动画持续时间
$transition_function-userGuide_spotLightcubic-bezier(0.4, 0, 0.2, 1)spotLight 过渡曲线
$color-userGuide_popup-text-defaultvar(--semi-color-text-0)气泡卡片文字颜色 - 默认
$color-userGuide_popup-text-primaryvar(--semi-color-tertiary-light-default)气泡卡片文字颜色 - primary
$color-userGuide_modal_content_indicator-bgvar(--semi-color-primary-light-active)弹窗指示器背景颜色
$color-userGuide_modal_content_indicator-bg-activevar(--semi-color-primary)弹窗指示器背景颜色 - 选中态
$width-userGuide_popover-default400px默认气泡卡片宽度
$height-userGuide_popup_content_cover-default200px默认气泡卡片封面高度
$width-userGuide_modal_content_cover-default600px默认弹窗式卡片封面宽度
$height-userGuide_modal_content_cover-default300px默认弹窗式卡片封面高度
$height-userGuide_modal_content_indicator-default30px默认弹窗式卡片指示器整体高度
$width-userGuide_modal_content_indicator_item-default6px默认弹窗式卡片单个指示器宽度
$height-userGuide_modal_content_indicator_item-default6px默认弹窗式卡片单个指示器高度
$font-userGuide_popup_content_title-lineHeight24px气泡卡片标题行高
$font-userGuide_popup_content_description-lineHeight20px气泡卡片详情行高
$font-userGuide_popup_content_indicator-lineHeight20px气泡卡片指示器行高
$font-userGuide_modal_content_title-lineHeight24px弹窗式卡片标题行高
$font-userGuide_modal_content_description-lineHeight20px弹窗式卡片详情行高
$spacing-userGuide_popup_content_body-padding24px气泡卡片内边距
$spacing-userGuide_popup_content_title-marginBottom8px气泡卡片标题底部外边距
$spacing-userGuide_popup_content_description-marginBottom48px气泡卡片详情底部外边距
$spacing-userGuide_popup_content_button-gap12px气泡卡片底部按钮间距
$spacing-userGuide_popup_content_button-marginLeft120px气泡卡片按钮组左边距
$spacing-userGuide_modal_content_title-marginBottom8px弹窗式卡片标题底部外边距
$spacing-userGuide_modal_content_body-padding24px 48px弹窗式卡片body内边距
$spacing-userGuide_modal_content_footer-padding24px弹窗式卡片footer内边距
$spacing-userGuide_popup_content_indicator-gap8px弹窗式片指示器间距
$spacing-userGuide_modal_content_button-gap12px弹窗式卡片底部按钮间距
$radius-userGuide_popup_content_covervar(--semi-border-radius-medium) var(--semi-border-radius-medium) 0 0气泡卡片封面圆角
$radius-userGuide_modal_content_indicatorvar(--semi-border-radius-large)气泡卡片指示器圆角
$font-userGuide_popup_content_title-fontSize$font-size-header-5气泡卡片标题字体大小
$font-userGuide_popup_content_description-fontSize$font-size-regular气泡卡片详情字体大小
$font-userGuide_popup_content_title-fontWeight$font-weight-bold气泡卡片标题字重
$font-userGuide_modal_content_title-fontSize$font-size-header-5弹窗式卡片标题字体大小
$font-userGuide_modal_content_description-fontSize$font-size-regular弹窗式卡详情字体大小
$font-userGuide_modal_content_title-fontWeight$font-weight-bold弹窗式卡片标题字重