mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-01 08:34:53 +02:00
move improvements to layout
This commit is contained in:
parent
8c0e4d2d8c
commit
3bab90891f
9 changed files with 199 additions and 218 deletions
|
@ -47,24 +47,22 @@ export function Breadcrumbs() {
|
|||
});
|
||||
|
||||
return (
|
||||
<div className="border-b px-4 py-2 overflow-x-auto scrollbar-hide bg-card">
|
||||
<nav className="flex items-center space-x-1 text-sm text-muted-foreground whitespace-nowrap">
|
||||
{breadcrumbs.map((crumb, index) => (
|
||||
<div key={crumb.href} className="flex items-center whitespace-nowrap">
|
||||
{index !== 0 && <ChevronRight className="h-4 w-4" />}
|
||||
<Link
|
||||
href={crumb.href}
|
||||
className={cn(
|
||||
"ml-1 hover:text-foreground whitespace-nowrap",
|
||||
index === breadcrumbs.length - 1 &&
|
||||
"text-foreground font-medium"
|
||||
)}
|
||||
>
|
||||
{crumb.label}
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<nav className="flex items-center space-x-1 text-muted-foreground">
|
||||
{breadcrumbs.map((crumb, index) => (
|
||||
<div key={crumb.href} className="flex items-center flex-nowrap">
|
||||
{index !== 0 && <ChevronRight className="h-4 w-4 flex-shrink-0" />}
|
||||
<Link
|
||||
href={crumb.href}
|
||||
className={cn(
|
||||
"ml-1 hover:text-foreground whitespace-nowrap",
|
||||
index === breadcrumbs.length - 1 &&
|
||||
"text-foreground font-medium"
|
||||
)}
|
||||
>
|
||||
{crumb.label}
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import Image from "next/image";
|
||||
|
||||
interface HeaderProps {
|
||||
orgId?: string;
|
||||
orgs?: any;
|
||||
}
|
||||
|
||||
export function Header({ orgId, orgs }: HeaderProps) {
|
||||
const { env } = useEnvContext();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<Image
|
||||
src="/logo/pangolin_orange.svg"
|
||||
alt="Pangolin Logo"
|
||||
width={34}
|
||||
height={34}
|
||||
/>
|
||||
<span className="font-[Space_Grotesk] font-bold text-2xl text-neutral-500">Pangolin</span>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
|
@ -1,16 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Header } from "@app/components/Header";
|
||||
import { SidebarNav } from "@app/components/SidebarNav";
|
||||
import { TopBar } from "@app/components/TopBar";
|
||||
import { OrgSelector } from "@app/components/OrgSelector";
|
||||
import { cn } from "@app/lib/cn";
|
||||
import { ListOrgsResponse } from "@server/routers/org";
|
||||
import SupporterStatus from "@app/components/SupporterStatus";
|
||||
import { Separator } from "@app/components/ui/separator";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { ExternalLink, Menu, X, Server } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import ProfileIcon from "@app/components/ProfileIcon";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
|
@ -61,139 +60,185 @@ export function Layout({
|
|||
const { user } = useUserContext();
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Mobile Menu Button */}
|
||||
{showSidebar && (
|
||||
<div className="md:hidden fixed top-4 left-4 z-50">
|
||||
<Sheet
|
||||
open={isMobileMenuOpen}
|
||||
onOpenChange={setIsMobileMenuOpen}
|
||||
>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Menu className="h-6 w-6" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent
|
||||
side="left"
|
||||
className="w-64 p-0 flex flex-col h-full"
|
||||
>
|
||||
<SheetTitle className="sr-only">
|
||||
Navigation Menu
|
||||
</SheetTitle>
|
||||
<SheetDescription className="sr-only">
|
||||
Main navigation menu for the application
|
||||
</SheetDescription>
|
||||
{showHeader && (
|
||||
<div className="flex h-16 items-center border-b px-4 shrink-0">
|
||||
<Header orgId={orgId} orgs={orgs} />
|
||||
<div className="flex flex-col h-screen overflow-hidden">
|
||||
{/* Full width header */}
|
||||
{showHeader && (
|
||||
<div className="border-b shrink-0 bg-card">
|
||||
<div className="h-16 flex items-center px-4">
|
||||
<div className="flex items-center gap-4">
|
||||
{showSidebar && (
|
||||
<div className="md:hidden">
|
||||
<Sheet
|
||||
open={isMobileMenuOpen}
|
||||
onOpenChange={setIsMobileMenuOpen}
|
||||
>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Menu className="h-6 w-6" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent
|
||||
side="left"
|
||||
className="w-64 p-0 flex flex-col h-full"
|
||||
>
|
||||
<SheetTitle className="sr-only">
|
||||
Navigation Menu
|
||||
</SheetTitle>
|
||||
<SheetDescription className="sr-only">
|
||||
Main navigation menu for the
|
||||
application
|
||||
</SheetDescription>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="p-4">
|
||||
<SidebarNav
|
||||
items={navItems}
|
||||
onItemClick={() =>
|
||||
setIsMobileMenuOpen(
|
||||
false
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{!isAdminPage && (
|
||||
<div className="p-4 border-t">
|
||||
<Link
|
||||
href="/admin"
|
||||
className="flex items-center gap-3 text-muted-foreground hover:text-foreground transition-colors px-3 py-2 rounded-md w-full"
|
||||
onClick={() =>
|
||||
setIsMobileMenuOpen(false)
|
||||
}
|
||||
>
|
||||
<Server className="h-4 w-4" />
|
||||
Server Admin
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 space-y-4 border-t shrink-0">
|
||||
<SupporterStatus />
|
||||
<OrgSelector
|
||||
orgId={orgId}
|
||||
orgs={orgs}
|
||||
/>
|
||||
{env?.app?.version && (
|
||||
<div className="text-xs text-muted-foreground text-center">
|
||||
v{env.app.version}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<SidebarNav
|
||||
items={navItems}
|
||||
onItemClick={() =>
|
||||
setIsMobileMenuOpen(false)
|
||||
}
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center hidden md:block"
|
||||
>
|
||||
<Image
|
||||
src="/logo/pangolin_orange.svg"
|
||||
alt="Pangolin Logo"
|
||||
width={35}
|
||||
height={35}
|
||||
/>
|
||||
{!isAdminPage && (
|
||||
<div className="mt-8 pt-4 border-t">
|
||||
<Link
|
||||
href="/admin"
|
||||
className="flex items-center justify-center gap-2 text-sm font-medium text-primary hover:text-primary/80 transition-colors px-4 py-2 rounded-md bg-primary/10 hover:bg-primary/20 w-full"
|
||||
onClick={() =>
|
||||
setIsMobileMenuOpen(false)
|
||||
}
|
||||
>
|
||||
<Server className="h-4 w-4" />
|
||||
Server Admin
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
{showBreadcrumbs && (
|
||||
<div className="hidden md:block overflow-x-auto scrollbar-hide">
|
||||
<Breadcrumbs />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showTopBar && (
|
||||
<div className="ml-auto flex items-center justify-end md:justify-between">
|
||||
<div className="hidden md:flex items-center space-x-3 mr-6">
|
||||
<Link
|
||||
href="https://docs.fossorial.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
<Link
|
||||
href="mailto:support@fossorial.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Support
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<ProfileIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 space-y-4 border-t shrink-0">
|
||||
<SupporterStatus />
|
||||
<OrgSelector orgId={orgId} orgs={orgs} />
|
||||
)}
|
||||
</div>
|
||||
{showBreadcrumbs && (
|
||||
<div className="md:hidden px-4 pb-2 overflow-x-auto scrollbar-hide">
|
||||
<Breadcrumbs />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* Desktop Sidebar */}
|
||||
{showSidebar && (
|
||||
<div className="hidden md:flex w-64 border-r bg-card flex-col h-full shrink-0">
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="p-4">
|
||||
<SidebarNav items={navItems} />
|
||||
</div>
|
||||
{!isAdminPage && user.serverAdmin && (
|
||||
<div className="p-4 border-t">
|
||||
<Link
|
||||
href="/admin"
|
||||
className="flex items-center gap-3 text-muted-foreground hover:text-foreground transition-colors px-3 py-2 rounded-md w-full"
|
||||
>
|
||||
<Server className="h-4 w-4" />
|
||||
Server Admin
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 space-y-4 border-t shrink-0">
|
||||
<SupporterStatus />
|
||||
<OrgSelector orgId={orgId} orgs={orgs} />
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs text-muted-foreground text-center">
|
||||
<Link
|
||||
href="https://github.com/fosrl/pangolin"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-1"
|
||||
>
|
||||
Open Source
|
||||
<ExternalLink size={12} />
|
||||
</Link>
|
||||
</div>
|
||||
{env?.app?.version && (
|
||||
<div className="text-xs text-muted-foreground text-center">
|
||||
v{env.app.version}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Desktop Sidebar */}
|
||||
{showSidebar && (
|
||||
<div className="hidden md:flex w-64 border-r bg-card flex-col h-full shrink-0">
|
||||
{showHeader && (
|
||||
<div className="flex h-16 items-center border-b px-4 shrink-0">
|
||||
<Header orgId={orgId} orgs={orgs} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main content */}
|
||||
<div
|
||||
className={cn(
|
||||
"flex-1 flex flex-col h-full min-w-0",
|
||||
!showSidebar && "w-full"
|
||||
)}
|
||||
<div className="flex-1 overflow-y-auto p-4 flex flex-col">
|
||||
<div className="flex-1">
|
||||
<SidebarNav items={navItems} />
|
||||
>
|
||||
<main className="flex-1 overflow-y-auto p-3 md:p-6 w-full">
|
||||
<div className="container mx-auto max-w-12xl">
|
||||
{children}
|
||||
</div>
|
||||
{!isAdminPage && user.serverAdmin && (
|
||||
<div className="mt-8 pt-4 border-t">
|
||||
<Link
|
||||
href="/admin"
|
||||
className="flex items-center justify-center gap-2 text-sm font-medium text-primary hover:text-primary/80 transition-colors px-4 py-2 rounded-md bg-primary/10 hover:bg-primary/20 w-full"
|
||||
>
|
||||
<Server className="h-4 w-4" />
|
||||
Server Admin
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 space-y-4 border-t shrink-0">
|
||||
<SupporterStatus />
|
||||
<OrgSelector orgId={orgId} orgs={orgs} />
|
||||
<div className="space-y-2">
|
||||
<div className="text-xs text-muted-foreground text-center">
|
||||
<Link
|
||||
href="https://github.com/fosrl/pangolin"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-1"
|
||||
>
|
||||
Open Source
|
||||
<ExternalLink size={12} />
|
||||
</Link>
|
||||
</div>
|
||||
{env?.app?.version && (
|
||||
<div className="text-xs text-muted-foreground text-center">
|
||||
v{env.app.version}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main content */}
|
||||
<div
|
||||
className={cn(
|
||||
"flex-1 flex flex-col h-full min-w-0",
|
||||
!showSidebar && "w-full"
|
||||
)}
|
||||
>
|
||||
{showTopBar && (
|
||||
<div className="h-16 border-b shrink-0 bg-card">
|
||||
<div className="flex h-full items-center justify-end px-4">
|
||||
<TopBar orgId={orgId} orgs={orgs} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showBreadcrumbs && <Breadcrumbs />}
|
||||
<main className="flex-1 overflow-y-auto p-3 md:p-6 w-full">
|
||||
<div className="container mx-auto max-w-12xl">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -66,7 +66,7 @@ export default function ProfileIcon() {
|
|||
<Enable2FaForm open={openEnable2fa} setOpen={setOpenEnable2fa} />
|
||||
<Disable2FaForm open={openDisable2fa} setOpen={setOpenDisable2fa} />
|
||||
|
||||
<div className="flex items-center md:gap-4 grow min-w-0 gap-2 md:gap-0">
|
||||
<div className="flex items-center md:gap-2 grow min-w-0 gap-2 md:gap-0">
|
||||
<span className="truncate max-w-full font-medium min-w-0">
|
||||
{user.email || user.name || user.username}
|
||||
</span>
|
||||
|
|
|
@ -129,22 +129,20 @@ export function SidebarNav({
|
|||
aria-disabled={disabled}
|
||||
>
|
||||
{item.icon && (
|
||||
<span className="mr-3 opacity-70">
|
||||
{item.icon}
|
||||
</span>
|
||||
<span className="mr-3">{item.icon}</span>
|
||||
)}
|
||||
{item.title}
|
||||
</Link>
|
||||
{hasChildren && (
|
||||
<button
|
||||
onClick={() => toggleItem(hydratedHref)}
|
||||
className="p-2 rounded-md opacity-70 hover:opacity-100"
|
||||
className="p-2 rounded-md text-muted-foreground hover:text-foreground cursor-pointer"
|
||||
disabled={disabled}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
<ChevronDown className="h-5 w-5" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
<ChevronRight className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import ProfileIcon from "@app/components/ProfileIcon";
|
||||
import Link from "next/link";
|
||||
|
||||
interface TopBarProps {
|
||||
orgId?: string;
|
||||
orgs?: any;
|
||||
}
|
||||
|
||||
export function TopBar({ orgId, orgs }: TopBarProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-end md:justify-between w-full h-full">
|
||||
<div className="hidden md:flex items-center space-x-4">
|
||||
<Link
|
||||
href="https://docs.fossorial.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
<Link
|
||||
href="mailto:support@fossorial.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Support
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<ProfileIcon />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
|
|||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-99 data-[state=open]:zoom-in-99 data-[state=closed]:slide-out-to-top-[10%] data-[state=open]:slide-in-from-bottom-[10%] sm:rounded-lg",
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-card p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-99 data-[state=open]:zoom-in-99 data-[state=closed]:slide-out-to-top-[5%] data-[state=open]:slide-in-from-bottom-[5%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue