AI Sidebar
在可搜索树中组织助手资源,支持选择与移动操作。
适用场景
- 当界面需要以下行为时使用 AI Sidebar:在可搜索树中组织助手资源,支持选择与移动操作。
安装
选择安装方式,用法示例将同步更新导入路径。
$ bunx --bun shadcn-vue@2.8.2 add https://orvui.efu.me/r/ai-sidebar.json https://orvui.efu.me/r/animated-sidebar.json
首次使用时,将安装的 src/components/orvui/styles.css 导入项目样式入口。
示例
预览
packages/vue/src/agents/ai-sidebar/demo.vue
<script setup lang="ts">
import { computed, onBeforeUnmount, ref } from "vue";
import {
Clock3,
GitPullRequest,
LayoutGrid,
PanelLeft,
Plug,
SquarePen,
} from "lucide-vue-next";
import {
AISidebar,
findResource,
type SidebarResource,
type SidebarResourceMove,
} from "@/components/orvui/agents/ai-sidebar";
import {
AnimatedSidebar,
AnimatedSidebarContent,
AnimatedSidebarGroup,
AnimatedSidebarGroupContent,
AnimatedSidebarGroupLabel,
AnimatedSidebarInset,
AnimatedSidebarMenu,
AnimatedSidebarMenuButton,
AnimatedSidebarMenuItem,
AnimatedSidebarProvider,
AnimatedSidebarRail,
AnimatedSidebarTrigger,
} from "@/components/orvui/motion/animated-sidebar";
const resources: SidebarResource[] = [
{
id: "design-system",
label: "Design system",
kind: "project",
},
{
id: "client-portal",
label: "Client portal",
kind: "project",
},
{
id: "marketing-site",
label: "Marketing website",
kind: "project",
},
{
id: "platform",
label: "Platform",
kind: "project",
children: [
{ id: "api", label: "API migration", kind: "file" },
{ id: "billing", label: "Billing states", kind: "file" },
{ id: "docs", label: "Read platform docs", kind: "bookmark" },
],
},
{
id: "mobile-app",
label: "Mobile app",
kind: "project",
},
{
id: "research-lab",
label: "Research lab",
kind: "folder",
},
{
id: "agent-workspace",
label: "Agent workspace",
kind: "project",
children: [
{
id: "resource-review",
label: "Review resource sidebar interaction details",
kind: "file",
},
{
id: "release-offer",
label: "Prepare release announcement",
kind: "file",
},
{
id: "haptics",
label: "Explore interaction feedback",
kind: "bookmark",
},
{
id: "promotion",
label: "Plan launch distribution",
kind: "file",
},
{
id: "motion-research",
label: "Research motion implementation patterns",
kind: "bookmark",
},
],
},
{
id: "release-notes",
label: "Release notes",
kind: "file",
},
];
const actions = [
{ label: "New workspace", icon: SquarePen },
{ label: "Pull requests", icon: GitPullRequest },
{ label: "Sites", icon: LayoutGrid },
{ label: "Scheduled", icon: Clock3 },
{ label: "Extensions", icon: Plug },
];
const active = ref("resource-review"),
items = ref(resources);
const activeLabel = computed(
() => findResource(items.value, active.value)?.label ?? active.value,
);
const pending = new Map<ReturnType<typeof setTimeout>, () => void>();
function move(_event: SidebarResourceMove) {
return new Promise<void>((resolve) => {
const handle = setTimeout(() => {
pending.delete(handle);
resolve();
}, 450);
pending.set(handle, resolve);
});
}
onBeforeUnmount(() => {
pending.forEach((resolve, handle) => {
clearTimeout(handle);
resolve();
});
pending.clear();
});
</script>
<template>
<div class="w-full px-0 py-2 sm:p-3">
<AnimatedSidebarProvider
:style="{ '--sidebar-width': '16rem' }"
class="h-[720px] min-h-0 w-full overflow-hidden rounded-2xl border border-foreground/[0.08] bg-background"
>
<AnimatedSidebar
aria-label="Workspace resources"
collapsible="offcanvas"
class="min-h-0 w-full"
panel-class-name="h-full bg-background"
><AnimatedSidebarContent class="gap-4 overflow-hidden px-2 py-4"
><AnimatedSidebarGroup class="shrink-0 px-1 py-0"
><AnimatedSidebarGroupContent
><AnimatedSidebarMenu class="gap-1"
><AnimatedSidebarMenuItem
v-for="action in actions"
:key="action.label"
><AnimatedSidebarMenuButton
class="font-normal text-foreground"
><template #icon
><component :is="action.icon" class="size-4" /></template
>{{ action.label }}</AnimatedSidebarMenuButton
></AnimatedSidebarMenuItem
></AnimatedSidebarMenu
></AnimatedSidebarGroupContent
></AnimatedSidebarGroup
>
<AnimatedSidebarGroup class="min-h-0 flex-1 px-1 py-0"
><AnimatedSidebarGroupLabel
class="mb-1 h-8 px-2 text-xs font-medium normal-case tracking-normal"
>Projects</AnimatedSidebarGroupLabel
><AnimatedSidebarGroupContent
class="relative min-h-0 flex-1 overflow-hidden"
><div
class="h-full overflow-y-auto overscroll-contain pb-8 [overflow-anchor:none] [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
<AISidebar
v-model:items="items"
v-model:active-id="active"
:default-expanded-ids="['platform', 'agent-workspace']"
:on-move="move"
/>
</div>
<div
aria-hidden="true"
class="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-8 bg-gradient-to-t from-background via-background/80 to-transparent" /></AnimatedSidebarGroupContent></AnimatedSidebarGroup></AnimatedSidebarContent
><AnimatedSidebarRail
/></AnimatedSidebar>
<AnimatedSidebarInset class="min-h-0 bg-background"
><header
class="flex h-14 shrink-0 items-center justify-between gap-4 border-border border-b px-5"
>
<div class="flex min-w-0 items-center gap-3">
<AnimatedSidebarTrigger
class="-ml-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
><PanelLeft aria-hidden="true" class="size-4"
/></AnimatedSidebarTrigger>
<div class="min-w-0">
<p
class="text-[10px] font-medium uppercase tracking-[0.14em] text-muted-foreground"
>
Workspace
</p>
<p class="truncate text-sm font-medium text-foreground">
Agent workspace
</p>
</div>
</div>
<span
class="shrink-0 rounded-full bg-muted px-2.5 py-1 text-[10px] font-medium text-muted-foreground"
>Draft</span
>
</header>
<div class="min-h-0 flex-1 overflow-y-auto p-6 sm:p-8">
<div class="mx-auto max-w-xl">
<p class="text-xs text-muted-foreground">Selected resource</p>
<h3
class="mt-2 text-xl font-semibold tracking-tight text-foreground sm:text-2xl"
>
{{ activeLabel }}
</h3>
<p class="mt-3 max-w-lg text-sm leading-6 text-muted-foreground">
Keep the resource tree predictable while files move between
projects, labels overflow, and folders expand around the current
selection.
</p>
<div class="mt-8 border-border border-t pt-6">
<p class="text-sm font-medium text-foreground">
Interaction notes
</p>
<ul
class="mt-3 space-y-3 text-sm leading-6 text-muted-foreground"
>
<li
v-for="note in [
'Folders only reveal their contents.',
'Files can be selected, moved, or renamed.',
'Rejected moves return to their previous location.',
]"
:key="note"
>
{{ note }}
</li>
</ul>
</div>
</div>
</div></AnimatedSidebarInset
>
</AnimatedSidebarProvider>
</div>
</template>
packages/vue/src/agents/ai-sidebar/src/AISidebar.vue
组合结构
使用公开部件 AISidebar 组合 AI Sidebar,并从该组件的公开入口导入。
AnimatedSidebarProvider
├── AnimatedSidebar
│ ├── AnimatedSidebarContent
│ │ └── AISidebar
│ │ ├── Folder or project
│ │ └── File or bookmark
│ └── AnimatedSidebarRail
└── AnimatedSidebarInset说明: Message Scroller 在新消息加入时跟随内容,同时允许读者离开并返回最新位置。 Prompt Input 通过自适应高度文本框、附件与键盘操作编写并提交提示词。
工作方式
在可搜索树中组织助手资源,支持选择与移动操作。
状态与事件
需要由表单、路由或父组件管理时,绑定下列状态并接收对应更新。v-model 是属性与 update 事件的简写;值的类型应与 API 一致。
| 组件 | Vue 绑定 | 更新事件 |
|---|---|---|
AISidebar | v-model:itemsSidebarResource[] | update:items |
AISidebar | v-model:activeIdnull | string | update:activeId |
可选受控属性未传入时,按组件文档的默认行为工作;不要用固定的 false 或空值代替未传入。异步请求和持久化在应用中处理,再把结果传回组件。
API 参考
items?SidebarResource[]props.defaultItemsdefaultItems?SidebarResource[]() => []activeId?null | stringprops.defaultActiveIddefaultActiveId?null | stringnulldefaultExpandedIds?string[]() => []ariaLabel?string"Resources"className?string—onMove?(move: SidebarResourceMove) => void | Promise<void>—onRename?(item: SidebarResource, label: string) => void | Promise<void>—renderIcon?(item: SidebarResource) => VNodeChild—renderMenu?(item: SidebarResource, controls: SidebarResourceMenuControls) => VNodeChild—事件
update:items[items: SidebarResource[]]@update:itemsitemsChange[items: SidebarResource[]]@itemsChangeupdate:activeId[id: string | null]@update:activeIdactiveChange[id: string]@activeChangemoveError[error: unknown, move: SidebarResourceMove]@moveErrorrenameError[error: unknown, item: SidebarResource]@renameError插槽
icon—作用域参数: v-bind
#iconmenu—作用域参数: v-bind
#menu无障碍
为交互控件提供明确的无障碍名称。保留文档中的键盘路径、可见焦点与减少动态效果状态;自定义插槽应继续保留所替代部件的语义。
常见问题
- 如何配置 AI Sidebar?
- 通过 items、defaultItems、activeId、defaultActiveId、defaultExpandedIds、ariaLabel、className、onMove、onRename、renderIcon、renderMenu 配置 AI Sidebar;API 表列出其类型与默认值。
- 如何连接到应用状态?
- 监听 update:items、itemsChange、update:activeId、activeChange、moveError、renameError,将组件连接到应用状态。
- 可以在服务端渲染吗?
- 可以在 Vue SSR 应用中正常渲染。服务端与客户端的初始 props 应保持一致;浏览器测量与交互资源在挂载后启动。
更新于
来源与许可 ↗MIT · 0.1.1