Menu

Change language

GitHub repo

Header — Mega Menu

blockmarketing

Navbar whose product and resources items open a full-width mega panel of grouped, described links plus a highlighted feature card, keyboard navigable and closing on Escape.

Installation

$npx uipkge-ng@latest add header-mega-menu
Manifest:https://uipkge.dev/r/angular/header-mega-menu.jsonInstalls to:src/app/components/blocks/header-mega-menu/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

MegaMenuLink
interface MegaMenuLink {
  icon: LucideIconData
  title: string
  body: string
}
MegaMenuGroup
interface MegaMenuGroup {
  heading: string
  items: MegaMenuLink[]
}
MegaMenuHighlight
interface MegaMenuHighlight {
  title: string
  body: string
  cta: string
}
MegaMenu
interface MegaMenu {
  label: string
  groups: MegaMenuGroup[]
  highlight: MegaMenuHighlight
}
MegaMenuPlainLink
interface MegaMenuPlainLink {
  label: string
  href: string
}

Files installed (2)

  • src/app/components/blocks/header-mega-menu/header-mega-menu.component.ts10.5 kB
    // Navbar whose product and resources items open a full-width mega panel of grouped,
    // described links plus a highlighted feature card — keyboard navigable, closing on
    // Escape. Port of the Vue/React block 1:1: panel columns derive from the `menus`
    // array, so a new group needs no extra markup.
    import { ChangeDetectionStrategy, Component, Input, 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 {
      UiNavigationMenuComponent,
      UiNavigationMenuContentComponent,
      UiNavigationMenuItemComponent,
      UiNavigationMenuListComponent,
      UiNavigationMenuTriggerComponent,
      navigationMenuTriggerStyle,
    } from '@/ui/navigation-menu/navigation-menu.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import {
      UiSheetCloseComponent,
      UiSheetComponent,
      UiSheetContentComponent,
      UiSheetHeaderComponent,
      UiSheetTitleComponent,
    } from '@/ui/sheet/sheet.component'
    
    export interface MegaMenuLink {
      icon: LucideIconData
      title: string
      body: string
    }
    
    export interface MegaMenuGroup {
      heading: string
      items: MegaMenuLink[]
    }
    
    export interface MegaMenuHighlight {
      title: string
      body: string
      cta: string
    }
    
    export interface MegaMenu {
      label: string
      groups: MegaMenuGroup[]
      highlight: MegaMenuHighlight
    }
    
    export interface MegaMenuPlainLink {
      label: string
      href: 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: MegaMenuPlainLink[] = [
      { label: 'Pricing', href: '#pricing' },
      { label: 'Customers', href: '#customers' },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-header-mega-menu, [ui-header-mega-menu]',
      standalone: true,
      // The block renders its own <header>: keep this wrapper out of the layout.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiButtonComponent,
        UiNavigationMenuComponent,
        UiNavigationMenuContentComponent,
        UiNavigationMenuItemComponent,
        UiNavigationMenuListComponent,
        UiNavigationMenuTriggerComponent,
        UiSeparatorComponent,
        UiSheetCloseComponent,
        UiSheetComponent,
        UiSheetContentComponent,
        UiSheetHeaderComponent,
        UiSheetTitleComponent,
      ],
      template: `
        <header data-slot="header-mega-menu" [class]="rootClass">
          <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 ui-navigation-menu class="hidden lg:flex">
              <ui-navigation-menu-list>
                @for (menu of megaMenus; track menu.label) {
                  <li ui-navigation-menu-item [value]="menu.label">
                    <button ui-navigation-menu-trigger>{{ menu.label }}</button>
                    <ui-navigation-menu-content>
                      <div class="grid w-[46rem] grid-cols-[1fr_1fr_16rem] gap-6 p-6">
                        @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>
                        }
    
                        <!-- Highlight rail: one promoted destination per panel, on the
                             muted surface so it reads as an aside, not a sixth link. -->
                        <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>
                    </ui-navigation-menu-content>
                  </li>
                }
    
                @for (link of plain; track link.href) {
                  <li ui-navigation-menu-item [value]="link.label">
                    <a [href]="link.href" [class]="plainLinkClass">{{ link.label }}</a>
                  </li>
                }
              </ui-navigation-menu-list>
            </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>
              <div ui-separator orientation="vertical" class="hidden h-5 sm:block"></div>
              <button ui-button size="sm">Book a demo</button>
            </div>
    
            <div ui-sheet [open]="mobileOpen()" (openChange)="mobileOpen.set($event)">
              <button
                ui-button
                variant="ghost"
                size="icon"
                class="ml-auto lg:hidden"
                aria-label="Open navigation menu"
                (click)="mobileOpen.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 megaMenus; 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)="mobileOpen.set(false)"
                              >
                                {{ item.title }}
                              </a>
                            </li>
                          }
                        }
                      </ul>
                    </div>
                  }
                  <div ui-separator></div>
                  @for (link of plain; track link.href) {
                    <a
                      [href]="link.href"
                      class="hover:bg-muted rounded-md px-3 py-2 text-sm font-medium transition-colors"
                      (click)="mobileOpen.set(false)"
                    >
                      {{ link.label }}
                    </a>
                  }
                  <div ui-separator></div>
                  <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 megaMenus = menus
      protected readonly plain = plainLinks
      protected readonly plainLinkClass = navigationMenuTriggerStyle()
    
      @Input('class') className?: string
    
      readonly mobileOpen = signal(false)
    
      get rootClass(): string {
        return cn('border-border bg-background/90 border-b backdrop-blur', this.className)
      }
    }
    
  • src/app/components/blocks/header-mega-menu/index.ts0.2 kB

Raw manifest:https://uipkge.dev/r/angular/header-mega-menu.json