--- /** * Header Component * Flexible navigation header with variant-based configuration * * Variants: * - layout: 'default' | 'centered' | 'minimal' * - position: 'fixed' | 'sticky' | 'static' * - size: 'sm' | 'md' | 'lg' * - variant: 'default' | 'solid' | 'transparent' * - colorScheme: 'default' | 'invert' (use 'invert' for dark backgrounds) * - shape: 'bar' | 'floating' (use 'floating' for capsule header) * * Features: * - Dynamic navigation from nav.config.ts (default) or custom nav prop * - Optional CTA button with customization * - Mobile menu with Escape key support * - Theme toggle * - GitHub/action buttons * - Full slot support for customization * - Inverted color scheme for use on dark/image backgrounds * - Floating capsule shape with scroll-reactive bg + color flip */ import type { HTMLAttributes } from 'astro/types'; import { cn } from '@/lib/cn'; import { getNavItems, type NavItem as NavConfigItem } from '@/config/nav.config'; import { headerVariants, headerInnerVariants } from './header.variants'; import Button from '@/components/ui/form/Button/Button.astro'; import Icon from '@/components/ui/primitives/Icon/Icon.astro'; import Logo from '@/components/ui/marketing/Logo/Logo.astro'; import ThemeToggle from '@/components/layout/ThemeToggle.astro'; import ThemeSelector from '@/components/layout/ThemeSelector.astro'; import ThemeSelectorDropdown from '@/components/layout/ThemeSelectorDropdown.astro'; import LanguageSwitcherDropdown from '@/components/layout/LanguageSwitcherDropdown.astro'; import siteConfig from '@/config/site.config'; import type { Locale } from '@/i18n/ui'; export interface NavItem { label: string; href: string; } export interface HeaderAction { icon: string; href: string; label: string; iconOnly?: boolean; target?: string; } interface Props extends HTMLAttributes<'header'> { /** Layout style: default (logo left, nav right), centered (logo center), minimal (logo + cta only) */ layout?: 'default' | 'centered' | 'minimal'; /** Position behavior */ position?: 'fixed' | 'sticky' | 'static'; /** Header height */ size?: 'sm' | 'md' | 'lg'; /** Background variant */ variant?: 'default' | 'solid' | 'transparent'; /** Color scheme for text/icons - use 'invert' for dark backgrounds */ colorScheme?: 'default' | 'invert'; /** Shape: 'bar' (full-width, default) or 'floating' (centered capsule) */ shape?: 'bar' | 'floating'; /** Override default navigation (replaces getNavRoutes()) */ nav?: NavItem[]; /** Additional navigation items (e.g., #features for landing pages) */ extraNav?: NavItem[]; /** Show CTA button */ showCta?: boolean; /** CTA button configuration */ cta?: { label?: string; href?: string; icon?: string }; /** Action buttons (GitHub, etc.) */ actions?: HeaderAction[]; /** Show theme toggle (default: true) */ showThemeToggle?: boolean; /** Show colour-theme selector swatches */ showThemeSelector?: boolean; /** Show mobile menu (default: true) */ showMobileMenu?: boolean; /** Show active state for current page (default: true) */ showActiveState?: boolean; /** Logo text override */ logoText?: string; /** Hide logo entirely */ hideLogo?: boolean; /** Show language switcher */ showLanguageSwitcher?: boolean; /** Current locale for language switcher */ currentLocale?: Locale; /** Show social icon links (desktop/tablet only, reads from siteConfig.socialLinks) */ showSocialLinks?: boolean; /** Show scroll progress bar at the bottom of the header */ showScrollProgress?: boolean; /** Position of the scroll progress bar: 'top' (above header) or 'bottom' (below header, default) */ scrollProgressPosition?: 'top' | 'bottom'; } const { layout = 'default', position = 'fixed', size = 'lg', variant = 'solid', colorScheme = 'default', shape = 'bar', nav, extraNav = [], showCta = false, cta = { label: 'Start a project', href: '/contact' }, actions = [], showThemeToggle = true, showThemeSelector = false, showMobileMenu = true, showSocialLinks = false, showActiveState = true, showScrollProgress = false, scrollProgressPosition = 'bottom', showLanguageSwitcher = false, currentLocale = 'de', logoText, hideLogo = false, class: className, ...attrs } = Astro.props; // Shape + color scheme helpers const isFloating = shape === 'floating'; const isInvert = colorScheme === 'invert'; // Get navigation items const defaultNav = getNavItems().map((item: NavConfigItem) => ({ label: item.label, href: item.href, })); const navItems: NavItem[] = nav || [...extraNav, ...defaultNav]; // Current path for active state const currentPath = Astro.url.pathname; // Check if we're on the landing page const isLandingPage = currentPath === '/'; // Process CTA href for landing page anchor links const ctaHref = cta.href?.startsWith('#') && !isLandingPage ? `/${cta.href}` : cta.href; // Check slots const hasLogoSlot = Astro.slots.has('logo'); const hasNavSlot = Astro.slots.has('nav'); const hasActionsSlot = Astro.slots.has('actions'); const hasMobileMenuSlot = Astro.slots.has('mobile-menu'); // Compute header classes const headerClasses = cn( headerVariants({ position, variant, shape }), isInvert && !isFloating && 'invert-section', className ); // Compute inner container classes const innerClasses = headerInnerVariants({ size, shape }); // Check if a nav item is active function isActive(href: string): boolean { if (!showActiveState) return false; if (href.startsWith('#')) return false; return currentPath === href || currentPath.startsWith(href + '/'); } // Map a social URL to its icon name + accessible label function getSocialIconData(url: string): { icon: string; label: string } { if (url.includes('github.com')) return { icon: 'github', label: 'GitHub' }; if (url.includes('instagram.com')) return { icon: 'instagram', label: 'Instagram' }; if (url.includes('x.com') || url.includes('twitter.com')) return { icon: 'x-twitter', label: 'X' }; if (url.includes('linkedin.com')) return { icon: 'linkedin', label: 'LinkedIn' }; if (url.includes('bsky.app')) return { icon: 'bluesky', label: 'Bluesky' }; return { icon: 'link', label: 'Social' }; } // Generate unique ID for this header instance const menuId = `mobile-menu-${Math.random().toString(36).slice(2, 9)}`; const buttonId = `${menuId}-button`; ---
{/* Logo */} { !hideLogo && (hasLogoSlot ? ( ) : ( )) } {/* Desktop Navigation */} { layout !== 'minimal' && (hasNavSlot ? ( ) : ( )) } {/* Actions Area */}
{ hasActionsSlot ? ( ) : ( <> {showThemeToggle && ( )} {showThemeSelector && ( )} {showLanguageSwitcher && ( )} {showSocialLinks && siteConfig.socialLinks.length > 0 && ( )} {actions.map((action) => ( ))} {showCta && ( )} ) } {/* Mobile Menu Toggle */} { showMobileMenu && layout !== 'minimal' && ( ) }
{/* Scroll Progress Bar */} {showScrollProgress && (
{/* Mobile Menu Backdrop - positioned outside header to blur page content */} { showMobileMenu && layout !== 'minimal' && (