tpmjs/apps/tutorial/src/hooks/useSlideshow.ts
Ajax Davis 29e1b9d9ac feat: add interactive tutorial slideshow app
Create apps/tutorial - a beautiful, animated Next.js slideshow that explains
TPMJS concepts from basic to advanced:

Slides:
- Welcome: Gradient TPMJS title with animated floating orbs
- Problem: Chaotic floating icons showing fragmented AI tools
- Solution: Icons organizing into a neat grid
- How It Works: Flow diagram (npm → Discovery → Registry → AI Agent)
- Discovery: Animated counters showing real-time sync stats
- Integration: Typewriter code animation showing SDK usage
- Quality Scoring: Animated gauge and progress bars
- Tool Detail: Mock tool card with all metadata
- Get Started: CTA buttons linking to tpmjs.com

Features:
- Framer Motion animations with spring physics
- Keyboard navigation (arrows, space, enter)
- Mouse navigation (arrows, progress dots)
- Touch-friendly swipe support
- Dark theme with cyan/purple gradient accents
- Responsive design
2025-12-28 23:41:15 +10:00

129 lines
3.1 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
export interface UseSlideshowOptions {
totalSlides: number;
autoPlay?: boolean;
autoPlayInterval?: number;
loop?: boolean;
}
export interface UseSlideshowReturn {
currentSlide: number;
totalSlides: number;
next: () => void;
prev: () => void;
goTo: (index: number) => void;
progress: number;
direction: 'left' | 'right' | null;
isFirst: boolean;
isLast: boolean;
isAutoPlaying: boolean;
toggleAutoPlay: () => void;
}
export function useSlideshow({
totalSlides,
autoPlay = false,
autoPlayInterval = 5000,
loop = false,
}: UseSlideshowOptions): UseSlideshowReturn {
const [currentSlide, setCurrentSlide] = useState(0);
const [direction, setDirection] = useState<'left' | 'right' | null>(null);
const [isAutoPlaying, setIsAutoPlaying] = useState(autoPlay);
const isFirst = currentSlide === 0;
const isLast = currentSlide === totalSlides - 1;
const progress = totalSlides > 1 ? currentSlide / (totalSlides - 1) : 0;
const next = useCallback(() => {
if (isLast && !loop) return;
setDirection('right');
setCurrentSlide((prev) => (prev + 1) % totalSlides);
}, [isLast, loop, totalSlides]);
const prev = useCallback(() => {
if (isFirst && !loop) return;
setDirection('left');
setCurrentSlide((prev) => (prev - 1 + totalSlides) % totalSlides);
}, [isFirst, loop, totalSlides]);
const goTo = useCallback(
(index: number) => {
if (index < 0 || index >= totalSlides) return;
setDirection(index > currentSlide ? 'right' : 'left');
setCurrentSlide(index);
},
[currentSlide, totalSlides]
);
const toggleAutoPlay = useCallback(() => {
setIsAutoPlaying((prev) => !prev);
}, []);
// Keyboard navigation
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Pause auto-play on any key press
if (isAutoPlaying) {
setIsAutoPlaying(false);
}
switch (e.key) {
case 'ArrowRight':
case ' ':
case 'Enter':
e.preventDefault();
next();
break;
case 'ArrowLeft':
e.preventDefault();
prev();
break;
case 'Home':
e.preventDefault();
goTo(0);
break;
case 'End':
e.preventDefault();
goTo(totalSlides - 1);
break;
case 'Escape':
e.preventDefault();
setIsAutoPlaying(false);
break;
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [next, prev, goTo, totalSlides, isAutoPlaying]);
// Auto-play
useEffect(() => {
if (!isAutoPlaying) return;
const timer = setInterval(() => {
if (isLast && !loop) {
setIsAutoPlaying(false);
return;
}
next();
}, autoPlayInterval);
return () => clearInterval(timer);
}, [isAutoPlaying, autoPlayInterval, next, isLast, loop]);
return {
currentSlide,
totalSlides,
next,
prev,
goTo,
progress,
direction,
isFirst,
isLast,
isAutoPlaying,
toggleAutoPlay,
};
}