Files
neo/frontend/layouts/default.vue
2026-01-04 08:48:43 +01:00

131 lines
4.0 KiB
Vue

<script setup lang="ts">
import { ref } from 'vue'
import AppSidebar from '@/components/AppSidebar.vue'
import AIChatBar from '@/components/AIChatBar.vue'
import SoftphoneDialog from '@/components/SoftphoneDialog.vue'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb'
import { Separator } from '@/components/ui/separator'
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
const route = useRoute()
const { breadcrumbs: customBreadcrumbs } = useBreadcrumbs()
const breadcrumbs = computed(() => {
// If custom breadcrumbs are set by the page, use those
if (customBreadcrumbs.value.length > 0) {
return customBreadcrumbs.value
}
// Otherwise, fall back to URL-based breadcrumbs
const paths = route.path.split('/').filter(Boolean)
return paths.map((path, index) => ({
name: path.charAt(0).toUpperCase() + path.slice(1),
path: '/' + paths.slice(0, index + 1).join('/'),
isLast: index === paths.length - 1,
}))
})
</script>
<template>
<SidebarProvider>
<AppSidebar />
<SidebarInset class="flex flex-col">
<header
class="relative z-10 flex h-16 shrink-0 items-center gap-2 bg-background transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 border-b shadow-md"
>
<div class="flex items-center gap-2 px-4">
<SidebarTrigger class="-ml-1" />
<Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink as-child>
<NuxtLink to="/">Home</NuxtLink>
</BreadcrumbLink>
</BreadcrumbItem>
<template v-if="breadcrumbs.length > 0">
<BreadcrumbSeparator />
<template v-for="(crumb, index) in breadcrumbs" :key="crumb.path">
<BreadcrumbItem>
<BreadcrumbPage v-if="crumb.isLast">
{{ crumb.name }}
</BreadcrumbPage>
<BreadcrumbLink v-else as-child>
<NuxtLink :to="crumb.path">{{ crumb.name }}</NuxtLink>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator v-if="!crumb.isLast" />
</template>
</template>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
<!-- Main scrollable content -->
<div class="layout-slot-container flex flex-1 flex-col gap-4 p-4 pt-0 overflow-y-auto">
<slot />
</div>
<!-- AI Chat Bar Component -->
<AIChatBar />
<!-- Softphone Dialog (Global) -->
<SoftphoneDialog />
</SidebarInset>
</SidebarProvider>
</template>
<style scoped>
.layout-slot-container {
position: relative;
background-color: #ffffff;
background-image: linear-gradient(to bottom, #DFDBE5 0%, rgba(223, 219, 229, 0) 100%);
background-size: 100% 150px;
background-repeat: no-repeat;
}
.layout-slot-container::before,
.layout-slot-container::after {
content: '';
position: absolute;
inset: 0 auto auto 0;
width: 100%;
height: 150px;
pointer-events: none;
background-image: linear-gradient(to bottom, rgba(156, 146, 172, 0.55) 0%, rgba(156, 146, 172, 0) 100%);
-webkit-mask-image: url("~/assets/images/pattern.svg");
-webkit-mask-repeat: repeat;
-webkit-mask-size: 600px 600px;
mask-image: url("~/assets/images/pattern.svg");
mask-repeat: repeat;
mask-size: 600px 600px;
z-index: 0;
}
/* Crisp pattern that fades out */
.layout-slot-container::before {
opacity: 1;
}
/* Slightly shifted, blurred layer to create a “blur into white” effect */
.layout-slot-container::after {
background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
-webkit-mask-image: none;
mask-image: none;
opacity: 1;
}
.layout-slot-container > * {
position: relative;
z-index: 1;
}
</style>