App sidebar
ProThe left rail: the brand, a new-thread button, the main places (threads, inbox, approvals…), the thread list, and the signed-in person at the foot.
Preview
Install
With the Candor CLI
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/app-sidebar
Brings in: dropdown-menu, locale, utils · npm: lucide-react@^1.67.0
Usage
"use client"
import * as React from "react"
import { BookOpen, Inbox, MessagesSquare, Play, Settings, ShieldCheck } from "lucide-react"
import { AppSidebar, type NavItem } from "@/components/app-sidebar"
import { SessionList } from "@/components/session-list"
import type { Session } from "@/lib/team"
const minutesAgo = (m: number) => new Date(Date.now() - m * 60_000).toISOString()
const nav: NavItem[] = [
{ id: "threads", label: "Threads", Icon: MessagesSquare },
{ id: "inbox", label: "Inbox", Icon: Inbox, count: 2, urgent: true },
{ id: "approvals", label: "Approvals", Icon: ShieldCheck, count: 1, urgent: true },
{ id: "runs", label: "Runs", Icon: Play, count: 4 },
{ id: "docs", label: "Docs", Icon: BookOpen },
{ id: "settings", label: "Settings", Icon: Settings },
]
const sessions: Session[] = [
{ id: "s1", title: "Add a region column to orders", updatedAt: minutesAgo(2), status: "running" },
{ id: "s2", title: "Why is checkout slow on Staging?", updatedAt: minutesAgo(40) },
{ id: "s3", title: "Clean up stale feature flags", updatedAt: minutesAgo(60 * 26) },
]
export default function Example() {
const [place, setPlace] = React.useState("threads")
const [thread, setThread] = React.useState("s1")
return (
<div className="h-[560px] w-64 overflow-hidden rounded-lg border">
<AppSidebar
brand={<span className="px-1 text-sm font-semibold">Keel</span>}
nav={nav}
activeNav={place}
onNavigate={setPlace}
onNewThread={() => {}}
user={{ name: "Sam", email: "sam@acme.example", role: "Owner · Acme" }}
usage={{ label: "Agent hours this month", used: 0.62 }}
onProfile={() => {}}
onShortcuts={() => {}}
onSignOut={() => {}}
>
<SessionList sessions={sessions} activeId={place === "threads" ? thread : undefined} onOpen={(id) => (setPlace("threads"), setThread(id))} />
</AppSidebar>
</div>
)
}<AppSidebar>
| Prop | Type | Default | Description |
|---|---|---|---|
| brandrequired | React.ReactNode | — | The product's logo or name, at the top. |
| navrequired | NavItem[] | — | The main places, each with an icon and an optional count. |
| activeNav | string | undefined | — | The id of the current place, marked as the current page. |
| onNavigate | ((id: string) => void) | undefined | — | Called with a place's id when it's chosen. For a place with an `href`, a plain click calls this instead of following the link (for an app's own router); other clicks open the link as usual. |
| onNewThread | (() => void) | undefined | — | Shows a new-thread button by the brand; called when it's pressed. |
| userrequired | { name: string; email: string; role?: string | undefined } | — | The signed-in person, at the foot; the role shows in place of the email when given. |
| usage | { label: string; used: number } | undefined | — | e.g. "62% of this month’s agent hours". |
| onSignOut | (() => void) | undefined | — | Adds Sign out to the person menu; called when it's chosen. |
| onProfile | (() => void) | undefined | — | Adds Profile to the person menu; called when it's chosen. |
| onShortcuts | (() => void) | undefined | — | Adds Keyboard shortcuts to the person menu; called when it's chosen. |
| search | React.ReactNode | undefined | — | Above the places; usually a CommandTrigger. |
| children | React.ReactNode | undefined | — | Usually a SessionList. |
| help | { href?: string | undefined; onOpen?: (() => void) | undefined; label?: string | undefined } | undefined | — | Where people get help, in the same place on every page (WCAG 3.2.6): a link (`href`) or your own handler (`onOpen`), at the foot above the person menu. `label` defaults to "Help". |
| className | string | undefined | — | Extra classes for the outer element. |
Strings
The fixed words, in English by default. Change any of them under appSidebar in <LocaleProvider strings={{ … }}>; the table is exported as appSidebarStrings.
| Key | English |
|---|---|
| newThread | New thread |
| main | Main |
| waitingOnYou | waiting on you |
| profile | Profile |
| keyboardShortcuts | Keyboard shortcuts |
| signOut | Sign out |
| help | Help |
Types
- NavItem
{ id: string label: string Icon: LucideIcon /** A count shown only when the service supplied it. */ count?: number | undefined /** Count means "needs you": shown in the accent, not grey. */ urgent?: boolean | undefined /** Where the place lives. Makes it a real link, so it opens in a new tab and works without JavaScript. */ href?: string | undefined }