migration wip
This commit is contained in:
223
components/content/Hero.tsx
Normal file
223
components/content/Hero.tsx
Normal file
@@ -0,0 +1,223 @@
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Container } from '../ui/Container';
|
||||
import { Button } from '../ui/Button';
|
||||
|
||||
// Hero height options
|
||||
type HeroHeight = 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||
|
||||
// Hero variant options
|
||||
type HeroVariant = 'default' | 'dark' | 'primary' | 'gradient';
|
||||
|
||||
interface HeroProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
backgroundImage?: string;
|
||||
backgroundAlt?: string;
|
||||
height?: HeroHeight;
|
||||
variant?: HeroVariant;
|
||||
ctaText?: string;
|
||||
ctaLink?: string;
|
||||
ctaVariant?: 'primary' | 'secondary' | 'outline';
|
||||
overlay?: boolean;
|
||||
overlayOpacity?: number;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Helper function to get height styles
|
||||
const getHeightStyles = (height: HeroHeight) => {
|
||||
switch (height) {
|
||||
case 'sm':
|
||||
return 'min-h-[300px] md:min-h-[400px]';
|
||||
case 'md':
|
||||
return 'min-h-[400px] md:min-h-[500px]';
|
||||
case 'lg':
|
||||
return 'min-h-[500px] md:min-h-[600px]';
|
||||
case 'xl':
|
||||
return 'min-h-[600px] md:min-h-[700px]';
|
||||
case 'full':
|
||||
return 'min-h-screen';
|
||||
default:
|
||||
return 'min-h-[500px] md:min-h-[600px]';
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get variant styles
|
||||
const getVariantStyles = (variant: HeroVariant) => {
|
||||
switch (variant) {
|
||||
case 'dark':
|
||||
return 'bg-gray-900 text-white';
|
||||
case 'primary':
|
||||
return 'bg-primary text-white';
|
||||
case 'gradient':
|
||||
return 'bg-gradient-to-br from-primary to-secondary text-white';
|
||||
default:
|
||||
return 'bg-gray-800 text-white';
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get overlay opacity
|
||||
const getOverlayOpacity = (opacity?: number) => {
|
||||
if (opacity === undefined) return 'bg-black/50';
|
||||
if (opacity >= 1) return 'bg-black';
|
||||
if (opacity <= 0) return 'bg-transparent';
|
||||
return `bg-black/${Math.round(opacity * 100)}`;
|
||||
};
|
||||
|
||||
export const Hero: React.FC<HeroProps> = ({
|
||||
title,
|
||||
subtitle,
|
||||
backgroundImage,
|
||||
backgroundAlt = '',
|
||||
height = 'md',
|
||||
variant = 'default',
|
||||
ctaText,
|
||||
ctaLink,
|
||||
ctaVariant = 'primary',
|
||||
overlay = true,
|
||||
overlayOpacity,
|
||||
children,
|
||||
className = '',
|
||||
}) => {
|
||||
const hasBackground = !!backgroundImage;
|
||||
const hasCTA = !!ctaText && !!ctaLink;
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
'relative w-full overflow-hidden flex items-center justify-center',
|
||||
getHeightStyles(height),
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Background Image */}
|
||||
{hasBackground && (
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src={backgroundImage}
|
||||
alt={backgroundAlt || title}
|
||||
fill
|
||||
priority
|
||||
className="object-cover"
|
||||
sizes="100vw"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Background Variant (if no image) */}
|
||||
{!hasBackground && (
|
||||
<div className={cn(
|
||||
'absolute inset-0 z-0',
|
||||
getVariantStyles(variant)
|
||||
)} />
|
||||
)}
|
||||
|
||||
{/* Overlay */}
|
||||
{overlay && hasBackground && (
|
||||
<div className={cn(
|
||||
'absolute inset-0 z-10',
|
||||
getOverlayOpacity(overlayOpacity)
|
||||
)} />
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-20 w-full">
|
||||
<Container
|
||||
maxWidth="6xl"
|
||||
padding="lg"
|
||||
className={cn(
|
||||
'text-center',
|
||||
// Add padding for full-height heroes
|
||||
height === 'full' && 'py-12 md:py-20'
|
||||
)}
|
||||
>
|
||||
{/* Title */}
|
||||
<h1
|
||||
className={cn(
|
||||
'font-bold leading-tight mb-4',
|
||||
'text-3xl sm:text-4xl md:text-5xl lg:text-6xl',
|
||||
'tracking-tight',
|
||||
// Ensure text contrast
|
||||
hasBackground || variant !== 'default' ? 'text-white' : 'text-gray-900'
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{subtitle && (
|
||||
<p
|
||||
className={cn(
|
||||
'text-lg sm:text-xl md:text-2xl',
|
||||
'mb-8 max-w-3xl mx-auto',
|
||||
'leading-relaxed',
|
||||
hasBackground || variant !== 'default' ? 'text-gray-100' : 'text-gray-600'
|
||||
)}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* CTA Button */}
|
||||
{hasCTA && (
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
variant={ctaVariant}
|
||||
size="lg"
|
||||
onClick={() => {
|
||||
if (ctaLink) {
|
||||
// Handle both internal and external links
|
||||
if (ctaLink.startsWith('http')) {
|
||||
window.open(ctaLink, '_blank');
|
||||
} else {
|
||||
// For Next.js routing, you'd use the router
|
||||
// This is a fallback for external links
|
||||
window.location.href = ctaLink;
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="animate-fade-in-up"
|
||||
>
|
||||
{ctaText}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Additional Content */}
|
||||
{children && (
|
||||
<div className="mt-8">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
// Sub-components for more complex hero layouts
|
||||
export const HeroContent: React.FC<{
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}> = ({ title, subtitle, children, className = '' }) => (
|
||||
<div className={cn('space-y-4 md:space-y-6', className)}>
|
||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold">{title}</h2>
|
||||
{subtitle && <p className="text-lg md:text-xl text-gray-200">{subtitle}</p>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const HeroActions: React.FC<{
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}> = ({ children, className = '' }) => (
|
||||
<div className={cn('flex flex-wrap gap-3 justify-center', className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Hero;
|
||||
Reference in New Issue
Block a user