mirror of
https://github.com/crocofied/CoreControl.git
synced 2025-12-17 15:36:50 +00:00
Sidebar Design Update
This commit is contained in:
parent
dec15c0ce0
commit
547212cd9e
@ -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">
|
||||||
<SidebarMenu>
|
<SidebarGroupLabel className="text-xs font-medium text-sidebar-foreground/60 uppercase tracking-wider px-4 mb-2">
|
||||||
{data.navMain.map((item) => (
|
Main Navigation
|
||||||
<SidebarMenuItem key={item.title}>
|
</SidebarGroupLabel>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarGroupContent>
|
||||||
<Link href={item.url} className="font-medium">
|
<SidebarMenu>
|
||||||
{item.icon && <item.icon className="mr-2" />}
|
{data.navMain.map((item) =>
|
||||||
{item.title}
|
item.items?.length ? (
|
||||||
</Link>
|
<Collapsible key={item.title} defaultOpen={hasActiveChild(item.items)} className="group/collapsible">
|
||||||
</SidebarMenuButton>
|
<SidebarMenuItem>
|
||||||
{item.items?.length && (
|
<CollapsibleTrigger asChild>
|
||||||
<SidebarMenuSub>
|
<SidebarMenuButton
|
||||||
{item.items.map((subItem) => (
|
className={cn(
|
||||||
<SidebarMenuSubItem key={subItem.title}>
|
"font-medium transition-all",
|
||||||
<SidebarMenuSubButton
|
(hasActiveChild(item.items) || isActive(item.url)) &&
|
||||||
asChild
|
"text-sidebar-accent-foreground bg-sidebar-accent/50",
|
||||||
isActive={subItem.isActive ?? false}
|
)}
|
||||||
>
|
>
|
||||||
<Link href={subItem.url}>
|
{item.icon && <item.icon className="h-4 w-4" />}
|
||||||
{subItem.icon && <subItem.icon className="mr-2" />}
|
<span>{item.title}</span>
|
||||||
{subItem.title}
|
<ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
||||||
</Link>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuSubButton>
|
</CollapsibleTrigger>
|
||||||
</SidebarMenuSubItem>
|
<CollapsibleContent>
|
||||||
))}
|
<SidebarMenuSub>
|
||||||
</SidebarMenuSub>
|
{item.items.map((subItem) => (
|
||||||
)}
|
<SidebarMenuSubItem key={subItem.title}>
|
||||||
</SidebarMenuItem>
|
<SidebarMenuSubButton asChild isActive={isActive(subItem.url)} className="transition-all">
|
||||||
))}
|
<Link href={subItem.url} className="flex items-center">
|
||||||
</SidebarMenu>
|
{subItem.icon && <subItem.icon className="h-3.5 w-3.5 mr-2" />}
|
||||||
|
<span>{subItem.title}</span>
|
||||||
|
</Link>
|
||||||
|
</SidebarMenuSubButton>
|
||||||
|
</SidebarMenuSubItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenuSub>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</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>
|
||||||
|
</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 />
|
||||||
|
|||||||
33
components/ui/collapsible.tsx
Normal file
33
components/ui/collapsible.tsx
Normal 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
20
package-lock.json
generated
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user