跳至内容

Sidebar

基于 UI SDK core behavior 组合非模态导航面板。

Composition

import { Sidebar, createSidebarController } from "@lenso/primitives/sidebar";
import { useState } from "react";

export function Navigation() {
  const [controller] = useState(createSidebarController);
  return (
    <>
      <button type="button" onClick={() => controller.setOpen(true)}>Open navigation</button>
      <Sidebar.Root aria-label="Workspace navigation" controller={controller} defaultOpen>
        <Sidebar.Trigger>Toggle navigation</Sidebar.Trigger>
        <Sidebar.Panel aria-label="Workspace destinations">
          <Sidebar.Header>Workspace</Sidebar.Header>
          <Sidebar.Content>
            <Sidebar.Menu><Sidebar.MenuItem>
              <Sidebar.Item href="/" selected>Home</Sidebar.Item>
            </Sidebar.MenuItem></Sidebar.Menu>
          </Sidebar.Content>
          <Sidebar.Footer>Account settings</Sidebar.Footer>
        </Sidebar.Panel>
      </Sidebar.Root>
    </>
  );
}

框架无关的定义从 @lenso/primitives/sidebar/core 导出,使用 definePrimitive、 createChange、bindPanel 和 createFocusRecovery。React 入口仅通过 createParts 将 slot 映射成原生元素。

open、defaultOpen(默认 true)和 onOpenChange(open, details) 遵循 SDK 受控状态与取消契约。details 提供 reason、event、source 和 cancel()。 原因包括 trigger-press、escape-key 和 controller。受控组件由调用方提交状态。

每个 Root 独立。外部操作使用 createSidebarController() 的 isOpen()、 setOpen(open) 和 toggle()。多个 Root 通过原生 HTML 排布;移除旧 Group 注册表、 Provider、targetId 和单独的方向模型。dir 从 DOM 继承。

Root、Trigger、Rail 和 Panel 暴露 data-open、data-closed、data-side 以及 { open, side } 渲染状态。Root 的 slot 为 sidebar,其他 slot 以 sidebar- 开头。 Item 默认是链接,接收 selected、nested,暴露 aria-current、data-selected 和 data-nested。Rail 需要通过内容或 aria-label 命名,不再内置英文标签。

切换按钮使用原生 Enter/Space 激活,打开面板保留焦点,Tab 遵循浏览器顺序。 面板内 Escape 请求关闭;后代已阻止的事件不重复处理。SDK panel binding 在实际关闭 后恢复焦点,focus recovery 处理聚焦面板被移除的情况。取消或未提交的受控关闭不会 转移焦点。此组件没有模态焦点限制、外部点击关闭或移动端抽屉;模态面板应组合 SDK Drawer。

@lenso/ui/sidebar 是可放在面板内的导航视觉组件,拥有自己的组合方式,并非此面板 primitive 的视觉别名。