learning_ai_common_plat/packages/ui/src/components/Sidebar.tsx

111 lines
3.1 KiB
TypeScript

'use client';
import * as React from 'react';
import { clsx } from 'clsx';
import { Menu, X } from 'lucide-react';
export interface SidebarProps {
children: React.ReactNode;
header?: React.ReactNode;
footer?: React.ReactNode;
collapsed?: boolean;
onToggle?: () => void;
className?: string;
width?: number;
}
export function Sidebar({
children,
header,
footer,
collapsed = false,
onToggle,
className,
width = 240,
}: SidebarProps) {
return (
<>
{/* Mobile toggle */}
{onToggle && (
<button
className={clsx(
'fixed top-3 left-3 z-[38] flex items-center justify-center w-10 h-10 rounded-lg',
'border border-[var(--bl-border,#2a2a4a)] bg-[var(--bl-bg-elevated,#12151c)]',
'text-[var(--bl-text-primary,#fff)] cursor-pointer',
'md:hidden'
)}
onClick={onToggle}
aria-label={collapsed ? 'Open menu' : 'Close menu'}
type="button"
>
{collapsed ? <Menu size={20} /> : <X size={20} />}
</button>
)}
{/* Overlay */}
{onToggle && !collapsed && (
<div
className="fixed inset-0 z-[39] bg-black/50 md:hidden"
onClick={onToggle}
aria-hidden="true"
/>
)}
<aside
aria-label="Primary"
className={clsx(
'flex flex-col h-full border-r shrink-0 transition-transform duration-200',
'border-[var(--bl-border,#2a2a4a)] bg-[var(--bl-bg-elevated,#12151c)]',
'fixed md:static z-40',
collapsed ? '-translate-x-full md:translate-x-0' : 'translate-x-0',
className
)}
style={{ width, minWidth: width }}
>
{header && <div className="border-b border-[var(--bl-border,#2a2a4a)] p-4">{header}</div>}
<nav aria-label="Main navigation" className="flex-1 overflow-y-auto p-2">
{children}
</nav>
{footer && (
<div className="border-t border-[var(--bl-border,#2a2a4a)] p-4 text-xs text-[var(--bl-text-secondary,#a0a0b0)]">
{footer}
</div>
)}
</aside>
</>
);
}
export interface SidebarItemProps {
href: string;
label: string;
icon?: React.ReactNode;
active?: boolean;
className?: string;
}
export function SidebarItem({ href, label, icon, active, className }: SidebarItemProps) {
return (
<a
href={href}
aria-current={active ? 'page' : undefined}
className={clsx(
'flex items-center gap-2.5 px-3 py-2 rounded-md text-sm transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--bl-accent,#5A8CFF)]',
active
? 'bg-[var(--bl-surface-card,#1a1a2e)] text-[var(--bl-text-primary,#fff)] font-semibold'
: 'text-[var(--bl-text-secondary,#a0a0b0)] hover:bg-[var(--bl-surface-muted,#252540)] hover:text-[var(--bl-text-primary,#fff)]',
className
)}
>
{icon && <span className="shrink-0">{icon}</span>}
<span>{label}</span>
</a>
);
}
Sidebar.displayName = 'Sidebar';
SidebarItem.displayName = 'SidebarItem';