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
53 lines
1.3 KiB
TypeScript
53 lines
1.3 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { onBeforeUnmount, watch, type Ref, type ComputedRef } from 'vue'
|
|
|
|
import type { Crumb } from './types'
|
|
|
|
/**
|
|
* Store the current breadcrumb segments. Pages set them on mount via
|
|
* `useCrumbs(crumbs)` and clear them on unmount automatically.
|
|
*
|
|
* The shell (`App.vue`) reads `crumbs` and renders `<AppBreadcrumb>` —
|
|
* empty array → component renders nothing.
|
|
*/
|
|
export const useBreadcrumbStore = defineStore('breadcrumb', {
|
|
state: () => ({
|
|
crumbs: [] as Crumb[],
|
|
}),
|
|
actions: {
|
|
setCrumbs(crumbs: Crumb[]) {
|
|
this.crumbs = crumbs
|
|
},
|
|
clear() {
|
|
this.crumbs = []
|
|
},
|
|
},
|
|
})
|
|
|
|
/**
|
|
* Composable for pages to declare their breadcrumb. Auto-clears on
|
|
* unmount so a stale breadcrumb never leaks to the next route.
|
|
*
|
|
* Accepts a static array OR a reactive ref / computed so pages with
|
|
* async data (e.g. doc workspace fetching the doc title) can rebuild
|
|
* crumbs as the data lands.
|
|
*/
|
|
export function useCrumbs(source: Crumb[] | Ref<Crumb[]> | ComputedRef<Crumb[]>) {
|
|
const store = useBreadcrumbStore()
|
|
|
|
if (Array.isArray(source)) {
|
|
store.setCrumbs(source)
|
|
} else {
|
|
watch(
|
|
source,
|
|
(next) => {
|
|
store.setCrumbs(next)
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
store.clear()
|
|
})
|
|
}
|