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-solid';

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, which can be any content; use renderHeader to customize the whole header
  • 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 { type JSX, createSignal } from 'solid-js';
import { Sidebar } from '@fibrous/semi-ai-solid';
import { Button, Switch } from '@fibrous/semi-ui-solid';

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

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

    const toggleVisible = () => setVisible((current) => !current);

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

MCP Configuration

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

  • Use options and customOptions to set the built-in 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; the component never changes the lists you pass, so write the callback argument back to options or customOptions
  • 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
  • Use renderIcon and renderDesc for the icon and description of an item, and renderItem to replace the whole row; they are called with { option, custom }

For other APIs of MCPConfigure, see MCPConfigure.

The type of MCP tool item is as follows:

interface MCPConfigureOption {
    icon?: string; // image URL; use renderIcon for other content
    label?: string;
    value: string; // required, identifies the tool and must be unique
    desc?: string; // plain text; use renderDesc for other content
    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 { type Component, type JSX, Show, createSignal } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import { MCPConfigure, type MCPConfigureOption as MCPOption } from '@fibrous/semi-ai-solid';
import { IconFigma, IconSemiLogo } from '@fibrous/semi-icons-solid';
import { Button, Form, FormInput, FormTextArea, Modal, type FormHandle } from '@fibrous/semi-ui-solid';

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

// icon only takes an image URL, so the built-in options get their icons from renderIcon
const builtinIcons: Record<string, Component> = {
    'Semi mcp': IconSemiLogo,
    figma: IconFigma,
};

const imageStyle: JSX.CSSProperties = { width: '100%', height: '100%' };

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

let index = 1;

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

export default function SidebarDemo() {
    const [visible, setVisible] = createSignal(true);
    const [options, setOptions] = createSignal(defaultOptions);
    const [mVisible, setMVisible] = createSignal(false);
    const [customOptions, setCustomOptions] = createSignal<MCPOption[]>([]);
    let form: FormHandle<McpFormValues> | undefined;

    const toggleVisible = () => setVisible((current) => !current);

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

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

    return (
        <div style={containerStyle}>
            <div style={{ 'flex-grow': 1, 'flex-shrink': 1, padding: '20px' }}>
                <Button aria-pressed={visible()} onClick={toggleVisible}>
                    MCP 配置
                </Button>
            </div>
            <MCPConfigure
                style={{ width: '500px' }}
                resizable={false}
                visible={visible()}
                onCancel={toggleVisible}
                options={options()}
                customOptions={customOptions()}
                onStatusChange={onStatusChange}
                onAddClick={() => setMVisible(true)}
                onConfigureClick={(_e, option) => console.log('configure click', option)}
                onEditClick={(_e, option) => console.log('edit click', option)}
                renderIcon={({ option }) => (
                    <Show when={option.icon} fallback={<Dynamic component={builtinIcons[option.value]} />}>
                        {(src) => <img alt="" src={src()} style={imageStyle} />}
                    </Show>
                )}
            />
            <Modal title="自定义 MCP" visible={mVisible()} onOk={handleOk} onCancel={() => setMVisible(false)} closeOnEsc>
                <Form ref={(node) => (form = node)} layout="vertical" onValueChange={(values) => console.log(values)}>
                    <FormInput rules={[{ required: true, message: '请输入 MCP 名称' }]} field="name" label="MCP 名称" style={{ width: '100%' }} />
                    <FormInput
                        initValue="/assets/placeholder/avatar-a.svg"
                        rules={[
                            { required: true, message: '请输入 MCP 图标 URL' },
                            {
                                validator: (_rule: unknown, value: string) => {
                                    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%' }}
                    />
                    <FormTextArea rules={[{ required: true, message: '请输入 MCP 介绍' }]} field="desc" label="MCP 介绍" style={{ width: '100%' }} />
                </Form>
            </Modal>
        </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
  • onClick is called when a reference is clicked, with the event and the item
  • Use renderInfoHeader for a group header and renderItem to replace an item; renderHeader renders the header of the whole container
Code
import { type JSX, createSignal } from 'solid-js';
import { Annotation, type AnnotationInfo, type AnnotationItem } from '@fibrous/semi-ai-solid';
import { Button } from '@fibrous/semi-ui-solid';

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

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: '/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] = createSignal(true);
    const [activeKey, setActiveKey] = createSignal<string | string[]>('1');

    const toggleVisible = () => setVisible((current) => !current);

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

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

    return (
        <div style={containerStyle}>
            <div style={{ 'flex-grow': 1, 'flex-shrink': 1, padding: '20px' }}>
                <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

jsonViewerProps and codeHighlightProps are the props of the Solid JsonViewer and CodeHighlight. They are spread after the built-in defaults, so value, width, height and the like override those defaults, and class and style are accepted too.

Code
import { createSignal } from 'solid-js';
import { Sidebar } from '@fibrous/semi-ai-solid';
import { Radio, RadioGroup } from '@fibrous/semi-ui-solid';

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] = createSignal('json');

    return (
        <>
            <RadioGroup type="button" buttonSize="middle" value={type()} onChange={(e) => setType(e.target.value)} style={{ 'margin-bottom': '10px' }}>
                <Radio value="json">JSON</Radio>
                <Radio value="css">CSS</Radio>
            </RadioGroup>
            <div style={{ height: '200px', 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; the button is shown only when it is set
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 { createSignal } from 'solid-js';
import { Sidebar } from '@fibrous/semi-ai-solid';

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] = createSignal<string | string[]>('code1');

    return (
        <div class="semi-sidebar-main">
            <CodeContent style={{ width: '600px' }} activeKey={activeKey()} codes={defaultCodes} onChange={setActiveKey} />
        </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 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 on callbacks of Upload (such as onSuccess) can be passed here too. The toolbar shows the image button only when action or customRequest is set
  • extensions and imgUploadProps are read only when the editor is created; remount the component to apply new values, for example by wrapping it in <Show when={version} keyed>
  • onContentChange is called after an edit, with the content as HTML


Code
import { createSignal } from 'solid-js';
import { Sidebar, type ImageUploadNodeOptions } from '@fibrous/semi-ai-solid';
import { Button } from '@fibrous/semi-ui-solid';

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] = createSignal(true);

    return (
        <>
            <Button aria-pressed={editable()} onClick={() => setEditable((current) => !current)}>
                可编辑
            </Button>
            <br />
            <br />
            <FileItem name="Semi Design 介绍" content={defaultFileContent} editable={editable()} style={{ border: '1px solid var(--semi-color-border)', padding: '12px' }} 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; the button is shown only when it is set
Code
import { createSignal } from 'solid-js';
import { Sidebar } from '@fibrous/semi-ai-solid';

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] = createSignal<string | string[]>('file1');

    return (
        <div class="semi-sidebar-main">
            <FileContent style={{ width: '600px' }} activeKey={activeKey()} files={defaultFiles} onChange={setActiveKey} />
        </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 the top button group through options (each item is { name, key }); activeKey together with onActiveOptionChange manages the selected item. Use renderOptionIcon for the button icon, or renderOptionItem to replace the whole button, whose second parameter onChange selects the item.

For content rendering in the main view, pass a render function via renderMainContent, called with the selected activeKey. You can use AnnotationContent in Annotation to render references, FileContent in Sidebar to render rich text, and CodeContent to render code. The render function runs again for every signal it reads, so read state signals such as the expanded panels only in the props you pass to the child components, which keeps the content from being rebuilt as a whole.

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 renderDetailContent, and customize the detail header through renderDetailHeader; when they return empty content, both fall back to the built-in ones. Clicking the back button of the detail header calls onBackWard.

工作区占位
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 { type Component, type JSX, Match, Switch, createSignal } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import { Annotation, Sidebar, type AnnotationInfo, type ImageUploadNodeOptions, type SideBarDetailContent, type SideBarOption } from '@fibrous/semi-ai-solid';
import { IconBriefStroked, IconCodeStroked, IconModalStroked, IconSearch } from '@fibrous/semi-icons-solid';

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

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

const optionList: SideBarOption[] = [
    { name: '查看搜索', key: 'searchResult' },
    { name: '文件预览', key: 'filePreview' },
    { name: '代码预览', key: 'codePreview' },
    { name: '浏览器', key: 'network' },
];

const optionIcons: Record<string, Component> = {
    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 '@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: 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: '/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] = createSignal('main');
    const [activeKey, setActiveKey] = createSignal('codePreview');
    const [activeCodeKey, setActiveCodeKey] = createSignal<string | string[]>('code1');
    const [activeFileKey, setActiveFileKey] = createSignal<string | string[]>('file1');
    const [activeReferKey, setActiveReferKey] = createSignal<string | string[]>('1');
    const [currentDetail, setCurrentDetail] = createSignal<SideBarDetailContent>();

    const onExpand = (_e: MouseEvent, content: SideBarDetailContent, mode: string) => {
        setMode(mode);
        setCurrentDetail(content);
    };

    return (
        <div style={containerStyle}>
            <div style={{ 'flex-grow': 1, 'flex-shrink': 1, padding: '20px' }}>工作区占位</div>
            <Sidebar
                showClose={false}
                visible
                motion={false}
                title="Agent 的工作空间"
                mode={mode()}
                defaultSize={{ width: '60%' }}
                onActiveOptionChange={(_e, key) => setActiveKey(key)}
                activeKey={activeKey()}
                options={optionList}
                renderOptionIcon={(option) => <Dynamic component={optionIcons[option.key]} />}
                onBackWard={(_e, next) => {
                    setMode(next);
                }}
                renderMainContent={(key) => (
                    <Switch>
                        <Match when={key === 'searchResult'}>
                            <AnnotationContent activeKey={activeReferKey()} info={defaultInfoList} onChange={setActiveReferKey} />
                        </Match>
                        <Match when={key === 'filePreview'}>
                            <FileContent activeKey={activeFileKey()} files={defaultFiles} onExpand={onExpand} onChange={setActiveFileKey} />
                        </Match>
                        <Match when={key === 'codePreview'}>
                            <CodeContent activeKey={activeCodeKey()} codes={defaultCodes} onExpand={onExpand} onChange={setActiveCodeKey} />
                        </Match>
                        <Match when={key === 'network'}>
                            <img alt="network" src="/assets/root-web-sites/test.jpg" style={{ width: '100%' }} />
                        </Match>
                    </Switch>
                )}
                detailContent={currentDetail()}
                imgUploadProps={imgUploadProps}
            />
        </div>
    );
}

API Reference

The class, style, ref and other undeclared attributes (such as aria-label, id and data-*) 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. For CodeContent, FileContent and AnnotationContent they land on their own root element (the collapse), and for CodeItem and FileItem on their own root div.

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.JSX.Element-
closeOnEscWhether pressing Esc calls onCancel.boolean-
containerRefReceives the .semi-sidebar-container element, as ref does.TypeRef<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.string | number150
motionWhether showing and hiding use the expand animation.booleantrue
onCancelCalled when the close button is clicked, or when Esc is pressed while closeOnEsc is true.Type(e: MouseEvent | KeyboardEvent) => void-
renderHeaderRenders the whole header; the built-in header is used when it returns no content.Type() => JSX.Element-
resizableWhether the width can be resized by dragging; minWidth, maxWidth and defaultSize apply only when true.booleantrue
showCloseWhether the header shows the close button.booleantrue
styleInline style of the container element, a style object or a string.JSX.CSSProperties | string-
titleTitle in the header; any content.JSX.Element-
visibleWhether the container is shown.boolean-

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.JSX.Element-
closeOnEscWhether pressing Esc calls onCancel.boolean-
containerRefReceives the .semi-sidebar-container element, as ref does.TypeRef<HTMLDivElement>-
customOptionsCustom MCP tools, listed on the custom tab.MCPSolidOption[]-
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: MCPSolidOption) => 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.Type(e: MouseEvent) => void-
onCancelCalled when the close button is clicked, or when Esc is pressed while closeOnEsc is true.Type(e: MouseEvent | KeyboardEvent) => void-
onConfigureClickCalled when the configure button of a built-in tool is clicked, with the event and the option.Type(e: MouseEvent, option: MCPSolidOption) => void-
onEditClickCalled when the edit button of a custom tool is clicked, with the event and the option.Type(e: MouseEvent, option: MCPSolidOption) => 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; write the new list back to options or customOptions yourself.Type(options: MCPSolidOption[], custom: boolean) => void-
optionsBuilt-in MCP tools; each item needs a unique value.MCPSolidOption[]-
placeholderPlaceholder of the search input, the locale text by default.string-
renderDescRenders the description of a tool row, called with { option, custom }; without it the option's desc text is shown.Type(props: MCPRenderItemProps) => JSX.Element-
renderHeaderRenders the whole header; the built-in header is used when it returns no content.Type() => JSX.Element-
renderIconRenders the icon of a tool row, called with { option, custom }; without it the option's icon is rendered as an image URL.Type(props: MCPRenderItemProps) => JSX.Element-
renderItemRenders a whole tool row, called with { option, custom }.Type(props: MCPRenderItemProps) => JSX.Element-
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 container element, a style object or a string.JSX.CSSProperties | string-
titleTitle in the header, the locale's MCP configure text by default.JSX.Element-
visibleWhether the container is shown.boolean-

MCPConfigureOption

PropertyDescriptionTypeDefault
activeWhether the tool is activebooleanfalse
configureWhether to show the configure buttonbooleanfalse
descDescription text, matched by the built-in filter; use renderDesc for other contentstring-
disabledWhether the tool is disabled; when true, the user cannot change its active statebooleanfalse
iconImage URL of the icon; use renderIcon for other contentstring-
labelLabel textstring-
valueRequired. Identifies the tool in onStatusChange and keys its row, so it must be unique within the same 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.JSX.Element-
closeOnEscWhether pressing Esc calls onCancel.boolean-
containerRefReceives the .semi-sidebar-container element, as ref does.TypeRef<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.Type(e: MouseEvent | KeyboardEvent) => 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: MouseEvent, item: AnnotationItem) => void-
renderHeaderRenders the whole header; the built-in header is used when it returns no content.Type() => JSX.Element-
renderInfoHeaderRenders a group header after its icon, called with the group; without it the group's header text is shown. AnnotationContent's renderHeader, renamed beside the container's renderHeader.ContentProps['renderHeader']-
renderItemRenders a reference item, called with the item.Type(annotation: AnnotationItem) => JSX.Element-
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 container element, a style object or a string.JSX.CSSProperties | string-
titleTitle in the header, the locale's references text by default.JSX.Element-
visibleWhether the container is shown.boolean-

AnnotationContent

Props

PropertyDescriptionTypeDefault
activeKeyKeys of the expanded groups, one or several.string | 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: MouseEvent, item: AnnotationItem) => void-
renderHeaderRenders a group header after its icon, called with the group; without it the group's header text is shown.Type(info: AnnotationInfo) => JSX.Element-
renderItemRenders a reference item, called with the item.Type(annotation: AnnotationItem) => JSX.Element-

AnnotationInfo

PropertyDescriptionTypeDefault
annotationsReferences of the groupAnnotationItem[]-
headerPlain text of the group header; customize it with renderInfoHeader (Annotation) or renderHeader (AnnotationContent)string-
keyUnique identifier of the groupstring-

AnnotationItem

PropertyDescriptionTypeDefault
detailDetail or supplementary text (such as a video summary or a text note)string-
durationDuration, usually of a video, in secondsnumber-
imgImage URL (such as a video cover or a text illustration)string-
logoLogo image URL of the site or platformstring-
onClickCalled when the item is clicked, with the event and the item (without onClick); used instead of the Annotation's(event: MouseEvent, item: AnnotationItem) => void-
orderCitation number (used for ordering and for the number badge)number-
siteNameName of the site or platformstring-
titleTitlestring-
typeContent type'video' | 'text'-
urlResource link, opened in a new tab; an item without url renders a link without href that only a pointer can usestring-

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.JSX.Element-
closeOnEscWhether pressing Esc calls onCancel.boolean-
containerRefHas no effect; use ref to get the container element.TypeRef<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.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: 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: MouseEvent, mode: string) => void | Promise<unknown>-
onCancelCalled when the close button is clicked, or when Esc is pressed while closeOnEsc is true.Type(e: MouseEvent | KeyboardEvent) => 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: 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 and a key; the icons come from renderOptionIcon.Option[]-
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) => JSX.Element-
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) => JSX.Element-
renderHeaderHas no effect; use title for the main view and renderDetailHeader for detail views.Type() => JSX.Element-
renderMainContentRenders the main view, called with the selected activeKey.Type(activeKey: string) => JSX.Element-
renderOptionIconRenders the icon of a built-in option button, called with the option.Type(option: Option) => JSX.Element-
renderOptionItemRenders an option button; the second argument, onChange, selects the option.Type(option: Option, onChange: (e: MouseEvent, activeKey: string) => void) => JSX.Element-
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 container element, a style object or a string.JSX.CSSProperties | string-
titleTitle of the main view; detail views use the detail header.JSX.Element-
visibleWhether the sidebar is shown.boolean-

SideBarOption

PropertyDescriptionTypeDefault
keyUnique identifier of the optionstring-
nameOption name; use renderOptionIcon for the iconstring-

CodeContent

Each item of codes is the props of CodeItem plus a key identifying its panel.

Props

PropertyDescriptionTypeDefault
activeKeyKeys of the expanded code panels, one or several.string | 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 code; the button is shown only when this is set.Type(e: MouseEvent, code: CodeItemProps, mode: string) => void-

CodeItem

Props

PropertyDescriptionTypeDefault
codeHighlightPropsProps of CodeHighlight, spread after code and language.TypePartial<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, width, height 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-

class, style and native attributes land on the root div.

FileContent

Each item of files is the props of FileItem plus a key identifying its panel. They are rendered read-only, and extensions and imgUploadProps reach their previews.

Props

PropertyDescriptionTypeDefault
activeKeyKeys of the expanded file panels, one or several.string | string[]-
filesThe file entries, rendered read-only: each is the FileItem props plus a key identifying its panel.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 file; the button is shown only when this is set.Type(e: MouseEvent, file: FileItemProps, mode: string) => void-

FileItem

Props

PropertyDescriptionTypeDefault
contentRich text as HTML; a change after mount is synced to the editor without calling onContentChange.string-
editableWhether the content is editable; a change after mount is synced to the editor.booleantrue
extensionstiptap extensions appended to the built-in ones, read when the editor is created.Extensions-
imgUploadPropsImage upload options, read when the editor is created.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-

class, style and native attributes land on the root div.