@umami/shiso 1.3.0 → 1.5.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bin/shiso.mjs +10 -1
  3. package/config.js +8 -0
  4. package/dist/chunks/App.js +270 -66
  5. package/dist/chunks/local.js +12 -3
  6. package/dist/chunks/pagefind.js +100 -0
  7. package/dist/entry-client.js +1 -1
  8. package/dist/entry-server.js +16 -5
  9. package/dist/search.js +10 -1
  10. package/docs.schema.json +30 -27
  11. package/package.json +10 -1
  12. package/scripts/build-runtime.mjs +1 -0
  13. package/scripts/check-package.mjs +1 -0
  14. package/scripts/generate-icon-registry.mjs +12 -2
  15. package/scripts/generate-search-index.mjs +4 -3
  16. package/scripts/load-shiso-config.mjs +167 -0
  17. package/scripts/pagefind-index.mjs +127 -0
  18. package/scripts/prerender.mjs +4 -2
  19. package/scripts/validate-config.mjs +23 -10
  20. package/scripts/vite-docs-config.mjs +52 -13
  21. package/src/App.tsx +12 -3
  22. package/src/components/DocContent.tsx +18 -4
  23. package/src/components/Header.tsx +61 -24
  24. package/src/components/Search.tsx +29 -2
  25. package/src/components/TopNav.tsx +9 -7
  26. package/src/components/docs/Button.tsx +67 -0
  27. package/src/components/docs/index.ts +1 -0
  28. package/src/components/ui/command.tsx +2 -2
  29. package/src/declarations.d.ts +7 -0
  30. package/src/entry-server.tsx +20 -3
  31. package/src/lib/content.ts +10 -2
  32. package/src/lib/head.ts +15 -7
  33. package/src/lib/locale.ts +1 -1
  34. package/src/lib/paths.ts +11 -7
  35. package/src/lib/search/provider.ts +13 -2
  36. package/src/lib/search/providers/pagefind.ts +180 -0
  37. package/src/lib/search.ts +32 -4
  38. package/src/lib/site-config.ts +21 -3
  39. package/src/lib/site-model.ts +7 -2
  40. package/src/lib/standalone-pages.ts +129 -0
  41. package/src/lib/types.ts +34 -4
  42. package/src/pages/StandalonePage.tsx +42 -0
  43. package/types/config.d.ts +19 -0
  44. package/types/search.d.ts +9 -0
  45. package/vite.config.ts +58 -18
@@ -11,6 +11,7 @@ import { MDXProvider } from "@mdx-js/react";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
  import * as ReactDOM from "react-dom";
13
13
  import { ICON_REGISTRY } from "@/lib/icon-registry.generated";
14
+ import shiso from "virtual:shiso-config";
14
15
  import rawConfig from "virtual:shiso-docs-config";
15
16
  import { LAST_MODIFIED } from "@/generated/last-modified";
16
17
 
@@ -4561,7 +4562,7 @@ function renderTag(Tag, props) {
4561
4562
  *
4562
4563
  * Documentation: [Base UI Button](https://base-ui.com/react/components/button)
4563
4564
  */
4564
- const Button$1 = /*#__PURE__*/ React$1.forwardRef(function Button(componentProps, forwardedRef) {
4565
+ const Button$2 = /*#__PURE__*/ React$1.forwardRef(function Button(componentProps, forwardedRef) {
4565
4566
  const { render, className, disabled = false, focusableWhenDisabled = false, nativeButton = true, style, ...elementProps } = componentProps;
4566
4567
  const { getButtonProps, buttonRef } = useButton({
4567
4568
  disabled,
@@ -4574,7 +4575,7 @@ const Button$1 = /*#__PURE__*/ React$1.forwardRef(function Button(componentProps
4574
4575
  props: [elementProps, getButtonProps]
4575
4576
  });
4576
4577
  });
4577
- Button$1.displayName = "Button";
4578
+ Button$2.displayName = "Button";
4578
4579
 
4579
4580
  //#endregion
4580
4581
  //#region ../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
@@ -8063,8 +8064,8 @@ const buttonVariants = cva("group/button inline-flex shrink-0 items-center justi
8063
8064
  size: "default"
8064
8065
  }
8065
8066
  });
8066
- function Button({ className, variant = "default", size = "default", ...props }) {
8067
- return /* @__PURE__ */ jsx(Button$1, {
8067
+ function Button$1({ className, variant = "default", size = "default", ...props }) {
8068
+ return /* @__PURE__ */ jsx(Button$2, {
8068
8069
  "data-slot": "button",
8069
8070
  className: cn(buttonVariants({
8070
8071
  variant,
@@ -9582,7 +9583,7 @@ function CodeBlock({ children, className }) {
9582
9583
  className: `code-block p-3 pr-12 text-sm text-foreground leading-[1.6] font-mono ${className || ""}`,
9583
9584
  children
9584
9585
  })
9585
- }), /* @__PURE__ */ jsx(Button, {
9586
+ }), /* @__PURE__ */ jsx(Button$1, {
9586
9587
  type: "button",
9587
9588
  variant: "ghost",
9588
9589
  size: "icon-sm",
@@ -11332,6 +11333,40 @@ function Badge({ color, size = "md", shape = "rounded", icon, stroke = false, di
11332
11333
  });
11333
11334
  }
11334
11335
 
11336
+ //#endregion
11337
+ //#region src/components/docs/Button.tsx
11338
+ /**
11339
+ * MDX-facing button. Renders a link when `href` is set (internal routes go
11340
+ * through react-router), otherwise a plain button element.
11341
+ */
11342
+ function Button({ href, variant = "default", size = "default", icon, className, children }) {
11343
+ const resolvedIcon = resolveIcon(icon, 16);
11344
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [resolvedIcon, children] });
11345
+ const baseClassName = cn(resolvedIcon ? "gap-2" : "", "[&_p]:m-0", className);
11346
+ if (!href) return /* @__PURE__ */ jsx(Button$1, {
11347
+ variant,
11348
+ size,
11349
+ className: baseClassName,
11350
+ children: content
11351
+ });
11352
+ const linkClassName = cn(buttonVariants({
11353
+ variant,
11354
+ size
11355
+ }), "no-underline hover:no-underline active:no-underline", baseClassName);
11356
+ if (/^https?:\/\//i.test(href)) return /* @__PURE__ */ jsx("a", {
11357
+ href,
11358
+ className: linkClassName,
11359
+ target: "_blank",
11360
+ rel: "noreferrer",
11361
+ children: content
11362
+ });
11363
+ return /* @__PURE__ */ jsx(Link, {
11364
+ to: href,
11365
+ className: linkClassName,
11366
+ children: content
11367
+ });
11368
+ }
11369
+
11335
11370
  //#endregion
11336
11371
  //#region src/components/ui/alert.tsx
11337
11372
  const alertVariants = cva("group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", {
@@ -20509,7 +20544,7 @@ function DialogContent$1({ className, children, showCloseButton = true, overlayC
20509
20544
  ...props,
20510
20545
  children: [children, showCloseButton && /* @__PURE__ */ jsxs(DialogClose, {
20511
20546
  "data-slot": "dialog-close",
20512
- render: /* @__PURE__ */ jsx(Button, {
20547
+ render: /* @__PURE__ */ jsx(Button$1, {
20513
20548
  variant: "ghost",
20514
20549
  className: "absolute top-2 right-2",
20515
20550
  size: "icon-sm"
@@ -20571,6 +20606,7 @@ var docs_exports = /* @__PURE__ */ __exportAll({
20571
20606
  Accordion: () => Accordion,
20572
20607
  AccordionGroup: () => AccordionGroup,
20573
20608
  Badge: () => Badge,
20609
+ Button: () => Button,
20574
20610
  Callout: () => Callout,
20575
20611
  Card: () => Card,
20576
20612
  CardGroup: () => CardGroup,
@@ -20628,7 +20664,7 @@ function Banner({ banner, dismissLabel }) {
20628
20664
  children: [/* @__PURE__ */ jsx("div", {
20629
20665
  className: "[&_a:hover]:opacity-[0.85] [&_a]:text-inherit [&_a]:underline [&_a]:underline-offset-2 [&_code]:rounded-sm [&_code]:bg-black/20 [&_code]:px-1 [&_code]:py-[0.0625rem] [&_code]:text-[0.8125rem] [&_code]:font-mono",
20630
20666
  children: renderInlineMarkdown(banner.content)
20631
- }), banner.dismissible && /* @__PURE__ */ jsx(Button, {
20667
+ }), banner.dismissible && /* @__PURE__ */ jsx(Button$1, {
20632
20668
  type: "button",
20633
20669
  variant: "ghost",
20634
20670
  size: "icon-xs",
@@ -22216,8 +22252,10 @@ function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
22216
22252
  * DOCS_PREFIX but not BASE_URL. React Router's `basename` adds BASE_URL, so
22217
22253
  * only code that bypasses the router (prerender output paths, canonical URLs,
22218
22254
  * raw <a href>) needs `toHref`.
22255
+ *
22256
+ * Values from `virtual:shiso-config` arrive with defaults applied and already
22257
+ * normalized by scripts/load-shiso-config.mjs.
22219
22258
  */
22220
- const shiso = rawConfig.$shiso || {};
22221
22259
  /** Strips trailing slashes; "/" and "" both normalize to "". */
22222
22260
  function normalizePrefix(value) {
22223
22261
  const trimmed = value.trim().replace(/\/+$/, "");
@@ -22225,11 +22263,14 @@ function normalizePrefix(value) {
22225
22263
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
22226
22264
  }
22227
22265
  const BASE_URL = normalizePrefix(import.meta.env?.BASE_URL || "/");
22228
- const DOCS_PREFIX = normalizePrefix(shiso.docsPrefix ?? "/docs");
22266
+ const DOCS_PREFIX = shiso.docsPrefix;
22229
22267
  /** Content directory, relative to the project root, without leading/trailing slashes. */
22230
- const CONTENT_DIR = (shiso.contentDir ?? "content/docs").replace(/^\/+|\/+$/g, "");
22268
+ const CONTENT_DIR = shiso.contentDir;
22269
+ /** Fixed root for standalone (non-docs) page files. Not configurable, so page
22270
+ * slugs can never collide with the docs content tree. */
22271
+ const PAGES_DIR = "content/pages";
22231
22272
  /** Absolute origin used for canonical and og:url tags. Undefined when unconfigured. */
22232
- const SITE_URL = shiso.siteUrl?.replace(/\/+$/, "") || void 0;
22273
+ const SITE_URL = shiso.siteUrl;
22233
22274
  /** Joins path segments with exactly one slash between them. */
22234
22275
  function joinPath(...parts) {
22235
22276
  const joined = parts.filter((part) => !!part).join("/").replace(/\/{2,}/g, "/");
@@ -22813,7 +22854,7 @@ function isNodeHidden(node) {
22813
22854
  * without Suspense, at the cost of bundling all pages together.
22814
22855
  *
22815
22856
  * The glob pattern must be a literal for Vite to statically analyze it, so it
22816
- * covers all of `content/` and the configured `$shiso.contentDir` is applied at
22857
+ * covers all of `content/` and the configured shiso.config `contentDir` is applied at
22817
22858
  * lookup time instead. That also lets later versioned/localized content roots
22818
22859
  * (`content/v2`, `content/es`) work without touching this glob.
22819
22860
  */
@@ -22826,6 +22867,13 @@ const docModules = import.meta.glob("/content/**/*.{md,mdx}", { eager: true });
22826
22867
  function resolveDocFile(fileSlug, contentDir = CONTENT_DIR) {
22827
22868
  return [`/${contentDir}/${fileSlug}.mdx`, `/${contentDir}/${fileSlug}.md`].find((candidate) => candidate in docModules);
22828
22869
  }
22870
+ /**
22871
+ * Resolves a standalone page slug (docs.json `pages[].page`) to a module key
22872
+ * under the fixed content/pages root.
22873
+ */
22874
+ function resolvePageFile(fileSlug) {
22875
+ return resolveDocFile(fileSlug, PAGES_DIR);
22876
+ }
22829
22877
  function getDocModule(filePath) {
22830
22878
  return docModules[filePath];
22831
22879
  }
@@ -22866,7 +22914,7 @@ function isValidLocale(value) {
22866
22914
  }
22867
22915
  /**
22868
22916
  * Locale for a page: its scope's language code when valid, then the
22869
- * site-wide `$shiso.locale`, then en-US.
22917
+ * site-wide shiso.config `locale`, then en-US.
22870
22918
  */
22871
22919
  function resolveLocale(language, fallback) {
22872
22920
  if (isValidLocale(language)) return Intl.getCanonicalLocales(language)[0];
@@ -22975,7 +23023,7 @@ function normalizeFooter(config) {
22975
23023
  attribution
22976
23024
  } : null;
22977
23025
  }
22978
- function resolveSiteModel(config, docs) {
23026
+ function resolveSiteModel(config, docs, shiso) {
22979
23027
  const appearance = config.appearance || {};
22980
23028
  const logo = config.logo ? typeof config.logo === "string" ? {
22981
23029
  light: config.logo,
@@ -23008,7 +23056,7 @@ function resolveSiteModel(config, docs) {
23008
23056
  },
23009
23057
  showTimestamp: config.metadata?.timestamp === true,
23010
23058
  drilldown: config.interaction?.drilldown,
23011
- locale: config.$shiso?.locale?.trim() || "en-US",
23059
+ locale: shiso?.locale || "en-US",
23012
23060
  labels: SHISO_THEME_LABELS,
23013
23061
  docs
23014
23062
  };
@@ -23085,6 +23133,58 @@ function resolveContextualOptions(options, page, labels = SHISO_THEME_LABELS) {
23085
23133
  return resolved;
23086
23134
  }
23087
23135
 
23136
+ //#endregion
23137
+ //#region src/lib/standalone-pages.ts
23138
+ function invalid(message) {
23139
+ return /* @__PURE__ */ new Error(`Invalid docs config: ${message}`);
23140
+ }
23141
+ /** Trims and canonicalizes a standalone route path; throws when malformed. */
23142
+ function normalizePath(rawPath) {
23143
+ const value = typeof rawPath === "string" ? rawPath.trim() : "";
23144
+ if (!value.startsWith("/")) throw invalid(`standalone page path "${String(rawPath)}" must start with "/".`);
23145
+ if (/[:*]/.test(value)) throw invalid(`standalone page path "${value}" must not use wildcard patterns.`);
23146
+ if (/\.mdx?$/i.test(value)) throw invalid(`standalone page path "${value}" must be a route, not a file — drop the extension.`);
23147
+ return value.replace(/\/{2,}/g, "/").replace(/\/+$/, "") || "/";
23148
+ }
23149
+ /** Mirrors normalizePageReference in docs-config.ts for the `page` slug. */
23150
+ function normalizePageSlug(rawSlug) {
23151
+ return (typeof rawSlug === "string" ? rawSlug : "").trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/^pages\//, "").replace(/\.mdx?$/, "").replace(/\/+$/, "") || "index";
23152
+ }
23153
+ function normalizeStandalonePages(config, resolvePageFile, site, options = {}) {
23154
+ const items = config.pages || [];
23155
+ if (!items.length) return [];
23156
+ const docsPrefix = options.docsPrefix || "";
23157
+ const pages = [];
23158
+ const seen = /* @__PURE__ */ new Set();
23159
+ for (const item of items) {
23160
+ const path = normalizePath(item?.path);
23161
+ if (seen.has(path)) throw invalid(`duplicate standalone page path "${path}".`);
23162
+ seen.add(path);
23163
+ if (path === "/404") throw invalid("standalone page path \"/404\" is reserved for the error page.");
23164
+ const docsPage = site.pageByUrl[path];
23165
+ if (docsPage) throw invalid(`standalone page path "${path}" collides with the docs page "${docsPage.fileSlug}". Standalone pages must live outside the docs navigation.`);
23166
+ if (docsPrefix && (path === docsPrefix || path.startsWith(`${docsPrefix}/`))) throw invalid(`standalone page path "${path}" is inside the docs prefix "${docsPrefix}". Standalone pages must live outside the docs tree.`);
23167
+ const fileSlug = normalizePageSlug(item?.page);
23168
+ const filePath = resolvePageFile(fileSlug);
23169
+ if (!filePath) throw new Error(`Missing standalone page file for "${fileSlug}": expected "content/pages/${fileSlug}.mdx" or ".md".`);
23170
+ pages.push({
23171
+ path,
23172
+ filePath,
23173
+ title: item?.title?.trim() || void 0
23174
+ });
23175
+ }
23176
+ return pages;
23177
+ }
23178
+ /**
23179
+ * Exact standalone page lookup by base-relative pathname. Tolerates trailing
23180
+ * slashes and an explicit `/index` suffix, like getPageByPathname.
23181
+ */
23182
+ function getStandalonePageByPathname(pages, pathname) {
23183
+ const trimmed = pathname.replace(/\/+$/, "") || "/";
23184
+ const collapsed = trimmed === "/index" ? "/" : trimmed.replace(/\/index$/, "") || "/";
23185
+ return pages.find((page) => page.path === trimmed || page.path === collapsed) || null;
23186
+ }
23187
+
23088
23188
  //#endregion
23089
23189
  //#region src/lib/site-config.ts
23090
23190
  assertDocsConfig(rawConfig, "docs.json");
@@ -23095,7 +23195,14 @@ const docsSite = normalizeDocsSite(siteConfig, resolveDocFile);
23095
23195
  const docsConfig = getDefaultScope(docsSite).docs;
23096
23196
  /** Landing page of the default scope: the site-wide "docs home" URL. */
23097
23197
  const docsHomeUrl = getDefaultScope(docsSite).firstPageUrl;
23098
- const siteModel = resolveSiteModel(siteConfig, docsConfig);
23198
+ const siteModel = resolveSiteModel(siteConfig, docsConfig, shiso);
23199
+ /** Standalone pages declared with the top-level `pages` key, e.g. a home page. */
23200
+ const standalonePages = normalizeStandalonePages(siteConfig, resolvePageFile, docsSite, { docsPrefix: DOCS_PREFIX });
23201
+ /** True when a standalone page owns "/", replacing the root docs redirect. */
23202
+ const hasRootStandalonePage = standalonePages.some((page) => page.path === "/");
23203
+ function getStandalonePage(pathname) {
23204
+ return getStandalonePageByPathname(standalonePages, stripBase(pathname));
23205
+ }
23099
23206
  /** Scope that owns the current pathname; the default scope for unknown paths. */
23100
23207
  function getScopeByPathname(pathname) {
23101
23208
  const page = getPageByPathname(pathname);
@@ -23168,7 +23275,7 @@ function LanguageSwitcher() {
23168
23275
  const options = getLanguageScopes(docsSite);
23169
23276
  if (!current.language || options.length < 2 && !current.hidden) return null;
23170
23277
  return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
23171
- render: /* @__PURE__ */ jsx(Button, {
23278
+ render: /* @__PURE__ */ jsx(Button$1, {
23172
23279
  variant: "outline",
23173
23280
  className: "h-auto gap-1.5 rounded-md bg-card px-2.5 py-1.5 text-sm font-medium text-foreground"
23174
23281
  }),
@@ -25967,7 +26074,7 @@ function CommandGroup({ className, ...props }) {
25967
26074
  function CommandItem({ className, children, ...props }) {
25968
26075
  return /* @__PURE__ */ jsxs(_e.Item, {
25969
26076
  "data-slot": "command-item",
25970
- className: cn("group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:*:[svg]:text-foreground", className),
26077
+ className: cn("group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-muted data-[selected=true]:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[selected=true]:*:[svg]:text-foreground", className),
25971
26078
  ...props,
25972
26079
  children: [children, /* @__PURE__ */ jsx(Check$1, { className: "ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" })]
25973
26080
  });
@@ -25975,6 +26082,21 @@ function CommandItem({ className, children, ...props }) {
25975
26082
 
25976
26083
  //#endregion
25977
26084
  //#region src/components/Search.tsx
26085
+ /** Enough results to make the list scroll; the dialog caps its own height. */
26086
+ const RESULT_LIMIT = 30;
26087
+ /**
26088
+ * Renders a snippet, turning provider-supplied `<mark>` tags into highlight
26089
+ * elements. Snippets are parsed — never injected as HTML — so any other
26090
+ * markup in the text renders literally.
26091
+ */
26092
+ function renderSnippet(snippet) {
26093
+ const parts = snippet.split(/<mark>(.*?)<\/mark>/g);
26094
+ if (parts.length === 1) return snippet;
26095
+ return parts.map((part, index) => index % 2 ? /* @__PURE__ */ jsx("mark", {
26096
+ className: "rounded-xs bg-primary/15 px-px text-primary",
26097
+ children: part
26098
+ }, index) : part);
26099
+ }
25978
26100
  /**
25979
26101
  * Provider-neutral search dialog. The selected provider and its index or
25980
26102
  * client are loaded on demand, so search stays out of the initial bundle.
@@ -26011,7 +26133,7 @@ function Search({ config, labels }) {
26011
26133
  try {
26012
26134
  const activeProvider = await loadProvider();
26013
26135
  const scope = getScopeByPathname(pathname);
26014
- const nextResults = await activeProvider.search(value, void 0, {
26136
+ const nextResults = await activeProvider.search(value, RESULT_LIMIT, {
26015
26137
  scopeId: scope.id,
26016
26138
  language: scope.language,
26017
26139
  version: scope.version
@@ -26066,7 +26188,7 @@ function Search({ config, labels }) {
26066
26188
  open,
26067
26189
  onOpenChange: (nextOpen) => nextOpen ? openDialog() : closeDialog(),
26068
26190
  children: [/* @__PURE__ */ jsxs(DialogTrigger, {
26069
- render: /* @__PURE__ */ jsx(Button, {
26191
+ render: /* @__PURE__ */ jsx(Button$1, {
26070
26192
  variant: "outline",
26071
26193
  className: "h-auto gap-2 rounded-md bg-card px-2.5 py-1.5 text-muted-foreground hover:border-input hover:bg-card hover:text-foreground"
26072
26194
  }),
@@ -26113,7 +26235,7 @@ function Search({ config, labels }) {
26113
26235
  children: [result.page, result.heading ? ` › ${result.heading}` : ""]
26114
26236
  }), result.snippet && /* @__PURE__ */ jsx("div", {
26115
26237
  className: "mt-[0.15rem] line-clamp-2 text-[0.8rem] text-muted-foreground",
26116
- children: result.snippet
26238
+ children: renderSnippet(result.snippet)
26117
26239
  })]
26118
26240
  }, result.url))
26119
26241
  }) : null]
@@ -26137,7 +26259,7 @@ function ThemeToggle({ label }) {
26137
26259
  localStorage.setItem("shiso-theme", next);
26138
26260
  } catch {}
26139
26261
  };
26140
- return /* @__PURE__ */ jsxs(Button, {
26262
+ return /* @__PURE__ */ jsxs(Button$1, {
26141
26263
  type: "button",
26142
26264
  variant: "ghost",
26143
26265
  size: "icon",
@@ -26182,7 +26304,10 @@ function TopNav({ docs, label }) {
26182
26304
  const navigate = useNavigate();
26183
26305
  const tabs = docs.tabs.filter((tab) => !tab.hidden);
26184
26306
  if (!tabs.length) return null;
26185
- const selected = docs.pages.find((item) => item.url === pathname)?.tabId || [...tabs].sort((a, b) => b.url.length - a.url.length).find((tab) => pathname === tab.url || pathname.startsWith(`${tab.url}/`))?.id || tabs[0]?.id;
26307
+ const page = docs.pages.find((item) => item.url === pathname);
26308
+ const matchedTabId = [...tabs].sort((a, b) => b.url.length - a.url.length).find((tab) => pathname === tab.url || pathname.startsWith(`${tab.url}/`))?.id;
26309
+ const fallbackTabId = getStandalonePage(pathname) ? void 0 : tabs[0]?.id;
26310
+ const selected = page?.tabId || matchedTabId || fallbackTabId;
26186
26311
  const tabClass = (tab) => cn("flex h-full items-center gap-1 whitespace-nowrap border-transparent border-b-2 font-medium", {
26187
26312
  "border-b-primary text-foreground": tab.id === selected,
26188
26313
  "text-muted-foreground hover:text-foreground": tab.id !== selected
@@ -26240,7 +26365,7 @@ function VersionSwitcher() {
26240
26365
  const options = docsSite.scopes.filter((scope) => scope.version && !scope.hidden && scope.language === current.language);
26241
26366
  if (!current.version || options.length < 2 && !current.hidden) return null;
26242
26367
  return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
26243
- render: /* @__PURE__ */ jsx(Button, {
26368
+ render: /* @__PURE__ */ jsx(Button$1, {
26244
26369
  variant: "outline",
26245
26370
  className: "h-auto gap-1.5 rounded-md bg-card px-2.5 py-1.5 text-sm font-medium text-foreground"
26246
26371
  }),
@@ -26262,24 +26387,54 @@ function VersionSwitcher() {
26262
26387
 
26263
26388
  //#endregion
26264
26389
  //#region src/components/Header.tsx
26390
+ /**
26391
+ * Header hrefs come from config, so they may point inside the site or off it.
26392
+ * In-app routes go through react-router; anything external (or explicitly
26393
+ * opened in a new tab) stays a plain anchor and triggers a document load.
26394
+ */
26395
+ function isRoutedHref(href, target) {
26396
+ return href.startsWith("/") && !isExternalHref(href) && target !== "_blank";
26397
+ }
26265
26398
  function NavbarLinkItem({ link, primary = false }) {
26266
26399
  const iconOnly = !link.label;
26267
26400
  const accessibleLabel = link.ariaLabel || link.icon || link.href;
26268
- return /* @__PURE__ */ jsxs("a", {
26401
+ const className = primary ? `ml-1 inline-flex items-center justify-center rounded-full bg-primary text-sm font-semibold text-primary-foreground hover:opacity-90 ${iconOnly ? "size-8" : "gap-1.5 px-3.5 py-1.5"}` : `inline-flex items-center rounded-md text-sm font-medium text-foreground hover:bg-accent hover:text-foreground ${iconOnly ? "size-8 justify-center" : "gap-1.5 px-2.5 py-1.5"}`;
26402
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ConfiguredIcon, { icon: link.icon }), link.label] });
26403
+ if (isRoutedHref(link.href, link.target)) return /* @__PURE__ */ jsx(Link, {
26404
+ to: link.href,
26405
+ className,
26406
+ "aria-label": iconOnly ? accessibleLabel : void 0,
26407
+ children: content
26408
+ });
26409
+ return /* @__PURE__ */ jsx("a", {
26269
26410
  href: link.href,
26270
- className: primary ? `ml-1 inline-flex items-center justify-center rounded-full bg-primary text-sm font-semibold text-primary-foreground hover:opacity-90 ${iconOnly ? "size-8" : "gap-1.5 px-3.5 py-1.5"}` : `inline-flex items-center rounded-md text-sm font-medium text-foreground hover:bg-accent hover:text-foreground ${iconOnly ? "size-8 justify-center" : "gap-1.5 px-2.5 py-1.5"}`,
26411
+ className,
26271
26412
  target: link.target,
26272
26413
  rel: link.target === "_blank" ? "noreferrer" : void 0,
26273
26414
  "aria-label": iconOnly ? accessibleLabel : void 0,
26274
- children: [/* @__PURE__ */ jsx(ConfiguredIcon, { icon: link.icon }), link.label]
26415
+ children: content
26275
26416
  });
26276
26417
  }
26277
26418
  function Header({ site }) {
26278
26419
  const { logo, navbar, name, appearance, labels, search } = site;
26279
26420
  const { pathname } = useLocation();
26280
26421
  const docs = getScopeByPathname(pathname).docs;
26281
- const brandHref = logo?.href || docsHomeUrl;
26422
+ const brandHref = logo?.href || (hasRootStandalonePage ? "/" : docsHomeUrl);
26282
26423
  const hasBrand = !!name || !!logo?.light || !!logo?.dark;
26424
+ const brandClassName = "inline-flex items-center gap-2 text-xl font-bold text-foreground tracking-[-0.03em]";
26425
+ const brandContent = /* @__PURE__ */ jsxs(Fragment, { children: [
26426
+ logo?.light ? /* @__PURE__ */ jsx("img", {
26427
+ src: logo.light,
26428
+ alt: "",
26429
+ className: "h-6 w-auto dark:hidden"
26430
+ }) : null,
26431
+ logo?.dark ? /* @__PURE__ */ jsx("img", {
26432
+ src: logo.dark,
26433
+ alt: "",
26434
+ className: "hidden h-6 w-auto dark:block"
26435
+ }) : null,
26436
+ name ? /* @__PURE__ */ jsx("span", { children: name }) : null
26437
+ ] });
26283
26438
  return /* @__PURE__ */ jsx("header", {
26284
26439
  className: "sticky top-0 z-50 h-[var(--header-height)] shrink-0 border-border border-b bg-[color-mix(in_srgb,var(--background)_92%,transparent)] backdrop-blur-md",
26285
26440
  children: /* @__PURE__ */ jsxs("div", {
@@ -26287,24 +26442,16 @@ function Header({ site }) {
26287
26442
  children: [
26288
26443
  /* @__PURE__ */ jsxs("div", {
26289
26444
  className: "flex min-w-0 items-center gap-5 justify-self-start",
26290
- children: [hasBrand ? /* @__PURE__ */ jsxs("a", {
26445
+ children: [hasBrand ? isRoutedHref(brandHref, logo?.target) ? /* @__PURE__ */ jsx(Link, {
26446
+ to: brandHref,
26447
+ className: brandClassName,
26448
+ children: brandContent
26449
+ }) : /* @__PURE__ */ jsx("a", {
26291
26450
  href: brandHref,
26292
26451
  target: logo?.target,
26293
26452
  rel: logo?.target === "_blank" ? "noreferrer" : void 0,
26294
- className: "inline-flex items-center gap-2 text-xl font-bold text-foreground tracking-[-0.03em]",
26295
- children: [
26296
- logo?.light ? /* @__PURE__ */ jsx("img", {
26297
- src: logo.light,
26298
- alt: "",
26299
- className: "h-6 w-auto dark:hidden"
26300
- }) : null,
26301
- logo?.dark ? /* @__PURE__ */ jsx("img", {
26302
- src: logo.dark,
26303
- alt: "",
26304
- className: "hidden h-6 w-auto dark:block"
26305
- }) : null,
26306
- name ? /* @__PURE__ */ jsx("span", { children: name }) : null
26307
- ]
26453
+ className: brandClassName,
26454
+ children: brandContent
26308
26455
  }) : null, /* @__PURE__ */ jsxs("div", {
26309
26456
  className: "hidden items-center gap-2 lg:flex",
26310
26457
  children: [/* @__PURE__ */ jsx(VersionSwitcher, {}), /* @__PURE__ */ jsx(LanguageSwitcher, {})]
@@ -26350,14 +26497,16 @@ function escapeJsonLd(value) {
26350
26497
  }
26351
26498
  function buildHead(pathname) {
26352
26499
  const page = getPageByPathname(pathname);
26353
- const frontmatter = (page ? getDocModule(page.filePath) : void 0)?.frontmatter;
26500
+ const standalone = page ? null : getStandalonePage(pathname);
26501
+ const filePath = page?.filePath || standalone?.filePath;
26502
+ const frontmatter = (filePath ? getDocModule(filePath) : void 0)?.frontmatter;
26354
26503
  const seo = getSeo();
26355
- const pageTitle = frontmatter?.title || page?.label;
26504
+ const pageTitle = frontmatter?.title || standalone?.title || page?.label;
26356
26505
  const title = getPageTitle(pageTitle);
26357
26506
  const description = frontmatter?.description || siteConfig.description;
26358
- const canonical = page ? toAbsoluteUrl(page.url) : void 0;
26507
+ const canonical = page ? toAbsoluteUrl(page.url) : standalone ? toAbsoluteUrl(standalone.path) : void 0;
26359
26508
  const hidden = !!page && (!!page.hidden || !!getScopeByPathname(pathname).hidden);
26360
- const noindex = !page || frontmatter?.noindex === true || hidden && seo.indexing !== "all";
26509
+ const noindex = !page && !standalone || frontmatter?.noindex === true || hidden && seo.indexing !== "all";
26361
26510
  const tags = [{
26362
26511
  tag: "title",
26363
26512
  children: title
@@ -26387,7 +26536,7 @@ function buildHead(pathname) {
26387
26536
  tag: "meta",
26388
26537
  attrs: {
26389
26538
  property: "og:type",
26390
- content: "article"
26539
+ content: standalone ? "website" : "article"
26391
26540
  }
26392
26541
  }, {
26393
26542
  tag: "meta",
@@ -26437,7 +26586,7 @@ function buildHead(pathname) {
26437
26586
  content: description
26438
26587
  }
26439
26588
  });
26440
- const lastModified = page && showTimestamp(frontmatter?.timestamp) ? getLastModified(page.filePath) : void 0;
26589
+ const lastModified = filePath && showTimestamp(frontmatter?.timestamp) ? getLastModified(filePath) : void 0;
26441
26590
  if (lastModified) tags.push({
26442
26591
  tag: "meta",
26443
26592
  attrs: {
@@ -26585,14 +26734,14 @@ function ContextualMenu({ options, labels }) {
26585
26734
  const optionIcon = (option, showCopied = false) => showCopied && copied ? /* @__PURE__ */ jsx(Check$1, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ConfiguredIcon, { icon: option.icon });
26586
26735
  return /* @__PURE__ */ jsxs("div", {
26587
26736
  className: "inline-flex shrink-0 items-stretch",
26588
- children: [/* @__PURE__ */ jsxs(Button, {
26737
+ children: [/* @__PURE__ */ jsxs(Button$1, {
26589
26738
  variant: "outline",
26590
26739
  size: "sm",
26591
26740
  className: "rounded-r-none bg-card text-foreground only:rounded-md",
26592
26741
  onClick: () => runOption(primary),
26593
26742
  children: [optionIcon(primary, primary.action === "copy"), primary.action === "copy" && copied ? labels.copied : primary.title]
26594
26743
  }), options.length > 1 && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
26595
- render: /* @__PURE__ */ jsx(Button, {
26744
+ render: /* @__PURE__ */ jsx(Button$1, {
26596
26745
  variant: "outline",
26597
26746
  size: "icon-sm",
26598
26747
  className: "rounded-l-none border-l-0 bg-card",
@@ -26651,7 +26800,8 @@ function resolveRelated(entries) {
26651
26800
  return links;
26652
26801
  }
26653
26802
  function DocContent({ page, doc, site }) {
26654
- const pagerPages = getScopeForPage(docsSite, page).docs.pages.filter((item) => !item.hidden);
26803
+ const scope = getScopeForPage(docsSite, page);
26804
+ const pagerPages = scope.docs.pages.filter((item) => !item.hidden);
26655
26805
  const pageIndex = pagerPages.findIndex((item) => item.slug === page.slug);
26656
26806
  const prev = pageIndex > 0 ? pagerPages[pageIndex - 1] : void 0;
26657
26807
  const next = pageIndex >= 0 ? pagerPages[pageIndex + 1] : void 0;
@@ -26666,8 +26816,16 @@ function DocContent({ page, doc, site }) {
26666
26816
  dateStyle: "medium",
26667
26817
  timeZone: "UTC"
26668
26818
  });
26819
+ const pagefindAttrs = page.hidden || scope.hidden ? {} : {
26820
+ "data-pagefind-body": "",
26821
+ ...page.scopeId !== "default" ? {
26822
+ "data-pagefind-filter": "scope[data-scope]",
26823
+ "data-scope": page.scopeId
26824
+ } : {}
26825
+ };
26669
26826
  return /* @__PURE__ */ jsxs("article", {
26670
26827
  className: "min-w-0 grow",
26828
+ ...pagefindAttrs,
26671
26829
  children: [
26672
26830
  eyebrow && /* @__PURE__ */ jsx("div", {
26673
26831
  className: "text-sm font-bold text-primary",
@@ -26705,6 +26863,7 @@ function DocContent({ page, doc, site }) {
26705
26863
  related.length > 0 && /* @__PURE__ */ jsxs("nav", {
26706
26864
  className: "mt-8",
26707
26865
  "aria-label": site.labels.relatedTopics,
26866
+ "data-pagefind-ignore": true,
26708
26867
  children: [/* @__PURE__ */ jsx("div", {
26709
26868
  className: "text-sm text-muted-foreground",
26710
26869
  children: site.labels.relatedTopics
@@ -26731,6 +26890,7 @@ function DocContent({ page, doc, site }) {
26731
26890
  }),
26732
26891
  /* @__PURE__ */ jsxs("div", {
26733
26892
  className: "mt-8 flex items-center justify-between",
26893
+ "data-pagefind-ignore": true,
26734
26894
  children: [/* @__PURE__ */ jsx(NavigationButton, {
26735
26895
  ...prev,
26736
26896
  isPrev: true
@@ -26922,7 +27082,7 @@ function CollapsibleGroup({ node, pathname, depth, drilldown, expandLabel, colla
26922
27082
  className: isTopLevel ? sectionLabelClass : groupLabelClass,
26923
27083
  children: [resolveIcon(node.icon), node.label]
26924
27084
  }), /* @__PURE__ */ jsx(CollapsibleTrigger, {
26925
- render: /* @__PURE__ */ jsx(Button, {
27085
+ render: /* @__PURE__ */ jsx(Button$1, {
26926
27086
  type: "button",
26927
27087
  variant: "ghost",
26928
27088
  size: "icon-xs",
@@ -26932,7 +27092,7 @@ function CollapsibleGroup({ node, pathname, depth, drilldown, expandLabel, colla
26932
27092
  children: chevron
26933
27093
  })]
26934
27094
  }) : /* @__PURE__ */ jsxs(CollapsibleTrigger, {
26935
- render: /* @__PURE__ */ jsx(Button, {
27095
+ render: /* @__PURE__ */ jsx(Button$1, {
26936
27096
  type: "button",
26937
27097
  variant: "ghost",
26938
27098
  className: cn(headerClass, "group/section group/collapsible-trigger flex h-auto w-full items-center justify-start gap-[0.4rem] whitespace-normal rounded-none px-0 py-0 text-left hover:bg-transparent aria-expanded:bg-transparent dark:hover:bg-transparent", {
@@ -27093,7 +27253,7 @@ function SheetContent({ className, children, side = "right", showCloseButton = t
27093
27253
  ...props,
27094
27254
  children: [children, showCloseButton && /* @__PURE__ */ jsxs(DialogClose, {
27095
27255
  "data-slot": "sheet-close",
27096
- render: /* @__PURE__ */ jsx(Button, {
27256
+ render: /* @__PURE__ */ jsx(Button$1, {
27097
27257
  variant: "ghost",
27098
27258
  className: "absolute top-3 right-3",
27099
27259
  size: "icon-sm"
@@ -27159,7 +27319,7 @@ function Docs({ page, doc, site }) {
27159
27319
  children: [/* @__PURE__ */ jsx("div", {
27160
27320
  className: "flex justify-end lg:hidden",
27161
27321
  children: /* @__PURE__ */ jsxs(SheetTrigger, {
27162
- render: /* @__PURE__ */ jsx(Button, {
27322
+ render: /* @__PURE__ */ jsx(Button$1, {
27163
27323
  variant: "outline",
27164
27324
  className: "bg-card"
27165
27325
  }),
@@ -27251,6 +27411,40 @@ function DocPage({ site }) {
27251
27411
  });
27252
27412
  }
27253
27413
 
27414
+ //#endregion
27415
+ //#region src/pages/StandalonePage.tsx
27416
+ /**
27417
+ * A standalone (non-docs) page: site chrome from Layout (banner, header),
27418
+ * the MDX content at full container width — no sidebar, TOC, or pager — and
27419
+ * the footer. MDX components come from the app-level MDXProvider.
27420
+ */
27421
+ function StandalonePageView({ page, site }) {
27422
+ const { pathname } = useLocation();
27423
+ const doc = getDocModule(page.filePath);
27424
+ useEffect(() => {
27425
+ if (!window.location.hash) window.scrollTo({
27426
+ top: 0,
27427
+ left: 0
27428
+ });
27429
+ }, [pathname]);
27430
+ if (!doc) return /* @__PURE__ */ jsx(Docs, {
27431
+ page: null,
27432
+ doc: null,
27433
+ site
27434
+ });
27435
+ const Content = doc.default;
27436
+ return /* @__PURE__ */ jsxs("div", {
27437
+ className: "flex min-h-full flex-col",
27438
+ children: [/* @__PURE__ */ jsx("article", {
27439
+ className: "grow py-8",
27440
+ children: /* @__PURE__ */ jsx("div", {
27441
+ className: "docs-markdown",
27442
+ children: /* @__PURE__ */ jsx(Content, {})
27443
+ })
27444
+ }), /* @__PURE__ */ jsx(Footer, { footer: site.footer })]
27445
+ });
27446
+ }
27447
+
27254
27448
  //#endregion
27255
27449
  //#region src/App.tsx
27256
27450
  const mdxComponents = {
@@ -27263,19 +27457,29 @@ function App() {
27263
27457
  components: mdxComponents,
27264
27458
  children: /* @__PURE__ */ jsx(Layout, {
27265
27459
  site: siteModel,
27266
- children: /* @__PURE__ */ jsxs(Routes, { children: [docsHomeUrl !== "/" ? /* @__PURE__ */ jsx(Route, {
27267
- path: "/",
27268
- element: /* @__PURE__ */ jsx(Navigate, {
27269
- to: docsHomeUrl,
27270
- replace: true
27460
+ children: /* @__PURE__ */ jsxs(Routes, { children: [
27461
+ standalonePages.map((page) => /* @__PURE__ */ jsx(Route, {
27462
+ path: page.path,
27463
+ element: /* @__PURE__ */ jsx(StandalonePageView, {
27464
+ page,
27465
+ site: siteModel
27466
+ })
27467
+ }, page.path)),
27468
+ docsHomeUrl !== "/" && !hasRootStandalonePage ? /* @__PURE__ */ jsx(Route, {
27469
+ path: "/",
27470
+ element: /* @__PURE__ */ jsx(Navigate, {
27471
+ to: docsHomeUrl,
27472
+ replace: true
27473
+ })
27474
+ }) : null,
27475
+ /* @__PURE__ */ jsx(Route, {
27476
+ path: "*",
27477
+ element: /* @__PURE__ */ jsx(DocPage, { site: siteModel })
27271
27478
  })
27272
- }) : null, /* @__PURE__ */ jsx(Route, {
27273
- path: "*",
27274
- element: /* @__PURE__ */ jsx(DocPage, { site: siteModel })
27275
- })] })
27479
+ ] })
27276
27480
  })
27277
27481
  }) });
27278
27482
  }
27279
27483
 
27280
27484
  //#endregion
27281
- export { createPath as _, docsSite as a, getSeo as c, getLastModified as d, getScopeForPage as f, Router as g, BrowserRouter as h, docsHomeUrl as i, siteName as l, toAbsoluteUrl as m, buildHead as n, getLocaleByPathname as o, BASE_URL as p, renderHeadToString as r, getRedirects as s, App as t, getDocModule as u, parsePath as v, ABSOLUTE_URL_REGEX as y };
27485
+ export { Router as _, docsSite as a, ABSOLUTE_URL_REGEX as b, getSeo as c, getDocModule as d, getLastModified as f, BrowserRouter as g, toAbsoluteUrl as h, docsHomeUrl as i, siteName as l, BASE_URL as m, buildHead as n, getLocaleByPathname as o, getScopeForPage as p, renderHeadToString as r, getRedirects as s, App as t, standalonePages as u, createPath as v, parsePath as y };