Header
blockmarketingSite header collection: sticky top bar, dismissible announcement stack, centered-logo split, floating pill, full-width mega menu, inline search with command palette, and a transparent-over-hero scroll variant — each with a mobile sheet fallback.
Installation
$pnpm dlx uipkge-ng@latest add header$npx uipkge-ng@latest add header$yarn dlx uipkge-ng@latest add header$bunx uipkge-ng@latest add headerManifest:
https://uipkge.dev/r/angular/header.jsonInstalls to:src/app/components/blocks/header/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.
MegaIteminterface MegaItem {
icon: LucideIconData
title: string
body: string
}MegaGroupinterface MegaGroup {
heading: string
items: MegaItem[]
}MegaMenuinterface MegaMenu {
label: string
groups: MegaGroup[]
highlight: { title: string; body: string; cta: string }
}SearchGroupinterface SearchGroup {
heading: string
items: { icon: LucideIconData; label: string }[]
}npm dependencies
Files installed (8)
src/app/components/blocks/header/header-01.component.ts4.2 kB
// Sticky marketing top nav. Brand + 5 links + sign-in/CTA on desktop, hamburger + // right-side Sheet on mobile. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, signal } from '@angular/core' import { Boxes, LucideAngularModule, Menu, X } from 'lucide-angular' import { UiButtonComponent } from '@/ui/button/button.component' import { UiSheetCloseComponent, UiSheetComponent, UiSheetContentComponent } from '@/ui/sheet/sheet.component' const links = [ { label: 'Features', href: '#features' }, { label: 'Pricing', href: '#pricing' }, { label: 'Customers', href: '#customers' }, { label: 'Docs', href: '#docs' }, { label: 'Blog', href: '#blog' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-header-01, [ui-header-01]', standalone: true, host: { '[attr.class]': '"contents"' }, imports: [LucideAngularModule, UiButtonComponent, UiSheetCloseComponent, UiSheetComponent, UiSheetContentComponent], template: ` <header data-slot="header-01" class="bg-background/80 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-40 border-b backdrop-blur" > <div class="mx-auto flex h-16 max-w-6xl items-center justify-between px-6"> <a href="#" class="flex items-center gap-2"> <div class="bg-primary text-primary-foreground flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="Boxes" class="size-4" /> </div> <span class="text-base font-semibold">Acme</span> </a> <nav class="hidden items-center gap-6 md:flex" aria-label="Primary"> @for (link of links; track link.href) { <a [href]="link.href" class="text-muted-foreground hover:text-foreground text-sm transition-colors">{{ link.label }}</a> } </nav> <div class="hidden items-center gap-2 md:flex"> <button ui-button variant="ghost" size="sm">Sign in</button> <button ui-button size="sm">Start free trial</button> </div> <div ui-sheet [open]="mobileOpen()" (openChange)="mobileOpen.set($event)"> <button ui-button variant="ghost" size="icon" class="md:hidden" aria-label="Open menu" (click)="mobileOpen.set(true)" > <lucide-icon [img]="Menu" class="size-5" /> </button> <div ui-sheet-content side="right" class="w-72"> <div class="flex h-full flex-col"> <div class="flex items-center justify-between border-b px-4 py-3"> <div class="flex items-center gap-2"> <div class="bg-primary text-primary-foreground flex size-7 items-center justify-center rounded-md"> <lucide-icon [img]="Boxes" class="size-3.5" /> </div> <span class="text-sm font-semibold">Acme</span> </div> <button ui-button variant="ghost" size="icon" class="size-8" aria-label="Close menu" (click)="mobileOpen.set(false)" > <lucide-icon [img]="X" class="size-4" /> </button> </div> <nav class="flex flex-1 flex-col gap-1 p-4" aria-label="Mobile"> @for (link of links; track link.href) { <a [href]="link.href" class="hover:bg-muted rounded-md px-3 py-2 text-sm transition-colors" (click)="mobileOpen.set(false)" >{{ link.label }}</a > } </nav> <div class="flex flex-col gap-2 border-t p-4"> <button ui-button variant="outline" class="w-full">Sign in</button> <button ui-button class="w-full">Start free trial</button> </div> </div> </div> </div> </div> </header> `, }) export class UiHeader01Component { protected readonly Boxes = Boxes protected readonly Menu = Menu protected readonly X = X protected readonly links = links readonly mobileOpen = signal(false) }src/app/components/blocks/header/header-announcement-stack.component.ts5.4 kB
// Two-tier header. A dismissible announcement strip sits above a sticky navbar; // dismissing it collapses the strip and the navbar settles flush to the top without a jump. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, OnInit, signal } from '@angular/core' import { ArrowRight, Layers, LucideAngularModule, Menu, X } from 'lucide-angular' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, } from '@/ui/sheet/sheet.component' const STORAGE_KEY = 'uipkge:announcement-dismissed' const links = [ { label: 'Platform', href: '#platform' }, { label: 'Pricing', href: '#pricing' }, { label: 'Customers', href: '#customers' }, { label: 'Docs', href: '#docs' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-header-announcement-stack, [ui-header-announcement-stack]', standalone: true, host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiSeparatorComponent, UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, ], template: ` <div data-slot="header-announcement-stack" class="sticky top-0 z-40"> @if (!dismissed()) { <div class="border-border bg-muted/60 border-b backdrop-blur"> <div class="mx-auto flex h-10 max-w-6xl items-center gap-3 px-6 text-sm"> <span ui-badge variant="secondary" class="shrink-0">New</span> <p class="min-w-0 truncate"> Row-level scoping now evaluates against SCIM groups. <a href="#changelog" class="hover:text-foreground underline underline-offset-4"> Read the changelog</a> </p> <lucide-icon [img]="ArrowRight" class="text-muted-foreground hidden size-3.5 shrink-0 sm:block" /> <button ui-button variant="ghost" size="icon" class="ml-auto size-7 shrink-0" aria-label="Dismiss announcement" (click)="dismiss()" > <lucide-icon [img]="X" class="size-3.5" /> </button> </div> </div> } <header class="border-border bg-background/90 border-b backdrop-blur"> <div class="mx-auto flex h-16 max-w-6xl items-center gap-6 px-6"> <a href="#top" class="flex shrink-0 items-center gap-2 font-semibold tracking-tight"> <lucide-icon [img]="Layers" class="text-primary size-5" /> Northwind </a> <nav class="hidden items-center gap-1 md:flex" aria-label="Primary"> @for (link of links; track link.href) { <a [href]="link.href" class="text-muted-foreground hover:text-foreground hover:bg-muted focus-visible:ring-ring rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none" >{{ link.label }}</a > } </nav> <div class="ml-auto hidden items-center gap-2 md:flex"> <button ui-button variant="ghost" size="sm">Sign in</button> <button ui-button size="sm">Book a demo</button> </div> <div ui-sheet [open]="open()" (openChange)="open.set($event)"> <button ui-button variant="ghost" size="icon" class="ml-auto md:hidden" aria-label="Open navigation menu" (click)="open.set(true)" > <lucide-icon [img]="Menu" class="size-5" /> </button> <div ui-sheet-content side="right" class="w-72"> <div ui-sheet-header> <h2 ui-sheet-title>Menu</h2> </div> <nav class="mt-6 grid gap-1" aria-label="Mobile"> @for (link of links; track link.href) { <a [href]="link.href" class="hover:bg-muted rounded-md px-3 py-2 text-sm font-medium transition-colors" (click)="open.set(false)" >{{ link.label }}</a > } </nav> <ui-separator class="my-5" /> <div class="grid gap-2"> <button ui-button variant="outline">Sign in</button> <button ui-button>Book a demo</button> </div> </div> </div> </div> </header> </div> `, }) export class UiHeaderAnnouncementStackComponent implements OnInit { protected readonly ArrowRight = ArrowRight protected readonly Layers = Layers protected readonly Menu = Menu protected readonly X = X protected readonly links = links readonly dismissed = signal(false) readonly open = signal(false) ngOnInit(): void { try { this.dismissed.set(window.sessionStorage.getItem(STORAGE_KEY) === '1') } catch { this.dismissed.set(false) } } dismiss(): void { this.dismissed.set(true) try { window.sessionStorage.setItem(STORAGE_KEY, '1') } catch { // Blocked storage: the dismissal still holds for this page view. } } }src/app/components/blocks/header/header-centered-logo.component.ts4.3 kB
// Symmetrical navbar: wordmark centred between two nav halves with a utility cluster on // the right. Below the breakpoint the split collapses into one sheet trigger. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, signal } from '@angular/core' import { Compass, LucideAngularModule, Menu, ShoppingBag, User } from 'lucide-angular' import { UiButtonComponent } from '@/ui/button/button.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, } from '@/ui/sheet/sheet.component' const leftLinks = [ { label: 'Collections', href: '#collections' }, { label: 'New in', href: '#new' }, { label: 'Studio', href: '#studio' }, ] const rightLinks = [ { label: 'Journal', href: '#journal' }, { label: 'Stockists', href: '#stockists' }, { label: 'Contact', href: '#contact' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-header-centered-logo, [ui-header-centered-logo]', standalone: true, host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiButtonComponent, UiSeparatorComponent, UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, ], template: ` <header data-slot="header-centered-logo" class="border-border bg-background border-b"> <div class="mx-auto flex h-16 max-w-6xl items-center px-6"> <nav class="hidden flex-1 basis-0 items-center gap-1 lg:flex" aria-label="Primary left"> @for (link of leftLinks; track link.href) { <a [href]="link.href" [class]="linkClass">{{ link.label }}</a> } </nav> <div ui-sheet [open]="open()" (openChange)="open.set($event)"> <button ui-button variant="ghost" size="icon" class="lg:hidden" aria-label="Open navigation menu" (click)="open.set(true)" > <lucide-icon [img]="Menu" class="size-5" /> </button> <div ui-sheet-content side="left" class="w-72"> <div ui-sheet-header> <h2 ui-sheet-title>Menu</h2> </div> <nav class="mt-6 grid gap-1" aria-label="Mobile"> @for (link of allLinks; track link.href) { <a [href]="link.href" class="hover:bg-muted rounded-md px-3 py-2 text-sm font-medium transition-colors" (click)="open.set(false)" >{{ link.label }}</a > } </nav> <ui-separator class="my-5" /> <button ui-button class="w-full">Account</button> </div> </div> <a href="#top" class="mx-auto flex items-center gap-2 text-base font-semibold tracking-[0.18em] uppercase lg:mx-6" > <lucide-icon [img]="Compass" class="text-primary size-4" /> Halden </a> <nav class="hidden flex-1 basis-0 items-center justify-end gap-1 lg:flex" aria-label="Primary right"> @for (link of rightLinks; track link.href) { <a [href]="link.href" [class]="linkClass">{{ link.label }}</a> } </nav> <div class="flex items-center gap-1 lg:ml-4"> <button ui-button variant="ghost" size="icon" aria-label="Account"> <lucide-icon [img]="User" class="size-4" /> </button> <button ui-button variant="ghost" size="icon" aria-label="Bag"> <lucide-icon [img]="ShoppingBag" class="size-4" /> </button> </div> </div> </header> `, }) export class UiHeaderCenteredLogoComponent { protected readonly Compass = Compass protected readonly Menu = Menu protected readonly ShoppingBag = ShoppingBag protected readonly User = User protected readonly leftLinks = leftLinks protected readonly rightLinks = rightLinks protected readonly allLinks = [...leftLinks, ...rightLinks] protected readonly linkClass = 'text-muted-foreground hover:text-foreground focus-visible:ring-ring rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none' readonly open = signal(false) }src/app/components/blocks/header/header-floating-pill.component.ts4.4 kB
// Detached pill navbar floating above the page. A sliding indicator tracks the active link // on hover and selection; the CTA stays pinned on the right. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core' import { CircleDot, LucideAngularModule, Menu } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiButtonComponent } from '@/ui/button/button.component' import { UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, } from '@/ui/sheet/sheet.component' const links = [ { label: 'Product', href: '#product' }, { label: 'Pricing', href: '#pricing' }, { label: 'Customers', href: '#customers' }, { label: 'Docs', href: '#docs' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-header-floating-pill, [ui-header-floating-pill]', standalone: true, host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiButtonComponent, UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, ], template: ` <div data-slot="header-floating-pill" class="pointer-events-none sticky top-0 z-40 px-4 pt-4"> <header class="border-border bg-card/85 pointer-events-auto mx-auto flex h-14 max-w-4xl items-center gap-3 rounded-full border px-3 shadow-sm backdrop-blur" > <a href="#top" class="flex shrink-0 items-center gap-2 pl-2 font-semibold tracking-tight"> <lucide-icon [img]="CircleDot" class="text-primary size-4" /> Verity </a> <nav class="relative hidden items-center md:flex" aria-label="Primary"> <span class="bg-muted absolute inset-y-1 rounded-full transition-colors duration-200 ease-out" [style.left]="indicatorLeft()" [style.width]="indicatorWidth()" aria-hidden="true" ></span> @for (link of links; track link.href; let i = $index) { <a [href]="link.href" data-pill-link [class]="pillLinkClass(i)" [attr.aria-current]="i === active() ? 'page' : null" (mouseenter)="hovered.set(i)" (mouseleave)="hovered.set(null)" (focus)="hovered.set(i)" (blur)="hovered.set(null)" (click)="active.set(i)" >{{ link.label }}</a > } </nav> <button ui-button size="sm" class="ml-auto rounded-full">Start free</button> <div ui-sheet [open]="open()" (openChange)="open.set($event)"> <button ui-button variant="ghost" size="icon" class="rounded-full md:hidden" aria-label="Open navigation menu" (click)="open.set(true)" > <lucide-icon [img]="Menu" class="size-5" /> </button> <div ui-sheet-content side="top"> <div ui-sheet-header> <h2 ui-sheet-title>Menu</h2> </div> <nav class="mt-6 grid gap-1" aria-label="Mobile"> @for (link of links; track link.href) { <a [href]="link.href" class="hover:bg-muted rounded-md px-3 py-2 text-sm font-medium transition-colors" (click)="open.set(false)" >{{ link.label }}</a > } </nav> </div> </div> </header> </div> `, }) export class UiHeaderFloatingPillComponent { protected readonly CircleDot = CircleDot protected readonly Menu = Menu protected readonly links = links readonly active = signal(0) readonly hovered = signal<number | null>(null) readonly open = signal(false) /** Indicator follows hover, falling back to the selected link (measured slots are even widths). */ readonly indicatorLeft = computed(() => `${(this.hovered() ?? this.active()) * 88}px`) readonly indicatorWidth = computed(() => '76px') pillLinkClass(i: number): string { return cn( 'focus-visible:ring-ring relative rounded-full px-3.5 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', i === (this.hovered() ?? this.active()) || (this.hovered() === null && i === this.active()) ? 'text-foreground' : 'text-muted-foreground hover:text-foreground', ) } }src/app/components/blocks/header/header-mega-menu.component.ts10.5 kB
// Navbar with full-width mega panels. Product and Resources open grouped link lists with // descriptions and a highlighted card. Port of the React block 1:1 (desktop hover panels // driven by local state; mobile falls back to a sheet). import { ChangeDetectionStrategy, Component, signal } from '@angular/core' import { ArrowRight, BarChart3, BookOpen, Boxes, Layers, LifeBuoy, Lock, LucideAngularModule, Menu, Workflow, type LucideIconData, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiButtonComponent } from '@/ui/button/button.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, } from '@/ui/sheet/sheet.component' interface MegaItem { icon: LucideIconData title: string body: string } interface MegaGroup { heading: string items: MegaItem[] } interface MegaMenu { label: string groups: MegaGroup[] highlight: { title: string; body: string; cta: string } } const menus: MegaMenu[] = [ { label: 'Product', groups: [ { heading: 'Core', items: [ { icon: Boxes, title: 'Metric layer', body: 'Versioned definitions your whole org shares.' }, { icon: BarChart3, title: 'Dashboards', body: 'Composed from certified metrics, not raw SQL.' }, { icon: Workflow, title: 'Pipelines', body: 'Scheduled materialisation with cost ceilings.' }, ], }, { heading: 'Governance', items: [ { icon: Lock, title: 'Row-level access', body: 'Scope evaluated per query, per identity.' }, { icon: BookOpen, title: 'Change history', body: 'Every definition edit reviewed and revertible.' }, { icon: LifeBuoy, title: 'Audit exports', body: 'Generated from the same history, not by hand.' }, ], }, ], highlight: { title: 'Ship your first dashboard today', body: 'Connect a warehouse, publish one certified metric, and invite the team. No migration required.', cta: 'Start the walkthrough', }, }, { label: 'Resources', groups: [ { heading: 'Learn', items: [ { icon: BookOpen, title: 'Documentation', body: 'Setup, modelling, and the access model.' }, { icon: Layers, title: 'Guides', body: 'Reconciliation and first-close checklists.' }, { icon: BarChart3, title: 'Benchmarks', body: 'Close speed across 240 finance teams.' }, ], }, { heading: 'Support', items: [ { icon: LifeBuoy, title: 'Help centre', body: 'Answers to the questions support gets most.' }, { icon: Workflow, title: 'Status', body: 'Live uptime and incident history.' }, { icon: Lock, title: 'Trust centre', body: 'SOC 2, subprocessors, and data residency.' }, ], }, ], highlight: { title: 'Read the rollout plan', body: 'The five-week implementation we run for every customer, written down so you can run it yourself.', cta: 'Open the plan', }, }, ] const plainLinks = [ { label: 'Pricing', href: '#pricing' }, { label: 'Customers', href: '#customers' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-header-mega-menu, [ui-header-mega-menu]', standalone: true, host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiButtonComponent, UiSeparatorComponent, UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, ], template: ` <header data-slot="header-mega-menu" class="border-border bg-background/90 border-b backdrop-blur"> <div class="mx-auto flex h-16 max-w-6xl items-center gap-6 px-6"> <a href="#top" class="flex shrink-0 items-center gap-2 font-semibold tracking-tight"> <lucide-icon [img]="Layers" class="text-primary size-5" /> Northwind </a> <nav class="hidden lg:flex" aria-label="Primary"> <ul class="flex items-center gap-1"> @for (menu of menus; track menu.label) { <li class="relative" (mouseenter)="openMenu.set(menu.label)" (mouseleave)="openMenu.set(null)"> <button type="button" [class]="triggerClass(menu.label)" [attr.aria-expanded]="openMenu() === menu.label" (click)="toggleMenu(menu.label)" (keydown.escape)="openMenu.set(null)" > {{ menu.label }} </button> @if (openMenu() === menu.label) { <div class="absolute top-full left-0 z-50 pt-2"> <div class="border-border bg-popover grid w-[46rem] grid-cols-[1fr_1fr_16rem] gap-6 rounded-lg border p-6 shadow-md" > @for (group of menu.groups; track group.heading) { <div> <p class="text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase"> {{ group.heading }} </p> <ul class="mt-3 space-y-1"> @for (item of group.items; track item.title) { <li> <a href="#" class="hover:bg-muted focus-visible:ring-ring flex gap-3 rounded-md p-2 transition-colors focus-visible:ring-2 focus-visible:outline-none" > <lucide-icon [img]="item.icon" class="text-muted-foreground mt-0.5 size-4 shrink-0" /> <span class="min-w-0"> <span class="block text-sm font-medium">{{ item.title }}</span> <span class="text-muted-foreground block text-xs leading-snug">{{ item.body }}</span> </span> </a> </li> } </ul> </div> } <div class="bg-muted/50 flex flex-col rounded-lg p-4"> <p class="text-sm font-semibold">{{ menu.highlight.title }}</p> <p class="text-muted-foreground mt-2 text-xs leading-relaxed">{{ menu.highlight.body }}</p> <button ui-button variant="link" class="mt-auto h-auto justify-start p-0 pt-4 text-xs"> {{ menu.highlight.cta }} <lucide-icon [img]="ArrowRight" class="ml-1 size-3" /> </button> </div> </div> </div> } </li> } @for (link of plainLinks; track link.href) { <li> <a [href]="link.href" class="hover:bg-muted hover:text-foreground focus-visible:ring-ring rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none" >{{ link.label }}</a > </li> } </ul> </nav> <div class="ml-auto flex items-center gap-2"> <button ui-button variant="ghost" size="sm" class="hidden sm:inline-flex">Sign in</button> <ui-separator orientation="vertical" class="hidden h-5 sm:block" /> <button ui-button size="sm">Book a demo</button> </div> <div ui-sheet [open]="open()" (openChange)="open.set($event)"> <button ui-button variant="ghost" size="icon" class="ml-auto lg:hidden" aria-label="Open navigation menu" (click)="open.set(true)" > <lucide-icon [img]="Menu" class="size-5" /> </button> <div ui-sheet-content side="right" class="w-72 overflow-y-auto"> <div ui-sheet-header> <h2 ui-sheet-title>Menu</h2> </div> <nav class="mt-6 grid gap-5" aria-label="Mobile"> @for (menu of menus; track menu.label) { <div> <p class="text-muted-foreground/70 text-xs font-medium tracking-wide uppercase">{{ menu.label }}</p> <ul class="mt-2 grid gap-1"> @for (group of menu.groups; track group.heading) { @for (item of group.items; track item.title) { <li> <a href="#" class="hover:bg-muted block rounded-md px-3 py-2 text-sm font-medium transition-colors" (click)="open.set(false)" >{{ item.title }}</a > </li> } } </ul> </div> } <ui-separator /> @for (link of plainLinks; track link.href) { <a [href]="link.href" class="hover:bg-muted rounded-md px-3 py-2 text-sm font-medium transition-colors" (click)="open.set(false)" >{{ link.label }}</a > } <ui-separator /> <div class="grid gap-2"> <button ui-button variant="outline">Sign in</button> <button ui-button>Book a demo</button> </div> </nav> </div> </div> </div> </header> `, }) export class UiHeaderMegaMenuComponent { protected readonly ArrowRight = ArrowRight protected readonly Layers = Layers protected readonly Menu = Menu protected readonly menus = menus protected readonly plainLinks = plainLinks readonly open = signal(false) readonly openMenu = signal<string | null>(null) toggleMenu(label: string): void { this.openMenu.set(this.openMenu() === label ? null : label) } triggerClass(label: string): string { return cn( 'hover:bg-muted hover:text-foreground focus-visible:ring-ring rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', this.openMenu() === label && 'bg-muted text-foreground', ) } }src/app/components/blocks/header/header-search-command.component.ts6.3 kB
// Navbar with inline search. Click the field or press ⌘K to open a command palette of // grouped results; Escape closes and the shortcut hint stays visible in the trigger. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, HostListener, signal } from '@angular/core' import { BarChart3, BookOpen, Compass, FileText, History, LucideAngularModule, Menu, Search, Users, type LucideIconData, } from 'lucide-angular' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCommandDialogComponent, UiCommandEmptyComponent, UiCommandGroupComponent, UiCommandInputComponent, UiCommandItemComponent, UiCommandListComponent, } from '@/ui/command/command.component' import { UiKbdComponent } from '@/ui/kbd/kbd.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, } from '@/ui/sheet/sheet.component' const links = [ { label: 'Product', href: '#product' }, { label: 'Pricing', href: '#pricing' }, { label: 'Docs', href: '#docs' }, ] interface SearchGroup { heading: string items: { icon: LucideIconData; label: string }[] } const groups: SearchGroup[] = [ { heading: 'Documentation', items: [ { icon: BookOpen, label: 'Connect a warehouse' }, { icon: FileText, label: 'Define your first metric' }, { icon: BarChart3, label: 'Publish a dashboard' }, ], }, { heading: 'Company', items: [ { icon: Users, label: 'Customer stories' }, { icon: History, label: 'Changelog' }, ], }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-header-search-command, [ui-header-search-command]', standalone: true, host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiButtonComponent, UiCommandDialogComponent, UiCommandEmptyComponent, UiCommandGroupComponent, UiCommandInputComponent, UiCommandItemComponent, UiCommandListComponent, UiKbdComponent, UiSeparatorComponent, UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, ], template: ` <header data-slot="header-search-command" class="border-border bg-background/90 border-b backdrop-blur"> <div class="mx-auto flex h-16 max-w-6xl items-center gap-4 px-6"> <a href="#top" class="flex shrink-0 items-center gap-2 font-semibold tracking-tight"> <lucide-icon [img]="Compass" class="text-primary size-5" /> Halden </a> <nav class="hidden items-center gap-1 md:flex" aria-label="Primary"> @for (link of links; track link.href) { <a [href]="link.href" class="text-muted-foreground hover:text-foreground hover:bg-muted focus-visible:ring-ring rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none" >{{ link.label }}</a > } </nav> <button type="button" class="border-border bg-muted/40 text-muted-foreground hover:bg-muted focus-visible:ring-ring ml-auto flex h-9 w-full max-w-64 items-center gap-2 rounded-md border px-3 text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none" (click)="open.set(true)" > <lucide-icon [img]="Search" class="size-4 shrink-0" /> <span class="truncate">Search docs…</span> <span ui-kbd class="ml-auto hidden sm:inline-flex">⌘K</span> </button> <ui-separator orientation="vertical" class="hidden h-5 md:block" /> <button ui-button size="sm" class="hidden shrink-0 sm:inline-flex">Start free</button> <div ui-sheet [open]="menuOpen()" (openChange)="menuOpen.set($event)"> <button ui-button variant="ghost" size="icon" class="shrink-0 md:hidden" aria-label="Open navigation menu" (click)="menuOpen.set(true)" > <lucide-icon [img]="Menu" class="size-5" /> </button> <div ui-sheet-content side="right" class="w-72"> <div ui-sheet-header> <h2 ui-sheet-title>Menu</h2> </div> <nav class="mt-6 grid gap-1" aria-label="Mobile"> @for (link of links; track link.href) { <a [href]="link.href" class="hover:bg-muted rounded-md px-3 py-2 text-sm font-medium transition-colors" (click)="menuOpen.set(false)" >{{ link.label }}</a > } </nav> <ui-separator class="my-5" /> <div class="grid gap-2"> <button ui-button variant="outline">Sign in</button> <button ui-button>Start free</button> </div> </div> </div> </div> <ui-command-dialog [open]="open()" (openChange)="open.set($event)" title="Search documentation" description="Search documentation, guides, and stories" > <ui-command-input placeholder="Search documentation, guides, and stories…" /> <ui-command-list> <ui-command-empty>No results.</ui-command-empty> @for (group of groups; track group.heading) { <ui-command-group [heading]="group.heading"> @for (item of group.items; track item.label) { <ui-command-item [value]="item.label" (select)="open.set(false)"> <lucide-icon [img]="item.icon" class="text-muted-foreground mr-2 size-4" /> {{ item.label }} </ui-command-item> } </ui-command-group> } </ui-command-list> </ui-command-dialog> </header> `, }) export class UiHeaderSearchCommandComponent { protected readonly Compass = Compass protected readonly Menu = Menu protected readonly Search = Search protected readonly links = links protected readonly groups = groups readonly open = signal(false) readonly menuOpen = signal(false) @HostListener('window:keydown', ['$event']) onKeydown(event: KeyboardEvent): void { if (event.key.toLowerCase() !== 'k' || !(event.metaKey || event.ctrlKey)) return event.preventDefault() this.open.set(!this.open()) } }src/app/components/blocks/header/header-transparent-scroll.component.ts4 kB
// Navbar that starts transparent over the hero and fades in a blurred, bordered surface // past the first scroll. Port of the React block 1:1 (starts unscrolled so server and first // client paint agree; the scroll listener is the only thing that swaps the surface). import { ChangeDetectionStrategy, Component, HostListener, signal } from '@angular/core' import { Layers, LucideAngularModule, Menu } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiButtonComponent } from '@/ui/button/button.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, } from '@/ui/sheet/sheet.component' const links = [ { label: 'Product', href: '#product' }, { label: 'Solutions', href: '#solutions' }, { label: 'Pricing', href: '#pricing' }, { label: 'Docs', href: '#docs' }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-header-transparent-scroll, [ui-header-transparent-scroll]', standalone: true, host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiButtonComponent, UiSeparatorComponent, UiSheetComponent, UiSheetContentComponent, UiSheetHeaderComponent, UiSheetTitleComponent, ], template: ` <header data-slot="header-transparent-scroll" [class]="headerClass"> <div class="mx-auto flex h-16 max-w-6xl items-center gap-6 px-6"> <a href="#top" class="flex items-center gap-2 font-semibold tracking-tight"> <lucide-icon [img]="Layers" class="text-primary size-5" /> Northwind </a> <nav class="hidden items-center gap-1 md:flex" aria-label="Primary"> @for (link of links; track link.href) { <a [href]="link.href" class="text-muted-foreground hover:text-foreground hover:bg-muted focus-visible:ring-ring rounded-md px-3 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none" >{{ link.label }}</a > } </nav> <div class="ml-auto hidden items-center gap-2 md:flex"> <button ui-button variant="ghost" size="sm">Sign in</button> <button ui-button size="sm">Start free</button> </div> <div ui-sheet [open]="open()" (openChange)="open.set($event)"> <button ui-button variant="ghost" size="icon" class="ml-auto md:hidden" aria-label="Open navigation menu" (click)="open.set(true)" > <lucide-icon [img]="Menu" class="size-5" /> </button> <div ui-sheet-content side="right" class="w-72"> <div ui-sheet-header> <h2 ui-sheet-title>Menu</h2> </div> <nav class="mt-6 grid gap-1" aria-label="Mobile"> @for (link of links; track link.href) { <a [href]="link.href" class="hover:bg-muted rounded-md px-3 py-2 text-sm font-medium transition-colors" (click)="open.set(false)" >{{ link.label }}</a > } </nav> <ui-separator class="my-5" /> <div class="grid gap-2"> <button ui-button variant="outline">Sign in</button> <button ui-button>Start free</button> </div> </div> </div> </div> </header> `, }) export class UiHeaderTransparentScrollComponent { protected readonly Layers = Layers protected readonly Menu = Menu protected readonly links = links readonly scrolled = signal(false) readonly open = signal(false) @HostListener('window:scroll') onScroll(): void { this.scrolled.set(window.scrollY > 24) } get headerClass(): string { return cn( 'fixed inset-x-0 top-0 z-40 transition-colors duration-200', this.scrolled() ? 'border-border bg-background/85 border-b backdrop-blur' : 'border-b border-transparent', ) } }src/app/components/blocks/header/index.ts0.5 kB
export { UiHeader01Component } from './header-01.component' export { UiHeaderAnnouncementStackComponent } from './header-announcement-stack.component' export { UiHeaderCenteredLogoComponent } from './header-centered-logo.component' export { UiHeaderFloatingPillComponent } from './header-floating-pill.component' export { UiHeaderMegaMenuComponent } from './header-mega-menu.component' export { UiHeaderSearchCommandComponent } from './header-search-command.component' export { UiHeaderTransparentScrollComponent } from './header-transparent-scroll.component'
Raw manifest:https://uipkge.dev/r/angular/header.json