Show · SideSheet

SideSheet

An overlay panel that slides out from the edge of the screen, typically used to host secondary action pages.

Demos

How to import

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

Basic Usage

By default, SideSheet slides from the right side of the screen and could be closed by clicking on the mask.

Control visibility with the controlled visible prop: the close button, the mask and Esc trigger onCancel first, and the sheet does not close by itself; set visible to false in the onCancel handler.

Code
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

You could use placement to set the position from which SideSheet comes in, supporting one of top, bottom, left, right.



Code
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

You could use size to set the size of SideSheet, supporting one of small(448px), medium(684px), and large(920px). Only takes effects when placement is set to left or right. If the default size does not meet your needs, you can also set the width by setting the width property, for example width={900} / width="800px"



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

Non-blocking Workflow

You could set mask={false} to continue working on the area outside SideSheet.

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

Rendered Inside Container

You could use getPopupContainer to render SideSheet in targeted DOM.

Render in this

Code
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>
    );
}

Customized Content

Use the title, footer and other props, which take JSX elements, together with Semi Components to create customized information display layers.

Code
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 Reference

Props

PropertyDescriptionTypeDefault
afterVisibleChangeCalled right in the update where visible becomes true, without waiting for the enter animation. When hiding, called after the leave animation ends, and once with false when unmounted while shown.Type(isVisible: boolean) => void-
bodyStyleStyle of the content area, as an object with kebab-case keys (such as 'border-bottom') or as a string.JSX.CSSProperties | string-
canVerticalSetWidthWhether width applies when placement is top or bottom. By default those sheets span the full width.boolean-
closableWhether the close button in the top right corner is shown.booleantrue
closeIconIcon inside the close button. null or undefined falls back to the default icon; hide the button with closable={false}.JSX.Element-
closeOnEscWhether Esc closes the sheet.booleanfalse
disableScrollWhether body scrolling is locked (overflow: hidden on body) while a masked sheet is rendered in document.body.booleantrue
footerFooter of the sheet, a JSX element. Nothing is rendered when it is empty.JSX.Elementnull
getPopupContainerParent element the sheet is rendered into. A custom container needs position: relative; this changes where the sheet sits in the DOM, not where it is shown.Type() => HTMLElement-
headerStyleStyle of the header, in the same form as bodyStyle.JSX.CSSProperties | string-
heightHeight, used when placement is top or bottom. A number is in pixels; a string is any valid CSS length. 448 when omitted.number | string-
keepDOMWhether the sheet content stays mounted while closed.booleanfalse
maskWhether the mask is shown. When false the content outside stays operable, and body scrolling is not locked.booleantrue
maskClosableWhether clicking the mask closes the sheet.booleantrue
maskStyleStyle of the mask, in the same form as bodyStyle.JSX.CSSProperties | string-
motionWhether animation is enabled.booleantrue
onCancelCalled when the user closes the sheet with the close button, the mask or Esc. The argument is the native MouseEvent, or the native KeyboardEvent for Esc. The sheet does not close by itself; set visible to false in the callback.Type(e: MouseEvent | KeyboardEvent) => void-
placementEdge the sheet slides in from: top, bottom, left or right.Placement"right"
sizeSize: small is 448px, medium 684px, large 920px. Used only when placement is left or right.'small' | 'medium' | 'large'"small"
styleStyle of the dialog element (.semi-sidesheet-inner), merged with the computed width and height, which take precedence.JSX.CSSProperties | string-
titleTitle, a JSX element. Nothing is rendered when it is empty, and it names the dialog.JSX.Element-
visibleWhether the sheet is visible. A controlled prop: set it to false in onCancel to close the sheet.booleanfalse
widthWidth, used when placement is left or right. A number is in pixels; a string is any valid CSS length. Decided by size when omitted.number | string-
zIndexz-index of the layer.number1000

aria-* attributes and style land on the role="dialog" element (.semi-sidesheet-inner); class, ref and other native attributes and events land on the outermost .semi-sidesheet element inside the portal.

Accessibility

ARIA

  • SideSheet has a dialog role to indicate that it is a pop-up component. The title element (.semi-sidesheet-title) has the heading role and aria-level="2" and names the dialog through aria-labelledby; the header itself has no role. When aria-label is passed it wins and that reference is not generated.
  • Every aria-* attribute and style land on the role="dialog" element; other undeclared props land on the outer element.
  • aria-modal is output only when the dialog is modal. A dialog is modal when it has a mask (mask) and is rendered in the body (judged by the actual container, so the getPopupContainer of ConfigProvider counts). With mask set to false, or a mask confined to a custom container, it is not output.

Keyboard and focus

  • Focus moves into the sheet when it opens.
  • With closeOnEsc set, pressing Esc closes the sheet (off by default).
  • A modal dialog (with a mask, rendered in the body): Tab cycles inside the sheet, the rest of the page is inert, and focus returns to the element focused before it opened when it closes. A sheet with a mask rendered in a custom container does not make the page inert; see the next item.
  • A non-modal dialog (mask set to false, or a mask confined to a custom container): Tab can leave the sheet for the page and the page outside the container stays usable; focus returns on close only if it is still inside the sheet or has fallen to the body, and focus the user has moved elsewhere on the page stays. With a mask confined to a custom container, other content inside that container can still be reached by Tab.

Design Tokens

These are the component's internal Sass variables and cannot be overridden. To customize styles, override the --semi-* CSS variables (see the Design Token page) or write unlayered CSS rules.
Sass variableValueUsage
$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

  • Does SideSheet disable body scrolling automatically? When a SideSheet with a mask is rendered in the body by default (no getPopupContainer), it adds overflow: hidden to the body on open to block scrolling. Set disableScroll to false to allow scrolling; scrolling is not locked when mask is false.