diff --git a/src/components/public/SiteHeader.tsx b/src/components/public/SiteHeader.tsx new file mode 100644 index 0000000..aa772c5 --- /dev/null +++ b/src/components/public/SiteHeader.tsx @@ -0,0 +1,263 @@ +'use client' +import { useState, useEffect, useRef, useCallback } from 'react' +import Link from 'next/link' +import Image from 'next/image' +import { Menu, X, ChevronDown, Phone, Mail } from 'lucide-react' +import { useSettingsStore } from '@/store/settingsStore' +import { useNavigationStore } from '@/store/navigationStore' +import clsx from 'clsx' + +export default function SiteHeader() { + const { settings } = useSettingsStore() + const { items } = useNavigationStore() + const [mobileOpen, setMobileOpen] = useState(false) + const [scrolled, setScrolled] = useState(false) + const mobileMenuRef = useRef(null) + const menuButtonRef = useRef(null) + + const visibleItems = items + .filter((i) => i.isVisible && i.parentId === null) + .sort((a, b) => a.order - b.order) + + useEffect(() => { + const handleScroll = () => setScrolled(window.scrollY > 10) + window.addEventListener('scroll', handleScroll, { passive: true }) + return () => window.removeEventListener('scroll', handleScroll) + }, []) + + useEffect(() => { + if (!mobileOpen) return + const handleKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setMobileOpen(false) + menuButtonRef.current?.focus() + } + } + document.addEventListener('keydown', handleKey) + return () => document.removeEventListener('keydown', handleKey) + }, [mobileOpen]) + + useEffect(() => { + settings.applyThemeVars?.() + }, [settings]) + + const closeMobile = useCallback(() => { + setMobileOpen(false) + menuButtonRef.current?.focus() + }, []) + + const headerStyle = { + backgroundColor: settings.headerBgColor, + color: settings.headerTextColor, + position: settings.headerPosition as 'sticky' | 'static' | 'fixed', + } + + return ( + <> + {/* Top Bar */} + {settings.showTopBar && settings.topBarText && ( +
+ {settings.topBarText} +
+ )} + + {/* Tricolor stripe */} +