From ff6f39660814d0c1e1e62dd65444f8d8400fdf58 Mon Sep 17 00:00:00 2001 From: Hirotaka Mizutani <52546+hirotaka@users.noreply.github.com> Date: Thu, 25 Jun 2026 18:55:58 +0900 Subject: [PATCH] feat(registry): Add docs pager and page toc blocks --- app/components/demo/DocsPagerDemo.vue | 29 ++++ app/components/demo/PageTocDemo.vue | 91 +++++++++++++ app/registry/blocks/docs-pager/DocsPager.vue | 96 ++++++++++++++ app/registry/blocks/docs-pager/index.ts | 1 + app/registry/blocks/page-toc/PageToc.vue | 131 +++++++++++++++++++ app/registry/blocks/page-toc/index.ts | 1 + content/docs/2.components/8.page-toc.md | 116 ++++++++++++++++ content/docs/2.components/9.docs-pager.md | 93 +++++++++++++ scripts/registry-verify.ts | 8 ++ 9 files changed, 566 insertions(+) create mode 100644 app/components/demo/DocsPagerDemo.vue create mode 100644 app/components/demo/PageTocDemo.vue create mode 100644 app/registry/blocks/docs-pager/DocsPager.vue create mode 100644 app/registry/blocks/docs-pager/index.ts create mode 100644 app/registry/blocks/page-toc/PageToc.vue create mode 100644 app/registry/blocks/page-toc/index.ts create mode 100644 content/docs/2.components/8.page-toc.md create mode 100644 content/docs/2.components/9.docs-pager.md diff --git a/app/components/demo/DocsPagerDemo.vue b/app/components/demo/DocsPagerDemo.vue new file mode 100644 index 0000000..5fcff39 --- /dev/null +++ b/app/components/demo/DocsPagerDemo.vue @@ -0,0 +1,29 @@ + + + diff --git a/app/components/demo/PageTocDemo.vue b/app/components/demo/PageTocDemo.vue new file mode 100644 index 0000000..4414104 --- /dev/null +++ b/app/components/demo/PageTocDemo.vue @@ -0,0 +1,91 @@ + + + diff --git a/app/registry/blocks/docs-pager/DocsPager.vue b/app/registry/blocks/docs-pager/DocsPager.vue new file mode 100644 index 0000000..47281e1 --- /dev/null +++ b/app/registry/blocks/docs-pager/DocsPager.vue @@ -0,0 +1,96 @@ + + + diff --git a/app/registry/blocks/docs-pager/index.ts b/app/registry/blocks/docs-pager/index.ts new file mode 100644 index 0000000..c3fff8e --- /dev/null +++ b/app/registry/blocks/docs-pager/index.ts @@ -0,0 +1 @@ +export { default as DocsPager, type DocsPagerItem, type DocsPagerProps } from "./DocsPager.vue"; diff --git a/app/registry/blocks/page-toc/PageToc.vue b/app/registry/blocks/page-toc/PageToc.vue new file mode 100644 index 0000000..2d2f5f2 --- /dev/null +++ b/app/registry/blocks/page-toc/PageToc.vue @@ -0,0 +1,131 @@ + + + diff --git a/app/registry/blocks/page-toc/index.ts b/app/registry/blocks/page-toc/index.ts new file mode 100644 index 0000000..1f64aec --- /dev/null +++ b/app/registry/blocks/page-toc/index.ts @@ -0,0 +1 @@ +export { default as PageToc, type PageTocItem, type PageTocProps } from "./PageToc.vue"; diff --git a/content/docs/2.components/8.page-toc.md b/content/docs/2.components/8.page-toc.md new file mode 100644 index 0000000..63313a7 --- /dev/null +++ b/content/docs/2.components/8.page-toc.md @@ -0,0 +1,116 @@ +--- +title: PageToc +description: Render an on-page table of contents from app-supplied heading links. +category: content +--- + +::component-preview +--- +name: PageTocDemo +--- +:: + +## Installation + +```bash +npx shadcn-vue@latest add "https://ui.stackhacker.io/r/page-toc.json" +``` + +## Usage + +```vue + + + +``` + +`href` defaults to `#${id}`. Pass a full path when your page shell needs route-aware anchors. + +```ts +const headings: PageTocItem[] = [ + { id: "installation", title: "Installation", href: "/docs/components/page-toc#installation" }, +]; +``` + +## App-Owned Headings + +`PageToc` owns the visual table-of-contents list and active-link behavior for the data you pass in. Your app owns markdown parsing, heading extraction, page layout, and current route handling. + +For Nuxt Content, map `page.body.toc.links` or another app-level source into `PageTocItem[]` before rendering the component. + +```ts +import type { PageTocItem } from "@/components/page-toc"; + +const headings: PageTocItem[] = page.body.toc.links.flatMap(link => [ + { + id: link.id, + title: link.text, + depth: link.depth, + }, + ...(link.children ?? []).map(child => ({ + id: child.id, + title: child.text, + depth: child.depth, + })), +]); +``` + +The component intentionally does not import Nuxt Content types, query content collections, inspect the current route, or create the page's aside column. + +## Active State + +By default, `PageToc` observes matching heading elements by `id` in the browser and highlights the active link. + +Use controlled active state when your page already knows the active heading, or when rendering a static preview. + +```vue + +``` + +## Examples + +### Default + +::component-preview +--- +name: PageTocDemo +--- +:: + +## API Reference + +### Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `items` | `PageTocItem[]` | `[]` | Heading links supplied by your app. | +| `title` | `string` | `"On this page"` | Label rendered above the links. Use an empty string to hide it. | +| `trackActive` | `boolean` | `true` | Observe matching page headings and update the active link. | +| `activeId` | `string` | — | Controlled active heading id. Disables internal active tracking when supplied. | +| `class` | `HTMLAttributes["class"]` | — | Additional CSS classes for the root nav. | + +### Types + +```ts +interface PageTocItem { + id: string; + title: string; + href?: string; + depth?: number; +} +``` diff --git a/content/docs/2.components/9.docs-pager.md b/content/docs/2.components/9.docs-pager.md new file mode 100644 index 0000000..747428d --- /dev/null +++ b/content/docs/2.components/9.docs-pager.md @@ -0,0 +1,93 @@ +--- +title: DocsPager +description: Render previous and next documentation links from app-owned page data. +category: content +--- + +::component-preview +--- +name: DocsPagerDemo +--- +:: + +## Installation + +```bash +npx shadcn-vue@latest add "https://ui.stackhacker.io/r/docs-pager.json" +``` + +## Usage + +```vue + + + +``` + +## App-Owned Pages + +`DocsPager` only renders the portable pager UI. Your app owns page discovery, sorting, current page detection, and route generation. + +Map Nuxt Content surroundings, a static docs array, a navigation tree, or any other source into `DocsPagerItem` objects before passing them to the component. + +```ts +import type { DocsPagerItem } from '@/components/docs-pager' + +const previous: DocsPagerItem | null = surrounding.previous + ? { + title: surrounding.previous.title, + description: surrounding.previous.description, + href: surrounding.previous.path + } + : null +``` + +The component intentionally does not query Nuxt Content, compute page order, require `NuxtLink`, or own documentation layout. + +## Examples + +### Default + +::component-preview +--- +name: DocsPagerDemo +--- +:: + +## API Reference + +### Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `previous` | `DocsPagerItem \| null` | `null` | Previous page supplied by your app. | +| `next` | `DocsPagerItem \| null` | `null` | Next page supplied by your app. | +| `previousLabel` | `string` | `'Previous'` | Label shown above the previous page title. | +| `nextLabel` | `string` | `'Next'` | Label shown above the next page title. | +| `class` | `string` | — | Additional CSS classes for the root element. | + +### Types + +```ts +interface DocsPagerItem { + title: string + href: string + description?: string +} +``` diff --git a/scripts/registry-verify.ts b/scripts/registry-verify.ts index 40fa0ac..0b39d7c 100644 --- a/scripts/registry-verify.ts +++ b/scripts/registry-verify.ts @@ -95,6 +95,14 @@ const expectedItems: Record = { dependencies: ["@lucide/vue"], registryDependencies: [], }, + "page-toc": { + dependencies: [], + registryDependencies: [], + }, + "docs-pager": { + dependencies: ["@lucide/vue"], + registryDependencies: [], + }, "pricing-plans": { dependencies: ["@lucide/vue"], registryDependencies: ["badge", "button", "card"],