代码演示
如何引入
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 中。
代码
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 | - |
| canVerticalSetWidth | placement 为 top、bottom 时 width 是否生效;默认这两种位置横向铺满。 | boolean | - |
| closable | 是否显示右上角的关闭按钮。 | boolean | true |
| closeIcon | 关闭按钮中的图标;传入 null 或 undefined 时回落到默认图标,隐藏关闭按钮请用 closable={false}。 | JSX.Element | - |
| closeOnEsc | 是否允许按 Esc 关闭面板。 | boolean | false |
| disableScroll | 带遮罩且渲染在 document.body 中时是否禁止 body 滚动,即给 body 添加 overflow: hidden。 | boolean | true |
| footer | 面板底部,传入 JSX 元素;内容为空时不渲染底部。 | JSX.Element | null |
| getPopupContainer | 指定弹层渲染到的父级 DOM。自定义容器需要设置 position: relative;这会改变浮层的 DOM 位置,不改变视图位置。 | Type() => HTMLElement | - |
| headerStyle | 面板头部的样式,写法同 bodyStyle。 | JSX.CSSProperties | string | - |
| height | 高度,placement 为 top、bottom 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时为 448。 | number | string | - |
| keepDOM | 关闭时是否保留面板内部节点而不销毁。 | boolean | false |
| mask | 是否显示遮罩;为 false 时允许操作外部区域,且不锁定 body 滚动。 | boolean | true |
| maskClosable | 是否允许点击遮罩关闭面板。 | boolean | true |
| maskStyle | 遮罩的样式,写法同 bodyStyle。 | JSX.CSSProperties | string | - |
| motion | 是否开启动画。 | boolean | true |
| 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。 | boolean | false |
| width | 宽度,placement 为 left、right 时生效;数字按像素处理,字符串为合法 CSS 长度。未设置时由 size 决定。 | number | string | - |
| zIndex | 弹层的 z-index。 | number | 1000 |
aria-* 与 style 落在 role="dialog" 的元素(.semi-sidesheet-inner)上;class、ref 与其他原生属性和事件落在 portal 内最外层的 .semi-sidesheet 元素上。
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时不锁定滚动。