Skip to content

Commit 8a29c33

Browse files
committed
Add support for a secondary topbar with breadcrumbs and actions.
Introduced a new sub-topbar layout that supports breadcrumbs and actions for improved navigation and user experience. Adjusted existing layouts, styles, and components to accommodate this change and ensure responsiveness across different screen sizes.
1 parent 86a6629 commit 8a29c33

File tree

13 files changed

+202
-40
lines changed

13 files changed

+202
-40
lines changed

resources/js/assets/layout/_main.scss

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33
flex-direction: column;
44
min-height: 100vh;
55
justify-content: space-between;
6-
padding: 6rem 2rem 0 2rem;
76
transition: margin-left var(--layout-section-transition-duration);
87
}
98

9+
.layout-main-container-padding-default {
10+
padding: 6rem 2rem 0 2rem;
11+
}
12+
13+
.layout-main-container-padding-topsubbar {
14+
padding: 10rem 2rem 0 2rem;
15+
}
16+
1017
.layout-main {
1118
flex: 1 1 auto;
1219
padding-bottom: 2rem;

resources/js/assets/layout/_menu.scss

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
.layout-sidebar {
44
position: fixed;
55
width: 20rem;
6-
height: calc(100vh - 8rem);
76
z-index: 999;
87
overflow-y: auto;
98
user-select: none;
10-
top: 6rem;
119
left: 2rem;
1210
transition:
1311
transform var(--layout-section-transition-duration),
@@ -18,6 +16,16 @@
1816
box-shadow: var(--p-card-shadow);
1917
}
2018

19+
.layout-sidebar-margin-default {
20+
height: calc(100vh - 8rem);
21+
top: 6rem;
22+
}
23+
24+
.layout-sidebar-margin-topsubbar {
25+
height: calc(100vh - 12rem);
26+
top: 10rem;
27+
}
28+
2129
.layout-menu {
2230
margin: 0;
2331
padding: 0;

resources/js/assets/layout/_topbar.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,25 @@
9696
}
9797
}
9898

99+
.layout-sub-topbar {
100+
position: fixed;
101+
height: 7.3rem;
102+
z-index: 996;
103+
width: 100%;
104+
padding: 0 2rem;
105+
background-color: var(--surface-card);
106+
transition: left var(--layout-section-transition-duration);
107+
border-bottom: 1px solid var(--surface-border);
108+
align-items: center;
109+
110+
.layout-sub-topbar-content {
111+
padding-top: 4rem;
112+
display: flex;
113+
gap: 1.5rem;
114+
align-items: center;
115+
}
116+
}
117+
99118
@media (max-width: 991px) {
100119
.layout-topbar {
101120
padding: 0 2rem;

resources/js/components/Breadcrumbs.vue

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,16 @@ const home = ref({
2323
</script>
2424

2525
<template>
26-
<Card class="mb-4">
27-
<template #content>
28-
<Breadcrumb :home="home" :model="breadcrumbs">
29-
<template #item="{ item, props }">
30-
<Link v-if="item.route" :href="item.route" custom>
31-
<span :class="[item.icon, 'text-color']" />
32-
<span class="text-primary font-semibold ml-2">{{ item.title }}</span>
33-
</Link>
34-
<a v-else :href="item.url" :target="item.target" v-bind="props.action">
35-
<span :class="[item.icon, 'text-color']" />
36-
<span class="text-surface-700 dark:text-surface-0">{{ item.title }}</span>
37-
</a>
38-
</template>
39-
</Breadcrumb>
26+
<Breadcrumb v-if="breadcrumbs.length" :home="home" :model="breadcrumbs">
27+
<template #item="{ item, props }">
28+
<Link v-if="item.route" :href="item.route" custom>
29+
<span :class="[item.icon, 'text-color']" />
30+
<span class="text-primary font-semibold ml-2 hidden md:inline-block">{{ item.title }}</span>
31+
</Link>
32+
<a v-else :href="item.url" :target="item.target" v-bind="props.action">
33+
<span :class="[item.icon, 'text-color']" />
34+
<span class="text-surface-700 dark:text-surface-0">{{ item.title }}</span>
35+
</a>
4036
</template>
41-
</Card>
37+
</Breadcrumb>
4238
</template>

resources/js/composables/useAppearance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Nora from '@primeuix/themes/nora';
77
const layoutConfig = reactive({
88
preset: localStorage.getItem('layout_preset') || 'Aura',
99
primary: localStorage.getItem('layout_primary') || 'noir',
10-
surface: localStorage.getItem('layout_surface') || 'soho',
10+
surface: localStorage.getItem('layout_surface') || 'zinc',
1111
menuMode: localStorage.getItem('layout_menu_mode') || 'static'
1212
});
1313

resources/js/layouts/AppLayout.vue

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,24 @@ import { computed, ref, watch } from 'vue';
44
import AppFooter from './AppFooter.vue';
55
import AppSidebar from './AppSidebar.vue';
66
import AppTopbar from './AppTopbar.vue';
7-
import type { BreadcrumbItemType } from '@/types';
8-
import Breadcrumbs from '@/components/Breadcrumbs.vue';
7+
import AppSubTopbar from './AppSubTopbar.vue';
8+
import { AppTopSubBarItem, BreadcrumbItemType } from '@/types';
99
1010
interface Props {
1111
breadcrumbs?: BreadcrumbItemType[];
12+
actions?: AppTopSubBarItem[];
1213
}
1314
1415
withDefaults(defineProps<Props>(), {
1516
breadcrumbs: () => [],
17+
actions: () => [
18+
{
19+
title: 'Packagist',
20+
icon: 'pi pi-github',
21+
url: 'https://packagist.org/packages/engnua/laravel-vue-primeui-starter-kit',
22+
target: '_blank',
23+
}
24+
]
1625
});
1726
1827
const { layoutConfig, layoutState, isSidebarActive } = useAppearance();
@@ -67,13 +76,17 @@ function isOutsideClicked(event: Event) {
6776

6877
<template>
6978
<div class="layout-wrapper" :class="containerClass">
70-
<AppTopbar />
71-
<AppSidebar />
72-
<div class="layout-main-container">
79+
<AppTopbar :class="{ 'border-b dark:border-surface-700': breadcrumbs.length || actions.length }" />
80+
<AppSubTopbar v-if="breadcrumbs.length || actions.length" :breadcrumbs="breadcrumbs" :actions="actions"/>
81+
<AppSidebar :class="{
82+
'layout-sidebar-margin-default': !breadcrumbs.length || !actions.length,
83+
'layout-sidebar-margin-topsubbar': breadcrumbs.length || actions.length,
84+
}" />
85+
<div class="layout-main-container" :class="{
86+
'layout-main-container-padding-default': !breadcrumbs.length || !actions.length,
87+
'layout-main-container-padding-topsubbar': breadcrumbs.length || actions.length,
88+
}">
7389
<div class="layout-main">
74-
<template v-if="breadcrumbs && breadcrumbs.length > 0">
75-
<Breadcrumbs :breadcrumbs="breadcrumbs" />
76-
</template>
7790
<slot />
7891
</div>
7992
<AppFooter />

resources/js/layouts/AppMenuFooter.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const menu = ref([
2020

2121
<template>
2222
<div class="mt-3">
23-
<ul class="layout-menu" :class="{ 'border-t': menu.length > 0 }">
23+
<ul class="layout-menu" :class="{ 'border-t dark:border-surface-700': menu.length > 0 }">
2424
<template v-for="(item, i) in menu" :key="item">
2525
<AppMenuItem v-if="!item.separator" :item="item" :index="i"></AppMenuItem>
2626
<li v-if="item.separator" class="menu-separator"></li>
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<script setup lang="ts">
2+
import { ref, computed } from 'vue';
3+
import type { AppTopSubBarItem, BreadcrumbItemType } from '@/types';
4+
import Breadcrumbs from '@/components/Breadcrumbs.vue';
5+
import { Link } from "@inertiajs/vue3";
6+
7+
interface Props {
8+
breadcrumbs?: BreadcrumbItemType[];
9+
actions?: AppTopSubBarItem[];
10+
}
11+
12+
const props = withDefaults(defineProps<Props>(), {
13+
breadcrumbs: () => [],
14+
actions: () => [],
15+
});
16+
17+
const mobileMenuRef = ref()
18+
19+
const mobileMenuItems = computed(() =>
20+
props.actions.map(action => ({
21+
label: action.title,
22+
icon: action.icon,
23+
route: action.route,
24+
url: action.url,
25+
target: action.target,
26+
command: action.command
27+
}))
28+
)
29+
30+
console.log(props.actions)
31+
32+
const toggleMobileMenu = (event) => {
33+
mobileMenuRef.value.toggle(event)
34+
}
35+
36+
</script>
37+
38+
<template>
39+
<div class="layout-sub-topbar">
40+
<div class="layout-sub-topbar-content" :class="{
41+
'justify-end': breadcrumbs.length === 0,
42+
'justify-between': breadcrumbs.length > 0
43+
}">
44+
<div class="layout-sub-topbar-breadcrumbs">
45+
<Breadcrumbs :breadcrumbs="breadcrumbs" />
46+
<div v-if="breadcrumbs.length === 0" class="p-[1.58rem]"></div>
47+
</div>
48+
<div v-if="actions?.length" class="layout-sub-topbar-actions hidden lg:flex space-x-2">
49+
<template v-for="(action, index) in actions" :key="index">
50+
<Button
51+
v-if="action.route"
52+
:key="`btn-route-${index}`"
53+
asChild
54+
v-slot="slotProps"
55+
class="inline-block"
56+
severity="secondary"
57+
>
58+
<Link :href="action.route" :class="slotProps.class">
59+
<span v-if="action.icon" :class="action.icon" />
60+
{{ action.title }}
61+
</Link>
62+
</Button>
63+
<Button
64+
v-else-if="action.url"
65+
:key="`btn-url-${index}`"
66+
as="a"
67+
:label="action.title"
68+
:icon="action.icon"
69+
:href="action.url"
70+
:target="action.target || '_self'"
71+
severity="secondary"
72+
/>
73+
<Button
74+
v-else
75+
:key="`btn-command-${index}`"
76+
:label="action.title"
77+
:icon="action.icon"
78+
@click="action.command"
79+
severity="secondary"
80+
/>
81+
</template>
82+
</div>
83+
<div class="lg:hidden">
84+
<Button
85+
severity="secondary"
86+
icon="pi pi-ellipsis-v"
87+
@click="toggleMobileMenu"
88+
text
89+
/>
90+
<Menu
91+
ref="mobileMenuRef"
92+
:model="mobileMenuItems"
93+
popup
94+
>
95+
<template #item="{ item, props }">
96+
<Link v-if="item.route" :href="item.route" v-bind="props.action">
97+
<span v-if="item.icon" :class="item.icon" />
98+
<span>{{ item.label }}</span>
99+
</Link>
100+
<a v-else :href="item.url" class="flex items-center" :target="item.target" v-bind="props.action">
101+
<span v-if="item.icon" :class="item.icon" />
102+
<span>{{ item.label }}</span>
103+
</a>
104+
</template>
105+
</Menu>
106+
</div>
107+
108+
</div>
109+
</div>
110+
</template>
111+

resources/js/layouts/AppTopbar.vue

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@
22
import { Link } from "@inertiajs/vue3";
33
import AppConfigurator from './AppConfigurator.vue';
44
import AppLogoIcon from '@/components/AppLogoIcon.vue';
5-
import type { BreadcrumbItemType } from '@/types';
65
import AppearanceSelector from '@/components/AppearanceSelector.vue';
76
import UserButton from '@/components/UserButton.vue';
87
import { useAppearance } from '@/composables/useAppearance';
98
109
const { toggleMenu } = useAppearance();
1110
12-
defineProps<{
13-
breadcrumbs?: BreadcrumbItemType[];
14-
}>();
15-
1611
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
1712
</script>
1813

@@ -30,7 +25,6 @@ const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
3025
<span>{{ appName}}</span>
3126
</Link>
3227
</div>
33-
3428
<div class="layout-topbar-actions">
3529
<div class="layout-config-menu">
3630
<div class="relative">

resources/js/pages/Dashboard.vue

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,11 @@ import { type BreadcrumbItem } from '@/types';
55
import { Head } from '@inertiajs/vue3';
66
import PlaceholderPattern from '../components/PlaceholderPattern.vue';
77
8-
const breadcrumbs = ref<BreadcrumbItem[]>([
9-
{
10-
title: 'Dashboard',
11-
},
12-
]);
8+
const breadcrumbs = ref<BreadcrumbItem[]>();
139
</script>
1410

1511
<template>
1612
<Head title="Dashboard" />
17-
1813
<AppLayout :breadcrumbs="breadcrumbs">
1914
<div class="flex h-full flex-1 flex-col gap-4 rounded-xl">
2015
<div class="grid auto-rows-min gap-4 md:grid-cols-3">

0 commit comments

Comments
 (0)