mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-12 19:40:41 +08:00
210 lines
5.5 KiB
Vue
210 lines
5.5 KiB
Vue
<script lang="ts">
|
||
import VerticalNav from "@layouts/components/VerticalNav.vue";
|
||
import { useDisplay } from "vuetify";
|
||
|
||
export default defineComponent({
|
||
setup(props, { slots }) {
|
||
const isOverlayNavActive = ref(false);
|
||
const isLayoutOverlayVisible = ref(false);
|
||
const toggleIsOverlayNavActive = useToggle(isOverlayNavActive);
|
||
|
||
const route = useRoute();
|
||
const { mdAndDown } = useDisplay();
|
||
|
||
// ℹ️ 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);
|
||
|
||
const scrollDistance = ref(window.scrollY);
|
||
|
||
onMounted(() => {
|
||
window.addEventListener("scroll", () => {
|
||
scrollDistance.value = window.scrollY;
|
||
});
|
||
});
|
||
|
||
return () => {
|
||
// 👉 Vertical nav
|
||
const verticalNav = h(
|
||
VerticalNav,
|
||
{ isOverlayNavActive: isOverlayNavActive.value, toggleIsOverlayNavActive },
|
||
{
|
||
"nav-header": () => slots["vertical-nav-header"]?.(),
|
||
"before-nav-items": () => slots["before-vertical-nav-items"]?.(),
|
||
default: () => slots["vertical-nav-content"]?.(),
|
||
"after-nav-items": () => slots["after-vertical-nav-items"]?.(),
|
||
}
|
||
);
|
||
|
||
// 👉 Navbar
|
||
const navbar = h("header", { class: ["layout-navbar navbar-blur"] }, [
|
||
h(
|
||
"div",
|
||
{ class: "navbar-content-container" },
|
||
slots.navbar?.({
|
||
toggleVerticalOverlayNavActive: toggleIsOverlayNavActive,
|
||
})
|
||
),
|
||
]);
|
||
|
||
const main = h(
|
||
"main",
|
||
{ class: "layout-page-content" },
|
||
h("div", { class: "page-content-container" }, slots.default?.())
|
||
);
|
||
|
||
// 👉 Footer
|
||
const footer = h("footer", { class: "layout-footer" }, [
|
||
h("div", { class: "footer-content-container" }, slots.footer?.()),
|
||
]);
|
||
|
||
// 👉 Overlay
|
||
const layoutOverlay = h("div", {
|
||
class: ["layout-overlay", { visible: isLayoutOverlayVisible.value }],
|
||
onClick: () => {
|
||
isLayoutOverlayVisible.value = !isLayoutOverlayVisible.value;
|
||
},
|
||
});
|
||
|
||
return h(
|
||
"div",
|
||
{
|
||
class: [
|
||
"layout-wrapper layout-nav-type-vertical layout-navbar-static layout-footer-static layout-content-width-fluid",
|
||
"layout-navbar-sticky",
|
||
mdAndDown.value && "layout-overlay-nav",
|
||
route.meta.layoutWrapperClasses,
|
||
scrollDistance.value > 20 && "window-scrolled",
|
||
],
|
||
},
|
||
[
|
||
verticalNav,
|
||
h("div", { class: "layout-content-wrapper" }, [navbar, main, footer]),
|
||
layoutOverlay,
|
||
]
|
||
);
|
||
};
|
||
},
|
||
});
|
||
</script>
|
||
|
||
<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: calc(var(--vh, 1vh) * 100);
|
||
transition: padding-inline-start 0.2s ease-in-out;
|
||
will-change: padding-inline-start;
|
||
}
|
||
|
||
.layout-navbar {
|
||
position: sticky;
|
||
z-index: variables.$layout-vertical-nav-layout-navbar-z-index;
|
||
inset-block-start: 0;
|
||
|
||
.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;
|
||
} @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: transform;
|
||
|
||
&.visible {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
}
|
||
|
||
&:not(.layout-overlay-nav) .layout-content-wrapper {
|
||
padding-inline-start: variables.$layout-vertical-nav-width;
|
||
}
|
||
|
||
// Adjust right column pl when vertical nav is collapsed
|
||
&.layout-vertical-nav-collapsed .layout-content-wrapper {
|
||
padding-inline-start: variables.$layout-vertical-nav-collapsed-width;
|
||
}
|
||
|
||
// 👉 Content height fixed
|
||
&.layout-content-height-fixed {
|
||
.layout-content-wrapper {
|
||
max-block-size: calc(var(--vh) * 100);
|
||
}
|
||
|
||
.layout-page-content {
|
||
display: flex;
|
||
overflow: hidden;
|
||
|
||
.page-content-container {
|
||
inline-size: 100%;
|
||
|
||
> :first-child {
|
||
max-block-size: 100%;
|
||
overflow-y: auto;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 👉 Window scrolled ios top bar
|
||
.window-scrolled::after {
|
||
content: "";
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0.5rem;
|
||
height: env(safe-area-inset-top);
|
||
width: calc(100% - 1rem);
|
||
background-color: rgb(var(--v-theme-surface), 0.9);
|
||
-webkit-backdrop-filter: blur(6px);
|
||
backdrop-filter: blur(6px);
|
||
z-index: 1003;
|
||
transition: padding 0.2s ease, background-color 0.18s ease;
|
||
}
|
||
</style>
|