From 34432d904279bf44ed005798770c2759f34bf4df Mon Sep 17 00:00:00 2001 From: "David W. Gray" Date: Sun, 11 May 2025 07:42:02 -0700 Subject: [PATCH 1/5] docs: implement on this page expand/collapse with useScrollspy (#2679) --- apps/docs/.vitepress/theme/Layout.vue | 52 +++++++++++++++++-- .../src/components/ComponentReference.vue | 4 +- apps/docs/src/components/PageContents.vue | 17 +++--- apps/docs/src/components/PageContentsItem.vue | 29 ++++++++--- apps/docs/src/types/index.ts | 6 +++ 5 files changed, 88 insertions(+), 20 deletions(-) diff --git a/apps/docs/.vitepress/theme/Layout.vue b/apps/docs/.vitepress/theme/Layout.vue index f01b921c4..cb85cdad1 100644 --- a/apps/docs/.vitepress/theme/Layout.vue +++ b/apps/docs/.vitepress/theme/Layout.vue @@ -138,7 +138,7 @@ header-class="pb-0 d-flex offcanvas-hidden-width" body-class="py-2" > - + @@ -193,6 +193,7 @@ import {VPNavBarSearch} from 'vitepress/theme' import {appInfoKey} from './keys' import {useMediaQuery} from '@vueuse/core' import PageContents from '../../src/components/PageContents.vue' +import {type ContentsItem, type HeaderItem} from '../../src/types' // https://vitepress.dev/reference/runtime-api#usedata const {page} = useData() @@ -201,9 +202,11 @@ const route = useRoute() const content = useTemplateRef>('_content') const target = useTemplateRef>('_target') -useScrollspy(content, target, { - contentQuery: ':scope > div > [id], #component-reference', +const {current: activeId, list: items} = useScrollspy(content, target, { + contentQuery: ':scope > div > [id], #component-reference, .component-reference h3', targetQuery: ':scope [href]', + rootMargin: '0px 0px -25%', + manual: true, }) const globalData = inject(appInfoKey, { @@ -295,6 +298,49 @@ const set = (newValue: keyof typeof map) => { colorMode.value = newValue } +const headers = computed(() => + items.value.map((item) => { + const rawTag = item.el?.tagName?.toUpperCase() ?? '' + const isHeading = /^H[1-6]$/.test(rawTag) + const tag = isHeading ? rawTag : 'DIV' + const level = tag.startsWith('H') ? parseInt(tag.replace('H', '')) : 3 + return { + ...item, + tag, + level, + } as HeaderItem + }) +) + +const contents = computed(() => { + const root: ContentsItem[] = [] + const stack: ContentsItem[] = [] + + headers.value.forEach((header) => { + const item = {...header, children: [] as ContentsItem[]} as ContentsItem + + while (stack.length && stack[stack.length - 1].level >= item.level) { + stack.pop() + } + + if (stack.length === 0) { + root.push(item) + } else { + stack[stack.length - 1].children.push(item) + } + + stack.push(item) + }) + + if (root.length !== 1) { + // Something isn't right if we have no root items or more than one root item + // eslint-disable-next-line no-console + console.warn('Unexpected header structure:', headers, 'Root items:', root) + } + + return root.length > 0 ? root[0] : undefined +}) + watch( () => route.path, () => { diff --git a/apps/docs/src/components/ComponentReference.vue b/apps/docs/src/components/ComponentReference.vue index 27c7df633..2a919f8bc 100644 --- a/apps/docs/src/components/ComponentReference.vue +++ b/apps/docs/src/components/ComponentReference.vue @@ -10,7 +10,9 @@ - {{ `<` + component.component + `>` }} +

+ {{ `<` + component.component + `>` }} +

- - Component Reference +