Zero-Knowledge-Platform/frontend/src/components/layout/Sidebar.tsx
2026-10-08 02:41:41 +05:30

245 lines
No EOL
9.5 KiB
TypeScript

import { NavLink, Link, useNavigate } from 'react-router-dom'
import {
ChevronDown,
ChevronRight,
FileText,
FolderOpen,
Home,
Info,
LogOut,
Plus,
ShieldCheck,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { Separator } from '@/components/ui/separator'
import { Button } from '@/components/ui/button'
import { useAuth } from '@/context/AuthContext'
import { useWorkspace } from '@/context/WorkspaceContext'
interface SidebarProps {
className?: string
}
const mainNav = [
{ to: '/', label: 'Home', icon: Home },
{ to: '/about', label: 'About', icon: Info },
{ to: '/security', label: 'Security', icon: ShieldCheck },
]
export function Sidebar({ className }: SidebarProps) {
const { isAuthenticated, signOut } = useAuth()
const navigate = useNavigate()
const {
parents,
childrenByParent,
expandedParents,
selection,
parentOfSelection,
toggleExpand,
selectParent,
selectChild,
requestCreate,
} = useWorkspace()
const createHint =
parentOfSelection === null
? 'Creates a new top-level page'
: `Creates a child under "${parentOfSelection.title || 'untitled page'}"`
function handleCreateParent() {
requestCreate('parent')
navigate('/workspace')
}
function handleCreateChild() {
requestCreate('child')
navigate('/workspace')
}
return (
<aside
className={cn(
'flex h-full w-[250px] flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground',
className
)}
>
<nav className="flex-1 overflow-y-auto px-2 py-4">
<p className="px-3 pb-2 text-[11px] font-semibold uppercase tracking-wider text-primary/50">
Navigation
</p>
<div className="space-y-1">
{mainNav.map((item) => (
<NavLink
key={item.to}
to={item.to}
end={item.to === '/'}
className={({ isActive }) =>
cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
isActive
? 'bg-sidebar-accent text-sidebar-accent-foreground shadow-[inset_0_0_0_1px_rgba(34,211,238,0.35)]'
: 'text-sidebar-foreground/70'
)
}
>
<item.icon className="h-4 w-4" />
{item.label}
</NavLink>
))}
</div>
{isAuthenticated ? (
<>
<p className="px-3 pb-2 pt-5 text-[11px] font-semibold uppercase tracking-wider text-primary/50">
Account
</p>
<div className="space-y-1">
<NavLink
to="/workspace"
className={({ isActive }) =>
cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
isActive
? 'bg-sidebar-accent text-sidebar-accent-foreground shadow-[inset_0_0_0_1px_rgba(34,211,238,0.35)]'
: 'text-sidebar-foreground/70'
)
}
>
<FolderOpen className="h-4 w-4" />
My Workspace
</NavLink>
</div>
<div className="mt-5 space-y-1">
<div className="flex items-center justify-between px-3 pb-1">
<p className="text-[11px] font-semibold uppercase tracking-wider text-primary/50">My Pages</p>
<button
type="button"
onClick={handleCreateParent}
title="Create a new top-level page"
aria-label="Create a new top-level page"
className="flex h-6 w-6 items-center justify-center rounded-md text-primary/70 transition-colors hover:bg-sidebar-accent hover:text-primary"
>
<Plus className="h-4 w-4" />
</button>
</div>
<Button size="sm" variant="secondary" className="w-full justify-start gap-2" onClick={handleCreateChild}>
<Plus className="h-4 w-4" />
Create
</Button>
<p className="px-1 text-[10px] leading-snug text-muted-foreground">{createHint}</p>
</div>
<div className="mt-2 max-h-[38vh] space-y-0.5 overflow-y-auto pr-1">
{parents.length === 0 ? (
<div className="rounded-lg border border-dashed border-border px-3 py-6 text-center">
<p className="text-xs text-muted-foreground">No pages yet. Click + to create one.</p>
</div>
) : (
parents.map((parent) => {
const isExpanded = expandedParents.has(parent.id)
const isParentActive =
selection !== null && selection.kind === 'parent' && selection.parent.id === parent.id
const children = childrenByParent[parent.id] ?? []
return (
<div key={parent.id}>
<div
className={cn(
'group flex items-center gap-1 rounded-md py-1 pl-1 pr-2 transition-colors',
isParentActive
? 'bg-sidebar-accent text-sidebar-accent-foreground shadow-[inset_0_0_0_1px_rgba(34,211,238,0.35)]'
: 'hover:bg-accent/60'
)}
>
<button
type="button"
onClick={() => toggleExpand(parent.id)}
className="flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground"
aria-label={isExpanded ? 'Collapse page' : 'Expand page'}
>
{isExpanded ? (
<ChevronDown className="h-3.5 w-3.5" />
) : (
<ChevronRight className="h-3.5 w-3.5" />
)}
</button>
<button
type="button"
onClick={() => selectParent(parent)}
className={cn(
'flex min-w-0 flex-1 items-center gap-2 py-0.5 text-left text-sm',
isParentActive ? 'font-medium' : 'font-normal'
)}
>
<FileText className="h-3.5 w-3.5 shrink-0 text-primary/60" />
<span className="truncate">{parent.title || '(untitled page)'}</span>
</button>
<span className="text-[10px] text-muted-foreground">{children.length}</span>
</div>
{isExpanded ? (
<div className="ml-[13px] border-l border-border pl-2.5">
{children.length === 0 ? (
<p className="py-1.5 pl-2 text-[11px] italic text-muted-foreground">
No child pages yet
</p>
) : (
children.map((child) => {
const isChildActive =
selection !== null &&
selection.kind === 'child' &&
selection.child.id === child.id
return (
<button
key={child.id}
type="button"
onClick={() => selectChild(child)}
className={cn(
'flex w-full items-center gap-2 rounded-md py-1.5 pl-2 pr-2 text-left text-sm transition-colors',
isChildActive
? 'bg-sidebar-accent text-sidebar-accent-foreground shadow-[inset_0_0_0_1px_rgba(34,211,238,0.35)]'
: 'text-sidebar-foreground/70 hover:bg-accent/60'
)}
>
<FileText className="h-3.5 w-3.5 shrink-0 text-primary/40" />
<span className="truncate">{child.title || '(untitled child)'}</span>
</button>
)
})
)}
</div>
) : null}
</div>
)
})
)}
</div>
</>
) : null}
</nav>
<Separator className="bg-sidebar-border" />
<div className="px-3 py-3">
{isAuthenticated ? (
<Button variant="outline" size="sm" className="w-full justify-start gap-2" onClick={signOut}>
<LogOut className="h-4 w-4" />
Sign out
</Button>
) : (
<div className="flex flex-col gap-2">
<Button size="sm" asChild>
<Link to="/signup">Sign up</Link>
</Button>
<Button variant="outline" size="sm" asChild>
<Link to="/login">Log in</Link>
</Button>
</div>
)}
</div>
</aside>
)
}