跳转到内容

Drawer 抽屉 ​

从屏幕某一边滑出的面板。

用法 ​

不传 open 即为非受控;Escape 关闭、Tab 在面板内循环,展开期间页面不可滚动

组件结构 ​

加粗的是必需部件。

data-scope="drawer":root · trigger · backdrop · positioner · content · header · title · description · body · footer · close-trigger

示例 ​

贴边方向 ​

side 只写为 data-side,面板贴在哪条边由皮肤按该值决定;root 与 content 报告的是同一条边

受控 ​

传入 open 后由宿主决定;Escape、点击面板外、按关闭按钮都只回写 open,不自行修改状态

当前:收起

尺寸 ​

size 写为 content 的 data-size,只改变面板贴边方向上的厚度;三档各自一个抽屉,打开后才可见厚度差异

头尾固定、正文滚动 ​

header / body / footer 把面板切为三段:头与尾固定在原处,只有正文一段滚动

关闭前拦截 ​

受控时组件不自行修改状态:Escape、点击面板外、按关闭按钮都只发一次收起意图,是否写回由宿主决定

拖动边缘改变厚度 ​

面板中放一根把手,拖动时把新厚度写进 content 的 --xh-drawer-size;该槽覆盖 size 三档,滑入滑出仍按面板自身宽度计算

局部抽屉 ​

把抽屉收进某块区域:遮罩与定位层从 fixed 换为 absolute,只覆盖该区域而不是整屏

这块区域就是抽屉的容器:展开时遮罩只盖住它,页面其余部分照常可点。

不给 container 时问全局配置的 portalContainer,再没有才落 body——整屏抽屉与从前一模一样。

设计指引 ​

何时使用 ​

  • 内容比对话框长(完整表单、详情),但仍属于当前上下文。
  • 窄屏上的导航或筛选面板。

何时不用 ​

  • 只确认一件事时,使用对话框或弹出确认。
  • 内容需要与页面主体对照查看时,并排展开,不遮挡。

特性 ​

  • side 决定滑出方向;contained 让它只占据某个容器而不是整个视口。
  • modal=false 时不渲染遮罩,定位层也不截获页面指针;页面可以与抽屉并行交互。展开期间切换 modal,滚动锁、背景失活与焦点陷阱会同步切换。
  • 可以拖动边缘调整厚度。
  • 关闭时内容立即失活并退出可访问树;面板与遮罩全部完成退场后释放模态资源并发出 onExitComplete / exit-complete。退场中重开不会被旧完成关闭,卸载立即清理。
  • 关闭前可以拦截,例如有未保存改动时先确认。
  • 面板走 M4 sheet 三件套(1px 描边、不透明底、投影),边界由描边承担,不只靠影分层;入场是整面板从画外推入的大尺度位移,走 slide 时长与曲线,退场仍走 exit 档。
  • 触发器与关闭按钮走 Action Control 家族配方:触发器为 text 档中性描边,展开期间压住为悬停同档的中性面;关闭按钮为 icon 档 ghost 面,悬停与按下沿画布承载阶梯换底;Space / Enter 与触屏按住期间投影 data-pressed。标题为 heading-3,说明文字为 13px 说明档。
  • Body 是模态滚动面:滚到头不带动页面,内容高度变化时保留稳定的滚动条空道;不用三段结构时 content 自身是唯一滚动层。

组合 ​

最佳实践 ​

  • 提交与取消固定在底部,用户不需要滚动到底部查找。
  • 有未保存改动时拦截关闭。

反模式 ​

  • 在抽屉内再打开抽屉。
  • 在宽屏上用抽屉承载可以直接展开的内容。

API 参考 ​

产物 ​

层值
自定义元素<xh-drawer>
Vue 组件XhDrawerBody XhDrawerCloseTrigger XhDrawerContent XhDrawerDescription XhDrawerFooter XhDrawerHeader XhDrawerRoot XhDrawerTitle XhDrawerTrigger
组合式函数useDrawer
状态机drawerMachine
皮肤@xihan-ui/styles/drawer.css

Props ​

属性类型必填说明
openboolean
defaultOpenboolean
modalboolean是否启用模态约束,默认 true。false 时不提供遮罩,页面其余部分保持可交互; 展开期间可以切换,滚动锁、背景失活与焦点陷阱会同步更新。
containedboolean浮层挂在局部容器中而不是视口:遮罩与定位层从 fixed 改为 absolute, 因此只覆盖该容器、不再覆盖整屏。 挂到哪个容器由适配器决定(Vue 由 root 的 container 决定,WC 本身是 Light DOM、 作者写在何处即在何处),这里只表达按局部容器绘制这一点。
sideDrawerSide滑出的边,默认 'right'。只影响输出的 data-side,不参与状态转移。
role'dialog' | 'alertdialog'
closeOnEscapeboolean
closeOnInteractOutsideboolean
restoreFocusboolean
sizeSize尺寸:sm / md / lg。横向放置时影响面板宽度、纵向放置时影响面板高度,随 side 而定。
variantOverlayBackdropVariant遮罩形态:opaque / blur / transparent。写在 backdrop 上,只影响该层的底色与模糊。
translationsPartial<DrawerTranslations>
onOpenChange(details: DrawerOpenChangeDetails) => voidopen 变化意图回调;受控时是唯一出口,非受控时随内部转移一并通知。
onExitComplete() => void退出动画结束或取消,且本层资源全部释放后通知;卸载和重新打开不通知。

事件 ​

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
exit-completeCustomEvent退出完成且本层资源已释放
open-changeDrawerOpenChangeDetailsopen 状态变化;detail 为 { open: boolean }

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhDrawerRootdefaultDrawerRootSlotProps

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhDrawerRootcontainer() => Element | null浮层挂载的容器;未提供时按全局配置,再未提供时挂载到 body。 提供后即为局部抽屉:遮罩与定位层从 fixed 换为 absolute,只覆盖该容器而不是整屏。 该容器要自带 position(relative 等),否则 absolute 会向上找到其他定位祖先。
XhDrawerRootchildrenSlotChildren<DrawerRootSlotProps>

状态 ​

公开状态写入 data-state。

部件取值
root'open' | 'closed'
trigger'open' | 'closed'
backdrop'open' | 'closed'
positioner'open' | 'closed'
content'open' | 'closed'

以下名称仅用于内部状态机。

状态:open · closed

事件:OPEN · TOGGLE · CLOSE · CONTROLLED.OPEN · CONTROLLED.CLOSE · PRESS.START · PRESS.END

判据:isOpenControlled

connect API ​

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
openboolean
sideDrawerSide已解析的滑出边(prop 未提供时是默认值),作者据此配置动画。
setOpen(next: boolean) => void
getRootProps() => T['element']
getTriggerProps() => T['button']
getBackdropProps() => T['element']
getPositionerProps() => T['element']
getContentProps() => T['element']
getHeaderProps() => T['element']
getTitleProps() => T['element']
getDescriptionProps() => T['element']
getBodyProps() => T['element']
getFooterProps() => T['element']
getCloseTriggerProps() => T['button']

无障碍 ​

键盘 ​

规格出处:W3C APG

按键生效条件行为
Enter / Spacefocus in trigger打开抽屉并把焦点移入 content
Escapeopen关闭并把焦点还给 trigger
Tabopen 且 modal在 content 内向后循环焦点
Shift+Tabopen 且 modal在 content 内向前循环焦点
Enter / Spaceheld in trigger / close-trigger按住期间该按钮投影 data-pressed,与指针 :active 同一副按压面;抬起、失焦或抽屉收起撤下

ARIA ​

以下属性由 connect 生成。

部件属性值
triggeraria-controlscontent 部件的 id
triggeraria-expanded'true' | 'false'
triggeraria-haspopup'dialog'
contentaria-describedbydescription 部件的 id
contentaria-hidden!open || undefined
contentaria-labelledbytitle 部件的 id
contentaria-modal'true' | 'false'
contentroleprops.role
close-triggeraria-labelprops.translations.close

样式参考 ​

皮肤 ​

@xihan-ui/styles/drawer.css 使用 [data-scope="drawer"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides。

数据属性 ​

由 connect 生成;条件不成立时不输出无值属性。

部件属性值
rootdata-contained''(条件成立时才出现)
rootdata-sideprops.side
rootdata-sizeprops.size
rootdata-state'open' | 'closed'
triggerdata-pressed''(条件成立时才出现)
triggerdata-state'open' | 'closed'
triggerdata-xh-action-control''
triggerdata-xh-action-display'always'
triggerdata-xh-action-profile'text'
triggerdata-xh-action-size'md'
triggerdata-xh-action-variant'outline'
backdropdata-contained''(条件成立时才出现)
backdropdata-state'open' | 'closed'
backdropdata-variantprops.variant
positionerdata-contained''(条件成立时才出现)
positionerdata-positioned''
positionerdata-state'open' | 'closed'
contentdata-contained''(条件成立时才出现)
contentdata-sideprops.side
contentdata-sizeprops.size
contentdata-state'open' | 'closed'
close-triggerdata-pressed''(条件成立时才出现)
close-triggerdata-xh-action-control''
close-triggerdata-xh-action-display'always'
close-triggerdata-xh-action-profile'icon'
close-triggerdata-xh-action-size'sm'
close-triggerdata-xh-action-variant'ghost'

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-drawer-backdrop-bgbackdropbackgrounddefault--xh-bg-overlaydrawer 的 backdrop 部件 background 覆盖槽。
--xh-drawer-backdrop-blurbackdrop-webkit-backdrop-filter
backdrop-filter
variant=blur--xh-overlay-backdrop-blurdrawer 的 backdrop 部件 -webkit-backdrop-filter、backdrop-filter 覆盖槽。
--xh-drawer-backdrop-layerbackdropz-indexdefault--xh-_layerdrawer 的 backdrop 部件 z-index 覆盖槽。
--xh-drawer-bgcontentbackgrounddefault--xh-material-elevated-bgdrawer 的 content 部件 background 覆盖槽。
--xh-drawer-bordercontentborderdefault--xh-material-elevated-borderdrawer 的 content 部件 border 覆盖槽。
--xh-drawer-close-bg-activeclose-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-presseddrawer 的 close-trigger 部件 background-color 覆盖槽。
--xh-drawer-close-bg-focusclose-triggerbackground-colorfocus-visible--xh-_action-variant-bg-focus-visibledrawer 的 close-trigger 部件 background-color 覆盖槽。
--xh-drawer-close-bg-hoverclose-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hoverdrawer 的 close-trigger 部件 background-color 覆盖槽。
--xh-drawer-close-fgclose-triggercolordefault--xh-fg-muteddrawer 的 close-trigger 部件 color 覆盖槽。
--xh-drawer-close-fg-focusclose-triggercolorfocus-visible--xh-drawer-close-fg-hoverdrawer 的 close-trigger 部件 color 覆盖槽。
--xh-drawer-close-fg-hoverclose-triggercolordisabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-fg-focus-visible
--xh-_action-variant-fg-hover
--xh-_action-variant-fg-pressed
drawer 的 close-trigger 部件 color 覆盖槽。
--xh-drawer-close-radiusclose-triggerborder-radiusdefault--xh-shape-controldrawer 的 close-trigger 部件 border-radius 覆盖槽。
--xh-drawer-close-sizeclose-trigger
content
title
block-size
inline-size
padding-inline-end
default
has([data-scope='drawer'][data-part='close-trigger'])
xh-action-profile=icon
--xh-_action-profile-visual-size
--xh-control-h-sm
drawer 的 close-trigger、content、title 部件 block-size、inline-size、padding-inline-end 覆盖槽。
--xh-drawer-description-fgdescriptioncolordefault--xh-fg-muteddrawer 的 description 部件 color 覆盖槽。
--xh-drawer-description-font-sizedescriptionfont-sizedefault--xh-text-secondary-sizedrawer 的 description 部件 font-size 覆盖槽。
--xh-drawer-fgcontentcolordefault--xh-material-elevated-fgdrawer 的 content 部件 color 覆盖槽。
--xh-drawer-footer-gapfootergapdefault--xh-control-gap-mddrawer 的 footer 部件 gap 覆盖槽。
--xh-drawer-footer-ptfooterpadding-block-startdefault--xh-space-2drawer 的 footer 部件 padding-block-start 覆盖槽。
--xh-drawer-gapcontentgapdefault--xh-stack-gap-mddrawer 的 content 部件 gap 覆盖槽。
--xh-drawer-header-gapheadergapdefault--xh-stack-gap-smdrawer 的 header 部件 gap 覆盖槽。
--xh-drawer-header-pbheaderpadding-block-enddefault--xh-space-2drawer 的 header 部件 padding-block-end 覆盖槽。
--xh-drawer-icon-sizeclose-trigger
content
root
trigger
--xh-icon-sizedefault--xh-_action-profile-glyph-size
--xh-glyph-size-md
drawer 的 close-trigger、content、root、trigger 部件 --xh-icon-size 覆盖槽。
--xh-drawer-layercontent
positioner
z-indexdefault--xh-_layerdrawer 的 content、positioner 部件 z-index 覆盖槽。
--xh-drawer-pxcontentpadding-inlinecontained
default
--xh-surface-px-mddrawer 的 content 部件 padding-inline 覆盖槽。
--xh-drawer-pycontentpadding-block-end
padding-block-start
contained
default
--xh-surface-py-mddrawer 的 content 部件 padding-block-end、padding-block-start 覆盖槽。
--xh-drawer-radiuscontentborder-end-end-radius
border-end-start-radius
border-start-end-radius
border-start-start-radius
side=bottom
side=left
side=right
side=top
--xh-shape-overlaydrawer 的 content 部件 border-end-end-radius、border-end-start-radius、border-start-end-radius、border-start-start-radius 覆盖槽。
--xh-drawer-shadowcontentbox-shadowdefault--xh-material-elevated-shadowdrawer 的 content 部件 box-shadow 覆盖槽。
--xh-drawer-sizecontentblock-size
inline-size
side=bottom
side=left
side=right
side=top
--xh-_drawer-sizedrawer 的 content 部件 block-size、inline-size 覆盖槽。
--xh-drawer-title-fgtitlecolordefault--xh-fg-defaultdrawer 的 title 部件 color 覆盖槽。
--xh-drawer-title-font-sizetitlefont-sizedefault--xh-text-heading-3-sizedrawer 的 title 部件 font-size 覆盖槽。
--xh-drawer-title-font-weighttitlefont-weightdefault--xh-text-heading-3-weightdrawer 的 title 部件 font-weight 覆盖槽。
--xh-drawer-trigger-bgtrigger--xh-ink-surface
background-color
default
focus-visible
xh-ink-surface
--xh-_action-variant-bg-focus-visible
--xh-_action-variant-bg-rest
drawer 的 trigger 部件 --xh-ink-surface、background-color 覆盖槽。
--xh-drawer-trigger-bg-activetriggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-presseddrawer 的 trigger 部件 background-color 覆盖槽。
--xh-drawer-trigger-bg-hovertriggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hoverdrawer 的 trigger 部件 background-color 覆盖槽。
--xh-drawer-trigger-bg-opentrigger--xh-ink-surface
background-color
focus-visible
state=open
xh-ink-surface
--xh-bg-subtledrawer 的 trigger 部件 --xh-ink-surface、background-color 覆盖槽。
--xh-drawer-trigger-bordertriggerborder
border-color
default
focus-visible
--xh-_action-variant-border-focus-visible
--xh-_action-variant-border-rest
drawer 的 trigger 部件 border、border-color 覆盖槽。
--xh-drawer-trigger-border-hovertriggerborder-colordisabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-border-hover
--xh-_action-variant-border-pressed
drawer 的 trigger 部件 border-color 覆盖槽。
--xh-drawer-trigger-border-opentriggerborder
border-color
focus-visible
state=open
--xh-border-control-hoverdrawer 的 trigger 部件 border、border-color 覆盖槽。
--xh-drawer-trigger-fgtriggercolordefault
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-fg-focus-visible
--xh-_action-variant-fg-hover
--xh-_action-variant-fg-pressed
--xh-_action-variant-fg-rest
drawer 的 trigger 部件 color 覆盖槽。
--xh-drawer-trigger-font-sizetriggerfont-sizedefault--xh-_action-profile-font-sizedrawer 的 trigger 部件 font-size 覆盖槽。
--xh-drawer-trigger-font-weighttriggerfont-weightdefault--xh-text-label-weightdrawer 的 trigger 部件 font-weight 覆盖槽。
--xh-drawer-trigger-gaptriggergapdefault--xh-_action-profile-gapdrawer 的 trigger 部件 gap 覆盖槽。
--xh-drawer-trigger-htriggerblock-size
inline-size
default
xh-action-profile=icon
--xh-_action-profile-visual-sizedrawer 的 trigger 部件 block-size、inline-size 覆盖槽。
--xh-drawer-trigger-pxtriggerpadding-inlinedefault--xh-_action-profile-padding-inlinedrawer 的 trigger 部件 padding-inline 覆盖槽。
--xh-drawer-trigger-radiustriggerborder-radiusdefault--xh-shape-controldrawer 的 trigger 部件 border-radius 覆盖槽。

动效 ​

动效角色:按压 · 状态 · 出现 · 导航(整幅滑入)(见动效规范)。

共享关键帧 xh-fade-in · xh-fade-out · xh-slide-fade-in · xh-slide-fade-out · xh-slide-in · xh-slide-out 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立。时长与缓动读动效令牌,改令牌即改全局节奏。

皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;另有按 dir 分支的规则。

Released under The MIT License