Dashboard Layout
blocklayoutFull dashboard shell — collapsible sidebar + sticky topbar (sidebar trigger, breadcrumb, theme switch, notifications) + main content projection. Drop it around a page: `<ui-dashboard-layout>page</ui-dashboard-layout>`.
Installation
$pnpm dlx uipkge-ng@latest add dashboard-layout$npx uipkge-ng@latest add dashboard-layout$yarn dlx uipkge-ng@latest add dashboard-layout$bunx uipkge-ng@latest add dashboard-layoutManifest:
https://uipkge.dev/r/angular/dashboard-layout.jsonInstalls to:src/app/components/blocks/dashboard-layout/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.
DashboardCrumbinterface DashboardCrumb {
label: string
href?: string
}npm dependencies
Files installed (2)
src/app/components/blocks/dashboard-layout/dashboard-layout.component.ts7.3 kB
// Full dashboard shell — collapsible sidebar + sticky topbar (sidebar trigger, breadcrumb, // command palette affordance, theme switch, notifications) + main content projection. // Drop it around a page: `<ui-dashboard-layout>page</ui-dashboard-layout>`. // // NOTE (Angular port): mirrors the React port 1:1 — the Vue original composes the // `sidebar-02`, `command-palette`, and `notifications-popover` blocks, which don't exist in // the Angular registry. Until they're ported, the sidebar body is a minimal inline nav and // notifications is a plain Bell button. Swap them for the dedicated blocks once available. import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { Bell, LayoutDashboard, LucideAngularModule, Settings, Users } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBreadcrumbComponent, UiBreadcrumbItemComponent, UiBreadcrumbLinkComponent, UiBreadcrumbListComponent, UiBreadcrumbPageComponent, UiBreadcrumbSeparatorComponent, } from '@/ui/breadcrumb/breadcrumb.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSidebarComponent, UiSidebarContentComponent, UiSidebarGroupComponent, UiSidebarGroupContentComponent, UiSidebarHeaderComponent, UiSidebarInsetComponent, UiSidebarMenuButtonComponent, UiSidebarMenuComponent, UiSidebarMenuItemComponent, UiSidebarProviderComponent, UiSidebarRailComponent, UiSidebarTriggerComponent, } from '@/ui/sidebar/sidebar.component' import { UiThemeSwitchComponent } from '@/ui/theme-switch/theme-switch.component' export interface DashboardCrumb { label: string href?: string } const NAV = [ { label: 'Dashboard', icon: LayoutDashboard }, { label: 'People', icon: Users }, { label: 'Settings', icon: Settings }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-dashboard-layout, [ui-dashboard-layout]', standalone: true, // React renders the provider itself: the host stays out of layout and `class` goes to the provider. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBreadcrumbComponent, UiBreadcrumbItemComponent, UiBreadcrumbLinkComponent, UiBreadcrumbListComponent, UiBreadcrumbPageComponent, UiBreadcrumbSeparatorComponent, UiButtonComponent, UiSeparatorComponent, UiSidebarComponent, UiSidebarContentComponent, UiSidebarGroupComponent, UiSidebarGroupContentComponent, UiSidebarHeaderComponent, UiSidebarInsetComponent, UiSidebarMenuButtonComponent, UiSidebarMenuComponent, UiSidebarMenuItemComponent, UiSidebarProviderComponent, UiSidebarRailComponent, UiSidebarTriggerComponent, UiThemeSwitchComponent, ], template: ` <!-- UiSidebarProvider hardcodes data-slot="sidebar-wrapper" on its host, so the block's data-slot lives on this root (matches React's provider-level slot for consumers). --> <div data-slot="dashboard-layout" [class]="rootClass"> <ui-sidebar-provider [class]="rootClass"> <a href="#main-content" class="bg-background text-foreground ring-ring sr-only z-50 rounded-md text-sm font-medium shadow-md ring-2 focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:px-3 focus:py-2" >Skip to content</a > <ui-sidebar collapsible="icon"> <ui-sidebar-header /> <ui-sidebar-content> <ui-sidebar-group> <ui-sidebar-group-content> <ui-sidebar-menu> @for (item of nav; track item.label) { <ui-sidebar-menu-item> <ui-sidebar-menu-button [tooltip]="item.label"> <lucide-icon [img]="item.icon" /> <span>{{ item.label }}</span> </ui-sidebar-menu-button> </ui-sidebar-menu-item> } </ui-sidebar-menu> </ui-sidebar-group-content> </ui-sidebar-group> </ui-sidebar-content> <ui-sidebar-rail /> </ui-sidebar> <ui-sidebar-inset> <header class="bg-background/80 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-30 flex h-14 w-full shrink-0 items-center justify-between border-b px-4 backdrop-blur-xl transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12" > <div class="flex items-center gap-2"> <ui-sidebar-trigger class="-ml-1" /> <ui-separator orientation="vertical" class="mr-2 h-4" /> <ui-breadcrumb> <ui-breadcrumb-list> @for (crumb of crumbs; track crumb.label; let i = $index; let last = $last) { <ui-breadcrumb-item [class]="i === 0 ? 'hidden md:block' : ''"> @if (crumb.href && !last) { <ui-breadcrumb-link [href]="crumb.href" class="text-muted-foreground/70 hover:text-foreground transition-colors" > {{ crumb.label }} </ui-breadcrumb-link> } @else { <ui-breadcrumb-page class="font-medium">{{ crumb.label }}</ui-breadcrumb-page> } </ui-breadcrumb-item> @if (!last) { <ui-breadcrumb-separator [class]="i === 0 ? 'hidden md:block' : ''" /> } } </ui-breadcrumb-list> </ui-breadcrumb> </div> <div class="flex items-center gap-1 px-2 sm:gap-3"> <div class="flex items-center gap-0.5"> <ui-theme-switch variant="icon-only" /> <button ui-button variant="ghost" size="icon" class="text-muted-foreground hover:text-foreground relative size-8 rounded-lg" aria-label="Notifications" > <lucide-icon [img]="Bell" class="size-4" /> </button> <!-- ProfileMenu removed from header: the sidebar's NavUser footer already owns the profile/account affordance. The block still accepts a user input so consumers can wire it into the sidebar or a custom slot. --> </div> </div> </header> <main id="main-content" tabindex="-1" class="flex flex-1 flex-col px-4 pt-4 pb-4 outline-none lg:px-6 lg:pt-6 lg:pb-6"> <ng-content /> </main> </ui-sidebar-inset> </ui-sidebar-provider> </div> `, }) export class UiDashboardLayoutComponent { protected readonly Bell = Bell protected readonly nav = NAV.map((n) => ({ ...n })) @Input() breadcrumbs: DashboardCrumb[] = [{ label: 'Dashboard' }] @Input() user: { name: string; email: string; avatar?: string } = { name: 'Alex Morgan', email: '[email protected]' } @Input('class') className?: string get crumbs(): DashboardCrumb[] { return this.breadcrumbs?.length ? this.breadcrumbs : [{ label: 'Dashboard' }] } get rootClass(): string { return cn(this.className) } }src/app/components/blocks/dashboard-layout/index.ts0.1 kB
export { UiDashboardLayoutComponent, type DashboardCrumb } from './dashboard-layout.component'
Raw manifest:https://uipkge.dev/r/angular/dashboard-layout.json