Skip to content

Instantly share code, notes, and snippets.

@Avenster
Created April 2, 2025 07:45
Show Gist options
  • Save Avenster/9206bd19541151829d3fe94b9910c5c7 to your computer and use it in GitHub Desktop.
Save Avenster/9206bd19541151829d3fe94b9910c5c7 to your computer and use it in GitHub Desktop.
"use client";
import { useState, useEffect, useRef } from "react";
import Image from "next/image";
import { ChevronLeft, ChevronRight, Info, Calendar, Users } from "lucide-react";
// Enhanced image data with school-related metadata
interface SchoolSlide {
src: string;
alt: string;
title: string;
description: string;
date?: string;
category: "Event" | "Achievement" | "Announcement" | "Facility";
}
// Sample school-related images with meaningful content
const schoolSlides: SchoolSlide[] = [
{
src: "/1.jpg",
alt: "School Science Fair",
title: "Annual Science Fair 2025",
description: "Students showcasing innovative science projects and experiments",
date: "March 15, 2025",
category: "Event"
},
{
src: "/2.jpg",
alt: "Basketball Championship",
title: "Regional Basketball Champions",
description: "Our team won the regional championship for the third consecutive year",
date: "February 28, 2025",
category: "Achievement"
},
{
src: "/3.jpg",
alt: "New Library Opening",
title: "New Digital Learning Center",
description: "Our newly renovated library features state-of-the-art technology resources",
category: "Facility"
},
{
src: "/4.jpg",
alt: "Parent-Teacher Conference",
title: "Spring Parent-Teacher Conference",
description: "Join us for the upcoming parent-teacher conference to discuss student progress",
date: "April 10, 2025",
category: "Announcement"
}
];
export default function SchoolImageSlider(): JSX.Element {
const [currentIndex, setCurrentIndex] = useState<number>(0);
const [isHovered, setIsHovered] = useState<boolean>(false);
const [isInfoVisible, setIsInfoVisible] = useState<boolean>(false);
const sliderRef = useRef<HTMLDivElement>(null);
const autoPlayRef = useRef<NodeJS.Timeout | null>(null);
// Category color mapping
const categoryColors = {
Event: "bg-blue-500",
Achievement: "bg-green-500",
Announcement: "bg-yellow-500",
Facility: "bg-purple-500"
};
const prevSlide = (): void => {
setCurrentIndex((prevIndex) => (prevIndex - 1 + schoolSlides.length) % schoolSlides.length);
};
const nextSlide = (): void => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % schoolSlides.length);
};
const goToSlide = (index: number): void => {
setCurrentIndex(index);
};
const toggleInfo = (): void => {
setIsInfoVisible(!isInfoVisible);
};
// Handle keyboard navigation
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "ArrowLeft") {
prevSlide();
} else if (e.key === "ArrowRight") {
nextSlide();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, []);
// Improved autoplay with pause on tab visibility change
useEffect(() => {
const startAutoPlay = () => {
if (autoPlayRef.current) clearInterval(autoPlayRef.current);
if (!isHovered) {
autoPlayRef.current = setInterval(() => {
nextSlide();
}, 5000); // Longer interval for reading content
}
};
const handleVisibilityChange = () => {
if (document.hidden && autoPlayRef.current) {
clearInterval(autoPlayRef.current);
autoPlayRef.current = null;
} else if (!document.hidden && !isHovered) {
startAutoPlay();
}
};
startAutoPlay();
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
if (autoPlayRef.current) clearInterval(autoPlayRef.current);
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, [isHovered]);
return (
<div className="relative w-full max-w-4xl mx-auto mt-8 mb-12 rounded-xl shadow-lg">
<div
ref={sliderRef}
className="relative h-[460px] overflow-hidden rounded-xl border-2 border-blue-100"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{/* Image with overlay gradient for text readability */}
<div className="relative h-full w-full">
<Image
src={schoolSlides[currentIndex].src}
alt={schoolSlides[currentIndex].alt}
fill
priority
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover transition-transform duration-700 ease-in-out"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent pointer-events-none" />
</div>
{/* Category badge */}
<div className={`absolute top-4 left-4 px-3 py-1 rounded-full text-white text-sm font-medium ${categoryColors[schoolSlides[currentIndex].category]}`}>
{schoolSlides[currentIndex].category}
</div>
{/* Bottom content */}
<div className="absolute bottom-0 left-0 right-0 p-6 text-white">
<h2 className="text-2xl font-bold mb-2">{schoolSlides[currentIndex].title}</h2>
{/* Show more info button */}
<button
onClick={toggleInfo}
className="flex items-center space-x-2 mb-2 text-sm text-blue-200 hover:text-white transition-colors"
>
<Info size={16} />
<span>{isInfoVisible ? "Hide details" : "Show details"}</span>
</button>
{/* Expandable info section */}
<div className={`overflow-hidden transition-all duration-300 ease-in-out ${isInfoVisible ? "max-h-40 opacity-100" : "max-h-0 opacity-0"}`}>
<p className="text-gray-200 mb-3">{schoolSlides[currentIndex].description}</p>
{schoolSlides[currentIndex].date && (
<div className="flex items-center text-sm text-gray-300 mb-2">
<Calendar size={14} className="mr-1" />
<span>{schoolSlides[currentIndex].date}</span>
</div>
)}
</div>
</div>
{/* Navigation arrows */}
<button
className="absolute left-2 top-1/2 transform -translate-y-1/2 bg-white/20 hover:bg-white/40 text-white p-2 rounded-full backdrop-blur-sm transition-all"
onClick={prevSlide}
aria-label="Previous slide"
>
<ChevronLeft size={24} />
</button>
<button
className="absolute right-2 top-1/2 transform -translate-y-1/2 bg-white/20 hover:bg-white/40 text-white p-2 rounded-full backdrop-blur-sm transition-all"
onClick={nextSlide}
aria-label="Next slide"
>
<ChevronRight size={24} />
</button>
</div>
{/* Enhanced indicator dots */}
<div className="flex justify-center mt-4 gap-2">
{schoolSlides.map((_, index) => (
<button
key={index}
onClick={() => goToSlide(index)}
className={`flex flex-col items-center group focus:outline-none`}
aria-label={`Go to slide ${index + 1}`}
>
<div className={`h-2 w-8 rounded-full transition-all duration-300 ${
index === currentIndex ? "bg-blue-600" : "bg-gray-300 group-hover:bg-blue-400"
}`} />
<span className={`text-xs mt-1 transition-all duration-300 ${
index === currentIndex ? "text-blue-600" : "text-gray-500"
}`}>
{index + 1}
</span>
</button>
))}
</div>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment