Skip to content

Commit 4b8281b

Browse files
authored
feat: add transitions to persistent layout (#19)
* feat: add transitions to persistent layout * Apply suggestion from @IronSinew
1 parent f16c81f commit 4b8281b

File tree

3 files changed

+149
-24
lines changed

3 files changed

+149
-24
lines changed

app/Http/Middleware/HandleInertiaRequests.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public function version(Request $request): ?string
3636
public function share(Request $request): array
3737
{
3838
return array_merge(parent::share($request), [
39+
'current_route_salted' => implode('|', [\Route::currentRouteName(), \Str::random(10)]),
3940
'can_access_admin_area' => $request->user()?->hasAccessToAdminArea() ?? false,
4041
]);
4142
}

resources/css/presets/recipebox/menubar/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export default {
5252
],
5353
}),
5454
menuitem: {
55-
class: "sm:relative sm:w-auto w-full static",
55+
class: "sm:relative sm:w-auto w-full static mr-1",
5656
},
5757
content: ({ props, context }) => ({
5858
class: [

resources/js/Layouts/AppLayout.vue

Lines changed: 147 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Chip from "primevue/chip";
77
import Dialog from "primevue/dialog";
88
import Menubar from "primevue/menubar";
99
import Message from "primevue/message";
10-
import { nextTick, onMounted, ref, watchEffect } from "vue";
10+
import { computed, nextTick, onMounted, ref, watch, watchEffect } from "vue";
1111
1212
import ApplicationMark from "@/Components/ApplicationMark.vue";
1313
import DarkModeButton from "@/Components/DarkModeButton.vue";
@@ -28,7 +28,6 @@ const searchModal = ref({
2828
const menuItems = ref([
2929
{
3030
label: "Categories",
31-
icon: "pi pi-star",
3231
route: "category.index",
3332
routeGroup: "category.*",
3433
items: [
@@ -40,13 +39,13 @@ const menuItems = ref([
4039
},
4140
{
4241
label: "Labels",
43-
icon: "pi pi-envelope",
4442
route: "label.index",
43+
routeGroup: "label.*",
4544
},
4645
{
4746
label: "All Recipes",
48-
icon: "pi pi-list",
4947
route: "recipe.all",
48+
routeGroup: "recipe.all",
5049
},
5150
]);
5251
@@ -120,6 +119,32 @@ const autocompleteInput = ref(null);
120119
const logout = () => {
121120
router.post(route("logout"));
122121
};
122+
123+
const animate = ref(true);
124+
const isLeaving = ref(false);
125+
126+
const theRoute = ref(route());
127+
128+
const currentRoute = computed(() => usePage().props.current_route_salted);
129+
watch(currentRoute, async () => {
130+
isLeaving.value = true;
131+
animate.value = false;
132+
133+
// Wait for leave animation to complete
134+
await new Promise((resolve) => setTimeout(resolve, 300));
135+
136+
isLeaving.value = false;
137+
nextTick(() => {
138+
animate.value = true;
139+
});
140+
});
141+
142+
router.on("navigate", () => {
143+
theRoute.value = route();
144+
});
145+
const current = computed(() => {
146+
return theRoute.value.current();
147+
});
123148
</script>
124149
125150
<template>
@@ -185,7 +210,7 @@ const logout = () => {
185210
<div>
186211
<Head :title="title" />
187212
188-
<div class="min-h-screen">
213+
<div class="min-h-screen transition-all duration-300">
189214
<nav class="sticky top-0 z-10">
190215
<!-- Primary Navigation Menu -->
191216
<div class="max-w-7xl mx-auto print:hidden">
@@ -206,17 +231,21 @@ const logout = () => {
206231
:href="
207232
route(item.route, item.routeObject || null)
208233
"
209-
:class="{
210-
'bg-gray-100 dark:bg-surface-600/80 rounded-lg':
211-
route().current(
212-
item.route,
213-
item.routeObject || null,
214-
),
215-
}"
216234
>
217-
<span v-ripple v-bind="props.action">
235+
<span
236+
v-ripple
237+
v-bind="props.action"
238+
:class="{
239+
'bg-gray-100 dark:bg-surface-600/80 rounded-lg':
240+
theRoute.current(item.route) &&
241+
root,
242+
}"
243+
>
218244
<!-- <span :class="item.icon" /> -->
219-
<span class="ml-2">{{ item.label }}</span>
245+
<span
246+
:class="{ 'ml-2': item.icon?.length }"
247+
>{{ item.label }}</span
248+
>
220249
</span>
221250
</Link>
222251
<a
@@ -226,14 +255,13 @@ const logout = () => {
226255
v-bind="props.action"
227256
:class="{
228257
'bg-gray-100 mx-2 dark:bg-surface-600/80 rounded-lg':
229-
route().current(
230-
item.routeGroup,
231-
item.routeObject || null,
232-
),
258+
theRoute.current(item.routeGroup),
233259
}"
234260
>
235261
<!-- <span :class="item.icon" /> -->
236-
<span class="ml-2">{{ item.label }}</span>
262+
<span :class="{ 'ml-2': item.icon?.length }">{{
263+
item.label
264+
}}</span>
237265
<Badge
238266
v-if="item.badge"
239267
:class="{ 'ml-auto': !root, 'ml-2': root }"
@@ -384,10 +412,18 @@ const logout = () => {
384412
</Message>
385413
</div>
386414
387-
<!-- Page Content -->
388-
<main>
389-
<slot />
390-
</main>
415+
<transition name="slide-fade" mode="out-in" appear>
416+
<div
417+
v-if="animate"
418+
:class="{ 'is-leaving': isLeaving }"
419+
class="mx-auto container h-full"
420+
>
421+
<!-- Page Content -->
422+
<main>
423+
<slot />
424+
</main>
425+
</div>
426+
</transition>
391427
392428
<div class="max-w-7xl mx-auto overflow-hidden mb-12 px-8">
393429
<div
@@ -490,8 +526,96 @@ const logout = () => {
490526
#header-slot > .header-wrapper:has(*) {
491527
display: block;
492528
}
529+
493530
div#mainSearch.autocomplete-search > input {
494531
padding-left: 45px !important;
495532
@apply py-4 w-full;
496533
}
534+
535+
/*
536+
Slide Fade Transition
537+
*/
538+
.slide-right-enter-active,
539+
.slide-left-enter-active,
540+
.slide-bottom-enter-active,
541+
.slide-top-enter-active {
542+
transition: all 0.3s ease-out;
543+
}
544+
545+
.slide-right-leave-active,
546+
.slide-left-leave-active,
547+
.slide-bottom-leave-active,
548+
.slide-top-enter-active {
549+
transition: all 0.3s;
550+
}
551+
552+
.slide-right-enter-from,
553+
.slide-right-leave-to {
554+
transform: translateX(50%);
555+
opacity: 0;
556+
}
557+
558+
.slide-left-enter-from,
559+
.slide-left-leave-to {
560+
transform: translateX(-50%);
561+
opacity: 0;
562+
}
563+
564+
.slide-bottom-enter-from,
565+
.slide-bottom-leave-to {
566+
transform: translateY(25%);
567+
opacity: 0;
568+
}
569+
570+
.slide-top-enter-from,
571+
.slide-top-leave-to {
572+
transform: translateY(-50%);
573+
opacity: 0;
574+
}
575+
576+
/*
577+
Fade Transition
578+
*/
579+
.fade-enter-active,
580+
.fade-leave-active {
581+
transition: opacity 0.5s ease-out;
582+
}
583+
584+
.fade-enter-from,
585+
.fade-leave-to {
586+
opacity: 0;
587+
}
588+
589+
/*
590+
Slide Fade Combined Transition
591+
*/
592+
.slide-fade-enter-active,
593+
.slide-fade-leave-active {
594+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
595+
}
596+
597+
.slide-fade-enter-from {
598+
transform: translateY(10px);
599+
opacity: 0;
600+
}
601+
602+
.slide-fade-leave-to {
603+
transform: translateY(-10px);
604+
opacity: 0;
605+
}
606+
607+
/* Add appear transition classes */
608+
.slide-fade-appear-active {
609+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
610+
}
611+
612+
.slide-fade-appear-from {
613+
transform: translateY(10px);
614+
opacity: 0;
615+
}
616+
617+
/* Optional: Add class for leaving state */
618+
.is-leaving {
619+
pointer-events: none;
620+
}
497621
</style>

0 commit comments

Comments
 (0)