@umami/shiso 1.12.0 → 1.14.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 (125) hide show
  1. package/bin/shiso.mjs +0 -0
  2. package/dist/chunks/App.js +166 -139
  3. package/dist/chunks/abnfDiagram-VCTEODGH.js +109 -0
  4. package/dist/chunks/arc.js +130 -0
  5. package/dist/chunks/architecture-7GRP2DOG.js +4 -0
  6. package/dist/chunks/architectureDiagram-5GKGNRK7.js +7466 -0
  7. package/dist/chunks/array.js +8 -0
  8. package/dist/chunks/blockDiagram-I7D4REHJ.js +2546 -0
  9. package/dist/chunks/c4Diagram-7LVT6UL2.js +3654 -0
  10. package/dist/chunks/channel.js +9 -0
  11. package/dist/chunks/chunk-2Q5K7J3B.js +21 -0
  12. package/dist/chunks/chunk-4HAMMTFA.js +6106 -0
  13. package/dist/chunks/chunk-5VM5RSS4.js +21 -0
  14. package/dist/chunks/chunk-75Z2AOVW.js +2185 -0
  15. package/dist/chunks/chunk-DU6HZSFF.js +8729 -0
  16. package/dist/chunks/chunk-F27PBJKO.js +95 -0
  17. package/dist/chunks/chunk-FOHPRMQF.js +27162 -0
  18. package/dist/chunks/chunk-GMAD6QVW.js +2369 -0
  19. package/dist/chunks/chunk-GVQU2GXP.js +58 -0
  20. package/dist/chunks/chunk-IMKFNOWR.js +2593 -0
  21. package/dist/chunks/chunk-JWPE2WC7.js +12 -0
  22. package/dist/chunks/chunk-L3NEJ4N5.js +420 -0
  23. package/dist/chunks/chunk-OSK3NFVY.js +1029 -0
  24. package/dist/chunks/chunk-P2QGCYS3.js +95 -0
  25. package/dist/chunks/chunk-POPQ4Y6H.js +33 -0
  26. package/dist/chunks/chunk-PWAF6VOD.js +467 -0
  27. package/dist/chunks/chunk-SHT3W25Y.js +4813 -0
  28. package/dist/chunks/chunk-SVP7TREG.js +686 -0
  29. package/dist/chunks/chunk-TICWLB2K.js +3800 -0
  30. package/dist/chunks/chunk-XXDRQBXY.js +12 -0
  31. package/dist/chunks/chunk-Y2CYZVJY.js +15 -0
  32. package/dist/chunks/classDiagram-ZZMXUADV.js +33 -0
  33. package/dist/chunks/classDiagram-v2-VYDZK3BY.js +33 -0
  34. package/dist/chunks/cose-bilkent-JH36ORCC.js +4268 -0
  35. package/dist/chunks/cynefin-OW5HDTMX.js +4 -0
  36. package/dist/chunks/cynefinDiagram-5FMLGOSQ.js +482 -0
  37. package/dist/chunks/cytoscape.esm.js +28282 -0
  38. package/dist/chunks/dagre-GXQ25YYZ.js +583 -0
  39. package/dist/chunks/dagre.js +4235 -0
  40. package/dist/chunks/defaultLocale.js +246 -0
  41. package/dist/chunks/diagram-S7CK7UJ4.js +402 -0
  42. package/dist/chunks/diagram-UQ7AKVKN.js +291 -0
  43. package/dist/chunks/diagram-VSXAHHWV.js +553 -0
  44. package/dist/chunks/diagram-VX7I27RA.js +798 -0
  45. package/dist/chunks/diagram-Z3DM3KII.js +186 -0
  46. package/dist/chunks/dist.js +92 -0
  47. package/dist/chunks/docs.js +1163 -347
  48. package/dist/chunks/ebnfDiagram-PWID7BFC.js +123 -0
  49. package/dist/chunks/erDiagram-RLTQ6QDP.js +2295 -0
  50. package/dist/chunks/eventmodeling-NTZA5JFV.js +4 -0
  51. package/dist/chunks/flowDiagram-HODETNUW.js +17 -0
  52. package/dist/chunks/ganttDiagram-EL5Y4UJY.js +3928 -0
  53. package/dist/chunks/gitGraph-4MIJSDKK.js +4 -0
  54. package/dist/chunks/gitGraphDiagram-WWUBYQGX.js +1153 -0
  55. package/dist/chunks/graphlib.js +4194 -0
  56. package/dist/chunks/info-A6RAGUB7.js +4 -0
  57. package/dist/chunks/infoDiagram-27XIBGKW.js +25 -0
  58. package/dist/chunks/init.js +14 -0
  59. package/dist/chunks/ishikawaDiagram-5VMMS53U.js +971 -0
  60. package/dist/chunks/journeyDiagram-3NMN7TZE.js +1226 -0
  61. package/dist/chunks/kanban-definition-UXKFOSKX.js +1241 -0
  62. package/dist/chunks/katex.js +26463 -0
  63. package/dist/chunks/line.js +48 -0
  64. package/dist/chunks/linear.js +391 -0
  65. package/dist/chunks/mermaid-parser.core.js +754 -0
  66. package/dist/chunks/mermaid.core.js +4898 -0
  67. package/dist/chunks/mindmap-definition-YA3MSWOX.js +1309 -0
  68. package/dist/chunks/ordinal.js +84 -0
  69. package/dist/chunks/packet-AYTQ26CC.js +4 -0
  70. package/dist/chunks/path.js +106 -0
  71. package/dist/chunks/pegDiagram-XKGWAZYB.js +115 -0
  72. package/dist/chunks/pie-WAS4IAKB.js +4 -0
  73. package/dist/chunks/pieDiagram-E7YTZNPT.js +297 -0
  74. package/dist/chunks/quadrantDiagram-AXDQQJYC.js +2234 -0
  75. package/dist/chunks/radar-RG4KPBEZ.js +4 -0
  76. package/dist/chunks/railroad-74A4TZTK.js +4 -0
  77. package/dist/chunks/railroad-abnf-HS5TGJTU.js +4 -0
  78. package/dist/chunks/railroad-ebnf-LZEXJU2U.js +4 -0
  79. package/dist/chunks/railroad-peg-WCYAUIDC.js +4 -0
  80. package/dist/chunks/railroadDiagram-O6MQD6OU.js +89 -0
  81. package/dist/chunks/requirementDiagram-BXWQKSXE.js +2464 -0
  82. package/dist/chunks/rough.esm.js +1396 -0
  83. package/dist/chunks/sankeyDiagram-P5KCCOFB.js +1308 -0
  84. package/dist/chunks/sequenceDiagram-WJ2MYXX4.js +5445 -0
  85. package/dist/chunks/sizeCapture-INFHLROL.js +56 -0
  86. package/dist/chunks/src.js +2653 -0
  87. package/dist/chunks/stateDiagram-D77RDMKH.js +368 -0
  88. package/dist/chunks/stateDiagram-v2-MP3YSRHH.js +33 -0
  89. package/dist/chunks/swimlanes-42K2YHIH.js +6925 -0
  90. package/dist/chunks/swimlanesDiagram-VR7AAH4N.js +32 -0
  91. package/dist/chunks/timeline-definition-24CTP7MA.js +1507 -0
  92. package/dist/chunks/treeView-Q6P3EWNA.js +4 -0
  93. package/dist/chunks/treemap-WGGIJYW6.js +4 -0
  94. package/dist/chunks/vennDiagram-4TSXK5OY.js +2803 -0
  95. package/dist/chunks/wardley-WFR3VGLG.js +4 -0
  96. package/dist/chunks/wardleyDiagram-VM6X3IG4.js +877 -0
  97. package/dist/chunks/xychartDiagram-S5SC5T6Z.js +2698 -0
  98. package/dist/components.js +2 -2
  99. package/dist/entry-client.js +1 -1
  100. package/dist/entry-server.js +2 -2
  101. package/docs.schema.json +1 -1
  102. package/package.json +2 -1
  103. package/scripts/expand-navigation-globs.mjs +77 -8
  104. package/scripts/lib/openapi.mjs +24 -13
  105. package/src/components/CodeBlock.tsx +36 -0
  106. package/src/components/Docs.tsx +44 -21
  107. package/src/components/OpenApiOperation.tsx +39 -52
  108. package/src/components/SideNav.tsx +8 -3
  109. package/src/components/docs/Changelog.tsx +163 -0
  110. package/src/components/docs/Mermaid.tsx +164 -0
  111. package/src/components/docs/Panel.tsx +23 -0
  112. package/src/components/docs/Param.tsx +6 -0
  113. package/src/components/docs/PropertiesTable.tsx +52 -36
  114. package/src/components/docs/Tiles.tsx +59 -0
  115. package/src/components/docs/Tree.tsx +359 -0
  116. package/src/components/docs/Update.tsx +72 -0
  117. package/src/components/docs/index.ts +8 -2
  118. package/src/components/docs/panel-context.tsx +25 -0
  119. package/src/components/docs/styles.ts +47 -6
  120. package/src/components/icons/index.ts +4 -0
  121. package/src/lib/openapi.ts +17 -1
  122. package/src/lib/rehype-shiki.ts +18 -1
  123. package/types/components.d.ts +83 -14
  124. package/src/components/docs/ParamField.tsx +0 -45
  125. package/src/components/docs/ResponseField.tsx +0 -36
@@ -1,3 +1,3 @@
1
- import { At as Danger, Bt as AccordionGroup, Dt as CardGroup, Et as Card, Ft as WarningBanner, It as Button, Lt as Badge, Mt as Note, Nt as Tip, Ot as Callout, Pt as Warning, St as CodeGroup, _ as Tooltip, at as Tabs, bt as Column, ct as ResponseField, dt as ParamField, ft as Link, gt as Expandable, it as Tab, jt as Info, kt as Check, lt as PropertiesTable, mt as Frame, n as ZoomableImage, ot as Step, pt as Icon, st as Steps, ut as Param, xt as Columns, zt as Accordion } from "./chunks/docs.js";
1
+ import { At as Column, Bt as Callout, C as Tooltip, Ct as Icon, Et as Expandable, Gt as Tip, Ht as Danger, It as Changelog, Jt as Button, Kt as Warning, Lt as Update, Mt as CodeGroup, Qt as AccordionGroup, Rt as Card, S as TreeFolder, St as Link, Ut as Info, Vt as Check, Wt as Note, Yt as Badge, Zt as Accordion, _ as FileTree, _t as Param, b as Tree, bn as mermaidSource, bt as usePanelContent, dt as Tiles, ft as Tab, gt as PropertiesTable, ht as Steps, jt as Columns, mt as Step, n as ZoomableImage, pt as Tabs, qt as WarningBanner, ut as Tile, v as FileTreeFile, vt as Panel, wt as Frame, x as TreeFile, xt as useSetPanelContent, y as FileTreeFolder, yn as Mermaid, yt as PanelProvider, zt as CardGroup } from "./chunks/docs.js";
2
2
 
3
- export { Accordion, AccordionGroup, Badge, Button, Callout, Card, CardGroup, Check, CodeGroup, Column, Columns, Danger, Expandable, Frame, Icon, Info, Link, Note, Param, ParamField, PropertiesTable, ResponseField, Step, Steps, Tab, Tabs, Tip, Tooltip, Warning, WarningBanner, ZoomableImage };
3
+ export { Accordion, AccordionGroup, Badge, Button, Callout, Card, CardGroup, Changelog, Check, CodeGroup, Column, Columns, Danger, Expandable, FileTree, FileTreeFile, FileTreeFolder, Frame, Icon, Info, Link, Mermaid, Note, Panel, PanelProvider, Param, PropertiesTable, Step, Steps, Tab, Tabs, Tile, Tiles, Tip, Tooltip, Tree, TreeFile, TreeFolder, Update, Warning, WarningBanner, ZoomableImage, mermaidSource, usePanelContent, useSetPanelContent };
@@ -1,4 +1,4 @@
1
- import { ar as BrowserRouter } from "./chunks/docs.js";
1
+ import { yr as BrowserRouter } from "./chunks/docs.js";
2
2
  import { h as BASE_URL, t as App } from "./chunks/App.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
 
@@ -1,10 +1,10 @@
1
- import { hr as ABSOLUTE_URL_REGEX, lr as Router, mr as parsePath, pr as createPath } from "./chunks/docs.js";
1
+ import { Cr as Router, Dr as createPath, Or as parsePath, kr as ABSOLUTE_URL_REGEX } from "./chunks/docs.js";
2
2
  import { a as docsSite, c as getSeo, d as standalonePages, f as getDocModule, g as toAbsoluteUrl, h as BASE_URL, i as docsHomeUrl, l as siteConfig, m as getScopeForPage, n as buildHead, o as getLocaleByPathname, p as getLastModified, r as renderHeadToString, s as getRedirects, t as App, u as siteName } from "./chunks/App.js";
3
3
  import * as React$1 from "react";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { renderToString } from "react-dom/server";
6
6
 
7
- //#region ../../node_modules/.pnpm/react-router@8.3.0_react-do_c27277bcf657dc321048682bd02ab633/node_modules/react-router/dist/production/lib/dom/server.js
7
+ //#region ../../node_modules/.pnpm/react-router@8.3.0_react-dom@19.2.8_react@19.2.8__react@19.2.8/node_modules/react-router/dist/production/lib/dom/server.js
8
8
  /**
9
9
  * react-router v8.3.0
10
10
  *
package/docs.schema.json CHANGED
@@ -807,7 +807,7 @@
807
807
  "properties": {
808
808
  "glob": {
809
809
  "allOf": [{ "$ref": "#/definitions/nonEmptyStringValue" }],
810
- "description": "Glob relative to contentDir. Matching Markdown and MDX files are expanded into page entries."
810
+ "description": "Glob relative to contentDir. Matching Markdown and MDX files are expanded into page entries; explicit page references take precedence."
811
811
  },
812
812
  "exclude": {
813
813
  "anyOf": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umami/shiso",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "description": "Open-source documentation framework for Markdown and MDX sites.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -83,6 +83,7 @@
83
83
  "github-slugger": "^2.0.0",
84
84
  "jiti": "^2.4.0",
85
85
  "lucide-react": "^1.28.0",
86
+ "mermaid": "^11.17.2",
86
87
  "react-router": "^8.3.0",
87
88
  "rehype-autolink-headings": "^7.1.0",
88
89
  "rehype-slug": "^6.0.0",
@@ -111,12 +111,47 @@ function pageCandidate(contentRoot, filePath) {
111
111
  return { filePath, relativeFile, fileSlug };
112
112
  }
113
113
 
114
+ function normalizePageReference(value) {
115
+ return String(value || '')
116
+ .trim()
117
+ .replace(/\\/g, '/')
118
+ .replace(/^\/+/, '')
119
+ .replace(/^docs\//, '')
120
+ .replace(/\.mdx?$/i, '')
121
+ .replace(/\/+$/, '');
122
+ }
123
+
124
+ /** Collects manual page references without crossing a version/language scope boundary. */
125
+ function collectExplicitPages(value, pages = new Set()) {
126
+ if (Array.isArray(value)) {
127
+ for (const item of value) {
128
+ if (typeof item === 'string') {
129
+ pages.add(normalizePageReference(item));
130
+ } else {
131
+ collectExplicitPages(item, pages);
132
+ }
133
+ }
134
+ return pages;
135
+ }
136
+
137
+ if (!value || typeof value !== 'object' || isGlobItem(value)) return pages;
138
+
139
+ if (typeof value.page === 'string') pages.add(normalizePageReference(value.page));
140
+ if (typeof value.root === 'string') pages.add(normalizePageReference(value.root));
141
+
142
+ for (const key of ['pages', 'groups', 'tabs', 'dropdowns']) {
143
+ if (Array.isArray(value[key])) collectExplicitPages(value[key], pages);
144
+ }
145
+
146
+ return pages;
147
+ }
148
+
114
149
  function matches(candidate, pattern) {
115
150
  const target = /\.mdx?$/i.test(pattern) ? candidate.relativeFile : candidate.fileSlug;
116
151
  return globRegex(pattern).test(target);
117
152
  }
118
153
 
119
- async function expandGlob(item, candidates, projectRoot) {
154
+ async function expandGlob(item, candidates, projectRoot, explicitPages) {
120
155
  assertGlobItem(item);
121
156
  const pattern = globPattern(item.glob);
122
157
  const exclusions = (item.exclude || []).map(globPattern);
@@ -129,8 +164,12 @@ async function expandGlob(item, candidates, projectRoot) {
129
164
  throw new Error(`Navigation glob "${pattern}" matched no Markdown or MDX files.`);
130
165
  }
131
166
 
167
+ // Manual entries own their page's placement and presentation, regardless of
168
+ // whether they appear before or after the glob that would otherwise include it.
169
+ const discovered = matched.filter(candidate => !explicitPages.has(candidate.fileSlug));
170
+
132
171
  const entries = await Promise.all(
133
- matched.map(async candidate => {
172
+ discovered.map(async candidate => {
134
173
  const source = await fs.readFile(candidate.filePath, 'utf8');
135
174
  const sourcePath = path.relative(projectRoot, candidate.filePath).replace(/\\/g, '/');
136
175
  const frontmatter = navigationFrontmatter(source, sourcePath);
@@ -181,8 +220,10 @@ export async function expandNavigationGlobs(config, { root, contentDir }) {
181
220
  );
182
221
  }
183
222
 
184
- async function expandObject(value) {
185
- if (Array.isArray(value)) return Promise.all(value.map(expandObject));
223
+ async function expandObject(value, explicitPages) {
224
+ if (Array.isArray(value)) {
225
+ return Promise.all(value.map(item => expandObject(item, explicitPages)));
226
+ }
186
227
  if (!value || typeof value !== 'object') return value;
187
228
 
188
229
  const entries = await Promise.all(
@@ -191,18 +232,46 @@ export async function expandNavigationGlobs(config, { root, contentDir }) {
191
232
  const expanded = [];
192
233
  for (const item of child) {
193
234
  if (isGlobItem(item)) {
194
- expanded.push(...(await expandGlob(item, candidates, projectRoot)));
235
+ expanded.push(...(await expandGlob(item, candidates, projectRoot, explicitPages)));
195
236
  } else {
196
- expanded.push(await expandObject(item));
237
+ expanded.push(await expandObject(item, explicitPages));
197
238
  }
198
239
  }
199
240
  return [key, expanded];
200
241
  }
201
- return [key, await expandObject(child)];
242
+ return [key, await expandObject(child, explicitPages)];
202
243
  }),
203
244
  );
204
245
  return Object.fromEntries(entries);
205
246
  }
206
247
 
207
- return { ...config, navigation: await expandObject(config.navigation) };
248
+ async function expandScope(scope) {
249
+ return expandObject(scope, collectExplicitPages(scope));
250
+ }
251
+
252
+ async function expandNavigation(navigation) {
253
+ if (Array.isArray(navigation.languages)) {
254
+ const languages = await Promise.all(
255
+ navigation.languages.map(async language => {
256
+ if (!Array.isArray(language.versions)) return expandScope(language);
257
+ return {
258
+ ...language,
259
+ versions: await Promise.all(language.versions.map(expandScope)),
260
+ };
261
+ }),
262
+ );
263
+ return { ...navigation, languages };
264
+ }
265
+
266
+ if (Array.isArray(navigation.versions)) {
267
+ return {
268
+ ...navigation,
269
+ versions: await Promise.all(navigation.versions.map(expandScope)),
270
+ };
271
+ }
272
+
273
+ return expandScope(navigation);
274
+ }
275
+
276
+ return { ...config, navigation: await expandNavigation(config.navigation) };
208
277
  }
@@ -588,13 +588,26 @@ export function hasOperationParameters(operation) {
588
588
  );
589
589
  }
590
590
 
591
+ function operationParameterSections(operation) {
592
+ return [
593
+ { location: 'header', heading: 'Headers', id: 'headers' },
594
+ { location: 'path', heading: 'Path parameters', id: 'path-parameters' },
595
+ { location: 'query', heading: 'Query parameters', id: 'query-parameters' },
596
+ { location: 'cookie', heading: 'Cookie parameters', id: 'cookie-parameters' },
597
+ ].filter(
598
+ ({ location }) =>
599
+ operation.parameters[location].length > 0 ||
600
+ (location === 'header' && operation.security.length > 0),
601
+ );
602
+ }
603
+
591
604
  /**
592
605
  * Anchor ids for the generated sections, in render order. Mirrors
593
606
  * operationSections in src/lib/openapi.ts (asserted by tests/openapi.test.mjs).
594
607
  */
595
608
  export function operationAnchors(operation) {
596
609
  return [
597
- ...(hasOperationParameters(operation) ? ['parameters'] : []),
610
+ ...operationParameterSections(operation).map(section => section.id),
598
611
  ...(operation.requestBody ? ['request-body'] : []),
599
612
  ...(operation.responses.length ? ['responses'] : []),
600
613
  ...(operation.samples.length ? ['code-samples'] : []),
@@ -617,13 +630,6 @@ export function normalizeOperationKey(value) {
617
630
 
618
631
  /** Search-index sections for an operation, matching operationAnchors ids. */
619
632
  export function operationSearchSections(operation) {
620
- const allParameters = [
621
- ...operation.parameters.path,
622
- ...operation.parameters.query,
623
- ...operation.parameters.header,
624
- ...operation.parameters.cookie,
625
- ];
626
-
627
633
  return [
628
634
  {
629
635
  heading: undefined,
@@ -632,11 +638,16 @@ export function operationSearchSections(operation) {
632
638
  .filter(Boolean)
633
639
  .join(' '),
634
640
  },
635
- {
636
- heading: 'Parameters',
637
- id: 'parameters',
638
- text: allParameters.map(schemaText).join(' '),
639
- },
641
+ ...operationParameterSections(operation).map(({ location, heading, id }) => ({
642
+ heading,
643
+ id,
644
+ text: [
645
+ ...operation.parameters[location].map(schemaText),
646
+ ...(location === 'header' && operation.security.length > 0
647
+ ? ['Authorization Authentication credentials']
648
+ : []),
649
+ ].join(' '),
650
+ })),
640
651
  {
641
652
  heading: 'Request body',
642
653
  id: 'request-body',
@@ -1,8 +1,10 @@
1
+ import type { ReactNode } from 'react';
1
2
  import { type ComponentProps, type CSSProperties, useRef, useState } from 'react';
2
3
  import { CheckIcon, Copy } from '@/components/icons';
3
4
  import { Button } from '@/components/ui/button';
4
5
  import { ScrollArea } from '@/components/ui/scroll-area';
5
6
  import { cn } from '@/lib/utils';
7
+ import { Mermaid, type MermaidPlacement } from './docs/Mermaid';
6
8
 
7
9
  /**
8
10
  * Renders a fenced code block. The `data-*` props are produced at build time by
@@ -15,6 +17,25 @@ export interface CodeBlockProps extends ComponentProps<'pre'> {
15
17
  'data-line-start'?: string;
16
18
  'data-line-count'?: string;
17
19
  'data-diff-markers'?: string;
20
+ 'data-placement'?: MermaidPlacement;
21
+ 'data-actions'?: string;
22
+ }
23
+
24
+ function reactChildrenToText(node: ReactNode): string {
25
+ if (typeof node === 'string' || typeof node === 'number') {
26
+ return String(node);
27
+ }
28
+ if (Array.isArray(node)) {
29
+ return node.map(reactChildrenToText).join('');
30
+ }
31
+ if (node && typeof node === 'object' && 'props' in (node as object)) {
32
+ const props = (node as { props?: { children?: ReactNode; value?: unknown } }).props;
33
+ if (typeof props?.value === 'string') {
34
+ return props.value;
35
+ }
36
+ return reactChildrenToText(props?.children);
37
+ }
38
+ return '';
18
39
  }
19
40
 
20
41
  /**
@@ -44,11 +65,26 @@ export function CodeBlock({ children, className, style, ...rest }: CodeBlockProp
44
65
  'data-language': language,
45
66
  'data-line-start': lineStart,
46
67
  'data-line-count': lineCount,
68
+ 'data-placement': placement,
69
+ 'data-actions': actions,
47
70
  ...preProps
48
71
  } = rest;
49
72
  const textInput = useRef<HTMLPreElement>(null);
50
73
  const [copied, setCopied] = useState(false);
51
74
 
75
+ // ```mermaid fences render as diagrams; the raw definition stays in the
76
+ // markup for search indexing and no-JS fallbacks.
77
+ if (language === 'mermaid') {
78
+ return (
79
+ <Mermaid
80
+ chart={reactChildrenToText(children).replace(/\n$/, '')}
81
+ title={title}
82
+ placement={placement}
83
+ actions={actions === undefined ? undefined : actions !== 'false'}
84
+ />
85
+ );
86
+ }
87
+
52
88
  const start = Number(lineStart) || 1;
53
89
  const lastLine = start + Math.max(Number(lineCount) || 1, 1) - 1;
54
90
  const gutter = `${String(lastLine).length}ch`;
@@ -1,6 +1,7 @@
1
1
  import { useEffect, useState } from 'react';
2
2
  import { useLocation, useNavigate } from 'react-router';
3
3
  import { DocContent } from '@/components/DocContent';
4
+ import { PanelProvider, usePanelContent } from '@/components/docs/panel-context';
4
5
  import { Footer } from '@/components/Footer';
5
6
  import { Menu } from '@/components/icons';
6
7
  import { LanguageSwitcher } from '@/components/LanguageSwitcher';
@@ -13,7 +14,7 @@ import { VersionSwitcher } from '@/components/VersionSwitcher';
13
14
  import { renderInlineMarkdown } from '@/lib/inline-markdown';
14
15
  import { getOperation, operationSections } from '@/lib/openapi';
15
16
  import { docsHomeUrl, getScopeByPathname } from '@/lib/site-config';
16
- import type { DocModule, NormalizedDocsPage, SiteModel } from '@/lib/types';
17
+ import type { DocModule, NormalizedDocsPage, SiteModel, TocEntry } from '@/lib/types';
17
18
 
18
19
  /**
19
20
  * 404 view driven by the `errors.404` config key. The standard defaults to
@@ -46,11 +47,46 @@ export interface DocsProps {
46
47
  }
47
48
 
48
49
  export function Docs({ page, doc, site }: DocsProps) {
50
+ if (!page || !doc) {
51
+ return (
52
+ <div className="flex min-h-full flex-col">
53
+ <div className="grow">
54
+ <NotFound site={site} />
55
+ </div>
56
+ <Footer footer={site.footer} />
57
+ </div>
58
+ );
59
+ }
60
+
61
+ // API reference pages append their generated section anchors to the TOC.
62
+ const operation = getOperation(doc.frontmatter?.openapi);
63
+ const toc = operation ? [...(doc.toc || []), ...operationSections(operation)] : doc.toc;
64
+
65
+ return (
66
+ <PanelProvider>
67
+ <DocsBody page={page} doc={doc} site={site} toc={toc} />
68
+ </PanelProvider>
69
+ );
70
+ }
71
+
72
+ function DocsBody({
73
+ page,
74
+ doc,
75
+ site,
76
+ toc,
77
+ }: {
78
+ page: NormalizedDocsPage;
79
+ doc: DocModule;
80
+ site: SiteModel;
81
+ toc: TocEntry[] | undefined;
82
+ }) {
49
83
  const { pathname } = useLocation();
50
84
  const [menuOpen, setMenuOpen] = useState(false);
51
85
  // Navigation follows the scope (version/language) that owns the current page.
52
86
  const scopeDocs = getScopeByPathname(pathname).docs;
53
87
  const { tabs, navigation } = scopeDocs;
88
+ // A <Panel> in the page replaces the table of contents in the right rail.
89
+ const panel = usePanelContent();
54
90
 
55
91
  // Close the mobile menu and start each newly loaded page at the top. Hash
56
92
  // links keep their native section-scrolling behavior.
@@ -62,21 +98,6 @@ export function Docs({ page, doc, site }: DocsProps) {
62
98
  }
63
99
  }, [pathname]);
64
100
 
65
- if (!page || !doc) {
66
- return (
67
- <div className="flex min-h-full flex-col">
68
- <div className="grow">
69
- <NotFound site={site} />
70
- </div>
71
- <Footer footer={site.footer} />
72
- </div>
73
- );
74
- }
75
-
76
- // API reference pages append their generated section anchors to the TOC.
77
- const operation = getOperation(doc.frontmatter?.openapi);
78
- const toc = operation ? [...(doc.toc || []), ...operationSections(operation)] : doc.toc;
79
-
80
101
  return (
81
102
  <div className="flex min-h-full flex-col gap-6 lg:gap-0">
82
103
  <Sheet open={menuOpen} onOpenChange={setMenuOpen}>
@@ -128,11 +149,13 @@ export function Docs({ page, doc, site }: DocsProps) {
128
149
  <div className="flex grow items-start gap-12">
129
150
  <DocContent page={page} doc={doc} site={site} />
130
151
  <div className="hidden min-w-0 max-w-60 basis-60 self-start lg:sticky lg:top-[calc(var(--header-height)+1.5rem)] lg:block lg:shrink-0">
131
- <PageLinks
132
- items={toc}
133
- title={site.labels.tableOfContents}
134
- navigationLabel={site.labels.tableOfContentsNavigation}
135
- />
152
+ {panel ?? (
153
+ <PageLinks
154
+ items={toc}
155
+ title={site.labels.tableOfContents}
156
+ navigationLabel={site.labels.tableOfContentsNavigation}
157
+ />
158
+ )}
136
159
  </div>
137
160
  </div>
138
161
  <Footer footer={site.footer} className="lg:mr-72" />
@@ -2,15 +2,10 @@ import { CodeBlock } from '@/components/CodeBlock';
2
2
  import { Badge } from '@/components/docs/Badge';
3
3
  import { CodeGroup } from '@/components/docs/CodeGroup';
4
4
  import { Expandable } from '@/components/docs/Expandable';
5
- import { ParamField } from '@/components/docs/ParamField';
6
- import { ResponseField } from '@/components/docs/ResponseField';
7
- import { operationSections, statusColor } from '@/lib/openapi';
5
+ import { PropertiesTable } from '@/components/docs/PropertiesTable';
6
+ import { operationParameterSections, operationSections, statusColor } from '@/lib/openapi';
8
7
  import type { NormalizedOperation, SchemaNode } from '@/lib/types';
9
8
 
10
- type ParamLocation = 'path' | 'query' | 'header' | 'cookie';
11
-
12
- const PARAM_LOCATIONS: ParamLocation[] = ['path', 'query', 'header', 'cookie'];
13
-
14
9
  function FieldChildren({ node }: { node: SchemaNode }) {
15
10
  return (
16
11
  <>
@@ -28,42 +23,35 @@ function FieldChildren({ node }: { node: SchemaNode }) {
28
23
  )}
29
24
  {node.children && node.children.length > 0 && (
30
25
  <Expandable title="properties">
31
- {node.children.map(child => (
32
- <SchemaField key={child.name || child.type} node={child} />
33
- ))}
26
+ <SchemaTable nodes={node.children} />
34
27
  </Expandable>
35
28
  )}
36
29
  </>
37
30
  );
38
31
  }
39
32
 
40
- function SchemaField({ node }: { node: SchemaNode }) {
33
+ function SchemaTable({ nodes }: { nodes: SchemaNode[] }) {
41
34
  return (
42
- <ResponseField
43
- name={node.name || node.type}
44
- type={node.name ? node.type : undefined}
45
- required={node.required}
46
- deprecated={node.deprecated}
47
- default={node.default}
48
- >
49
- <FieldChildren node={node} />
50
- </ResponseField>
35
+ <PropertiesTable>
36
+ {nodes.map(node => (
37
+ <PropertiesTable.Row
38
+ key={node.name || node.type}
39
+ name={node.name || node.type}
40
+ type={node.type}
41
+ required={node.required}
42
+ deprecated={node.deprecated}
43
+ default={node.default}
44
+ >
45
+ <FieldChildren node={node} />
46
+ </PropertiesTable.Row>
47
+ ))}
48
+ </PropertiesTable>
51
49
  );
52
50
  }
53
51
 
54
52
  /** Renders a schema tree: a root object's properties, or the node itself. */
55
53
  function SchemaFields({ node }: { node: SchemaNode }) {
56
- if (!node.name && node.children?.length) {
57
- return (
58
- <>
59
- {node.children.map(child => (
60
- <SchemaField key={child.name || child.type} node={child} />
61
- ))}
62
- </>
63
- );
64
- }
65
-
66
- return <SchemaField node={node} />;
54
+ return <SchemaTable nodes={!node.name && node.children?.length ? node.children : [node]} />;
67
55
  }
68
56
 
69
57
  function HighlightedCode({
@@ -107,34 +95,33 @@ export interface OpenApiOperationProps {
107
95
  /** The generated reference for one API operation, rendered under the page body. */
108
96
  export function OpenApiOperation({ operation }: OpenApiOperationProps) {
109
97
  const sections = new Map(operationSections(operation).map(entry => [entry.name, entry.id]));
110
- const showParameters = sections.has('Parameters');
111
98
 
112
99
  return (
113
100
  <div className="docs-markdown">
114
- {showParameters && (
115
- <section>
116
- <h2 id={sections.get('Parameters')}>Parameters</h2>
117
- {operation.security.length > 0 && (
118
- <ParamField header="Authorization" type="string" required>
119
- Authentication credentials, e.g. <code>Bearer &lt;token&gt;</code> (
120
- {operation.security.join(', ')}).
121
- </ParamField>
122
- )}
123
- {PARAM_LOCATIONS.map(location =>
124
- operation.parameters[location].map(parameter => (
125
- <ParamField
126
- key={`${location}-${parameter.name}`}
127
- {...{ [location]: parameter.name }}
101
+ {operationParameterSections(operation).map(({ location, name }) => (
102
+ <section key={location}>
103
+ <h2 id={sections.get(name)}>{name}</h2>
104
+ <PropertiesTable>
105
+ {location === 'header' && operation.security.length > 0 && (
106
+ <PropertiesTable.Row name="Authorization" type="string" required>
107
+ Authentication credentials, e.g. <code>Bearer &lt;token&gt;</code>.
108
+ </PropertiesTable.Row>
109
+ )}
110
+ {operation.parameters[location].map(parameter => (
111
+ <PropertiesTable.Row
112
+ key={parameter.name}
113
+ name={parameter.name || parameter.type}
128
114
  type={parameter.type}
129
115
  required={parameter.required}
116
+ deprecated={parameter.deprecated}
117
+ default={parameter.default}
130
118
  >
131
- <FieldChildren node={{ ...parameter, name: undefined, description: undefined }} />
132
- {parameter.description}
133
- </ParamField>
134
- )),
135
- )}
119
+ <FieldChildren node={parameter} />
120
+ </PropertiesTable.Row>
121
+ ))}
122
+ </PropertiesTable>
136
123
  </section>
137
- )}
124
+ ))}
138
125
  {operation.requestBody && (
139
126
  <section>
140
127
  <h2 id={sections.get('Request body')}>Request body</h2>
@@ -275,12 +275,17 @@ function NavNodes({
275
275
  )}
276
276
  >
277
277
  {resolveIcon(icon)}
278
- {label}
279
278
  {method ? (
280
- <MethodBadge color={methodColor(method)} size="xs" className="ml-auto font-mono">
279
+ <MethodBadge
280
+ color={methodColor(method)}
281
+ size="xs"
282
+ className="w-10 shrink-0 justify-center whitespace-nowrap font-mono"
283
+ >
281
284
  {method === 'DELETE' ? 'DEL' : method}
282
285
  </MethodBadge>
283
- ) : tag ? (
286
+ ) : null}
287
+ {label}
288
+ {!method && tag ? (
284
289
  <Badge
285
290
  variant="secondary"
286
291
  className="ml-auto h-auto rounded-sm px-[0.35rem] py-[0.05rem] text-[0.7rem] text-muted-foreground uppercase"