跳转到内容

SideNav 侧栏导航

用于组织应用的主要导航入口。

用法

组织应用的主要导航入口

组件结构

加粗的是必需部件。

data-scope="side-nav"root · list · item · group · group-label · branch · branch-trigger · branch-text · branch-indicator · positioner · branch-content · link · link-text

示例

折叠模式

以图标保留入口,子级在浮层中展开

尺寸

适配不同密度的应用侧栏

禁用项

保留不可用入口的位置与说明

设计指引

何时使用

  • 管理后台或控制台的主导航。
  • 导航包含分组或可展开的子级。

何时不用

  • 顶部横向导航,使用导航菜单
  • 文件或组织结构,使用

特性

  • 支持分组、嵌套分支与当前项高亮:当前项铺品牌淡底行面、字取淡底前景,不另画指示条;通往当前项的展开分支只落与悬停同档的中性面。
  • accordion 限制同一层级只展开一个分支。
  • 折叠后保留图标入口,子级在浮层中展示。
  • 方向键上下移动,左右键展开或收起分支。

组合

  • 放入布局的侧栏区域。

最佳实践

  • 导航层级保持在两到三级。
  • 折叠模式下为每个入口保留清晰图标。

反模式

  • 不要为单个入口创建分支。
  • 不要在折叠时卸载导航树。

API 参考

产物

自定义元素<xh-side-nav>
Vue 组件XhSideNavBranch XhSideNavBranchContent XhSideNavBranchIndicator XhSideNavBranchText XhSideNavBranchTrigger XhSideNavGroup XhSideNavGroupLabel XhSideNavItem XhSideNavLink XhSideNavLinkText XhSideNavList XhSideNavRoot
组合式函数useSideNav
状态机sideNavMachine
皮肤@xihan-ui/styles/side-nav.css

Props

属性类型必填说明
collectionSideNavNode[]入口树,层级与文本的唯一事实源。默认为空。
valuestring | null选中的叶子(单选)。提供即受控:cell 直读 prop,写入只发 onValueChange。
defaultValuestring | null
expandedValuestring[]展开集合。提供即受控,语义同上。
defaultExpandedValuestring[]
accordionboolean同层手风琴:展开一枝时收起同层其余分支,默认 false(可多开)。
collapsedboolean折叠为图标栏:内嵌展开整体收起、文字由皮肤隐藏,只剩图标一列。 顶层分支改为浮层弹出:悬停 / 点击 / 右方向键在旁侧弹出子级面板。
collapsedPopoutboolean折叠态下顶层分支是否弹出子级面板,默认 true;关闭即回到纯图标栏。
disabledboolean整个侧栏禁用。
loopboolean上下键到达首尾是否回绕,默认 false。
dirDirection文字方向,默认 ltr;只对调左右方向键的展开 / 收起语义。
toneTone语气:brand / neutral / success / warning / danger / info,决定使用哪族颜色。
sizeSize尺寸:sm / md / lg。
translationsPartial<SideNavTranslations>
onValueChange(details: SideNavValueChangeDetails) => void选中意图回调;受控时是唯一出口,非受控时随内部写入一并通知。
onExpandedValueChange(details: SideNavExpandedValueChangeDetails) => void展开集合变化意图回调;语义同上。

事件

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
value-changeSideNavValueChangeDetails选中变化;detail 为 { value: string | null }
expanded-value-changeSideNavExpandedValueChangeDetails展开集合变化;detail 为 { value: string[] }

插槽

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhSideNavRootdefaultSideNavRootSlotProps

状态

公开状态写入 data-state

部件取值
branch'open' | 'closed'
branch-trigger'open' | 'closed'
branch-indicator'open' | 'closed'
branch-content'open' | 'closed'
popout-positioner'open' | 'closed'

以下名称仅用于内部状态机。

状态idle · popout

事件VALUE.SET · LINK.SELECT · EXPANDED.SET · BRANCH.EXPAND · BRANCH.COLLAPSE · BRANCH.TOGGLE · NODE.FOCUS · FOCUS.CLEAR · POPOUT.OPEN · POPOUT.CLOSE · PRESENCE.SET · PRESS.START · PRESS.END

判据canChange · canPopout · canPress

connect API

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
valuestring | null选中的叶子;尚未选中时为 null。
expandedValuestring[]
collapsedboolean折叠为图标栏;顶层分支改为浮层弹出子级面板。
popoutValuestring | null折叠态下正在弹出子级面板的顶层分支;未弹出时为 null。
openPopout(value: string) => void弹出某顶层分支的子级面板(仅折叠态有效)。
closePopout() => void
focusedValuestring | nullroving tabindex 的锚点;无可见锚点时为 null。
isSelected(value: string) => boolean
isExpanded(value: string) => boolean
isActiveBranch(value: string) => boolean选中项的祖先分支:展开高亮当前所在的分支。
select(value: string) => void
setValue(next: string | null) => void
setExpandedValue(next: string[]) => void
expand(value: string) => void
collapse(value: string) => void
getRootProps() => T['element']
getListProps() => T['element']
getItemProps() => T['element']叶子行的列表项容器:链接与分支一样是列表的一条,作者把 link 包在其中。
getGroupProps(props: SideNavNodeProps) => T['element']
getGroupLabelProps(props: SideNavNodeProps) => T['element']
getBranchProps(props: SideNavNodeProps) => T['element']
getBranchTriggerProps(props: SideNavNodeProps) => T['button']
getBranchTextProps() => T['element']行文字的载体:折叠为图标栏时由皮肤整体隐藏,不会裁出半个字。
getBranchIndicatorProps(props: SideNavNodeProps) => T['element']
isPopoutPanel(value: string) => boolean该分支在折叠态下是否以浮层面板出现;决定作者是否需要渲染定位层。
getPopoutPositionerProps(props: SideNavNodeProps) => T['element']弹出面板的定位层。使用引擎坐标、承载层号,作者须把它移到浮层落点, 避免祖先的层叠上下文困住面板。非弹出分支不渲染这一层。
getBranchContentProps(props: SideNavNodeProps) => T['element']
getLinkProps(props: SideNavNodeProps) => T['element']
getLinkTextProps() => T['element']链接文字的载体:折叠时由皮肤整体隐藏。

无障碍

键盘

规格出处:W3C APG

按键生效条件行为
Enter / Spaceheld on link / branch-trigger, 侧栏未禁用且入口未禁用按住期间链接行或分支行投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下,弹出面板随选中收起时一并撤下。导航当前(aria-current)与按压互相独立,激活与展开语义照旧由这一次按键承担
Enter / Spacefocus in branch-trigger展开/收起该枝(原生按钮激活)
Enterfocus in link激活链接(原生行为)并落选中
ArrowDownfocus in 行下一可见行(roving tabindex)
ArrowUpfocus in 行上一可见行
ArrowRightfocus in 收起的分支行展开该枝;已展开时进第一个子行(RTL 与 ArrowLeft 对调)
ArrowLeftfocus in 展开的分支行收起该枝;叶子或已收起时回父分支(RTL 与 ArrowRight 对调)
Homefocus in 行第一可见行
Endfocus in 行最后一可见行
ArrowRight / Enter / Spacefocus in 折叠态顶层分支行弹出子级面板并落焦第一行(RTL 与 ArrowLeft 对调)
ArrowLeft / Escapefocus in 弹出面板收回面板,焦点还给触发按钮(RTL 与 ArrowRight 对调;Escape 归消解层)

ARIA

以下属性由 connect 生成。

部件属性
rootaria-labeltranslations?.root
rootrole'navigation'
grouparia-labelledbygroup-label 部件的 id
grouprole'group'
branch-triggeraria-controlscontent 部件的 id
branch-triggeraria-disabled'true' | 'false'
branch-triggeraria-expanded'true' | 'false'
branch-indicatoraria-hidden'true'
branch-contentaria-hidden!open || undefined
linkaria-current'page' | undefined
linkaria-disabled'true' | undefined
popout-positioneraria-hidden!open || undefined
  • listbranch-content 使用列表语义。
  • 将文字放入 branch-textlink-text,确保折叠后仍有可访问名称。
  • 装饰图标使用 aria-hidden="true"

样式参考

皮肤

@xihan-ui/styles/side-nav.css 使用 [data-scope="side-nav"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides

数据属性

connect 生成;条件不成立时不输出无值属性。

部件属性
rootdata-collapsed''(条件成立时才出现)
rootdata-disabled''(条件成立时才出现)
rootdata-sizeprops.size
rootdata-toneprops.tone
listdata-collapsed''(条件成立时才出现)
group-labeldata-collapsed''(条件成立时才出现)
branchdata-disabled''(条件成立时才出现)
branchdata-in-path''(条件成立时才出现)
branchdata-state'open' | 'closed'
branch-triggerdata-disabled''(条件成立时才出现)
branch-triggerdata-highlighted''(条件成立时才出现)
branch-triggerdata-in-path''(条件成立时才出现)
branch-triggerdata-pressed''(条件成立时才出现)
branch-triggerdata-state'open' | 'closed'
branch-triggerdata-valueitemValue(el)
branch-triggerdata-xh-collection-context'page'
branch-triggerdata-xh-collection-item''
branch-triggerdata-xh-collection-sizeprops.size
branch-textdata-xh-collection-slot'text'
branch-indicatordata-state'open' | 'closed'
branch-indicatordata-xh-collection-slot'suffix'
branch-contentdata-popout''
branch-contentdata-state'open' | 'closed'
linkdata-current''(条件成立时才出现)
linkdata-disabled''(条件成立时才出现)
linkdata-highlighted''(条件成立时才出现)
linkdata-pressed''(条件成立时才出现)
linkdata-valueitemValue(el)
linkdata-xh-collection-context'page'
linkdata-xh-collection-item''
linkdata-xh-collection-sizeprops.size
link-textdata-xh-collection-slot'text'
popout-positionerdata-hidden''(条件成立时才出现)
popout-positionerdata-placementplaced?.placement
popout-positionerdata-positioned''(条件成立时才出现)
popout-positionerdata-sizeprops.size
popout-positionerdata-state'open' | 'closed'
popout-positionerdata-toneprops.tone

CSS 变量

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-side-nav-branch-indicator-sizebranch-indicator--xh-icon-size
block-size
inline-size
default--xh-control-indicator-sizeside-nav 的 branch-indicator 部件 --xh-icon-size、block-size、inline-size 覆盖槽。
--xh-side-nav-collapsed-wrootinline-sizecollapsed56pxside-nav 的 root 部件 inline-size 覆盖槽。
--xh-side-nav-fgrootcolordefault--xh-fg-defaultside-nav 的 root 部件 color 覆盖槽。
--xh-side-nav-gapbranch
branch-content
group
list
root
gapdefault--xh-space-1side-nav 的 branch、branch-content、group、list、root 部件 gap 覆盖槽。
--xh-side-nav-group-label-pxgroup-labelpadding-inlinedefault--xh-_side-nav-row-pxside-nav 的 group-label 部件 padding-inline 覆盖槽。
--xh-side-nav-group-label-pygroup-labelpadding-blockdefault--xh-space-1side-nav 的 group-label 部件 padding-block 覆盖槽。
--xh-side-nav-icon-sizebranch-trigger
link
positioner
root
--xh-icon-sizedefault
is([data-part='root'], [data-part='positioner'])
size=lg
size=sm
--xh-_collection-glyph-size
--xh-glyph-size-lg
--xh-glyph-size-md
--xh-glyph-size-sm
side-nav 的 branch-trigger、link、positioner、root 部件 --xh-icon-size 覆盖槽。
--xh-side-nav-indentbranch-contentpadding-inline-startdefault--xh-space-4side-nav 的 branch-content 部件 padding-inline-start 覆盖槽。
--xh-side-nav-indicator-colorbranch-trigger
link
colorcurrent
disabled
error
highlighted
hover
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
xh-collection-context=page
xh-collection-slot=indicator
--xh-fg-brandside-nav 的 branch-trigger、link 部件 color 覆盖槽。
--xh-side-nav-link-font-sizebranch-trigger
link
font-sizedefault--xh-_side-nav-row-font-sizeside-nav 的 branch-trigger、link 部件 font-size 覆盖槽。
--xh-side-nav-link-gapbranch-trigger
link
gapdefault--xh-_side-nav-row-gapside-nav 的 branch-trigger、link 部件 gap 覆盖槽。
--xh-side-nav-link-hbranch-trigger
link
min-block-sizedefault--xh-_side-nav-row-hside-nav 的 branch-trigger、link 部件 min-block-size 覆盖槽。
--xh-side-nav-link-pxbranch-trigger
link
padding-inlinedefault--xh-_side-nav-row-pxside-nav 的 branch-trigger、link 部件 padding-inline 覆盖槽。
--xh-side-nav-link-radiusbranch-trigger
link
border-radiusdefault--xh-shape-controlside-nav 的 branch-trigger、link 部件 border-radius 覆盖槽。
--xh-side-nav-prootpaddingdefault--xh-space-2side-nav 的 root 部件 padding 覆盖槽。
--xh-side-nav-popout-bgbranch-contentbackgroundpopout--xh-bg-surfaceside-nav 的 branch-content 部件 background 覆盖槽。
--xh-side-nav-popout-borderbranch-contentborderpopout--xh-border-defaultside-nav 的 branch-content 部件 border 覆盖槽。
--xh-side-nav-popout-layerpositionerz-indexdefault--xh-_layerside-nav 的 positioner 部件 z-index 覆盖槽。
--xh-side-nav-popout-max-hbranch-contentmax-block-sizepopout--xh-overlay-menu-max-hside-nav 的 branch-content 部件 max-block-size 覆盖槽。
--xh-side-nav-popout-max-wbranch-contentmax-inline-sizepopout--xh-overlay-max-wside-nav 的 branch-content 部件 max-inline-size 覆盖槽。
--xh-side-nav-popout-min-wbranch-contentmin-inline-sizepopout--xh-overlay-menu-min-wside-nav 的 branch-content 部件 min-inline-size 覆盖槽。
--xh-side-nav-popout-pbranch-contentpaddingpopout--xh-space-1side-nav 的 branch-content 部件 padding 覆盖槽。
--xh-side-nav-popout-radiusbranch-contentborder-radiuspopout--xh-shape-overlayside-nav 的 branch-content 部件 border-radius 覆盖槽。
--xh-side-nav-popout-shadowbranch-contentbox-shadowpopout--xh-elevation-floatingside-nav 的 branch-content 部件 box-shadow 覆盖槽。
--xh-side-nav-row-bg-activebranch-trigger
link
background-colorcurrent
disabled
error
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
xh-collection-context=page
--xh-bg-brand-subtleside-nav 的 branch-trigger、link 部件 background-color 覆盖槽。
--xh-side-nav-row-bg-hoverbranch-trigger
link
background-colordisabled
error
highlighted
hover
is(:focus-visible, [data-highlighted])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
--xh-bg-subtleside-nav 的 branch-trigger、link 部件 background-color 覆盖槽。
--xh-side-nav-row-bg-in-pathbranch-trigger
link
background-colorin-path--xh-bg-subtleside-nav 的 branch-trigger、link 部件 background-color 覆盖槽。
--xh-side-nav-row-bg-pressedbranch-trigger
link
background-colordisabled
error
is(:active, [data-pressed])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
--xh-bg-subtle-hoverside-nav 的 branch-trigger、link 部件 background-color 覆盖槽。
--xh-side-nav-row-fgbranch-trigger
link
colordefault
disabled
error
highlighted
hover
in-path
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
currentColorside-nav 的 branch-trigger、link 部件 color 覆盖槽。
--xh-side-nav-row-fg-activebranch-trigger
link
colorcurrent
disabled
error
highlighted
hover
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
xh-collection-context=page
--xh-fg-on-brand-subtleside-nav 的 branch-trigger、link 部件 color 覆盖槽。
--xh-side-nav-row-fg-in-pathbranch-trigger
link
colorin-path--xh-side-nav-row-fgside-nav 的 branch-trigger、link 部件 color 覆盖槽。
--xh-side-nav-row-font-weight-activebranch-trigger
link
font-weightcurrent
disabled
error
highlighted
hover
is(:active, [data-pressed])
is(:focus-visible, [data-highlighted])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
xh-collection-context=page
--xh-font-weight-mediumside-nav 的 branch-trigger、link 部件 font-weight 覆盖槽。
--xh-side-nav-wrootinline-sizedefault240pxside-nav 的 root 部件 inline-size 覆盖槽。

动效

共享关键帧 xh-pop-in · xh-pop-outfamily/motion.css 提供,皮肤 @import 它,单独引入仍成立;rotatetransition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。

皮肤之外还有一段:退场由适配器的退场闸门把关,动画播完才真收起。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

RTL

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。

Released under The MIT License