Sheet 侧边面板
从视口一侧滑出的模态面板,放筛选条件、详情、次要表单这类「看一眼 / 改一下就回来」的内容。 打断流程、要求明确答复的用 Modal。
标题、说明、正文、底部操作各占一个插槽。面板初始是关闭的:把 open 设为 true 或调用 show() 才会从 side 指定的一侧滑出,这里只展示结构。
侧边面板默认关闭,页面上看不到它;下面的代码就是它的全部结构。
<pt-sheet side="right">
<span slot="title">筛选条件</span>
<span slot="description">修改后点应用生效。</span>
<p>正文内容。</p>
<pt-button slot="footer" variant="secondary">重置</pt-button>
<pt-button slot="footer" variant="brand">应用</pt-button>
</pt-sheet>打开与关闭都走 open 属性(show() / hide() 只是它的糖)。关闭后会发 ptClose, detail.reason 说明是怎么关的:close-button / escape / overlay 是用户发起的, api 是调用了 hide()。直接把 open 设为 false 不发 ptClose —— 那是你自己关的, 受控写法下不需要再被告知一次。三个开关事件都不冒泡(弹层天然互相嵌套),外层统一监听要走捕获阶段。
方位与尺寸
side 取 top / right(默认)/ bottom / left:
- 左右两侧:高度铺满,宽 75%,≥640px 时最多 384px。要别的宽度设
--pt-sheet-size(设了之后宽度与上限都按它)。 - 上下两侧:宽度铺满,高度随内容。
方位是物理方向,RTL 下不翻转。面板本身就贴着视口边缘,窄屏下不另换形态。 打开时 500ms、关闭时 300ms 从所在的边滑入滑出(遮罩淡入淡出);系统开了「减少动态效果」时不播。
点遮罩默认关闭
与 Modal 相反,面板是临时的旁支内容,点回页面就该回去,所以点遮罩默认关闭(与 Radix 一致)。 面板里有填到一半的表单时关掉它:原生 HTML 写 close-on-overlay="false",框架里 closeOnOverlay={false}。
拦截关闭
用户点 ✕、按 Esc、点遮罩的那一刻,面板先发一个可取消的 ptRequestClose, detail.reason 同上。监听方调用 event.preventDefault(),面板就保持打开,也不会发 ptClose:
<PtSheet
open={open}
onPtRequestClose={event => {
if (dirty) event.preventDefault();
}}
onPtClose={() => setOpen(false)}
>
…
</PtSheet>hide() 与直接把 open 设为 false 不经过这个事件,也拦不住。
焦点与层级
与 Modal 共用同一套模态层:打开时焦点落在面板容器本身,Tab 困在面板里,关闭后还给打开它的元素; auto-focus="false" 时焦点不动(在 ptOpen 里自己聚焦),焦点陷阱照样生效。打开期间锁住页面滚动。
面板打开时进浏览器的顶层,不被祖先的 overflow 裁切、不受 transform 与层叠上下文影响。 面板里再打开的下拉、气泡卡片、弹窗都比它后进顶层、盖在它之上;Esc 每次只关最上面那一层。
右上角 ✕ 的读屏名称默认是内置文案「关闭」(随 locale 切换),这一处要说别的话时用 close-label; 不要它时加 hide-close,并自己在 footer 里放关闭入口。
页脚
footer 插槽里的按钮窄屏下纵向排列、后写的在上(主按钮写在后面,窄屏时就在最上面);≥640px 时横排靠行尾。
API
属性
| 属性 | Attribute | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
ariaLabelText | aria-label | string | undefined | — | 无障碍名称:原生 HTML 里写 aria-label,React / Vue / Angular 包装层里属性名是 ariaLabelText。 没有用 title 插槽时必须给。 |
autoFocus | auto-focus | boolean | true | 打开时把焦点移进面板(落在面板容器本身)。设为 false 时焦点原地不动 —— 适合在 ptOpen 里自己聚焦某个输入框;焦点陷阱照样生效,第一次 Tab 就会被拉进面板。 默认为真:原生 HTML 里关掉它写 `auto-focus="false"`。 |
closeLabel | close-label | string | undefined | — | 右上角关闭按钮的无障碍名称。不设置时用内置文案(`t('modal.close')`,随 locale 切换) |
closeOnOverlay | close-on-overlay | boolean | true | 点遮罩关闭(默认开,与源包 / Radix 一致)。面板里有填到一半的内容时关掉它: 原生 HTML 里写 `close-on-overlay="false"`,框架里 `closeOnOverlay={false}`。 |
hideClose | hide-close | boolean | false | 隐藏右上角关闭按钮。隐藏后必须自己在 footer 里提供关闭入口 |
open | open | boolean | false | 是否打开。用户交互时组件自己改写;外部直接赋值立即生效,不发 ptRequestClose / ptClose |
side | side | "bottom" | "left" | "right" | "top" | 'right' | 从哪一侧滑出 |
事件
| 事件 | detail 类型 | 说明 |
|---|---|---|
ptClose | { reason: PtSheetCloseReason; } | 关闭后触发(退场动画开始时,不等它播完),detail 说明是怎么关的:`close-button` / `escape` / `overlay` 是用户发起的(之前的 ptRequestClose 没被取消),`api` 是调用了 hide()。 直接把 open 设为 false **不发**:那是使用方自己关的,受控写法下再回告一次只会多绕一圈。 **不冒泡**:弹层常互相嵌套(弹窗里的面板、面板里的下拉),冒泡的话外层上的监听会收到内层的开关。 |
ptOpen | void | 打开并渲染出来之后触发(交互或属性打开都发) **不冒泡**:弹层常互相嵌套(弹窗里的面板、面板里的下拉),冒泡的话外层上的监听会收到内层的开关。 |
ptRequestClose | { reason: PtSheetRequestCloseReason; } | 用户要关闭面板时(点 ✕ / 按 Esc / 点遮罩)、真正关闭之前触发,可取消: `event.preventDefault()` 后面板保持打开,也不会发 ptClose。 `hide()` 与直接把 open 设为 false 不经过它。 **不冒泡**:弹层常互相嵌套(弹窗里的面板、面板里的下拉),冒泡的话外层上的监听会收到内层的开关。 |
方法
| 方法 | 说明 |
|---|---|
hide() => Promise<void> | 关闭面板(ptClose 的 reason 为 `api`) |
show() => Promise<void> | 打开面板 |
插槽
| 名称 | 说明 |
|---|---|
(默认) | 正文 |
description | 标题下的说明 |
footer | 底部操作区。窄屏下按钮纵向排列(后写的在上),≥640px 时靠行尾横排 |
title | 标题 |
可定制的内部元素(::part())
| part | 说明 |
|---|---|
base | 进顶层的最外层容器(铺满视口,包着遮罩与面板) |
body | 正文 |
close | 右上角关闭按钮 |
footer | 底部 |
header | 头部(标题与说明) |
overlay | 遮罩 |
panel | 面板本体 |
CSS 变量
| 变量 | 说明 |
|---|---|
--pt-sheet-size | 左右两侧时的面板宽度。默认 75%、≥640px 时最多 384px;设了之后宽度与上限都按它。上下两侧时不起作用 |