代码演示
如何引入
<script lang="ts">
import { Banner, Button, Form, FormDatePicker, FormRadio, FormRadioGroup, FormSelect, Radio, RadioGroup, SideSheet, TextArea, TypographyText } from '@fibrae/semi-ui-svelte';
</script>
基本
默认侧边栏从右滑出,支持点击遮罩区关闭。
用 bind:visible 控制显示:关闭按钮、遮罩和 Esc 关闭时先把 visible 写回 false,再调用 onCancel。只传单向的 visible 加 onCancel 时不会自行关闭,需要在 onCancel 中把 visible 设为 false。
代码
<script lang="ts">
import { Button, SideSheet } from '@fibrae/semi-ui-svelte';
let visible = $state(false);
</script>
<Button onclick={() => (visible = !visible)}>Open SideSheet</Button>
<SideSheet bind:visible title="滑动侧边栏">
<p>This is the content of a basic sidesheet.</p>
<p>Here is more content...</p>
</SideSheet>自定义位置
可以通过设置 placement 属性设置侧边栏滑出位置,支持 top、bottom、left、right。
代码
<script lang="ts">
import { Button, Radio, RadioGroup, SideSheet, type SideSheetProps } from '@fibrae/semi-ui-svelte';
let visible = $state(false);
let placement = $state<SideSheetProps['placement']>('right');
</script>
<RadioGroup aria-label="侧边栏位置" onChange={(e) => (placement = e.target.value as SideSheetProps['placement'])} 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={() => (visible = !visible)}>Open SideSheet</Button>
<SideSheet bind:visible {placement} title="滑动侧边栏">
<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"
代码
<script lang="ts">
import { Button, Radio, RadioGroup, SideSheet, type SideSheetProps } from '@fibrae/semi-ui-svelte';
let visible = $state(false);
let size = $state<SideSheetProps['size']>('small');
</script>
<RadioGroup aria-label="侧边栏尺寸" onChange={(e) => (size = e.target.value as SideSheetProps['size'])} value={size}>
<Radio value="small">small</Radio>
<Radio value="medium">medium</Radio>
<Radio value="large">large</Radio>
</RadioGroup>
<br />
<br />
<Button onclick={() => (visible = !visible)}>Open SideSheet</Button>
<SideSheet bind:visible {size} title="滑动侧边栏">
<p>This is the content of a basic sidesheet.</p>
<p>Here is more content...</p>
</SideSheet>可操作的外部区域
当 mask={false} 时允许对外部区域进行操作。
代码
<script lang="ts">
import { Button, SideSheet, TextArea } from '@fibrae/semi-ui-svelte';
let visible = $state(false);
let value = $state('');
</script>
<Button onclick={() => (visible = true)}>Open SideSheet</Button>
<TextArea bind:value placeholder="Please enter something" style="margin-top: 12px" />
<SideSheet bind:visible disableScroll={false} mask={false} title="可操作外部的侧边栏">
<p>这里是输入的内容:</p>
<p>{value}</p>
</SideSheet>渲染在指定容器
可以通过 getPopupContainer 指定父级 DOM,弹层将会渲染至该 DOM 中。
代码
<script lang="ts">
import { Button, SideSheet } from '@fibrae/semi-ui-svelte';
let visible = $state(false);
let container = $state<HTMLElement>();
</script>
<div bind:this={container} 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)">
<span>Render in this</span>
<br />
<br />
<Button onclick={() => (visible = true)}>Open SideSheet</Button>
<SideSheet bind:visible getPopupContainer={() => container!} title="渲染在指定容器内部" width={220}>
<p>This is the content of a basic sidesheet.</p>
<p>Here is more content...</p>
</SideSheet>
</div>自定义内容区域
可以通过 title 属性(纯文本或 snippet)、footer snippet 等创建出丰富的内容样式。
代码
<script lang="ts">
import { Banner, Button, Form, FormDatePicker, FormRadio, FormRadioGroup, FormSelect, SideSheet, TypographyText } from '@fibrae/semi-ui-svelte';
let visible = $state(false);
const createTime = new Date();
const border = 'border-bottom: 1px solid var(--semi-color-border)';
const users = [
{ value: '1', label: '曲晨一' },
{ value: '2', label: '夏可曼' },
{ value: '3', label: '曲晨三' },
{ value: '4', label: '蔡妍' },
];
</script>
<Button onclick={() => (visible = true)}>More Information</Button>
<SideSheet bind:visible bodyStyle={border} closable={false} headerStyle={border} title="创建资源包">
{#snippet footer()}
<div style="display: flex; justify-content: flex-end">
<Button style="margin-right: 8px">重置</Button>
<Button theme="solid">提交</Button>
</div>
{/snippet}
<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} icon={null} type="warning">
{#snippet description()}
<TypographyText strong>当前部署环境:线上部署</TypographyText>
<br />
<TypographyText>请选择正确的SCM构建产物,防止出现不符合预期的发布操作。</TypographyText>
{/snippet}
</Banner>
<br />
<FormSelect field="users" initValue={['1', '2', '3', '4']} label={{ text: '创建用户', required: true }} multiple optionList={users} style="width: 560px" />
</Form>
</SideSheet>API 参考
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| afterVisibleChange | 显示时在 visible 变为 true 的那次提交立即触发,不等进场动画;隐藏时在离场动画结束后触发;显示期间被卸载时以 false 触发一次。 | Type(isVisible: boolean) => void | - |
| bodyStyle | 面板内容区的样式,字符串。 | string | - |
| canVerticalSetWidth | placement 为 top、bottom 时 width 是否生效;默认这两种位置横向铺满。 | boolean | - |
| children | 面板的正文,写在标签内。 | Snippet | - |
| closable | 是否显示右上角的关闭按钮。 | boolean | true |
| closeIcon | 关闭按钮中图标的 snippet;隐藏按钮用 closable 为 false。 | Snippet | - |
| closeOnEsc | 是否允许按 Esc 关闭面板。 | boolean | false |
| disableScroll | 带遮罩且渲染在 document.body 中时是否禁止 body 滚动,即给 body 添加 overflow: hidden。 | boolean | true |
| footer | 面板底部的 snippet。内容为空时不渲染底部。 | Snippet | - |
| getPopupContainer | 指定弹层渲染到的父级 DOM。自定义容器需要设置 position: relative;这会改变浮层的 DOM 位置,不改变视图位置。 | Type() => HTMLElement | - |
| headerStyle | 面板头部的样式,字符串。 | string | - |
| height | 高度,placement 为 top、bottom 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时为 448。 | number | string | - |
| keepDOM | 关闭时是否保留面板内部节点而不销毁。 | boolean | false |
| mask | 是否显示遮罩;为 false 时允许操作外部区域。 | boolean | true |
| maskClosable | 是否允许点击遮罩关闭面板。 | boolean | true |
| maskStyle | 遮罩的样式,字符串。 | string | - |
| motion | 是否开启动画。 | boolean | true |
| onCancel | 用户通过关闭按钮、遮罩或 Esc 关闭面板时触发,visible 已先被写回 false;参数为原生 MouseEvent,按 Esc 时为原生 KeyboardEvent。 | Type(event: MouseEvent | KeyboardEvent) => void | - |
| placement | 面板滑出的位置。 | Placement | "right" |
| size | 尺寸,small 为 448px、medium 为 684px、large 为 920px,仅 placement 为 left、right 时生效。 | 'small' | 'medium' | 'large' | "small" |
| style | 落在 role=dialog 元素上的样式,字符串。 | string | - |
| title | 标题,纯文本或 snippet,同时作为对话框的可访问名称。声明为 prop,不会作为原生 title 属性落到元素上。 | Snippet | string | - |
| visible | 面板是否可见,配合 bind:visible 使用。 | boolean | - |
| width | 宽度,placement 为 left、right 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时由 size 决定。 | number | string | - |
| zIndex | 弹层的 z-index。 | number | 1000 |
Methods
| 方法 | 说明 | 类型 |
|---|---|---|
| getRoot | 返回 Portal 内的最外层 .semi-sidesheet 元素,未渲染时为 null。 | Type() => HTMLDivElement | null |
aria-* 与 style 落在 role="dialog" 的元素(.semi-sidesheet-inner)上;class 与其他原生属性和事件落在 portal 内最外层的 .semi-sidesheet 元素上,getRoot() 返回的也是这个元素。
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时不锁定滚动。