Carbon React 的 UI Shell 组件体系全解析:用 Header、SideNav 与 Switcher 搭建产品外壳
2026/9/16 17:23:18 网站建设 项目流程

Carbon React 的 UI Shell 组件体系全解析:用 Header、SideNav 与 Switcher 搭建产品外壳

【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbon

UI Shell 是 IBM Carbon Design System 中用于构建"产品外壳"(product shell)的一组 React 组件集合,它承载应用最顶层的导航框架:顶栏(Header)、侧边导航(SideNav)以及右侧面板(RightPanel / HeaderPanel)中的产品切换器(Switcher)。本篇文章以 packages/react/src/components/UIShell/README.md 为骨架,逐层拆解每一个组件的职责、Props、组合关系与底层实现,并结合仓库源码给出可直接复制的实战示例,帮助你快速为 Carbon 应用搭建符合 IBM 设计规范的一体化导航壳。

UI Shell 在 Carbon 中的定位

在 Carbon 的官方设计语境中,"Shell" 承担的角色类似 macOS 顶部的 Apple 菜单、全局系统控件以及底部/侧边 Dock:它把整个产品组合(portfolio)的导航统一收纳起来,让用户在任意页面都能快速定位并切换位置。Carbon 的 UI Shell 结构大致可分为三大区域:

  • Header(顶栏):应用最顶部的横条,包含菜单按钮、产品名、导航链接与全局操作(搜索、通知、应用切换等);
  • SideNav(侧边导航):位于顶栏下方或随顶栏联动的次级导航容器,可固定宽度或灵活展开;
  • RightPanel / HeaderPanel(右侧面板):配合全局操作弹出的抽屉式面板,常用于承载应用切换器(Switcher)。

在代码层面,所有这些组件都由 packages/react/src/index.ts 通过export * from './components/UIShell'统一导出,你可以直接从@carbon/react包中引入。

组件总览:UI Shell 的完整家族树

根据 README 的组件清单,UI Shell 由三个顶级容器及其子组件构成。下面用一棵组件树展示其完整结构(圆括号内为该组件的核心职责):

Header(应用顶栏) ├── HeaderMenuButton(菜单展开/收起触发按钮) ├── HeaderName(产品名称,如 "IBM [Product]") ├── HeaderGlobalBar(全局操作容器) │ └── HeaderGlobalAction(单个全局操作,如搜索/通知图标按钮) └── HeaderNavigation(顶栏导航区) ├── HeaderMenu(可展开的导航子菜单) └── HeaderMenuItem(导航菜单项,通常是链接) SideNav(页面侧边导航容器) ├── SideNavHeader(侧边导航顶部区域) │ └── SideNavDetails(侧边导航标题) │ └── SideNavSwitcher(顶层的可选项切换下拉框) ├── SideNavItems(子导航项容器) │ ├── SideNavLink(侧边导航中的链接) │ └── SideNavMenu(可折叠的分组菜单) │ └── SideNavMenuItem(分组菜单内的链接) RightPanel / HeaderPanel(右侧面板容器) └── Switcher(面板内的产品链接列表) ├── SwitcherItem(列表项,通常是链接) └── SwitcherDivider(列表项之间的分隔线)

此外,UI Shell 还提供了几个在 README 之外但同样由 index.ts 导出的配套组件:Content(页面主内容容器)、SkipToContent(无障碍"跳过导航"链接)、HeaderContainer(顶栏与侧边导航联动状态管理)、HeaderSideNavItems(把 Header 菜单项复用到 SideNav 中)等。

Header 顶栏家族:逐组件拆解

Header 家族全部以<header>语义元素渲染,由 Header.tsx 定义,其内部通过usePrefix生成cds--header类名,并透传aria-label/aria-labelledby,方便屏幕阅读器识别整个导航区。

HeaderMenuButton:顶栏最左侧的汉堡按钮

HeaderMenuButton.tsx 渲染一个<button type="button">,默认图标来自@carbon/icons-reactMenuClose。它支持的 Props 包括:

  • isActive:为true时显示关闭(Close)图标,用于表示侧边导航当前处于展开状态;
  • isCollapsible:为false时按钮会被加--header__menu-toggle__hidden类而隐藏(用于不需要展开/收起交互的场景);
  • renderMenuIcon/renderCloseIcon:自定义展开与收起图标;
  • aria-label:既作为无障碍标签,也会被写入title属性。
<HeaderMenuButton aria-label={isSideNavExpanded ? 'Close menu' : 'Open menu'} onClick={onClickSideNavExpand} isActive={isSideNavExpanded} aria-expanded={isSideNavExpanded} />

HeaderName:展示产品名称

HeaderName.tsx 渲染一个链接,默认prefix'IBM',会在产品名前渲染前缀与空格,形成 "IBM [Platform Name]" 的经典样式:

<HeaderName href="/" prefix="IBM"> My Product </HeaderName>

如果不想显示前缀,将prefix传为空字符串即可。它底层复用 Link.tsx,因此也支持as多态替换为 React Router 的Link等自定义元素。

HeaderGlobalBar 与 HeaderGlobalAction:全局操作区

HeaderGlobalBar.tsx 是一个纯容器(cds--header__global),用于包裹多个HeaderGlobalAction。而 HeaderGlobalAction.tsx 本质是一个特殊的图标按钮——它基于Button组件并固定使用hasIconOnlysize="lg"kind="ghost"tooltipPosition="bottom",同时额外支持:

  • isActive:激活态(例如通知面板打开时高亮);
  • tooltipAlignment:tooltip 对齐方式,取值'start' | 'center' | 'end'
  • tooltipHighContrast:默认true,使用高对比度 tooltip 主题;
  • tooltipDropShadow:是否给 tooltip 加投影;
  • 注意children应传入一个图标组件。
<HeaderGlobalBar> <HeaderGlobalAction aria-label="Search" onClick={handleSearch}> <Search size={20} /> </HeaderGlobalAction> <HeaderGlobalAction aria-label="Notifications" onClick={handleNotify}> <Notification size={20} /> </HeaderGlobalAction> </HeaderGlobalBar>

HeaderNavigation / HeaderMenu / HeaderMenuItem:顶栏导航

HeaderNavigation.tsx 渲染<nav>+<ul class="cds--header__menu-bar">,作为导航链接与子菜单的容器;HeaderMenuItem.tsx 渲染单个菜单链接,支持isActive(高亮当前页,配合aria-current="page")与多态as;HeaderMenu.tsx 渲染可展开的下拉菜单:

  • menuLinkName(必填):菜单标题文字;
  • children:应传入一系列HeaderMenuItem
  • isActive:激活整个菜单;isCurrentPage为已弃用别名(源码中用deprecate标记);
  • renderMenuContent:自定义菜单标题右侧内容(默认是ChevronDown箭头)。

从 HeaderMenu.tsx 的实现可以看到完整的无障碍交互:触发元素带aria-haspopup="menu"aria-expanded;按 Enter/Space 切换展开、按 Escape 关闭并把焦点还给菜单按钮;失焦到菜单外部时自动收起;所有子项tabIndex被设为 -1,由组件统一管理焦点,避免 Tab 键经过大量菜单项。

<HeaderNavigation aria-label="IBM [Platform]"> <HeaderMenuItem href="/">Link 1</HeaderMenuItem> <HeaderMenu menuLinkName="Link 4" aria-label="Link 4"> <HeaderMenuItem href="/sub-1">Sub-link 1</HeaderMenuItem> <HeaderMenuItem isActive href="/sub-2">Sub-link 2</HeaderMenuItem> </HeaderMenu> </HeaderNavigation>

HeaderContainer:顶栏与侧边导航的联动状态

HeaderContainer.tsx 采用render prop模式,统一管理顶栏菜单按钮与 SideNav 的展开/收起状态,其render函数会收到两个参数:

  • isSideNavExpanded:当前侧边导航是否展开;
  • onClickSideNavExpand:切换展开状态的回调。

同时它监听了全局Escape键:按下时自动收起侧边导航(源码位于 HeaderContainer.tsx)。如果不想引入额外状态管理,直接使用HeaderContainer是最省心的方式:

<HeaderContainer render={({ isSideNavExpanded, onClickSideNavExpand }) => ( <Header aria-label="Platform"> <SkipToContent /> <HeaderMenuButton aria-label={isSideNavExpanded ? 'Close menu' : 'Open menu'} onClick={onClickSideNavExpand} isActive={isSideNavExpanded} aria-expanded={isSideNavExpanded} /> <HeaderName href="/" prefix="IBM"> Platform </HeaderName> <SideNav aria-label="Side navigation" expanded={isSideNavExpanded} isPersistent={false} onSideNavBlur={onClickSideNavExpand}> <SideNavItems>{/* ... */}</SideNavItems> </SideNav> </Header> )} />

SideNav 侧边导航家族:容器、标题与菜单

SideNav是页面级次级导航的容器,源码位于 SideNav.tsx,它通过forwardRef暴露 DOM 节点,并支持大量配置项,是 UI Shell 中最灵活的组件之一。

SideNav 的关键 Props 与受控/非受控模式

  • expanded:传入该 prop 后 SideNav 变为受控组件(源码通过expandedProp !== undefined判定,见 SideNav.tsx);
  • defaultExpanded:非受控模式下的初始展开状态,默认false
  • isChildOfHeader:默认true,表示 SideNav 挂在 Header 之下(对应cds--side-nav--ux样式);独立页面导航可设为false
  • isFixedNav:默认false,为true时不渲染遮罩层,展开/收起由cds--side-nav--expanded/--collapsed控制;
  • isPersistent:默认true,为false时侧边导航在收起状态下会被隐藏(cds--side-nav--hidden),并在< lg断点下通过inert属性从可聚焦元素中移除(SideNav.tsx);
  • isRail:开启 rail(窄轨)模式,鼠标悬停/移出与点击会临时展开侧边导航(相关事件处理见 SideNav.tsx);
  • enterDelayMs:展开动画的延迟毫秒数,默认100
  • addFocusListeners/addMouseListeners:是否添加焦点/鼠标监听(默认均为true);
  • onToggleonOverlayClickonSideNavBlur:展开切换、遮罩点击、失焦时的回调;
  • href:按 Escape 收起时跳转的目标地址。

SideNav 的展开状态会通过 SideNavContext.tsx 以 React Context 向SideNavItemsSideNavMenu等后代传递isRailisSideNavExpanded,子组件据此决定图标/文本的显隐与tabIndex

SideNavHeader / SideNavDetails / SideNavSwitcher:侧边导航头部

  • SideNavHeader.tsx:渲染侧边导航顶部区域,renderIcon(必填)用于显示品牌或产品图标;
  • SideNavDetails.tsx:渲染标题(title必填,输出<h2 class="cds--side-nav__title">),可把SideNavSwitcher作为子组件放入;
  • SideNavSwitcher.tsx:渲染一个<select>下拉框,用于在顶层产品/环境间切换。labelTextoptions: string[]均为必填,onChange在失焦或变更时触发;默认第一个 disabled 选项即labelText本身。
<SideNavHeader renderIcon={ProductIcon}> <SideNavDetails title="Platform"> <SideNavSwitcher labelText="Environment" onChange={(event) => setEnv(event.target.value)} options={['Production', 'Staging', 'Development']} /> </SideNavDetails> </SideNavHeader>

SideNavItems / SideNavLink / SideNavMenu / SideNavMenuItem

  • SideNavItems.tsx:渲染<ul class="cds--side-nav__items">,向下传递 SideNavContext;
  • SideNavLink.tsx:渲染单个链接,支持isActivelarge(大号变体)、renderIcon(图标)以及tabIndex覆盖;
  • SideNavMenu.tsx:渲染可折叠分组,title必填,支持defaultExpanded(默认false)、isActivelargerenderIcon;按钮带aria-expanded,按 Escape 收起。它还会自动检测后代是否有isActive/aria-current的子项(hasActiveDescendant),从而在展开前就给分组加上激活态样式(SideNavMenu.tsx);
  • SideNavMenuItem.tsx:分组内的链接项,支持isActivearia-current="page"与多态as
<SideNav aria-label="Side navigation"> <SideNavItems> <SideNavLink renderIcon={DashboardIcon} href="/overview"> Overview </SideNavLink> <SideNavMenu renderIcon={FolderIcon} title="Category" defaultExpanded> <SideNavMenuItem href="/reports">Report 1</SideNavMenuItem> <SideNavMenuItem isActive aria-current="page" href="/reports/2"> Report 2 </SideNavMenuItem> </SideNavMenu> </SideNavItems> </SideNav>

RightPanel 右侧面板与 Switcher 应用切换器

README 中列出的RightPanel在源码中对应 HeaderPanel.tsx。它是一个可展开的面板容器,通常配合HeaderGlobalAction使用:

  • expanded:面板是否展开(支持受控);
  • addFocusListeners:默认true,控制失焦/键盘监听;
  • onHeaderPanelFocus:面板收起时的回调;
  • href:按 Escape 时跳转的地址。

面板内部可放置 Switcher.tsx,它渲染一个链接列表(cds--switcher),要求传入aria-labelaria-labelledby(二者取其一),并实现方向键焦点循环管理(handleSwitcherItemFocus,见 Switcher.tsx)。其子组件:

  • SwitcherItem.tsx:单个产品链接,支持hreftargetrelisSelected
  • SwitcherDivider.tsx:列表项之间的分隔线。
<HeaderGlobalAction aria-label={isPanelExpanded ? 'Close switcher' : 'Open switcher'} isActive={isPanelExpanded} onClick={() => setIsPanelExpanded(!isPanelExpanded)} tooltipAlignment="end"> <SwitcherIcon size={20} /> </HeaderGlobalAction> <HeaderPanel expanded={isPanelExpanded} href="#switcher-button"> <Switcher aria-label="Switcher Container" expanded={isPanelExpanded}> <SwitcherItem href="/app-1">App 1</SwitcherItem> <SwitcherDivider /> <SwitcherItem href="/app-2">App 2</SwitcherItem> </Switcher> </HeaderPanel>

配套组件:Content、SkipToContent 与 HeaderSideNavItems

  • Content.ts:页面主内容容器,默认渲染<main class="cds--content">,可通过tagName自定义标签(如'section'),适合与SkipToContent的锚点配合;
  • SkipToContent.tsx:无障碍"跳过导航"链接,默认文案为'Skip to main content'href'#main-content'tabIndex为 0;
  • HeaderSideNavItems.tsx:允许把HeaderMenuItem原样复用到 SideNav 内,hasDivider(默认false)可为复用的导航区与下方 SideNav 项之间增加分隔线。
<SideNav aria-label="Side navigation"> <SideNavItems> <HeaderSideNavItems hasDivider> <HeaderMenuItem href="/">Link 1</HeaderMenuItem> <HeaderMenuItem href="/2">Link 2</HeaderMenuItem> </HeaderSideNavItems> <SideNavLink href="/other">Other</SideNavLink> </SideNavItems> </SideNav>

响应式与无障碍行为:来自源码的实现细节

Carbon 官方对 Header 的响应式要求是:在较小屏幕下,带持久化 SideNav 的 Header 应将侧边导航折叠为汉堡菜单。这一行为在源码中有两处直接体现:

  1. 断点感知:SideNav 通过@carbon/layoutbreakpoints.lg构造媒体查询(min-width: lg),并用useMatchMedia判断当前视口(SideNav.tsx);在非 rail 且未展开且视口小于 lg 时,为导航节点设置inert属性,将其从 Tab 焦点序列中移除(SideNav.tsx)。
  2. 焦点联动:当用户 Tab 离开展开的菜单按钮时,焦点会被引导到 SideNav 上(useWindowEvent('keydown')处理 Tab 键,见 SideNav.tsx);而Escape键在HeaderContainerSideNavHeaderMenuSideNavMenuHeaderPanel中均有统一的收起处理,保证键盘用户可以随时退出导航层级。

交互行为均有对应的单元测试覆盖,例如 HeaderMenu-test.js、SideNav-test.js、Switcher-test.js 等,可据此验证各组件的展开/收起、键盘导航与激活态逻辑。

从示例到落地:可运行的整体骨架

仓库中的 Storybook 故事文件 UIShell.HeaderBase.stories.js 提供了多个开箱即用的组合示例,覆盖以下场景:

  • Header with Navigation:菜单按钮 + 产品名 + 顶栏导航 + 非持久化 SideNav;
  • Header with Navigation and Actions:在上述基础上加入HeaderGlobalBar(搜索/通知/应用切换);
  • Header with Navigation, Actions and Side Nav:完整形态,SideNav 内混用HeaderSideNavItemsSideNavMenuSideNavLink
  • Header with Side Nav:仅含产品名 + SideNav 的轻量布局;
  • Header with Actions and Right Panel:通知图标 +HeaderPanel右侧面板;
  • Header with Actions and Switcher:应用切换器面板的完整交互。

下面是一个精简版的可运行骨架,把上述知识点串起来:

import { Content, Header, HeaderContainer, HeaderMenuButton, HeaderName, HeaderNavigation, HeaderMenuItem, HeaderMenu, HeaderGlobalBar, HeaderGlobalAction, SideNav, SideNavItems, SideNavLink, SideNavMenu, SideNavMenuItem, SkipToContent, } from '@carbon/react'; import { Search, Notification, Fade } from '@carbon/icons-react'; export default function AppShell() { return ( <HeaderContainer render={({ isSideNavExpanded, onClickSideNavExpand }) => ( <> <Header aria-label="IBM Platform Name"> <SkipToContent /> <HeaderMenuButton aria-label={isSideNavExpanded ? 'Close menu' : 'Open menu'} onClick={onClickSideNavExpand} isActive={isSideNavExpanded} aria-expanded={isSideNavExpanded} /> <HeaderName href="/" prefix="IBM"> [Platform] </HeaderName> <HeaderNavigation aria-label="IBM [Platform]"> <HeaderMenuItem href="/">Link 1</HeaderMenuItem> <HeaderMenu menuLinkName="Link 2"> <HeaderMenuItem href="/2a">Sub-link 1</HeaderMenuItem> <HeaderMenuItem href="/2b">Sub-link 2</HeaderMenuItem> </HeaderMenu> </HeaderNavigation> <HeaderGlobalBar> <HeaderGlobalAction aria-label="Search" onClick={() => {}}> <Search size={20} /> </HeaderGlobalAction> <HeaderGlobalAction aria-label="Notifications" onClick={() => {}}> <Notification size={20} /> </HeaderGlobalAction> </HeaderGlobalBar> <SideNav aria-label="Side navigation" expanded={isSideNavExpanded} isPersistent={false} onSideNavBlur={onClickSideNavExpand}> <SideNavItems> <SideNavLink renderIcon={Fade} href="/overview"> Overview </SideNavLink> <SideNavMenu renderIcon={Fade} title="Category"> <SideNavMenuItem href="/reports">Report</SideNavMenuItem> </SideNavMenu> </SideNavItems> </SideNav> </Header> <Content id="main-content"> {/* 页面主体内容 */} </Content> </> )} /> ); }

小结

Carbon 的 UI Shell 通过HeaderSideNavHeaderPanel三大容器及其子组件,为产品级应用提供了统一、可访问、响应式的外壳方案:顶栏负责品牌与全局操作,侧边导航承载次级信息架构,右侧面板与 Switcher 完成应用间的切换。理解这些组件各自的 Props、受控/非受控语义以及 Context 传递关系,是快速落地一套符合 Carbon 规范导航框架的关键。建议在实际开发中先阅读 UIShell README 掌握组件树,再对照 Storybook 示例 选择与业务最匹配的组合形态,最后用各组件对应的单元测试(如 SideNav-test.js)验证交互边界。

【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbon

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询