forked from cardosofelipe/fast-next-template
- Refactored JSX elements to improve readability by collapsing multi-line props and attributes into single lines if their length permits. - Improved consistency in component imports by grouping and consolidating them. - No functional changes, purely restructuring for clarity and maintainability.
156 lines
3.9 KiB
TypeScript
156 lines
3.9 KiB
TypeScript
/**
|
|
* Application Layout Component
|
|
* Main layout wrapper that combines sidebar, header, breadcrumbs, and content area
|
|
* Provides the standard application shell for authenticated pages
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { ReactNode } from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
import { Sidebar } from './Sidebar';
|
|
import { AppHeader } from './AppHeader';
|
|
import { AppBreadcrumbs, type BreadcrumbItem } from './AppBreadcrumbs';
|
|
|
|
interface Project {
|
|
id: string;
|
|
slug: string;
|
|
name: string;
|
|
}
|
|
|
|
interface AppLayoutProps {
|
|
/** Page content */
|
|
children: ReactNode;
|
|
/** Current project (for project-specific pages) */
|
|
currentProject?: Project;
|
|
/** List of available projects */
|
|
projects?: Project[];
|
|
/** Callback when project is changed */
|
|
onProjectChange?: (projectSlug: string) => void;
|
|
/** Custom breadcrumb items (overrides auto-generation) */
|
|
breadcrumbs?: BreadcrumbItem[];
|
|
/** Hide breadcrumbs */
|
|
hideBreadcrumbs?: boolean;
|
|
/** Hide sidebar */
|
|
hideSidebar?: boolean;
|
|
/** Additional className for main content area */
|
|
className?: string;
|
|
/** Additional className for the outer wrapper */
|
|
wrapperClassName?: string;
|
|
}
|
|
|
|
export function AppLayout({
|
|
children,
|
|
currentProject,
|
|
projects = [],
|
|
onProjectChange,
|
|
breadcrumbs,
|
|
hideBreadcrumbs = false,
|
|
hideSidebar = false,
|
|
className,
|
|
wrapperClassName,
|
|
}: AppLayoutProps) {
|
|
return (
|
|
<div
|
|
className={cn('flex min-h-screen flex-col bg-background', wrapperClassName)}
|
|
data-testid="app-layout"
|
|
>
|
|
{/* Header */}
|
|
<AppHeader
|
|
currentProject={currentProject}
|
|
projects={projects}
|
|
onProjectChange={onProjectChange}
|
|
/>
|
|
|
|
{/* Main content area with sidebar */}
|
|
<div className="flex flex-1">
|
|
{/* Sidebar */}
|
|
{!hideSidebar && <Sidebar projectSlug={currentProject?.slug} />}
|
|
|
|
{/* Content area */}
|
|
<div className="flex flex-1 flex-col">
|
|
{/* Breadcrumbs */}
|
|
{!hideBreadcrumbs && <AppBreadcrumbs items={breadcrumbs} />}
|
|
|
|
{/* Main content */}
|
|
<main className={cn('flex-1', className)} id="main-content" tabIndex={-1}>
|
|
{children}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Page Container Component
|
|
* Standard container for page content with consistent padding and max-width
|
|
*/
|
|
interface PageContainerProps {
|
|
/** Page content */
|
|
children: ReactNode;
|
|
/** Maximum width constraint */
|
|
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '4xl' | '6xl' | 'full';
|
|
/** Additional className */
|
|
className?: string;
|
|
}
|
|
|
|
const maxWidthClasses: Record<string, string> = {
|
|
sm: 'max-w-sm',
|
|
md: 'max-w-md',
|
|
lg: 'max-w-lg',
|
|
xl: 'max-w-xl',
|
|
'2xl': 'max-w-2xl',
|
|
'4xl': 'max-w-4xl',
|
|
'6xl': 'max-w-6xl',
|
|
full: 'max-w-full',
|
|
};
|
|
|
|
export function PageContainer({ children, maxWidth = '6xl', className }: PageContainerProps) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'container mx-auto px-4 py-6 lg:px-6 lg:py-8',
|
|
maxWidthClasses[maxWidth],
|
|
className
|
|
)}
|
|
data-testid="page-container"
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Page Header Component
|
|
* Consistent header for page titles and actions
|
|
*/
|
|
interface PageHeaderProps {
|
|
/** Page title */
|
|
title: string;
|
|
/** Optional description */
|
|
description?: string;
|
|
/** Action buttons or other content */
|
|
actions?: ReactNode;
|
|
/** Additional className */
|
|
className?: string;
|
|
}
|
|
|
|
export function PageHeader({ title, description, actions, className }: PageHeaderProps) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between',
|
|
className
|
|
)}
|
|
data-testid="page-header"
|
|
>
|
|
<div className="space-y-1">
|
|
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">{title}</h1>
|
|
{description && <p className="text-muted-foreground">{description}</p>}
|
|
</div>
|
|
{actions && <div className="flex items-center gap-2">{actions}</div>}
|
|
</div>
|
|
);
|
|
}
|