Skip to content

Commit 729f49d

Browse files
committed
cleaning up structure similar to react starter kit
1 parent 010885b commit 729f49d

29 files changed

+746
-125
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import { SidebarInset } from '@/components/ui/sidebar'
3+
4+
interface Props {
5+
variant?: 'header' | 'sidebar'
6+
class?: string
7+
}
8+
9+
defineProps<Props>()
10+
</script>
11+
12+
<template>
13+
<SidebarInset v-if="variant === 'sidebar'" :class="class">
14+
<slot />
15+
</SidebarInset>
16+
<main
17+
v-else
18+
class="flex h-full flex-1 flex-col gap-4 rounded-xl max-w-7xl mx-auto w-full"
19+
:class="class"
20+
>
21+
<slot />
22+
</main>
23+
</template>
Lines changed: 186 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,197 @@
11
<script setup lang="ts">
2-
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from '@/components/ui/breadcrumb';
3-
import { SidebarTrigger } from '@/components/ui/sidebar';
4-
import { type BreadcrumbItem as BreadcrumbItemType } from '@/types';
2+
import { Link, usePage } from '@inertiajs/vue3'
3+
import type { BreadcrumbItem } from '@/types'
4+
import AppLogo from '@/components/AppLogo.vue'
5+
import AppLogoIcon from '@/components/AppLogoIcon.vue'
6+
import { Button } from "@/components/ui/button"
7+
import {
8+
NavigationMenu,
9+
NavigationMenuItem,
10+
NavigationMenuLink,
11+
NavigationMenuList,
12+
navigationMenuTriggerStyle,
13+
} from '@/components/ui/navigation-menu'
14+
import {
15+
DropdownMenu,
16+
DropdownMenuContent,
17+
DropdownMenuTrigger,
18+
} from '@/components/ui/dropdown-menu'
19+
import {
20+
Tooltip,
21+
TooltipContent,
22+
TooltipProvider,
23+
TooltipTrigger,
24+
} from '@/components/ui/tooltip'
25+
import type { NavItem } from '@/types'
26+
import {
27+
Sheet,
28+
SheetContent,
29+
SheetHeader,
30+
SheetTrigger,
31+
} from '@/components/ui/sheet'
32+
import type { SharedData } from '@/types'
33+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
34+
import { Menu, ChevronDown, FolderGit2, BookOpenText, Search, LayoutGrid } from 'lucide-vue-next'
35+
import { useInitials } from '@/composables/useInitials.ts'
36+
import { cn } from '@/lib/utils'
37+
import UserMenuContent from '@/components/UserMenuContent.vue'
38+
import Icon from '@/components/Icon.vue'
539
640
interface Props {
7-
breadcrumbs?: BreadcrumbItemType[];
41+
breadcrumbs?: BreadcrumbItem[]
842
}
943
1044
const props = withDefaults(defineProps<Props>(), {
11-
breadcrumbs: () => [],
12-
});
45+
breadcrumbs: () => []
46+
})
47+
48+
const mainNavItems: NavItem[] = [
49+
{
50+
title: 'Dashboard',
51+
url: '/dashboard',
52+
icon: LayoutGrid,
53+
},
54+
]
55+
56+
const rightNavItems: NavItem[] = [
57+
{
58+
title: 'Repository',
59+
url: 'https://github.com/laravel/vue-starter-kit',
60+
icon: FolderGit2,
61+
},
62+
{
63+
title: 'Documentation',
64+
url: 'https://laravel.com/docs/starter-kits',
65+
icon: BookOpenText,
66+
},
67+
]
68+
69+
const activeItemStyles = "bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100"
70+
71+
const { auth } = usePage<SharedData>().props
72+
const getInitials = useInitials()
1373
</script>
1474

1575
<template>
16-
<header
17-
class="flex h-16 shrink-0 items-center gap-2 px-4 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12"
18-
>
19-
<div class="flex items-center gap-2">
20-
<SidebarTrigger class="-ml-1" />
21-
<template v-if="breadcrumbs.length > 0">
22-
<Breadcrumb>
23-
<BreadcrumbList>
24-
<template v-for="(item, index) in breadcrumbs" :key="index">
25-
<BreadcrumbItem>
26-
<template v-if="index === breadcrumbs.length - 1">
27-
<BreadcrumbPage>{{ item.title }}</BreadcrumbPage>
28-
</template>
29-
<template v-else>
30-
<BreadcrumbLink :href="item.href">
76+
<div>
77+
<div class="border-b border-sidebar-border/80">
78+
<div class="flex h-16 items-center px-4 md:max-w-7xl mx-auto">
79+
<!-- Mobile Menu -->
80+
<div class="lg:hidden">
81+
<Sheet>
82+
<SheetTrigger :as-child="true">
83+
<Button variant="ghost" size="icon" class="mr-2 w-[34px] h-[34px]">
84+
<Menu class="h-5 w-5" />
85+
</Button>
86+
</SheetTrigger>
87+
<SheetContent side="left" class="bg-neutral-50 h-full flex flex-col items-stretch w-64">
88+
<SheetHeader class="text-left flex justify-start">
89+
<AppLogoIcon class="h-6 w-6 fill-current text-black dark:text-white" />
90+
</SheetHeader>
91+
<div class="flex flex-col h-full space-y-4 mt-6 flex-1">
92+
<div class="flex flex-col text-sm h-full justify-between">
93+
<div class="flex flex-col space-y-4">
94+
<Link v-for="item in mainNavItems"
95+
:key="item.title"
96+
:href="item.url"
97+
class="flex items-center space-x-2 font-medium">
98+
<Icon v-if="item.icon" :icon-node="item.icon" class="h-5 w-5" />
99+
<span>{{ item.title }}</span>
100+
</Link>
101+
</div>
102+
103+
<div class="flex flex-col space-y-4">
104+
<a v-for="item in rightNavItems"
105+
:key="item.title"
106+
:href="item.url"
107+
target="_blank"
108+
rel="noopener noreferrer"
109+
class="flex items-center space-x-2 font-medium">
110+
<Icon v-if="item.icon" :icon-node="item.icon" class="h-5 w-5" />
111+
<span>{{ item.title }}</span>
112+
</a>
113+
</div>
114+
</div>
115+
</div>
116+
</SheetContent>
117+
</Sheet>
118+
</div>
119+
120+
<Link href="/dashboard" class="flex items-center space-x-2">
121+
<AppLogo />
122+
</Link>
123+
124+
<!-- Desktop Navigation -->
125+
<div class="hidden lg:flex items-center space-x-6 h-full ml-6">
126+
<NavigationMenu class="h-full flex items-stretch">
127+
<NavigationMenuList class="h-full space-x-2 flex items-stretch">
128+
<NavigationMenuItem v-for="(item, index) in mainNavItems"
129+
:key="index"
130+
class="h-full flex items-center relative">
131+
<Link :href="item.url">
132+
<NavigationMenuLink :class="[
133+
navigationMenuTriggerStyle(),
134+
activeItemStyles,
135+
'px-3 h-9 cursor-pointer'
136+
]">
137+
<Icon v-if="item.icon" :icon-node="item.icon" class="mr-2 h-4 w-4" />
31138
{{ item.title }}
32-
</BreadcrumbLink>
33-
</template>
34-
</BreadcrumbItem>
35-
<BreadcrumbSeparator v-if="index !== breadcrumbs.length - 1" />
36-
</template>
37-
</BreadcrumbList>
38-
</Breadcrumb>
39-
</template>
139+
</NavigationMenuLink>
140+
</Link>
141+
<div class="h-0.5 translate-y-px bg-black dark:bg-white w-full absolute bottom-0 left-0"></div>
142+
</NavigationMenuItem>
143+
</NavigationMenuList>
144+
</NavigationMenu>
145+
</div>
146+
147+
<div class="ml-auto flex items-center space-x-2">
148+
<div class="relative flex items-center space-x-1">
149+
<Button variant="ghost" size="icon" class="w-9 h-9 cursor-pointer">
150+
<Search class="h-5 w-5" />
151+
</Button>
152+
<div class="hidden lg:flex space-x-1">
153+
<TooltipProvider v-for="item in rightNavItems"
154+
:key="item.title"
155+
:delay-duration="0">
156+
<Tooltip>
157+
<TooltipTrigger>
158+
<Button variant="ghost" size="icon" :as-child="true" class="w-9 h-9 cursor-pointer">
159+
<a :href="item.url" target="_blank" rel="noopener noreferrer">
160+
<span class="sr-only">{{ item.title }}</span>
161+
<Icon v-if="item.icon" :icon-node="item.icon" class="h-5 w-5" />
162+
</a>
163+
</Button>
164+
</TooltipTrigger>
165+
<TooltipContent>
166+
<p>{{ item.title }}</p>
167+
</TooltipContent>
168+
</Tooltip>
169+
</TooltipProvider>
170+
</div>
171+
</div>
172+
<DropdownMenu>
173+
<DropdownMenuTrigger :as-child="true">
174+
<Button variant="ghost" class="h-9 px-1.5">
175+
<Avatar class="h-7 w-7 overflow-hidden rounded-lg">
176+
<AvatarImage :src="auth.user.avatar" :alt="auth.user.name" />
177+
<AvatarFallback class="rounded-lg text-black dark:text-white">
178+
{{ getInitials(auth.user.name) }}
179+
</AvatarFallback>
180+
</Avatar>
181+
<ChevronDown class="h-4 w-4 hidden lg:block" />
182+
</Button>
183+
</DropdownMenuTrigger>
184+
<DropdownMenuContent class="w-56" align="end">
185+
<UserMenuContent :user="auth.user" />
186+
</DropdownMenuContent>
187+
</DropdownMenu>
188+
</div>
189+
</div>
190+
</div>
191+
<div v-if="props.breadcrumbs.length > 1" class="w-full flex border-b border-sidebar-border/70">
192+
<div class="flex h-12 items-center justify-start w-full px-4 md:max-w-7xl mx-auto text-neutral-500">
193+
<Breadcrumbs :breadcrumbs="props.breadcrumbs" />
194+
</div>
40195
</div>
41-
</header>
42-
</template>
196+
</div>
197+
</template>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script setup lang="ts">
2+
import AppLogoIcon from '@/components/AppLogoIcon.vue'
3+
4+
interface Props {
5+
class?: string
6+
}
7+
8+
defineProps<Props>()
9+
</script>
10+
11+
<template>
12+
<div class="flex aspect-square size-8 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground">
13+
<AppLogoIcon class="size-5 fill-current text-white dark:text-black" />
14+
</div>
15+
<div class="grid flex-1 text-left text-sm leading-tight">
16+
<span class="truncate mb-0.5 font-semibold leading-none">Laravel</span>
17+
<span class="truncate text-[11px] tracking-tight opacity-80 leading-none">Starter Kit</span>
18+
</div>
19+
</template>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script setup lang="ts">
2+
import { ref, onMounted } from 'vue'
3+
import { SidebarProvider } from '@/components/ui/sidebar'
4+
5+
interface Props {
6+
variant?: 'header' | 'sidebar'
7+
}
8+
9+
const props = withDefaults(defineProps<Props>(), {
10+
variant: 'header'
11+
})
12+
13+
const isOpen = ref(true)
14+
15+
onMounted(() => {
16+
isOpen.value = localStorage.getItem('sidebar') !== 'false'
17+
})
18+
19+
const handleSidebarChange = (open: boolean) => {
20+
isOpen.value = open
21+
localStorage.setItem('sidebar', String(open))
22+
}
23+
</script>
24+
25+
<template>
26+
<div v-if="variant === 'header'" class="flex flex-col min-h-screen w-full">
27+
<slot />
28+
</div>
29+
<SidebarProvider
30+
v-else
31+
:default-open="isOpen"
32+
:open="isOpen"
33+
@update:open="handleSidebarChange"
34+
>
35+
<slot />
36+
</SidebarProvider>
37+
</template>

0 commit comments

Comments
 (0)