Files
ai-toolkit/ui/src/components/Sidebar.tsx
2026-05-26 07:30:09 -06:00

148 lines
5.1 KiB
TypeScript

'use client';
import { useEffect } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Home, Settings, BrainCircuit, Images, Plus, X } from 'lucide-react';
import { FaXTwitter, FaDiscord, FaYoutube } from 'react-icons/fa6';
import { createGlobalState } from 'react-global-hooks';
import ThemeToggle from './ThemeToggle';
import ThemeLogo from './ThemeLogo';
import ActiveJobWidget from './ActiveJobWidget';
export const mobileSidebarState = createGlobalState<boolean>(false);
const Sidebar = () => {
const [isMobileOpen, setIsMobileOpen] = mobileSidebarState.use();
const pathname = usePathname();
// Close mobile menu on route change
useEffect(() => {
setIsMobileOpen(false);
}, [pathname]);
// Lock body scroll when mobile menu open
useEffect(() => {
if (isMobileOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isMobileOpen]);
const navigation = [
{ name: 'Dashboard', href: '/dashboard', icon: Home },
{ name: 'New Job', href: '/jobs/new', icon: Plus },
{ name: 'Queue', href: '/jobs', icon: BrainCircuit },
{ name: 'Datasets', href: '/datasets', icon: Images },
{ name: 'Settings', href: '/settings', icon: Settings },
];
const socialsBoxClass =
'flex flex-col items-center justify-center p-1 hover:bg-gray-800 rounded-lg transition-colors';
const socialIconClass = 'w-5 h-5 text-gray-400 hover:text-white';
const sidebarContent = (
<>
<div className="px-4 py-3 flex items-center justify-between">
<h1 className="text-l">
<ThemeLogo />
<span className="font-bold uppercase">Ostris</span>
<span className="ml-2 uppercase text-gray-300">AI-Toolkit</span>
</h1>
<button
onClick={() => setIsMobileOpen(false)}
className="md:hidden text-gray-400 hover:text-white p-1"
aria-label="Close menu"
>
<X className="w-5 h-5" />
</button>
</div>
<nav className="flex-1">
<ul className="px-2 py-4 space-y-2">
{navigation.map(item => (
<li key={item.name}>
<Link
href={item.href}
className="flex items-center px-4 py-2 text-gray-300 hover:bg-gray-800 rounded-lg transition-colors"
>
<item.icon className="w-5 h-5 mr-3" />
{item.name}
</Link>
</li>
))}
</ul>
</nav>
<ActiveJobWidget />
<a
href="https://ostris.com/support"
target="_blank"
rel="noreferrer"
className="group flex items-center space-x-2 px-4 py-3 text-gray-400 hover:text-gray-200 transition-colors"
>
<svg
height="20"
width="20"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
style={{ overflow: 'visible' }}
>
<path
className="animate-heartbeat"
d="m7 3c-1.5355 0-3.0784 0.5-4.25 1.7-2.3431 2.4-2.2788 6.1 0 8.5l9.25 9.8 9.25-9.8c2.279-2.4 2.343-6.1 0-8.5-2.343-2.3-6.157-2.3-8.5 0l-0.75 0.8-0.75-0.8c-1.172-1.2-2.7145-1.7-4.25-1.7z"
fill="#c0392b"
/>
</svg>
<span className="uppercase text-sm font-medium tracking-wide">Support AI-Toolkit</span>
</a>
{/* Social links grid */}
<div className="px-1 py-1 border-t border-gray-800">
<div className="grid grid-cols-4 gap-4">
<a href="https://discord.gg/VXmU2f5WEU" target="_blank" rel="noreferrer" className={socialsBoxClass}>
<FaDiscord className={socialIconClass} />
</a>
<a href="https://www.youtube.com/@ostrisai" target="_blank" rel="noreferrer" className={socialsBoxClass}>
<FaYoutube className={socialIconClass} />
</a>
<a href="https://x.com/ostrisai" target="_blank" rel="noreferrer" className={socialsBoxClass}>
<FaXTwitter className={socialIconClass} />
</a>
<ThemeToggle />
</div>
</div>
<div className="text-center text-[10px] text-gray-400 py-1 bg-gray-800">
Ostris AI-Toolkit v{process.env.NEXT_PUBLIC_APP_VERSION}
</div>
</>
);
return (
<>
{/* Desktop sidebar - always visible on md+ */}
<div className="hidden md:flex flex-col w-59 bg-gray-900 text-gray-100">{sidebarContent}</div>
{/* Mobile overlay sidebar */}
<div
className={`md:hidden fixed inset-0 bg-black/60 z-40 transition-opacity duration-300 ease-in-out ${
isMobileOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'
}`}
onClick={() => setIsMobileOpen(false)}
aria-hidden="true"
/>
<div
className={`md:hidden fixed top-0 left-0 bottom-0 w-64 max-w-[85vw] bg-gray-900 text-gray-100 z-50 flex flex-col shadow-xl transform transition-transform duration-300 ease-in-out ${
isMobileOpen ? 'translate-x-0' : '-translate-x-full'
}`}
>
{sidebarContent}
</div>
</>
);
};
export default Sidebar;