ADD new frontend
This commit is contained in:
200
frontend/src/components/layout/PublicLayout/PublicLayout.tsx
Normal file
200
frontend/src/components/layout/PublicLayout/PublicLayout.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useTheme } from '../../../contexts/ThemeContext';
|
||||
import { PublicHeader } from '../PublicHeader';
|
||||
import { Footer } from '../Footer';
|
||||
|
||||
export interface PublicLayoutProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
/**
|
||||
* Control header visibility
|
||||
*/
|
||||
showHeader?: boolean;
|
||||
/**
|
||||
* Control footer visibility
|
||||
*/
|
||||
showFooter?: boolean;
|
||||
/**
|
||||
* Header configuration
|
||||
*/
|
||||
headerProps?: {
|
||||
showThemeToggle?: boolean;
|
||||
showAuthButtons?: boolean;
|
||||
navigationItems?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
href: string;
|
||||
external?: boolean;
|
||||
}>;
|
||||
variant?: 'default' | 'transparent' | 'minimal';
|
||||
logo?: React.ReactNode;
|
||||
};
|
||||
/**
|
||||
* Footer configuration
|
||||
*/
|
||||
footerProps?: {
|
||||
compact?: boolean;
|
||||
showPrivacyLinks?: boolean;
|
||||
showThemeToggle?: boolean;
|
||||
showVersion?: boolean;
|
||||
};
|
||||
/**
|
||||
* Layout variant
|
||||
*/
|
||||
variant?: 'default' | 'centered' | 'full-width';
|
||||
/**
|
||||
* Add minimum height to content area
|
||||
*/
|
||||
minHeight?: 'screen' | 'content' | 'none';
|
||||
/**
|
||||
* Content container max width
|
||||
*/
|
||||
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '7xl' | 'full' | 'none';
|
||||
/**
|
||||
* Content padding
|
||||
*/
|
||||
contentPadding?: 'none' | 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
export interface PublicLayoutRef {
|
||||
scrollToTop: () => void;
|
||||
scrollToBottom: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* PublicLayout - Layout wrapper for public pages (landing, login, register)
|
||||
*
|
||||
* Features:
|
||||
* - Modular header and footer with configurable props
|
||||
* - Multiple layout variants for different page types
|
||||
* - Responsive design with flexible content areas
|
||||
* - Theme integration and consistent styling
|
||||
* - Accessible structure with proper landmarks
|
||||
* - Scroll utilities for navigation
|
||||
*/
|
||||
export const PublicLayout = forwardRef<PublicLayoutRef, PublicLayoutProps>(({
|
||||
children,
|
||||
className,
|
||||
showHeader = true,
|
||||
showFooter = true,
|
||||
headerProps = {},
|
||||
footerProps = {},
|
||||
variant = 'default',
|
||||
minHeight = 'screen',
|
||||
maxWidth = '7xl',
|
||||
contentPadding = 'md',
|
||||
}, ref) => {
|
||||
const { resolvedTheme } = useTheme();
|
||||
const layoutRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
// Scroll utilities
|
||||
const scrollToTop = React.useCallback(() => {
|
||||
layoutRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}, []);
|
||||
|
||||
const scrollToBottom = React.useCallback(() => {
|
||||
layoutRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||||
}, []);
|
||||
|
||||
// Expose ref methods
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
scrollToTop,
|
||||
scrollToBottom,
|
||||
}), [scrollToTop, scrollToBottom]);
|
||||
|
||||
// Max width classes
|
||||
const maxWidthClasses = {
|
||||
sm: 'max-w-sm',
|
||||
md: 'max-w-md',
|
||||
lg: 'max-w-lg',
|
||||
xl: 'max-w-xl',
|
||||
'2xl': 'max-w-2xl',
|
||||
'7xl': 'max-w-7xl',
|
||||
full: 'max-w-full',
|
||||
none: '',
|
||||
};
|
||||
|
||||
// Content padding classes
|
||||
const paddingClasses = {
|
||||
none: '',
|
||||
sm: 'px-4 py-6',
|
||||
md: 'px-4 py-8 sm:px-6 lg:px-8',
|
||||
lg: 'px-4 py-12 sm:px-6 lg:px-8 lg:py-16',
|
||||
};
|
||||
|
||||
// Min height classes
|
||||
const minHeightClasses = {
|
||||
screen: 'min-h-screen',
|
||||
content: 'min-h-[50vh]',
|
||||
none: '',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={layoutRef}
|
||||
className={clsx(
|
||||
'flex flex-col',
|
||||
minHeightClasses[minHeight],
|
||||
'bg-[var(--bg-primary)]',
|
||||
resolvedTheme,
|
||||
className
|
||||
)}
|
||||
data-testid="public-layout"
|
||||
>
|
||||
{/* Header */}
|
||||
{showHeader && (
|
||||
<PublicHeader
|
||||
showThemeToggle={true}
|
||||
showAuthButtons={true}
|
||||
variant="default"
|
||||
{...headerProps}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Main content */}
|
||||
<main
|
||||
role="main"
|
||||
className={clsx(
|
||||
'flex-1 flex flex-col',
|
||||
variant === 'centered' && 'items-center justify-center',
|
||||
variant === 'full-width' && 'w-full',
|
||||
variant === 'default' && 'w-full'
|
||||
)}
|
||||
aria-label="Contenido principal"
|
||||
>
|
||||
{variant === 'centered' ? (
|
||||
// Centered variant - useful for login/register pages
|
||||
<div className={clsx(
|
||||
'w-full mx-auto flex flex-col',
|
||||
maxWidthClasses[maxWidth],
|
||||
paddingClasses[contentPadding]
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
// Default and full-width variants
|
||||
<div className={clsx(
|
||||
variant === 'default' && maxWidthClasses[maxWidth] && `mx-auto w-full ${maxWidthClasses[maxWidth]}`,
|
||||
paddingClasses[contentPadding]
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
{showFooter && (
|
||||
<Footer
|
||||
compact={false}
|
||||
showPrivacyLinks={true}
|
||||
showThemeToggle={false} // Header already has theme toggle
|
||||
showVersion={true}
|
||||
{...footerProps}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
PublicLayout.displayName = 'PublicLayout';
|
||||
1
frontend/src/components/layout/PublicLayout/index.ts
Normal file
1
frontend/src/components/layout/PublicLayout/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { PublicLayout, type PublicLayoutProps, type PublicLayoutRef } from './PublicLayout';
|
||||
Reference in New Issue
Block a user