feat(frontend): Implement navigation and layout (#44)
Implements the main navigation and layout structure: - Sidebar component with collapsible navigation and keyboard shortcut - AppHeader with project switcher and user menu - AppBreadcrumbs with auto-generation from pathname - ProjectSwitcher dropdown for quick project navigation - UserMenu with profile, settings, and logout - AppLayout component combining all layout elements Features: - Responsive design (mobile sidebar sheet, desktop sidebar) - Keyboard navigation (Cmd/Ctrl+B to toggle sidebar) - Dark mode support - WCAG AA accessible (ARIA labels, focus management) All 125 tests passing. Follows design system guidelines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
100
frontend/src/components/layout/AppHeader.tsx
Normal file
100
frontend/src/components/layout/AppHeader.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Application Header Component
|
||||
* Top header bar for the main application layout
|
||||
* Includes logo, project switcher, and user menu
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { Link } from '@/lib/i18n/routing';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ThemeToggle } from '@/components/theme';
|
||||
import { LocaleSwitcher } from '@/components/i18n';
|
||||
import { ProjectSwitcher } from './ProjectSwitcher';
|
||||
import { UserMenu } from './UserMenu';
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface AppHeaderProps {
|
||||
/** Currently selected project */
|
||||
currentProject?: Project;
|
||||
/** List of available projects */
|
||||
projects?: Project[];
|
||||
/** Callback when project is changed */
|
||||
onProjectChange?: (projectSlug: string) => void;
|
||||
/** Additional className */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function AppHeader({
|
||||
currentProject,
|
||||
projects = [],
|
||||
onProjectChange,
|
||||
className,
|
||||
}: AppHeaderProps) {
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
'sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60',
|
||||
className
|
||||
)}
|
||||
data-testid="app-header"
|
||||
>
|
||||
<div className="flex h-14 items-center px-4 lg:px-6">
|
||||
{/* Left side - Logo and Project Switcher */}
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Logo - visible on mobile, hidden on desktop when sidebar is visible */}
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center gap-2 lg:hidden"
|
||||
aria-label="Syndarix home"
|
||||
>
|
||||
<Image
|
||||
src="/logo-icon.svg"
|
||||
alt=""
|
||||
width={28}
|
||||
height={28}
|
||||
className="h-7 w-7"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="font-semibold text-foreground">Syndarix</span>
|
||||
</Link>
|
||||
|
||||
{/* Project Switcher */}
|
||||
{projects.length > 0 && (
|
||||
<div className="hidden sm:block">
|
||||
<ProjectSwitcher
|
||||
currentProject={currentProject}
|
||||
projects={projects}
|
||||
onProjectChange={onProjectChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right side - Actions */}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<ThemeToggle />
|
||||
<LocaleSwitcher />
|
||||
<UserMenu />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Project Switcher */}
|
||||
{projects.length > 0 && (
|
||||
<div className="border-t px-4 py-2 sm:hidden">
|
||||
<ProjectSwitcher
|
||||
currentProject={currentProject}
|
||||
projects={projects}
|
||||
onProjectChange={onProjectChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user