Files
nyone/resources/js/components/app-header.tsx
2026-05-18 01:01:25 +03:30

251 lines
11 KiB
TypeScript

import { Link, usePage } from '@inertiajs/react';
import {
LayoutGrid,
LifeBuoy,
Menu,
Radio,
Search,
Shield,
} from 'lucide-react';
import AppLogo from '@/components/app-logo';
import AppLogoIcon from '@/components/app-logo-icon';
import { Breadcrumbs } from '@/components/breadcrumbs';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuList,
navigationMenuTriggerStyle,
} from '@/components/ui/navigation-menu';
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { UserMenuContent } from '@/components/user-menu-content';
import { useCurrentUrl } from '@/hooks/use-current-url';
import { useInitials } from '@/hooks/use-initials';
import { useTranslation } from '@/lib/translations';
import { cn } from '@/lib/utils';
import { dashboard, home } from '@/routes';
import { dashboard as adminDashboard } from '@/routes/admin';
import { index as adminSupportIndex } from '@/routes/admin/support';
import { index as supportIndex } from '@/routes/support';
import type { BreadcrumbItem, NavItem } from '@/types';
type Props = {
breadcrumbs?: BreadcrumbItem[];
};
const activeItemStyles = 'bg-accent text-accent-foreground';
export function AppHeader({ breadcrumbs = [] }: Props) {
const page = usePage();
const { auth } = page.props;
const getInitials = useInitials();
const { isCurrentUrl, whenCurrentUrl } = useCurrentUrl();
const { t } = useTranslation();
const mainNavItems: NavItem[] = [
{
title: 'Live',
href: home(),
icon: Radio,
},
{
title: 'Studio',
href: dashboard(),
icon: LayoutGrid,
},
{
title: 'Contact admin',
href: supportIndex(),
icon: LifeBuoy,
},
];
if (auth.user?.is_admin) {
mainNavItems.push({
title: 'Admin',
href: adminDashboard(),
icon: Shield,
});
mainNavItems.push({
title: 'Support',
href: adminSupportIndex(),
icon: LifeBuoy,
});
}
return (
<>
<div className="border-b border-sidebar-border/80 bg-background/95 backdrop-blur">
<div className="mx-auto flex h-16 items-center px-4 md:max-w-7xl">
{/* Mobile Menu */}
<div className="lg:hidden">
<Sheet>
<SheetTrigger asChild>
<Button
variant="ghost"
size="icon"
className="mr-2 h-[34px] w-[34px]"
>
<Menu className="h-5 w-5" />
</Button>
</SheetTrigger>
<SheetContent
side="left"
className="flex h-full w-64 flex-col items-stretch justify-between bg-sidebar"
>
<SheetTitle className="sr-only">
{t('Navigation menu')}
</SheetTitle>
<SheetHeader className="flex justify-start text-left">
<AppLogoIcon className="h-6 w-6 fill-current text-black dark:text-white" />
</SheetHeader>
<div className="flex h-full flex-1 flex-col space-y-4 p-4">
<div className="flex h-full flex-col justify-between text-sm">
<div className="flex flex-col space-y-4">
{mainNavItems.map((item) => (
<Link
key={item.title}
href={item.href}
className="flex items-center space-x-2 font-medium"
>
{item.icon && (
<item.icon className="h-5 w-5" />
)}
<span>{t(item.title)}</span>
</Link>
))}
</div>
<div />
</div>
</div>
</SheetContent>
</Sheet>
</div>
<Link
href={home()}
prefetch
className="flex items-center space-x-2"
>
<AppLogo />
</Link>
{/* Desktop Navigation */}
<div className="ml-6 hidden h-full items-center space-x-6 lg:flex">
<NavigationMenu className="flex h-full items-stretch">
<NavigationMenuList className="flex h-full items-stretch space-x-2">
{mainNavItems.map((item, index) => (
<NavigationMenuItem
key={index}
className="relative flex h-full items-center"
>
<Link
href={item.href}
className={cn(
navigationMenuTriggerStyle(),
whenCurrentUrl(
item.href,
activeItemStyles,
),
'h-9 cursor-pointer px-3',
)}
>
{item.icon && (
<item.icon className="mr-2 h-4 w-4" />
)}
{t(item.title)}
</Link>
{isCurrentUrl(item.href) && (
<div className="absolute bottom-0 left-0 h-0.5 w-full translate-y-px bg-primary"></div>
)}
</NavigationMenuItem>
))}
</NavigationMenuList>
</NavigationMenu>
</div>
<div className="ml-auto flex items-center space-x-2">
<div className="relative flex items-center space-x-1">
<Button
asChild
variant="ghost"
size="icon"
className="group h-9 w-9 cursor-pointer"
>
<Link href={home()}>
<Search className="!size-5 opacity-80 group-hover:opacity-100" />
</Link>
</Button>
<div className="ml-1 hidden gap-1 lg:flex">
<Tooltip>
<TooltipTrigger asChild>
<Link
href={home()}
className="group inline-flex h-9 w-9 items-center justify-center rounded-md bg-transparent p-0 text-sm font-medium text-accent-foreground ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none"
>
<span className="sr-only">
{t('Live directory')}
</span>
<Radio className="size-5 opacity-80 group-hover:opacity-100" />
</Link>
</TooltipTrigger>
<TooltipContent>
<p>{t('Live directory')}</p>
</TooltipContent>
</Tooltip>
</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="size-10 rounded-full p-1"
>
<Avatar className="size-8 overflow-hidden rounded-full">
<AvatarImage
src={auth.user?.avatar ?? undefined}
alt={auth.user?.name}
/>
<AvatarFallback className="rounded-md bg-secondary text-secondary-foreground">
{getInitials(auth.user?.name ?? '')}
</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end">
{auth.user && (
<UserMenuContent user={auth.user} />
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
{breadcrumbs.length > 1 && (
<div className="flex w-full border-b border-sidebar-border/70">
<div className="mx-auto flex h-12 w-full items-center justify-start px-4 text-neutral-500 md:max-w-7xl">
<Breadcrumbs breadcrumbs={breadcrumbs} />
</div>
</div>
)}
</>
);
}