Sidebar Design Update

This commit is contained in:
headlessdev 2025-04-18 00:07:21 +02:00
parent dec15c0ce0
commit 547212cd9e
4 changed files with 149 additions and 63 deletions

View File

@ -1,10 +1,25 @@
import * as React from "react" "use client"
import type * as React from "react"
import Image from "next/image" import Image from "next/image"
import { AppWindow, Settings, LayoutDashboardIcon, Briefcase, Server, Network, Activity } from "lucide-react" import { usePathname } from "next/navigation"
import {
AppWindow,
Settings,
LayoutDashboardIcon,
Briefcase,
Server,
Network,
Activity,
LogOut,
ChevronDown,
} from "lucide-react"
import { import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
SidebarGroup, SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader, SidebarHeader,
SidebarMenu, SidebarMenu,
SidebarMenuButton, SidebarMenuButton,
@ -13,12 +28,15 @@ import {
SidebarMenuSubButton, SidebarMenuSubButton,
SidebarMenuSubItem, SidebarMenuSubItem,
SidebarRail, SidebarRail,
SidebarFooter,
} from "@/components/ui/sidebar" } from "@/components/ui/sidebar"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import Link from "next/link" import Link from "next/link"
import Cookies from "js-cookie" import Cookies from "js-cookie"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import packageJson from "@/package.json" import packageJson from "@/package.json"
import { cn } from "@/lib/utils"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
interface NavItem { interface NavItem {
title: string title: string
@ -33,7 +51,7 @@ const data: { navMain: NavItem[] } = {
{ {
title: "Dashboard", title: "Dashboard",
icon: LayoutDashboardIcon, icon: LayoutDashboardIcon,
url: "/dashboard" url: "/dashboard",
}, },
{ {
title: "My Infrastructure", title: "My Infrastructure",
@ -72,23 +90,38 @@ const data: { navMain: NavItem[] } = {
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const router = useRouter() const router = useRouter()
const pathname = usePathname()
const logout = async () => { const logout = async () => {
Cookies.remove('token') Cookies.remove("token")
router.push("/") router.push("/")
} }
// Check if a path is active (exact match or starts with path for parent items)
const isActive = (path: string) => {
if (path === "#") return false
return pathname === path || (path !== "/dashboard" && pathname?.startsWith(path))
}
// Check if any child item is active
const hasActiveChild = (items?: NavItem[]) => {
if (!items) return false
return items.some((item) => isActive(item.url))
}
return ( return (
<Sidebar {...props}> <Sidebar {...props}>
<SidebarHeader> <SidebarHeader className="border-b border-sidebar-border/30 pb-2">
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem> <SidebarMenuItem>
<SidebarMenuButton size="lg" asChild> <SidebarMenuButton size="lg" asChild className="gap-3">
<a href="https://github.com/crocofied/corecontrol"> <a href="https://github.com/crocofied/corecontrol" className="transition-all hover:opacity-80">
<Image src="/logo.png" width={48} height={48} alt="Logo"/> <div className="flex items-center justify-center rounded-lg overflow-hidden bg-gradient-to-br from-teal-500 to-emerald-600 shadow-sm">
<Image src="/logo.png" width={48} height={48} alt="CoreControl Logo" className="object-cover" />
</div>
<div className="flex flex-col gap-0.5 leading-none"> <div className="flex flex-col gap-0.5 leading-none">
<span className="font-semibold">CoreControl</span> <span className="font-semibold text-base">CoreControl</span>
<span className="">v{packageJson.version}</span> <span className="text-xs text-sidebar-foreground/70">v{packageJson.version}</span>
</div> </div>
</a> </a>
</SidebarMenuButton> </SidebarMenuButton>
@ -96,44 +129,77 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</SidebarMenu> </SidebarMenu>
</SidebarHeader> </SidebarHeader>
<SidebarContent className="flex flex-col h-full"> <SidebarContent className="flex flex-col h-full py-4">
<SidebarGroup className="flex-grow"> <SidebarGroup className="flex-grow">
<SidebarGroupLabel className="text-xs font-medium text-sidebar-foreground/60 uppercase tracking-wider px-4 mb-2">
Main Navigation
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
{data.navMain.map((item) => ( {data.navMain.map((item) =>
<SidebarMenuItem key={item.title}> item.items?.length ? (
<SidebarMenuButton asChild> <Collapsible key={item.title} defaultOpen={hasActiveChild(item.items)} className="group/collapsible">
<Link href={item.url} className="font-medium"> <SidebarMenuItem>
{item.icon && <item.icon className="mr-2" />} <CollapsibleTrigger asChild>
{item.title} <SidebarMenuButton
</Link> className={cn(
"font-medium transition-all",
(hasActiveChild(item.items) || isActive(item.url)) &&
"text-sidebar-accent-foreground bg-sidebar-accent/50",
)}
>
{item.icon && <item.icon className="h-4 w-4" />}
<span>{item.title}</span>
<ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" />
</SidebarMenuButton> </SidebarMenuButton>
{item.items?.length && ( </CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub> <SidebarMenuSub>
{item.items.map((subItem) => ( {item.items.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}> <SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton <SidebarMenuSubButton asChild isActive={isActive(subItem.url)} className="transition-all">
asChild <Link href={subItem.url} className="flex items-center">
isActive={subItem.isActive ?? false} {subItem.icon && <subItem.icon className="h-3.5 w-3.5 mr-2" />}
> <span>{subItem.title}</span>
<Link href={subItem.url}>
{subItem.icon && <subItem.icon className="mr-2" />}
{subItem.title}
</Link> </Link>
</SidebarMenuSubButton> </SidebarMenuSubButton>
</SidebarMenuSubItem> </SidebarMenuSubItem>
))} ))}
</SidebarMenuSub> </SidebarMenuSub>
)} </CollapsibleContent>
</SidebarMenuItem> </SidebarMenuItem>
))} </Collapsible>
) : (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
className={cn(
"font-medium transition-all",
isActive(item.url) && "text-sidebar-accent-foreground bg-sidebar-accent/50",
)}
>
<Link href={item.url}>
{item.icon && <item.icon className="h-4 w-4" />}
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
),
)}
</SidebarMenu> </SidebarMenu>
</SidebarGroupContent>
</SidebarGroup> </SidebarGroup>
<div className="p-4"> <SidebarFooter className="border-t border-sidebar-border/30 pt-4 mt-auto">
<Button variant="destructive" className="w-full" onClick={logout}> <Button
variant="outline"
className="w-full justify-start text-destructive hover:text-destructive hover:bg-destructive/10 border-none shadow-none"
onClick={logout}
>
<LogOut className="h-4 w-4 mr-2" />
Logout Logout
</Button> </Button>
</div> </SidebarFooter>
</SidebarContent> </SidebarContent>
<SidebarRail /> <SidebarRail />

View File

@ -0,0 +1,33 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
function Collapsible({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}
function CollapsibleTrigger({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
return (
<CollapsiblePrimitive.CollapsibleTrigger
data-slot="collapsible-trigger"
{...props}
/>
)
}
function CollapsibleContent({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
return (
<CollapsiblePrimitive.CollapsibleContent
data-slot="collapsible-content"
{...props}
/>
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

20
package-lock.json generated
View File

@ -1,18 +1,19 @@
{ {
"name": "corecontrol", "name": "corecontrol",
"version": "0.0.5", "version": "0.0.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "corecontrol", "name": "corecontrol",
"version": "0.0.5", "version": "0.0.6",
"dependencies": { "dependencies": {
"@prisma/client": "^6.6.0", "@prisma/client": "^6.6.0",
"@prisma/extension-accelerate": "^1.3.0", "@prisma/extension-accelerate": "^1.3.0",
"@radix-ui/react-accordion": "^1.2.4", "@radix-ui/react-accordion": "^1.2.4",
"@radix-ui/react-alert-dialog": "^1.1.7", "@radix-ui/react-alert-dialog": "^1.1.7",
"@radix-ui/react-checkbox": "^1.1.5", "@radix-ui/react-checkbox": "^1.1.5",
"@radix-ui/react-collapsible": "^1.1.4",
"@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dialog": "^1.1.7",
"@radix-ui/react-dropdown-menu": "^2.1.7", "@radix-ui/react-dropdown-menu": "^2.1.7",
"@radix-ui/react-label": "^2.1.3", "@radix-ui/react-label": "^2.1.3",
@ -4576,21 +4577,6 @@
"optional": true "optional": true
} }
} }
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "15.3.0",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.0.tgz",
"integrity": "sha512-vHUQS4YVGJPmpjn7r5lEZuMhK5UQBNBRSB+iGDvJjaNk649pTIcRluDWNb9siunyLLiu/LDPHfvxBtNamyuLTw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
} }
} }
} }

View File

@ -14,6 +14,7 @@
"@radix-ui/react-accordion": "^1.2.4", "@radix-ui/react-accordion": "^1.2.4",
"@radix-ui/react-alert-dialog": "^1.1.7", "@radix-ui/react-alert-dialog": "^1.1.7",
"@radix-ui/react-checkbox": "^1.1.5", "@radix-ui/react-checkbox": "^1.1.5",
"@radix-ui/react-collapsible": "^1.1.4",
"@radix-ui/react-dialog": "^1.1.7", "@radix-ui/react-dialog": "^1.1.7",
"@radix-ui/react-dropdown-menu": "^2.1.7", "@radix-ui/react-dropdown-menu": "^2.1.7",
"@radix-ui/react-label": "^2.1.3", "@radix-ui/react-label": "^2.1.3",