website-01/components/front/BannerCarousel.tsx

122 lines
3.9 KiB
TypeScript
Raw Permalink Normal View History

2026-06-22 14:43:46 +08:00
'use client';
import { useEffect, useState, useCallback } from 'react';
import Link from 'next/link';
import type { Banner } from '@/lib/types';
import { resolveUploadUrl, cn } from '@/lib/utils';
const INTERVAL = 5000;
export function BannerCarousel({ banners }: { banners: Banner[] }) {
const [idx, setIdx] = useState(0);
const total = banners.length;
const next = useCallback(() => {
setIdx((p) => (total === 0 ? 0 : (p + 1) % total));
}, [total]);
useEffect(() => {
if (total <= 1) return;
const t = setInterval(next, INTERVAL);
return () => clearInterval(t);
}, [next, total]);
if (total === 0) {
return <HeroPlaceholder />;
}
return (
<div className="relative h-[420px] overflow-hidden bg-gray-100">
{banners.map((b, i) => (
<div
key={b.id}
className={cn(
'absolute inset-0 transition-opacity duration-700',
i === idx ? 'opacity-100' : 'opacity-0',
)}
>
{b.link ? (
<Link href={b.link} target="_blank" rel="noreferrer" className="block h-full w-full">
<BG image={b.image} title={b.title} />
</Link>
) : (
<BG image={b.image} title={b.title} />
)}
</div>
))}
{total > 1 && (
<div className="absolute bottom-4 left-1/2 flex -translate-x-1/2 gap-2">
{banners.map((_, i) => (
<button
key={i}
aria-label={`${i + 1}`}
onClick={() => setIdx(i)}
className={cn(
'h-2 rounded-full transition-all',
i === idx ? 'w-6 bg-white' : 'w-2 bg-white/50',
)}
/>
))}
</div>
)}
</div>
);
}
function BG({ image, title }: { image: string; title: string }) {
return (
<div
className="relative h-full w-full bg-cover bg-center"
style={{ backgroundImage: `url(${resolveUploadUrl(image)})` }}
>
<div className="absolute inset-0 bg-black/40" />
<div className="container-page flex h-full items-center">
<h2 className="max-w-xl text-2xl font-bold leading-snug text-white sm:text-3xl md:text-4xl">
{title}
</h2>
</div>
</div>
);
}
function HeroPlaceholder() {
return (
<div className="relative flex h-[480px] items-center overflow-hidden bg-gradient-to-br from-brand-700 via-brand-600 to-brand-800">
<div
className="absolute inset-0 opacity-30"
style={{
backgroundImage:
'radial-gradient(circle at 20% 50%, rgba(255,255,255,0.15) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255,255,255,0.1) 0%, transparent 40%)',
}}
/>
<div className="container-page relative">
<span className="inline-block rounded-full bg-white/15 px-4 py-1.5 text-sm text-white backdrop-blur">
· · SaaS
</span>
<h1 className="mt-6 max-w-2xl text-4xl font-bold leading-tight text-white sm:text-5xl md:text-6xl">
<br />
</h1>
<p className="mt-4 max-w-xl text-lg text-brand-100">
&ldquo;&rdquo;
</p>
<div className="mt-8 flex flex-col gap-4 sm:flex-row">
<Link
href="/contact"
className="rounded-lg bg-white px-7 py-3 text-sm font-semibold text-brand-700 shadow-lg transition-colors hover:bg-brand-50"
>
</Link>
<Link
href="/products"
className="rounded-lg border border-white/40 px-7 py-3 text-sm font-medium text-white transition-colors hover:bg-white/10"
>
</Link>
</div>
</div>
</div>
);
}