Getting Started
Components
- Agent Status
- Agent Template Card
- API Key Card
- Audio Player
- Bar Visualizer
- Channel Status Card
- Chat Composer
- Code Editor
- Condition Builder
- Confirm Dialog
- Conversation
- Conversation Bar
- Dataset Card
- Document Status Badge
- Extraction Strategy Select
- File Icon
- File Upload
- Indexing Progress
- Live Waveform
- Matrix
- Message
- Mic Selector
- Model Diff View
- Model Selector
- Model Type Filter
- Node Catalog
- Option Editor
- Orb
- Output Variables View
- Package Card
- Prompt Editor
- Provider Status Card
- Radio Card
- Resource Card
- Resource Sidebar
- Response
- Run Node List
- Scrub Bar
- Segment Card
- Shimmering Text
- Speech Input
- Token Trend Chart
- Tool Call Card
- Transcript Viewer
- Usage Stat Card
- Variable Binding Editor
- Variable Selector
- Voice Button
- Voice Picker
- Waveform
- Workflow Node Card
- Workspace Selector
import {
BotIcon,
DatabaseIcon,
FilesIcon,
SettingsIcon,
WorkflowIcon,
} from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { ResourceSidebar } from "@/components/ui/resource-sidebar"
const navItems = [
{
title: "Overview",
href: "#overview",
icon: <BotIcon />,
active: true,
},
{
title: "Knowledge",
href: "#knowledge",
icon: <DatabaseIcon />,
children: [
{ title: "Datasets", href: "#datasets", icon: <FilesIcon /> },
{ title: "Retrieval", href: "#retrieval", icon: <SettingsIcon /> },
],
},
{
title: "Workflows",
href: "#workflows",
icon: <WorkflowIcon />,
},
]
export function ResourceSidebarDemo() {
return (
<div className="h-[420px] w-full max-w-3xl overflow-hidden rounded-lg border">
<ResourceSidebar
className="h-full"
title="Support Agent"
description="Production agent for support and escalation."
icon={<BotIcon className="size-5" />}
backHref="#"
navItems={navItems}
footer={
<div className="flex items-center justify-between gap-2">
<span className="text-muted-foreground text-xs">Status</span>
<Badge className="rounded-md">Live</Badge>
</div>
}
/>
</div>
)
}
Installation
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/resource-sidebar.jsonpnpm dlx shadcn@latest add https://ui.zgi.ai/r/resource-sidebar.json
yarn dlx shadcn@latest add https://ui.zgi.ai/r/resource-sidebar.jsonComponent
"use client"
import * as React from "react"
import {
ArrowLeftIcon,
PanelLeftCloseIcon,
PanelLeftOpenIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
export interface ResourceSidebarNavItem {
title: React.ReactNode
href?: string
icon?: React.ReactNode
active?: boolean
children?: ResourceSidebarNavItem[]
onSelect?: () => void
}
export interface ResourceSidebarProps
extends Omit<React.ComponentProps<"aside">, "title"> {
collapsed?: boolean
onCollapsedChange?: (collapsed: boolean) => void
title?: React.ReactNode
description?: React.ReactNode
icon?: React.ReactNode
backHref?: string
backLabel?: React.ReactNode
navItems?: ResourceSidebarNavItem[]
footer?: React.ReactNode
loading?: boolean
expandedClassName?: string
}
function SidebarLink({
item,
collapsed,
depth = 0,
}: {
item: ResourceSidebarNavItem
collapsed?: boolean
depth?: number
}) {
const isChild = depth > 0
const content = (
<>
{item.active && !collapsed ? (
<span
className={cn(
"absolute top-2 bottom-2 rounded-full",
isChild ? "bg-primary/70 left-3 w-0.5" : "bg-primary left-0.5 w-1"
)}
aria-hidden="true"
/>
) : null}
{item.icon ? (
<span
className={cn(
"flex size-7 shrink-0 items-center justify-center rounded-md transition-colors [&_svg]:size-4",
item.active
? "bg-primary/10 text-primary"
: "text-muted-foreground group-hover/link:bg-muted group-hover/link:text-foreground",
isChild && "size-5 bg-transparent"
)}
>
{item.icon}
</span>
) : null}
<span
className={cn(
"truncate transition-all",
collapsed ? "w-0 opacity-0" : "w-full opacity-100",
isChild ? "text-xs" : "text-sm"
)}
>
{item.title}
</span>
</>
)
const className = cn(
"group/link relative flex min-h-9 items-center gap-2 rounded-md px-2.5 font-medium text-muted-foreground transition-colors hover:bg-muted/70 hover:text-foreground",
item.active && "bg-primary/5 text-foreground",
collapsed && "w-8 justify-center px-0",
isChild && !collapsed && "ml-5 min-h-8 pl-5 text-sm font-normal"
)
if (item.href) {
return (
<a href={item.href} className={className} onClick={item.onSelect}>
{content}
</a>
)
}
return (
<button type="button" className={className} onClick={item.onSelect}>
{content}
</button>
)
}
export function ResourceSidebar({
collapsed = false,
onCollapsedChange,
title,
description,
icon,
backHref,
backLabel = "Back",
navItems = [],
footer,
loading = false,
expandedClassName = "w-56",
className,
...props
}: ResourceSidebarProps) {
const ToggleIcon = collapsed ? PanelLeftOpenIcon : PanelLeftCloseIcon
return (
<aside
className={cn(
"bg-background relative flex h-full shrink-0 flex-col border-r transition-all",
collapsed ? "w-12" : expandedClassName,
className
)}
{...props}
>
<div className="border-b p-3">
<div className="flex items-center justify-between gap-2">
{backHref && !collapsed ? (
<a
href={backHref}
className="text-muted-foreground hover:bg-muted hover:text-foreground inline-flex min-w-0 items-center gap-1 rounded-md px-1.5 py-1 text-xs transition-colors"
>
<ArrowLeftIcon className="size-3.5" />
<span className="truncate">{backLabel}</span>
</a>
) : (
<span />
)}
<Button
type="button"
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-foreground size-8 rounded-md"
onClick={() => onCollapsedChange?.(!collapsed)}
aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
>
<ToggleIcon className="size-4" />
</Button>
</div>
<div
className={cn(
"mt-2 flex min-w-0 items-center gap-2",
collapsed && "justify-center"
)}
>
{loading ? (
<Skeleton className="size-9 rounded-lg" />
) : (
<div className="bg-primary/10 text-primary flex size-10 shrink-0 items-center justify-center rounded-lg">
{icon}
</div>
)}
<div className={cn("min-w-0 flex-1", collapsed && "hidden")}>
{loading ? (
<div className="space-y-1">
<Skeleton className="h-3.5 w-24" />
<Skeleton className="h-3 w-32" />
</div>
) : (
<>
<div className="truncate text-sm font-semibold">{title}</div>
{description ? (
<div className="text-muted-foreground mt-0.5 line-clamp-2 text-xs leading-5">
{description}
</div>
) : null}
</>
)}
</div>
</div>
</div>
<nav className="flex flex-1 flex-col gap-1 p-3">
{navItems.map((item, index) => (
<div key={index} className="space-y-1">
<SidebarLink item={item} collapsed={collapsed} />
{!collapsed && item.children?.length ? (
<div className="border-border/70 ml-4 space-y-1 border-l py-1">
{item.children.map((child, childIndex) => (
<SidebarLink key={childIndex} item={child} depth={1} />
))}
</div>
) : null}
</div>
))}
</nav>
{footer && !collapsed ? (
<div className="border-t p-2">{footer}</div>
) : null}
</aside>
)
}
Usage
import { BotIcon } from "lucide-react"
import { ResourceSidebar } from "@/components/ui/resource-sidebar"
export function Example() {
return (
<ResourceSidebar
title="Support Agent"
icon={<BotIcon className="size-5" />}
navItems={[{ title: "Overview", href: "#", active: true }]}
/>
)
}Props
| Prop | Type | Default |
|---|---|---|
| collapsed | boolean | false |
| onCollapsedChange | (collapsed: boolean) => void | - |
| title | ReactNode | - |
| description | ReactNode | - |
| icon | ReactNode | - |
| backHref | string | - |
| navItems | ResourceSidebarNavItem[] | [] |
| footer | ReactNode | - |
| loading | boolean | false |
Deploy and Scale Agents with ZGI
ZGI delivers the infrastructure and developer experience you need to ship reliable audio & agent applications at scale.
Talk to an expert