mirror of
https://github.com/OwethuManagedServices/oms-website-nextjs.git
synced 2025-12-17 17:18:09 +00:00
170 lines
4.8 KiB
TypeScript
170 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import { useRef, useEffect, useState } from "react";
|
|
import Image from "next/image";
|
|
|
|
type Client = {
|
|
name: string;
|
|
logoUrl: string;
|
|
};
|
|
|
|
type ClientLogosSectionProps = {
|
|
title: string;
|
|
description?: string;
|
|
clients: Client[];
|
|
speed?: number; // pixels per frame
|
|
squareSize?: number;
|
|
};
|
|
|
|
const ClientLogosSection = ({
|
|
title,
|
|
description,
|
|
clients = [],
|
|
speed = 1,
|
|
squareSize = 120,
|
|
}: ClientLogosSectionProps) => {
|
|
if (clients.length === 0) return null;
|
|
|
|
const extendedClients = [...clients, ...clients];
|
|
const squareDim = `${squareSize}px`;
|
|
const padding = Math.round(squareSize / 6);
|
|
const paddingDim = `${padding}px`;
|
|
|
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
const [direction, setDirection] = useState<"left" | "right">("left");
|
|
const [paused, setPaused] = useState(false);
|
|
let resumeTimeout: NodeJS.Timeout;
|
|
|
|
const pauseAndScroll = (dir: "left" | "right") => {
|
|
if (!scrollRef.current) return;
|
|
|
|
setPaused(true);
|
|
|
|
// Scroll manually
|
|
scrollRef.current.scrollBy({
|
|
left: dir === "left" ? -200 : 200,
|
|
behavior: "smooth",
|
|
});
|
|
|
|
// Clear previous timeout and resume after 3 seconds
|
|
clearTimeout(resumeTimeout);
|
|
resumeTimeout = setTimeout(() => {
|
|
setPaused(false);
|
|
}, 3000);
|
|
|
|
// Set the direction for automatic scroll after pause
|
|
setDirection(dir === "left" ? "right" : "left");
|
|
};
|
|
|
|
useEffect(() => {
|
|
const container = scrollRef.current;
|
|
if (!container) return;
|
|
|
|
let animationFrame: number;
|
|
|
|
const step = () => {
|
|
if (!paused) {
|
|
if (direction === "left") {
|
|
container.scrollLeft += speed;
|
|
if (container.scrollLeft >= container.scrollWidth / 2) container.scrollLeft = 0;
|
|
} else {
|
|
container.scrollLeft -= speed;
|
|
if (container.scrollLeft <= 0) container.scrollLeft = container.scrollWidth / 2;
|
|
}
|
|
}
|
|
animationFrame = requestAnimationFrame(step);
|
|
};
|
|
|
|
animationFrame = requestAnimationFrame(step);
|
|
|
|
return () => cancelAnimationFrame(animationFrame);
|
|
}, [direction, paused, speed]);
|
|
|
|
return (
|
|
<section className="py-16 md:py-20 bg-background overflow-hidden">
|
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
|
<h2 className="text-3xl md:text-4xl font-bold mb-12 text-foreground">
|
|
{title}
|
|
</h2>
|
|
</div>
|
|
|
|
{/* Logos Container */}
|
|
<div className="relative w-full overflow-hidden">
|
|
<div
|
|
ref={scrollRef}
|
|
className="flex flex-nowrap overflow-x-hidden scrollbar-hide"
|
|
>
|
|
{extendedClients.map((client, index) => (
|
|
<div
|
|
key={`${client.name}-${index}`}
|
|
className="flex-shrink-0 mx-12 md:mx-16 py-4"
|
|
>
|
|
<div
|
|
title={client.name}
|
|
className="
|
|
relative flex items-center justify-center
|
|
bg-muted/30 dark:bg-muted/20
|
|
rounded-lg shadow-sm
|
|
overflow-hidden
|
|
grayscale hover:grayscale-0
|
|
opacity-70 hover:opacity-100
|
|
transition-all duration-300 ease-in-out
|
|
cursor-pointer
|
|
"
|
|
style={{
|
|
width: squareDim,
|
|
height: squareDim,
|
|
padding: paddingDim,
|
|
}}
|
|
>
|
|
<Image
|
|
src={client.logoUrl}
|
|
alt={`${client.name} Logo`}
|
|
fill
|
|
style={{ objectFit: "contain" }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Arrow Controls */}
|
|
<div className="flex justify-center mt-8 space-x-6">
|
|
<button
|
|
onClick={() => pauseAndScroll("right")}
|
|
className="px-4 py-2 rounded-full bg-muted hover:bg-muted/70 transition"
|
|
>
|
|
←
|
|
</button>
|
|
<button
|
|
onClick={() => pauseAndScroll("left")}
|
|
className="px-4 py-2 rounded-full bg-muted hover:bg-muted/70 transition"
|
|
>
|
|
→
|
|
</button>
|
|
</div>
|
|
|
|
{description && (
|
|
<div className="container mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
|
<p className="mt-10 text-muted-foreground italic text-sm">
|
|
{description}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export const defaultClients: Client[] = [
|
|
{ name: "ABSA", logoUrl: "/images/absa.png" },
|
|
{ name: "SYBRIN", logoUrl: "/images/sybrin.svg" },
|
|
{ name: "SASOL", logoUrl: "/images/sasol.png" },
|
|
{ name: "JACARANDA", logoUrl: "/images/jacaranda.png" },
|
|
{ name: "SALESFORCE", logoUrl: "/images/salesforce.png" },
|
|
{ name: "BCX", logoUrl: "/images/bcx.png" },
|
|
{ name: "TOYOTA", logoUrl: "/images/toyota-logo.png" },
|
|
];
|
|
|
|
export default ClientLogosSection;
|