Files
klz-cables.com/components/content/Hero.tsx
2025-12-29 18:18:48 +01:00

223 lines
5.8 KiB
TypeScript

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;