代码演示
如何引入
<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 中。
代码
<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 | 面板内容区的样式。 | StyleValue | semiGlobalDefault("SideSheet", "bodyStyle") |
| canVerticalSetWidth | placement 为 top、bottom 时 width 是否生效;默认这两种位置横向铺满。 | boolean | semiGlobalDefault("SideSheet", "canVerticalSetWidth") |
| closable | 是否显示右上角的关闭按钮。 | boolean | semiDefault("SideSheet", "closable", true) |
| closeOnEsc | 是否允许按 Esc 关闭面板。 | boolean | semiDefault("SideSheet", "closeOnEsc", false) |
| disableScroll | 带遮罩且渲染在 document.body 中时是否禁止 body 滚动,即给 body 添加 overflow: hidden。 | boolean | semiDefault("SideSheet", "disableScroll", true) |
| getPopupContainer | 指定弹层渲染到的父级 DOM。自定义容器需要设置 position: relative;这会改变浮层的 DOM 位置,不改变视图位置。 | Type(() => HTMLElement) | undefined |
| headerStyle | 面板头部的样式。 | StyleValue | semiGlobalDefault("SideSheet", "headerStyle") |
| height | 高度,placement 为 top、bottom 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时为 448。 | string | number | semiGlobalDefault("SideSheet", "height") |
| keepDOM | 关闭时是否保留面板内部节点而不销毁。 | boolean | semiDefault("SideSheet", "keepDOM", false) |
| mask | 是否显示遮罩;为 false 时允许操作外部区域。 | boolean | semiDefault("SideSheet", "mask", true) |
| maskClosable | 是否允许点击遮罩关闭面板。 | boolean | semiDefault("SideSheet", "maskClosable", true) |
| maskStyle | 遮罩的样式。 | StyleValue | semiGlobalDefault("SideSheet", "maskStyle") |
| motion | 是否开启动画。 | boolean | semiDefault("SideSheet", "motion", true) |
| placement | 面板滑出的位置。 | Placement | semiDefault("SideSheet", "placement", "right") |
| size | 尺寸,small 为 448px、medium 为 684px、large 为 920px,仅 placement 为 left、right 时生效。 | "small" | "medium" | "large" | semiDefault("SideSheet", "size", "small") |
| title | 纯文本标题;同时给出 title 插槽时插槽优先。 | string | semiGlobalDefault("SideSheet", "title") |
| visible | 面板是否可见,配合 v-model:visible 使用。 | boolean | semiGlobalDefault("SideSheet", "visible") |
| width | 宽度,placement 为 left、right 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时由 size 决定。 | string | number | semiGlobalDefault("SideSheet", "width") |
| zIndex | 弹层的 z-index。 | number | semiDefault("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
| 名称 | 说明 | 类型 |
|---|---|---|
| root | Portal 内的最外层 .semi-sidesheet 元素,未渲染时为 null。 | HTMLDivElement | null |
aria-* 与 style 落在 role="dialog" 的元素(.semi-sidesheet-inner)上,与 React 相同;class 与其他原生属性落在 portal 内最外层的 .semi-sidesheet 元素上,组件 ref 暴露的 root 也是这个元素。
Accessibility
ARIA
- SideSheet 具有
dialogrole 来表示它是一个弹窗组件。标题元素(.semi-sidesheet-title)具有headingrole 和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-show | 180ms | 侧边栏打开时-蒙层颜色-动画持续时间 |
| $animation_function-sideSheet_mask-show | cubic-bezier(0.25, 0.46, 0.45, 0.94) | 侧边栏打开时-蒙层颜色-过渡曲线 |
| $animation_delay-sideSheet_mask-show | 0ms | 侧边栏打开时-蒙层颜色-延迟时间 |
| $animation_duration-sideSheet_mask-hide | 180ms | 侧边栏关闭时-蒙层颜色-动画持续时间 |
| $animation_function-sideSheet_mask-hide | cubic-bezier(0.25, 0.46, 0.45, 0.94) | 侧边栏关闭时-蒙层颜色-过渡曲线 |
| $animation_delay-sideSheet_mask-hide | 0ms | 侧边栏关闭时-蒙层颜色-延迟时间 |
| $animation_duration-sideSheet_inner-show | 180ms | 侧边栏打开-动画持续时间 |
| $animation_function-sideSheet_inner-show | cubic-bezier(0.25, 0.46, 0.45, 0.94) | 侧边栏打开-过渡曲线 |
| $animation_delay-sideSheet_inner-show | 0ms | 侧边栏打开-延迟时间 |
| $animation_duration-sideSheet_inner-hide | 180ms | 侧边栏关闭-动画持续时间 |
| $animation_function-sideSheet_inner-hide | cubic-bezier(0.25, 0.46, 0.45, 0.94) | 侧边栏关闭-过渡曲线 |
| $animation_delay-sideSheet_inner-hide | 0ms | 侧边栏关闭-延迟时间 |
| $color-sideSheet_mask-bg | var(--semi-color-overlay-bg) | 侧边栏打开后蒙层颜色 |
| $color-sideSheet-bg | var(--semi-color-bg-2) | 侧边栏背景颜色 |
| $color-sideSheet_main-text | var(--semi-color-text-0) | 侧边栏默认文本颜色 |
| $color-sideSheet_header-borderBottom | var(--semi-color-border) | 侧边栏 header 底部边框颜色 |
| $spacing-sideSheet-margin | 0 | 侧边栏整体外边距 |
| $spacing-sideSheet_title-margin | 0 | 侧边栏标题外边距 |
| $spacing-sideSheet_header-padding | $spacing-loose | 侧边栏 header 内边距 |
| $spacing-sideSheet_header-paddingBottom | $spacing-base-loose | 侧边栏 header 底部内边距 |
| $spacing-sideSheet_body-paddingY | 0 | 侧边栏 body 垂直内边距 |
| $spacing-sideSheet_body-paddingX | $spacing-loose | 侧边栏 body 水平内边距 |
| $spacing-sideSheet_footer-padding | $spacing-loose | 侧边栏 footer 内边距 |
| $width-sideSheet_header-borderBottom | 0px | 侧边栏 header 底部边框宽度 |
| $width-sideSheet_size-small | 448px | 小尺寸侧边栏宽度 |
| $width-sideSheet_size-medium | 684px | 中尺寸侧边栏宽度 |
| $width-sideSheet_size-large | 920px | 大尺寸侧边栏宽度 |
| $font-sideSheet_title-fontWeight | $font-weight-bold | 侧边栏标题文本字重 |
| $font-sideSheet_title-fontSize | $font-size-header-5 | 侧边栏标题文本字号 |
| $filter-sideSheet_mask-bg | none | |
| $filter-sideSheet-bg | none |
FAQ:
- SideSheet 会自动禁止 body 的滚动吗?当带遮罩的 SideSheet 默认渲染在 body 中时(即不传入 getPopupContainer 参数),会在打开时自动给 body 添加
overflow: hidden来禁止滚动。可以将disableScroll设为false允许滚动;mask为false时不锁定滚动。