展示类

滑动侧边栏

可从屏幕边沿滑出的浮层面板,通常用于承载二级操作页面

代码演示

如何引入

import { Banner, Button, Form, FormDatePicker, FormRadio, FormRadioGroup, FormSelect, Radio, RadioGroup, SideSheet, TextArea, Typography } from '@fibrous/semi-ui-solid';

基本

默认侧边栏从右滑出,支持点击遮罩区关闭。

用受控的 visible 控制显示:关闭按钮、遮罩和 Esc 关闭时先触发 onCancel,面板不会自行关闭,需要在 onCancel 中把 visible 设为 false。

代码
import { createSignal } from 'solid-js';
import { SideSheet, Button } from '@fibrous/semi-ui-solid';

export default function SideSheetDemo() {
    const [visible, setVisible] = createSignal(false);
    const change = () => {
        setVisible(!visible());
    };
    return (
        <>
            <Button onClick={change}>Open SideSheet</Button>
            <SideSheet title="滑动侧边栏" visible={visible()} onCancel={change}>
                <p>This is the content of a basic sidesheet.</p>
                <p>Here is more content...</p>
            </SideSheet>
        </>
    );
}

自定义位置

可以通过设置 placement 属性设置侧边栏滑出位置,支持 top、bottom、left、right。



代码
import { createSignal } from 'solid-js';
import { SideSheet, RadioGroup, Radio, Button, type SideSheetProps, type RadioChangeEvent } from '@fibrous/semi-ui-solid';

export default function SideSheetDemo() {
    const [visible, setVisible] = createSignal(false);
    const change = () => {
        setVisible(!visible());
    };
    const [placement, setPlacement] = createSignal<SideSheetProps['placement']>('right');
    const changePlacement = (e: RadioChangeEvent) => {
        setPlacement(e.target.value as SideSheetProps['placement']);
    };
    return (
        <>
            <RadioGroup aria-label="侧边栏位置" onChange={changePlacement} value={placement()}>
                <Radio value="right">right</Radio>
                <Radio value="left">left</Radio>
                <Radio value="top">top</Radio>
                <Radio value="bottom">bottom</Radio>
            </RadioGroup>
            <br />
            <br />
            <Button onClick={change}>Open SideSheet</Button>
            <SideSheet title="滑动侧边栏" visible={visible()} onCancel={change} placement={placement()}>
                <p>This is the content of a basic sidesheet.</p>
                <p>Here is more content...</p>
            </SideSheet>
        </>
    );
}

自定义尺寸

可以通过设置 size 属性设置侧边栏尺寸,支持 small(448px)、medium(684px)、large(920px),仅在 placement 为 left 或 right 时生效。若默认的尺寸不满足你的需求,你还可以通过设置 width 属性自行设置宽度,例如 width={900} / width="800px"



代码
import { createSignal } from 'solid-js';
import { SideSheet, RadioGroup, Radio, Button, type SideSheetProps, type RadioChangeEvent } from '@fibrous/semi-ui-solid';

export default function SideSheetDemo() {
    const [visible, setVisible] = createSignal(false);
    const change = () => {
        setVisible(!visible());
    };
    const [size, setSize] = createSignal<SideSheetProps['size']>('small');
    const changeSize = (e: RadioChangeEvent) => {
        setSize(e.target.value as SideSheetProps['size']);
    };
    return (
        <>
            <RadioGroup aria-label="侧边栏尺寸" onChange={changeSize} value={size()}>
                <Radio value="small">small</Radio>
                <Radio value="medium">medium</Radio>
                <Radio value="large">large</Radio>
            </RadioGroup>
            <br />
            <br />
            <Button onClick={change}>Open SideSheet</Button>
            <SideSheet title="滑动侧边栏" visible={visible()} onCancel={change} size={size()}>
                <p>This is the content of a basic sidesheet.</p>
                <p>Here is more content...</p>
            </SideSheet>
        </>
    );
}

可操作的外部区域

当 mask={false} 时允许对外部区域进行操作。

代码
import { createSignal } from 'solid-js';
import { SideSheet, TextArea, Button } from '@fibrous/semi-ui-solid';

export default function SideSheetDemo() {
    const [visible, setVisible] = createSignal(false);
    const [value, setValue] = createSignal('');
    return (
        <>
            <Button onClick={() => setVisible(true)}>Open SideSheet</Button>
            <TextArea placeholder="Please enter something" onChange={(value) => setValue(value)} style={{ 'margin-top': '12px' }} />
            <SideSheet title="可操作外部的侧边栏" visible={visible()} onCancel={() => setVisible(false)} mask={false} disableScroll={false}>
                <p>这里是输入的内容:</p>
                <p>{value()}</p>
            </SideSheet>
        </>
    );
}

渲染在指定容器

可以通过 getPopupContainer 指定父级 DOM,弹层将会渲染至该 DOM 中。

Render in this

代码
import { createSignal } from 'solid-js';
import { SideSheet, Button } from '@fibrous/semi-ui-solid';

export default function SideSheetDemo() {
    const [visible, setVisible] = createSignal(false);
    const getContainer = () => {
        return document.querySelector('.sidesheet-container') as HTMLElement;
    };
    return (
        <div
            style={{
                height: '320px',
                overflow: 'hidden',
                position: 'relative',
                border: '1px solid var(--semi-color-border)',
                'border-radius': '2px',
                padding: '24px',
                'text-align': 'center',
                background: 'var(--semi-color-fill-0)',
            }}
            class="sidesheet-container"
        >
            <span>Render in this</span>
            <br />
            <br />
            <Button onClick={() => setVisible(true)}>Open SideSheet</Button>
            <SideSheet title="渲染在指定容器内部" visible={visible()} onCancel={() => setVisible(false)} width={220} getPopupContainer={getContainer}>
                <p>This is the content of a basic sidesheet.</p>
                <p>Here is more content...</p>
            </SideSheet>
        </div>
    );
}

自定义内容区域

可以通过自定义 title、footer 等属性创建出丰富的内容样式,这些属性接受 JSX 元素。

代码
import { createSignal } from 'solid-js';
import { Banner, Button, Form, FormDatePicker, FormRadio, FormRadioGroup, FormSelect, SideSheet, Typography } from '@fibrous/semi-ui-solid';

export default function SideSheetDemo() {
    const [visible, setVisible] = createSignal(false);
    const createTime = new Date();

    const show = () => {
        setVisible(true);
    };

    const handleCancel = () => {
        setVisible(false);
    };

    return (
        <>
            <Button onClick={show}>More Information</Button>
            <SideSheet
                title="创建资源包"
                headerStyle={{ 'border-bottom': '1px solid var(--semi-color-border)' }}
                bodyStyle={{ 'border-bottom': '1px solid var(--semi-color-border)' }}
                visible={visible()}
                footer={
                    <div style={{ display: 'flex', 'justify-content': 'flex-end' }}>
                        <Button style={{ 'margin-right': '8px' }}>重置</Button>
                        <Button theme="solid">提交</Button>
                    </div>
                }
                closable={false}
                onCancel={handleCancel}
            >
                <Form>
                    <FormDatePicker field="date" type="dateTime" initValue={createTime} style={{ width: '272px' }} label={{ text: '创建时间', required: true }} />
                    <FormRadioGroup field="type" label="目标操作系统" direction="horizontal" initValue="all">
                        <FormRadio value="all">全平台</FormRadio>
                        <FormRadio value="ios">iOS</FormRadio>
                        <FormRadio value="android">Android</FormRadio>
                        <FormRadio value="web">Web</FormRadio>
                    </FormRadioGroup>
                    <FormRadioGroup field="origin" label="资源包来源" direction="horizontal" initValue="scm">
                        <FormRadio value="scm">从SCM上传</FormRadio>
                        <FormRadio value="manual">手动上传</FormRadio>
                    </FormRadioGroup>
                    <Banner
                        fullMode={false}
                        icon={null}
                        type="warning"
                        bordered
                        description={
                            <>
                                <Typography.Text strong>当前部署环境:线上部署</Typography.Text>
                                <br />
                                <Typography.Text>请选择正确的SCM构建产物,防止出现不符合预期的发布操作。</Typography.Text>
                            </>
                        }
                    />
                    <br />
                    <FormSelect field="users" label={{ text: '创建用户', required: true }} style={{ width: '560px' }} multiple initValue={['1', '2', '3', '4']}>
                        <FormSelect.Option value="1">曲晨一</FormSelect.Option>
                        <FormSelect.Option value="2">夏可曼</FormSelect.Option>
                        <FormSelect.Option value="3">曲晨三</FormSelect.Option>
                        <FormSelect.Option value="4">蔡妍</FormSelect.Option>
                    </FormSelect>
                </Form>
            </SideSheet>
        </>
    );
}

API 参考

Props

属性说明类型默认值
afterVisibleChange面板显示时在 visible 变为 true 的那次更新中立即触发,不等进场动画;隐藏时在离场动画结束后触发;显示期间被卸载时以 false 触发一次。Type(isVisible: boolean) => void-
bodyStyle面板内容区的样式,可以是对象(键用 kebab-case,如 'border-bottom')或字符串。JSX.CSSProperties | string-
canVerticalSetWidthplacement 为 top、bottom 时 width 是否生效;默认这两种位置横向铺满。boolean-
closable是否显示右上角的关闭按钮。booleantrue
closeIcon关闭按钮中的图标;传入 null 或 undefined 时回落到默认图标,隐藏关闭按钮请用 closable={false}。JSX.Element-
closeOnEsc是否允许按 Esc 关闭面板。booleanfalse
disableScroll带遮罩且渲染在 document.body 中时是否禁止 body 滚动,即给 body 添加 overflow: hidden。booleantrue
footer面板底部,传入 JSX 元素;内容为空时不渲染底部。JSX.Elementnull
getPopupContainer指定弹层渲染到的父级 DOM。自定义容器需要设置 position: relative;这会改变浮层的 DOM 位置,不改变视图位置。Type() => HTMLElement-
headerStyle面板头部的样式,写法同 bodyStyle。JSX.CSSProperties | string-
height高度,placement 为 top、bottom 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时为 448。number | string-
keepDOM关闭时是否保留面板内部节点而不销毁。booleanfalse
mask是否显示遮罩;为 false 时允许操作外部区域,且不锁定 body 滚动。booleantrue
maskClosable是否允许点击遮罩关闭面板。booleantrue
maskStyle遮罩的样式,写法同 bodyStyle。JSX.CSSProperties | string-
motion是否开启动画。booleantrue
onCancel用户通过关闭按钮、遮罩或 Esc 关闭面板时触发,参数为原生 MouseEvent,按 Esc 时为原生 KeyboardEvent。面板不会自行关闭,需要在回调中把 visible 设为 false。Type(e: MouseEvent | KeyboardEvent) => void-
placement面板滑出的位置,支持 top、bottom、left、right。Placement"right"
size尺寸,small 为 448px、medium 为 684px、large 为 920px,仅 placement 为 left、right 时生效。'small' | 'medium' | 'large'"small"
style对话框元素(.semi-sidesheet-inner)的样式,与组件计算出的 width、height 合并,计算出的尺寸优先。JSX.CSSProperties | string-
title标题,传入 JSX 元素;内容为空时不渲染标题,并作为对话框的可访问名称。JSX.Element-
visible面板是否可见,受控属性;关闭时需在 onCancel 中同步设为 false。booleanfalse
width宽度,placement 为 left、right 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时由 size 决定。number | string-
zIndex弹层的 z-index。number1000

aria-* 与 style 落在 role="dialog" 的元素(.semi-sidesheet-inner)上;class、ref 与其他原生属性和事件落在 portal 内最外层的 .semi-sidesheet 元素上。

Accessibility

ARIA

  • SideSheet 具有 dialog role 来表示它是一个弹窗组件。标题元素(.semi-sidesheet-title)具有 heading role 和 aria-level="2",并通过 aria-labelledby 为对话框命名;header 本身没有 role。传入 aria-label 时以 aria-label 为准,不生成该引用。
  • 全部 aria-* 属性与 style 落在 role="dialog" 的元素上,其他未声明的属性落在外层元素上。
  • aria-modal 只在对话框是模态时输出。模态的判定是:有遮罩(mask),且渲染在 body 中(以实际容器判断,ConfigProvider 的 getPopupContainer 同样计入)。mask 为 false,或遮罩限定在自定义容器内时不输出。

键盘和焦点

  • 打开时焦点移入侧边栏。
  • 设置 closeOnEsc 后,按 Esc 关闭侧边栏(默认关闭该行为)。
  • 模态对话框(带遮罩且渲染在 body 中):Tab 在侧边栏内循环,页面其余部分不可交互,关闭后焦点回到打开前的元素。带遮罩但渲染在自定义容器内的侧边栏不会让页面不可交互,见下一条。
  • 非模态对话框(mask 为 false,或遮罩限定在自定义容器内):Tab 可以离开侧边栏回到页面,容器外的页面保持可用;关闭时焦点仍在侧边栏内或已落到 body 才回到打开前的元素,用户已移到页面其他位置的焦点不动。遮罩限定在自定义容器内时,容器内的其他内容仍可被 Tab 聚焦到。

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$animation_duration-sideSheet_mask-show180ms侧边栏打开时-蒙层颜色-动画持续时间
$animation_function-sideSheet_mask-showcubic-bezier(0.25, 0.46, 0.45, 0.94)侧边栏打开时-蒙层颜色-过渡曲线
$animation_delay-sideSheet_mask-show0ms侧边栏打开时-蒙层颜色-延迟时间
$animation_duration-sideSheet_mask-hide180ms侧边栏关闭时-蒙层颜色-动画持续时间
$animation_function-sideSheet_mask-hidecubic-bezier(0.25, 0.46, 0.45, 0.94)侧边栏关闭时-蒙层颜色-过渡曲线
$animation_delay-sideSheet_mask-hide0ms侧边栏关闭时-蒙层颜色-延迟时间
$animation_duration-sideSheet_inner-show180ms侧边栏打开-动画持续时间
$animation_function-sideSheet_inner-showcubic-bezier(0.25, 0.46, 0.45, 0.94)侧边栏打开-过渡曲线
$animation_delay-sideSheet_inner-show0ms侧边栏打开-延迟时间
$animation_duration-sideSheet_inner-hide180ms侧边栏关闭-动画持续时间
$animation_function-sideSheet_inner-hidecubic-bezier(0.25, 0.46, 0.45, 0.94)侧边栏关闭-过渡曲线
$animation_delay-sideSheet_inner-hide0ms侧边栏关闭-延迟时间
$color-sideSheet_mask-bgvar(--semi-color-overlay-bg)侧边栏打开后蒙层颜色
$color-sideSheet-bgvar(--semi-color-bg-2)侧边栏背景颜色
$color-sideSheet_main-textvar(--semi-color-text-0)侧边栏默认文本颜色
$color-sideSheet_header-borderBottomvar(--semi-color-border)侧边栏 header 底部边框颜色
$spacing-sideSheet-margin0侧边栏整体外边距
$spacing-sideSheet_title-margin0侧边栏标题外边距
$spacing-sideSheet_header-padding$spacing-loose侧边栏 header 内边距
$spacing-sideSheet_header-paddingBottom$spacing-base-loose侧边栏 header 底部内边距
$spacing-sideSheet_body-paddingY0侧边栏 body 垂直内边距
$spacing-sideSheet_body-paddingX$spacing-loose侧边栏 body 水平内边距
$spacing-sideSheet_footer-padding$spacing-loose侧边栏 footer 内边距
$width-sideSheet_header-borderBottom0px侧边栏 header 底部边框宽度
$width-sideSheet_size-small448px小尺寸侧边栏宽度
$width-sideSheet_size-medium684px中尺寸侧边栏宽度
$width-sideSheet_size-large920px大尺寸侧边栏宽度
$font-sideSheet_title-fontWeight$font-weight-bold侧边栏标题文本字重
$font-sideSheet_title-fontSize$font-size-header-5侧边栏标题文本字号
$filter-sideSheet_mask-bgnone
$filter-sideSheet-bgnone

FAQ:

  • SideSheet 会自动禁止 body 的滚动吗?当带遮罩的 SideSheet 默认渲染在 body 中时(即不传入 getPopupContainer 参数),会在打开时自动给 body 添加 overflow: hidden 来禁止滚动。可以将 disableScroll 设为 false 允许滚动;mask 为 false 时不锁定滚动。