浮动 UI
对所有相对于触发器定位的交互元素使用 @floating-ui/dom。不要手动计算坐标或依赖 CSS 绝对偏移来实现下拉菜单、弹出框、菜单、组合框列表或工具提示。
必需结构
- 通过
createPortal(..., document.body)渲染覆盖层,以避免布局和堆叠上下文将其裁剪。 - 在
autoUpdate内部使用computePosition进行定位;当覆盖层关闭或卸载时,清理autoUpdate返回的函数。 - 从
apps/desktop/src/components/ui中最接近的现有控件开始。匹配其placement、offset、flip、shift和size中间件,除非交互要求有意识的不同。 - 将计算出的
left、top、width 和可用高度存储在 React 状态中。将状态应用于门户根元素;不要直接修改元素样式。 - 当参考元素宽度或视口高度限制了覆盖层时,使用
size。可能增长的列表必须使用components/virtual/VirtualList.tsx;将固定的页眉和页脚保持在虚拟视口之外。
交互不变规则
- 当焦点位于门户覆盖层内部时,保持触发器的打开/焦点边框可见。
- 在外部指针交互和 Escape 键时关闭,然后将焦点返回到触发器。
- 外部点击检查必须同时包含触发器和覆盖层。嵌套的门户覆盖层必须阻止其指针事件到达父级的外部点击监听器,这样与子菜单交互永远不会关闭其父菜单。
- 使用
aria-expanded、aria-controls和适当的aria-haspopup暴露触发器状态;为覆盖层赋予匹配的菜单、列表框、对话框或工具提示语义。 - 验证在所有视口边缘的放置、嵌套覆盖层点击、键盘关闭、虚拟滚动和固定页脚行为。