docling-studio/frontend/src/shared/breadcrumb/AppBreadcrumb.vue
Pier-Jean Malandrino be51105aaa feat(#208): doc workspace breadcrumb (Studio > <doc> > <mode>)
Adds the breadcrumb anchoring the user across modes on the doc
workspace. Empty / hidden on routes that don't opt in.

Components
- shared/breadcrumb/AppBreadcrumb.vue: data-driven, accessible
  (<nav aria-label>, <ol>, aria-current=page on the leaf).
  Renders nothing when crumbs.length === 0.
- shared/breadcrumb/types.ts: Crumb = LinkCrumb | LeafCrumb
  discriminated union.
- shared/breadcrumb/store.ts: Pinia store + useCrumbs(source)
  composable that auto-clears on unmount. Accepts a static array
  OR a reactive ref/computed so pages with async doc fetches can
  rebuild crumbs as data lands.
- shared/breadcrumb/text.ts: truncate(text, max) helper.

Wiring
- App.vue main outlet now sits below <AppBreadcrumb>; the shell
  reads from useBreadcrumbStore so pages don't need teleports.
- DocWorkspacePage provides Studio > <id-truncated> > <mode-label>;
  once the doc is fetched (#216 / E4), the id placeholder will
  swap for the truncated filename.

i18n
- breadcrumb.aria, breadcrumb.studio, breadcrumb.mode.{ask,inspect,
  chunks} added in fr + en.

Tests
- shared/breadcrumb/text.test.ts: 5 cases on truncate.
- shared/breadcrumb/store.test.ts: store actions + useCrumbs reactive/
  static seeding (the lifecycle-clear path is covered end-to-end by
  the integration tests landing in #211 / #216 — the project doesn't
  use @vue/test-utils so a unit test for onBeforeUnmount is overkill).

Refs #208
2026-04-29 17:52:59 +02:00

93 lines
2 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="crumbs.length > 0" class="breadcrumb" :aria-label="t('breadcrumb.aria')">
<ol class="breadcrumb__list">
<li
v-for="(crumb, index) in crumbs"
:key="`${index}-${crumb.label}`"
class="breadcrumb__item"
>
<RouterLink
v-if="crumb.kind === 'link'"
:to="crumb.to"
class="breadcrumb__link"
:title="crumb.label"
>
{{ crumb.label }}
</RouterLink>
<span v-else class="breadcrumb__leaf" aria-current="page" :title="crumb.label">
{{ crumb.label }}
</span>
<span v-if="index < crumbs.length - 1" class="breadcrumb__sep" aria-hidden="true"> </span>
</li>
</ol>
</nav>
</template>
<script setup lang="ts">
import { useI18n } from '../i18n'
import type { Crumb } from './types'
defineProps<{ crumbs: Crumb[] }>()
const { t } = useI18n()
</script>
<style scoped>
.breadcrumb {
display: flex;
align-items: center;
min-height: 32px;
padding: 0 1rem;
font-size: 0.875rem;
color: var(--color-text-muted, #9ca3af);
border-bottom: 1px solid var(--color-border, #1a1a1d);
background: var(--color-surface, #111113);
}
.breadcrumb__list {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0;
padding: 0;
list-style: none;
overflow: hidden;
}
.breadcrumb__item {
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-width: 0;
}
.breadcrumb__link {
color: var(--color-text-muted, #9ca3af);
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 16rem;
transition: color 0.15s;
}
.breadcrumb__link:hover {
color: var(--color-text, #f3f4f6);
text-decoration: underline;
}
.breadcrumb__leaf {
color: var(--color-text, #f3f4f6);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 16rem;
font-weight: 500;
}
.breadcrumb__sep {
color: var(--color-text-muted, #6b7280);
user-select: none;
}
</style>