- Options API component with segments/{id?,label}, rootLabel, showRoot props
- Emits navigate(segment.id) for intermediate clicks, navigate(null) for root
- Computed visibleSegments collapses >4 segments to first+ellipsis+last two
- Segments without id render as plain non-clickable spans (admin static segments)
- Uses AppIcon for chevronRight separator (no inline SVG)
- All 10 Vitest tests pass
- Also creates AppIcon.vue (Rule 3: BreadcrumbBar imports it)
73 lines
2.0 KiB
Vue
73 lines
2.0 KiB
Vue
<template>
|
|
<nav aria-label="Navigation">
|
|
<ol class="flex items-center gap-1 text-sm flex-wrap">
|
|
<li v-if="showRoot" class="flex items-center gap-1">
|
|
<button
|
|
@click="$emit('navigate', null)"
|
|
class="text-indigo-600 hover:underline font-medium"
|
|
>
|
|
{{ rootLabel }}
|
|
</button>
|
|
</li>
|
|
|
|
<template v-for="(segment, idx) in visibleSegments" :key="segment.id ?? 'static-' + idx">
|
|
<li
|
|
v-if="showRoot || idx > 0"
|
|
class="shrink-0"
|
|
aria-hidden="true"
|
|
>
|
|
<AppIcon name="chevronRight" class="w-3 h-3 text-gray-400" />
|
|
</li>
|
|
|
|
<li v-if="segment.id === 'ellipsis'" class="flex items-center">
|
|
<span class="px-2 py-1 text-gray-400">…</span>
|
|
</li>
|
|
|
|
<li v-else-if="idx === visibleSegments.length - 1" class="flex items-center">
|
|
<span class="text-gray-900 font-medium">{{ segment.label }}</span>
|
|
</li>
|
|
|
|
<li v-else-if="segment.id" class="flex items-center">
|
|
<button
|
|
@click="$emit('navigate', segment.id)"
|
|
class="text-indigo-600 hover:underline font-medium"
|
|
>
|
|
{{ segment.label }}
|
|
</button>
|
|
</li>
|
|
|
|
<li v-else class="flex items-center">
|
|
<span class="text-gray-500">{{ segment.label }}</span>
|
|
</li>
|
|
</template>
|
|
</ol>
|
|
</nav>
|
|
</template>
|
|
|
|
<script>
|
|
import AppIcon from './AppIcon.vue'
|
|
|
|
export default {
|
|
name: 'BreadcrumbBar',
|
|
components: { AppIcon },
|
|
props: {
|
|
segments: { type: Array, default: () => [] },
|
|
rootLabel: { type: String, default: 'Home' },
|
|
showRoot: { type: Boolean, default: true },
|
|
},
|
|
emits: ['navigate'],
|
|
computed: {
|
|
visibleSegments() {
|
|
if (this.segments.length > 4) {
|
|
return [
|
|
this.segments[0],
|
|
{ id: 'ellipsis', label: '…' },
|
|
...this.segments.slice(-2),
|
|
]
|
|
}
|
|
return this.segments
|
|
},
|
|
},
|
|
}
|
|
</script>
|