website-01/components/front/BannerCarousel.tsx
2026-06-22 14:43:46 +08:00

122 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
);
}