展示类

滑动侧边栏

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

代码演示

如何引入

<script setup lang="ts">
    import { SideSheet } from '@fibrous/semi-ui-vue';
</script>

基本

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

用 v-model:visible 控制显示:关闭按钮、遮罩和 Esc 关闭时先更新 visible,再触发 @cancel。只写 :visible 加 @cancel 时不会自行关闭,需要在 @cancel 中把 visible 设为 false。

代码
<script setup lang="ts">
    import { ref } from 'vue';
    import { Button, SideSheet } from '@fibrous/semi-ui-vue';

    const visible = ref(false);
</script>

<template>
    <Button @click="visible = !visible">Open SideSheet</Button>
    <SideSheet v-model:visible="visible" title="滑动侧边栏">
        <p>This is the content of a basic sidesheet.</p>
        <p>Here is more content...</p>
    </SideSheet>
</template>

自定义位置

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



代码
<script setup lang="ts">
    import { ref } from 'vue';
    import { Button, Radio, RadioGroup, SideSheet, type RadioValue, type SideSheetProps } from '@fibrous/semi-ui-vue';

    const visible = ref(false);
    const placement = ref<RadioValue | undefined>('right');
</script>

<template>
    <RadioGroup v-model="placement" aria-label="侧边栏位置">
        <Radio value="right">right</Radio>
        <Radio value="left">left</Radio>
        <Radio value="top">top</Radio>
        <Radio value="bottom">bottom</Radio>
    </RadioGroup>
    <br />
    <br />
    <Button @click="visible = !visible">Open SideSheet</Button>
    <SideSheet v-model:visible="visible" :placement="placement as SideSheetProps['placement']" title="滑动侧边栏">
        <p>This is the content of a basic sidesheet.</p>
        <p>Here is more content...</p>
    </SideSheet>
</template>

自定义尺寸

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



代码
<script setup lang="ts">
    import { ref } from 'vue';
    import { Button, Radio, RadioGroup, SideSheet, type RadioValue, type SideSheetProps } from '@fibrous/semi-ui-vue';

    const visible = ref(false);
    const size = ref<RadioValue | undefined>('small');
</script>

<template>
    <RadioGroup v-model="size" aria-label="侧边栏尺寸">
        <Radio value="small">small</Radio>
        <Radio value="medium">medium</Radio>
        <Radio value="large">large</Radio>
    </RadioGroup>
    <br />
    <br />
    <Button @click="visible = !visible">Open SideSheet</Button>
    <SideSheet v-model:visible="visible" :size="size as SideSheetProps['size']" title="滑动侧边栏">
        <p>This is the content of a basic sidesheet.</p>
        <p>Here is more content...</p>
    </SideSheet>
</template>

可操作的外部区域

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

代码
<script setup lang="ts">
    import { ref } from 'vue';
    import { Button, SideSheet, TextArea } from '@fibrous/semi-ui-vue';

    const visible = ref(false);
    const value = ref('');
</script>

<template>
    <Button @click="visible = true">Open SideSheet</Button>
    <TextArea v-model="value" placeholder="Please enter something" style="margin-top: 12px" />
    <SideSheet v-model:visible="visible" :disableScroll="false" :mask="false" title="可操作外部的侧边栏">
        <p>这里是输入的内容:</p>
        <p>{{ value }}</p>
    </SideSheet>
</template>

渲染在指定容器

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

Render in this

代码
<script setup lang="ts">
    import { ref } from 'vue';
    import { Button, SideSheet } from '@fibrous/semi-ui-vue';

    const visible = ref(false);
    const getContainer = () => document.querySelector('.sidesheet-container') as HTMLElement;
</script>

<template>
    <div
        class="sidesheet-container"
        :style="{
            height: '320px',
            overflow: 'hidden',
            position: 'relative',
            border: '1px solid var(--semi-color-border)',
            borderRadius: '2px',
            padding: '24px',
            textAlign: 'center',
            background: 'var(--semi-color-fill-0)',
        }"
    >
        <span>Render in this</span>
        <br />
        <br />
        <Button @click="visible = true">Open SideSheet</Button>
        <SideSheet v-model:visible="visible" :getPopupContainer="getContainer" title="渲染在指定容器内部" :width="220">
            <p>This is the content of a basic sidesheet.</p>
            <p>Here is more content...</p>
        </SideSheet>
    </div>
</template>

自定义内容区域

可以通过 #title、#footer 等插槽创建出丰富的内容样式。

代码
<script setup lang="ts">
    import { ref } from 'vue';
    import { Banner, Button, FormDatePicker, FormRadio, FormRadioGroup, FormSelect, Form, SideSheet, TypographyText } from '@fibrous/semi-ui-vue';

    const visible = ref(false);
    const createTime = new Date();
    const border = { borderBottom: '1px solid var(--semi-color-border)' };
</script>

<template>
    <Button @click="visible = true">More Information</Button>
    <SideSheet v-model:visible="visible" :bodyStyle="border" :closable="false" :headerStyle="border" title="创建资源包">
        <template #footer>
            <div style="display: flex; justify-content: flex-end">
                <Button style="margin-right: 8px">重置</Button>
                <Button theme="solid">提交</Button>
            </div>
        </template>
        <Form>
            <FormDatePicker field="date" :initValue="createTime" :label="{ text: '创建时间', required: true }" style="width: 272px" type="dateTime" />
            <FormRadioGroup direction="horizontal" field="type" initValue="all" label="目标操作系统">
                <FormRadio value="all">全平台</FormRadio>
                <FormRadio value="ios">iOS</FormRadio>
                <FormRadio value="android">Android</FormRadio>
                <FormRadio value="web">Web</FormRadio>
            </FormRadioGroup>
            <FormRadioGroup direction="horizontal" field="origin" initValue="scm" label="资源包来源">
                <FormRadio value="scm">从SCM上传</FormRadio>
                <FormRadio value="manual">手动上传</FormRadio>
            </FormRadioGroup>
            <Banner bordered :fullMode="false" type="warning">
                <template #icon />
                <template #description>
                    <TypographyText strong>当前部署环境:线上部署</TypographyText>
                    <br />
                    <TypographyText>请选择正确的SCM构建产物,防止出现不符合预期的发布操作。</TypographyText>
                </template>
            </Banner>
            <br />
            <FormSelect field="users" :initValue="['1', '2', '3', '4']" :label="{ text: '创建用户', required: true }" multiple style="width: 560px">
                <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>
</template>

API 参考

Props

属性说明类型默认值
bodyStyle面板内容区的样式。StyleValuesemiGlobalDefault("SideSheet", "bodyStyle")
canVerticalSetWidthplacement 为 top、bottom 时 width 是否生效;默认这两种位置横向铺满。booleansemiGlobalDefault("SideSheet", "canVerticalSetWidth")
closable是否显示右上角的关闭按钮。booleansemiDefault("SideSheet", "closable", true)
closeOnEsc是否允许按 Esc 关闭面板。booleansemiDefault("SideSheet", "closeOnEsc", false)
disableScroll带遮罩且渲染在 document.body 中时是否禁止 body 滚动,即给 body 添加 overflow: hidden。booleansemiDefault("SideSheet", "disableScroll", true)
getPopupContainer指定弹层渲染到的父级 DOM。自定义容器需要设置 position: relative;这会改变浮层的 DOM 位置,不改变视图位置。Type(() => HTMLElement)undefined
headerStyle面板头部的样式。StyleValuesemiGlobalDefault("SideSheet", "headerStyle")
height高度,placement 为 top、bottom 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时为 448。string | numbersemiGlobalDefault("SideSheet", "height")
keepDOM关闭时是否保留面板内部节点而不销毁。booleansemiDefault("SideSheet", "keepDOM", false)
mask是否显示遮罩;为 false 时允许操作外部区域。booleansemiDefault("SideSheet", "mask", true)
maskClosable是否允许点击遮罩关闭面板。booleansemiDefault("SideSheet", "maskClosable", true)
maskStyle遮罩的样式。StyleValuesemiGlobalDefault("SideSheet", "maskStyle")
motion是否开启动画。booleansemiDefault("SideSheet", "motion", true)
placement面板滑出的位置。PlacementsemiDefault("SideSheet", "placement", "right")
size尺寸,small 为 448px、medium 为 684px、large 为 920px,仅 placement 为 left、right 时生效。"small" | "medium" | "large"semiDefault("SideSheet", "size", "small")
title纯文本标题;同时给出 title 插槽时插槽优先。stringsemiGlobalDefault("SideSheet", "title")
visible面板是否可见,配合 v-model:visible 使用。booleansemiGlobalDefault("SideSheet", "visible")
width宽度,placement 为 left、right 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时由 size 决定。string | numbersemiGlobalDefault("SideSheet", "width")
zIndex弹层的 z-index。numbersemiDefault("SideSheet", "zIndex", 1000)

Events

事件说明回调参数
cancel用户通过关闭按钮、遮罩或 Esc 关闭面板时触发,晚于 update:visible;载荷为原生 MouseEvent,按 Esc 时为原生 KeyboardEvent。[event: MouseEvent | KeyboardEvent]
update:visible关闭按钮、遮罩或 Esc 关闭面板时先于 cancel 触发,值为 false,供 v-model:visible 回写。[visible: boolean]
afterVisibleChange显示时在 visible 变为 true 的那次提交立即触发,不等进场动画;隐藏时在离场动画结束后触发;显示期间被卸载时以 false 触发一次;在 KeepAlive 中失活、重新激活时不触发。[visible: boolean]

Slots

插槽说明作用域参数
default面板的正文。any
title标题,优先于 title 属性,同时作为对话框的可访问名称。内容为空时不渲染标题。any
footer面板底部。内容为空时不渲染底部。any
closeIcon关闭按钮中的图标;空插槽时回落到默认图标,隐藏按钮用 closable 为 false。any

Exposes

名称说明类型
rootPortal 内的最外层 .semi-sidesheet 元素,未渲染时为 null。HTMLDivElement | null

aria-* 与 style 落在 role="dialog" 的元素(.semi-sidesheet-inner)上,与 React 相同;class 与其他原生属性落在 portal 内最外层的 .semi-sidesheet 元素上,组件 ref 暴露的 root 也是这个元素。

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 时不锁定滚动。