Sidebar 02
block sidebarFull-featured app sidebar -- collapsible="icon" rail with TeamSwitcher header, primary nav, projects section, secondary nav, and user dropdown footer. Each section ships as a sibling file in the same folder so consumers can edit one piece at a time.
Also available for Vue ->Installation
$ pnpm dlx shadcn@latest add https://react.uipkge.dev/r/react/sidebar-02.json$ npx shadcn@latest add https://react.uipkge.dev/r/react/sidebar-02.json$ yarn dlx shadcn@latest add https://react.uipkge.dev/r/react/sidebar-02.json$ bunx shadcn@latest add https://react.uipkge.dev/r/react/sidebar-02.json
Or with the named registry:
npx shadcn@latest add @uipkge-react/sidebar-02
Examples
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
Team type Team {
name: string
logo: LucideIcon | ((props: { className?: string }) => React.ReactElement)
plan: string
} npm dependencies
Files (6)
-
components/blocks/sidebar-02/Sidebar02.tsx 2.3 kB
'use client' import { BookOpen, Bot, Frame, LifeBuoy, Map, PieChart, Send, Settings2, SquareTerminal } from 'lucide-react' import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarRail } from '@/components/ui/sidebar' import { NavMain } from './NavMain' import { NavProjects } from './NavProjects' import { NavSecondary } from './NavSecondary' import { NavUser } from './NavUser' import { TeamSwitcher } from './TeamSwitcher' const data = { user: { name: 'shadcn', email: '[email protected]', }, navMain: [ { title: 'Playground', url: '#', icon: SquareTerminal, isActive: true, items: [ { title: 'History', url: '#' }, { title: 'Starred', url: '#' }, { title: 'Settings', url: '#' }, ], }, { title: 'Models', url: '#', icon: Bot, items: [ { title: 'Genesis', url: '#' }, { title: 'Explorer', url: '#' }, { title: 'Quantum', url: '#' }, ], }, { title: 'Documentation', url: '#', icon: BookOpen, items: [ { title: 'Introduction', url: '#' }, { title: 'Get Started', url: '#' }, { title: 'Tutorials', url: '#' }, { title: 'Changelog', url: '#' }, ], }, { title: 'Settings', url: '#', icon: Settings2, items: [ { title: 'General', url: '#' }, { title: 'Team', url: '#' }, { title: 'Billing', url: '#' }, { title: 'Limits', url: '#' }, ], }, ], navSecondary: [ { title: 'Support', url: '#', icon: LifeBuoy }, { title: 'Feedback', url: '#', icon: Send }, ], projects: [ { name: 'Design Engineering', url: '#', icon: Frame }, { name: 'Sales & Marketing', url: '#', icon: PieChart }, { name: 'Travel', url: '#', icon: Map }, ], } export function Sidebar02() { return ( <Sidebar collapsible="icon"> <SidebarHeader> <TeamSwitcher /> </SidebarHeader> <SidebarContent> <NavMain items={data.navMain} /> <NavProjects projects={data.projects} /> <NavSecondary items={data.navSecondary} className="mt-auto" /> </SidebarContent> <SidebarFooter> <NavUser user={data.user} /> </SidebarFooter> <SidebarRail /> </Sidebar> ) } -
components/blocks/sidebar-02/TeamSwitcher.tsx 4.3 kB
'use client' // Edit teams + activeTeam below to match your tenant model. The dropdown // is the full team switcher pattern -- avatar tile, label, kbd shortcut, // and a "Add team" footer row. Wire setActive() to your tenant API. import * as React from 'react' import { AudioWaveform, Check, ChevronsUpDown, Command, Plus, type LucideIcon } from 'lucide-react' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/components/ui/sidebar' // 4-quadrant uipkge logo. Inline component so it can sit next to Lucide // icons in the teams array without an extra file. const UipkgeMark = ({ className }: { className?: string }) => ( <svg viewBox="0 0 24 24" className={className} fill="currentColor" aria-hidden="true"> <rect x="2" y="2" width="9" height="9" rx="1.5" /> <rect x="13" y="2" width="9" height="9" rx="1.5" opacity="0.55" /> <rect x="2" y="13" width="9" height="9" rx="1.5" opacity="0.55" /> <rect x="13" y="13" width="9" height="9" rx="1.5" /> </svg> ) type Team = { name: string logo: LucideIcon | ((props: { className?: string }) => React.ReactElement) plan: string } const teams: Team[] = [ { name: 'uipkge', logo: UipkgeMark, plan: 'UI registry' }, { name: 'uipkge HRMS', logo: AudioWaveform, plan: 'Vertical' }, { name: 'uipkge Hospital', logo: Command, plan: 'Vertical' }, ] export function TeamSwitcher() { const { isMobile } = useSidebar() const [activeTeam, setActiveTeam] = React.useState<Team>(teams[0]!) const ActiveLogo = activeTeam.logo return ( <SidebarMenu> <SidebarMenuItem> <DropdownMenu> <DropdownMenuTrigger asChild> <SidebarMenuButton size="lg" className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center" > <div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg group-data-[collapsible=icon]:size-6"> <ActiveLogo className="size-4 group-data-[collapsible=icon]:size-3.5" /> </div> <div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden"> <span className="font-display truncate font-bold">{activeTeam.name}</span> <span className="text-muted-foreground truncate text-xs">{activeTeam.plan}</span> </div> <ChevronsUpDown className="ml-auto size-4 group-data-[collapsible=icon]:hidden" /> </SidebarMenuButton> </DropdownMenuTrigger> <DropdownMenuContent className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg" side={isMobile ? 'bottom' : 'right'} align="start" sideOffset={4} > <DropdownMenuLabel className="text-muted-foreground text-xs">Teams</DropdownMenuLabel> {teams.map((team, i) => { const Logo = team.logo return ( <DropdownMenuItem key={team.name} className="gap-2 p-2" onSelect={() => setActiveTeam(team)}> <div className="flex size-6 items-center justify-center rounded-sm border"> <Logo className="size-3.5 shrink-0" /> </div> {team.name} {activeTeam === team ? ( <Check className="ml-auto size-4" /> ) : ( <DropdownMenuShortcut>⌘{i + 1}</DropdownMenuShortcut> )} </DropdownMenuItem> ) })} <DropdownMenuSeparator /> <DropdownMenuItem className="gap-2 p-2"> <div className="bg-background flex size-6 items-center justify-center rounded-md border"> <Plus className="size-4" /> </div> <div className="text-muted-foreground font-medium">Add team</div> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> ) } -
components/blocks/sidebar-02/NavMain.tsx 2.3 kB
'use client' import { ChevronRight, type LucideIcon } from 'lucide-react' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, } from '@/components/ui/sidebar' export interface NavMainProps { items: { title: string url: string icon: LucideIcon isActive?: boolean items?: { title: string url: string isActive?: boolean }[] }[] } export function NavMain({ items }: NavMainProps) { return ( <SidebarGroup> <SidebarGroupLabel>Platform</SidebarGroupLabel> <SidebarMenu> {items.map((item) => ( <Collapsible key={item.title} asChild defaultOpen={item.isActive}> <SidebarMenuItem> <SidebarMenuButton asChild tooltip={item.title} isActive={item.isActive}> <a href={item.url}> <item.icon /> <span>{item.title}</span> </a> </SidebarMenuButton> {item.items?.length ? ( <> <CollapsibleTrigger asChild> <SidebarMenuAction className="data-[state=open]:rotate-90"> <ChevronRight /> <span className="sr-only">Toggle</span> </SidebarMenuAction> </CollapsibleTrigger> <CollapsibleContent> <SidebarMenuSub> {item.items.map((subItem) => ( <SidebarMenuSubItem key={subItem.title}> <SidebarMenuSubButton asChild isActive={subItem.isActive}> <a href={subItem.url}> <span>{subItem.title}</span> </a> </SidebarMenuSubButton> </SidebarMenuSubItem> ))} </SidebarMenuSub> </CollapsibleContent> </> ) : null} </SidebarMenuItem> </Collapsible> ))} </SidebarMenu> </SidebarGroup> ) } -
components/blocks/sidebar-02/NavProjects.tsx 2.7 kB
'use client' import { Folder, Forward, MoreHorizontal, Trash2, type LucideIcon } from 'lucide-react' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, useSidebar, } from '@/components/ui/sidebar' export interface NavProjectsProps { projects: { name: string url: string icon: LucideIcon }[] } export function NavProjects({ projects }: NavProjectsProps) { const { isMobile } = useSidebar() return ( // Upstream shadcn-ui hides this entire group on icon-mode collapse // with `group-data-[collapsible=icon]:hidden`. We keep it visible so // the project icons stay reachable in the narrow column; the // SidebarGroupLabel below and the `<span>` children of each // SidebarMenuButton already self-hide on collapse, leaving an // icon-only column that lines up with NavMain. <SidebarGroup> <SidebarGroupLabel>Projects</SidebarGroupLabel> <SidebarMenu> {projects.map((item) => ( <SidebarMenuItem key={item.name}> <SidebarMenuButton asChild> <a href={item.url}> <item.icon /> <span>{item.name}</span> </a> </SidebarMenuButton> <DropdownMenu> <DropdownMenuTrigger asChild> <SidebarMenuAction showOnHover> <MoreHorizontal /> <span className="sr-only">More</span> </SidebarMenuAction> </DropdownMenuTrigger> <DropdownMenuContent className="w-48 rounded-lg" side={isMobile ? 'bottom' : 'right'} align={isMobile ? 'end' : 'start'} > <DropdownMenuItem> <Folder className="text-muted-foreground" /> <span>View Project</span> </DropdownMenuItem> <DropdownMenuItem> <Forward className="text-muted-foreground" /> <span>Share Project</span> </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem> <Trash2 className="text-muted-foreground" /> <span>Delete Project</span> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> ))} <SidebarMenuItem> <SidebarMenuButton> <MoreHorizontal /> <span>More</span> </SidebarMenuButton> </SidebarMenuItem> </SidebarMenu> </SidebarGroup> ) } -
components/blocks/sidebar-02/NavSecondary.tsx 1 kB
'use client' import * as React from 'react' import { type LucideIcon } from 'lucide-react' import { SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem, } from '@/components/ui/sidebar' export interface NavSecondaryProps extends React.ComponentProps<typeof SidebarGroup> { items: { title: string url: string icon: LucideIcon isActive?: boolean }[] } export function NavSecondary({ items, ...props }: NavSecondaryProps) { return ( <SidebarGroup {...props}> <SidebarGroupContent> <SidebarMenu> {items.map((item) => ( <SidebarMenuItem key={item.title}> <SidebarMenuButton asChild size="sm" isActive={item.isActive}> <a href={item.url}> <item.icon /> <span>{item.title}</span> </a> </SidebarMenuButton> </SidebarMenuItem> ))} </SidebarMenu> </SidebarGroupContent> </SidebarGroup> ) } -
components/blocks/sidebar-02/NavUser.tsx 4.8 kB
'use client' import * as React from 'react' import { BadgeCheck, Bell, ChevronsUpDown, CreditCard, LogOut, Monitor, Moon, Palette, Sparkles, Sun, } from 'lucide-react' import { useTheme } from '@/lib/use-theme' import { Avatar, AvatarFallback } from '@/components/ui/avatar' import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/components/ui/sidebar' export interface NavUserProps { user: { name: string email: string } } export function NavUser({ user }: NavUserProps) { const { isMobile } = useSidebar() const { theme, setTheme } = useTheme() const initials = React.useMemo(() => { const parts = user.name.trim().split(/\s+/).slice(0, 2) return parts.map((p) => p[0]?.toUpperCase()).join('') || 'U' }, [user.name]) return ( <SidebarMenu> <SidebarMenuItem> <DropdownMenu> <DropdownMenuTrigger asChild> <SidebarMenuButton size="lg" className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center" > <Avatar className="h-8 w-8 shrink-0 rounded-lg group-data-[collapsible=icon]:size-6"> <AvatarFallback className="rounded-lg text-xs group-data-[collapsible=icon]:text-[10px]"> {initials} </AvatarFallback> </Avatar> <div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden"> <span className="truncate font-medium">{user.name}</span> <span className="truncate text-xs">{user.email}</span> </div> <ChevronsUpDown className="ml-auto size-4 group-data-[collapsible=icon]:hidden" /> </SidebarMenuButton> </DropdownMenuTrigger> <DropdownMenuContent className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg" side={isMobile ? 'bottom' : 'right'} align="end" sideOffset={4} > <DropdownMenuLabel className="p-0 font-normal"> <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> <Avatar className="h-8 w-8 rounded-lg"> <AvatarFallback className="rounded-lg text-xs">{initials}</AvatarFallback> </Avatar> <div className="grid flex-1 text-left text-sm leading-tight"> <span className="truncate font-semibold">{user.name}</span> <span className="truncate text-xs">{user.email}</span> </div> </div> </DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <Sparkles /> Upgrade to Pro </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuItem> <BadgeCheck /> Account </DropdownMenuItem> <DropdownMenuItem> <CreditCard /> Billing </DropdownMenuItem> <DropdownMenuItem> <Bell /> Notifications </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuSub> <DropdownMenuSubTrigger> <Palette /> Theme <span className="text-muted-foreground ml-auto text-xs capitalize">{theme}</span> </DropdownMenuSubTrigger> <DropdownMenuSubContent className="min-w-36"> <DropdownMenuRadioGroup value={theme} onValueChange={(v) => setTheme(v)}> <DropdownMenuRadioItem value="light"> <Sun /> Light </DropdownMenuRadioItem> <DropdownMenuRadioItem value="dark"> <Moon /> Dark </DropdownMenuRadioItem> <DropdownMenuRadioItem value="system"> <Monitor /> System </DropdownMenuRadioItem> </DropdownMenuRadioGroup> </DropdownMenuSubContent> </DropdownMenuSub> <DropdownMenuSeparator /> <DropdownMenuItem> <LogOut /> Log out </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </SidebarMenuItem> </SidebarMenu> ) }
Raw manifest: https://react.uipkge.dev/r/react/sidebar-02.json