Drops the /flash and /html5 sections (old URLs redirect to /browse), the GitHub star count endpoint, the unused HOME_RECENT_HOURS setting, upstream's design notes and code review docs, and the issue template's link to upstream discussions.
11 KiB
Layout Components
This document describes the layout components that form the structure of the Dotcom Arcade application.
Public pages use PortalLayout instead of AppShell; see
Discovery Portal. AppShell remains
for settings and admin pages.
Component Hierarchy
AppShell
├── Header
│ ├── Logo & Title
│ ├── Sidebar Toggle
│ ├── SearchBar (Desktop)
│ ├── PrimaryColorPicker
│ ├── ThemePicker
│ └── UserMenu / Login Buttons
├── SearchBar (Mobile)
└── Sidebar
├── Game Navigation
├── Library Navigation
├── Management Section (Admin only)
└── Dashboard & Settings
AppShell
Main application layout container that wraps all pages.
Location
D:\Repositories\Personal\flashpoint-web\frontend\src\components\layout\AppShell.tsx
Purpose
Provides the main application structure with header and sidebar navigation.
Props
interface AppShellProps {
children: ReactNode;
}
Implementation
export function AppShell({ children }: AppShellProps) {
const sidebarOpen = useUIStore((state) => state.sidebarOpen);
return (
<div className="flex flex-col h-screen bg-background text-foreground">
<Header />
<div className="flex flex-1 overflow-hidden">
<Sidebar isOpen={sidebarOpen} />
<main className="flex-1 overflow-auto bg-background pt-6">
{children}
</main>
</div>
</div>
);
}
Features
- Fixed Height Layout - Uses
h-screento fill viewport - Flexbox Structure - Column layout with header and content
- Overflow Management - Prevents content from overflowing viewport
- Theme Support - Uses Tailwind theme variables
Usage
// In App.tsx
<Route path="/*" element={
<AppShell>
<Routes>
{/* Application routes */}
</Routes>
</AppShell>
} />
Header
Top navigation bar with search, theme controls, and user menu.
Location
D:\Repositories\Personal\flashpoint-web\frontend\src\components\layout\Header.tsx
Features
-
Logo and Branding
- Flashpoint logo
- Application title (hidden on mobile when sidebar is collapsed)
- Link to home page
-
Sidebar Toggle
- Hamburger menu button
- Toggles sidebar width between collapsed and expanded
-
Search Bar
- Desktop: Center of header
- Mobile: Below main header
-
Theme Controls
- PrimaryColorPicker - 21 color options
- ThemePicker - Light/Dark/System modes
-
User Menu
- Guest Mode: Shows "Guest" badge + Login button
- Authenticated: Avatar dropdown with user info and logout
Props
No props - reads state from Zustand stores.
Implementation Details
Logo and Title
<Link
to="/"
className="flex items-center hover:opacity-80 transition-opacity min-w-0 ml-4"
title="Flashpoint Archive - Home"
>
<img
src="/images/logo.png"
alt="Flashpoint Archive"
className="h-8 w-auto object-contain flex-shrink-0"
/>
<div className={cn(
"overflow-hidden transition-all duration-500 hidden sm:block",
sidebarCollapsed ? "max-w-0 opacity-0" : "max-w-xs opacity-100"
)}>
<span className="text-xl font-bold whitespace-nowrap">
Flashpoint Archive
</span>
</div>
</Link>
Sidebar Toggle
<Button
variant="ghost"
size="icon"
onClick={toggleSidebarCollapsed}
aria-label="Toggle sidebar"
title="Toggle sidebar width"
>
<Menu size={20} />
</Button>
Responsive Search
{/* Desktop Search */}
<div className="flex-1 max-w-2xl mx-auto hidden md:block">
<SearchBar />
</div>
{/* Mobile Search */}
<div className="md:hidden px-4 pb-3">
<SearchBar />
</div>
User Menu (Authenticated)
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="flex items-center gap-2">
<Avatar className="h-8 w-8">
<AvatarFallback className="text-xs">
{getUserInitials(user.username)}
</AvatarFallback>
</Avatar>
<span className="text-sm hidden sm:block">{user.username}</span>
<Badge variant={getRoleBadgeVariant(user.role)} className="text-xs">
{user.role}
</Badge>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuLabel>
<div>
<p className="text-sm font-medium">{user.username}</p>
<p className="text-xs text-muted-foreground">{user.email}</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout}>
<LogOut className="mr-2 h-4 w-4" />
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
User Menu (Guest)
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-muted">
<User size={16} className="text-muted-foreground" />
<span className="text-sm hidden sm:block">{user.username}</span>
<Badge variant="outline" className="text-xs">
{user.role}
</Badge>
</div>
<Button asChild size="sm">
<Link to="/login">Login</Link>
</Button>
</div>
Responsive Behavior
-
Mobile (< 768px):
- Title hidden when sidebar collapsed
- Search bar moves below header
- User info condensed
-
Desktop (>= 768px):
- Full title visible
- Search bar in center of header
- Full user menu with dropdown
Sidebar
Collapsible navigation sidebar with route highlighting.
Location
D:\Repositories\Personal\flashpoint-web\frontend\src\components\layout\Sidebar.tsx
Props
interface SidebarProps {
isOpen: boolean;
}
Features
-
Collapsible Width
- Expanded: 256px (w-64)
- Collapsed: 64px (w-16)
- Smooth transition with 500ms duration
-
Mobile Overlay
- Fixed positioning on mobile
- Backdrop overlay with blur
- Swipe gesture support (swipe left to close)
-
Route Highlighting
- Active route highlighted with primary color
- Uses React Router location
-
Permission-Based Navigation
- Guest users see limited navigation
- Admin sections hidden for non-admins
- RoleGuard component for permission checks
-
Scroll Support
- ScrollArea component for overflow
- Maintains scroll position
Navigation Structure
Game Navigation
const gameNavItems: NavItem[] = [
{ path: '/', icon: Home, label: 'Home' },
{ path: '/animations', icon: Film, label: 'Animations' },
{ path: '/browse', icon: Gamepad2, label: 'Browse' },
];
Library Navigation (Hidden for Guests)
const libraryNavItems: NavItem[] = [
{ path: '/favorites', icon: Heart, label: 'Favorites' },
{ path: '/playlists', icon: ListIcon, label: 'Playlists' },
];
Management Section (Admin Only)
<RoleGuard permissions={['users.read', 'roles.read', 'activities.read']}>
<div className="space-y-1">
<RoleGuard permission="users.read">
<Link to="/users">Users</Link>
</RoleGuard>
<RoleGuard permission="roles.read">
<Link to="/roles">Roles</Link>
</RoleGuard>
<RoleGuard permission="activities.read">
<Link to="/activities">Activity Logs</Link>
</RoleGuard>
</div>
</RoleGuard>
Bottom Section (Dashboard & Settings)
<div className="border-t p-4">
<Link to="/dashboard">
<BarChart3 size={20} />
Dashboard
</Link>
<Link to="/settings">
<Settings size={20} />
Settings
</Link>
</div>
Active Route Detection
const isNavItemActive = (item: NavItem) => {
const itemPath = item.path.split('?')[0];
const itemQuery = item.path.split('?')[1];
if (itemQuery) {
// Exact match including query params
return (
location.pathname === itemPath && location.search === `?${itemQuery}`
);
}
return location.pathname === itemPath;
};
Mobile Behavior
Swipe Gesture
const sidebarRef = useSwipeGesture<HTMLElement>({
onSwipeLeft: () => {
if (window.innerWidth < 1024) {
setSidebarOpen(false);
}
},
minSwipeDistance: 50,
});
Auto-Close on Navigation
useEffect(() => {
if (window.innerWidth < 1024) {
setSidebarOpen(false);
}
}, [location.pathname, setSidebarOpen]);
Backdrop Overlay
{isOpen && (
<div
className="fixed inset-0 bg-black/50 z-30 lg:hidden backdrop-blur-sm"
onClick={handleBackdropClick}
aria-hidden="true"
/>
)}
Responsive CSS Classes
<aside
className={cn(
'bg-card border-r flex flex-col overflow-hidden',
sidebarCollapsed ? 'w-16' : 'w-64',
// Mobile: fixed positioning with slide-in
'lg:relative fixed inset-y-0 left-0 z-40',
'transition-all duration-500 ease-in-out',
'lg:translate-x-0', // Always visible on desktop
isOpen ? 'translate-x-0' : '-translate-x-full' // Slide on mobile
)}
>
Collapsed State Behavior
When sidebarCollapsed is true:
- Width: 64px (w-16)
- Icons only (no text)
- Tooltips on hover (via
titleattribute) - Centered icons
- Management section label hidden
Layout State Management
Layout state is managed via the useUIStore Zustand store:
interface UIState {
sidebarOpen: boolean;
sidebarCollapsed: boolean;
setSidebarOpen: (open: boolean) => void;
toggleSidebarOpen: () => void;
setSidebarCollapsed: (collapsed: boolean) => void;
toggleSidebarCollapsed: () => void;
}
State Persistence
UI state is persisted to localStorage:
persist(
(set) => ({
/* ... */
}),
{
name: 'flashpoint-ui-state',
partialize: (state) => ({
sidebarCollapsed: state.sidebarCollapsed,
// sidebarOpen is intentionally not persisted (session-only)
}),
}
);
Styling and Theming
Tailwind Classes
Layout components use Tailwind utility classes:
bg-card- Card background color (theme-aware)border-border- Border color (theme-aware)text-foreground- Text color (theme-aware)bg-primary/10- Primary color with opacity
Custom CSS Variables
Applied via ThemeStore:
--primary- Primary theme color--background- Background color--foreground- Text color--card- Card background--muted- Muted text color
Accessibility
ARIA Labels
<Button
aria-label="Toggle sidebar"
title="Toggle sidebar width"
>
<Menu />
</Button>
Keyboard Navigation
- Tab navigation works across all interactive elements
- Sidebar links are keyboard accessible
- Dropdowns support arrow key navigation
Screen Readers
- Proper semantic HTML (
<header>,<nav>,<main>,<aside>) - ARIA attributes on interactive elements
- Hidden decorative elements with
aria-hidden="true"