Files
panel/resources/js/@layouts/components/VerticalNavLayout.vue
2025-08-04 16:33:07 +03:30

217 lines
5.5 KiB
Vue
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { VerticalNav } from '@layouts/components'
import { useLayoutConfigStore } from '@layouts/stores/config'
const props = defineProps({
navItems: {
type: null,
required: true,
},
verticalNavAttrs: {
type: Object,
required: false,
default: () => ({}),
},
})
const { width: windowWidth } = useWindowSize()
const configStore = useLayoutConfigStore()
const isOverlayNavActive = ref(false)
const isLayoutOverlayVisible = ref(false)
const toggleIsOverlayNavActive = useToggle(isOverlayNavActive)
// This is alternative to below two commented watcher
// We want to show overlay if overlay nav is visible and want to hide overlay if overlay is hidden and vice versa.
syncRef(isOverlayNavActive, isLayoutOverlayVisible)
// })
// Hide overlay if user open overlay nav in <md and increase the window width without closing overlay nav
watch(windowWidth, () => {
if (!configStore.isLessThanOverlayNavBreakpoint && isLayoutOverlayVisible.value)
isLayoutOverlayVisible.value = false
})
const verticalNavAttrs = computed(() => {
const vNavAttrs = toRef(props, 'verticalNavAttrs')
const {
wrapper: verticalNavWrapper,
wrapperProps: verticalNavWrapperProps,
...additionalVerticalNavAttrs
} = vNavAttrs.value
return {
verticalNavWrapper,
verticalNavWrapperProps,
additionalVerticalNavAttrs,
}
})
</script>
<template>
<div
class="layout-wrapper"
data-allow-mismatch
:class="configStore._layoutClasses"
>
<component
:is="verticalNavAttrs.verticalNavWrapper ? verticalNavAttrs.verticalNavWrapper : 'div'"
v-bind="verticalNavAttrs.verticalNavWrapperProps"
class="vertical-nav-wrapper"
>
<VerticalNav
:is-overlay-nav-active="isOverlayNavActive"
:toggle-is-overlay-nav-active="toggleIsOverlayNavActive"
:nav-items="props.navItems"
v-bind="{ ...verticalNavAttrs.additionalVerticalNavAttrs }"
>
<template #nav-header>
<slot name="vertical-nav-header" />
</template>
<template #before-nav-items>
<slot name="before-vertical-nav-items" />
</template>
</VerticalNav>
</component>
<div class="layout-content-wrapper">
<header
class="layout-navbar"
:class="[{ 'navbar-blur': configStore.isNavbarBlurEnabled }]"
>
<div class="navbar-content-container">
<slot
name="navbar"
:toggle-vertical-overlay-nav-active="toggleIsOverlayNavActive"
/>
</div>
</header>
<main class="layout-page-content">
<div class="page-content-container">
<slot />
</div>
</main>
<footer class="layout-footer"
v-if="!$route.meta.hideFooter">
<div class="footer-content-container">
<slot name="footer" />
</div>
</footer>
</div>
<div
class="layout-overlay"
:class="[{ visible: isLayoutOverlayVisible }]"
@click="() => { isLayoutOverlayVisible = !isLayoutOverlayVisible }"
/>
</div>
</template>
<style lang="scss">
@use "@configured-variables" as variables;
@use "@layouts/styles/placeholders";
@use "@layouts/styles/mixins";
.layout-wrapper.layout-nav-type-vertical {
// TODO(v2): Check why we need height in vertical nav & min-height in horizontal nav
block-size: 100%;
.layout-content-wrapper {
display: flex;
flex-direction: column;
flex-grow: 1;
min-block-size: 100dvh;
transition: padding-inline-start 0.2s ease-in-out;
will-change: padding-inline-start;
@media screen and (min-width: 1280px) {
padding-inline-start: variables.$layout-vertical-nav-width;
}
}
.layout-navbar {
z-index: variables.$layout-vertical-nav-layout-navbar-z-index;
.navbar-content-container {
block-size: variables.$layout-vertical-nav-navbar-height;
}
@at-root {
.layout-wrapper.layout-nav-type-vertical {
.layout-navbar {
@if variables.$layout-vertical-nav-navbar-is-contained {
@include mixins.boxed-content;
}
/* stylelint-disable-next-line @stylistic/indentation */
@else {
.navbar-content-container {
@include mixins.boxed-content;
}
}
}
}
}
}
&.layout-navbar-sticky .layout-navbar {
@extend %layout-navbar-sticky;
}
&.layout-navbar-hidden .layout-navbar {
@extend %layout-navbar-hidden;
}
// 👉 Footer
.layout-footer {
@include mixins.boxed-content;
}
// 👉 Layout overlay
.layout-overlay {
position: fixed;
z-index: variables.$layout-overlay-z-index;
background-color: rgb(0 0 0 / 60%);
cursor: pointer;
inset: 0;
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease-in-out;
will-change: opacity;
&.visible {
opacity: 1;
pointer-events: auto;
}
}
// Adjust right column pl when vertical nav is collapsed
&.layout-vertical-nav-collapsed .layout-content-wrapper {
@media screen and (min-width: 1280px) {
padding-inline-start: variables.$layout-vertical-nav-collapsed-width;
}
}
// 👉 Content height fixed
&.layout-content-height-fixed {
.layout-content-wrapper {
max-block-size: 100dvh;
}
.layout-page-content {
display: flex;
overflow: hidden;
.page-content-container {
inline-size: 100%;
> :first-child {
max-block-size: 100%;
overflow-y: auto;
}
}
}
}
}
</style>