208 lines
7.0 KiB
Vue
208 lines
7.0 KiB
Vue
<script setup lang="ts">
|
|
import { Link, usePage } from '@inertiajs/vue3';
|
|
import {
|
|
Folder,
|
|
LayoutGrid,
|
|
Layers3,
|
|
Menu,
|
|
Shield,
|
|
Users,
|
|
} from 'lucide-vue-next';
|
|
import { computed } from 'vue';
|
|
import AppLogo from '@/components/AppLogo.vue';
|
|
import Breadcrumbs from '@/components/Breadcrumbs.vue';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetHeader,
|
|
SheetTitle,
|
|
SheetTrigger,
|
|
} from '@/components/ui/sheet';
|
|
import UserAvatar from '@/components/ui/avatar/UserAvatar.vue';
|
|
import UserMenuContent from '@/components/UserMenuContent.vue';
|
|
import { useCurrentUrl } from '@/composables/useCurrentUrl';
|
|
import { dashboard } from '@/routes';
|
|
import { index as adminUsers } from '@/routes/admin/users';
|
|
import { index as projectTemplatesIndex } from '@/routes/project-templates';
|
|
import { index as projectsIndex } from '@/routes/projects';
|
|
import { index as teamsIndex } from '@/routes/teams';
|
|
import type { AppHeaderProps, NavItem } from '@/types';
|
|
|
|
const props = withDefaults(defineProps<AppHeaderProps>(), {
|
|
breadcrumbs: () => [],
|
|
});
|
|
|
|
const page = usePage();
|
|
const auth = computed(() => page.props.auth);
|
|
const { isCurrentOrParentUrl } = useCurrentUrl();
|
|
|
|
const mainNavItems = computed<NavItem[]>(() => {
|
|
const items: NavItem[] = [
|
|
{
|
|
title: 'Dashboard',
|
|
href: dashboard(),
|
|
icon: LayoutGrid,
|
|
},
|
|
{
|
|
title: 'Projects',
|
|
href: projectsIndex(),
|
|
icon: Folder,
|
|
},
|
|
];
|
|
|
|
if (page.props.auth.permissions.projectCreate) {
|
|
items.push({
|
|
title: 'Project templates',
|
|
href: projectTemplatesIndex(),
|
|
icon: Layers3,
|
|
});
|
|
}
|
|
|
|
if (page.props.auth.permissions.globalAdmin) {
|
|
items.push({
|
|
title: 'Users',
|
|
href: adminUsers(),
|
|
icon: Shield,
|
|
});
|
|
}
|
|
|
|
if (page.props.auth.permissions.canManageTeams) {
|
|
items.push({
|
|
title: 'Teams',
|
|
href: teamsIndex(),
|
|
icon: Users,
|
|
});
|
|
}
|
|
|
|
return items;
|
|
});
|
|
|
|
const navItemClasses = (item: NavItem): string =>
|
|
[
|
|
'inline-flex h-9 items-center gap-2 rounded-lg px-3 text-sm font-medium transition-colors duration-200',
|
|
isCurrentOrParentUrl(item.href)
|
|
? 'bg-primary text-primary-foreground shadow-[0_8px_20px_rgba(0,110,47,0.16)]'
|
|
: 'text-on-surface-variant hover:bg-surface-container-high hover:text-on-surface',
|
|
].join(' ');
|
|
</script>
|
|
|
|
<template>
|
|
<header
|
|
class="border-outline-variant/80 bg-surface/90 shadow-kanban-nav sticky top-0 z-50 border-b backdrop-blur-md"
|
|
>
|
|
<div
|
|
class="sm:px-container-margin flex h-16 w-full items-center gap-3 px-4"
|
|
>
|
|
<Sheet>
|
|
<SheetTrigger :as-child="true">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
class="text-on-surface-variant hover:bg-surface-container-high hover:text-on-surface size-9 rounded-lg lg:hidden"
|
|
>
|
|
<Menu class="size-5" />
|
|
<span class="sr-only">Open navigation</span>
|
|
</Button>
|
|
</SheetTrigger>
|
|
<SheetContent
|
|
side="left"
|
|
class="border-outline-variant bg-surface w-[320px] p-0"
|
|
>
|
|
<SheetTitle class="sr-only">Navigation menu</SheetTitle>
|
|
<SheetHeader class="border-outline-variant border-b p-4">
|
|
<Link
|
|
:href="dashboard()"
|
|
class="flex items-center gap-3"
|
|
>
|
|
<AppLogo />
|
|
</Link>
|
|
</SheetHeader>
|
|
|
|
<div class="flex flex-col gap-5 p-4">
|
|
<nav class="flex flex-col gap-1" aria-label="Main">
|
|
<Link
|
|
v-for="item in mainNavItems"
|
|
:key="item.title"
|
|
:href="item.href"
|
|
:class="navItemClasses(item)"
|
|
>
|
|
<component
|
|
v-if="item.icon"
|
|
:is="item.icon"
|
|
class="size-4"
|
|
/>
|
|
{{ item.title }}
|
|
</Link>
|
|
</nav>
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
|
|
<Link
|
|
:href="dashboard()"
|
|
class="flex shrink-0 items-center gap-2"
|
|
aria-label="KanbanGit dashboard"
|
|
>
|
|
<AppLogo />
|
|
</Link>
|
|
|
|
<nav
|
|
class="ml-3 hidden min-w-0 flex-1 items-center gap-1 lg:flex"
|
|
aria-label="Main"
|
|
>
|
|
<Link
|
|
v-for="item in mainNavItems"
|
|
:key="item.title"
|
|
:href="item.href"
|
|
:class="navItemClasses(item)"
|
|
>
|
|
<component
|
|
v-if="item.icon"
|
|
:is="item.icon"
|
|
class="size-4"
|
|
/>
|
|
<span class="truncate">{{ item.title }}</span>
|
|
</Link>
|
|
</nav>
|
|
|
|
<div class="ml-auto flex items-center gap-3">
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger :as-child="true">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
class="focus-visible:ring-primary/30 hover:bg-surface-container-high size-10 rounded-full p-1 focus-visible:ring-2"
|
|
>
|
|
<UserAvatar rounded />
|
|
<span class="sr-only">Open user menu</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
class="border-outline-variant bg-popover w-56"
|
|
>
|
|
<UserMenuContent :user="auth.user" />
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="props.breadcrumbs.length > 1"
|
|
class="border-outline-variant/70 bg-surface-container-low/70 border-t"
|
|
>
|
|
<div
|
|
class="text-on-surface-variant sm:px-container-margin flex h-11 w-full items-center px-4 text-sm"
|
|
>
|
|
<Breadcrumbs :breadcrumbs="breadcrumbs" />
|
|
</div>
|
|
</div>
|
|
</header>
|
|
</template>
|