Import source: src/components/shell/header

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:10:58 +03:30
parent 01d7090d89
commit 19c4661b6e
+215
View File
@@ -0,0 +1,215 @@
"use client";
import Link from "next/link";
import { useQuery } from "@tanstack/react-query";
import {
Bell,
CircleHelp,
Command,
LogOut,
Menu,
MonitorCog,
Moon,
Search,
Sun,
UserCircle,
} from "lucide-react";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { panelService } from "@/api/services/panel-service";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Logo } from "@/components/common/logo";
import { cn } from "@/lib/utils";
import { useUiStore } from "@/stores/ui-store";
export function Header() {
const { data } = useQuery({
queryKey: ["dashboard-summary"],
queryFn: () => panelService.dashboard.summary(),
});
const setCommandOpen = useUiStore((state) => state.setCommandOpen);
const setTheme = useUiStore((state) => state.setTheme);
const setDensity = useUiStore((state) => state.setDensity);
const setMobileSidebarOpen = useUiStore((state) => state.setMobileSidebarOpen);
const collapsed = useUiStore((state) => state.sidebarCollapsed);
const summary = data?.data;
return (
<header
className={cn(
"glass-bar sticky top-2 z-20 mx-2 flex h-[var(--header-height)] items-center gap-2 rounded-xl border border-border/60 px-3 transition-[margin] sm:gap-3 lg:mr-3",
collapsed ? "lg:ml-[96px]" : "lg:ml-[calc(var(--sidebar-width)+24px)]",
)}
>
<Button
className="lg:hidden"
size="icon"
variant="ghost"
onClick={() => setMobileSidebarOpen(true)}
aria-label="Open navigation"
>
<Menu className="h-4 w-4" />
</Button>
<Link href="/dashboard" className="lg:hidden">
<Logo compact />
</Link>
<div className="relative hidden min-w-44 flex-1 md:block">
<Search className="pointer-events-none absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
className="pl-8"
placeholder="Search tools, domains, files, users"
onFocus={() => setCommandOpen(true)}
/>
</div>
<Button
variant="outline"
size="sm"
className="ml-auto md:ml-0"
onClick={() => setCommandOpen(true)}
>
<Command className="h-4 w-4" />
<kbd className="hidden border-border/70 sm:inline-flex">Ctrl K</kbd>
</Button>
<Select defaultValue={summary?.account.id ?? "acct-main"}>
<SelectTrigger className="hidden w-44 xl:flex">
<SelectValue placeholder="Account" />
</SelectTrigger>
<SelectContent>
{summary?.accounts.map((account) => (
<SelectItem key={account.id} value={account.id}>
{account.username}
</SelectItem>
))}
</SelectContent>
</Select>
<Select defaultValue={summary?.server.id ?? "srv-dal-01"}>
<SelectTrigger className="hidden w-48 xl:flex">
<SelectValue placeholder="Server" />
</SelectTrigger>
<SelectContent>
{summary?.servers.map((server) => (
<SelectItem key={server.id} value={server.id}>
{server.name}
</SelectItem>
))}
</SelectContent>
</Select>
<HeaderMenu
icon={<Bell className="h-4 w-4" />}
label="Notifications"
items={[
"Email queue has deferred messages",
"Backup verified",
"Certificate renewal queued",
]}
/>
<HeaderMenu
icon={<CircleHelp className="h-4 w-4" />}
label="Help"
items={["Documentation", "Keyboard shortcuts", "Support tickets"]}
/>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button size="icon" variant="ghost" aria-label="Theme selector">
<Sun className="h-4 w-4 dark:hidden" />
<Moon className="hidden h-4 w-4 dark:block" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content
align="end"
className="glass-modal z-50 w-44 rounded-lg p-1 data-[state=open]:animate-scale-in"
>
{(["light", "dark", "system"] as const).map((theme) => (
<DropdownMenu.Item
key={theme}
className="rounded-md px-2 py-1.5 text-sm capitalize outline-none transition-colors hover:bg-primary/10 hover:text-primary"
onSelect={() => setTheme(theme)}
>
{theme}
</DropdownMenu.Item>
))}
<DropdownMenu.Separator className="my-1 h-px bg-border/70" />
<DropdownMenu.Item
className="rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-primary/10 hover:text-primary"
onSelect={() => setDensity("compact")}
>
Compact density
</DropdownMenu.Item>
<DropdownMenu.Item
className="rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-primary/10 hover:text-primary"
onSelect={() => setDensity("comfortable")}
>
Comfortable density
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button variant="outline" size="sm">
<UserCircle className="h-4 w-4" />
<span className="hidden md:inline">{summary?.user.name ?? "Account"}</span>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content
align="end"
className="glass-modal z-50 w-56 rounded-lg p-1 data-[state=open]:animate-scale-in"
>
<DropdownMenu.Label className="px-2 py-1.5 text-xs text-muted-foreground">
{summary?.user.email}
</DropdownMenu.Label>
<DropdownMenu.Item className="rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-primary/10 hover:text-primary">
<MonitorCog className="mr-2 inline h-4 w-4" />
Profile settings
</DropdownMenu.Item>
<DropdownMenu.Item className="rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-primary/10 hover:text-primary">
<LogOut className="mr-2 inline h-4 w-4" />
<Link href="/logout">Logout</Link>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</header>
);
}
function HeaderMenu({
icon,
label,
items,
}: {
icon: React.ReactNode;
label: string;
items: string[];
}) {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button size="icon" variant="ghost" aria-label={label}>
{icon}
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content
align="end"
className="glass-modal z-50 w-72 rounded-lg p-1 data-[state=open]:animate-scale-in"
>
<DropdownMenu.Label className="px-2 py-1.5 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
{label}
</DropdownMenu.Label>
{items.map((item) => (
<DropdownMenu.Item
key={item}
className="rounded-md px-2 py-2 text-sm outline-none transition-colors hover:bg-primary/10 hover:text-primary"
>
{item}
</DropdownMenu.Item>
))}
</DropdownMenu.Content>
</DropdownMenu.Root>
);
}