You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
72 lines
2.3 KiB
72 lines
2.3 KiB
"use client";
|
|
import React, { useEffect, useState } from "react";
|
|
import type { Banner } from "../types";
|
|
|
|
export interface BannerCarouselProps {
|
|
items: Banner[];
|
|
intervalMs?: number;
|
|
basePath?: string;
|
|
aspectClass?: string; // e.g. aspect-[16/6], aspect-[16/9]
|
|
}
|
|
|
|
export function BannerCarousel({ items, intervalMs = 5000, basePath = "", aspectClass = "aspect-[16/6]" }: BannerCarouselProps) {
|
|
const [index, setIndex] = useState(0);
|
|
const total = items.length;
|
|
const go = (i: number) => {
|
|
if (total === 0) return;
|
|
const n = (i + total) % total;
|
|
setIndex(n);
|
|
};
|
|
useEffect(() => {
|
|
if (total <= 1) return;
|
|
const t = setInterval(() => setIndex((i) => (i + 1) % total), intervalMs);
|
|
return () => clearInterval(t);
|
|
}, [total, intervalMs]);
|
|
|
|
if (items.length === 0) return null;
|
|
const current = items[index];
|
|
|
|
return (
|
|
<div className={`relative w-full overflow-hidden rounded-lg group ${aspectClass}`}>
|
|
<a href={current.href ? `${basePath}${current.href}` : "#"} className="block w-full h-full">
|
|
<img
|
|
src={current.image}
|
|
alt={current.title}
|
|
className="absolute inset-0 w-full h-full object-cover"
|
|
/>
|
|
</a>
|
|
{total > 1 && (
|
|
<>
|
|
<button
|
|
type="button"
|
|
aria-label="Previous"
|
|
onClick={() => go(index - 1)}
|
|
className="absolute left-3 top-1/2 -translate-y-1/2 h-9 w-9 rounded-full bg-black/40 text-white opacity-0 group-hover:opacity-100 transition flex items-center justify-center"
|
|
>
|
|
‹
|
|
</button>
|
|
<button
|
|
type="button"
|
|
aria-label="Next"
|
|
onClick={() => go(index + 1)}
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 h-9 w-9 rounded-full bg-black/40 text-white opacity-0 group-hover:opacity-100 transition flex items-center justify-center"
|
|
>
|
|
›
|
|
</button>
|
|
</>
|
|
)}
|
|
<div className="absolute inset-x-0 bottom-3 flex justify-center gap-2">
|
|
{items.map((_, i) => (
|
|
<button
|
|
key={i}
|
|
aria-label={`Go to slide ${i + 1}`}
|
|
onClick={() => go(i)}
|
|
className={`h-2 w-2 rounded-full ${i === index ? "bg-white" : "bg-white/50"}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
|
|
|