Redesign app layout chrome
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import { BookOpen, Folder, LayoutGrid, Menu, Search } from 'lucide-vue-next';
|
||||
import {
|
||||
Folder,
|
||||
LayoutGrid,
|
||||
Layers3,
|
||||
Menu,
|
||||
Search,
|
||||
Shield,
|
||||
Users,
|
||||
} from 'lucide-vue-next';
|
||||
import { computed } from 'vue';
|
||||
import AppLogo from '@/components/AppLogo.vue';
|
||||
import AppLogoIcon from '@/components/AppLogoIcon.vue';
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@@ -11,12 +18,6 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuList,
|
||||
navigationMenuTriggerStyle,
|
||||
} from '@/components/ui/navigation-menu';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
@@ -24,246 +25,215 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@/components/ui/sheet';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import UserAvatar from '@/components/ui/avatar/UserAvatar.vue';
|
||||
import UserMenuContent from '@/components/UserMenuContent.vue';
|
||||
import { useCurrentUrl } from '@/composables/useCurrentUrl';
|
||||
import { toUrl } from '@/lib/utils';
|
||||
import { dashboard } from '@/routes/index';
|
||||
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 { BreadcrumbItem, NavItem } from '@/types';
|
||||
import UserAvatar from '@/components/ui/avatar/UserAvatar.vue';
|
||||
|
||||
type Props = {
|
||||
breadcrumbs?: BreadcrumbItem[];
|
||||
};
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
breadcrumbs: () => [],
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
breadcrumbs?: BreadcrumbItem[];
|
||||
}>(),
|
||||
{
|
||||
breadcrumbs: () => [],
|
||||
},
|
||||
);
|
||||
|
||||
const page = usePage();
|
||||
const auth = computed(() => page.props.auth);
|
||||
const { isCurrentUrl, whenCurrentUrl } = useCurrentUrl();
|
||||
const { isCurrentOrParentUrl } = useCurrentUrl();
|
||||
|
||||
const activeItemStyles =
|
||||
'text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100';
|
||||
const mainNavItems = computed<NavItem[]>(() => {
|
||||
const items: NavItem[] = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
href: dashboard(),
|
||||
icon: LayoutGrid,
|
||||
},
|
||||
{
|
||||
title: 'Projects',
|
||||
href: projectsIndex(),
|
||||
icon: Folder,
|
||||
},
|
||||
];
|
||||
|
||||
const mainNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
href: dashboard(),
|
||||
icon: LayoutGrid,
|
||||
},
|
||||
];
|
||||
if (page.props.auth.permissions.projectCreate) {
|
||||
items.push({
|
||||
title: 'Project templates',
|
||||
href: projectTemplatesIndex(),
|
||||
icon: Layers3,
|
||||
});
|
||||
}
|
||||
|
||||
const rightNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Repository',
|
||||
href: 'https://github.com/laravel/vue-starter-kit',
|
||||
icon: Folder,
|
||||
},
|
||||
{
|
||||
title: 'Documentation',
|
||||
href: 'https://laravel.com/docs/starter-kits#vue',
|
||||
icon: BookOpen,
|
||||
},
|
||||
];
|
||||
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>
|
||||
<div>
|
||||
<div class="border-sidebar-border/80 border-b">
|
||||
<div class="mx-auto flex h-16 items-center px-4 md:max-w-7xl">
|
||||
<!-- Mobile Menu -->
|
||||
<div class="lg:hidden">
|
||||
<Sheet>
|
||||
<SheetTrigger :as-child="true">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="mr-2 h-9 w-9"
|
||||
>
|
||||
<Menu class="h-5 w-5" />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="left" class="w-[300px] p-6">
|
||||
<SheetTitle class="sr-only"
|
||||
>Navigation menu</SheetTitle
|
||||
>
|
||||
<SheetHeader class="flex justify-start text-left">
|
||||
<AppLogoIcon
|
||||
class="size-6 fill-current text-black dark:text-white"
|
||||
/>
|
||||
</SheetHeader>
|
||||
<div
|
||||
class="flex h-full flex-1 flex-col justify-between space-y-4 py-6"
|
||||
>
|
||||
<nav class="-mx-3 space-y-1">
|
||||
<Link
|
||||
v-for="item in mainNavItems"
|
||||
:key="item.title"
|
||||
:href="item.href"
|
||||
class="hover:bg-accent flex items-center gap-x-3 rounded-lg px-3 py-2 text-sm font-medium"
|
||||
:class="
|
||||
whenCurrentUrl(
|
||||
item.href,
|
||||
activeItemStyles,
|
||||
)
|
||||
"
|
||||
>
|
||||
<component
|
||||
v-if="item.icon"
|
||||
:is="item.icon"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
{{ item.title }}
|
||||
</Link>
|
||||
</nav>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<a
|
||||
v-for="item in rightNavItems"
|
||||
:key="item.title"
|
||||
:href="toUrl(item.href)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center space-x-2 text-sm font-medium"
|
||||
>
|
||||
<component
|
||||
v-if="item.icon"
|
||||
:is="item.icon"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<span>{{ item.title }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
|
||||
<Link :href="dashboard()" class="flex items-center gap-x-2">
|
||||
<AppLogo />
|
||||
</Link>
|
||||
|
||||
<!-- Desktop Menu -->
|
||||
<div class="hidden h-full lg:flex lg:flex-1">
|
||||
<NavigationMenu class="ml-10 flex h-full items-stretch">
|
||||
<NavigationMenuList
|
||||
class="flex h-full items-stretch space-x-2"
|
||||
<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"
|
||||
>
|
||||
<NavigationMenuItem
|
||||
v-for="(item, index) in mainNavItems"
|
||||
:key="index"
|
||||
class="relative flex h-full items-center"
|
||||
<AppLogo />
|
||||
</Link>
|
||||
</SheetHeader>
|
||||
|
||||
<div class="flex flex-col gap-5 p-4">
|
||||
<div class="relative">
|
||||
<Search
|
||||
class="text-on-surface-variant pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2"
|
||||
/>
|
||||
<input
|
||||
disabled
|
||||
aria-label="Search"
|
||||
placeholder="Coming soon"
|
||||
class="border-outline-variant bg-surface-container-low text-on-surface placeholder:text-on-surface-variant h-10 w-full rounded-lg border py-2 pl-9 pr-3 text-sm disabled:cursor-not-allowed disabled:opacity-100"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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)"
|
||||
>
|
||||
<Link
|
||||
:class="[
|
||||
navigationMenuTriggerStyle(),
|
||||
whenCurrentUrl(
|
||||
item.href,
|
||||
activeItemStyles,
|
||||
),
|
||||
'h-9 cursor-pointer px-3',
|
||||
]"
|
||||
:href="item.href"
|
||||
>
|
||||
<component
|
||||
v-if="item.icon"
|
||||
:is="item.icon"
|
||||
class="mr-2 h-4 w-4"
|
||||
/>
|
||||
{{ item.title }}
|
||||
</Link>
|
||||
<div
|
||||
v-if="isCurrentUrl(item.href)"
|
||||
class="absolute bottom-0 left-0 h-0.5 w-full translate-y-px bg-black dark:bg-white"
|
||||
></div>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
<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">
|
||||
<div class="relative hidden sm:block">
|
||||
<Search
|
||||
class="text-on-surface-variant pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2"
|
||||
/>
|
||||
<input
|
||||
disabled
|
||||
aria-label="Search"
|
||||
placeholder="Coming soon"
|
||||
class="border-outline-variant bg-surface-container-low text-on-surface placeholder:text-on-surface-variant h-10 w-48 rounded-lg border py-2 pl-9 pr-3 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-100 md:w-64"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center space-x-2">
|
||||
<div class="relative flex items-center space-x-1">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger :as-child="true">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="group h-9 w-9 cursor-pointer"
|
||||
class="focus-visible:ring-primary/30 hover:bg-surface-container-high size-10 rounded-full p-1 focus-visible:ring-2"
|
||||
>
|
||||
<Search
|
||||
class="size-5 opacity-80 group-hover:opacity-100"
|
||||
/>
|
||||
<UserAvatar rounded />
|
||||
<span class="sr-only">Open user menu</span>
|
||||
</Button>
|
||||
|
||||
<div class="hidden space-x-1 lg:flex">
|
||||
<template
|
||||
v-for="item in rightNavItems"
|
||||
:key="item.title"
|
||||
>
|
||||
<TooltipProvider :delay-duration="0">
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
as-child
|
||||
class="group h-9 w-9 cursor-pointer"
|
||||
>
|
||||
<a
|
||||
:href="toUrl(item.href)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span class="sr-only">{{
|
||||
item.title
|
||||
}}</span>
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="size-5 opacity-80 group-hover:opacity-100"
|
||||
/>
|
||||
</a>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ item.title }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger :as-child="true">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="focus-within:ring-primary relative size-10 w-auto rounded-full p-1 focus-within:ring-2"
|
||||
>
|
||||
<UserAvatar rounded />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" class="w-56">
|
||||
<UserMenuContent :user="auth.user" />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</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-sidebar-border/70 flex w-full border-b"
|
||||
class="border-outline-variant/70 bg-surface-container-low/70 border-t"
|
||||
>
|
||||
<div
|
||||
class="mx-auto flex h-12 w-full items-center justify-start px-4 text-neutral-500 md:max-w-7xl"
|
||||
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>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user