99 lines
3.8 KiB
TypeScript
99 lines
3.8 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { GUIDE_CONTENT } from '../constants';
|
|
import { Leaf, Menu, X, GitBranch } from 'lucide-react';
|
|
|
|
export const Sidebar: React.FC = () => {
|
|
const [activeId, setActiveId] = useState<string>('');
|
|
const [isMobileOpen, setIsMobileOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
setActiveId(entry.target.id);
|
|
}
|
|
});
|
|
},
|
|
{ rootMargin: '-20% 0px -60% 0px' }
|
|
);
|
|
|
|
GUIDE_CONTENT.forEach((section) => {
|
|
const element = document.getElementById(section.id);
|
|
if (element) observer.observe(element);
|
|
});
|
|
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
const scrollToSection = (id: string) => {
|
|
const element = document.getElementById(id);
|
|
if (element) {
|
|
element.scrollIntoView({ behavior: 'smooth' });
|
|
setIsMobileOpen(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{/* Mobile Toggle */}
|
|
<div className="fixed top-4 right-4 z-50 lg:hidden">
|
|
<button
|
|
onClick={() => setIsMobileOpen(!isMobileOpen)}
|
|
className="p-2 bg-card rounded-full shadow-lg border border-border text-foreground"
|
|
>
|
|
{isMobileOpen ? <X size={24} /> : <Menu size={24} />}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Sidebar Container */}
|
|
<nav className={`
|
|
fixed top-0 left-0 h-full bg-card/95 backdrop-blur-md border-r border-border
|
|
w-72 transform transition-transform duration-300 z-40
|
|
${isMobileOpen ? 'translate-x-0' : '-translate-x-full'}
|
|
lg:translate-x-0
|
|
`}>
|
|
<div className="p-6 h-full flex flex-col">
|
|
<div className="flex items-center gap-3 mb-10 group cursor-default">
|
|
<div className="p-2 bg-primary/10 rounded-lg group-hover:bg-primary/20 transition-colors">
|
|
<Leaf className="text-primary" size={24} />
|
|
</div>
|
|
<div>
|
|
<h1 className="font-bold text-foreground text-lg leading-tight">Git for Sylvi</h1>
|
|
<span className="text-xs text-muted-foreground font-mono">v1.0.0</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto pr-2 custom-scrollbar">
|
|
<div className="space-y-1">
|
|
{GUIDE_CONTENT.map((section) => (
|
|
<button
|
|
key={section.id}
|
|
onClick={() => scrollToSection(section.id)}
|
|
className={`
|
|
w-full text-left px-4 py-3 rounded-md text-sm transition-all duration-200
|
|
flex items-center gap-3 group
|
|
${activeId === section.id
|
|
? 'bg-primary/10 text-primary font-semibold shadow-sm'
|
|
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'
|
|
}
|
|
`}
|
|
>
|
|
<span className={`w-1.5 h-1.5 rounded-full transition-colors ${activeId === section.id ? 'bg-primary' : 'bg-muted-foreground/30 group-hover:bg-muted-foreground'}`}></span>
|
|
<span className="truncate">{section.title.split('. ')[1]}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6 pt-6 border-t border-border">
|
|
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
|
<GitBranch size={16} />
|
|
<span>Happy Coding! 🌱</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</>
|
|
);
|
|
}; |