Demos
How to import
import { UserGuide } from '@fibrous/semi-ui-solid';
Basic Usage
Code
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>
);
}Theme
popup bubble card mode provides two themes default and primary, set by the theme property.
Code
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 position
popup bubble card mode provides 16 positions, optional values are top, topLeft, topRight, left, leftTop, leftBottom, right, rightTop, rightBottom, bottom, bottomLeft, bottomRight, leftTopOver, rightTopOver, leftBottomOver, rightBottomOver, and can be set by the showArrow property to display the arrow.
Code
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>
);
}Set the size of the highlight area
Set by the spotlightPadding property.
Code
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>
);
}Customize the button
Set by the nextButtonProps and prevButtonProps properties.
Set the button text with children in nextButtonProps and prevButtonProps. The text of the button on the last step is set by finishText.
Code
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>
);
}Controlled
Set by the current property.
Bind the current step with current and write it back in onChange; bind the visibility with visible and close the guide in onSkip and onFinish.
Code
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>
);
}Modal guide
Set by the mode property.
Use renderCover for the cover and renderDescription for rich-text descriptions; they are called for the current step only.
Code
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>
);
}No mask
Set by the mask property.
Code
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 Reference
Props
| Property | Description | Type | Default |
|---|---|---|---|
| class | Custom class name. In popup mode it lands on the popover card; in modal mode it lands on the outer element of the dialog. | string | - |
| current | Index of the current step, starting at 0. When set, the parent controls the step and writes it back with onChange; when not set, the component keeps the step itself. | number | - |
| finishText | Text of the finish button on the last step. When empty, the text from the locale pack is used. | string | - |
| getPopupContainer | In modal mode the dialog is rendered into this DOM node. In popup mode body scrolling is not locked when it points to an element other than body. The popover card sits in the top layer and is not affected. | Type() => HTMLElement | - |
| mask | Whether to show the mask. A step's own mask overrides it. | boolean | true |
| mode | Guide mode: popup for a bubble card, modal for a dialog. | 'popup' | 'modal' | "popup" |
| nextButtonProps | Props of the next button, which may include class, style and onClick. onClick runs before the built-in handling. The button text is children; the text of the last step comes from finishText. | ButtonProps | - |
| onChange | Callback when the step changes, with the new index. | Type(current: number) => void | - |
| onFinish | Callback when the finish button on the last step is clicked. | Type() => void | - |
| onNext | Callback when the next button is clicked, with the new index. It is not called on the last step, where onFinish handles the click instead. | Type(current: number) => void | - |
| onPrev | Callback when the previous button is clicked, with the new index. | Type(current: number) => void | - |
| onSkip | Callback when the skip button is clicked or Escape is pressed. | Type() => void | - |
| position | Position of the bubble card relative to the target element. A step's own position overrides it. | Position | "bottom" |
| prevButtonProps | Props of the previous button, which may include class, style and onClick. onClick runs before the built-in handling. The button text is children. | ButtonProps | - |
| renderCover | Renders the cover of the current step, with { item, index }. A step it renders nothing for has no cover area. | Type(props: StepRenderProps) => JSX.Element | - |
| renderDescription | Renders the description of the current step, with { item, index }. Empty content falls back to the description of the step. | Type(props: StepRenderProps) => JSX.Element | - |
| renderTitle | Renders the title of the current step, with { item, index }. Empty content falls back to the title of the step. | Type(props: StepRenderProps) => JSX.Element | - |
| showPrevButton | Whether to show the previous button. It is never shown on the first step. | boolean | true |
| showSkipButton | Whether to show the skip button. It is never shown on the last step. | boolean | true |
| spotlightPadding | Extra space in pixels around the highlighted area. A step's own spotlightPadding overrides it; 5 when neither is set. | number | - |
| steps | Data of the guide steps, data only. Custom content is rendered by renderTitle, renderDescription and renderCover. | StepItem[] | - |
| style | Custom style. In popup mode it lands on the popover card; in modal mode it lands on .semi-modal. | JSX.CSSProperties | string | - |
| theme | Theme of the popup mode. A step's own theme overrides it. | 'default' | 'primary' | "default" |
| visible | Whether the guide is shown. | boolean | false |
| zIndex | The z-index of the mask in popup mode and of the dialog in modal mode. The popover card sits in the top layer, always above the mask. | number | 1030 |
UserGuide has no root element. In popup mode class and style land on the popover card; in modal mode they are handed to Modal and land where Modal puts them. Undeclared attributes such as ref, aria-* and native events are dropped. The dialog's accessible name comes only from the title of the current step.
StepItem
Each item of steps (type UserGuideStepItem) holds data only. Custom content goes through renderTitle, renderDescription and renderCover, called with { item, index } (type UserGuideStepRenderProps).
| Property | Description | Type | Default |
|---|---|---|---|
| class | Custom class name of the step | string | - |
| target | The target element, the highlight area will focus on this element. The function form is called when the step is measured and may return null or undefined while the target is absent | Element | null | (() => Element | null | undefined) | - |
| title | Text of the title; renderTitle wins when it renders content | string | - |
| description | Text of the description; renderDescription wins when it renders content | string | - |
| mask | Whether to display the mask of this step, it will override the global configuration | boolean | - |
| showArrow | Whether to display the arrow (only valid when mode=popup) | boolean | true |
| spotlightPadding | The inner padding of the highlight area of this step, it will override the global configuration | number | - |
| theme | The theme of this step, it will override the global configuration | default | primary | - |
| position | The position of the pop-up layer of this step, it will override the global configuration | Position | - |
Accessibility
- Each step is a dialog named by the step
title; Escape works like clicking Skip. - In both modes, focus moves to the primary button (Next or Finish) when each step appears. In popup mode Tab cycles inside the card, and focus returns to the element focused before the guide when it closes; in modal mode, Modal manages the focus cycle and its return.
Design Tokens
| Sass variable | Value | Usage |
|---|---|---|
| $transition_duration-userGuide_spotLight | 200ms | spotLight 动画持续时间 |
| $transition_function-userGuide_spotLight | cubic-bezier(0.4, 0, 0.2, 1) | spotLight 过渡曲线 |
| $color-userGuide_popup-text-default | var(--semi-color-text-0) | 气泡卡片文字颜色 - 默认 |
| $color-userGuide_popup-text-primary | var(--semi-color-tertiary-light-default) | 气泡卡片文字颜色 - primary |
| $color-userGuide_modal_content_indicator-bg | var(--semi-color-primary-light-active) | 弹窗指示器背景颜色 |
| $color-userGuide_modal_content_indicator-bg-active | var(--semi-color-primary) | 弹窗指示器背景颜色 - 选中态 |
| $width-userGuide_popover-default | 400px | 默认气泡卡片宽度 |
| $height-userGuide_popup_content_cover-default | 200px | 默认气泡卡片封面高度 |
| $width-userGuide_modal_content_cover-default | 600px | 默认弹窗式卡片封面宽度 |
| $height-userGuide_modal_content_cover-default | 300px | 默认弹窗式卡片封面高度 |
| $height-userGuide_modal_content_indicator-default | 30px | 默认弹窗式卡片指示器整体高度 |
| $width-userGuide_modal_content_indicator_item-default | 6px | 默认弹窗式卡片单个指示器宽度 |
| $height-userGuide_modal_content_indicator_item-default | 6px | 默认弹窗式卡片单个指示器高度 |
| $font-userGuide_popup_content_title-lineHeight | 24px | 气泡卡片标题行高 |
| $font-userGuide_popup_content_description-lineHeight | 20px | 气泡卡片详情行高 |
| $font-userGuide_popup_content_indicator-lineHeight | 20px | 气泡卡片指示器行高 |
| $font-userGuide_modal_content_title-lineHeight | 24px | 弹窗式卡片标题行高 |
| $font-userGuide_modal_content_description-lineHeight | 20px | 弹窗式卡片详情行高 |
| $spacing-userGuide_popup_content_body-padding | 24px | 气泡卡片内边距 |
| $spacing-userGuide_popup_content_title-marginBottom | 8px | 气泡卡片标题底部外边距 |
| $spacing-userGuide_popup_content_description-marginBottom | 48px | 气泡卡片详情底部外边距 |
| $spacing-userGuide_popup_content_button-gap | 12px | 气泡卡片底部按钮间距 |
| $spacing-userGuide_popup_content_button-marginLeft | 120px | 气泡卡片按钮组左边距 |
| $spacing-userGuide_modal_content_title-marginBottom | 8px | 弹窗式卡片标题底部外边距 |
| $spacing-userGuide_modal_content_body-padding | 24px 48px | 弹窗式卡片body内边距 |
| $spacing-userGuide_modal_content_footer-padding | 24px | 弹窗式卡片footer内边距 |
| $spacing-userGuide_popup_content_indicator-gap | 8px | 弹窗式片指示器间距 |
| $spacing-userGuide_modal_content_button-gap | 12px | 弹窗式卡片底部按钮间距 |
| $radius-userGuide_popup_content_cover | var(--semi-border-radius-medium) var(--semi-border-radius-medium) 0 0 | 气泡卡片封面圆角 |
| $radius-userGuide_modal_content_indicator | var(--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 | 弹窗式卡片标题字重 |