跳到主要内容

悬浮卡片体系Floating card

所有“浮在页面之上的容器”都是同一张卡片:圆角 --ok-r-5、表面 --ok-surface、1px 描边加顶部高光,四周和边缘留一圈缝,从停靠的那一侧滑入。Dialog、Drawer、Sheet、Panel 以及 AppShell 的侧栏,只是它在不同位置、不同尺寸、是否模态下的用法。

一张卡片,五个位置

下面直接用基座组件 FloatingCard。切换 placement 和 modal,再打开卡片:外观、间隙、进退场的方式都是同一套。演示用了 contained,把卡片限制在演示框里。

  • center:居中,从触发点(没有触发点时从中心)0.6 放大到位。
  • left / right / top / bottom:贴着对应的边滑入;手机宽度下卡片撑满整宽,只滑进三分之一的距离。size 在左右是宽度,在上下是高度。
  • 五个位置共用一条进场、一条退场:进场 303ms 用 soft 弹簧落位、不过冲(--ok-float-in-dur / --ok-float-in-ease),退场 160ms 加速走开(--ok-float-out-dur / --ok-float-out-ease),遮罩跟着同一档时长。见 动效 · 悬浮卡片。
  • 没有阴影。模态时靠遮罩压暗并模糊背景,非模态时靠 --ok-line-2 的描边和页面分开。

模态与非模态

  • 模态(默认,Dialog / Drawer / Sheet)。带遮罩;锁住页面滚动;焦点圈在卡片里,打开时自动移进去,关闭后还给触发元素;Esc 和点遮罩关闭;role="dialog" 加 aria-modal。
  • 非模态(modal={false},Panel)。没有遮罩,不锁滚动,不圈焦点,页面其余部分照常可用;焦点在卡片里时 Esc 才关闭,关闭时把焦点还回原处;默认 role="complementary",需要用标题或 aria-label 起名字。

选哪种,看用户是不是必须先处理它:必须回应的(确认、表单)用模态;辅助性的、要和页面对照着看的(检查器、筛选面板)用非模态。dismissible={false} 可以关掉 Esc 和点遮罩,这时一定要在卡片里留一个明确的关闭或确认按钮。

四个封装

日常直接用封装好的组件:它们各有一张 sm / md / lg 尺寸表,并补上各自的结构和默认值。这里的四个都渲染在整个视口上。

  • Dialog(center,模态):宽度 sm 320 / md 400 / lg 560。自带图标位、标题、说明、操作区。各种宽度下形态不变,都从触发点放大进场。
  • Drawer(四边,默认模态):左右宽度 sm 280 / md 360 / lg 480,上下高度 sm 240 / md 360 / lg 520。
  • Sheet(bottom,默认模态):高度跟随内容,或 sm 40% / md 60% / lg 85% / full。带抓手,可拖拽关闭;宽屏上最宽 560 并居中。
  • Panel(四边,非模态):左右宽度 sm 280 / md 320 / lg 400,上下高度 sm 200 / md 280 / lg 400。支持 container 加 contained。
  • AppShell.Sidebar / Aside(left / right,模态):宽屏时是停靠在两侧、占据布局空间的卡片;窄屏时同一份内容改由 FloatingCard 从对应一侧滑入。见 AppShell。

contained:限制在一个区域里

默认卡片渲染到 body 下、盖住整个视口。传 container(一个 position: relative 的元素)加 contained 之后,卡片渲染进这个元素,定位、遮罩都只在它里面,也不再锁页面滚动。

  • 上面“五个位置”的演示就是这么做的;AppShell 的 contained 模式也用它把侧栏抽屉限制在骨架自己的范围里。
  • 容器需要 position: relative,通常再加 overflow: hidden,否则滑入滑出的过程会露到容器外面。
  • container 是元素本身而不是 ref:用 useState 存回调 ref 拿到的节点,节点就绪后会触发一次重渲染。
  • Dialog、Drawer、Sheet 没有开放 contained;需要限制范围时用 Panel,或者直接用 FloatingCard。

结构件

卡片里面用四个结构件搭:它们负责内边距、滚动和无障碍接线。

FloatingHeader / FloatingBody / FloatingFooter / FloatingGrip

属性类型默认值说明
FloatingHeadertitle, description, icon, level, closable—标题、说明和关闭按钮。标题和说明会自动成为卡片的 aria-labelledby / aria-describedby;在可拖拽的卡片里同时是拖拽把手。
FloatingBodypadded—内容区:占满头尾之间的空间,超出时原生滚动。内容自己通栏(列表、表格)时把 padded 关掉。
FloatingFooteralign: 'start' | 'end' | 'between' | 'stretch'—操作区:一排按钮,默认靠右。
FloatingGrip——顶上的小横杠,也是拖拽关闭的把手。纯装饰,对读屏隐藏。Sheet 默认自带。

在结构件或自己的子组件里,可以用 useFloatingCard() 拿到所在卡片的 close()、placement、modal 等。

FloatingCard 属性

属性

属性类型默认值说明
open*boolean—是否打开。基座只有受控写法;封装组件另外支持 defaultOpen。
onOpenChange(open: boolean) => void—请求开合时触发(Esc、点遮罩、关闭按钮、拖拽关闭)。
placement'center' | 'left' | 'right' | 'top' | 'bottom''center'停靠位置。
modalbooleantrue模态:遮罩、锁滚动、圈焦点。
dismissiblebooleantrue允许 Esc 和点遮罩关闭。
sizenumber | string—主轴尺寸:left / right / center 是宽度,top / bottom 是高度。数字按 px。永远不会超过可用空间。
gapnumber12(窄屏 8)卡片到边缘的间隙(px)。
stretchbooleantrue贴边时是否在另一个方向上撑满;false 则按内容大小并居中。
role'dialog' | 'alertdialog' | 'complementary' | 'region''dialog'语义角色。
initialFocusRefObject<HTMLElement>—模态打开时先聚焦哪个元素;不传则找带 data-autofocus 的元素,再没有就聚焦卡片本身。
restoreFocusbooleantrue关闭后把焦点还给之前的元素。
containerHTMLElement | null—渲染到哪个容器,默认 body。
containedbooleanfalse配合 container:把浮层限制在容器范围内。
dragToDismissbooleanfalse按住带 data-ok-drag-handle 的区域朝停靠的一侧拖拽关闭。只对贴边的卡片有效。
onExited() => void—退场动画结束、节点卸载之后。