Main content area
<Sidebar.Provider defaultOpen>
  <Sidebar>
    <Sidebar.Content>
      <Sidebar.Group>
        <Sidebar.GroupLabel>Overview</Sidebar.GroupLabel>
        <Sidebar.Menu>
          <Sidebar.MenuButton icon={HouseIcon} active>Home</Sidebar.MenuButton>
          <Sidebar.MenuButton icon={GlobeIcon}>Domains</Sidebar.MenuButton>
        </Sidebar.Menu>
      </Sidebar.Group>
    </Sidebar.Content>
  </Sidebar>
</Sidebar.Provider>

安装

批量导入

import { Sidebar } from "@cloudflare/kumo";

细粒度导入

import { Sidebar } from "@cloudflare/kumo/components/sidebar";

用法

至少需要 Provider、Sidebar、Content(可滚动区域)、Menu 和MenuButton。 添加 Header /Footer 可将内容固定在滚动区域的上方或下方。 使用 Group +GroupLabel 来划分区块。

import { Sidebar } from "@cloudflare/kumo";
import { HouseIcon, CodeIcon, GearIcon } from "@phosphor-icons/react";

function AppLayout({ children }) {
  return (
    <Sidebar.Provider defaultOpen>
      <Sidebar>
        <Sidebar.Content>
          <Sidebar.Group>
            <Sidebar.GroupLabel>Navigation</Sidebar.GroupLabel>
            <Sidebar.Menu>
              <Sidebar.MenuButton icon={HouseIcon} active>Home</Sidebar.MenuButton>
              {/* MenuItem only needed to wrap Collapsible */}
              <Sidebar.MenuItem>
                <Sidebar.Collapsible>
                  <Sidebar.CollapsibleTrigger
                    render={
                      <Sidebar.MenuButton icon={CodeIcon}>
                        Compute <Sidebar.MenuChevron />
                      </Sidebar.MenuButton>
                    }
                  />
                  <Sidebar.CollapsibleContent>
                    <Sidebar.MenuSub>
                      <Sidebar.MenuSubButton>Workers</Sidebar.MenuSubButton>
                    </Sidebar.MenuSub>
                  </Sidebar.CollapsibleContent>
                </Sidebar.Collapsible>
              </Sidebar.MenuItem>
            </Sidebar.Menu>
          </Sidebar.Group>
        </Sidebar.Content>
        <Sidebar.Footer>
          <Sidebar.Trigger />
        </Sidebar.Footer>
      </Sidebar>
      <div className="flex-1">{children}</div>
    </Sidebar.Provider>
  );
}

示例

基本用法

最小可用的侧边栏:只包含分组、菜单按钮和可折叠子菜单,没有页眉和页脚。MenuButton 和 MenuSubButton 会自动包裹 <li> — 无需 MenuItem / MenuSubItem。

Main content area
<Sidebar.Provider defaultOpen>
  <Sidebar>
    <Sidebar.Content>
      <Sidebar.Group>
        <Sidebar.GroupLabel>Overview</Sidebar.GroupLabel>
        <Sidebar.Menu>
          <Sidebar.MenuButton icon={HouseIcon} active>Home</Sidebar.MenuButton>
          <Sidebar.MenuButton icon={ChartBarIcon}>Analytics</Sidebar.MenuButton>
        </Sidebar.Menu>
      </Sidebar.Group>
    </Sidebar.Content>
  </Sidebar>
</Sidebar.Provider>

切换与折叠状态

在页脚中使用 Sidebar.Trigger,或通过 useSidebar().toggleSidebar 以编程方式切换。传入 tooltip 可在折叠时悬停显示标签。

Click the button or the sidebar trigger to toggle

<Sidebar.MenuButton icon={HouseIcon} tooltip="Home" active>
  Home
</Sidebar.MenuButton>

<Sidebar.Footer>
  <Sidebar.Trigger />
</Sidebar.Footer>

// 或以编程方式:
const { toggleSidebar } = useSidebar();

加载中

在路由与权限解析期间,Sidebar.Loading 会以导航项形状的骨架行替代导航内容。折叠时只保留图标方块。遵循减弱动效设置。

Toggle to compare the loading state with the loaded nav

<Sidebar>
  <Sidebar.Header>…</Sidebar.Header>
  {isLoading ? (
    <Sidebar.Loading />
  ) : (
    <Sidebar.Content>…</Sidebar.Content>
  )}
  <Sidebar.Footer>…</Sidebar.Footer>
</Sidebar>

可调整宽度

拖动边缘即可调整宽度。拖到 minWidth 以下会折叠;从折叠状态向外拖动会展开。 调整手柄支持键盘操作:可用方向键、Home 和 End。

Drag the sidebar edge to resize

<Sidebar.Provider defaultOpen resizable defaultWidth={240} minWidth={180} maxWidth={400}>
  <Sidebar>
    <Sidebar.Content>...</Sidebar.Content>
    <Sidebar.ResizeHandle />
  </Sidebar>
</Sidebar.Provider>

右侧

使用 side="right" 可将侧边栏放在右边缘。在 DOM 中需将 <main> 放在 <Sidebar> 之前。

Main content area
<Sidebar.Provider defaultOpen side="right">
  <main className="flex-1">...</main>
  <Sidebar>
    <Sidebar.Content>
      <Sidebar.Group>
        <Sidebar.GroupLabel>Details</Sidebar.GroupLabel>
        <Sidebar.Menu>
          <Sidebar.MenuButton icon={GearIcon} active>Properties</Sidebar.MenuButton>
          <Sidebar.MenuButton icon={ChartBarIcon}>Metrics</Sidebar.MenuButton>
        </Sidebar.Menu>
      </Sidebar.Group>
    </Sidebar.Content>
  </Sidebar>
</Sidebar.Provider>

窥视

在 Provider 上设置 peekable。侧边栏折叠时, 悬停或聚焦会临时展开它,移开后重新折叠。 窥视期间 data-state 属性为 "peeking"。

State: Expanded

Collapse, then hover the sidebar to peek

<Sidebar.Provider defaultOpen peekable>
  <Sidebar>
    <Sidebar.Content>...</Sidebar.Content>
    <Sidebar.Footer>
      <Sidebar.Trigger />
    </Sidebar.Footer>
  </Sidebar>
</Sidebar.Provider>

// 读取窥视状态:
const { state, isPeeking } = useSidebar();
// state: "expanded" | "collapsed" | "peeking"

自动滚动

在较长的可折叠区域上使用 autoScrollOnOpen,可让新展开的内容保持在可视范围内。 当滚动区域底部附近的分组在可视区域下方展开时,这尤其有用。

Open Workers near the bottom of the list

<Sidebar.Collapsible autoScrollOnOpen>
  <Sidebar.CollapsibleTrigger
    render={
      <Sidebar.MenuButton icon={CodeIcon}>
        Workers <Sidebar.MenuChevron />
      </Sidebar.MenuButton>
    }
  />
  <Sidebar.CollapsibleContent>
    <Sidebar.MenuSub>...</Sidebar.MenuSub>
  </Sidebar.CollapsibleContent>
</Sidebar.Collapsible>

过渡完成

当需要等待侧边栏或可折叠区域的过渡动画结束后再执行工作(如测量布局或滚动到新展开的内容)时,使用 onOpenChangeComplete。回调会收到完成后的展开状态,在减弱动效禁用过渡时同样会触发。Sidebar.Collapsible 在侧边栏或移动抽屉显示/隐藏已展开的区域时同样会完成,因此回调始终与用户所见一致。

No transition has completed yet.

Toggle the sidebar or open Compute to see the completion callback.

const [completion, setCompletion] = useState("");

<Sidebar.Provider
  defaultOpen
  onOpenChangeComplete={(open) => {
    setCompletion("Sidebar " + (open ? "opened." : "collapsed."));
  }}
>
  <Sidebar>
    <Sidebar.Content>
      <Sidebar.Menu>
        <Sidebar.MenuItem>
          <Sidebar.Collapsible
            onOpenChangeComplete={(open) => {
              setCompletion(
                "Compute section " + (open ? "opened." : "collapsed."),
              );
            }}
          >
            <Sidebar.CollapsibleTrigger render={<Sidebar.MenuButton>Compute</Sidebar.MenuButton>} />
            <Sidebar.CollapsibleContent>...</Sidebar.CollapsibleContent>
          </Sidebar.Collapsible>
        </Sidebar.MenuItem>
      </Sidebar.Menu>
    </Sidebar.Content>
    <Sidebar.Footer><Sidebar.Trigger /></Sidebar.Footer>
  </Sidebar>
</Sidebar.Provider>

滚动到指定项

为导航项标记 itemId,然后调用 useSidebar().scrollToItem(id, options) 将其滚动到可视区域。 若该项已完全可见,可使用 scrollItemIntoView(id, options) 保持当前位置不变。align 可为 "start"、"center"、"end" 或 "auto"(默认 — 已可见时不执行操作)。behavior 默认为 "auto"(即时),使跨应用跳转进入时不产生动画;应用内的「跳转到区块」流程可传入 "smooth"。遵循 prefers-reduced-motion。与 Sidebar.SlidingViews 配合正常 — 导航项通过每个 Provider 的注册表解析,而非 DOM 查询。

itemId 适用于两种用法:直接放在 Sidebar.MenuButton 上(常见场景,会自动包裹 <li>),或在包裹 Collapsible 时放在 Sidebar.MenuItem 上。若将 itemId 放在嵌套于 MenuItem 内的 MenuButton 上同样有效 — 按钮本身会成为滚动目标。

Jump to any tagged item.

<Sidebar.MenuButton itemId="zero-trust" href="/zt">
  Zero Trust
</Sidebar.MenuButton>

// 其他位置:
const { scrollToItem } = useSidebar();
scrollToItem("zero-trust", { align: "center", behavior: "smooth" });

滑动视图

使用 Sidebar.SlidingViews 和 Sidebar.SlidingView可在不同导航表面之间实现水平动画过渡(如账户 ↔ 区域)。 未激活的视图会自动标记 aria-hidden 和 inert。 动画遵循 prefers-reduced-motion。

Active: Account surface

Click the header button to slide between views

const [surface, setSurface] = useState("account");

<Sidebar.SlidingViews activeKey={surface} direction="left">
  <Sidebar.SlidingView value="account">
    <Sidebar.Content>...account nav...</Sidebar.Content>
  </Sidebar.SlidingView>
  <Sidebar.SlidingView value="zone">
    <Sidebar.Content>...zone nav...</Sidebar.Content>
  </Sidebar.SlidingView>
</Sidebar.SlidingViews>

完整示例

囊括全部功能的完整示例,展示每个子组件:带账户切换器的页眉、带标签的分组、含嵌套展开的可折叠区域、徽标、滑动视图以及页脚触发器。

Main content area
<Sidebar>
  <Sidebar.Header>
    <AccountSwitcher />
  </Sidebar.Header>
  <Sidebar.Content>
    <Sidebar.Group>
      <Sidebar.Menu>
        <Sidebar.MenuButton icon={HouseIcon} active>Home</Sidebar.MenuButton>
      </Sidebar.Menu>
    </Sidebar.Group>
    <Sidebar.Group>
      <Sidebar.GroupLabel>Build</Sidebar.GroupLabel>
      <Sidebar.Menu>
        <Sidebar.MenuItem>
          <Sidebar.Collapsible defaultOpen>
            <Sidebar.CollapsibleTrigger
              render={
                <Sidebar.MenuButton icon={CodeIcon}>
                  Compute <Sidebar.MenuChevron />
                </Sidebar.MenuButton>
              }
            />
            <Sidebar.CollapsibleContent>
              <Sidebar.MenuSub>
                <Sidebar.MenuSubButton>
                  Containers <Sidebar.MenuBadge>Beta</Sidebar.MenuBadge>
                </Sidebar.MenuSubButton>
              </Sidebar.MenuSub>
            </Sidebar.CollapsibleContent>
          </Sidebar.Collapsible>
        </Sidebar.MenuItem>
      </Sidebar.Menu>
    </Sidebar.Group>
  </Sidebar.Content>
  <Sidebar.Footer>
    <Sidebar.Trigger />
  </Sidebar.Footer>
</Sidebar>

移动端

在窄视口下,侧边栏渲染为导航抽屉。使用 mobileBreakpoint 控制阈值。 抽屉关闭时使用 inert 和 aria-hidden,打开时将焦点移入,并支持 Escape 关闭。 此演示通过极高的断点强制进入移动端模式。

Click the button to open the mobile sidebar

Press Escape or click the backdrop to close

<Sidebar.Provider mobileBreakpoint={9999}>
  <Sidebar>
    <Sidebar.Content>...</Sidebar.Content>
  </Sidebar>
</Sidebar.Provider>

移动端全屏

传入 fullScreenOnMobile 可让抽屉覆盖整个视口,而不是只留一条页面可见。 导航项拥有更宽松的触摸目标,由于面板背后没有内容,背景遮罩不再显示。 在页眉内添加 Sidebar.Close,以便从导航内部关闭面板。 配合页面页眉中的面包屑,关闭导航后仍能看到当前路由。

Account analytics

Drill into the nav — the trail is derived from the tree, so it always matches where you are.

<Sidebar.Provider mobileBreakpoint={9999}>
  <Sidebar fullScreenOnMobile>
    <Sidebar.Header>
      <Sidebar.Close />
    </Sidebar.Header>
    <Sidebar.Content>...</Sidebar.Content>
  </Sidebar>

  <header>
    <Sidebar.Trigger />
    <Breadcrumbs size="sm">
      <Breadcrumbs.Link href="/">Company</Breadcrumbs.Link>
      <Breadcrumbs.Separator />
      <Breadcrumbs.Current>Analytics</Breadcrumbs.Current>
    </Breadcrumbs>
  </header>
</Sidebar.Provider>

API 参考

Sidebar

主侧边栏容器。桌面端渲染为 <aside>,移动端渲染为导航抽屉。

属性类型默认值说明
defaultOpenboolean-Initial open state when uncontrolled.
openboolean-Controlled open state.
variant"sidebar" | "floating" | "inset""sidebar"Sidebar layout variant.
side"left" | "right""left"Which side the sidebar is on.
collapsible"icon" | "offcanvas" | "none""icon"-
resizableboolean-Enable drag-to-resize on the sidebar edge.
defaultWidthnumber-Initial width in pixels when resizable.
minWidthnumber-Minimum width in pixels when resizing.
maxWidthnumber-Maximum width in pixels when resizing.
containedboolean-When true, the collapsed sidebar uses absolute positioning instead of fixed, keeping it scoped inside a bounded parent. Useful for demos and embedded sidebars.
peekableboolean-When true, hovering or focusing the collapsed sidebar temporarily expands it. The `state` will be `"peeking"` during the peek. Moving away collapses it back.
animationDurationnumber-Duration of sidebar expand/collapse animation in milliseconds.
mobileBreakpointnumber-Viewport width (in px) below which the sidebar renders as a mobile dialog sheet instead of the desktop aside rail.
childrenReactNode-Content — typically `<Sidebar>` + main content.
classNamestring-Additional CSS classes for the wrapper div.

Sidebar.Provider

管理展开/折叠状态与移动端检测的 Context 提供者。

属性类型默认值说明
defaultOpenboolean-Initial open state when uncontrolled.
openboolean-Controlled open state.
variantSidebarVariant-Sidebar layout variant.
sideSidebarSide-Which side the sidebar is on.
collapsible"icon" | "offcanvas" | "none"--
resizableboolean-Enable drag-to-resize on the sidebar edge.
defaultWidthnumber-Initial width in pixels when resizable.
minWidthnumber-Minimum width in pixels when resizing.
maxWidthnumber-Maximum width in pixels when resizing.
containedboolean-When true, the collapsed sidebar uses absolute positioning instead of fixed, keeping it scoped inside a bounded parent. Useful for demos and embedded sidebars.
peekableboolean-When true, hovering or focusing the collapsed sidebar temporarily expands it. The `state` will be `"peeking"` during the peek. Moving away collapses it back.
animationDurationnumber-Duration of sidebar expand/collapse animation in milliseconds.
mobileBreakpointnumber-Viewport width (in px) below which the sidebar renders as a mobile dialog sheet instead of the desktop aside rail.
children*ReactNode-Content — typically `<Sidebar>` + main content.
classNamestring-Additional CSS classes for the wrapper div.

Sidebar.Content

可滚动的中间区域(flex-1 overflow-y-auto)。使用 Header / Footer 可将内容固定在此滚动区域的上方或下方。

Sidebar.MenuButton

主要交互元素。支持图标、激活状态、链接以及折叠时的自动提示。 自动包裹 <li> — 除非包裹 Collapsible,否则无需 MenuItem 包装器。

属性类型默认值说明
iconReact.ComponentType<{ className?: string }> | React.ReactNode--
activeboolean--
sizeSidebarMenuButtonSize-Button size. - `"base"` — Standard nav item - `"sm"` — Compact nav item
hrefstring--
targetReact.HTMLAttributeAnchorTarget-Link target — only meaningful when `href` is provided.
tooltipstring--
itemIdstring-Anchor id for `useSidebar().scrollToItem(id)`.
classNamestring--
childrenReactNode--

Sidebar.MenuSubButton

子菜单中的按钮,用于嵌套导航。 自动包裹 <li> — 无需 MenuSubItem 包装器。

属性类型默认值说明
activeboolean-Marks this sub-item as currently active/selected.
hrefstring-Navigation URL. When set, renders as a link via LinkProvider.
targetReact.HTMLAttributeAnchorTarget-Link target — only meaningful when `href` is provided.