Module Update
This commit is contained in:
185
src/modules/layout/components/app-sidebar.tsx
Normal file
185
src/modules/layout/components/app-sidebar.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { ChevronRight } from "lucide-react"
|
||||
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/shared/components/ui/collapsible"
|
||||
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/shared/components/ui/tooltip"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/shared/components/ui/select"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import { useSidebar } from "./sidebar-provider"
|
||||
import { NAV_CONFIG, Role } from "../config/navigation"
|
||||
|
||||
interface AppSidebarProps {
|
||||
mode?: "mobile" | "desktop"
|
||||
}
|
||||
|
||||
export function AppSidebar({ mode }: AppSidebarProps) {
|
||||
const { expanded, toggleSidebar, isMobile } = useSidebar()
|
||||
const pathname = usePathname()
|
||||
|
||||
// MOCK ROLE: In real app, get this from auth context / session
|
||||
const [currentRole, setCurrentRole] = React.useState<Role>("admin")
|
||||
|
||||
const navItems = NAV_CONFIG[currentRole]
|
||||
|
||||
// Ensure consistent state for hydration
|
||||
if (!expanded && mode === 'mobile') return null
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-4">
|
||||
{/* Sidebar Header */}
|
||||
<div className={cn("flex h-16 items-center border-b px-4 transition-all duration-300", !expanded && !isMobile ? "justify-center px-2" : "justify-between")}>
|
||||
{expanded || isMobile ? (
|
||||
<Link href="/" className="flex items-center gap-2 font-bold">
|
||||
<div className="bg-primary text-primary-foreground flex size-8 items-center justify-center rounded-lg">
|
||||
NE
|
||||
</div>
|
||||
<span className="truncate text-lg">Next_Edu</span>
|
||||
</Link>
|
||||
) : (
|
||||
<div className="bg-primary text-primary-foreground flex size-8 items-center justify-center rounded-lg">
|
||||
NE
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Role Switcher (Dev Only - for Demo) */}
|
||||
{(expanded || isMobile) && (
|
||||
<div className="px-4">
|
||||
<label className="text-muted-foreground mb-2 block text-xs font-medium uppercase">
|
||||
View As (Dev Mode)
|
||||
</label>
|
||||
<Select value={currentRole} onValueChange={(v) => setCurrentRole(v as Role)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select Role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="teacher">Teacher</SelectItem>
|
||||
<SelectItem value="student">Student</SelectItem>
|
||||
<SelectItem value="parent">Parent</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Navigation */}
|
||||
<ScrollArea className="flex-1 px-3">
|
||||
<nav className="flex flex-col gap-2 py-4">
|
||||
<TooltipProvider delayDuration={0}>
|
||||
{navItems.map((item, index) => {
|
||||
const isActive = pathname.startsWith(item.href)
|
||||
const hasChildren = item.items && item.items.length > 0
|
||||
|
||||
if (!expanded && !isMobile) {
|
||||
// Collapsed Mode (Icon Only + Tooltip)
|
||||
return (
|
||||
<Tooltip key={index}>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex size-10 items-center justify-center rounded-md transition-colors",
|
||||
isActive && "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||
)}
|
||||
>
|
||||
<item.icon className="size-5" />
|
||||
<span className="sr-only">{item.title}</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{item.title}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
// Expanded Mode
|
||||
if (hasChildren) {
|
||||
return (
|
||||
<Collapsible key={index} defaultOpen={isActive} className="group/collapsible">
|
||||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex w-full items-center justify-between rounded-md p-2 text-sm font-medium transition-colors",
|
||||
isActive && "text-sidebar-accent-foreground"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<item.icon className="size-4" />
|
||||
<span>{item.title}</span>
|
||||
</div>
|
||||
<ChevronRight className="text-muted-foreground size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="data-[state=open]:animate-accordion-down data-[state=closed]:animate-accordion-up overflow-hidden">
|
||||
<div className="ml-6 mt-1 flex flex-col gap-1 border-l pl-2">
|
||||
{item.items?.map((subItem, subIndex) => (
|
||||
<Link
|
||||
key={subIndex}
|
||||
href={subItem.href}
|
||||
className={cn(
|
||||
"text-muted-foreground hover:text-foreground block rounded-md px-2 py-1 text-sm transition-colors",
|
||||
pathname === subItem.href && "text-foreground font-medium"
|
||||
)}
|
||||
>
|
||||
{subItem.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={index}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex items-center gap-2 rounded-md p-2 text-sm font-medium transition-colors",
|
||||
isActive && "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||
)}
|
||||
>
|
||||
<item.icon className="size-4" />
|
||||
<span>{item.title}</span>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</TooltipProvider>
|
||||
</nav>
|
||||
</ScrollArea>
|
||||
|
||||
{/* Sidebar Footer */}
|
||||
<div className="p-4">
|
||||
{!isMobile && (
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="hover:bg-sidebar-accent text-sidebar-foreground flex w-full items-center justify-center rounded-md border p-2 text-sm transition-colors"
|
||||
>
|
||||
{expanded ? "Collapse" : <ChevronRight className="size-4" />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
AppSidebar.displayName = "AppSidebar"
|
||||
Reference in New Issue
Block a user