Sales Sidebar
blocklayoutApp sidebar for sales workspaces: team switcher header, collapsible platform nav with badges, store list, secondary links, and user footer. Composed from the sidebar, collapsible, dropdown-menu and badge primitives; place inside <ui-sidebar-provider> for the icon rail, Cmd/Ctrl+B and mobile sheet. Selection flows through [(active)].
Installation
$pnpm dlx uipkge-ng@latest add sales-sidebar$npx uipkge-ng@latest add sales-sidebar$yarn dlx uipkge-ng@latest add sales-sidebar$bunx uipkge-ng@latest add sales-sidebarManifest:
https://uipkge.dev/r/angular/sales-sidebar.jsonInstalls to:src/app/components/blocks/sales-sidebar/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
SalesNavChildinterface SalesNavChild {
title: string
}SalesNavIteminterface SalesNavItem {
title: string
icon: LucideIconData
badge?: string
children?: SalesNavChild[]
}SalesTeaminterface SalesTeam {
name: string
plan: string
initials: string
}npm dependencies
Files installed (2)
src/app/components/blocks/sales-sidebar/sales-sidebar.component.ts14 kB
import { Component, EventEmitter, Input, Output, signal, ChangeDetectionStrategy } from '@angular/core' import { UiSidebarComponent, UiSidebarContentComponent, UiSidebarFooterComponent, UiSidebarGroupComponent, UiSidebarGroupLabelComponent, UiSidebarHeaderComponent, UiSidebarMenuButtonComponent, UiSidebarMenuComponent, UiSidebarMenuItemComponent, UiSidebarMenuSubButtonComponent, UiSidebarMenuSubComponent, UiSidebarMenuSubItemComponent, UiSidebarRailComponent, injectSidebar, } from '@/ui/sidebar/sidebar.component' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiCollapsibleComponent, UiCollapsibleContentComponent, UiCollapsibleTriggerComponent, } from '@/ui/collapsible/collapsible.component' import { UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuLabelComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, } from '@/ui/dropdown-menu/dropdown-menu.component' import { LucideAngularModule, LayoutDashboard, ShoppingCart, Users, Package, TrendingUp, Settings, LifeBuoy, Store, Plus, Check, ChevronsUpDown, ChevronRight, LogOut, type LucideIconData, } from 'lucide-angular' export interface SalesNavChild { title: string } export interface SalesNavItem { title: string icon: LucideIconData badge?: string children?: SalesNavChild[] } export interface SalesTeam { name: string plan: string initials: string } const NAV_MAIN: SalesNavItem[] = [ { title: 'Dashboard', icon: LayoutDashboard }, { title: 'Orders', icon: ShoppingCart, badge: '12', children: [{ title: 'All orders' }, { title: 'Pending' }, { title: 'Returns' }], }, { title: 'Customers', icon: Users }, { title: 'Products', icon: Package, children: [{ title: 'Catalog' }, { title: 'Inventory' }, { title: 'Categories' }], }, { title: 'Analytics', icon: TrendingUp }, { title: 'Settings', icon: Settings }, ] const NAV_SECONDARY: SalesNavItem[] = [{ title: 'Support', icon: LifeBuoy }] const STORES: SalesNavItem[] = [ { title: 'Flagship store', icon: Store }, { title: 'Outlet store', icon: Store }, ] const TEAMS: SalesTeam[] = [ { name: 'Acme Inc', plan: 'Flagship store', initials: 'AC' }, { name: 'Globex', plan: 'Outlet store', initials: 'GX' }, ] /** * Sales-sidebar navigation: team switcher, collapsible platform groups (one open at a * time), store list, secondary links and a user menu. Composed from the sidebar, * collapsible, dropdown-menu and badge primitives; renders inside <ui-sidebar>, so it * gets the icon rail, tooltips and the mobile sheet from the primitive. Selection is * owned by the parent via [(active)]. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-sales-sidebar-nav, [ui-sales-sidebar-nav]', standalone: true, host: { class: 'contents' }, imports: [ LucideAngularModule, UiBadgeComponent, UiCollapsibleComponent, UiCollapsibleTriggerComponent, UiCollapsibleContentComponent, UiDropdownMenuComponent, UiDropdownMenuTriggerComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuLabelComponent, UiDropdownMenuSeparatorComponent, UiSidebarHeaderComponent, UiSidebarContentComponent, UiSidebarFooterComponent, UiSidebarGroupComponent, UiSidebarGroupLabelComponent, UiSidebarMenuComponent, UiSidebarMenuItemComponent, UiSidebarMenuButtonComponent, UiSidebarMenuSubComponent, UiSidebarMenuSubItemComponent, UiSidebarMenuSubButtonComponent, ], template: ` <ui-sidebar-header> <ul ui-sidebar-menu> <li ui-sidebar-menu-item> <ui-dropdown-menu> <button type="button" ui-sidebar-menu-button ui-dropdown-menu-trigger size="lg" class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center" > <span class="bg-sidebar-primary text-sidebar-primary-foreground flex size-8 shrink-0 items-center justify-center rounded-lg text-xs font-bold group-data-[collapsible=icon]:size-6 group-data-[collapsible=icon]:text-[10px]" > {{ activeTeam().initials }} </span> <span class="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden"> <span class="truncate font-bold">{{ activeTeam().name }}</span> <span class="text-muted-foreground truncate text-xs">{{ activeTeam().plan }}</span> </span> <lucide-icon [img]="ChevronsUpDown" class="text-muted-foreground ml-auto size-4 group-data-[collapsible=icon]:hidden" /> </button> <ui-dropdown-menu-content class="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg" [side]="sidebar.isMobile ? 'bottom' : 'right'" align="start" [sideOffset]="4" > <ui-dropdown-menu-label class="text-muted-foreground text-xs">Stores</ui-dropdown-menu-label> @for (team of teams; track team.name) { <ui-dropdown-menu-item class="gap-2 p-2" (select)="selectTeam(team)"> <span class="flex size-6 items-center justify-center rounded-sm border text-xs font-bold">{{ team.initials }}</span> {{ team.name }} @if (team.name === activeTeam().name) { <lucide-icon [img]="Check" class="ml-auto size-4" /> } </ui-dropdown-menu-item> } </ui-dropdown-menu-content> </ui-dropdown-menu> </li> </ul> </ui-sidebar-header> <ui-sidebar-content class="gap-4"> <ui-sidebar-group> <ui-sidebar-group-label>Platform</ui-sidebar-group-label> <ul ui-sidebar-menu> @for (item of navMain; track item.title) { @if (item.children?.length) { <li ui-sidebar-menu-item ui-collapsible [open]="isExpanded(item.title)" (openChange)="setExpanded(item.title, $event)" > <button type="button" ui-sidebar-menu-button ui-collapsible-trigger [tooltip]="item.title" [isActive]="isActive(item.title)" (click)="select(item.title)" > <lucide-icon [img]="item.icon" class="size-4 shrink-0" /> <span class="flex-1 text-left">{{ item.title }}</span> @if (item.badge) { <span ui-badge variant="secondary">{{ item.badge }}</span> } <lucide-icon [img]="ChevronRight" class="size-4 shrink-0 transition-transform duration-200" [class.rotate-90]="isExpanded(item.title)" /> </button> <ui-collapsible-content> <ul ui-sidebar-menu-sub> @for (child of item.children; track child.title) { <li ui-sidebar-menu-sub-item> <button type="button" ui-sidebar-menu-sub-button [isActive]="isActive(child.title)" (click)="select(child.title)" > <span>{{ child.title }}</span> </button> </li> } </ul> </ui-collapsible-content> </li> } @else { <li ui-sidebar-menu-item> <button type="button" ui-sidebar-menu-button [tooltip]="item.title" [isActive]="isActive(item.title)" (click)="select(item.title)" > <lucide-icon [img]="item.icon" class="size-4 shrink-0" /> <span class="flex-1 text-left">{{ item.title }}</span> @if (item.badge) { <span ui-badge variant="secondary">{{ item.badge }}</span> } </button> </li> } } </ul> </ui-sidebar-group> <ui-sidebar-group> <ui-sidebar-group-label>Stores</ui-sidebar-group-label> <ul ui-sidebar-menu> @for (store of stores; track store.title) { <li ui-sidebar-menu-item> <button type="button" ui-sidebar-menu-button [tooltip]="store.title" [isActive]="isActive(store.title)" (click)="select(store.title)" > <lucide-icon [img]="store.icon" class="size-4 shrink-0" /> <span class="flex-1 text-left">{{ store.title }}</span> </button> </li> } </ul> </ui-sidebar-group> <ui-sidebar-group class="mt-auto"> <ul ui-sidebar-menu> @for (item of navSecondary; track item.title) { <li ui-sidebar-menu-item> <button type="button" ui-sidebar-menu-button [tooltip]="item.title" [isActive]="isActive(item.title)" (click)="select(item.title)" > <lucide-icon [img]="item.icon" class="size-4 shrink-0" /> <span class="flex-1 text-left">{{ item.title }}</span> </button> </li> } </ul> </ui-sidebar-group> </ui-sidebar-content> <ui-sidebar-footer> <ul ui-sidebar-menu> <li ui-sidebar-menu-item> <ui-dropdown-menu> <button type="button" ui-sidebar-menu-button ui-dropdown-menu-trigger size="lg" class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-data-[collapsible=icon]:!justify-center" > <span class="bg-muted text-foreground flex size-8 shrink-0 items-center justify-center rounded-full text-xs font-bold group-data-[collapsible=icon]:size-6 group-data-[collapsible=icon]:text-[10px]" > JD </span> <span class="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden"> <span class="truncate font-semibold">Jane Doe</span> <span class="text-muted-foreground truncate text-xs">jane@acme.com</span> </span> <lucide-icon [img]="ChevronsUpDown" class="text-muted-foreground ml-auto size-4 group-data-[collapsible=icon]:hidden" /> </button> <ui-dropdown-menu-content class="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg" [side]="sidebar.isMobile ? 'bottom' : 'right'" align="end" [sideOffset]="4" > <ui-dropdown-menu-item><lucide-icon [img]="Settings" /> Account settings</ui-dropdown-menu-item> <ui-dropdown-menu-item><lucide-icon [img]="Plus" /> Invite member</ui-dropdown-menu-item> <ui-dropdown-menu-separator /> <ui-dropdown-menu-item><lucide-icon [img]="LogOut" /> Log out</ui-dropdown-menu-item> </ui-dropdown-menu-content> </ui-dropdown-menu> </li> </ul> </ui-sidebar-footer> `, }) export class UiSalesSidebarNavComponent { readonly sidebar = injectSidebar() @Input() active = 'Dashboard' @Output() activeChange = new EventEmitter<string>() protected readonly navMain = NAV_MAIN protected readonly navSecondary = NAV_SECONDARY protected readonly stores = STORES protected readonly teams = TEAMS protected readonly Settings = Settings protected readonly Plus = Plus protected readonly Check = Check protected readonly ChevronsUpDown = ChevronsUpDown protected readonly ChevronRight = ChevronRight protected readonly LogOut = LogOut private readonly expanded = signal<string | null>('Orders') private readonly activeTeamSignal = signal<SalesTeam>(TEAMS[0]!) activeTeam(): SalesTeam { return this.activeTeamSignal() } isActive(title: string): boolean { return this.active === title } isExpanded(title: string): boolean { return this.expanded() === title } select(title: string): void { this.active = title this.activeChange.emit(title) } /** Accordion: opening a group closes the others. */ setExpanded(title: string, open: boolean): void { this.expanded.set(open ? title : this.expanded() === title ? null : this.expanded()) } toggleGroup(title: string): void { this.setExpanded(title, !this.isExpanded(title)) } selectTeam(team: SalesTeam): void { this.activeTeamSignal.set(team) } } /** * Sales sidebar block: a `collapsible="icon"` Sidebar around the nav. Place it inside * <ui-sidebar-provider> next to <ui-sidebar-inset> (with a <ui-sidebar-trigger>) -- * the primitive then handles the icon rail, Cmd/Ctrl+B and the mobile sheet. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-sales-sidebar, [ui-sales-sidebar]', standalone: true, // Renders <ui-sidebar> itself: keep this wrapper out of the provider's flex layout. host: { class: 'contents' }, imports: [UiSidebarComponent, UiSidebarRailComponent, UiSalesSidebarNavComponent], template: ` <ui-sidebar collapsible="icon"> <ui-sales-sidebar-nav [active]="active" (activeChange)="activeChange.emit($event)" /> <button ui-sidebar-rail></button> </ui-sidebar> `, }) export class UiSalesSidebarComponent { @Input() active = 'Dashboard' @Output() activeChange = new EventEmitter<string>() }src/app/components/blocks/sales-sidebar/index.ts0.2 kB
export { UiSalesSidebarComponent, UiSalesSidebarNavComponent, type SalesNavItem, type SalesNavChild, type SalesTeam, } from './sales-sidebar.component'
Raw manifest:https://uipkge.dev/r/angular/sales-sidebar.json