banatie-service/apps/landing/src/components/docs/variant-c/DocsTOCC.tsx

213 lines
7.4 KiB
TypeScript

'use client';
/**
* Table of Contents - Variant C: Modern & Visual (Shopify-inspired)
*
* Design Philosophy: Floating card with colorful visual indicators
*
* Features:
* - Wrapped in large gradient-bordered card
* - Colorful dot indicators (alternating purple/cyan/amber)
* - Large text sizes (text-base for items)
* - Generous spacing (p-6, gap-4)
* - Floating shadow effect with colored glow
* - NO section numbers (more visual/playful)
* - Active state with gradient background
* - Smooth hover animations with scale
* - Fun, engaging design with emoji header
*
* Visual Elements:
* - Large colored dots instead of numbers
* - Gradient progress bar with rainbow colors
* - Active item with gradient highlight
* - Hover effects with shadow and scale
*
* Behavior:
* - Extracts H2 and H3 headings (no numbers)
* - Shows reading progress with colorful bar
* - Click to smooth scroll to section
* - Visual feedback with animations
*/
import { useEffect, useState } from 'react';
interface TocItem {
id: string;
text: string;
level: number;
}
interface DocsTOCCProps {
items: TocItem[];
}
const getColorForIndex = (index: number): string => {
const colors = ['purple', 'cyan', 'amber'];
return colors[index % colors.length];
};
const getDotClasses = (color: string, isActive: boolean): string => {
const baseClasses = 'w-2.5 h-2.5 rounded-full flex-shrink-0 transition-all duration-300';
if (isActive) {
return `${baseClasses} shadow-lg ${
color === 'purple'
? 'bg-purple-500 shadow-purple-500/50'
: color === 'cyan'
? 'bg-cyan-500 shadow-cyan-500/50'
: 'bg-amber-500 shadow-amber-500/50'
}`;
}
return `${baseClasses} ${
color === 'purple'
? 'bg-purple-500/40'
: color === 'cyan'
? 'bg-cyan-500/40'
: 'bg-amber-500/40'
}`;
};
export const DocsTOCC = ({ items }: DocsTOCCProps) => {
const [activeId, setActiveId] = useState<string>('');
const [scrollProgress, setScrollProgress] = useState(0);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveId(entry.target.id);
}
});
},
{ rootMargin: '-20% 0px -35% 0px' }
);
items.forEach((item) => {
const element = document.getElementById(item.id);
if (element) observer.observe(element);
});
const handleScroll = () => {
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollTop = window.scrollY;
const progress = (scrollTop / (documentHeight - windowHeight)) * 100;
setScrollProgress(Math.min(progress, 100));
};
window.addEventListener('scroll', handleScroll);
return () => {
observer.disconnect();
window.removeEventListener('scroll', handleScroll);
};
}, [items]);
const scrollToSection = (id: string) => {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
};
if (items.length === 0) {
return null;
}
return (
<nav className="p-6 sticky top-6" aria-label="Table of contents">
{/* Floating Card with Gradient Border */}
<div className="rounded-2xl border-2 border-purple-500/40 bg-slate-900/60 backdrop-blur-md shadow-xl shadow-purple-500/20 overflow-hidden">
{/* Header with Icon and Progress */}
<div className="p-6 pb-4 border-b-2 border-purple-500/20">
<div className="flex items-center gap-3 mb-4">
<span className="text-2xl">📍</span>
<h3 className="text-sm font-bold text-white uppercase tracking-wider">
On This Page
</h3>
</div>
{/* Rainbow Progress Bar */}
<div className="relative h-2 bg-slate-800 rounded-full overflow-hidden">
<div
className="absolute top-0 left-0 h-full bg-gradient-to-r from-purple-500 via-cyan-500 to-amber-500 transition-all duration-300 shadow-lg"
style={{ width: `${scrollProgress}%` }}
></div>
</div>
<div className="mt-2 flex items-center justify-between">
<p className="text-xs text-gray-400">{Math.round(scrollProgress)}% complete</p>
<span className="text-xs text-purple-400 font-semibold">
{items.findIndex((item) => item.id === activeId) + 1}/{items.length}
</span>
</div>
</div>
{/* TOC Items with Colorful Dots */}
<ul className="p-6 space-y-3">
{items.map((item, index) => {
const isActive = activeId === item.id;
const isH3 = item.level === 3;
const color = getColorForIndex(index);
const cleanText = item.text.replace(/^\d+\.?\s*/, '');
return (
<li key={item.id} className={isH3 ? 'ml-6' : ''}>
<button
onClick={() => scrollToSection(item.id)}
className={`
flex items-start gap-3 text-left w-full transition-all duration-300 py-2 px-3 rounded-lg
${
isActive
? 'bg-gradient-to-br from-purple-500/20 to-cyan-500/20 text-white font-semibold shadow-lg scale-105'
: 'text-gray-400 hover:text-white hover:bg-slate-800/50 hover:scale-102'
}
`}
>
{/* Colorful Dot Indicator */}
<span className="mt-1.5">
{isH3 ? (
<span className="text-gray-600 text-xs ml-0.5"></span>
) : (
<span className={getDotClasses(color, isActive)}></span>
)}
</span>
{/* Text */}
<span className="flex-1 text-base leading-relaxed">{cleanText}</span>
</button>
</li>
);
})}
</ul>
{/* Back to Top Button - Colorful */}
{scrollProgress > 20 && (
<div className="p-6 pt-0">
<button
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
className="w-full px-4 py-3 text-sm bg-gradient-to-r from-purple-600/30 to-cyan-600/30 hover:from-purple-600/50 hover:to-cyan-600/50 text-white rounded-xl transition-all duration-300 flex items-center justify-center gap-2 border-2 border-purple-500/40 hover:border-purple-500/60 shadow-lg hover:shadow-purple-500/30 hover:scale-105"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 10l7-7m0 0l7 7m-7-7v18" />
</svg>
<span className="font-semibold">Back to top</span>
</button>
</div>
)}
</div>
{/* Fun Floating Tip Card */}
<div className="mt-6 p-4 rounded-xl bg-gradient-to-br from-amber-500/10 to-transparent border-2 border-amber-500/30 shadow-lg">
<div className="flex items-start gap-2">
<span className="text-xl">💡</span>
<p className="text-xs text-amber-300">
<strong>Tip:</strong> Click any item to jump directly to that section!
</p>
</div>
</div>
</nav>
);
};