dml_frontend/components/Breadcrumbs.vue
2025-06-08 18:26:21 +02:00

248 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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.

<template>
<nav
v-if="breadcrumbs.length"
class="breadcrumbs"
:class="{ expanded: isOpen }"
aria-label="Breadcrumb"
>
<div class="breadcrumb-container">
<div class="breadcrumb-trigger" @click="toggleOpen" aria-label="Breadcrumb öffnen">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512"
aria-hidden="true"
focusable="false"
>
<title>Startseite</title>
<path
d="M575.8 255.5c0 18-15 32.1-32 32.1l-32 0 .7 160.2c0 2.7-.2 5.4-.5 8.1l0 16.2c0 22.1-17.9 40-40 40l-16 0c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1L416 512l-24 0c-22.1 0-40-17.9-40-40l0-24 0-64c0-17.7-14.3-32-32-32l-64 0c-17.7 0-32 14.3-32 32l0 64 0 24c0 22.1-17.9 40-40 40l-24 0-31.9 0c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2l-16 0c-22.1 0-40-17.9-40-40l0-112c0-.9 0-1.9 .1-2.8l0-69.7-32 0c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
/>
</svg>
</div>
<!-- Breadcrumbs selbst (ein- und ausklappbar) -->
<ul :class="{ open: isOpen, closed: !isOpen }">
<li
v-for="(crumb, index) in breadcrumbs"
:key="index"
>
<router-link
v-if="index < breadcrumbs.length - 1"
:to="crumb.to"
:title="crumb.labelFull"
>{{ crumb.label }}
</router-link>
<span v-else :title="crumb.labelFull"><span v-if="breadcrumbs.length > 1" class="between"></span>{{ crumb.label }}</span>
</li>
<li class="close-btn" @click="isOpen = false" title="Schließen"></li>
</ul>
</div>
</nav>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { i18nPages } from '~/i18n/i18n-pages'
interface Breadcrumb {
label: string
labelFull: string
to: string
}
const isOpen = ref(false)
function toggleOpen() {
isOpen.value = !isOpen.value
}
function formatLabel(segment: string): { label: string; labelFull: string } {
const labelFull = segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ')
let label = labelFull
if (label.length > 25) {
label = label.slice(0, 22) + '...'
}
return { label, labelFull }
}
function buildUrl(loc: string, path: string): string {
if (loc === 'de') {
return path.startsWith('/') ? path : '/' + path
}
return `/${loc}${path.startsWith('/') ? path : '/' + path}`
}
const route = useRoute()
const { locale, t } = useI18n()
const breadcrumbs = computed<Breadcrumb[]>(() => {
const loc = locale.value
const pathWithoutLang = route.path.replace(`/${loc}`, '')
const segments = pathWithoutLang.split('/').filter(Boolean)
if (segments.length === 2 && segments[0] === 'projekt') {
const referencesPath = i18nPages.references?.[loc] || '/references'
const referencesLabel = t('references') || 'References'
const first = {
label: referencesLabel,
labelFull: referencesLabel,
to: buildUrl(loc, referencesPath)
}
const { label, labelFull } = formatLabel(segments[1])
const second = {
label,
labelFull,
to: route.path
}
return [first, second]
}
if (segments.length === 2 && segments[0] === 'artikel') {
const magazinePath = i18nPages.magazin?.[loc] || '/magazin'
const magazineLabel = t('magazin') || 'Magazin'
const first = {
label: magazineLabel,
labelFull: magazineLabel,
to: buildUrl(loc, magazinePath)
}
const { label, labelFull } = formatLabel(segments[1])
const second = {
label,
labelFull,
to: route.path
}
return [first, second]
}
let path = ''
return segments.map(segment => {
path += '/' + segment
const { label, labelFull } = formatLabel(segment)
return {
label,
labelFull,
to: buildUrl(loc, path)
}
})
})
watch(() => route.fullPath, () => {
isOpen.value = false
})
</script>
<style lang="sass" scoped>
.breadcrumbs
position: fixed
top: 22vh
left: -5px
background-color: rgba(white, .98)
border: 1px solid darken($lightgrey, 5%)
z-index: 22
padding: .25rem
border-top-right-radius: .8rem
border-bottom-right-radius: .8rem
transition: all 0.4s ease
.breadcrumb-container
display: flex
align-items: center
gap: 0.5rem
.breadcrumb-trigger
cursor: pointer
display: flex
align-items: center
justify-content: center
width: 1.8rem
height: 1.8rem
svg
width: 1rem
height: 1rem
path
fill: darken($lightgrey, 20%)
&:hover path
fill: darken($lightgrey, 30%)
// Animierbarer Breadcrumb-Block
ul
display: flex
flex-wrap: wrap
gap: 0.5rem
list-style: none
margin: 0
padding: 0
overflow: hidden
max-width: 0
opacity: 0
transition: all 0.4s ease
&.open
max-width: 500px
opacity: 1
&.closed
max-height: 0
opacity: 0
li
font-size: 1.2rem
display: flex
padding: .5rem
align-items: center
color: $darkgrey
a
color: darken($primaryColor, 10%)
.between
margin-right: 1.5rem
font-size: 1.5rem
font-family: 'Mainfont-Bold'
.close-btn
cursor: pointer
font-size: 1.4rem
font-weight: bold
background-color: darken($beige, 10%)
color: white
width: 1.5rem
height: 1.5rem
border-radius: 50%
position: relative
margin: .5rem .5rem 0 2rem
&::before
position: absolute
content: '<'
color: white
top: 50%
left: 50%
transform: translate(-50%, -50%)
&:hover
color: darken($primaryColor, 15%)
a
color: #007BFF
text-decoration: none
&:hover
transform: scale(1.2)
span
font-weight: bold
</style>