创作者中心
概览
切换侧栏,观察标识、分组与页脚的位置。窄屏时打开抽屉。
<script setup lang="ts">
import { ref } from 'vue'
import { FileText, House, Images, Settings } from '@lucide/vue'
import {
AppShell,
Avatar,
Heading,
Inline,
NavLink,
Sidebar,
SidebarGroup,
SidebarLabel,
SidebarTrigger,
Stack,
Text,
} from '@hina-ui/vue'
const selected = ref('overview')
const groups = [
{
label: '内容',
items: [
{ id: 'overview', label: '概览', icon: House },
{ id: 'articles', label: '文章', icon: FileText },
{ id: 'library', label: '媒体库', icon: Images },
],
},
{ label: '管理', items: [{ id: 'settings', label: '设置', icon: Settings }] },
]
</script>
<template>
<AppShell mobile-title="Hina UI" class="border-line h-112 w-full rounded-lg border">
<template #sidebar>
<Sidebar>
<template #icon>
<Avatar src="/favicon.png" name="Hina UI" class="rounded-md" />
</template>
<template #wordmark>
<Stack gap="none">
<Text weight="medium" class="truncate">Hina UI</Text>
<Text size="xs" tone="muted" class="truncate">工作空间</Text>
</Stack>
</template>
<SidebarGroup v-for="group in groups" :key="group.label" :label="group.label">
<NavLink
v-for="item in group.items"
:key="item.id"
:href="`#${item.id}`"
:label="item.label"
:active="selected === item.id"
@click.prevent="selected = item.id"
>
<template #icon><component :is="item.icon" /></template>
{{ item.label }}
</NavLink>
</SidebarGroup>
<template #footer>
<Inline gap="sm" align="center" :wrap="false">
<Avatar src="/avatars/paper.webp" name="星见书音" />
<SidebarLabel as="div" class="flex-1">
<Stack gap="none">
<Text size="sm" weight="medium" class="truncate">星见书音</Text>
<Text size="xs" tone="muted" class="truncate">管理员</Text>
</Stack>
</SidebarLabel>
</Inline>
</template>
</Sidebar>
</template>
<template #header>
<SidebarTrigger />
<Heading :level="2" size="sm" class="truncate">创作者中心</Heading>
</template>
<Stack gap="sm" class="p-6">
<Heading :level="3" size="sm">
{{ groups.flatMap(group => group.items).find(item => item.id === selected)?.label }}
</Heading>
<Text size="sm" tone="muted">切换侧栏,观察标识、分组与页脚的位置。窄屏时打开抽屉。</Text>
</Stack>
</AppShell>
</template>
用法
import { Sidebar, SidebarGroup, SidebarLabel, SidebarTrigger } from '@hina-ui/vue'
Sidebar 放进 AppShell 的 sidebar 插槽,条目写在默认插槽里,通常是一串 NavLink。它自带导航地标与滚动容器,条目再多也只在栏内滚动。
它的形态由 AppShell 提供,自身不持有状态。脱离 AppShell 时它始终是展开形态,也无法收起。
侧栏条目放在默认插槽里。
<script setup lang="ts">
import { AppShell, NavLink, Sidebar, Text } from '@hina-ui/vue'
</script>
<template>
<AppShell class="border-line h-64 w-full rounded-lg border">
<template #sidebar>
<Sidebar>
<NavLink href="#" active label="概览">概览</NavLink>
<NavLink href="#" label="我的书架">我的书架</NavLink>
<NavLink href="#" label="收藏">收藏</NavLink>
</Sidebar>
</template>
<Text size="sm" tone="muted" class="block p-6">侧栏条目放在默认插槽里。</Text>
</AppShell>
</template>
侧栏内的每个 NavLink 都应当写 label:收起为 rail 后文字淡出,label 会接手成为悬停提示与无障碍名。
示例
分组
SidebarGroup 把条目归到一个可折叠的标题下。label 是组名,defaultOpen 决定初始是否展开,默认展开。
「创作」一组默认收起。
<script setup lang="ts">
import { AppShell, NavLink, Sidebar, SidebarGroup, Text } from '@hina-ui/vue'
</script>
<template>
<AppShell class="border-line h-96 w-full rounded-lg border">
<template #sidebar>
<Sidebar>
<NavLink href="#" active label="概览">概览</NavLink>
<SidebarGroup label="作品库">
<NavLink href="#" label="Galgame">Galgame</NavLink>
<NavLink href="#" label="轻小说">轻小说</NavLink>
</SidebarGroup>
<SidebarGroup label="创作" :default-open="false">
<NavLink href="#" label="我的条目">我的条目</NavLink>
<NavLink href="#" label="草稿箱">草稿箱</NavLink>
</SidebarGroup>
</Sidebar>
</template>
<Text size="sm" tone="muted" class="block p-6">「创作」一组默认收起。</Text>
</AppShell>
</template>
品牌图标与字标
icon 与 wordmark 插槽组成默认页眉。图标位固定为 32 × 32 像素,SVG 和图片按比例显示;字标可以是文字、SVG、Image 或组合内容。
| 插槽内容 | 展开 | 收起为 rail |
|---|---|---|
icon + wordmark | 图标与字标并排显示 | 图标保持原位,字标淡出 |
只有 icon | 显示图标 | 保持原位 |
只有 wordmark | 字标从页眉起始位置显示 | 品牌区整体收起 |
| 均未提供 | 不生成品牌区 | 不生成品牌区 |
有图标时,收起后品牌行保留高度;只有字标时,品牌区连同上下内边距一起收起,导航向上填补空位。重新展开或进入移动端抽屉时,品牌区完整显示。字标自动使用与导航文字相同的过渡,不需要再包 SidebarLabel。
两个插槽均提供 { state }。传入 header 时由它完全接管页眉,icon 和 wordmark 不再渲染。
图标与字标
品牌插槽
图标与字标
只有字标
品牌插槽
只有字标
只有图标
品牌插槽
只有图标
<script setup lang="ts">
import { House } from '@lucide/vue'
import { AppShell, Image, NavLink, Sidebar, SidebarTrigger, Stack, Text } from '@hina-ui/vue'
import Wordmark from '../../../components/docs/Wordmark.vue'
const examples = [
{ mode: 'both', label: '图标与字标' },
{ mode: 'wordmark', label: '只有字标' },
{ mode: 'icon', label: '只有图标' },
]
</script>
<template>
<Stack gap="lg" class="w-full">
<Stack v-for="example in examples" :key="example.mode" gap="sm">
<Text size="sm" tone="muted">{{ example.label }}</Text>
<AppShell mobile-title="Hina UI" class="border-line h-52 w-full rounded-lg border">
<template #sidebar>
<Sidebar>
<template v-if="example.mode !== 'wordmark'" #icon>
<Image src="/favicon.png" alt="Hina UI" class="rounded-md" />
</template>
<template v-if="example.mode !== 'icon'" #wordmark>
<Wordmark class="items-center" />
</template>
<NavLink href="#" active label="概览" @click.prevent>
<template #icon><House /></template>
概览
</NavLink>
</Sidebar>
</template>
<template #header>
<SidebarTrigger />
<Text size="sm" weight="medium">品牌插槽</Text>
</template>
<Text size="sm" tone="muted" class="block p-6">{{ example.label }}</Text>
</AppShell>
</Stack>
</Stack>
</template>
页眉与页脚
header 完整替换默认品牌页眉,footer 位于条目区下方,两者都不随条目滚动。自定义页眉不会被自动视为 logo 隐藏。
页眉保持展开时的内容宽度。页脚随侧栏实际宽度收起,内部按钮与浮层锚点不会超出 rail。水平排布的头像和文字可使用 Inline 并设置 :wrap="false";文字使用 truncate 或 whitespace-nowrap 避免收起时换行。把文字与附属操作放进 SidebarLabel,它会与 NavLink 的文字一起淡出,展开时延后淡入;标识和 Avatar 留在外面,位置与尺寸保持不变。
SidebarLabel 不改变内容的占位。rail 形态下其内容不可见、不可交互,也不进入朗读和键盘焦点序列。header 和 footer 仍提供 { state },供自定义内容读取当前形态。
创作者中心
概览
切换侧栏,观察标识、分组与页脚的位置。窄屏时打开抽屉。
<script setup lang="ts">
import { ref } from 'vue'
import { FileText, House, Images, Settings } from '@lucide/vue'
import {
AppShell,
Avatar,
Heading,
Inline,
NavLink,
Sidebar,
SidebarGroup,
SidebarLabel,
SidebarTrigger,
Stack,
Text,
} from '@hina-ui/vue'
const selected = ref('overview')
const groups = [
{
label: '内容',
items: [
{ id: 'overview', label: '概览', icon: House },
{ id: 'articles', label: '文章', icon: FileText },
{ id: 'library', label: '媒体库', icon: Images },
],
},
{ label: '管理', items: [{ id: 'settings', label: '设置', icon: Settings }] },
]
</script>
<template>
<AppShell mobile-title="Hina UI" class="border-line h-112 w-full rounded-lg border">
<template #sidebar>
<Sidebar>
<template #header>
<Inline gap="sm" align="center" :wrap="false">
<Avatar src="/favicon.png" name="Hina UI" class="rounded-md" />
<SidebarLabel as="div" class="flex-1">
<Stack gap="none">
<Text weight="medium" class="truncate">Hina UI</Text>
<Text size="xs" tone="muted" class="truncate">工作空间</Text>
</Stack>
</SidebarLabel>
</Inline>
</template>
<SidebarGroup v-for="group in groups" :key="group.label" :label="group.label">
<NavLink
v-for="item in group.items"
:key="item.id"
:href="`#${item.id}`"
:label="item.label"
:active="selected === item.id"
@click.prevent="selected = item.id"
>
<template #icon><component :is="item.icon" /></template>
{{ item.label }}
</NavLink>
</SidebarGroup>
<template #footer>
<Inline gap="sm" align="center" :wrap="false">
<Avatar src="/avatars/paper.webp" name="星见书音" />
<SidebarLabel as="div" class="flex-1">
<Stack gap="none">
<Text size="sm" weight="medium" class="truncate">星见书音</Text>
<Text size="xs" tone="muted" class="truncate">管理员</Text>
</Stack>
</SidebarLabel>
</Inline>
</template>
</Sidebar>
</template>
<template #header>
<SidebarTrigger />
<Heading :level="2" size="sm" class="truncate">创作者中心</Heading>
</template>
<Stack gap="sm" class="p-6">
<Heading :level="3" size="sm">
{{ groups.flatMap(group => group.items).find(item => item.id === selected)?.label }}
</Heading>
<Text size="sm" tone="muted">切换侧栏,观察标识、分组与页脚的位置。窄屏时打开抽屉。</Text>
</Stack>
</AppShell>
</template>
行为
- 三种形态的宽度分别是展开 256 像素、rail 56 像素、隐藏 0 像素,切换时宽度连续过渡。
- 收起为 rail 时,
SidebarGroup强制展开、组标题原位淡出并显示分隔线,标题占位保持不变,已展开的条目不会随收起动作上下移动。 - 条目区使用不带边缘阴影的 ScrollArea,页眉与页脚固定在两端。
- 完全隐藏时,侧栏整体退出交互与键盘焦点序列。
- 宽度和文字过渡使用 Hina 动画 token;系统开启减弱动态效果时直接切换。
- 搬入移动端 Drawer 时,侧栏撑满抽屉高度,页脚固定在底部,仅条目区滚动。页眉内默认提供关闭按钮,设置
:closable="false"可隐藏;未提供页眉或品牌插槽时,也不保留按钮行的占位。不重复显示抽屉标题;水平内边距由抽屉提供,页眉、条目区和页脚保留各自的纵向内边距。
无障碍
- 条目区是
nav地标,默认无障碍名取自界面语言(简体中文为「侧边导航」),label可覆盖。 - rail 形态下条目的文字虽然不可见,但 NavLink 的
label会作为aria-label保留。 closable仅控制移动端关闭按钮,隐藏后仍可按 Escape、点击遮罩或通过受控状态关闭抽屉。- 隐藏的字标退出朗读与焦点序列。品牌图标使用图片时提供
alt,使用 SVG 时提供合适的无障碍名;若图标包含链接,链接本身也应有名称。 - 收起后被隐藏的组标题同时退出键盘序列,不会出现能聚焦却看不见的控件。
API
Sidebar
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | 取自界面语言 | 导航地标的无障碍名 |
closable | boolean | true | 是否显示移动端抽屉的关闭按钮 |
class | string | — | 追加到根元素的类 |
| 插槽 | 插槽参数 | 说明 |
|---|---|---|
default | — | 侧栏条目 |
header | { state } | 完整替换页眉,优先于品牌插槽 |
icon | { state } | 固定方形区域内的品牌图标,rail 时保留 |
wordmark | { state } | 品牌字标,rail 时自动淡出 |
footer | { state } | 条目区下方的内容 |
SidebarGroup
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | 必填 | 组名 |
defaultOpen | boolean | true | 初始是否展开 |
class | string | — | 追加到根元素的类 |
| 插槽 | 说明 |
|---|---|
default | 组内的条目 |
SidebarLabel
控制自定义文字与附属内容在 rail 形态下的显隐;不在侧栏状态提供方内时始终显示。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
as | string | 'span' | 渲染的元素 |
class | string | — | 追加到根元素的类 |
| 插槽 | 说明 |
|---|---|
default | 随侧栏收起而隐藏的内容 |
SidebarTrigger
切换侧栏形态的按钮,通常放在 AppShell 的 header 插槽里。它没有可配置的行为,脱离 AppShell 时不渲染。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
class | string | — | 追加到根元素的类 |