@takazudo/zudo-doc 0.2.21 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/gen-z-index.mjs +164 -0
- package/bin/tags-audit-runner.ts +126 -0
- package/bin/tags-audit.mjs +64 -0
- package/bin/zudo-doc.mjs +83 -0
- package/dist/ai-chat-modal/index.d.ts +8 -0
- package/dist/ai-chat-modal/index.js +243 -0
- package/dist/body-foot-util/body-foot-util-area.d.ts +4 -8
- package/dist/body-foot-util/edit-link.d.ts +4 -8
- package/dist/body-foot-util/index.d.ts +4 -3
- package/dist/breadcrumb/breadcrumb.d.ts +5 -9
- package/dist/breadcrumb/find-path.d.ts +2 -5
- package/dist/breadcrumb/index.d.ts +4 -4
- package/dist/breadcrumb/types.d.ts +3 -5
- package/dist/category-nav/index.d.ts +86 -0
- package/dist/category-nav/index.js +44 -0
- package/dist/category-tree-nav/index.d.ts +65 -0
- package/dist/category-tree-nav/index.js +24 -0
- package/dist/code-group/index.d.ts +10 -0
- package/dist/code-group/index.js +37 -0
- package/dist/code-syntax/code-block-enhancer-script.d.ts +1 -3
- package/dist/code-syntax/code-block-enhancer.d.ts +3 -6
- package/dist/code-syntax/index.d.ts +9 -8
- package/dist/code-syntax/mermaid-init-script.d.ts +3 -5
- package/dist/code-syntax/mermaid-init.d.ts +4 -7
- package/dist/code-syntax/tabs-init-script.d.ts +1 -3
- package/dist/code-syntax/tabs-init.d.ts +3 -6
- package/dist/code-syntax/tabs.d.ts +4 -7
- package/dist/color-scheme-utils.d.ts +125 -0
- package/dist/color-scheme-utils.js +151 -0
- package/dist/compose-meta-title/index.d.ts +7 -0
- package/dist/compose-meta-title/index.js +10 -0
- package/dist/content/component-map.d.ts +13 -17
- package/dist/content/content-blockquote.d.ts +3 -6
- package/dist/content/content-code.d.ts +3 -6
- package/dist/content/content-code.js +1 -1
- package/dist/content/content-link.d.ts +3 -6
- package/dist/content/content-link.js +1 -1
- package/dist/content/content-ol.d.ts +3 -6
- package/dist/content/content-paragraph.d.ts +3 -6
- package/dist/content/content-strong.d.ts +3 -6
- package/dist/content/content-table.d.ts +3 -6
- package/dist/content/content-ul.d.ts +3 -6
- package/dist/content/heading-h2.d.ts +3 -6
- package/dist/content/heading-h3.d.ts +3 -6
- package/dist/content/heading-h4.d.ts +3 -6
- package/dist/content/index.d.ts +29 -13
- package/dist/content-admonition/index.d.ts +15 -0
- package/dist/content-admonition/index.js +14 -0
- package/dist/design-token-panel-bootstrap.d.ts +38 -0
- package/dist/design-token-panel-bootstrap.js +29 -0
- package/dist/desktop-sidebar-toggle-island/index.d.ts +5 -0
- package/dist/desktop-sidebar-toggle-island/index.js +62 -0
- package/dist/details/details.d.ts +3 -7
- package/dist/details/index.d.ts +2 -2
- package/dist/doc-body-end/index.d.ts +21 -0
- package/dist/doc-body-end/index.js +15 -0
- package/dist/doc-content-header/index.d.ts +69 -0
- package/dist/doc-content-header/index.js +49 -0
- package/dist/doc-history/index.d.ts +10 -0
- package/dist/doc-history/index.js +407 -0
- package/dist/doc-history-area/index.d.ts +84 -0
- package/dist/doc-history-area/index.js +76 -0
- package/dist/doc-metainfo-area/index.d.ts +46 -0
- package/dist/doc-metainfo-area/index.js +41 -0
- package/dist/doc-page-props/index.d.ts +93 -0
- package/dist/doc-page-props/index.js +0 -0
- package/dist/doc-page-renderer/index.d.ts +128 -0
- package/dist/doc-page-renderer/index.js +103 -0
- package/dist/doc-page-shell/index.d.ts +136 -0
- package/dist/doc-page-shell/index.js +127 -0
- package/dist/doc-pager/index.d.ts +25 -0
- package/dist/doc-pager/index.js +41 -0
- package/dist/doc-route-entries/index.d.ts +125 -0
- package/dist/doc-route-entries/index.js +76 -0
- package/dist/doc-route-paths/index.d.ts +54 -0
- package/dist/doc-route-paths/index.js +61 -0
- package/dist/doc-tags-area/index.d.ts +38 -0
- package/dist/doc-tags-area/index.js +30 -0
- package/dist/doclayout/anchors.d.ts +7 -9
- package/dist/doclayout/doc-layout-with-defaults.d.ts +16 -12
- package/dist/doclayout/doc-layout.d.ts +5 -9
- package/dist/doclayout/index.d.ts +6 -6
- package/dist/eject/index.d.ts +33 -0
- package/dist/eject/index.js +229 -0
- package/dist/extract-headings/index.d.ts +43 -0
- package/dist/extract-headings/index.js +111 -0
- package/dist/factory-context/index.d.ts +80 -0
- package/dist/factory-context/index.js +0 -0
- package/dist/features.css +882 -0
- package/dist/footer/footer.d.ts +4 -8
- package/dist/footer/index.d.ts +13 -3
- package/dist/footer/types.d.ts +4 -6
- package/dist/footer-with-defaults/index.d.ts +63 -0
- package/dist/footer-with-defaults/index.js +128 -0
- package/dist/frontmatter-preview-data/index.d.ts +26 -0
- package/dist/frontmatter-preview-data/index.js +23 -0
- package/dist/github-helpers/index.d.ts +17 -0
- package/dist/github-helpers/index.js +16 -0
- package/dist/head/doc-head.d.ts +3 -6
- package/dist/head/index.d.ts +9 -5
- package/dist/head/og-tags.d.ts +4 -7
- package/dist/head/twitter-card.d.ts +3 -6
- package/dist/head/types.d.ts +1 -3
- package/dist/head-with-defaults/index.d.ts +49 -0
- package/dist/head-with-defaults/index.js +62 -0
- package/dist/header/header.d.ts +6 -10
- package/dist/header/index.d.ts +16 -6
- package/dist/header/nav-active.d.ts +5 -7
- package/dist/header/nav-overflow-script.d.ts +1 -3
- package/dist/header/right-items.d.ts +3 -6
- package/dist/header/types.d.ts +10 -12
- package/dist/header-with-defaults/index.d.ts +77 -0
- package/dist/header-with-defaults/index.js +163 -0
- package/dist/html-preview-wrapper/dedent.d.ts +1 -3
- package/dist/html-preview-wrapper/highlighted-code.d.ts +3 -7
- package/dist/html-preview-wrapper/html-preview-wrapper.d.ts +6 -10
- package/dist/html-preview-wrapper/html-preview.d.ts +5 -9
- package/dist/html-preview-wrapper/index.d.ts +8 -5
- package/dist/html-preview-wrapper/preflight.d.ts +1 -3
- package/dist/html-preview-wrapper/preview-base.d.ts +4 -8
- package/dist/i18n-version/index.d.ts +7 -5
- package/dist/i18n-version/language-switcher.d.ts +5 -8
- package/dist/i18n-version/types.d.ts +3 -5
- package/dist/i18n-version/version-banner.d.ts +4 -8
- package/dist/i18n-version/version-switcher.d.ts +7 -10
- package/dist/icons/index.d.ts +9 -13
- package/dist/image-enlarge/index.d.ts +13 -0
- package/dist/image-enlarge/index.js +176 -0
- package/dist/index.d.ts +26 -2
- package/dist/inline-version-switcher/index.d.ts +31 -0
- package/dist/inline-version-switcher/index.js +44 -0
- package/dist/integrations/claude-resources/escape-for-mdx.d.ts +1 -3
- package/dist/integrations/claude-resources/generate.d.ts +2 -4
- package/dist/integrations/claude-resources/index.d.ts +7 -10
- package/dist/integrations/doc-history/index.d.ts +21 -24
- package/dist/integrations/doc-history/pre-build.d.ts +7 -9
- package/dist/integrations/llms-txt/dev-middleware.d.ts +7 -11
- package/dist/integrations/llms-txt/emit.d.ts +2 -7
- package/dist/integrations/llms-txt/generate.d.ts +3 -8
- package/dist/integrations/llms-txt/index.d.ts +33 -8
- package/dist/integrations/llms-txt/load.d.ts +3 -7
- package/dist/integrations/llms-txt/strip.d.ts +2 -6
- package/dist/integrations/llms-txt/types.d.ts +9 -13
- package/dist/integrations/search-index/build-emitter.d.ts +4 -7
- package/dist/integrations/search-index/collect.d.ts +2 -5
- package/dist/integrations/search-index/dev-middleware.d.ts +5 -8
- package/dist/integrations/search-index/index.d.ts +7 -5
- package/dist/integrations/search-index/types.d.ts +5 -7
- package/dist/island-types/index.d.ts +44 -0
- package/dist/island-types/index.js +12 -0
- package/dist/locale-merge/index.d.ts +114 -0
- package/dist/locale-merge/index.js +27 -0
- package/dist/math-block/index.d.ts +18 -0
- package/dist/math-block/index.js +29 -0
- package/dist/md-utils/index.d.ts +6 -8
- package/dist/md-utils/index.js +2 -1
- package/dist/mdx-components/index.d.ts +46 -0
- package/dist/mdx-components/index.js +161 -0
- package/dist/mermaid-enlarge/index.d.ts +11 -0
- package/dist/mermaid-enlarge/index.js +290 -0
- package/dist/metainfo/doc-metainfo.d.ts +5 -9
- package/dist/metainfo/doc-tags.d.ts +7 -11
- package/dist/metainfo/frontmatter-preview.d.ts +8 -12
- package/dist/metainfo/index.d.ts +6 -4
- package/dist/nav-data-prep/index.d.ts +72 -0
- package/dist/nav-data-prep/index.js +34 -0
- package/dist/nav-indexing/category-nav.d.ts +4 -8
- package/dist/nav-indexing/category-tree-nav.d.ts +4 -8
- package/dist/nav-indexing/doc-card-grid.d.ts +4 -8
- package/dist/nav-indexing/docs-sitemap.d.ts +4 -8
- package/dist/nav-indexing/index.d.ts +37 -10
- package/dist/nav-indexing/nav-card-grid.d.ts +4 -8
- package/dist/nav-indexing/site-tree-nav-demo.d.ts +4 -8
- package/dist/nav-indexing/tag-nav.d.ts +6 -10
- package/dist/nav-indexing/types.d.ts +6 -8
- package/dist/nav-indexing/versions-page-content.d.ts +4 -8
- package/dist/nav-scope/index.d.ts +41 -0
- package/dist/nav-scope/index.js +41 -0
- package/dist/nav-source-cache/index.d.ts +13 -0
- package/dist/nav-source-cache/index.js +28 -0
- package/dist/nav-source-docs/index.d.ts +169 -0
- package/dist/nav-source-docs/index.js +123 -0
- package/dist/page-loading/index.d.ts +1 -2
- package/dist/page-loading/page-loading-overlay.d.ts +4 -8
- package/dist/plugins/claude-resources.d.ts +3 -0
- package/dist/plugins/claude-resources.js +27 -0
- package/dist/plugins/connect-adapter.d.ts +31 -0
- package/dist/plugins/connect-adapter.js +78 -0
- package/dist/plugins/doc-history.d.ts +3 -0
- package/dist/plugins/doc-history.js +54 -0
- package/dist/plugins/llms-txt.d.ts +3 -0
- package/dist/plugins/llms-txt.js +48 -0
- package/dist/plugins/plugin-utils.d.ts +17 -0
- package/dist/plugins/plugin-utils.js +11 -0
- package/dist/plugins/routes.d.ts +2 -0
- package/dist/plugins/routes.js +100 -0
- package/dist/plugins/search-index.d.ts +3 -0
- package/dist/plugins/search-index.js +24 -0
- package/dist/preset.d.ts +198 -0
- package/dist/preset.js +185 -0
- package/dist/render-markdown/index.d.ts +5 -0
- package/dist/render-markdown/index.js +115 -0
- package/dist/robots.d.ts +36 -0
- package/dist/robots.js +17 -0
- package/dist/route-enumerators/index.d.ts +204 -0
- package/dist/route-enumerators/index.js +156 -0
- package/dist/routes/404.d.ts +7 -0
- package/dist/routes/404.js +47 -0
- package/dist/routes/_chrome.d.ts +32 -0
- package/dist/routes/_chrome.js +365 -0
- package/dist/routes/_context.d.ts +56 -0
- package/dist/routes/_context.js +222 -0
- package/dist/routes/_docs-helpers.d.ts +43 -0
- package/dist/routes/_docs-helpers.js +173 -0
- package/dist/routes/_virtual.d.js +0 -0
- package/dist/routes/api-ai-chat.d.ts +5 -0
- package/dist/routes/api-ai-chat.js +24 -0
- package/dist/routes/docs-slug.d.ts +21 -0
- package/dist/routes/docs-slug.js +26 -0
- package/dist/routes/docs-tags-index.d.ts +7 -0
- package/dist/routes/docs-tags-index.js +11 -0
- package/dist/routes/docs-tags-tag.d.ts +23 -0
- package/dist/routes/docs-tags-tag.js +19 -0
- package/dist/routes/docs-versions.d.ts +7 -0
- package/dist/routes/docs-versions.js +11 -0
- package/dist/routes/index.d.ts +7 -0
- package/dist/routes/index.js +117 -0
- package/dist/routes/locale-docs-slug.d.ts +27 -0
- package/dist/routes/locale-docs-slug.js +40 -0
- package/dist/routes/locale-docs-tags-index.d.ts +18 -0
- package/dist/routes/locale-docs-tags-index.js +15 -0
- package/dist/routes/locale-docs-tags-tag.d.ts +25 -0
- package/dist/routes/locale-docs-tags-tag.js +22 -0
- package/dist/routes/locale-docs-versions.d.ts +18 -0
- package/dist/routes/locale-docs-versions.js +15 -0
- package/dist/routes/locale-index.d.ts +24 -0
- package/dist/routes/locale-index.js +132 -0
- package/dist/routes/robots.txt.d.ts +3 -0
- package/dist/routes/robots.txt.js +8 -0
- package/dist/routes/sitemap.xml.d.ts +5 -0
- package/dist/routes/sitemap.xml.js +31 -0
- package/dist/routes/v-docs-slug.d.ts +27 -0
- package/dist/routes/v-docs-slug.js +34 -0
- package/dist/routes/v-locale-docs-slug.d.ts +31 -0
- package/dist/routes/v-locale-docs-slug.js +50 -0
- package/dist/safelist.css +1 -1
- package/dist/search-widget/index.d.ts +38 -0
- package/dist/search-widget/index.js +169 -0
- package/dist/search-widget-script/index.d.ts +18 -0
- package/dist/search-widget-script/index.js +465 -0
- package/dist/settings.d.ts +270 -0
- package/dist/settings.js +0 -0
- package/dist/sidebar/index.d.ts +21 -3
- package/dist/sidebar/sidebar.d.ts +5 -9
- package/dist/sidebar/types.d.ts +4 -6
- package/dist/sidebar-prepaint/index.d.ts +21 -0
- package/dist/sidebar-prepaint/index.js +23 -0
- package/dist/sidebar-resizer/index.d.ts +2 -6
- package/dist/sidebar-resizer/sidebar-resizer-init.d.ts +6 -10
- package/dist/sidebar-toggle-island/index.d.ts +13 -0
- package/dist/sidebar-toggle-island/index.js +122 -0
- package/dist/sidebar-tree/build-tree.d.ts +4 -8
- package/dist/sidebar-tree/category-meta.d.ts +3 -6
- package/dist/sidebar-tree/index.d.ts +18 -3
- package/dist/sidebar-tree/types.d.ts +6 -8
- package/dist/sidebar-tree-island/index.d.ts +13 -0
- package/dist/sidebar-tree-island/index.js +454 -0
- package/dist/sidebar-utils/index.d.ts +64 -0
- package/dist/sidebar-utils/index.js +88 -0
- package/dist/sidebar-with-defaults/index.d.ts +38 -0
- package/dist/sidebar-with-defaults/index.js +45 -0
- package/dist/site-tree-nav/index.d.ts +71 -0
- package/dist/site-tree-nav/index.js +36 -0
- package/dist/site-tree-nav-island/index.d.ts +11 -0
- package/dist/site-tree-nav-island/index.js +176 -0
- package/dist/slug/index.d.ts +4 -0
- package/dist/slug/index.js +19 -0
- package/dist/smart-break/index.d.ts +55 -0
- package/dist/{toc/smart-break.js → smart-break/index.js} +30 -3
- package/dist/tab-item/index.d.ts +2 -2
- package/dist/tab-item/tab-item.d.ts +6 -7
- package/dist/tag-helpers/index.d.ts +40 -0
- package/dist/tag-helpers/index.js +54 -0
- package/dist/tag-pages/index.d.ts +114 -0
- package/dist/tag-pages/index.js +146 -0
- package/dist/tags-audit.d.ts +107 -0
- package/dist/tags-audit.js +321 -0
- package/dist/theme/color-scheme-provider.d.ts +4 -8
- package/dist/theme/color-tweak-export-modal.d.ts +4 -7
- package/dist/theme/design-token-serde.d.ts +14 -17
- package/dist/theme/design-token-types.d.ts +4 -6
- package/dist/theme/iframe-bridge.d.ts +12 -14
- package/dist/theme/index.d.ts +7 -9
- package/dist/theme/theme-toggle.d.ts +3 -8
- package/dist/theme-toggle/color-scheme-sync.d.ts +5 -7
- package/dist/theme-toggle/index.d.ts +4 -8
- package/dist/toc/cx.d.ts +2 -3
- package/dist/toc/index.d.ts +8 -6
- package/dist/toc/mobile-toc.d.ts +5 -9
- package/dist/toc/mobile-toc.js +1 -1
- package/dist/toc/toc-title.d.ts +1 -3
- package/dist/toc/toc.d.ts +5 -9
- package/dist/toc/toc.js +1 -1
- package/dist/toc/types.d.ts +1 -3
- package/dist/toc/use-active-heading.d.ts +4 -7
- package/dist/transitions/index.d.ts +1 -1
- package/dist/transitions/page-events.d.ts +4 -6
- package/dist/tree-nav-shared/index.d.ts +22 -0
- package/dist/tree-nav-shared/index.js +66 -0
- package/dist/url-helpers/index.d.ts +37 -0
- package/dist/url-helpers/index.js +106 -0
- package/dist/url-normalizer/index.d.ts +39 -1
- package/dist/url-normalizer/normalize.d.ts +3 -5
- package/dist/use-modal-dialog/index.d.ts +70 -0
- package/dist/use-modal-dialog/index.js +73 -0
- package/dist/versions-page/index.d.ts +55 -0
- package/dist/versions-page/index.js +73 -0
- package/eject/breadcrumb/breadcrumb.tsx +194 -0
- package/eject/breadcrumb/find-path.ts +26 -0
- package/eject/breadcrumb/index.ts +4 -0
- package/eject/breadcrumb/types.ts +32 -0
- package/eject/code-group/index.tsx +78 -0
- package/eject/content-admonition/index.tsx +50 -0
- package/eject/details/details.tsx +34 -0
- package/eject/details/index.ts +4 -0
- package/eject/doc-pager/index.tsx +91 -0
- package/eject/footer/footer.tsx +167 -0
- package/eject/footer/index.ts +19 -0
- package/eject/footer/types.ts +60 -0
- package/eject/header/header.tsx +725 -0
- package/eject/header/index.ts +36 -0
- package/eject/header/nav-active.ts +122 -0
- package/eject/header/nav-overflow-script.ts +302 -0
- package/eject/header/right-items.ts +78 -0
- package/eject/header/types.ts +69 -0
- package/eject/page-loading/index.ts +10 -0
- package/eject/page-loading/page-loading-overlay.tsx +103 -0
- package/eject/sidebar/index.ts +27 -0
- package/eject/sidebar/sidebar.tsx +137 -0
- package/eject/sidebar/types.ts +73 -0
- package/eject/tab-item/index.ts +7 -0
- package/eject/tab-item/tab-item.tsx +70 -0
- package/eject/theme-toggle/color-scheme-sync.ts +73 -0
- package/eject/theme-toggle/index.tsx +120 -0
- package/eject/toc/cx.ts +38 -0
- package/eject/toc/index.ts +11 -0
- package/eject/toc/mobile-toc.tsx +133 -0
- package/eject/toc/toc-title.ts +33 -0
- package/eject/toc/toc.tsx +119 -0
- package/eject/toc/types.ts +14 -0
- package/eject/toc/use-active-heading.ts +162 -0
- package/package.json +371 -11
- package/routes-src/404.tsx +49 -0
- package/routes-src/_chrome.tsx +470 -0
- package/routes-src/_context.ts +314 -0
- package/routes-src/_docs-helpers.ts +284 -0
- package/routes-src/_virtual.d.ts +15 -0
- package/routes-src/api-ai-chat.tsx +46 -0
- package/routes-src/docs-slug.tsx +43 -0
- package/routes-src/docs-tags-index.tsx +14 -0
- package/routes-src/docs-tags-tag.tsx +29 -0
- package/routes-src/docs-versions.tsx +15 -0
- package/routes-src/index.tsx +134 -0
- package/routes-src/locale-docs-slug.tsx +69 -0
- package/routes-src/locale-docs-tags-index.tsx +23 -0
- package/routes-src/locale-docs-tags-tag.tsx +39 -0
- package/routes-src/locale-docs-versions.tsx +23 -0
- package/routes-src/locale-index.tsx +150 -0
- package/routes-src/robots.txt.tsx +13 -0
- package/routes-src/sitemap.xml.tsx +48 -0
- package/routes-src/v-docs-slug.tsx +64 -0
- package/routes-src/v-locale-docs-slug.tsx +85 -0
- package/dist/toc/smart-break.d.ts +0 -27
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
|
|
4
|
+
// Layout-level JSX port of `src/components/header` for the
|
|
5
|
+
// zudo-doc framework primitives layer (super-epic #473, sub-issue
|
|
6
|
+
// #476). The component is intentionally server-render-friendly: it
|
|
7
|
+
// emits the same markup the legacy Astro template did, leaving the two
|
|
8
|
+
// interactive child islands (the mobile sidebar toggle and the dropdown
|
|
9
|
+
// "..." overflow controller) as either consumer-supplied slots or an
|
|
10
|
+
// inline-script `<script dangerouslySetInnerHTML>`.
|
|
11
|
+
//
|
|
12
|
+
// Why this shape:
|
|
13
|
+
// * The Astro template embeds three Astro-only sub-components
|
|
14
|
+
// (`<LanguageSwitcher />`, `<VersionSwitcher />`, `<Search />`).
|
|
15
|
+
// None of those have a JSX equivalent yet (Task #3 ports them), so
|
|
16
|
+
// they are exposed as `languageSwitcher` / `versionSwitcher` /
|
|
17
|
+
// `search` slot props. The host project keeps using
|
|
18
|
+
// `header` until the sibling ports land — this file exists
|
|
19
|
+
// so consumers of the v2 package can opt into the JSX path early.
|
|
20
|
+
// * The two Preact islands (`SidebarToggle`, `ThemeToggle`) are also
|
|
21
|
+
// accepted as slots so consumers control hydration boundaries
|
|
22
|
+
// (e.g. wrap them in zfb's `<Island when="media">` / `<Island
|
|
23
|
+
// when="load">`). The matching `<slot name="sidebar" />` in the
|
|
24
|
+
// legacy template becomes `sidebarSlot` so the mobile-sheet tree
|
|
25
|
+
// can still flow in from the layout.
|
|
26
|
+
// * Everything host-derived (siteName, headerNav, headerRightItems,
|
|
27
|
+
// githubRepoUrl, …) is supplied via props. Sub-issue #1729 inverted
|
|
28
|
+
// the older direct `@/config` / `@/utils` imports — the host
|
|
29
|
+
// wrapper (`pages/lib/_header-with-defaults.tsx`) now computes the
|
|
30
|
+
// same values from its own modules and passes them through.
|
|
31
|
+
// Pure helpers used by the active-link logic live in
|
|
32
|
+
// `./nav-active.ts` so they stay unit-testable without booting the
|
|
33
|
+
// host config.
|
|
34
|
+
// * The inline overflow script is a pure-JS string emitted via
|
|
35
|
+
// `dangerouslySetInnerHTML` (see `./nav-overflow-script.ts`). The
|
|
36
|
+
// behaviour is identical to the original `<script>` block — only
|
|
37
|
+
// the TypeScript syntax was stripped because a raw `<script>` tag
|
|
38
|
+
// ships its body to the browser as-is.
|
|
39
|
+
|
|
40
|
+
import type { ComponentChildren, JSX, VNode } from "preact";
|
|
41
|
+
import {
|
|
42
|
+
computeActiveNavPath,
|
|
43
|
+
isNavItemActive,
|
|
44
|
+
isNavItemActiveByCategory,
|
|
45
|
+
pathForMatch,
|
|
46
|
+
} from "./nav-active.js";
|
|
47
|
+
import { NAV_OVERFLOW_SCRIPT } from "./nav-overflow-script.js";
|
|
48
|
+
import type {
|
|
49
|
+
HeaderNavItem,
|
|
50
|
+
HeaderRightItem,
|
|
51
|
+
Locale,
|
|
52
|
+
} from "./types.js";
|
|
53
|
+
import { GitHub as GitHubIcon } from "../icons/index.js";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Boundary helpers the host injects into `<Header>`. These are the URL
|
|
57
|
+
* builders the legacy header.tsx used to import from `@/utils/base` —
|
|
58
|
+
* pulled into a single prop bag so the v2 package is host-agnostic.
|
|
59
|
+
*/
|
|
60
|
+
export interface HeaderUrlHelpers {
|
|
61
|
+
withBase(path: string): string;
|
|
62
|
+
stripBase(path: string): string;
|
|
63
|
+
navHref(
|
|
64
|
+
path: string,
|
|
65
|
+
lang?: Locale,
|
|
66
|
+
currentVersion?: string,
|
|
67
|
+
): string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Boundary helpers for locale-aware behaviour the host injects into
|
|
72
|
+
* `<Header>`. Matches the surface the legacy header.tsx used from
|
|
73
|
+
* `@/config/i18n`.
|
|
74
|
+
*/
|
|
75
|
+
export interface HeaderI18n {
|
|
76
|
+
defaultLocale: string;
|
|
77
|
+
locales: readonly string[];
|
|
78
|
+
t(key: string, lang?: string): string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Props for the JSX `<Header />` port. Mirrors the Astro template's
|
|
83
|
+
* `Props` interface plus a small set of slot props that replace child
|
|
84
|
+
* Astro components and named `<slot>` outlets.
|
|
85
|
+
*/
|
|
86
|
+
export interface HeaderProps {
|
|
87
|
+
/** Active locale; `undefined` matches the legacy "no-lang" code path. */
|
|
88
|
+
lang?: Locale;
|
|
89
|
+
/** Current page URL path (as the layout passes from `Astro.url.pathname`). */
|
|
90
|
+
currentPath?: string;
|
|
91
|
+
/** Optional active version slug. Forwarded into `navHref` for nav links. */
|
|
92
|
+
currentVersion?: string;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The page's resolved "big category" (the host's nav section — see
|
|
96
|
+
* `getNavSectionForSlug`). When supplied, the header highlights the
|
|
97
|
+
* nav item whose `categoryMatch` (or a child's) equals this value,
|
|
98
|
+
* which is the authoritative "page is under this category" signal.
|
|
99
|
+
* Falls back to URL-path matching when omitted (home, 404, tag, and
|
|
100
|
+
* version pages carry no section).
|
|
101
|
+
*/
|
|
102
|
+
activeCategory?: string;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Children projected into the mobile `<SidebarToggle>` island —
|
|
106
|
+
* replaces the legacy `<slot name="sidebar" />`. Consumers pass the
|
|
107
|
+
* sidebar tree they want to surface in the mobile sheet.
|
|
108
|
+
*/
|
|
109
|
+
sidebarSlot?: ComponentChildren;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Replacement for the `<SidebarToggle client:media="...">` element in
|
|
113
|
+
* the legacy template. Consumers wrap their preferred Preact /
|
|
114
|
+
* `<Island>` toggle (with the sidebar slot already nested inside) and
|
|
115
|
+
* the shell drops it in untouched. When omitted nothing renders in
|
|
116
|
+
* that slot — the layout is still valid (e.g. doc pages with
|
|
117
|
+
* `hide_sidebar`).
|
|
118
|
+
*/
|
|
119
|
+
sidebarToggle?: ComponentChildren;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Replacement for `<ThemeToggle client:load />`. Rendered only when
|
|
123
|
+
* `colorModeEnabled` is `true` AND a `theme-toggle` entry survives
|
|
124
|
+
* `filterHeaderRightItems` — matching the original template.
|
|
125
|
+
*/
|
|
126
|
+
themeToggle?: ComponentChildren;
|
|
127
|
+
|
|
128
|
+
/** Replacement for the `<LanguageSwitcher />` Astro child. */
|
|
129
|
+
languageSwitcher?: ComponentChildren;
|
|
130
|
+
|
|
131
|
+
/** Replacement for the `<VersionSwitcher />` Astro child. */
|
|
132
|
+
versionSwitcher?: ComponentChildren;
|
|
133
|
+
|
|
134
|
+
/** Replacement for the `<Search />` Astro child. */
|
|
135
|
+
search?: ComponentChildren;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* When provided, emits `data-zfb-transition-persist={persistKey}` on the
|
|
139
|
+
* `<header>` element so zfb's client-router preserves DOM-node identity
|
|
140
|
+
* across same-locale View Transition swaps. Omit to disable persist
|
|
141
|
+
* (back-compat default — the header is re-rendered on every swap).
|
|
142
|
+
*
|
|
143
|
+
* **Locale keying**: callers MUST key by locale (e.g. `"header-en"`,
|
|
144
|
+
* `"header-ja"`). Cross-locale swaps must NOT share the same key; a
|
|
145
|
+
* key mismatch tells the router to replace the header, re-rendering
|
|
146
|
+
* the locale toggle anchors and all other SSR'd locale-specific
|
|
147
|
+
* content with fresh markup. See zudolab/zudo-doc#1546.
|
|
148
|
+
*
|
|
149
|
+
* **headerOverride scope**: hosts that supply their own `<header>`
|
|
150
|
+
* element via `headerOverride` on `<DocLayoutWithDefaults>` are
|
|
151
|
+
* responsible for adding `data-zfb-transition-persist` to their custom
|
|
152
|
+
* element themselves. This package only injects the attribute on the
|
|
153
|
+
* default `<Header>` shell. A key of `"header-${lang}"` is recommended
|
|
154
|
+
* for consistency, matching the Astro reference implementation
|
|
155
|
+
* (zudolab/zudo-doc#1546).
|
|
156
|
+
*/
|
|
157
|
+
persistKey?: string;
|
|
158
|
+
|
|
159
|
+
/** Site-name string shown in the logo anchor (host `settings.siteName`). */
|
|
160
|
+
siteName: string;
|
|
161
|
+
|
|
162
|
+
/** Header-nav items in render order (host `settings.headerNav`). */
|
|
163
|
+
headerNav: HeaderNavItem[];
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Header-right items, **already filtered** by the host via
|
|
167
|
+
* `filterHeaderRightItems` (see `./right-items.ts`). The renderer no
|
|
168
|
+
* longer re-checks `settings.colorMode` / `locales.length` etc — if
|
|
169
|
+
* an item is in this array, the renderer emits its markup. Slot
|
|
170
|
+
* presence (e.g. `languageSwitcher` being supplied) still gates per
|
|
171
|
+
* the legacy template: a slot left undefined renders an empty
|
|
172
|
+
* wrapper, matching prior behaviour.
|
|
173
|
+
*/
|
|
174
|
+
headerRightItems: HeaderRightItem[];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Whether the host has a `colorMode` config. Required because the
|
|
178
|
+
* `theme-toggle` right-item is two-gated: the filter drops it when
|
|
179
|
+
* `colorMode` is off entirely, but the renderer also checks it before
|
|
180
|
+
* emitting the wrapping `<div>` so a `headerRightItems` array that
|
|
181
|
+
* still contains `theme-toggle` but is rendered against a config
|
|
182
|
+
* with `colorMode === false` stays a no-op (the legacy template's
|
|
183
|
+
* shape).
|
|
184
|
+
*/
|
|
185
|
+
colorModeEnabled: boolean;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Whether the host has more than one locale configured (`locales.length > 1`
|
|
189
|
+
* in the legacy template's gate). The renderer needs this to keep
|
|
190
|
+
* the language-switcher slot empty on single-locale sites even when
|
|
191
|
+
* `headerRightItems` contains a `language-switcher` entry.
|
|
192
|
+
*/
|
|
193
|
+
hasLocales: boolean;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Whether the host has `versions` configured. Currently informational —
|
|
197
|
+
* the version-switcher gate lives entirely in the host slot's
|
|
198
|
+
* presence — but accepted for symmetry with `colorModeEnabled` /
|
|
199
|
+
* `hasLocales` so the prop bag fully describes the feature surface.
|
|
200
|
+
*/
|
|
201
|
+
hasVersions: boolean;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Resolved GitHub repo URL (no trailing slash) or `null` when
|
|
205
|
+
* unconfigured. Replaces the legacy direct call to
|
|
206
|
+
* `buildGitHubRepoUrl()` in this component.
|
|
207
|
+
*/
|
|
208
|
+
githubRepoUrl: string | null;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Localised aria-label / title for the GitHub anchor — host-side
|
|
212
|
+
* translated string (the legacy `t("header.github", lang)`).
|
|
213
|
+
*/
|
|
214
|
+
githubLabel: string;
|
|
215
|
+
|
|
216
|
+
/** URL builder helpers — see `HeaderUrlHelpers`. */
|
|
217
|
+
urlHelpers: HeaderUrlHelpers;
|
|
218
|
+
|
|
219
|
+
/** i18n helpers and config — see `HeaderI18n`. */
|
|
220
|
+
i18n: HeaderI18n;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Site-header shell — JSX port of `src/components/header`.
|
|
225
|
+
*
|
|
226
|
+
* Responsibilities (matching the legacy template byte-for-byte):
|
|
227
|
+
* 1. Render the sticky `<header>` with the site logo and the desktop
|
|
228
|
+
* nav, including the dropdown-parent / overflow-bucket markup the
|
|
229
|
+
* controller script reshapes at runtime.
|
|
230
|
+
* 2. Iterate `headerRightItems` (already filtered by the caller via
|
|
231
|
+
* `filterHeaderRightItems`), emitting the matching trigger button /
|
|
232
|
+
* icon link / consumer-supplied slot for each entry.
|
|
233
|
+
* 3. Emit the inline overflow controller script. The script wires the
|
|
234
|
+
* "..." overflow menu, manages `aria-expanded` on dropdowns, and
|
|
235
|
+
* re-runs after View Transitions (via `AFTER_NAVIGATE_EVENT` from
|
|
236
|
+
* `../transitions/page-events`).
|
|
237
|
+
*/
|
|
238
|
+
export function Header(props: HeaderProps): JSX.Element {
|
|
239
|
+
const {
|
|
240
|
+
lang,
|
|
241
|
+
currentPath = "",
|
|
242
|
+
currentVersion,
|
|
243
|
+
activeCategory,
|
|
244
|
+
sidebarSlot,
|
|
245
|
+
sidebarToggle,
|
|
246
|
+
themeToggle,
|
|
247
|
+
languageSwitcher,
|
|
248
|
+
versionSwitcher,
|
|
249
|
+
search,
|
|
250
|
+
persistKey,
|
|
251
|
+
siteName,
|
|
252
|
+
headerNav,
|
|
253
|
+
headerRightItems,
|
|
254
|
+
colorModeEnabled,
|
|
255
|
+
hasLocales,
|
|
256
|
+
githubRepoUrl,
|
|
257
|
+
githubLabel,
|
|
258
|
+
urlHelpers,
|
|
259
|
+
i18n,
|
|
260
|
+
} = props;
|
|
261
|
+
|
|
262
|
+
const isNonDefaultLocale = lang != null && lang !== i18n.defaultLocale;
|
|
263
|
+
const pathWithoutBase = urlHelpers.stripBase(currentPath);
|
|
264
|
+
const matchPath = pathForMatch(pathWithoutBase, lang, i18n.defaultLocale);
|
|
265
|
+
|
|
266
|
+
const activeNavPath = computeActiveNavPath(headerNav, matchPath);
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<header
|
|
270
|
+
class="sticky top-0 z-toolbar flex h-[3.5rem] items-center border-b border-muted bg-surface px-hsp-lg"
|
|
271
|
+
data-header
|
|
272
|
+
// Strategy B persist (zudolab/zudo-doc#1546): the header now carries
|
|
273
|
+
// data-zfb-transition-persist when a locale-keyed persistKey is
|
|
274
|
+
// supplied (e.g. "header-en" / "header-ja"). Cross-locale swaps use
|
|
275
|
+
// different keys, so the router replaces the header element entirely,
|
|
276
|
+
// re-rendering the locale toggle anchors and all locale-specific SSR
|
|
277
|
+
// content with fresh markup. Same-locale swaps share the key and
|
|
278
|
+
// preserve DOM-node identity; each embedded element refreshes via
|
|
279
|
+
// AFTER_NAVIGATE_EVENT or URL derivation:
|
|
280
|
+
// - ThemeToggle: re-applies from localStorage on AFTER_NAVIGATE_EVENT
|
|
281
|
+
// (color-scheme-provider.tsx bootstrap script, #1546 verified (a))
|
|
282
|
+
// - VersionSwitcher: VERSION_SWITCHER_INIT_SCRIPT re-wires toggle on
|
|
283
|
+
// AFTER_NAVIGATE_EVENT (version-switcher.tsx:340, verified (a))
|
|
284
|
+
// - Search: <site-search> custom element re-registers on
|
|
285
|
+
// AFTER_NAVIGATE_EVENT (_search-widget-script.ts:184, verified (a))
|
|
286
|
+
// - SidebarToggle (mobile): closes on AFTER_NAVIGATE_EVENT
|
|
287
|
+
// (sidebar-toggle.tsx:72, verified (a); sidebar content is
|
|
288
|
+
// re-serialised into Island data-props on every SSR render, so
|
|
289
|
+
// same-locale swaps see stale SSR in the persist window but the
|
|
290
|
+
// Island re-hydrates with correct nodes on mount)
|
|
291
|
+
// - Header nav + aria-current: NAV_OVERFLOW_SCRIPT re-runs on
|
|
292
|
+
// AFTER_NAVIGATE_EVENT (nav-overflow-script.ts:198, verified (a))
|
|
293
|
+
// - LanguageSwitcher: SSR'd locale links are locale-static within
|
|
294
|
+
// a same-locale persist window — no per-page fields go stale
|
|
295
|
+
// (verified (a) — locale does not change during same-locale nav)
|
|
296
|
+
// Omit persistKey to fall back to the old repaint-on-every-swap path.
|
|
297
|
+
data-zfb-transition-persist={persistKey}
|
|
298
|
+
>
|
|
299
|
+
{sidebarToggle ?? (
|
|
300
|
+
// Render an inert wrapper so consumers that omit the slot still
|
|
301
|
+
// get the named-slot semantics from the legacy template
|
|
302
|
+
// (mobile sidebar contents stay accessible to assistive tech
|
|
303
|
+
// even without the toggle island).
|
|
304
|
+
<SidebarSlotFallback>{sidebarSlot}</SidebarSlotFallback>
|
|
305
|
+
)}
|
|
306
|
+
|
|
307
|
+
<a
|
|
308
|
+
href={urlHelpers.withBase(isNonDefaultLocale ? `/${lang}/` : "/")}
|
|
309
|
+
class="whitespace-nowrap text-title font-bold text-fg hover:underline focus:underline shrink-0"
|
|
310
|
+
data-header-logo
|
|
311
|
+
>
|
|
312
|
+
{siteName}
|
|
313
|
+
</a>
|
|
314
|
+
|
|
315
|
+
<nav
|
|
316
|
+
aria-label="Main"
|
|
317
|
+
class="relative ml-hsp-xl hidden min-w-0 flex-1 items-center gap-x-hsp-2xs whitespace-nowrap lg:flex"
|
|
318
|
+
data-header-nav
|
|
319
|
+
>
|
|
320
|
+
{headerNav.map((item) => renderNavItem(
|
|
321
|
+
item,
|
|
322
|
+
activeNavPath,
|
|
323
|
+
activeCategory,
|
|
324
|
+
lang,
|
|
325
|
+
currentVersion,
|
|
326
|
+
urlHelpers,
|
|
327
|
+
i18n,
|
|
328
|
+
))}
|
|
329
|
+
|
|
330
|
+
<div class="relative shrink-0" data-nav-more style="display:none">
|
|
331
|
+
<button
|
|
332
|
+
type="button"
|
|
333
|
+
class="px-hsp-md py-vsp-2xs text-small font-medium text-muted hover:underline cursor-pointer"
|
|
334
|
+
data-nav-more-toggle
|
|
335
|
+
aria-expanded="false"
|
|
336
|
+
>
|
|
337
|
+
{"···"}
|
|
338
|
+
</button>
|
|
339
|
+
<ul
|
|
340
|
+
class="absolute right-0 top-full z-dropdown mt-vsp-3xs hidden min-w-[8rem] border border-muted rounded bg-surface shadow-lg whitespace-nowrap"
|
|
341
|
+
data-nav-more-menu
|
|
342
|
+
/>
|
|
343
|
+
</div>
|
|
344
|
+
</nav>
|
|
345
|
+
|
|
346
|
+
<div
|
|
347
|
+
class="ml-auto flex shrink-0 items-center gap-x-hsp-md"
|
|
348
|
+
data-header-right
|
|
349
|
+
>
|
|
350
|
+
{headerRightItems.map((item, i) => renderRightItem(
|
|
351
|
+
item,
|
|
352
|
+
i,
|
|
353
|
+
{
|
|
354
|
+
lang,
|
|
355
|
+
githubRepoUrl,
|
|
356
|
+
githubLabel,
|
|
357
|
+
themeToggle,
|
|
358
|
+
languageSwitcher,
|
|
359
|
+
versionSwitcher,
|
|
360
|
+
search,
|
|
361
|
+
colorModeEnabled,
|
|
362
|
+
hasLocales,
|
|
363
|
+
},
|
|
364
|
+
))}
|
|
365
|
+
</div>
|
|
366
|
+
|
|
367
|
+
<script dangerouslySetInnerHTML={{ __html: NAV_OVERFLOW_SCRIPT }} />
|
|
368
|
+
</header>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Visually-hidden fallback container so the `<slot name="sidebar" />`
|
|
373
|
+
* semantic is preserved even when the consumer doesn't pass an
|
|
374
|
+
* interactive `sidebarToggle` slot. Renders nothing visible — the
|
|
375
|
+
* legacy template only surfaces this content via the toggle island. */
|
|
376
|
+
function SidebarSlotFallback({
|
|
377
|
+
children,
|
|
378
|
+
}: {
|
|
379
|
+
children?: ComponentChildren;
|
|
380
|
+
}): VNode | null {
|
|
381
|
+
if (children === undefined || children === null) return null;
|
|
382
|
+
return <span hidden>{children}</span>;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function renderNavItem(
|
|
386
|
+
item: HeaderNavItem,
|
|
387
|
+
activeNavPath: string | undefined,
|
|
388
|
+
activeCategory: string | undefined,
|
|
389
|
+
lang: Locale | undefined,
|
|
390
|
+
currentVersion: string | undefined,
|
|
391
|
+
urlHelpers: HeaderUrlHelpers,
|
|
392
|
+
i18n: HeaderI18n,
|
|
393
|
+
): VNode {
|
|
394
|
+
// Category matching (the page's resolved big category) is the primary
|
|
395
|
+
// signal; URL-path matching stays as a secondary fallback so items that
|
|
396
|
+
// declare no `categoryMatch`, and pages with no resolved section (home,
|
|
397
|
+
// 404, tag, version), still highlight as they did before.
|
|
398
|
+
const isActive =
|
|
399
|
+
isNavItemActiveByCategory(item, activeCategory) ||
|
|
400
|
+
isNavItemActive(item, activeNavPath);
|
|
401
|
+
const href = urlHelpers.navHref(item.path, lang, currentVersion);
|
|
402
|
+
const label = item.labelKey ? i18n.t(item.labelKey, lang) : item.label;
|
|
403
|
+
|
|
404
|
+
if (item.children && item.children.length > 0) {
|
|
405
|
+
return (
|
|
406
|
+
<div
|
|
407
|
+
class="group relative shrink-0"
|
|
408
|
+
data-nav-item
|
|
409
|
+
data-nav-item-dropdown
|
|
410
|
+
>
|
|
411
|
+
<a
|
|
412
|
+
href={href}
|
|
413
|
+
aria-current={isActive ? "page" : undefined}
|
|
414
|
+
aria-haspopup="true"
|
|
415
|
+
aria-expanded="false"
|
|
416
|
+
class={[
|
|
417
|
+
"flex items-center gap-x-hsp-xs px-hsp-md py-vsp-2xs text-small font-medium transition-colors",
|
|
418
|
+
isActive
|
|
419
|
+
? "bg-fg text-bg"
|
|
420
|
+
: "text-muted hover:underline focus:underline",
|
|
421
|
+
].join(" ")}
|
|
422
|
+
>
|
|
423
|
+
{label}
|
|
424
|
+
<svg
|
|
425
|
+
class={[
|
|
426
|
+
"h-[0.5rem] w-[0.5rem] shrink-0",
|
|
427
|
+
isActive ? "text-bg" : "text-muted",
|
|
428
|
+
].join(" ")}
|
|
429
|
+
fill="none"
|
|
430
|
+
viewBox="0 0 24 24"
|
|
431
|
+
stroke="currentColor"
|
|
432
|
+
stroke-width="3"
|
|
433
|
+
aria-hidden="true"
|
|
434
|
+
>
|
|
435
|
+
<path
|
|
436
|
+
stroke-linecap="round"
|
|
437
|
+
stroke-linejoin="round"
|
|
438
|
+
d="M19 9l-7 7-7-7"
|
|
439
|
+
/>
|
|
440
|
+
</svg>
|
|
441
|
+
</a>
|
|
442
|
+
<div class="absolute left-0 top-full z-dropdown hidden group-hover:block group-focus-within:block pt-vsp-3xs">
|
|
443
|
+
<div class="min-w-[10rem] border border-muted rounded bg-surface shadow-lg py-vsp-3xs">
|
|
444
|
+
{item.children.map((child) => {
|
|
445
|
+
const childHref = urlHelpers.navHref(child.path, lang, currentVersion);
|
|
446
|
+
const childLabel = child.labelKey
|
|
447
|
+
? i18n.t(child.labelKey, lang)
|
|
448
|
+
: child.label;
|
|
449
|
+
const childActive =
|
|
450
|
+
isNavItemActiveByCategory(child, activeCategory) ||
|
|
451
|
+
activeNavPath === child.path;
|
|
452
|
+
return (
|
|
453
|
+
<a
|
|
454
|
+
href={childHref}
|
|
455
|
+
data-active={childActive ? "" : undefined}
|
|
456
|
+
class={[
|
|
457
|
+
"block px-hsp-md py-vsp-2xs text-small hover:bg-accent/10 hover:underline",
|
|
458
|
+
childActive ? "font-bold text-accent" : "text-fg",
|
|
459
|
+
].join(" ")}
|
|
460
|
+
>
|
|
461
|
+
{childLabel}
|
|
462
|
+
</a>
|
|
463
|
+
);
|
|
464
|
+
})}
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return (
|
|
472
|
+
<a
|
|
473
|
+
href={href}
|
|
474
|
+
aria-current={isActive ? "page" : undefined}
|
|
475
|
+
data-nav-item
|
|
476
|
+
class={[
|
|
477
|
+
"px-hsp-md py-vsp-2xs text-small font-medium transition-colors shrink-0",
|
|
478
|
+
isActive
|
|
479
|
+
? "bg-fg text-bg"
|
|
480
|
+
: "text-muted hover:underline focus:underline",
|
|
481
|
+
].join(" ")}
|
|
482
|
+
>
|
|
483
|
+
{label}
|
|
484
|
+
</a>
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
interface RightItemContext {
|
|
489
|
+
lang: Locale | undefined;
|
|
490
|
+
githubRepoUrl: string | null;
|
|
491
|
+
githubLabel: string;
|
|
492
|
+
themeToggle: ComponentChildren;
|
|
493
|
+
languageSwitcher: ComponentChildren;
|
|
494
|
+
versionSwitcher: ComponentChildren;
|
|
495
|
+
search: ComponentChildren;
|
|
496
|
+
colorModeEnabled: boolean;
|
|
497
|
+
hasLocales: boolean;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Shared trigger-button shell for header-right items that dispatch a
|
|
502
|
+
* CustomEvent on click. The legacy template used an inline `onclick`
|
|
503
|
+
* attribute string; we preserve that DOM-level behaviour by spreading via
|
|
504
|
+
* a plain object — Preact's typed JSX rejects a string-valued `onclick`
|
|
505
|
+
* prop, but the renderer forwards the literal attribute through a spread.
|
|
506
|
+
*/
|
|
507
|
+
function TriggerButton({
|
|
508
|
+
index,
|
|
509
|
+
id,
|
|
510
|
+
ariaLabel,
|
|
511
|
+
event,
|
|
512
|
+
children,
|
|
513
|
+
}: {
|
|
514
|
+
index: number;
|
|
515
|
+
id: string;
|
|
516
|
+
ariaLabel: string;
|
|
517
|
+
event: string;
|
|
518
|
+
children: ComponentChildren;
|
|
519
|
+
}): VNode {
|
|
520
|
+
const inlineOnclick: Record<string, string> = {
|
|
521
|
+
onclick: `window.dispatchEvent(new CustomEvent('${event}'))`,
|
|
522
|
+
};
|
|
523
|
+
return (
|
|
524
|
+
<button
|
|
525
|
+
key={`right-${index}`}
|
|
526
|
+
id={id}
|
|
527
|
+
type="button"
|
|
528
|
+
class="flex items-center justify-center text-muted transition-colors hover:text-fg"
|
|
529
|
+
aria-label={ariaLabel}
|
|
530
|
+
{...inlineOnclick}
|
|
531
|
+
>
|
|
532
|
+
{children}
|
|
533
|
+
</button>
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Shared wrapper div for header-right component slots. Pass `className`
|
|
539
|
+
* to apply Tailwind classes; omit it entirely (or pass `undefined`) for
|
|
540
|
+
* the plain `<div>` variant (search slot has no extra class).
|
|
541
|
+
*/
|
|
542
|
+
function SlotWrapper({
|
|
543
|
+
index,
|
|
544
|
+
className,
|
|
545
|
+
children,
|
|
546
|
+
}: {
|
|
547
|
+
index: number;
|
|
548
|
+
className?: string;
|
|
549
|
+
children: ComponentChildren;
|
|
550
|
+
}): VNode {
|
|
551
|
+
return (
|
|
552
|
+
<div key={`right-${index}`} class={className}>
|
|
553
|
+
{children}
|
|
554
|
+
</div>
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
type RightItemHandler = (
|
|
559
|
+
item: HeaderRightItem,
|
|
560
|
+
index: number,
|
|
561
|
+
ctx: RightItemContext,
|
|
562
|
+
) => VNode | null;
|
|
563
|
+
|
|
564
|
+
// Dispatch table keyed by `${type}:${trigger|component}`, or just `type`
|
|
565
|
+
// for link/html items that carry no sub-type discriminant.
|
|
566
|
+
const RIGHT_ITEM_DISPATCH: Record<string, RightItemHandler> = {
|
|
567
|
+
"trigger:design-token-panel": (_item, index) => (
|
|
568
|
+
<TriggerButton
|
|
569
|
+
index={index}
|
|
570
|
+
id="design-token-trigger"
|
|
571
|
+
ariaLabel="Toggle design token panel"
|
|
572
|
+
event="toggle-design-token-panel"
|
|
573
|
+
>
|
|
574
|
+
<svg
|
|
575
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
576
|
+
width="20"
|
|
577
|
+
height="20"
|
|
578
|
+
viewBox="0 0 24 24"
|
|
579
|
+
fill="none"
|
|
580
|
+
stroke="currentColor"
|
|
581
|
+
stroke-width="2"
|
|
582
|
+
stroke-linecap="round"
|
|
583
|
+
stroke-linejoin="round"
|
|
584
|
+
aria-hidden="true"
|
|
585
|
+
>
|
|
586
|
+
<circle cx="13.5" cy="6.5" r="2.5" />
|
|
587
|
+
<circle cx="17.5" cy="10.5" r="2.5" />
|
|
588
|
+
<circle cx="8.5" cy="7.5" r="2.5" />
|
|
589
|
+
<circle cx="6.5" cy="12.5" r="2.5" />
|
|
590
|
+
<path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z" />
|
|
591
|
+
</svg>
|
|
592
|
+
</TriggerButton>
|
|
593
|
+
),
|
|
594
|
+
|
|
595
|
+
"trigger:ai-chat": (_item, index) => (
|
|
596
|
+
<TriggerButton
|
|
597
|
+
index={index}
|
|
598
|
+
id="ai-chat-trigger"
|
|
599
|
+
ariaLabel="Open AI assistant"
|
|
600
|
+
event="toggle-ai-chat"
|
|
601
|
+
>
|
|
602
|
+
<svg
|
|
603
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
604
|
+
width="20"
|
|
605
|
+
height="20"
|
|
606
|
+
viewBox="0 0 24 24"
|
|
607
|
+
fill="currentColor"
|
|
608
|
+
aria-hidden="true"
|
|
609
|
+
>
|
|
610
|
+
<path d="M9.5 2.5Q10.5 11.5 18 13Q10.5 14.5 9.5 23.5Q8.5 14.5 1 13Q8.5 11.5 9.5 2.5Z" />
|
|
611
|
+
<path d="M19 0.5Q19.5 4 23.5 5Q19.5 6 19 9.5Q18.5 6 14.5 5Q18.5 4 19 0.5Z" />
|
|
612
|
+
</svg>
|
|
613
|
+
</TriggerButton>
|
|
614
|
+
),
|
|
615
|
+
|
|
616
|
+
"component:version-switcher": (_item, index, ctx) => (
|
|
617
|
+
<SlotWrapper index={index} className="hidden lg:block">
|
|
618
|
+
{ctx.versionSwitcher}
|
|
619
|
+
</SlotWrapper>
|
|
620
|
+
),
|
|
621
|
+
|
|
622
|
+
"component:github-link": (_item, index, ctx) => {
|
|
623
|
+
if (!ctx.githubRepoUrl) return null;
|
|
624
|
+
return (
|
|
625
|
+
<a
|
|
626
|
+
key={`right-${index}`}
|
|
627
|
+
href={ctx.githubRepoUrl}
|
|
628
|
+
target="_blank"
|
|
629
|
+
rel="noopener noreferrer"
|
|
630
|
+
class="flex items-center justify-center text-muted transition-colors hover:text-fg"
|
|
631
|
+
aria-label={ctx.githubLabel}
|
|
632
|
+
title={ctx.githubLabel}
|
|
633
|
+
>
|
|
634
|
+
<span class="sr-only">{ctx.githubLabel}</span>
|
|
635
|
+
<GitHubIcon />
|
|
636
|
+
</a>
|
|
637
|
+
);
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
"component:theme-toggle": (_item, index, ctx) => {
|
|
641
|
+
// Mirrors the legacy template's two-gate behaviour: the
|
|
642
|
+
// `filterHeaderRightItems` caller drops this item entirely when
|
|
643
|
+
// color-mode is off, but the renderer still cross-checks the host
|
|
644
|
+
// flag so an inconsistent caller (item present + colorMode off)
|
|
645
|
+
// stays a no-op instead of emitting an empty island slot.
|
|
646
|
+
if (!ctx.colorModeEnabled) return null;
|
|
647
|
+
return (
|
|
648
|
+
<SlotWrapper index={index} className="hidden lg:flex items-center">
|
|
649
|
+
{ctx.themeToggle}
|
|
650
|
+
</SlotWrapper>
|
|
651
|
+
);
|
|
652
|
+
},
|
|
653
|
+
|
|
654
|
+
"component:language-switcher": (_item, index, ctx) => {
|
|
655
|
+
// Same two-gate shape as theme-toggle above. The legacy template
|
|
656
|
+
// gated on `lang && locales.length > 1`; the host signals the
|
|
657
|
+
// multi-locale half via `hasLocales`, and the `lang` half is still
|
|
658
|
+
// checked here so single-locale renders (where `lang` is undefined)
|
|
659
|
+
// emit nothing.
|
|
660
|
+
if (!(ctx.lang && ctx.hasLocales)) return null;
|
|
661
|
+
return (
|
|
662
|
+
<SlotWrapper index={index} className="hidden lg:flex items-center">
|
|
663
|
+
{ctx.languageSwitcher}
|
|
664
|
+
</SlotWrapper>
|
|
665
|
+
);
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
"component:search": (_item, index, ctx) => (
|
|
669
|
+
<SlotWrapper index={index}>{ctx.search}</SlotWrapper>
|
|
670
|
+
),
|
|
671
|
+
|
|
672
|
+
link: (item, index) => {
|
|
673
|
+
if (item.type !== "link") return null;
|
|
674
|
+
const label = item.label ?? item.ariaLabel;
|
|
675
|
+
const isExternal = /^https?:\/\//.test(item.href);
|
|
676
|
+
return (
|
|
677
|
+
<a
|
|
678
|
+
key={`right-${index}`}
|
|
679
|
+
href={item.href}
|
|
680
|
+
target={isExternal ? "_blank" : undefined}
|
|
681
|
+
rel={isExternal ? "noopener noreferrer" : undefined}
|
|
682
|
+
class="flex items-center justify-center text-muted transition-colors hover:text-fg"
|
|
683
|
+
aria-label={item.ariaLabel}
|
|
684
|
+
title={label}
|
|
685
|
+
>
|
|
686
|
+
{item.icon === "github" ? (
|
|
687
|
+
<>
|
|
688
|
+
{label && <span class="sr-only">{label}</span>}
|
|
689
|
+
<GitHubIcon />
|
|
690
|
+
</>
|
|
691
|
+
) : (
|
|
692
|
+
label
|
|
693
|
+
)}
|
|
694
|
+
</a>
|
|
695
|
+
);
|
|
696
|
+
},
|
|
697
|
+
|
|
698
|
+
html: (item, index) => {
|
|
699
|
+
if (item.type !== "html") return null;
|
|
700
|
+
return (
|
|
701
|
+
<span
|
|
702
|
+
key={`right-${index}`}
|
|
703
|
+
// Mirrors `<Fragment set:html={item.html} />` from the Astro
|
|
704
|
+
// template — the legacy code already trusts this string, and
|
|
705
|
+
// this port preserves that contract.
|
|
706
|
+
dangerouslySetInnerHTML={{ __html: item.html }}
|
|
707
|
+
/>
|
|
708
|
+
);
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
function renderRightItem(
|
|
713
|
+
item: HeaderRightItem,
|
|
714
|
+
index: number,
|
|
715
|
+
ctx: RightItemContext,
|
|
716
|
+
): VNode | null {
|
|
717
|
+
const key =
|
|
718
|
+
item.type === "trigger"
|
|
719
|
+
? `trigger:${item.trigger}`
|
|
720
|
+
: item.type === "component"
|
|
721
|
+
? `component:${item.component}`
|
|
722
|
+
: item.type;
|
|
723
|
+
const handler = RIGHT_ITEM_DISPATCH[key];
|
|
724
|
+
return handler ? handler(item, index, ctx) : null;
|
|
725
|
+
}
|