morethanadiagnosis-hub/web/components/common/Header.tsx
Claude 9232ebe294
feat(web): complete Phase 1 - foundation components, layouts, and hooks
Implemented complete design system and foundational infrastructure:

**Design System Components:**
- Button (all variants: primary, secondary, ghost, danger)
- Input & Textarea (with validation and error states)
- Card (elevated, outlined, flat variants)
- Modal/Dialog (with focus trap and accessibility)
- Avatar (with fallback initials)
- Badge (all color variants)
- Form helpers (FormField, Checkbox, Select)
- Link component with Next.js integration
- Navigation (Header, Footer with responsive design)

**Layouts:**
- MainLayout (with Header/Footer for public pages)
- AuthLayout (minimal layout for auth flows)
- DashboardLayout (with sidebar navigation)

**Hooks & Utilities:**
- useAuth() - authentication state management
- useApi() - API calls with loading/error states
- useLocalStorage() - persistent state management
- apiClient - Axios instance with token refresh
- authStore - Zustand store for auth state

**Configuration:**
- Tailwind config with design tokens
- Dark mode support via CSS variables
- Global styles with accessibility focus
- WCAG 2.2 AA+ compliant focus indicators

All components follow accessibility best practices with proper ARIA labels,
keyboard navigation, and screen reader support.

Job ID: MTAD-IMPL-2025-11-18-CL
2025-11-18 01:02:05 +00:00

192 lines
6.6 KiB
TypeScript

'use client'
import React from 'react'
import { Link } from './Link'
import { Button } from './Button'
import { Avatar } from './Avatar'
export interface HeaderProps {
isAuthenticated?: boolean
userDisplayName?: string
onLogin?: () => void
onLogout?: () => void
}
export const Header = ({
isAuthenticated = false,
userDisplayName,
onLogin,
onLogout,
}: HeaderProps) => {
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false)
const navItems = [
{ label: 'Blog', href: '/blog' },
{ label: 'Forum', href: '/forum' },
{ label: 'Podcast', href: '/podcast' },
{ label: 'Resources', href: '/resources' },
{ label: 'Merch', href: '/merch' },
{ label: 'Tribute', href: '/tribute' },
]
return (
<header className="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" aria-label="Main navigation">
<div className="flex justify-between h-16">
{/* Logo */}
<div className="flex items-center">
<Link href="/" variant="neutral" className="no-underline">
<span className="text-xl font-bold text-primary-600 dark:text-primary-400">
MoreThanADiagnosis
</span>
</Link>
</div>
{/* Desktop Navigation */}
<div className="hidden md:flex md:items-center md:space-x-6">
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
variant="neutral"
className="text-gray-700 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white no-underline hover:underline"
>
{item.label}
</Link>
))}
</div>
{/* Auth Section */}
<div className="hidden md:flex md:items-center md:space-x-4">
{isAuthenticated ? (
<>
<Link href="/dashboard" variant="neutral" className="no-underline">
<div className="flex items-center space-x-2">
<Avatar
alt={userDisplayName || 'User'}
fallbackText={userDisplayName}
size="sm"
/>
<span className="text-gray-700 dark:text-gray-300">
{userDisplayName}
</span>
</div>
</Link>
<Button variant="ghost" size="sm" onClick={onLogout}>
Logout
</Button>
</>
) : (
<>
<Button variant="ghost" size="sm" onClick={onLogin}>
Login
</Button>
<Link href="/auth/signup">
<Button variant="primary" size="sm">
Sign Up
</Button>
</Link>
</>
)}
</div>
{/* Mobile menu button */}
<div className="flex items-center md:hidden">
<button
type="button"
className="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white p-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-expanded={mobileMenuOpen}
aria-label="Toggle mobile menu"
>
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{mobileMenuOpen ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
)}
</svg>
</button>
</div>
</div>
{/* Mobile menu */}
{mobileMenuOpen && (
<div className="md:hidden py-4 border-t border-gray-200 dark:border-gray-700">
<div className="space-y-2">
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
variant="neutral"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 rounded-md no-underline"
onClick={() => setMobileMenuOpen(false)}
>
{item.label}
</Link>
))}
<div className="border-t border-gray-200 dark:border-gray-700 pt-2 mt-2">
{isAuthenticated ? (
<>
<Link
href="/dashboard"
variant="neutral"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 rounded-md no-underline"
onClick={() => setMobileMenuOpen(false)}
>
Dashboard
</Link>
<button
className="w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 rounded-md"
onClick={() => {
onLogout?.()
setMobileMenuOpen(false)
}}
>
Logout
</button>
</>
) : (
<>
<button
className="w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 rounded-md"
onClick={() => {
onLogin?.()
setMobileMenuOpen(false)
}}
>
Login
</button>
<Link
href="/auth/signup"
variant="neutral"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 rounded-md no-underline"
onClick={() => setMobileMenuOpen(false)}
>
Sign Up
</Link>
</>
)}
</div>
</div>
</div>
)}
</nav>
</header>
)
}