AI 组件

侧边信息栏

展示 AI 场景下的配置和详情信息

使用场景

侧边信息栏主要用于在 AI 场景下,用于信息展示,功能配置。包括 MCP 配置、参考来源、代码预览、富文本预览及编辑等功能

代码演示

如何引入

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

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

基础容器

侧边信息栏的基础容器,基础容器是MCP 配置、参考来源、以及侧边消息栏的基础容器。

  • visible 配合 onCancel 使用控制显示和隐藏
  • title 设置标题,可传任意内容;需要自定义整个头部时使用 renderHeader
  • motion 设置是否有展开动画
  • resizable 设置宽度是否可伸缩,为 true 时候可通过 minWidth,maxWidth 设置最小,最大宽度
是否有动画
是否可伸缩
基础容器示例
代码
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 配置

使用 MCPConfigure 进行 MCP 工具的展示、启用/关闭、配置及搜索

  • 通过 options 和 customOptions 设置内置的 MCP 工具和自定义 MCP 工具
  • 通过 visible 设置组件的显示和隐藏, 使用 onCancel 监听用户的关闭行为
  • 使用 onStatusChange 处理 MCP 工具的启用/关闭,组件不会自行修改传入的列表,需把回调参数写回 options 或 customOptions
  • 使用 onAddClick 处理自定义 MCP 页的点击添加按钮后的操作
  • 可通过 onConfigureClick 监听内置 MCP 工具的配置,通过 onEditClick 监听自定义 MCP 工具的配置
  • 工具项的图标和描述使用 renderIcon、renderDesc 渲染,整行用 renderItem 替换;参数为 { option, custom }

其他 MCPConfigure 的 API 见 MCPConfigure

传入 MCP 工具项的类型如下

interface MCPConfigureOption {
    icon?: string; // 图片地址,其他内容使用 renderIcon
    label?: string;
    value: string; // 必填,标识工具项,需唯一
    desc?: string; // 纯文本,其他内容使用 renderDesc
    active?: boolean;
    disabled?: boolean;
    configure?: boolean; // 是否显示配置按钮
}

用例如下:

MCP 配置
已激活 MCP 数: 0/2
支持 Semi 的文档、源码搜索,辅助开发
Figma MCP Server 连接Figma与AI开发工具的功能。它通过标准化的模型上下文协MCP),将组件、变量等设计数据和上下文暴露给AI,从而实现从设计稿到代码的智能生成,显著提升开发效率。
代码
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>
    );
}

参考来源

使用 Annotation 组件可以管理参考来源的展示

  • activeKey 配合 onChange 管理当前展开的项
  • info 设置参考来源的具体内容
  • onClick 在点击某条参考来源时触发,参数为事件和该条目
  • 分组标题使用 renderInfoHeader 渲染,条目使用 renderItem 替换;renderHeader 是整个容器头部
代码
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>
    );
}

代码展示

可通过 Sidebar 中的 CodeItem 展示代码,CodeItem 基于 JsonViewer 以及 CodeHighlight 实现。

  • content: 内容字符串
  • isJson: 是否为 json
  • language: 编程语言名称,同 CodeHighlight 的 language,需先通过 CodeHighlight.register 注册
  • jsonViewerProps: 配置其他 JsonViewer 参数
  • codeHighlightProps: 配置其他 CodeHighlight 参数

jsonViewerProps 与 codeHighlightProps 是 Solid 版 JsonViewer 与 CodeHighlight 的属性,晚于内置默认值展开,因此其中的 value、width、height 等会覆盖默认值,也可以带 class、style。

代码
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>
        </>
    );
}

代码列表

用户可通过 Sidebar 中的 CodeContent 组件展示代码列表信息。

  • activeKey 配合 onChange 管理当前展开的面板
  • onExpand 监听面板头部的展开按钮,设置后才显示展开按钮
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 }}
        />
    );
};
代码
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>
    );
}

富文本编辑器

可通过 Sidebar 中的 FileItem 查看、编辑富文本内容, FileItem 基于 tiptap 实现。

  • content:富文本 HTML。挂载后变化会同步到编辑器,与编辑器当前内容相同时不会重置,不会打断输入也不会丢失光标,同步不触发 onContentChange
  • editable: 设置是否可编辑,挂载后变化会同步到编辑器
  • imgUploadProps: 设置图片上传路径以及图片上传后 src 的设置,通过 action 设置上传地址,通过 getUploadImageSrc(response) 函数根据上传响应返回图片上传后地址(未提供时,响应是字符串则直接作为地址,否则取 response.src),用于富文本中 img 节点的 src;Upload 的 on 开头回调(如 onSuccess)也可在这里传入。只有设置了 action 或 customRequest,工具栏才显示图片按钮
  • extensions 与 imgUploadProps 只在创建编辑器时读取,要换用新值时重新挂载组件,例如用 <Show when={版本} keyed> 包裹
  • onContentChange 在编辑后触发,参数为 HTML 字符串


代码
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} />
        </>
    );
}

富文本列表

用户可通过 Sidebar 中的 FileContent 组件展示富文本列表信息。

  • activeKey 配合 onChange 管理当前展开的面板
  • onExpand 监听面板头部的展开按钮,设置后才显示展开按钮
代码
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>
    );
}

侧边信息栏

侧边信息栏 Sidebar 有主视图(mode 为 main)和详情视图(mode 为 code、file 及其他)。

主视图可以通过 title 配置标题,通过 showClose 决定是否展示关闭按钮,通过 options 设置顶部的按钮组(每项为 { name, key }),activeKey 配合 onActiveOptionChange 管理当前选中的项。按钮图标使用 renderOptionIcon 渲染,需要替换整个按钮时使用 renderOptionItem,其第二个参数 onChange 用于选中该项。

对于主视图的内容渲染,可通过 renderMainContent 传入渲染函数,参数为当前选中的 activeKey。可使用 Annotation 中的 AnnotationContent 渲染参考来源,使用 Sidebar 中内置的 FileContent 渲染富文本, CodeContent 渲染代码。渲染函数会随其中读取的信号重新执行,面板状态等信号建议只在传给子组件的属性中读取,避免内容整体重建。

对于详情视图,如果是富文本或者代码,分别设置 mode 为 code、file,通过 detailContent 设置显示内容,即可通过内置的代码显示组件, 富文本编辑器进行显示;detailContent 的 key 变化时会重新挂载。如果想要自定义详情视图的展示,则通过 renderDetailContent 自行渲染,详情头部通过 renderDetailHeader 自定义,两者返回内容为空时回退到内置内容。点击详情头部的返回按钮触发 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 }}
        />
    );
};
代码
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 参考

Sidebar、Annotation、MCPConfigure 与 Container 的 class、style、ref 和其他未声明的属性(如 aria-label、id、data-*)落在 .semi-sidebar-container 元素上;根元素被命名(aria-label 或 aria-labelledby)且没有指定 role 时,role 为 group。CodeContent、FileContent、AnnotationContent 的这些属性落在各自的根元素(折叠面板)上,CodeItem 与 FileItem 的落在各自的根元素 div 上。

Container

Props

属性说明类型默认值
afterVisibleChange可见状态变化完成后的回调,参数为当前是否可见;有动画时在动画结束后触发。Type(isVisible: boolean) => void-
children容器的内容。JSX.Element-
closeOnEsc按下 Esc 键时是否触发 onCancel。boolean-
containerRef接收 .semi-sidebar-container 元素,作用同 ref。TypeRef<HTMLDivElement>-
defaultSize初始尺寸(宽度与高度),仅在 resizable 为 true 时生效。{ width?: number | string; height?: number | string }-
maxWidth可调整的最大宽度,仅在 resizable 为 true 时生效。string | number-
minWidth可调整的最小宽度,仅在 resizable 为 true 时生效。string | number150
motion显示与隐藏时是否使用展开动画。booleantrue
onCancel点击关闭按钮,或在 closeOnEsc 为 true 时按下 Esc 触发。Type(e: MouseEvent | KeyboardEvent) => void-
renderHeader自定义整个头部;返回内容为空时使用内置头部。Type() => JSX.Element-
resizable宽度是否可拖拽调整;为 true 时 minWidth、maxWidth 与 defaultSize 生效。booleantrue
showClose是否显示头部的关闭按钮。booleantrue
style容器元素的内联样式,可传样式对象或样式字符串。JSX.CSSProperties | string-
title头部标题,可传任意内容。JSX.Element-
visible是否显示。boolean-

MCPConfigure

Props

属性说明类型默认值
afterVisibleChange可见状态变化完成后的回调,参数为当前是否可见;有动画时在动画结束后触发。Type(isVisible: boolean) => void-
children不生效,内容由 MCP 工具列表渲染。JSX.Element-
closeOnEsc按下 Esc 键时是否触发 onCancel。boolean-
containerRef接收 .semi-sidebar-container 元素,作用同 ref。TypeRef<HTMLDivElement>-
customOptions自定义 MCP 工具列表,在自定义页签中展示。MCPSolidOption[]-
defaultSize初始尺寸(宽度与高度),仅在 resizable 为 true 时生效。{ width?: number | string; height?: number | string }-
filter自定义搜索过滤,参数为转为小写的输入值和选项;默认按 label 与 desc 匹配。Type(inputValue: string, option: MCPSolidOption) => boolean-
maxWidth可调整的最大宽度,仅在 resizable 为 true 时生效。string | number-
minWidth可调整的最小宽度,仅在 resizable 为 true 时生效,默认 150。string | number-
motion显示与隐藏时是否使用展开动画,默认 true。boolean-
onAddClick点击自定义页签中的新增按钮时触发。Type(e: MouseEvent) => void-
onCancel点击关闭按钮,或在 closeOnEsc 为 true 时按下 Esc 触发。Type(e: MouseEvent | KeyboardEvent) => void-
onConfigureClick点击内置 MCP 工具的配置按钮时触发,参数为事件和该选项。Type(e: MouseEvent, option: MCPSolidOption) => void-
onEditClick点击自定义 MCP 工具的编辑按钮时触发,参数为事件和该选项。Type(e: MouseEvent, option: MCPSolidOption) => void-
onSearch搜索时触发,参数为输入值和是否在自定义列表中搜索。Type(inputValue: string, custom: boolean) => void-
onStatusChange切换启用或关闭时触发,参数为切换后的完整选项列表和是否为自定义列表;需自行把新列表写回 options 或 customOptions。Type(options: MCPSolidOption[], custom: boolean) => void-
options内置 MCP 工具列表;每项的 value 必填且唯一。MCPSolidOption[]-
placeholder搜索框的占位文字,默认使用语言包文案。string-
renderDesc渲染工具项的描述,参数为 { option, custom };不传时显示选项的 desc 文本。Type(props: MCPRenderItemProps) => JSX.Element-
renderHeader自定义整个头部;返回内容为空时使用内置头部。Type() => JSX.Element-
renderIcon渲染工具项的图标,参数为 { option, custom };不传时选项的 icon 作为图片地址渲染。Type(props: MCPRenderItemProps) => JSX.Element-
renderItem自定义整行工具项,参数为 { option, custom }。Type(props: MCPRenderItemProps) => JSX.Element-
resizable宽度是否可拖拽调整,默认 true;为 true 时 minWidth、maxWidth 与 defaultSize 生效。boolean-
showClose是否显示头部的关闭按钮,默认 true。boolean-
style容器元素的内联样式,可传样式对象或样式字符串。JSX.CSSProperties | string-
title头部标题,默认使用语言包中的 MCP 配置。JSX.Element-
visible是否显示。boolean-

MCPConfigureOption

属性说明类型默认值
active是否处于激活状态booleanfalse
configure是否显示配置按钮booleanfalse
desc描述文本,内置过滤会匹配它;其他内容使用 renderDescstring-
disabled是否禁用,为 true 时用户无法更改激活状态booleanfalse
icon图标的图片地址;其他内容使用 renderIconstring-
label标签文本string-
value对应的值,必填。在 onStatusChange 中标识选项,也作为列表项的 key,同一列表内需唯一string-

Annotation

Props

属性说明类型默认值
activeKey当前展开的分组 key,支持单个或多个。string | string[]-
afterVisibleChange可见状态变化完成后的回调,参数为当前是否可见;有动画时在动画结束后触发。Type(isVisible: boolean) => void-
children不生效,内容由参考来源列表渲染。JSX.Element-
closeOnEsc按下 Esc 键时是否触发 onCancel。boolean-
containerRef接收 .semi-sidebar-container 元素,作用同 ref。TypeRef<HTMLDivElement>-
defaultSize初始尺寸(宽度与高度),仅在 resizable 为 true 时生效。{ width?: number | string; height?: number | string }-
info参考来源分组列表,每组包含 header、key 与 annotations。AnnotationInfo[]-
maxWidth可调整的最大宽度,仅在 resizable 为 true 时生效。string | number-
minWidth可调整的最小宽度,仅在 resizable 为 true 时生效,默认 150。string | number-
motion显示与隐藏时是否使用展开动画,默认 true。boolean-
onCancel点击关闭按钮,或在 closeOnEsc 为 true 时按下 Esc 触发。Type(e: MouseEvent | KeyboardEvent) => void-
onChange展开的分组变化时触发,参数为当前展开的 key。Type(key: string | string[]) => void-
onClick点击某条参考来源时触发,参数为事件和该条目。Type(e: MouseEvent, item: AnnotationItem) => void-
renderHeader自定义整个头部;返回内容为空时使用内置头部。Type() => JSX.Element-
renderInfoHeader渲染分组标题图标后的内容,参数为该分组;不传时显示分组的 header 文本。即 AnnotationContent 的 renderHeader,与容器的 renderHeader 区分。ContentProps['renderHeader']-
renderItem自定义参考来源条目的渲染,参数为该条目。Type(annotation: AnnotationItem) => JSX.Element-
resizable宽度是否可拖拽调整,默认 true;为 true 时 minWidth、maxWidth 与 defaultSize 生效。boolean-
showClose是否显示头部的关闭按钮,默认 true。boolean-
style容器元素的内联样式,可传样式对象或样式字符串。JSX.CSSProperties | string-
title头部标题,默认使用语言包中的参考来源。JSX.Element-
visible是否显示。boolean-

AnnotationContent

Props

属性说明类型默认值
activeKey当前展开的分组 key,支持单个或多个。string | string[]-
info参考来源分组列表,每组包含 header、key 与 annotations。AnnotationInfo[]-
onChange展开的分组变化时触发,参数为当前展开的 key。Type(key: string | string[]) => void-
onClick点击某条参考来源时触发,参数为事件和该条目。Type(e: MouseEvent, item: AnnotationItem) => void-
renderHeader渲染分组标题图标后的内容,参数为该分组;不传时显示分组的 header 文本。Type(info: AnnotationInfo) => JSX.Element-
renderItem自定义参考来源条目的渲染,参数为该条目。Type(annotation: AnnotationItem) => JSX.Element-

AnnotationInfo

属性说明类型默认值
annotations该分组下的参考来源条目AnnotationItem[]-
header分组标题的纯文本,也可用 renderInfoHeader(Annotation)或 renderHeader(AnnotationContent)自定义string-
key分组的唯一标识string-

AnnotationItem

属性说明类型默认值
detail内容详情/补充说明(如视频简介、文本备注等)string-
duration时长(通常为视频时长,单位:秒)number-
img图片地址(如视频封面图、文本配图地址)string-
logo站点/内容所属平台的 logo 图片地址string-
onClick点击该条目时触发,参数为事件和该条目(不含 onClick),优先于 Annotation 的 onClick(event: MouseEvent, item: AnnotationItem) => void-
order引用序号(用于内容排序/标注序号展示)number-
siteName内容所属的站点/平台名称string-
title内容标题string-
type内容类型'video' | 'text'-
url资源链接,在新标签页打开;没有 url 的条目渲染为无 href 的链接,只能用指针点击string-

Props

属性说明类型默认值
activeKey主视图中当前选中的选项 key。string-
afterVisibleChange可见状态变化完成后的回调,参数为当前是否可见;有动画时在动画结束后触发。Type(isVisible: boolean) => void-
children不生效,内容由主视图或详情视图渲染。JSX.Element-
closeOnEsc按下 Esc 键时是否触发 onCancel。boolean-
containerRef不生效,需要容器元素时使用 ref。TypeRef<HTMLDivElement>-
defaultSize初始尺寸(宽度与高度),仅在 resizable 为 true 时生效。{ width?: number | string; height?: number | string }-
detailContentcode 与 file 模式显示的条目,为 CodeItem 或 FileItem 的属性加 key;key 变化时重新挂载该条目。file 模式下其中的 editable、imgUploadProps 与 onContentChange 不生效,由 fileEditable、imgUploadProps 与 onFileContentChange 决定。DetailContent-
fileEditablefile 模式的富文本是否可编辑。booleantrue
imgUploadPropsfile 模式的图片上传配置,编辑器创建时读取,之后变化不生效。ImageUploadNodeOptions-
maxWidth可调整的最大宽度,仅在 resizable 为 true 时生效。string | number-
minWidth可调整的最小宽度,仅在 resizable 为 true 时生效,默认 150。string | number-
mode展示模式:main 为主视图,code 与 file 使用内置详情,其他字符串通过 renderDetailContent 自行渲染。string"main"
motion显示与隐藏时是否使用展开动画,默认 true。boolean-
onActiveOptionChange点击选项按钮时触发,参数为事件和该选项的 key;需自行把 key 写回 activeKey。Type(e: MouseEvent, activeKey: string) => void-
onBackWard点击详情头部的返回按钮时触发,参数为事件和 main;需自行把 mode 改回 main,返回值被忽略。Type(e: MouseEvent, mode: string) => void | Promise<unknown>-
onCancel点击关闭按钮,或在 closeOnEsc 为 true 时按下 Esc 触发。Type(e: MouseEvent | KeyboardEvent) => void-
onDetailContentCopy点击详情头部的复制按钮后触发,在写入剪贴板完成后异步调用,参数为事件、复制的内容和结果(写入被拒绝时为 false)。Type(e: MouseEvent, content: string, res: boolean) => void-
onFileContentChangefile 模式下编辑富文本后触发,参数为 HTML 字符串。Type(content: string) => void-
options主视图顶部的选项按钮,每项包含 name 与 key;图标由 renderOptionIcon 渲染。Option[]-
renderDetailContent自定义详情内容,参数为当前模式;返回内容为空时,code 与 file 模式回退到内置详情。Type(mode: string) => JSX.Element-
renderDetailHeader自定义详情头部,参数为当前模式和 detailContent;返回内容为空时使用内置头部。Type(mode: string, detailContent?: DetailContent) => JSX.Element-
renderHeader不生效,主视图使用 title,详情视图使用 renderDetailHeader。Type() => JSX.Element-
renderMainContent渲染主视图内容,参数为当前选中的 activeKey。Type(activeKey: string) => JSX.Element-
renderOptionIcon渲染内置选项按钮的图标,参数为该选项。Type(option: Option) => JSX.Element-
renderOptionItem自定义选项按钮,第二个参数 onChange 用于选中该选项。Type(option: Option, onChange: (e: MouseEvent, activeKey: string) => void) => JSX.Element-
resizable宽度是否可拖拽调整,默认 true;为 true 时 minWidth、maxWidth 与 defaultSize 生效。boolean-
showClose是否显示头部的关闭按钮,默认 true。boolean-
style容器元素的内联样式,可传样式对象或样式字符串。JSX.CSSProperties | string-
title主视图的标题;详情视图使用内置的详情头部。JSX.Element-
visible是否显示。boolean-

SideBarOption

属性说明类型默认值
key选项的唯一标识string-
name选项名称,图标使用 renderOptionIconstring-

CodeContent

codes 的每一项为 CodeItem 的属性加用于标识面板的 key。

Props

属性说明类型默认值
activeKey当前展开的代码面板 key,支持单个或多个。string | string[]-
codes代码条目列表,每项为 CodeItem 的属性加用于标识面板的 key。CodeItemProps[]-
onChange展开的面板变化时触发,参数为当前展开的 key。Type(activeKey: string | string[]) => void-
onExpand点击面板头部的展开按钮时触发,参数为事件、该代码条目和 code;设置后才显示展开按钮。Type(e: MouseEvent, code: CodeItemProps, mode: string) => void-

CodeItem

Props

属性说明类型默认值
codeHighlightPropsCodeHighlight 的属性,在 code 与 language 之后展开。TypePartial<CodeHighlightProps>-
content代码/文本内容。string-
isJson是否为 JSON 格式内容,为 true 时使用 JsonViewer 展示。boolean-
jsonViewerPropsJsonViewer 的属性,在 value、width、height 等默认值之后展开。TypePartial<JsonViewerProps>-
key面板的唯一标识,用于 CodeContent 的 activeKey,也决定 Sidebar 详情的重新挂载。string-
language代码语言类型(如 js、ts、css 等),同 CodeHighlight 的 language。string-
name名称,显示在面板头部与 Sidebar 详情头部。string-

class、style 与原生属性落在根元素 div 上。

FileContent

files 的每一项为 FileItem 的属性加用于标识面板的 key,以只读方式渲染,extensions 与 imgUploadProps 会传给其预览。

Props

属性说明类型默认值
activeKey当前展开的文件面板 key,支持单个或多个。string | string[]-
files文件条目列表,以只读方式渲染,每项为 FileItem 的属性加用于标识面板的 key。FileItemProps[]-
onChange展开的面板变化时触发,参数为当前展开的 key。Type(activeKey: string | string[]) => void-
onExpand点击面板头部的展开按钮时触发,参数为事件、该文件条目和 file;设置后才显示展开按钮。Type(e: MouseEvent, file: FileItemProps, mode: string) => void-

FileItem

Props

属性说明类型默认值
content富文本 HTML,挂载后变化会同步到编辑器,不触发 onContentChange。string-
editable是否可编辑,挂载后变化会同步到编辑器。booleantrue
extensions追加在内置扩展之后的 tiptap 扩展,编辑器创建时读取,之后变化不生效。Extensions-
imgUploadProps图片上传配置,编辑器创建时读取,之后变化不生效。ImageUploadNodeOptions-
key面板的唯一标识,用于 FileContent 的 activeKey,也决定 Sidebar 详情的重新挂载。string-
name名称,显示在面板头部、Sidebar 详情头部,并作为编辑区的 aria-label。string-
onContentChange编辑后触发,参数为 HTML 字符串。Type(content: string) => void-

class、style 与原生属性落在根元素 div 上。