AI Component · Sidebar

Sidebar

Display configuration and detail information in AI scenarios

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

import { Annotation, MCPConfigure, Sidebar } from '@fibrous/semi-ai-react';

const { Container, CodeContent, CodeItem, FileContent, FileItem } = Sidebar;
const { AnnotationContent } = Annotation;

Basic Container

The basic container of the Sidebar is the base container for MCP configuration, References and Sidebar.

  • visible together with onCancel is used to control show/hide
  • title sets the title
  • motion sets whether to enable the expand animation
  • resizable sets whether the width is resizable. When true, you can use minWidth and maxWidth to control min and max width
是否有动画
是否可伸缩
基础容器示例
Code
import { useState, useCallback, type CSSProperties } from 'react';
import { Sidebar } from '@fibrous/semi-ai-react';
import { Button, Switch } from '@fibrous/semi-ui-react';

const { Container } = Sidebar;
const containerStyle: CSSProperties = {
    display: 'flex',
    height: '500px',
    border: '1px solid var(--semi-color-border)',
    borderRadius: 8,
    overflow: 'hidden',
    boxSizing: 'border-box',
};

export default function SidebarDemo() {
    const [visible, setVisible] = useState(true);
    const [motion, setMotion] = useState(true);
    const [resizable, setResizable] = useState(true);

    const toggleVisible = useCallback(() => {
        setVisible((visible) => !visible);
    }, []);

    const onMotionChange = useCallback((checked: boolean) => {
        setMotion(checked);
    }, []);

    const onResizableChange = useCallback((checked: boolean) => {
        setResizable(checked);
    }, []);

    return (
        <div style={containerStyle}>
            <div style={{ flexGrow: 1, flexShrink: 1, padding: 20 }}>
                <div>
                    <Button aria-pressed={visible} onClick={toggleVisible}>
                        显示容器
                    </Button>
                    <div style={{ display: 'flex', alignItems: 'center', marginTop: 10 }}>
                        <span id="sidebar-container-motion">是否有动画</span> <Switch aria-labelledby="sidebar-container-motion" checked={motion} onChange={onMotionChange} />
                    </div>
                    <div style={{ display: 'flex', alignItems: 'center', marginTop: 10 }}>
                        <span id="sidebar-container-resizable">是否可伸缩</span> <Switch aria-labelledby="sidebar-container-resizable" checked={resizable} onChange={onResizableChange} />
                    </div>
                </div>
            </div>
            <Container motion={motion} resizable={resizable} minWidth={250} maxWidth="60%" visible={visible} title="基础容器示例" style={resizable ? {} : { width: 200 }} onCancel={toggleVisible} />
        </div>
    );
}

MCP Configuration

Use MCPConfigure to display, enable/disable, configure and search MCP tools.

  • Use options and customOptions to set built-in MCP tools and custom MCP tools
  • Use visible to control show and hide, and onCancel to listen to user close behavior
  • Use onStatusChange to handle enabling/disabling of MCP tools
  • Use onAddClick to handle actions after clicking the Add button in custom MCP page
  • Use onConfigureClick to listen to configuration of built-in MCP tools, and onEditClick to listen to configuration of custom MCP tools

For other APIs of MCPConfigure, see MCPConfigure.

The type of MCP tool item is as follows:

interface MCPOption {
    icon?: ReactNode;
    label?: string;
    value: string; // required, identifies the tool and must be unique
    desc?: ReactNode;
    active?: boolean;
    disabled?: boolean;
    configure?: boolean; // whether to show configure button
}

Usage example:

MCP 配置
已激活 MCP 数: 0/2
支持 Semi 的文档、源码搜索,辅助开发
Figma MCP Server 连接Figma与AI开发工具的功能。它通过标准化的模型上下文协MCP),将组件、变量等设计数据和上下文暴露给AI,从而实现从设计稿到代码的智能生成,显著提升开发效率。
Code
import { useState, useCallback, useRef, type CSSProperties, type MouseEvent, type RefObject } from 'react';
import { MCPConfigure, type MCPConfigureOption as MCPOption } from '@fibrous/semi-ai-react';
import { IconSemiLogo, IconFigma } from '@fibrous/semi-icons-react';
import { Button, Modal, Form, type FormHandle } from '@fibrous/semi-ui-react';

interface McpFormValues {
    name: string;
    src: string;
    desc: string;
}

interface CustomOptionCreateModelProps {
    visible: boolean;
    handleOk: () => void;
    handleCancel: () => void;
    formRef: RefObject<FormHandle<McpFormValues> | null>;
}

const defaultOptions: MCPOption[] = [
    {
        icon: <IconSemiLogo />,
        value: 'Semi mcp',
        label: 'Semi',
        configure: true,
        desc: '支持 Semi 的文档、源码搜索,辅助开发',
    },
    {
        icon: <IconFigma />,
        value: 'figma',
        label: 'Figma',
        desc: 'Figma MCP Server 连接Figma与AI开发工具的功能。它通过标准化的模型上下文协MCP),将组件、变量等设计数据和上下文暴露给AI,从而实现从设计稿到代码的智能生成,显著提升开发效率。',
    },
];

const CustomOptionCreateModel = ({ visible, handleOk, handleCancel, formRef }: CustomOptionCreateModelProps) => {
    return (
        <Modal title="自定义 MCP" visible={visible} onOk={handleOk} onCancel={handleCancel} closeOnEsc>
            <Form ref={formRef} layout="vertical" onValueChange={(values) => console.log(values)}>
                <Form.Input rules={[{ required: true, message: '请输入 MCP 名称' }]} field="name" label="MCP 名称" style={{ width: '100%' }} />
                <Form.Input
                    initValue="/assets/placeholder/avatar-a.svg"
                    rules={[
                        { required: true, message: '请输入 MCP 图标 URL' },
                        {
                            validator: (_rule, value) => {
                                const urlRegex = /^(?:https?:\/\/)?(?:www\.)?[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+(?::\d+)?(?:\/[^\s]*)?$/i;
                                return urlRegex.test(value);
                            },
                            message: '请输入有效的 MCP 图标 URL',
                        },
                    ]}
                    field="src"
                    label="MCP 图标 URL"
                    style={{ width: '100%' }}
                />
                <Form.TextArea rules={[{ required: true, message: '请输入 MCP 介绍' }]} field="desc" label="MCP 介绍" style={{ width: '100%' }} />
            </Form>
        </Modal>
    );
};

let index = 1;

const containerStyle: CSSProperties = {
    display: 'flex',
    height: '500px',
    border: '1px solid var(--semi-color-border)',
    borderRadius: 8,
    overflow: 'hidden',
    boxSizing: 'border-box',
};

export default function SidebarDemo() {
    const [visible, setVisible] = useState(true);
    const toggleVisible = useCallback(() => {
        setVisible((visible) => !visible);
    }, []);
    const [options, setOptions] = useState(defaultOptions);
    const [mVisible, setMVisible] = useState(false);
    const [customOptions, setCustomOptions] = useState<MCPOption[]>([]);
    const formRef = useRef<FormHandle<McpFormValues>>(null);

    const handleOk = useCallback(() => {
        formRef.current?.formApi
            .validate()
            .then((values) => {
                const newOptions = [
                    ...customOptions,
                    {
                        label: values.name,
                        icon: values.src,
                        value: `mcp-${index++}`,
                        desc: values.desc,
                    },
                ];
                setCustomOptions(newOptions);
                setMVisible(false);
            })
            .catch((errors) => {
                console.log('errors', errors);
            });
    }, [customOptions]);

    const showDialog = useCallback(() => {
        setMVisible(true);
    }, []);

    const handleCancel = useCallback(() => {
        setMVisible(false);
    }, []);

    const onStatusChange = useCallback((options: MCPOption[], custom: boolean) => {
        if (custom) {
            setCustomOptions(options);
        } else {
            setOptions(options);
        }
    }, []);

    const onAddClick = useCallback(() => {
        showDialog();
    }, [showDialog]);

    const onConfigureClick = useCallback((_e: MouseEvent, option: MCPOption) => {
        console.log('configure click', option);
    }, []);

    const onEditClick = useCallback((_e: MouseEvent, option: MCPOption) => {
        console.log('edit click', option);
    }, []);

    return (
        <div style={containerStyle}>
            <div style={{ flexGrow: 1, flexShrink: 1, padding: 20 }}>
                <Button aria-pressed={visible} onClick={toggleVisible}>
                    MCP 配置
                </Button>
            </div>
            <MCPConfigure style={{ width: 500 }} resizable={false} visible={visible} onCancel={toggleVisible} options={options} customOptions={customOptions} onStatusChange={onStatusChange} onAddClick={onAddClick} onConfigureClick={onConfigureClick} onEditClick={onEditClick} />
            <CustomOptionCreateModel formRef={formRef} visible={mVisible} handleOk={handleOk} handleCancel={handleCancel} />
        </div>
    );
}

References

Use the Annotation component to manage the display of reference sources.

  • Use activeKey together with onChange to manage the currently expanded item
  • info sets the reference content list
Code
import { useState, useCallback, type ComponentProps, type CSSProperties, type MouseEvent } from 'react';
import { Annotation, type AnnotationItem } from '@fibrous/semi-ai-react';
import { Button } from '@fibrous/semi-ui-react';

const containerStyle: CSSProperties = {
    display: 'flex',
    height: '500px',
    border: '1px solid var(--semi-color-border)',
    borderRadius: 8,
    overflow: 'hidden',
    boxSizing: 'border-box',
};

const defaultInfoList: ComponentProps<typeof Annotation>['info'] = [
    {
        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: '/assets/placeholder/avatar-a.svg',
                siteName: 'Semi Design',
                detail: ' As a comprehensive, easy-to-use, and high-quality modern enterprise-level application UI solution',
                img: '/assets/root-web-sites/abstract.jpg',
            },
            {
                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: '/assets/placeholder/avatar-a.svg',
                siteName: 'Semi Design',
                img: '/assets/root-web-sites/abstract.jpg',
            },
            {
                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: '/assets/placeholder/avatar-a.svg',
                siteName: 'Semi Design',
                img: '/assets/root-web-sites/abstract.jpg',
            },
        ],
    },
    {
        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: '/assets/placeholder/avatar-a.svg',
                siteName: 'Semi Design resource',
                img: '/assets/root-web-sites/abstract.jpg',
            },
        ],
    },
];

export default function SidebarDemo() {
    const [visible, setVisible] = useState(true);
    const [activeKey, setActiveKey] = useState<string | string[]>('1');

    const toggleVisible = useCallback(() => {
        setVisible((visible) => !visible);
    }, []);

    const onChange = useCallback((key: string | string[]) => {
        console.log('onChange', key);
        setActiveKey(key);
    }, []);

    const onClick = useCallback((e: MouseEvent, item: AnnotationItem) => {
        console.log('onClick', e, item);
    }, []);

    return (
        <div style={containerStyle}>
            <div style={{ flexGrow: 1, flexShrink: 1, padding: 20 }}>
                <Button aria-pressed={visible} onClick={toggleVisible}>
                    参考来源
                </Button>
            </div>
            <Annotation defaultSize={{ width: 420 }} visible={visible} onCancel={toggleVisible} activeKey={activeKey} info={defaultInfoList} onChange={onChange} onClick={onClick} />
        </div>
    );
}

Code Display

You can use CodeItem in Sidebar to display code. CodeItem is built based on JsonViewer and CodeHighlight.

  • content: content string
  • isJson: whether the content is json
  • language: programming language name, same as language in CodeHighlight; register it with CodeHighlight.register first
  • jsonViewerProps: configure other JsonViewer props
  • codeHighlightProps: configure other CodeHighlight props
Code
import { useState, useCallback } from 'react';
import { Sidebar } from '@fibrous/semi-ai-react';
import { RadioGroup, Radio, type RadioChangeEvent } from '@fibrous/semi-ui-react';

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,
};

export default function SidebarDemo() {
    const [type, setType] = useState('json');
    const onRadioChange = useCallback((e: RadioChangeEvent) => {
        setType(e.target.value);
    }, []);

    return (
        <>
            <RadioGroup type="button" buttonSize="middle" value={type} onChange={onRadioChange} style={{ marginBottom: 10 }}>
                <Radio value="json">JSON</Radio>
                <Radio value="css">CSS</Radio>
            </RadioGroup>
            <div style={{ height: 200, overflow: 'auto' }}>
                <CodeItem {...(type === 'json' ? jsonProps : codeProps)} />
            </div>
        </>
    );
}

Code List

You can use CodeContent in Sidebar to display a list of code blocks.

  • Use activeKey together with onChange to manage the expanded panels
  • Use onExpand to listen to the expand button in a panel header
import React from 'react';
import { AutoComplete } from '@fibrous/semi-ui-react';
import { IconSearch } from '@fibrous/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 }}
        />
    );
};
Code
import { useState, useCallback } from 'react';
import { Sidebar } from '@fibrous/semi-ai-react';

const { CodeContent } = Sidebar;

const defaultCodes = [
    {
        name: 'Component.js',
        key: 'code1',
        language: 'javascript',
        content: `import React from 'react';
import { AutoComplete } from '@fibrous/semi-ui-react';
import { IconSearch } from '@fibrous/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
        }
    }
}`,
    },
];

export default function SidebarDemo() {
    const [activeKey, setActiveKey] = useState<string | string[]>('code1');

    const onChange = useCallback((key: string | string[]) => {
        setActiveKey(key);
    }, []);

    return (
        <div className="semi-sidebar-main">
            <CodeContent style={{ width: 600 }} activeKey={activeKey} codes={defaultCodes} onChange={onChange} />
        </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 content, supports the same types as TiptapContent. 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 call onContentChange
  • editable: whether the content is editable; a change after mount is synced to the editor
  • imgUploadProps: configure image upload endpoint and how to set the src after upload. Use action to set upload url and getUploadImageSrc(response) to return the uploaded image url from the upload response (when omitted, a string response is used as the url, otherwise response.src), which will be used as src of img node in rich text. The toolbar shows the image button only when action or customRequest is set


Code
import { useState, useCallback } from 'react';
import { Sidebar, type ImageUploadNodeOptions } from '@fibrous/semi-ai-react';
import { Button } from '@fibrous/semi-ui-react';

const { FileItem } = Sidebar;

const defaultFileContent = `<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',
    getUploadImageSrc: () => {
        // response is the action's result; real code reads the uploaded image URL from it
        return '/assets/placeholder/avatar-a.svg';
    },
};

export default function SidebarDemo() {
    const [editable, setEditable] = useState(true);
    const [content] = useState(defaultFileContent);
    const toggleEditable = useCallback(() => {
        setEditable((editable) => !editable);
    }, []);

    return (
        <>
            <Button aria-pressed={editable} onClick={toggleEditable}>
                可编辑
            </Button>
            <br />
            <br />
            <FileItem name="Semi Design 介绍" content={content} editable={editable} style={{ border: '1px solid var(--semi-color-border)', padding: 12 }} imgUploadProps={imgUploadProps} />
        </>
    );
}

Rich Text List

You can use FileContent in Sidebar to display a list of rich text documents.

  • Use activeKey together with onChange to manage the expanded panels
  • Use onExpand to listen to the expand button in a panel header
Code
import { useState, useCallback } from 'react';
import { Sidebar } from '@fibrous/semi-ai-react';

const { FileContent } = Sidebar;

const defaultFiles = [
    {
        key: 'file1',
        name: 'Semi Design 介绍',
        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>
<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>
<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>
`,
    },
    {
        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 @fibrous/semi-ui-react for React versions below v19, as well as a new package @fibrous/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 @fibrous/semi-ui-19. For React versions below v19, continue using @fibrous/semi-ui-react as before.</p>
`,
    },
];

export default function SidebarDemo() {
    const [activeKey, setActiveKey] = useState<string | string[]>('file1');

    const onChange = useCallback((key: string | string[]) => {
        setActiveKey(key);
    }, []);

    return (
        <div className="semi-sidebar-main">
            <FileContent style={{ width: 600 }} activeKey={activeKey} files={defaultFiles} onChange={onChange} />
        </div>
    );
}

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 top button group through options.

For content rendering in the main view, you can pass a render function via renderMainContent. 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. If you want to customize how the detail view is rendered, you can use renderDetailContent to handle it yourself.

工作区占位
Agent 的工作空间
import React from 'react';
import { AutoComplete } from '@fibrous/semi-ui-react';
import { IconSearch } from '@fibrous/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 }}
        />
    );
};
Code
import { useState, useCallback, type ComponentProps, type CSSProperties, type MouseEvent } from 'react';
import { Sidebar, Annotation, type CodeItemProps, type FileItemProps, type ImageUploadNodeOptions } from '@fibrous/semi-ai-react';
import { IconSearch, IconBriefStroked, IconCodeStroked, IconModalStroked } from '@fibrous/semi-icons-react';

const { FileContent, CodeContent } = Sidebar;
const { AnnotationContent } = Annotation;

const containerStyle: CSSProperties = {
    display: 'flex',
    height: '800px',
    border: '1px solid var(--semi-color-border)',
    borderRadius: 8,
    overflow: 'hidden',
    boxSizing: 'border-box',
};

const optionList = [
    {
        icon: <IconSearch />,
        name: '查看搜索',
        key: 'searchResult',
    },
    {
        icon: <IconBriefStroked />,
        name: '文件预览',
        key: 'filePreview',
    },
    {
        icon: <IconCodeStroked />,
        name: '代码预览',
        key: 'codePreview',
    },
    {
        icon: <IconModalStroked />,
        name: '浏览器',
        key: 'network',
    },
];

const defaultCodes = [
    {
        name: 'Component.js',
        key: 'code1',
        language: 'javascript',
        content: `import React from 'react';
import { AutoComplete } from '@fibrous/semi-ui-react';
import { IconSearch } from '@fibrous/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 defaultFiles = [
    {
        key: 'file1',
        name: 'Semi Design 介绍',
        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>
<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>
<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>
`,
    },
    {
        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 @fibrous/semi-ui-react for React versions below v19, as well as a new package @fibrous/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 @fibrous/semi-ui-19. For React versions below v19, continue using @fibrous/semi-ui-react as before.</p>
`,
    },
];

const defaultInfoList: ComponentProps<typeof AnnotationContent>['info'] = [
    {
        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: '/assets/placeholder/avatar-a.svg',
                siteName: 'Semi Design',
                detail: ' As a comprehensive, easy-to-use, and high-quality modern enterprise-level application UI solution',
                img: '/assets/root-web-sites/abstract.jpg',
            },
            {
                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: '/assets/placeholder/avatar-a.svg',
                siteName: 'Semi Design',
                img: '/assets/root-web-sites/abstract.jpg',
            },
            {
                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: '/assets/placeholder/avatar-a.svg',
                siteName: 'Semi Design',
                img: '/assets/root-web-sites/abstract.jpg',
            },
        ],
    },
    {
        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: '/assets/placeholder/avatar-a.svg',
                siteName: 'Semi Design resource',
                img: '/assets/root-web-sites/abstract.jpg',
            },
        ],
    },
];

const imgUploadProps: ImageUploadNodeOptions = {
    action: 'https://api.semi.design/upload',
    getUploadImageSrc: () => {
        // response is the action's result; real code reads the uploaded image URL from it
        return '/assets/placeholder/avatar-a.svg';
    },
};

export default function SidebarDemo() {
    const [mode, setMode] = useState('main');
    const [activeKey, setActiveKey] = useState('codePreview');
    const [activeCodeKey, setActiveCodeKey] = useState<string | string[]>('code1');
    const [activeFileKey, setActiveFileKey] = useState<string | string[]>('file1');
    const [activeReferKey, setActiveReferKey] = useState<string | string[]>('1');
    const [currentDetail, setCurrentDetail] = useState<CodeItemProps | FileItemProps>();

    const onExpand = useCallback((_e: MouseEvent, content: CodeItemProps | FileItemProps, mode: string) => {
        setMode(mode);
        setCurrentDetail(content);
    }, []);

    const onActiveCodeKeyChange = useCallback((codeKey: string | string[]) => {
        setActiveCodeKey(codeKey);
    }, []);

    const onActiveFileKeyChange = useCallback((fileKey: string | string[]) => {
        setActiveFileKey(fileKey);
    }, []);

    const onActiveReferKeyChange = useCallback((referKey: string | string[]) => {
        setActiveReferKey(referKey);
    }, []);

    const renderMainContent = useCallback(
        (activeKey: string) => {
            switch (activeKey) {
                case 'searchResult':
                    return <AnnotationContent activeKey={activeReferKey} info={defaultInfoList} onChange={onActiveReferKeyChange} />;
                case 'filePreview':
                    return <FileContent activeKey={activeFileKey} files={defaultFiles} onExpand={onExpand} onChange={onActiveFileKeyChange} />;
                case 'codePreview':
                    return <CodeContent activeKey={activeCodeKey} codes={defaultCodes} onExpand={onExpand} onChange={onActiveCodeKeyChange} />;
                case 'network':
                    return <img alt="network" src="/assets/root-web-sites/test.jpg" style={{ width: '100%' }} />;
                default:
                    return;
            }
        },
        [activeCodeKey, activeFileKey, activeReferKey, onExpand, onActiveCodeKeyChange, onActiveFileKeyChange, onActiveReferKeyChange]
    );

    const onActiveOptionChange = useCallback((_e: MouseEvent, key: string) => {
        setActiveKey(key);
    }, []);

    const onBackWard = useCallback((_e: MouseEvent, mode: string) => {
        setMode(mode);
    }, []);

    return (
        <div style={containerStyle}>
            <div style={{ flexGrow: 1, flexShrink: 1, padding: 20 }}>工作区占位</div>
            <Sidebar
                showClose={false}
                visible
                motion={false}
                title="Agent 的工作空间"
                mode={mode}
                defaultSize={{ width: '60%' }}
                onActiveOptionChange={onActiveOptionChange}
                activeKey={activeKey}
                options={optionList}
                onBackWard={onBackWard}
                renderMainContent={renderMainContent}
                detailContent={currentDetail}
                imgUploadProps={imgUploadProps}
            />
        </div>
    );
}

API Reference

Container

Props

PropertyDescriptionTypeDefault
afterVisibleChangeCalled after a visibility change has completed, with whether the container is visible; with motion it runs when the animation ends.Type(isVisible: boolean) => void-
childrenContent of the container.React.ReactNode-
closeOnEscWhether pressing Esc calls onCancel, false by default.boolean-
containerRefReceives the root element (.semi-sidebar-container), as ref does.TypeReact.RefObject<HTMLDivElement>-
defaultSizeInitial size (width and height); applies only when resizable is true.{ width?: number | string; height?: number | string }-
maxWidthMaximum width when resizing; applies only when resizable is true.string | number-
minWidthMinimum width when resizing; applies only when resizable is true, 150 by default.string | number-
motionWhether showing and hiding use the expand animation, true by default.boolean-
onCancelCalled when the close button is clicked, or when Esc is pressed while closeOnEsc is true, with the event.Type(e: any) => void-
refReference to the root element (.semi-sidebar-container); null after it is hidden and its leave animation has ended.TypeReact.Ref<HTMLDivElement>-
renderHeaderRenders the whole header; the built-in header is used when it returns no content.Type() => ReactNode-
resizableWhether the width can be resized by dragging, true by default; minWidth, maxWidth and defaultSize apply only when true.boolean-
showCloseWhether the header shows the close button, true by default.boolean-
styleInline style of the root element.CSSProperties-
titleTitle in the header; any content.ReactNode-
visibleWhether the container is shown.boolean-

The class name (className) and other native attributes (such as data-* and aria-label) land on the root element .semi-sidebar-container; when only aria-label is set without role, the root element's role is group.

MCPConfigure

Props

PropertyDescriptionTypeDefault
afterVisibleChangeCalled after a visibility change has completed, with whether the container is visible; with motion it runs when the animation ends.Type(isVisible: boolean) => void-
childrenHas no effect; the content is the MCP tool list.React.ReactNode-
closeOnEscWhether pressing Esc calls onCancel, false by default.boolean-
containerRefReceives the root element (.semi-sidebar-container), as ref does.TypeReact.RefObject<HTMLDivElement>-
customOptionsCustom MCP tools, listed on the custom tab; each item needs a unique value.MCPReactOption[]-
defaultSizeInitial size (width and height); applies only when resizable is true.{ width?: number | string; height?: number | string }-
filterCustom search filter, called with the lower-cased input and an option; the label and desc are matched by default.Type(inputValue: string, option: MCPReactOption) => boolean-
maxWidthMaximum width when resizing; applies only when resizable is true.string | number-
minWidthMinimum width when resizing; applies only when resizable is true, 150 by default.string | number-
motionWhether showing and hiding use the expand animation, true by default.boolean-
onAddClickCalled when the add button on the custom tab is clicked, with the event.Type(e: React.MouseEvent<HTMLButtonElement>) => void-
onCancelCalled when the close button is clicked, or when Esc is pressed while closeOnEsc is true, with the event.Type(e: any) => void-
onConfigureClickCalled when the configure button of a built-in tool is clicked, with the event and the option.Type(e: React.MouseEvent<HTMLButtonElement>, option: MCPReactOption) => void-
onEditClickCalled when the edit button of a custom tool is clicked, with the event and the option.Type(e: React.MouseEvent<HTMLButtonElement>, option: MCPReactOption) => void-
onSearchCalled on search, with the input value and whether the custom list is searched.Type(inputValue: string, custom: boolean) => void-
onStatusChangeCalled when a tool is enabled or disabled, with the whole updated list and whether it is the custom list; the component does not modify the list, so write the new list back to options or customOptions.Type(options: MCPReactOption[], custom: boolean) => void-
optionsBuilt-in MCP tools; each item needs a unique value.MCPReactOption[]-
placeholderPlaceholder of the search input, the locale text by default.string-
refReference to the root element (.semi-sidebar-container); null after it is hidden and its leave animation has ended.TypeReact.Ref<HTMLDivElement>-
renderHeaderRenders the whole header; the built-in header is used when it returns no content.Type() => ReactNode-
renderItemRenders a whole tool row, called with { option, custom }.Type(props: { option: MCPReactOption; custom: boolean }) => ReactNode-
resizableWhether the width can be resized by dragging, true by default; minWidth, maxWidth and defaultSize apply only when true.boolean-
showCloseWhether the header shows the close button, true by default.boolean-
styleInline style of the root element.CSSProperties-
titleTitle in the header, the locale's MCP configure text by default.ReactNode-
visibleWhether the container is shown.boolean-

The class name (className) and other native attributes land on the root element .semi-sidebar-container.

MCPReactOption

PropertyDescriptionTypeDefault
activeWhether this MCP is activebooleanfalse
configureWhether to show configuration related operation/indicatorbooleanfalse
descDescription contentReactNode-
disabledWhether this MCP is disabled. When true, users cannot toggle its active statebooleanfalse
iconIcon elementReactNode-
labelLabel textstring-
valueValue, required. Identifies the option in onStatusChange and is used as the item key, so it must be unique in a liststring-

Annotation

Props

PropertyDescriptionTypeDefault
activeKeyKeys of the expanded groups, one or several.string | string[]-
afterVisibleChangeCalled after a visibility change has completed, with whether the container is visible; with motion it runs when the animation ends.Type(isVisible: boolean) => void-
childrenHas no effect; the content is the reference list.React.ReactNode-
closeOnEscWhether pressing Esc calls onCancel, false by default.boolean-
containerRefReceives the root element (.semi-sidebar-container), as ref does.TypeReact.RefObject<HTMLDivElement>-
defaultSizeInitial size (width and height); applies only when resizable is true.{ width?: number | string; height?: number | string }-
infoGroups of references; each has a header, a key and its annotations.AnnotationInfo[]-
maxWidthMaximum width when resizing; applies only when resizable is true.string | number-
minWidthMinimum width when resizing; applies only when resizable is true, 150 by default.string | number-
motionWhether showing and hiding use the expand animation, true by default.boolean-
onCancelCalled when the close button is clicked, or when Esc is pressed while closeOnEsc is true, with the event.Type(e: any) => void-
onChangeCalled when the expanded groups change, with the expanded keys.Type(key: string | string[]) => void-
onClickCalled when a reference is clicked, with the event and the item.Type(e: React.MouseEvent, item: AnnotationItem) => void-
refReference to the root element (.semi-sidebar-container); null after it is hidden and its leave animation has ended.TypeReact.Ref<HTMLDivElement>-
renderHeaderRenders the whole header; the built-in header is used when it returns no content.Type() => ReactNode-
renderItemRenders a reference item, called with the item.Type(annotation: AnnotationItem) => ReactNode-
resizableWhether the width can be resized by dragging, true by default; minWidth, maxWidth and defaultSize apply only when true.boolean-
showCloseWhether the header shows the close button, true by default.boolean-
styleInline style of the root element.CSSProperties-
titleTitle in the header, the locale's references text by default.ReactNode-
visibleWhether the container is shown.boolean-

The class name (className) and other native attributes land on the root element .semi-sidebar-container.

AnnotationContent

Props

PropertyDescriptionTypeDefault
activeKeyKeys of the expanded groups, one or several.string | string[]-
classNameClass name of the Collapse root element.string-
infoGroups of references; each has a header, a key and its annotations.AnnotationInfo[][]
onChangeCalled when the expanded groups change, with the expanded keys.Type(key: string | string[]) => void-
onClickCalled when a reference is clicked, with the event and the item.Type(e: React.MouseEvent, item: AnnotationItem) => void-
renderItemRenders a reference item, called with the item.Type(annotation: AnnotationItem) => ReactNode-
styleInline style of the Collapse root element.React.CSSProperties-

Other native attributes land on the Collapse root element.

AnnotationItem

PropertyDescriptionTypeDefault
detailExtra detail text such as video description or text notestring-
durationDuration, usually used for videos, in secondsnumber-
imgImage url, such as video cover or illustrationstring-
logoLogo url of the site/platform to which the content belongsstring-
onClickCallback when item is clicked, returns event and current reference item(event: MouseEvent, item: AnnotationItem) => void-
orderOrder/index numbernumber-
siteNameName of the site/platform where the content belongsstring-
titleTitlestring-
typeContent type'video' | 'text'-
urlResource url. Clicking the reference will open this linkstring-

Props

PropertyDescriptionTypeDefault
activeKeyKey of the selected option in the main view.string-
afterVisibleChangeCalled after a visibility change has completed, with whether the sidebar is visible; with motion it runs when the animation ends.Type(isVisible: boolean) => void-
childrenHas no effect; the content is the main or detail view.React.ReactNode-
closeOnEscWhether pressing Esc calls onCancel, false by default.boolean-
containerRefHas no effect; use ref to get the container element.TypeReact.RefObject<HTMLDivElement>-
defaultSizeInitial size (width and height); applies only when resizable is true.{ width?: number | string; height?: number | string }-
detailContentThe 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, imgUploadProps and onContentChange have no effect, as fileEditable, imgUploadProps and onFileContentChange decide them.DetailContent-
fileEditableWhether the rich text in file mode is editable.booleantrue
imgUploadPropsImage upload options of file mode, read when the editor is created; later changes have no effect. The toolbar shows the image button only when action or customRequest is set.ImageUploadNodeOptions-
maxWidthMaximum width when resizing; applies only when resizable is true.string | number-
minWidthMinimum width when resizing; applies only when resizable is true, 150 by default.string | number-
modeDisplay mode: main is the main view, code and file use the built-in details, and any other string is rendered by renderDetailContent.string"main"
motionWhether showing and hiding use the expand animation, true by default.boolean-
onActiveOptionChangeCalled when an option button is clicked, with the event and the option key; write the key back to activeKey yourself.Type(e: React.MouseEvent, activeKey: string) => void-
onBackWardCalled when the back button of the detail header is clicked, with the event and main; set mode back to main yourself. The return value is ignored.Type(e: React.MouseEvent, mode: string) => void | Promise<any>-
onCancelCalled when the close button is clicked, or when Esc is pressed while closeOnEsc is true, with the event.Type(e: any) => void-
onDetailContentCopyCalled after the copy button of the detail header is clicked, once the clipboard write has settled, with the event, the copied content and the result (false when the write is rejected).Type(e: React.MouseEvent, content: string, res: boolean) => void-
onFileContentChangeCalled after the rich text is edited in file mode, with the HTML string.Type(content: string) => void-
optionsOption buttons at the top of the main view, each with a name, a key and an optional icon.Option[]-
refReference to the root element (.semi-sidebar-container); null after it is hidden and its leave animation has ended.TypeReact.Ref<HTMLDivElement>-
renderDetailContentRenders the detail content, called with the mode; when it returns no content, code and file modes fall back to the built-in detail.Type(mode: string) => ReactNode-
renderDetailHeaderRenders the detail header, called with the mode and detailContent; the built-in header is used when it returns no content.Type(mode: string, detailContent?: DetailContent) => ReactNode-
renderHeaderHas no effect; use title for the main view and renderDetailHeader for detail views.Type() => ReactNode-
renderMainContentRenders the main view, called with the selected activeKey.Type(activeKey: string) => ReactNode-
renderOptionItemRenders an option button; the second argument, onChange, selects the option when called with the event and the option key.Type(option: Option, onChange: (e: React.MouseEvent, activeKey: string) => void) => ReactNode-
resizableWhether the width can be resized by dragging, true by default; minWidth, maxWidth and defaultSize apply only when true.boolean-
showCloseWhether the header shows the close button, true by default.boolean-
styleInline style of the root element.CSSProperties-
titleTitle of the main view; detail views use the detail header.ReactNode-
visibleWhether the sidebar is shown.boolean-

The class name (className) and other native attributes land on the root element .semi-sidebar-container.

Option

PropertyDescriptionTypeDefault
iconOption iconReactNode-
keyUnique key of the optionstring-
nameOption nameReactNode-

CodeContent

Props

PropertyDescriptionTypeDefault
activeKeyKeys of the expanded code panels, one or several.string | string[]-
classNameClass name of the Collapse root element.string-
codesThe code entries, each with the CodeItem props and a key identifying its panel.CodeItemProps[][]
onChangeCalled when the expanded panels change, with the expanded keys.Type(activeKey: string | string[]) => void-
onExpandCalled when the expand button in a panel header is clicked, with the event, the code entry and the mode code; the button is shown only when this is set.Type(e: React.MouseEvent, code: CodeItemProps, mode: string) => void-
styleInline style of the Collapse root element.React.CSSProperties-

Other native attributes land on the Collapse root element.

CodeItem

Props

PropertyDescriptionTypeDefault
codeHighlightPropsProps of CodeHighlight, spread after code and language.CodeHighlightProps-
contentCode or text content.string-
isJsonWhether the content is JSON; when true, JsonViewer displays it.boolean-
jsonViewerPropsProps of JsonViewer, spread after the defaults for value, height, width and so on.TypePartial<JsonViewerProps>{}
keyIdentifies the panel for the activeKey of CodeContent, and decides when a Sidebar detail remounts.string-
languageLanguage of the code (such as js, ts or css), same as language of CodeHighlight.string-
nameName, shown in the panel header and in the Sidebar detail header.string-

Other native attributes land on the .semi-sidebar-code-content root element.

FileContent

Props

PropertyDescriptionTypeDefault
activeKeyKeys of the expanded file panels, one or several.string | string[]-
classNameClass name of the Collapse root element.string-
filesThe file entries, rendered read-only: each is the FileItem props plus a key identifying its panel; its className, style and other attributes are passed to the preview.FileItemProps[][]
onChangeCalled when the expanded panels change, with the expanded keys.Type(activeKey: string | string[]) => void-
onExpandCalled when the expand button in a panel header is clicked, with the event, the file entry and the mode file; the button is shown only when this is set.Type(e: React.MouseEvent, file: FileItemProps, mode: string) => void-
styleInline style of the Collapse root element.React.CSSProperties-

Other native attributes land on the Collapse root element.

FileItem

Props

PropertyDescriptionTypeDefault
classNameClass name of the root element.string-
contentRich text as an HTML string. A change after mount is synced to the editor, and the editor is not reset when the value equals its current content; the sync does not call onContentChange.string-
editableWhether the content is editable; a change after mount is synced to the editor.booleantrue
extensionstiptap extensions (Extension, Node or Mark) appended to the built-in ones, read when the editor is created; later changes have no effect.Extensions[]
imgUploadPropsImage upload options, read when the editor is created; later changes have no effect. The toolbar shows the image button only when action or customRequest is set; getUploadImageSrc(response) returns the image address from the upload response, and when it is not provided, a string response is used as the address and otherwise response.src is used.ImageUploadNodeOptions-
keyIdentifies the panel for the activeKey of FileContent, and decides when a Sidebar detail remounts.string-
nameName, shown in the panel header and the Sidebar detail header, and used as the aria-label of the editing area.string-
onContentChangeCalled after an edit, with the content as HTML.Type(content: string) => void-
styleInline style of the root element.React.CSSProperties-

Other native attributes land on the root element.