@umami/shiso 1.4.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
3
3
  All notable changes to `@umami/shiso` are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## 2.0.0 - 2026-08-21
7
+
8
+ ### Breaking
9
+
10
+ - Move project settings (`docsPrefix`, `contentDir`, `siteUrl`, `locale`) from
11
+ the `$shiso` key in docs.json to a new optional `shiso.config.ts` file at the
12
+ project root. `shiso check` now rejects docs.json files that still contain
13
+ `$shiso` with a migration message.
14
+
15
+ ### Added
16
+
17
+ - New `@umami/shiso/config` export providing `defineConfig` and the
18
+ `ShisoConfig` type for shiso.config.ts.
19
+ - `shiso.config.ts`, `shiso.config.mjs`, and `shiso.config.js` are supported;
20
+ the file is optional and defaults apply when absent. Config edits (and
21
+ creating the file) hot-reload during development.
22
+
6
23
  ## 1.1.11 - 2026-08-19
7
24
 
8
25
  ### Changed
package/bin/shiso.mjs CHANGED
@@ -5,6 +5,7 @@ import fs from 'node:fs/promises';
5
5
  import path from 'node:path';
6
6
  import process from 'node:process';
7
7
  import { fileURLToPath } from 'node:url';
8
+ import { loadShisoConfig } from '../scripts/load-shiso-config.mjs';
8
9
  import { validateProject } from '../scripts/validate-config.mjs';
9
10
 
10
11
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
@@ -22,7 +23,7 @@ Commands:
22
23
  dev Start the development server
23
24
  build Validate and build the static site
24
25
  preview Preview the production build
25
- check Validate docs.json
26
+ check Validate docs.json and shiso.config
26
27
 
27
28
  Options after dev, build, or preview are passed to Vite.
28
29
  `;
@@ -55,6 +56,13 @@ async function check(projectRoot) {
55
56
  }
56
57
 
57
58
  console.log('docs.json is valid.');
59
+
60
+ // Fails fast on a broken shiso.config.* before any Vite build starts.
61
+ const shiso = await loadShisoConfig({ root: projectRoot });
62
+
63
+ if (shiso.sourcePath) {
64
+ console.log(`${path.basename(shiso.sourcePath)} loaded.`);
65
+ }
58
66
  }
59
67
 
60
68
  async function main() {
package/config.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Identity helper that gives shiso.config.ts typing today and room for future
3
+ * config forms (functions, plugins) without another migration. Ships as plain
4
+ * JS so it resolves under Node and jiti with no build step or dependencies.
5
+ */
6
+ export function defineConfig(config) {
7
+ return config;
8
+ }
@@ -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
  }),
@@ -26081,7 +26188,7 @@ function Search({ config, labels }) {
26081
26188
  open,
26082
26189
  onOpenChange: (nextOpen) => nextOpen ? openDialog() : closeDialog(),
26083
26190
  children: [/* @__PURE__ */ jsxs(DialogTrigger, {
26084
- render: /* @__PURE__ */ jsx(Button, {
26191
+ render: /* @__PURE__ */ jsx(Button$1, {
26085
26192
  variant: "outline",
26086
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"
26087
26194
  }),
@@ -26152,7 +26259,7 @@ function ThemeToggle({ label }) {
26152
26259
  localStorage.setItem("shiso-theme", next);
26153
26260
  } catch {}
26154
26261
  };
26155
- return /* @__PURE__ */ jsxs(Button, {
26262
+ return /* @__PURE__ */ jsxs(Button$1, {
26156
26263
  type: "button",
26157
26264
  variant: "ghost",
26158
26265
  size: "icon",
@@ -26197,7 +26304,10 @@ function TopNav({ docs, label }) {
26197
26304
  const navigate = useNavigate();
26198
26305
  const tabs = docs.tabs.filter((tab) => !tab.hidden);
26199
26306
  if (!tabs.length) return null;
26200
- 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;
26201
26311
  const tabClass = (tab) => cn("flex h-full items-center gap-1 whitespace-nowrap border-transparent border-b-2 font-medium", {
26202
26312
  "border-b-primary text-foreground": tab.id === selected,
26203
26313
  "text-muted-foreground hover:text-foreground": tab.id !== selected
@@ -26255,7 +26365,7 @@ function VersionSwitcher() {
26255
26365
  const options = docsSite.scopes.filter((scope) => scope.version && !scope.hidden && scope.language === current.language);
26256
26366
  if (!current.version || options.length < 2 && !current.hidden) return null;
26257
26367
  return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
26258
- render: /* @__PURE__ */ jsx(Button, {
26368
+ render: /* @__PURE__ */ jsx(Button$1, {
26259
26369
  variant: "outline",
26260
26370
  className: "h-auto gap-1.5 rounded-md bg-card px-2.5 py-1.5 text-sm font-medium text-foreground"
26261
26371
  }),
@@ -26277,24 +26387,54 @@ function VersionSwitcher() {
26277
26387
 
26278
26388
  //#endregion
26279
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
+ }
26280
26398
  function NavbarLinkItem({ link, primary = false }) {
26281
26399
  const iconOnly = !link.label;
26282
26400
  const accessibleLabel = link.ariaLabel || link.icon || link.href;
26283
- 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", {
26284
26410
  href: link.href,
26285
- 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,
26286
26412
  target: link.target,
26287
26413
  rel: link.target === "_blank" ? "noreferrer" : void 0,
26288
26414
  "aria-label": iconOnly ? accessibleLabel : void 0,
26289
- children: [/* @__PURE__ */ jsx(ConfiguredIcon, { icon: link.icon }), link.label]
26415
+ children: content
26290
26416
  });
26291
26417
  }
26292
26418
  function Header({ site }) {
26293
26419
  const { logo, navbar, name, appearance, labels, search } = site;
26294
26420
  const { pathname } = useLocation();
26295
26421
  const docs = getScopeByPathname(pathname).docs;
26296
- const brandHref = logo?.href || docsHomeUrl;
26422
+ const brandHref = logo?.href || (hasRootStandalonePage ? "/" : docsHomeUrl);
26297
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
+ ] });
26298
26438
  return /* @__PURE__ */ jsx("header", {
26299
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",
26300
26440
  children: /* @__PURE__ */ jsxs("div", {
@@ -26302,24 +26442,16 @@ function Header({ site }) {
26302
26442
  children: [
26303
26443
  /* @__PURE__ */ jsxs("div", {
26304
26444
  className: "flex min-w-0 items-center gap-5 justify-self-start",
26305
- 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", {
26306
26450
  href: brandHref,
26307
26451
  target: logo?.target,
26308
26452
  rel: logo?.target === "_blank" ? "noreferrer" : void 0,
26309
- className: "inline-flex items-center gap-2 text-xl font-bold text-foreground tracking-[-0.03em]",
26310
- children: [
26311
- logo?.light ? /* @__PURE__ */ jsx("img", {
26312
- src: logo.light,
26313
- alt: "",
26314
- className: "h-6 w-auto dark:hidden"
26315
- }) : null,
26316
- logo?.dark ? /* @__PURE__ */ jsx("img", {
26317
- src: logo.dark,
26318
- alt: "",
26319
- className: "hidden h-6 w-auto dark:block"
26320
- }) : null,
26321
- name ? /* @__PURE__ */ jsx("span", { children: name }) : null
26322
- ]
26453
+ className: brandClassName,
26454
+ children: brandContent
26323
26455
  }) : null, /* @__PURE__ */ jsxs("div", {
26324
26456
  className: "hidden items-center gap-2 lg:flex",
26325
26457
  children: [/* @__PURE__ */ jsx(VersionSwitcher, {}), /* @__PURE__ */ jsx(LanguageSwitcher, {})]
@@ -26365,14 +26497,16 @@ function escapeJsonLd(value) {
26365
26497
  }
26366
26498
  function buildHead(pathname) {
26367
26499
  const page = getPageByPathname(pathname);
26368
- 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;
26369
26503
  const seo = getSeo();
26370
- const pageTitle = frontmatter?.title || page?.label;
26504
+ const pageTitle = frontmatter?.title || standalone?.title || page?.label;
26371
26505
  const title = getPageTitle(pageTitle);
26372
26506
  const description = frontmatter?.description || siteConfig.description;
26373
- const canonical = page ? toAbsoluteUrl(page.url) : void 0;
26507
+ const canonical = page ? toAbsoluteUrl(page.url) : standalone ? toAbsoluteUrl(standalone.path) : void 0;
26374
26508
  const hidden = !!page && (!!page.hidden || !!getScopeByPathname(pathname).hidden);
26375
- const noindex = !page || frontmatter?.noindex === true || hidden && seo.indexing !== "all";
26509
+ const noindex = !page && !standalone || frontmatter?.noindex === true || hidden && seo.indexing !== "all";
26376
26510
  const tags = [{
26377
26511
  tag: "title",
26378
26512
  children: title
@@ -26402,7 +26536,7 @@ function buildHead(pathname) {
26402
26536
  tag: "meta",
26403
26537
  attrs: {
26404
26538
  property: "og:type",
26405
- content: "article"
26539
+ content: standalone ? "website" : "article"
26406
26540
  }
26407
26541
  }, {
26408
26542
  tag: "meta",
@@ -26452,7 +26586,7 @@ function buildHead(pathname) {
26452
26586
  content: description
26453
26587
  }
26454
26588
  });
26455
- const lastModified = page && showTimestamp(frontmatter?.timestamp) ? getLastModified(page.filePath) : void 0;
26589
+ const lastModified = filePath && showTimestamp(frontmatter?.timestamp) ? getLastModified(filePath) : void 0;
26456
26590
  if (lastModified) tags.push({
26457
26591
  tag: "meta",
26458
26592
  attrs: {
@@ -26600,14 +26734,14 @@ function ContextualMenu({ options, labels }) {
26600
26734
  const optionIcon = (option, showCopied = false) => showCopied && copied ? /* @__PURE__ */ jsx(Check$1, { className: "size-3.5" }) : /* @__PURE__ */ jsx(ConfiguredIcon, { icon: option.icon });
26601
26735
  return /* @__PURE__ */ jsxs("div", {
26602
26736
  className: "inline-flex shrink-0 items-stretch",
26603
- children: [/* @__PURE__ */ jsxs(Button, {
26737
+ children: [/* @__PURE__ */ jsxs(Button$1, {
26604
26738
  variant: "outline",
26605
26739
  size: "sm",
26606
26740
  className: "rounded-r-none bg-card text-foreground only:rounded-md",
26607
26741
  onClick: () => runOption(primary),
26608
26742
  children: [optionIcon(primary, primary.action === "copy"), primary.action === "copy" && copied ? labels.copied : primary.title]
26609
26743
  }), options.length > 1 && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
26610
- render: /* @__PURE__ */ jsx(Button, {
26744
+ render: /* @__PURE__ */ jsx(Button$1, {
26611
26745
  variant: "outline",
26612
26746
  size: "icon-sm",
26613
26747
  className: "rounded-l-none border-l-0 bg-card",
@@ -26948,7 +27082,7 @@ function CollapsibleGroup({ node, pathname, depth, drilldown, expandLabel, colla
26948
27082
  className: isTopLevel ? sectionLabelClass : groupLabelClass,
26949
27083
  children: [resolveIcon(node.icon), node.label]
26950
27084
  }), /* @__PURE__ */ jsx(CollapsibleTrigger, {
26951
- render: /* @__PURE__ */ jsx(Button, {
27085
+ render: /* @__PURE__ */ jsx(Button$1, {
26952
27086
  type: "button",
26953
27087
  variant: "ghost",
26954
27088
  size: "icon-xs",
@@ -26958,7 +27092,7 @@ function CollapsibleGroup({ node, pathname, depth, drilldown, expandLabel, colla
26958
27092
  children: chevron
26959
27093
  })]
26960
27094
  }) : /* @__PURE__ */ jsxs(CollapsibleTrigger, {
26961
- render: /* @__PURE__ */ jsx(Button, {
27095
+ render: /* @__PURE__ */ jsx(Button$1, {
26962
27096
  type: "button",
26963
27097
  variant: "ghost",
26964
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", {
@@ -27119,7 +27253,7 @@ function SheetContent({ className, children, side = "right", showCloseButton = t
27119
27253
  ...props,
27120
27254
  children: [children, showCloseButton && /* @__PURE__ */ jsxs(DialogClose, {
27121
27255
  "data-slot": "sheet-close",
27122
- render: /* @__PURE__ */ jsx(Button, {
27256
+ render: /* @__PURE__ */ jsx(Button$1, {
27123
27257
  variant: "ghost",
27124
27258
  className: "absolute top-3 right-3",
27125
27259
  size: "icon-sm"
@@ -27185,7 +27319,7 @@ function Docs({ page, doc, site }) {
27185
27319
  children: [/* @__PURE__ */ jsx("div", {
27186
27320
  className: "flex justify-end lg:hidden",
27187
27321
  children: /* @__PURE__ */ jsxs(SheetTrigger, {
27188
- render: /* @__PURE__ */ jsx(Button, {
27322
+ render: /* @__PURE__ */ jsx(Button$1, {
27189
27323
  variant: "outline",
27190
27324
  className: "bg-card"
27191
27325
  }),
@@ -27277,6 +27411,40 @@ function DocPage({ site }) {
27277
27411
  });
27278
27412
  }
27279
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
+
27280
27448
  //#endregion
27281
27449
  //#region src/App.tsx
27282
27450
  const mdxComponents = {
@@ -27289,19 +27457,29 @@ function App() {
27289
27457
  components: mdxComponents,
27290
27458
  children: /* @__PURE__ */ jsx(Layout, {
27291
27459
  site: siteModel,
27292
- children: /* @__PURE__ */ jsxs(Routes, { children: [docsHomeUrl !== "/" ? /* @__PURE__ */ jsx(Route, {
27293
- path: "/",
27294
- element: /* @__PURE__ */ jsx(Navigate, {
27295
- to: docsHomeUrl,
27296
- 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 })
27297
27478
  })
27298
- }) : null, /* @__PURE__ */ jsx(Route, {
27299
- path: "*",
27300
- element: /* @__PURE__ */ jsx(DocPage, { site: siteModel })
27301
- })] })
27479
+ ] })
27302
27480
  })
27303
27481
  }) });
27304
27482
  }
27305
27483
 
27306
27484
  //#endregion
27307
- 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 };
@@ -1,4 +1,4 @@
1
- import { h as BrowserRouter, p as BASE_URL, t as App } from "./chunks/App.js";
1
+ import { g as BrowserRouter, m as BASE_URL, t as App } from "./chunks/App.js";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
 
4
4
  //#region src/entry-client.tsx