Usage Scenarios
The Sidebar is mainly used in AI scenarios for information display and feature configuration, including MCP configuration, reference sources, code preview, rich text preview and editing, etc.
Code Examples
How to import
<script lang="ts">
import { Annotation, MCPConfigure, Sidebar } from '@fibrae/semi-ai-svelte';
const { Container, CodeContent, CodeItem, FileContent, FileItem } = Sidebar;
const { AnnotationContent } = Annotation;
</script>
Basic Container
The basic container of the Sidebar is the base container for MCP configuration, References and Sidebar.
bind:visiblecontrols show/hide. Clicking the close button, or pressing Esc whilecloseOnEscistrue, sets it tofalsefirst and then callsonCanceltitlesets the title, plain text or a snippet; use theheadersnippet to customize the whole headermotionsets whether there is an expand animationresizablesets whether the width can be resized; whentrue, useminWidthandmaxWidthto set the minimum and maximum width
Code
<script lang="ts">
import { Sidebar } from '@fibrae/semi-ai-svelte';
import { Button, Switch } from '@fibrae/semi-ui-svelte';
const { Container } = Sidebar;
const containerStyle = 'display: flex; height: 500px; border: 1px solid var(--semi-color-border); border-radius: 8px; overflow: hidden; box-sizing: border-box';
let visible = $state(true);
let motion = $state(true);
let resizable = $state(true);
</script>
<div style={containerStyle}>
<div style="flex-grow: 1; flex-shrink: 1; padding: 20px">
<div>
<Button aria-pressed={visible} onclick={() => (visible = !visible)}>显示容器</Button>
<div style="display: flex; align-items: center; margin-top: 10px">
<span id="sidebar-container-motion">是否有动画</span>
<Switch aria-labelledby="sidebar-container-motion" bind:checked={motion} />
</div>
<div style="display: flex; align-items: center; margin-top: 10px">
<span id="sidebar-container-resizable">是否可伸缩</span>
<Switch aria-labelledby="sidebar-container-resizable" bind:checked={resizable} />
</div>
</div>
</div>
<Container bind:visible maxWidth="60%" minWidth={250} {motion} {resizable} style={resizable ? undefined : 'width: 200px'} title="基础容器示例" />
</div>MCP Configuration
Use MCPConfigure to display, enable/disable, configure and search MCP tools.
- Use
bind:optionsandbind:customOptionsto bind the built-in and custom MCP tools; switching a tool updates the binding first, then callsonStatusChange - Use
bind:visibleto control show and hide, andonCancelto listen to user close behavior - Use
onStatusChangeto customize how enabling/disabling an MCP tool is handled - Use
onAddClickto handle the add button of the custom MCP page - Use
onConfigureClickto listen to configuring a built-in MCP tool, andonEditClickto listen to editing a custom one - Use the
iconanddescsnippets for the icon and description of an item, anditemto replace the whole row; the snippet parameter is{ item, index, custom }
For other APIs of MCPConfigure, see MCPConfigure.
The type of MCP tool item is as follows:
interface MCPConfigureOption {
icon?: string; // image URL; use the icon snippet for other content
label?: string;
value: string; // required, identifies the tool and must be unique
desc?: string; // plain text; use the desc snippet for other content
active?: boolean;
disabled?: boolean;
configure?: boolean; // whether to show configure button
}
Usage example:
Code
<script lang="ts">
import { MCPConfigure, type MCPConfigureOption } from '@fibrae/semi-ai-svelte';
import { IconFigma, IconSemiLogo } from '@fibrae/semi-icons-svelte';
import { Button, Form, FormInput, FormTextArea, Modal, type FormApi } from '@fibrae/semi-ui-svelte';
interface McpFormValues {
name: string;
src: string;
desc: string;
}
const containerStyle = 'display: flex; height: 500px; border: 1px solid var(--semi-color-border); border-radius: 8px; overflow: hidden; box-sizing: border-box';
// icon only takes an image URL, so the built-in options get their icons from the icon snippet
const builtinIcons: Record<string, typeof IconFigma> = {
'Semi mcp': IconSemiLogo,
figma: IconFigma,
};
const srcRules = [
{ required: true, message: '请输入 MCP 图标 URL' },
{
validator: (_rule: unknown, value: string) => /^(?:https?:\/\/)?(?:www\.)?[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+(?::\d+)?(?:\/[^\s]*)?$/i.test(value),
message: '请输入有效的 MCP 图标 URL',
},
];
let index = 1;
let visible = $state(true);
let options = $state.raw<MCPConfigureOption[]>([
{
value: 'Semi mcp',
label: 'Semi',
configure: true,
desc: '支持 Semi 的文档、源码搜索,辅助开发',
},
{
value: 'figma',
label: 'Figma',
desc: 'Figma MCP Server 连接Figma与AI开发工具的功能。它通过标准化的模型上下文协MCP),将组件、变量等设计数据和上下文暴露给AI,从而实现从设计稿到代码的智能生成,显著提升开发效率。',
},
]);
let customOptions = $state.raw<MCPConfigureOption[]>([]);
let modalVisible = $state(false);
let formApi: FormApi<McpFormValues> | undefined;
const handleOk = () => {
formApi
?.validate()
.then((values) => {
customOptions = [
...customOptions,
{
label: values.name,
icon: values.src,
value: `mcp-${index++}`,
desc: values.desc,
},
];
modalVisible = false;
})
.catch((errors) => {
console.log('errors', errors);
});
};
const onConfigureClick = (_event: MouseEvent, option: MCPConfigureOption) => {
console.log('configure click', option);
};
const onEditClick = (_event: MouseEvent, option: MCPConfigureOption) => {
console.log('edit click', option);
};
</script>
<div style={containerStyle}>
<div style="flex-grow: 1; flex-shrink: 1; padding: 20px">
<Button aria-pressed={visible} onclick={() => (visible = !visible)}>MCP 配置</Button>
</div>
<MCPConfigure bind:customOptions bind:options bind:visible onAddClick={() => (modalVisible = true)} {onConfigureClick} {onEditClick} resizable={false} style="width: 500px">
{#snippet icon({ item })}
{#if item.icon}
<img alt="" src={item.icon} style="width: 100%; height: 100%" />
{:else}
{@const Icon = builtinIcons[item.value]}
<Icon />
{/if}
{/snippet}
</MCPConfigure>
<Modal bind:visible={modalVisible} closeOnEsc onOk={handleOk} title="自定义 MCP">
<Form getFormApi={(api) => (formApi = api)} layout="vertical" onValueChange={(values) => console.log(values)}>
<FormInput field="name" label="MCP 名称" rules={[{ required: true, message: '请输入 MCP 名称' }]} style="width: 100%" />
<FormInput field="src" initValue="/assets/placeholder/avatar-a.svg" label="MCP 图标 URL" rules={srcRules} style="width: 100%" />
<FormTextArea field="desc" label="MCP 介绍" rules={[{ required: true, message: '请输入 MCP 介绍' }]} style="width: 100%" />
</Form>
</Modal>
</div>References
Use the Annotation component to manage the display of reference sources.
bind:activeKeymanages the currently expanded iteminfosets the reference content listonClickis called when a reference is clicked, with the event and its item- Use the
infoHeadersnippet for a group header anditemto replace an item;headeris the whole container header
Code
<script lang="ts">
import { Annotation, type AnnotationInfo, type AnnotationItem } from '@fibrae/semi-ai-svelte';
import { Button } from '@fibrae/semi-ui-svelte';
const containerStyle = 'display: flex; height: 500px; border: 1px solid var(--semi-color-border); border-radius: 8px; overflow: hidden; box-sizing: border-box';
const logo = '/assets/placeholder/avatar-a.svg';
const img = '/assets/root-web-sites/abstract.jpg';
const defaultInfoList: AnnotationInfo[] = [
{
header: 'Semi design introduction',
key: '1',
annotations: [
{
order: 1,
type: 'video',
duration: 4432,
title: 'Semi Design is a design system designed',
url: 'https://semi.design/',
logo,
siteName: 'Semi Design',
detail: ' As a comprehensive, easy-to-use, and high-quality modern enterprise-level application UI solution',
img,
},
{
order: 2,
title: 'Quick start',
type: 'video',
duration: 56,
url: 'https://semi.design/',
detail: ' As a comprehensive, easy-to-use, and high-quality modern enterprise-level application UI solution',
logo,
siteName: 'Semi Design',
img,
},
{
order: 3,
title: 'Use components in a modular way',
url: 'https://semi.design/',
detail: `Semi provides esm format dist, and the css of the component is only imported by the corresponding js.
When used in Webpack, Rspack, create-react-app or Vite projects, there is no need to configure any compilation items.`,
logo,
siteName: 'Semi Design',
img,
},
],
},
{
header: 'Design resource',
key: '2',
annotations: [
{
order: 2,
title: 'Semi Design resource',
url: 'https://semi.design/',
detail: 'Semi Design provides a wealth of design resources to help designers and developers collaborate efficiently. Whether you are a community user or a ByteDance internal designer, you can find UI Kit resource and Figma plug-ins that suit you here.',
logo,
siteName: 'Semi Design resource',
img,
},
],
},
];
const defaultSize = { width: 420 };
let visible = $state(true);
let activeKey = $state<string | string[]>('1');
const onChange = (key: string | string[]) => {
console.log('change', key);
};
const onClick = (event: MouseEvent, item: AnnotationItem) => {
console.log('click', event, item);
};
</script>
<div style={containerStyle}>
<div style="flex-grow: 1; flex-shrink: 1; padding: 20px">
<Button aria-pressed={visible} onclick={() => (visible = !visible)}>参考来源</Button>
</div>
<Annotation bind:activeKey bind:visible {defaultSize} info={defaultInfoList} {onChange} {onClick} />
</div>Code Display
You can use CodeItem in Sidebar to display code. CodeItem is built based on JsonViewer and CodeHighlight.
content: content stringisJson: whether the content is jsonlanguage: programming language name, same aslanguageinCodeHighlight; register it withCodeHighlight.registerfirstjsonViewerProps: configure other JsonViewer propscodeHighlightProps: configure other CodeHighlight props
The value of JsonViewer in jsonViewerProps defaults to content, and both jsonViewerProps and codeHighlightProps accept class and style. The custom rendering and search button of JsonViewer are available through the customRender and searchButton snippets of CodeItem.
Code
<script lang="ts">
import { Sidebar } from '@fibrae/semi-ai-svelte';
import { Radio, RadioGroup, type RadioValue } from '@fibrae/semi-ui-svelte';
const { CodeItem } = Sidebar;
const code = `.semi-animation-react-demo-auto {
button {
height: 50px;
border: 0;
cursor: pointer !important;
background: #777;
color: white;
outline: none;
-webkit-appearance: none;
}
button:hover {
background: #878787;
}
.auto-main {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr;
background: #575757;
}
.content {
grid-column: span 3;
}
.item {
background: indianred;
width: 100%;
overflow: hidden;
color: white;
}
.item p {
margin: 0;
padding: 10px;
}
}
`;
const json = `{
"axisX": {
"title": {
"visible": false,
"position": "center"
},
"label": {
"visible": true,
"style": {
"fontSize": 12,
"fontWeight": 400,
"lineHeight": 16,
"fontFamily": [
"Inter"
],
"fill": "rgba(0, 0, 0, 0.47843137254901963)"
},
"space": 12
}
}
}`;
const codeProps = {
content: code,
language: 'css',
};
const jsonProps = {
isJson: true,
content: json,
};
let type = $state<RadioValue | undefined>('json');
const itemProps = $derived(type === 'json' ? jsonProps : codeProps);
</script>
<RadioGroup bind:value={type} buttonSize="middle" style="margin-bottom: 10px" type="button">
<Radio value="json">JSON</Radio>
<Radio value="css">CSS</Radio>
</RadioGroup>
<div style="height: 200px; overflow: auto">
<CodeItem {...itemProps} />
</div>Code List
You can use CodeContent in Sidebar to display a list of code blocks.
- Use
bind:activeKeyto manage the expanded panels - Use
onExpandto listen to the expand button in a panel header; the button is shown only when it is set - The
customRenderandsearchButtonsnippets are forwarded to the JsonViewer of every JSON panel
Code
<script lang="ts">
import { Sidebar } from '@fibrae/semi-ai-svelte';
const { CodeContent } = Sidebar;
const defaultCodes = [
{
name: 'Component.js',
key: 'code1',
language: 'javascript',
content: `import React from 'react';
import { AutoComplete } from '@fibrae/semi-ui-react';
import { IconSearch } from '@fibrae/semi-icons-react';
() => {
const [stringData, setStringData] = useState([]);
const [value, setValue] = useState('');
const handleStringSearch = (value) => {
let result;
if (value) {
result = ['gmail.com', '163.com', 'qq.com'].map(domain => value+domain);
} else {
result = [];
}
setStringData(result);
};
const handleChange = (value) => {
console.log('onChange', value);
setValue(value);
};
return (
<AutoComplete
data={stringData}
value={value}
showClear
prefix={<IconSearch />}
placeholder="搜索... "
onSearch={handleStringSearch}
onChange={handleChange}
style={{ width: 200 }}
/>
);
};
`,
},
{
name: 'Style.css',
key: 'code2',
language: 'css',
content: `.semi-animation-react-demo-auto {
button {
height: 50px;
border: 0;
cursor: pointer !important;
background: #777;
color: white;
outline: none;
-webkit-appearance: none;
}
button:hover {
background: #878787;
}
.auto-main {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr;
background: #575757;
}
.content {
grid-column: span 3;
}
.item {
background: indianred;
width: 100%;
overflow: hidden;
color: white;
}
.item p {
margin: 0;
padding: 10px;
}
}
`,
},
{
name: 'Chart.json',
key: 'code3',
isJson: true,
language: 'html',
content: `{
"axisX": {
"title": {
"visible": false,
"position": "center"
},
"label": {
"visible": true,
"style": {
"fontSize": 12,
"fontWeight": 400,
"lineHeight": 16,
"fontFamily": [
"Inter"
],
"fill": "rgba(0, 0, 0, 0.47843137254901963)"
},
"space": 12
},
"domainLine": {
"visible": true,
"style": {
"lineWidth": 1,
"stroke": "rgba(0, 0, 0, 0.12156862745098039)",
"lineDash": []
}
},
"tick": {
"visible": false,
"style": {
"lineWidth": 1,
"stroke": "rgba(255, 255, 255, 0)"
}
},
"subTick": {
"visible": false
},
"grid": {
"visible": false
},
"subGrid": {
"visible": false
}
}
}`,
},
];
let activeKey = $state<string | string[]>('code1');
</script>
<div class="semi-sidebar-main">
<CodeContent bind:activeKey codes={defaultCodes} style="width: 600px" />
</div>Rich Text Editor
You can use FileItem in Sidebar to view and edit rich text content. FileItem is implemented based on tiptap.
content: rich text as HTML. A change after mount is synced to the editor; it is not applied when it equals what the editor already holds, so typing and the cursor are not interrupted. The sync does not callonContentChangeeditable: whether the content is editable; a change after mount is synced to the editorimgUploadProps: configure image upload endpoint and how to set the src after upload. Useactionto set upload url andgetUploadImageSrc(response)to return the uploaded image url from the upload response (when omitted, a string response is used as the url, otherwiseresponse.src), which will be used as src of img node in rich text; theoncallbacks of Upload (such asonSuccess) can be passed here too. The toolbar shows the image button only whenactionorcustomRequestis setextensionsandimgUploadPropsare read only when the editor is created; use{#key}to remount it with new valuesonContentChangeis called after an edit, with the content as HTML
Code
<script lang="ts">
import { Sidebar, type ImageUploadNodeOptions } from '@fibrae/semi-ai-svelte';
import { Button } from '@fibrae/semi-ui-svelte';
const { FileItem } = Sidebar;
const content = `<h2>
Semi Design 介绍
</h2>
<p>
Semi Design 是由 <strong>抖音前端团队</strong>和MED产品设计团队设计、开发并维护的<em>设计系统</em>。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户,详情见https://semi.design/zh-CN/start/introduction。Semi Design的特点包括:
</p>
<ul>
<li>
设计简洁、现代化。
</li>
<li>
提供主题方案,可深度样式定制。
</li>
</ul>
<p>
国际化,覆盖了简/繁体中文、英语、日语、韩语、葡萄牙语等20+种语言,日期时间组件提供全球时区支持,全部组件可自动适配阿拉伯文RTL布局。
</p>
<pre><code class="language-css">body {
display: none;
}</code></pre>
<p>
采用 Foundation 和 Adapter 跨框架技术方案,方便扩展。
</p>
<blockquote>
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
<br />
— Semi Design
</blockquote>`;
const imgUploadProps: ImageUploadNodeOptions = {
action: 'https://api.semi.design/upload',
// response is the action's result; real code reads the uploaded image URL from it
getUploadImageSrc: () => '/assets/placeholder/avatar-a.svg',
};
let editable = $state(true);
</script>
<Button aria-pressed={editable} onclick={() => (editable = !editable)}>可编辑</Button>
<br />
<br />
<FileItem {content} {editable} {imgUploadProps} name="Semi Design 介绍" style="border: 1px solid var(--semi-color-border); padding: 12px" />Rich Text List
You can use FileContent in Sidebar to display a list of rich text documents.
- Use
bind:activeKeyto manage the expanded panels - Use
onExpandto listen to the expand button in a panel header; the button is shown only when it is set
Code
<script lang="ts">
import { Sidebar } from '@fibrae/semi-ai-svelte';
const { FileContent } = Sidebar;
const intro = `<h2>
Semi Design 介绍
</h2>
<p>
Semi Design 是由 <strong>抖音前端团队</strong>和MED产品设计团队设计、开发并维护的<em>设计系统</em>。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户,详情见https://semi.design/zh-CN/start/introduction。Semi Design的特点包括:
</p>
<ul>
<li>
设计简洁、现代化。
</li>
<li>
提供主题方案,可深度样式定制。
</li>
</ul>
<p>
国际化,覆盖了简/繁体中文、英语、日语、韩语、葡萄牙语等20+种语言,日期时间组件提供全球时区支持,全部组件可自动适配阿拉伯文RTL布局。
</p>
<pre><code class="language-css">body {
display: none;
}</code></pre>
<p>
采用 Foundation 和 Adapter 跨框架技术方案,方便扩展。
</p>
<blockquote>
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
<br />
— Semi Design
</blockquote>
`;
const defaultFiles = [
{
key: 'file1',
name: 'Semi Design 介绍',
content: `\n${intro}${intro}${intro}`,
},
{
key: 'file2',
name: 'Semi Design DSM',
content: `<h2>
Semi Design DSM
</h2>
<p>Semi DSM is a design system management tool provided by Semi Design. It supports global and component-level style customization and keeps synchronization between Figma and front-end code. Suitable for teams of all sizes. Whether you need to simplify workflow, improve team collaboration, or increase productivity, we have features suitable for you.</p>
`,
},
{
key: 'file3',
name: 'React v19 Adaptation',
content: `<h2>
React v19 Adaptation
</h2>
<p>Since the release of React v19, React has introduced numerous underlying mechanism and API changes, including upgrades and adjustments to the render mechanism, ref, context, TypeScript types, and related deprecated APIs. To ensure that the Semi Design component library is smoothly compatible with both React v19 and lower versions, we provide the original component package @fibrae/semi-ui-react for React versions below v19, as well as a new package @fibrae/semi-ui-19 specifically adapted for React v19, so users can choose as needed. This guide will help you understand how to install, use, and the precautions to take.
Installation & Usage
If your project is using React v19, please use @fibrae/semi-ui-19. For React versions below v19, continue using @fibrae/semi-ui-react as before.</p>
`,
},
];
let activeKey = $state<string | string[]>('file1');
</script>
<div class="semi-sidebar-main">
<FileContent bind:activeKey files={defaultFiles} style="width: 600px" />
</div>Sidebar
The Sidebar has a main view (mode is main) and detail views (mode is code, file and other custom values).
In the main view you can configure the title through title, decide whether to show close button through showClose, and configure the top button group through options (each item is { name, key }); bind:activeKey binds the selected item. Use the optionIcon snippet (parameter { item, index }) for the button icon, or optionItem to replace the whole button, whose onClick parameter selects the item.
For content rendering in the main view, use the mainContent snippet (parameter { activeKey }) and render by the selected item. You can use AnnotationContent in Annotation to render references, FileContent in Sidebar to render rich text, and CodeContent to render code.
For detail views, if the content is rich text or code, set mode to code or file and provide detailContent to display the content with built-in code display component / rich text editor; a new key in detailContent remounts it. If you want to customize how the detail view is rendered, use the detailBody snippet (parameter { mode }), and customize the detail header through the detailHeader snippet (parameter { mode, detailContent }); with empty content, both fall back to the built-in ones. Clicking the back button of the detail header calls onBackWard.
Code
<script lang="ts">
import { Annotation, Sidebar, type AnnotationInfo, type ImageUploadNodeOptions, type SideBarDetailContent } from '@fibrae/semi-ai-svelte';
import { IconBriefStroked, IconCodeStroked, IconModalStroked, IconSearch } from '@fibrae/semi-icons-svelte';
const { FileContent, CodeContent } = Sidebar;
const { AnnotationContent } = Annotation;
const containerStyle = 'display: flex; height: 800px; border: 1px solid var(--semi-color-border); border-radius: 8px; overflow: hidden; box-sizing: border-box';
const optionList = [
{
name: '查看搜索',
key: 'searchResult',
},
{
name: '文件预览',
key: 'filePreview',
},
{
name: '代码预览',
key: 'codePreview',
},
{
name: '浏览器',
key: 'network',
},
];
const optionIcons: Record<string, typeof IconSearch> = {
searchResult: IconSearch,
filePreview: IconBriefStroked,
codePreview: IconCodeStroked,
network: IconModalStroked,
};
const defaultCodes = [
{
name: 'Component.js',
key: 'code1',
language: 'javascript',
content: `import React from 'react';
import { AutoComplete } from '@fibrae/semi-ui-react';
import { IconSearch } from '@fibrae/semi-icons-react';
() => {
const [stringData, setStringData] = useState([]);
const [value, setValue] = useState('');
const handleStringSearch = (value) => {
let result;
if (value) {
result = ['gmail.com', '163.com', 'qq.com'].map(domain => value+domain);
} else {
result = [];
}
setStringData(result);
};
const handleChange = (value) => {
console.log('onChange', value);
setValue(value);
};
return (
<AutoComplete
data={stringData}
value={value}
showClear
prefix={<IconSearch />}
placeholder="搜索... "
onSearch={handleStringSearch}
onChange={handleChange}
style={{ width: 200 }}
/>
);
};
`,
},
{
name: 'Style.css',
key: 'code2',
language: 'css',
content: `.semi-animation-react-demo-auto {
button {
height: 50px;
border: 0;
cursor: pointer !important;
background: #777;
color: white;
outline: none;
-webkit-appearance: none;
}
button:hover {
background: #878787;
}
.auto-main {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr;
background: #575757;
}
.content {
grid-column: span 3;
}
.item {
background: indianred;
width: 100%;
overflow: hidden;
color: white;
}
.item p {
margin: 0;
padding: 10px;
}
}
`,
},
{
name: 'Chart.json',
key: 'code3',
isJson: true,
language: 'html',
content: `{
"axisX": {
"title": {
"visible": false,
"position": "center"
},
"label": {
"visible": true,
"style": {
"fontSize": 12,
"fontWeight": 400,
"lineHeight": 16,
"fontFamily": [
"Inter"
],
"fill": "rgba(0, 0, 0, 0.47843137254901963)"
},
"space": 12
},
"domainLine": {
"visible": true,
"style": {
"lineWidth": 1,
"stroke": "rgba(0, 0, 0, 0.12156862745098039)",
"lineDash": []
}
},
"tick": {
"visible": false,
"style": {
"lineWidth": 1,
"stroke": "rgba(255, 255, 255, 0)"
}
},
"subTick": {
"visible": false
},
"grid": {
"visible": false
},
"subGrid": {
"visible": false
}
}
}`,
},
];
const intro = `<h2>
Semi Design 介绍
</h2>
<p>
Semi Design 是由 <strong>抖音前端团队</strong>和MED产品设计团队设计、开发并维护的<em>设计系统</em>。作为一个全面、易用、优质的现代应用UI解决方案,Semi Design从字节跳动各业务线的复杂场景中提炼而来,目前已经支撑了近千个平台产品,服务了内外部超过10万用户,详情见https://semi.design/zh-CN/start/introduction。Semi Design的特点包括:
</p>
<ul>
<li>
设计简洁、现代化。
</li>
<li>
提供主题方案,可深度样式定制。
</li>
</ul>
<p>
国际化,覆盖了简/繁体中文、英语、日语、韩语、葡萄牙语等20+种语言,日期时间组件提供全球时区支持,全部组件可自动适配阿拉伯文RTL布局。
</p>
<pre><code class="language-css">body {
display: none;
}</code></pre>
<p>
采用 Foundation 和 Adapter 跨框架技术方案,方便扩展。
</p>
<blockquote>
Semi Design 是由抖音前端团队和MED产品设计团队设计、开发并维护的设计系统
<br />
— Semi Design
</blockquote>
`;
const defaultFiles = [
{
key: 'file1',
name: 'Semi Design 介绍',
content: `\n${intro}${intro}${intro}`,
},
{
key: 'file2',
name: 'Semi Design DSM',
content: `<h2>
Semi Design DSM
</h2>
<p>Semi DSM is a design system management tool provided by Semi Design. It supports global and component-level style customization and keeps synchronization between Figma and front-end code. Suitable for teams of all sizes. Whether you need to simplify workflow, improve team collaboration, or increase productivity, we have features suitable for you.</p>
`,
},
{
key: 'file3',
name: 'React v19 Adaptation',
content: `<h2>
React v19 Adaptation
</h2>
<p>Since the release of React v19, React has introduced numerous underlying mechanism and API changes, including upgrades and adjustments to the render mechanism, ref, context, TypeScript types, and related deprecated APIs. To ensure that the Semi Design component library is smoothly compatible with both React v19 and lower versions, we provide the original component package @fibrae/semi-ui-react for React versions below v19, as well as a new package @fibrae/semi-ui-19 specifically adapted for React v19, so users can choose as needed. This guide will help you understand how to install, use, and the precautions to take.
Installation & Usage
If your project is using React v19, please use @fibrae/semi-ui-19. For React versions below v19, continue using @fibrae/semi-ui-react as before.</p>
`,
},
];
const logo = '/assets/placeholder/avatar-a.svg';
const img = '/assets/root-web-sites/abstract.jpg';
const defaultInfoList: AnnotationInfo[] = [
{
header: 'Semi design introduction',
key: '1',
annotations: [
{
order: 1,
type: 'video',
duration: 4432,
title: 'Semi Design is a design system designed',
url: 'https://semi.design/',
logo,
siteName: 'Semi Design',
detail: ' As a comprehensive, easy-to-use, and high-quality modern enterprise-level application UI solution',
img,
},
{
order: 2,
title: 'Quick start',
type: 'video',
duration: 56,
url: 'https://semi.design/',
detail: ' As a comprehensive, easy-to-use, and high-quality modern enterprise-level application UI solution',
logo,
siteName: 'Semi Design',
img,
},
{
order: 3,
title: 'Use components in a modular way',
url: 'https://semi.design/',
detail: `Semi provides esm format dist, and the css of the component is only imported by the corresponding js.
When used in Webpack, Rspack, create-react-app or Vite projects, there is no need to configure any compilation items.`,
logo,
siteName: 'Semi Design',
img,
},
],
},
{
header: 'Design resource',
key: '2',
annotations: [
{
order: 2,
title: 'Semi Design resource',
url: 'https://semi.design/',
detail: 'Semi Design provides a wealth of design resources to help designers and developers collaborate efficiently. Whether you are a community user or a ByteDance internal designer, you can find UI Kit resource and Figma plug-ins that suit you here.',
logo,
siteName: 'Semi Design resource',
img,
},
],
},
];
const imgUploadProps: ImageUploadNodeOptions = {
action: 'https://api.semi.design/upload',
// response is the action's result; real code reads the uploaded image URL from it
getUploadImageSrc: () => logo,
};
const defaultSize = { width: '60%' };
let mode = $state('main');
let activeKey = $state('codePreview');
let activeCodeKey = $state<string | string[]>('code1');
let activeFileKey = $state<string | string[]>('file1');
let activeReferKey = $state<string | string[]>('1');
let currentDetail = $state.raw<SideBarDetailContent>();
const onExpand = (_event: MouseEvent, content: SideBarDetailContent, expandMode: string) => {
mode = expandMode;
currentDetail = content;
};
</script>
<div style={containerStyle}>
<div style="flex-grow: 1; flex-shrink: 1; padding: 20px">工作区占位</div>
<Sidebar bind:activeKey {defaultSize} detailContent={currentDetail} {imgUploadProps} {mode} motion={false} onBackWard={(_event, backMode) => (mode = backMode)} options={optionList} showClose={false} title="Agent 的工作空间" visible>
{#snippet optionIcon({ item })}
{@const Icon = optionIcons[item.key]}
<Icon />
{/snippet}
{#snippet mainContent({ activeKey: currentKey })}
{#if currentKey === 'searchResult'}
<AnnotationContent bind:activeKey={activeReferKey} info={defaultInfoList} />
{:else if currentKey === 'filePreview'}
<FileContent bind:activeKey={activeFileKey} files={defaultFiles} {onExpand} />
{:else if currentKey === 'codePreview'}
<CodeContent bind:activeKey={activeCodeKey} codes={defaultCodes} {onExpand} />
{:else if currentKey === 'network'}
<img alt="network" src="/assets/root-web-sites/test.jpg" style="width: 100%" />
{/if}
{/snippet}
</Sidebar>
</div>API Reference
The class, style and other undeclared attributes (such as aria-label) of Sidebar, Annotation, MCPConfigure and Container land on the .semi-sidebar-container element; when the root is named (aria-label or aria-labelledby) and no role is given, its role is group. Undeclared attributes of CodeContent, CodeItem, FileContent, FileItem and AnnotationContent land on their own root elements.
Sidebar, Annotation, MCPConfigure and Sidebar.Container expose getRoot() through export function; get the instance with bind:this and call it. It returns HTMLDivElement | null, and null while the container is hidden. The type of Container is SidebarContainerHandle (exported from @fibrae/semi-ai-svelte).
A snippet written inside a component tag becomes a prop of that component by its name, so only names the component declares, such as optionIcon and mainContent, can go inside the tag; declare your own helper snippets outside the component tag.
Container
Props
| Property | Description | Type | Default |
|---|---|---|---|
| afterVisibleChange | Called after the show or hide animation ends, with whether the container is visible. | Type(visible: boolean) => void | - |
| children | The content area of the container. | Snippet | - |
| closeOnEsc | Whether pressing Esc calls onCancel; off by default. | boolean | false |
| defaultSize | Initial width and height; applies only when resizable is true. | { width?: number | string; height?: number | string } | - |
| header | Replaces the whole header, the title and close button included. | Snippet | - |
| maxWidth | Maximum width when resizing; applies only when resizable is true. | number | string | - |
| minWidth | Minimum width when resizing; applies only when resizable is true. | number | string | 150 |
| motion | Whether showing and hiding use the expand animation. | boolean | true |
| onCancel | Called when the close button is clicked, or when Esc is pressed while closeOnEsc is true. | Type(event: MouseEvent | KeyboardEvent) => void | - |
| onintroend | Native introend listener, run alongside the component's own handling of the animation end. | TypeEventHandler<CustomEvent<null>, T> | null | - |
| onoutroend | Native outroend listener, run alongside the component's own handling of the animation end. | TypeEventHandler<CustomEvent<null>, T> | null | - |
| resizable | Whether the width can be resized by dragging; minWidth, maxWidth and defaultSize apply only when true. | boolean | true |
| showClose | Whether the header shows the close button. | boolean | true |
| title | Title, either plain text or a snippet. | Snippet | string | - |
| visible | Whether the container is shown; bind it with bind:visible. When the close button or Esc cancels, false is written first, then onCancel is called. | boolean | - |
Methods
| Method | Description | Type |
|---|---|---|
| getRoot | Returns the .semi-sidebar-container root element; null while hidden. | Type() => HTMLDivElement | null |
MCPConfigure
Props
| Property | Description | Type | Default |
|---|---|---|---|
| afterVisibleChange | Called after the show or hide animation ends, with whether it is visible. | Type(visible: boolean) => void | - |
| closeOnEsc | Whether pressing Esc calls onCancel. | boolean | - |
| customOptions | Custom MCP tools; bind it with bind:customOptions. Switching a tool writes the new list back before onStatusChange is called. | MCPConfigureOption[] | - |
| defaultSize | Initial width and height; applies only when resizable is true. | { width?: number | string; height?: number | string } | - |
| desc | Description of an option, with item, index and custom; the desc field is shown when absent. | TypeSnippet<[MCPConfigureItemSnippetProps]> | - |
| filter | Custom search filter, called with the lower-cased input and an option; the label and desc are matched by default. | Type(inputValue: string, option: MCPConfigureOption) => boolean | - |
| header | Replaces the whole header, the title and close button included. | Snippet | - |
| icon | Icon of an option, with item, index and custom. When provided it is used for every option; the icon field is an image URL only when it is absent. | TypeSnippet<[MCPConfigureItemSnippetProps]> | - |
| item | Replaces the whole built-in row, with item, index and custom. | TypeSnippet<[MCPConfigureItemSnippetProps]> | - |
| maxWidth | Maximum width when resizing; applies only when resizable is true. | number | string | - |
| minWidth | Minimum width when resizing; applies only when resizable is true. | number | string | - |
| motion | Whether showing and hiding use the expand animation. | boolean | - |
| onAddClick | Called when the add button of the custom page is clicked. | Type(event: MouseEvent) => void | - |
| onCancel | Called when the close button is clicked, or when Esc is pressed while closeOnEsc is true. | Type(event: MouseEvent | KeyboardEvent) => void | - |
| onConfigureClick | Called when the configure button is clicked, with the event and its option. | Type(event: MouseEvent, option: MCPConfigureOption) => void | - |
| onEditClick | Called when the edit button of a custom tool is clicked, with the event and its option. | Type(event: MouseEvent, option: MCPConfigureOption) => void | - |
| onSearch | Called when the search input changes, with the input value and whether the custom list is shown. | Type(inputValue: string, custom: boolean) => void | - |
| onStatusChange | Called after a tool is switched, with the list whose active flag changed and whether it is the custom list. | Type(options: MCPConfigureOption[], custom: boolean) => void | - |
| options | Built-in MCP tools; bind it with bind:options. Each item needs a unique value. Switching a tool writes the new list back before onStatusChange is called. | MCPConfigureOption[] | - |
| placeholder | Placeholder of the search input, the locale text by default. | string | - |
| resizable | Whether the width can be resized by dragging; minWidth, maxWidth and defaultSize apply only when true. | boolean | - |
| showClose | Whether the header shows the close button. | boolean | - |
| title | Title, plain text or a snippet; the locale's MCP configure text by default. | Snippet | string | - |
| visible | Whether it is shown; bind it with bind:visible. | boolean | - |
Methods
| Method | Description | Type |
|---|---|---|
| getRoot | Returns the .semi-sidebar-container root element; null while hidden. | Type() => any |
MCPConfigureOption
| Property | Description | Type | Default |
|---|---|---|---|
| active | Whether this MCP is active | boolean | false |
| configure | Whether to show the configure button | boolean | false |
| desc | Description text, matched by the built-in filter; use the desc snippet for other content | string | - |
| disabled | Whether this MCP is disabled. When true, users cannot toggle its active state | boolean | false |
| icon | Image URL of the icon; use the icon snippet for other content | string | - |
| label | Label text | string | - |
| value | Value, required. Identifies the option in onStatusChange and is used as the item key, so it must be unique in a list | string | - |
Annotation
Props
| Property | Description | Type | Default |
|---|---|---|---|
| activeKey | Keys of the expanded groups; bind it with bind:activeKey. | string | string[] | - |
| afterVisibleChange | Called after the show or hide animation ends, with whether it is visible. | Type(visible: boolean) => void | - |
| closeOnEsc | Whether pressing Esc calls onCancel. | boolean | - |
| defaultSize | Initial width and height; applies only when resizable is true. | { width?: number | string; height?: number | string } | - |
| header | Replaces the whole header, the title and close button included. | Snippet | - |
| info | Groups of references; each has a header, a key and its annotations. | AnnotationInfo[] | - |
| infoHeader | Content of a group header after its icon, with item and index; the header field is shown when absent. The container's header replaces the whole header, so the group header is named infoHeader. | TypeSnippet<[AnnotationHeaderSnippetProps]> | - |
| item | Replaces the built-in reference item, with item and index (its position in the group). | TypeSnippet<[AnnotationItemSnippetProps]> | - |
| maxWidth | Maximum width when resizing; applies only when resizable is true. | number | string | - |
| minWidth | Minimum width when resizing; applies only when resizable is true. | number | string | - |
| motion | Whether showing and hiding use the expand animation. | boolean | - |
| onCancel | Called when the close button is clicked, or when Esc is pressed while closeOnEsc is true. | Type(event: MouseEvent | KeyboardEvent) => void | - |
| onChange | Called when a group opens or closes, with the expanded keys. | Type(activeKey: string | string[]) => void | - |
| onClick | Called when a reference is clicked, with the event and its item; the native click of the root is not reported apart from it. | Type(event: MouseEvent, item: AnnotationItem) => void | - |
| resizable | Whether the width can be resized by dragging; minWidth, maxWidth and defaultSize apply only when true. | boolean | - |
| showClose | Whether the header shows the close button. | boolean | - |
| title | Title, plain text or a snippet; the locale text by default. | Snippet | string | - |
| visible | Whether it is shown; bind it with bind:visible. | boolean | - |
Methods
| Method | Description | Type |
|---|---|---|
| getRoot | Returns the .semi-sidebar-container root element; null while hidden. | Type() => any |
AnnotationContent
Props
| Property | Description | Type | Default |
|---|---|---|---|
| activeKey | Keys of the expanded groups; bind it with bind:activeKey. | string | string[] | - |
| header | Content of a group header after its icon, with item and index; the header field is shown when absent. | TypeSnippet<[AnnotationHeaderSnippetProps]> | - |
| info | Groups of references; each has a header, a key and its annotations. | AnnotationInfo[] | [] |
| item | Replaces the built-in reference item, with item and index (its position in the group). | TypeSnippet<[AnnotationItemSnippetProps]> | - |
| onChange | Called when a group opens or closes, with the expanded keys. | Type(activeKey: string | string[]) => void | - |
| onClick | Called when a reference is clicked, with the event and its item; the native click of the root is not reported apart from it. | Type(event: MouseEvent, item: AnnotationItem) => void | - |
AnnotationInfo
| Property | Description | Type | Default |
|---|---|---|---|
| annotations | Reference items of the group | AnnotationItem[] | - |
| header | Plain-text group title; customize it with the infoHeader (Annotation) or header (AnnotationContent) snippet | string | - |
| key | Unique key of the group | string | - |
AnnotationItem
| Property | Description | Type | Default |
|---|---|---|---|
| detail | Extra detail text such as video description or text note | string | - |
| duration | Duration, usually used for videos, in seconds | number | - |
| img | Image url, such as video cover or illustration | string | - |
| logo | Logo url of the site/platform to which the content belongs | string | - |
| order | Order/index number | number | - |
| siteName | Name of the site/platform where the content belongs | string | - |
| title | Title | string | - |
| type | Content type | 'video' | 'text' | - |
| url | Resource url, opened in a new tab. An item without it renders a link without href that only the pointer can activate | string | - |
Sidebar
Props
| Property | Description | Type | Default |
|---|---|---|---|
| activeKey | Key of the selected option in the main view; bind it with bind:activeKey. | string | - |
| afterVisibleChange | Called after the show or hide animation ends, with whether it is visible. | Type(visible: boolean) => void | - |
| closeOnEsc | Whether pressing Esc calls onCancel. | boolean | - |
| customRender | Forwarded to the customRender snippet of the JsonViewer in the built-in code detail, for the content matched by customRenderRule. | TypeSnippet<[JsonViewerCustomRenderArgs]> | - |
| defaultSize | Initial width and height; applies only when resizable is true. | { width?: number | string; height?: number | string } | - |
| detailBody | Content in detail modes, with mode; empty content falls back to the built-in code or file detail. detailContent is already the data prop, so this is named detailBody. | TypeSnippet<[SideBarDetailBodySnippetProps]> | - |
| detailContent | The item shown in code and file modes: the CodeItem or FileItem props plus a key; a new key remounts it. In file mode its editable and imgUploadProps have no effect, as fileEditable and imgUploadProps decide them. | SideBarDetailContent | - |
| detailHeader | Header in detail modes, with mode and detailContent; empty content falls back to the built-in header. | TypeSnippet<[SideBarDetailHeaderSnippetProps]> | - |
| fileEditable | Whether the rich text in file mode is editable. | boolean | true |
| imgUploadProps | Image upload options of file mode, read when the editor is created; later changes have no effect. | ImageUploadNodeOptions | - |
| mainContent | Content below the options in the main view, with activeKey. | TypeSnippet<[SideBarMainContentSnippetProps]> | - |
| maxWidth | Maximum width when resizing; applies only when resizable is true. | number | string | - |
| minWidth | Minimum width when resizing; applies only when resizable is true. | number | string | - |
| mode | Display mode: main is the main view, code and file use the built-in details, and any other string is rendered by the detailBody snippet. | string | "main" |
| motion | Whether showing and hiding use the expand animation. | boolean | - |
| onActiveOptionChange | Called after activeKey took the clicked option, with the event and the option's key. | Type(event: MouseEvent, activeKey: string) => void | - |
| onBackWard | Called when the back button of the detail header is clicked, with the event and main. | Type(event: MouseEvent, mode: string) => void | - |
| onCancel | Called when the close button is clicked, or when Esc is pressed while closeOnEsc is true. | Type(event: MouseEvent | KeyboardEvent) => void | - |
| onDetailContentCopy | Called after the copy button wrote to the clipboard, with the event, the content and whether it succeeded. | Type(event: MouseEvent, content: string, success: boolean) => void | - |
| onFileContentChange | Called after an edit in file mode, with the content as HTML. | Type(content: string) => void | - |
| optionIcon | Icon of the built-in option button, with item and index. | TypeSnippet<[SideBarOptionSnippetProps]> | - |
| optionItem | Replaces the whole option button, with item, index, active and onClick (which selects the option). | TypeSnippet<[SideBarOptionItemSnippetProps]> | - |
| options | Option buttons at the top of the main view, each with a name and a key; icons use the optionIcon snippet. | SideBarOption[] | - |
| resizable | Whether the width can be resized by dragging; minWidth, maxWidth and defaultSize apply only when true. | boolean | - |
| searchButton | Forwarded to the searchButton snippet of the JsonViewer in the built-in code detail, replacing its search button. | TypeSnippet<[JsonViewerSearchButtonArgs]> | - |
| showClose | Whether the header shows the close button. | boolean | - |
| title | Title, plain text or a snippet. | Snippet | string | - |
| visible | Whether it is shown; bind it with bind:visible. | boolean | - |
Methods
| Method | Description | Type |
|---|---|---|
| getRoot | Returns the .semi-sidebar-container root element; null while hidden. | Type() => any |
SideBarOption
| Property | Description | Type | Default |
|---|---|---|---|
| key | Unique key of the option | string | - |
| name | Option name; use the optionIcon snippet for the icon | string | - |
CodeContent
Each item of codes is the props of CodeItem plus a key that identifies its panel.
Props
| Property | Description | Type | Default |
|---|---|---|---|
| activeKey | Keys of the expanded code panels; bind it with bind:activeKey. | string | string[] | - |
| codes | The code entries, each with the CodeItem props and a key identifying its panel. | CodeItemData[] | [] |
| customRender | Forwarded to the customRender snippet of the JsonViewer in every JSON panel. | TypeSnippet<[JsonViewerCustomRenderArgs]> | - |
| onChange | Called when a panel opens or closes through its arrow, with the expanded keys. | Type(activeKey: string | string[]) => void | - |
| onExpand | Called when the expand button beside a panel header is clicked, with the event, its entry and code. Without it the button is not rendered. | Type(event: MouseEvent, item: T, mode: string) => void | - |
| searchButton | Forwarded to the searchButton snippet of the JsonViewer in every JSON panel, replacing its search button. | TypeSnippet<[JsonViewerSearchButtonArgs]> | - |
CodeItem
Props
| Property | Description | Type | Default |
|---|---|---|---|
| codeHighlightProps | Other props for CodeHighlight, plus an extra class and style. | TypePartial<CodeHighlightProps> | - |
| content | The code or JSON string. | string | - |
| customRender | The customRender snippet of JsonViewer, for the content matched by jsonViewerProps.options.customRenderRule. | TypeSnippet<[JsonViewerCustomRenderArgs]> | - |
| isJson | When true the content is shown by JsonViewer, otherwise by CodeHighlight. | boolean | false |
| jsonViewerProps | Other props for JsonViewer, plus an extra class and style. value (the content), width and height (100%), options (read-only, auto wrap) and showSearch (false) have defaults; a prop given here replaces its default as a whole. | TypePartial<JsonViewerProps> | - |
| language | Language name as in the CodeHighlight language prop; register it with CodeHighlight.register first, otherwise plain text is shown. | string | - |
| name | Name of the code item, shown by the CodeContent panel header and the Sidebar detail header; CodeItem itself does not render it. | string | - |
| searchButton | The searchButton snippet of JsonViewer, replacing its search button. | TypeSnippet<[JsonViewerSearchButtonArgs]> | - |
FileContent
Each item of files is the props of FileItem plus a key that identifies its panel; they are rendered read-only, and extensions and imgUploadProps reach their preview.
Props
| Property | Description | Type | Default |
|---|---|---|---|
| activeKey | Keys of the expanded file panels; bind it with bind:activeKey. | string | string[] | - |
| files | The file entries, rendered read-only: each entry's extensions and imgUploadProps reach its preview. | FileItemData[] | [] |
| onChange | Called when a panel opens or closes through its arrow, with the expanded keys. | Type(activeKey: string | string[]) => void | - |
| onExpand | Called when the expand button beside a panel header is clicked, with the event, its entry and file. Without it the button is not rendered. | Type(event: MouseEvent, item: T, mode: string) => void | - |
FileItem
Props
| Property | Description | Type | Default |
|---|---|---|---|
| content | Rich text as HTML. Later changes are synced to the editor; content equal to the editor's is not reset, and syncing does not call onContentChange. | string | - |
| editable | Whether it is editable; later changes are synced to the editor. | boolean | true |
| extensions | tiptap extensions appended to the built-in ones, read when the editor is created; later changes have no effect. | Extensions | [] |
| imgUploadProps | Image upload options, read when the editor is created; later changes have no effect. | ImageUploadNodeOptions | - |
| name | File name, used as the accessible name of the editing area; later changes are synced to the editor. | string | - |
| onContentChange | Called after an edit, with the content as HTML. | Type(content: string) => void | - |