Menu

Change language

GitHub repo

Header — Transparent Over Hero

blockmarketing

Marketing navbar that starts transparent over the hero and fades in a bordered, blurred surface once the page scrolls, with a nav row, theme-neutral CTA pair, and a mobile sheet.

Installation

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

Variants

Loading interactive previews…

Schema

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

HeaderTransparentScrollLink
interface HeaderTransparentScrollLink {
  label: string
  href: string
}

npm dependencies

Files installed (2)

  • src/app/components/blocks/header-transparent-scroll/header-transparent-scroll.component.ts5.2 kB
    // Marketing navbar that starts transparent over the hero and fades in a bordered,
    // blurred surface once the page scrolls, with a nav row, theme-neutral CTA pair,
    // and a mobile sheet. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, signal } from '@angular/core'
    import { Layers, LucideAngularModule, Menu, X } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import { UiSheetCloseComponent, UiSheetComponent, UiSheetContentComponent } from '@/ui/sheet/sheet.component'
    
    export interface HeaderTransparentScrollLink {
      label: string
      href: string
    }
    
    const links: HeaderTransparentScrollLink[] = [
      { 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,
      // React renders the root <header> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiButtonComponent,
        UiSeparatorComponent,
        UiSheetCloseComponent,
        UiSheetComponent,
        UiSheetContentComponent,
      ],
      template: `
        <header data-slot="header-transparent-scroll" [class]="rootClass">
          <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 navLinks; 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]="mobileOpen()" (openChange)="mobileOpen.set($event)">
              <button
                ui-button
                variant="ghost"
                size="icon"
                class="ml-auto md: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">
                <div class="flex h-full flex-col">
                  <div class="flex items-center justify-between border-b px-4 py-3">
                    <span class="text-sm font-semibold">Menu</span>
                    <button ui-button ui-sheet-close variant="ghost" size="icon" class="size-8" aria-label="Close menu">
                      <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 navLinks; 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>
                    }
                  </nav>
                  <div ui-separator class="my-5"></div>
                  <div class="flex flex-col gap-2 p-4 pt-0">
                    <button ui-button variant="outline" class="w-full">Sign in</button>
                    <button ui-button class="w-full">Start free</button>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </header>
      `,
    })
    export class UiHeaderTransparentScrollComponent implements OnInit, OnDestroy {
      protected readonly Layers = Layers
      protected readonly Menu = Menu
      protected readonly X = X
    
      protected readonly navLinks = links
    
      @Input('class') className?: string
    
      readonly scrolled = signal(false)
      readonly mobileOpen = signal(false)
    
      private readonly onScroll = (): void => {
        this.scrolled.set(window.scrollY > 24)
      }
    
      ngOnInit(): void {
        // Starts unscrolled so server and first client paint agree; the listener is
        // the only thing that swaps in the solid surface.
        if (typeof window === 'undefined') return
        this.onScroll()
        window.addEventListener('scroll', this.onScroll, { passive: true })
      }
    
      ngOnDestroy(): void {
        if (typeof window === 'undefined') return
        window.removeEventListener('scroll', this.onScroll)
      }
    
      get rootClass(): 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',
          this.className,
        )
      }
    
      closeMobile(): void {
        this.mobileOpen.set(false)
      }
    }
    
  • src/app/components/blocks/header-transparent-scroll/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/header-transparent-scroll.json