249 lines
6.0 KiB
Vue
249 lines
6.0 KiB
Vue
<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: 12vh
|
||
left: -12px
|
||
background-color: rgba(white, .98)
|
||
border: 1px solid darken($lightgrey, 5%)
|
||
z-index: 22
|
||
padding: .2rem 0
|
||
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
|
||
transform: translateX(10px)
|
||
|
||
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.1rem
|
||
display: flex
|
||
padding: 0.1rem .3rem
|
||
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.2rem
|
||
font-weight: bold
|
||
background-color: darken($beige, 10%)
|
||
color: white
|
||
width: 1.5rem
|
||
height: 1.5rem
|
||
border-radius: 50%
|
||
position: relative
|
||
margin: .2rem .5rem 0 1rem
|
||
|
||
&::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> |