@umami/shiso 1.11.0 → 1.13.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 (148) hide show
  1. package/bin/shiso.mjs +20 -1
  2. package/dist/chunks/App.js +412 -51
  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 +989 -93
  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 +2 -2
  100. package/dist/entry-server.js +31 -3
  101. package/docs.schema.json +90 -0
  102. package/mdx.config.ts +13 -97
  103. package/package.json +6 -4
  104. package/scripts/build-runtime.mjs +1 -0
  105. package/scripts/check-content.mjs +358 -0
  106. package/scripts/expand-navigation-globs.mjs +277 -0
  107. package/scripts/expand-openapi-navigation.mjs +94 -0
  108. package/scripts/generate-openapi.mjs +117 -0
  109. package/scripts/generate-search-index.mjs +31 -1
  110. package/scripts/lib/openapi.mjs +653 -0
  111. package/scripts/load-docs-config.mjs +52 -6
  112. package/scripts/load-shiso-config.mjs +45 -3
  113. package/scripts/prerender.mjs +83 -3
  114. package/scripts/vite-docs-config.mjs +27 -6
  115. package/src/App.tsx +0 -1
  116. package/src/components/CodeBlock.tsx +108 -8
  117. package/src/components/DocContent.tsx +18 -0
  118. package/src/components/Docs.tsx +45 -17
  119. package/src/components/OpenApiOperation.tsx +205 -0
  120. package/src/components/SideNav.tsx +13 -2
  121. package/src/components/docs/Changelog.tsx +163 -0
  122. package/src/components/docs/CodeGroup.tsx +6 -2
  123. package/src/components/docs/Mermaid.tsx +164 -0
  124. package/src/components/docs/Panel.tsx +23 -0
  125. package/src/components/docs/ParamField.tsx +17 -5
  126. package/src/components/docs/PropertiesTable.tsx +2 -2
  127. package/src/components/docs/ResponseField.tsx +17 -5
  128. package/src/components/docs/Tiles.tsx +59 -0
  129. package/src/components/docs/Tree.tsx +359 -0
  130. package/src/components/docs/Update.tsx +72 -0
  131. package/src/components/docs/index.ts +7 -0
  132. package/src/components/docs/panel-context.tsx +25 -0
  133. package/src/components/docs/styles.ts +49 -0
  134. package/src/components/icons/index.ts +4 -0
  135. package/src/entry-server.tsx +50 -0
  136. package/src/lib/code-blocks.ts +18 -0
  137. package/src/lib/code-meta.ts +87 -0
  138. package/src/lib/docs-config.ts +4 -1
  139. package/src/lib/openapi.generated.ts +4 -0
  140. package/src/lib/openapi.ts +63 -0
  141. package/src/lib/rehype-shiki.ts +213 -0
  142. package/src/lib/site-model.ts +2 -0
  143. package/src/lib/types.ts +116 -2
  144. package/src/styles/global.css +63 -73
  145. package/types/components.d.ts +79 -0
  146. package/types/config.d.ts +11 -4
  147. package/vite.config.ts +49 -3
  148. package/CHANGELOG.md +0 -171
@@ -0,0 +1,205 @@
1
+ import { CodeBlock } from '@/components/CodeBlock';
2
+ import { Badge } from '@/components/docs/Badge';
3
+ import { CodeGroup } from '@/components/docs/CodeGroup';
4
+ import { Expandable } from '@/components/docs/Expandable';
5
+ import { ParamField } from '@/components/docs/ParamField';
6
+ import { ResponseField } from '@/components/docs/ResponseField';
7
+ import { styles } from '@/components/docs/styles';
8
+ import { operationSections, statusColor } from '@/lib/openapi';
9
+ import type { NormalizedOperation, SchemaNode } from '@/lib/types';
10
+
11
+ type ParamLocation = 'path' | 'query' | 'header' | 'cookie';
12
+
13
+ const PARAM_LOCATIONS: ParamLocation[] = ['path', 'query', 'header', 'cookie'];
14
+
15
+ function FieldChildren({ node }: { node: SchemaNode }) {
16
+ return (
17
+ <>
18
+ {node.description}
19
+ {node.enum && node.enum.length > 0 && (
20
+ <div className="mt-1">
21
+ Options:{' '}
22
+ {node.enum.map((value, index) => (
23
+ <span key={value}>
24
+ {index > 0 && ', '}
25
+ <code>{value}</code>
26
+ </span>
27
+ ))}
28
+ </div>
29
+ )}
30
+ {node.children && node.children.length > 0 && (
31
+ <Expandable title="properties">
32
+ {node.children.map(child => (
33
+ <SchemaField key={child.name || child.type} node={child} />
34
+ ))}
35
+ </Expandable>
36
+ )}
37
+ </>
38
+ );
39
+ }
40
+
41
+ function SchemaField({ node }: { node: SchemaNode }) {
42
+ const hasDetails = Boolean(node.description || node.enum?.length || node.children?.length);
43
+
44
+ return (
45
+ <ResponseField
46
+ name={node.name || node.type}
47
+ type={node.name ? node.type : undefined}
48
+ required={node.required}
49
+ deprecated={node.deprecated}
50
+ default={node.default}
51
+ >
52
+ {hasDetails ? <FieldChildren node={node} /> : null}
53
+ </ResponseField>
54
+ );
55
+ }
56
+
57
+ /** Renders a schema tree: a root object's properties, or the node itself. */
58
+ function SchemaFields({ node }: { node: SchemaNode }) {
59
+ if (!node.name && node.children?.length) {
60
+ return (
61
+ <>
62
+ {node.children.map(child => (
63
+ <SchemaField key={child.name || child.type} node={child} />
64
+ ))}
65
+ </>
66
+ );
67
+ }
68
+
69
+ return <SchemaField node={node} />;
70
+ }
71
+
72
+ function HighlightedCode({
73
+ language,
74
+ title,
75
+ html,
76
+ source,
77
+ lineCount,
78
+ }: {
79
+ language: string;
80
+ title?: string;
81
+ html?: string;
82
+ source: string;
83
+ lineCount?: number;
84
+ }) {
85
+ return (
86
+ <CodeBlock
87
+ data-language={language}
88
+ data-title={title}
89
+ data-line-count={lineCount ? String(lineCount) : undefined}
90
+ >
91
+ {html ? (
92
+ // Highlighted at build time by the openapi generator; same markup as
93
+ // the rehype-shiki pipeline produces for fenced code blocks.
94
+ <code
95
+ className={`language-${language}`}
96
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: build-time generated markup from the project's own OpenAPI spec.
97
+ dangerouslySetInnerHTML={{ __html: html }}
98
+ />
99
+ ) : (
100
+ <code className={`language-${language}`}>{source}</code>
101
+ )}
102
+ </CodeBlock>
103
+ );
104
+ }
105
+
106
+ export interface OpenApiOperationProps {
107
+ operation: NormalizedOperation;
108
+ }
109
+
110
+ /** The generated reference for one API operation, rendered under the page body. */
111
+ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
112
+ const sections = new Map(operationSections(operation).map(entry => [entry.name, entry.id]));
113
+ const showParameters = sections.has('Parameters');
114
+
115
+ return (
116
+ <div className="docs-markdown">
117
+ {showParameters && (
118
+ <section>
119
+ <h2 id={sections.get('Parameters')}>Parameters</h2>
120
+ <div className={styles.fieldGroup}>
121
+ {operation.security.length > 0 && (
122
+ <ParamField header="Authorization" type="string" required>
123
+ Authentication credentials, e.g. <code>Bearer &lt;token&gt;</code>.
124
+ </ParamField>
125
+ )}
126
+ {PARAM_LOCATIONS.map(location =>
127
+ operation.parameters[location].map(parameter => (
128
+ <ParamField
129
+ key={`${location}-${parameter.name}`}
130
+ {...{ [location]: parameter.name }}
131
+ type={parameter.type}
132
+ required={parameter.required}
133
+ >
134
+ <FieldChildren node={{ ...parameter, name: undefined, description: undefined }} />
135
+ {parameter.description}
136
+ </ParamField>
137
+ )),
138
+ )}
139
+ </div>
140
+ </section>
141
+ )}
142
+ {operation.requestBody && (
143
+ <section>
144
+ <h2 id={sections.get('Request body')}>Request body</h2>
145
+ <SchemaFields node={operation.requestBody.schema} />
146
+ {operation.requestBody.example && (
147
+ <HighlightedCode
148
+ language="json"
149
+ title="Example request"
150
+ html={operation.requestBody.exampleHtml}
151
+ source={operation.requestBody.example}
152
+ />
153
+ )}
154
+ </section>
155
+ )}
156
+ {operation.responses.length > 0 && (
157
+ <section>
158
+ <h2 id={sections.get('Responses')}>Responses</h2>
159
+ {operation.responses.map(response => (
160
+ <div key={response.status} className="mt-6 first:mt-0">
161
+ <div className="flex items-center gap-2">
162
+ <Badge color={statusColor(response.status)} size="sm">
163
+ {response.status}
164
+ </Badge>
165
+ {response.description && (
166
+ <span className="text-muted-foreground text-sm">{response.description}</span>
167
+ )}
168
+ </div>
169
+ {response.schema && (
170
+ <div className={styles.fieldGroup}>
171
+ <SchemaFields node={response.schema} />
172
+ </div>
173
+ )}
174
+ {response.example && (
175
+ <HighlightedCode
176
+ language="json"
177
+ title={`${response.status} example`}
178
+ html={response.exampleHtml}
179
+ source={response.example}
180
+ />
181
+ )}
182
+ </div>
183
+ ))}
184
+ </section>
185
+ )}
186
+ {operation.samples.length > 0 && (
187
+ <section>
188
+ <h2 id={sections.get('Code samples')}>Code samples</h2>
189
+ <CodeGroup>
190
+ {operation.samples.map(sample => (
191
+ <HighlightedCode
192
+ key={sample.language}
193
+ language={sample.language}
194
+ title={sample.label}
195
+ html={sample.html}
196
+ source={sample.source}
197
+ lineCount={sample.lineCount}
198
+ />
199
+ ))}
200
+ </CodeGroup>
201
+ </section>
202
+ )}
203
+ </div>
204
+ );
205
+ }
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode, useEffect, useState } from 'react';
2
2
  import { Link, useLocation, useNavigate } from 'react-router';
3
+ import { Badge as MethodBadge } from '@/components/docs/Badge';
3
4
  import { resolveIcon } from '@/components/docs/utils';
4
5
  import { ChevronRight, ExternalLink } from '@/components/icons';
5
6
  import { Badge } from '@/components/ui/badge';
@@ -7,6 +8,7 @@ import { Button } from '@/components/ui/button';
7
8
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
8
9
  import { ScrollArea } from '@/components/ui/scroll-area';
9
10
  import { flattenNav, isNodeHidden } from '@/lib/docs-config';
11
+ import { methodColor } from '@/lib/openapi';
10
12
  import type { DocsTab, NavGroupNode, NavNode } from '@/lib/types';
11
13
  import { cn } from '@/lib/utils';
12
14
 
@@ -256,7 +258,7 @@ function NavNodes({
256
258
  }
257
259
 
258
260
  if (node.kind === 'page') {
259
- const { url, label, icon, tag } = node.page;
261
+ const { url, label, icon, tag, method } = node.page;
260
262
  const isSelected = url === pathname;
261
263
 
262
264
  rendered.push(
@@ -273,8 +275,17 @@ function NavNodes({
273
275
  )}
274
276
  >
275
277
  {resolveIcon(icon)}
278
+ {method ? (
279
+ <MethodBadge
280
+ color={methodColor(method)}
281
+ size="xs"
282
+ className="w-10 shrink-0 justify-center whitespace-nowrap font-mono"
283
+ >
284
+ {method === 'DELETE' ? 'DEL' : method}
285
+ </MethodBadge>
286
+ ) : null}
276
287
  {label}
277
- {tag ? (
288
+ {!method && tag ? (
278
289
  <Badge
279
290
  variant="secondary"
280
291
  className="ml-auto h-auto rounded-sm px-[0.35rem] py-[0.05rem] text-[0.7rem] text-muted-foreground uppercase"
@@ -0,0 +1,163 @@
1
+ import { Children, Fragment, isValidElement, type ReactElement, useMemo } from 'react';
2
+ import { useSearchParams } from 'react-router';
3
+ import { Badge } from './Badge';
4
+ import { styles } from './styles';
5
+ import { Update } from './Update';
6
+
7
+ export interface ChangelogProps {
8
+ children?: React.ReactNode;
9
+ }
10
+
11
+ interface UpdateChildInfo {
12
+ key: string;
13
+ tags: string[];
14
+ }
15
+
16
+ /** Flattens fragments so <Update> entries are found however MDX nests them. */
17
+ function flatElements(children: React.ReactNode): ReactElement<{ tags?: unknown }>[] {
18
+ const flat: ReactElement<{ tags?: unknown }>[] = [];
19
+
20
+ Children.forEach(children, child => {
21
+ if (!isValidElement<{ tags?: unknown }>(child)) {
22
+ return;
23
+ }
24
+ if (child.type === Fragment) {
25
+ flat.push(...flatElements(child.props.children));
26
+ } else {
27
+ flat.push(child);
28
+ }
29
+ });
30
+
31
+ return flat;
32
+ }
33
+
34
+ /** Reads `tags` off <Update> children without rendering them. */
35
+ function updateInfo(children: React.ReactNode): Map<string, UpdateChildInfo> {
36
+ const info = new Map<string, UpdateChildInfo>();
37
+
38
+ flatElements(children).forEach((child, index) => {
39
+ if (child.type !== Update) {
40
+ return;
41
+ }
42
+
43
+ const key = String(child.key ?? `update-${index}`);
44
+ const raw = child.props.tags;
45
+ const tags = Array.isArray(raw)
46
+ ? [...new Set(raw.filter((tag): tag is string => typeof tag === 'string' && tag.trim()))].map(
47
+ tag => tag.trim(),
48
+ )
49
+ : [];
50
+ info.set(key, { key, tags });
51
+ });
52
+
53
+ return info;
54
+ }
55
+
56
+ /**
57
+ * Groups <Update> entries with Mintlify-style tag filtering.
58
+ * Selected tags sync to `?tags=a,b` so filtered views are shareable.
59
+ * Pages without any tagged updates render as a plain list.
60
+ */
61
+ export function Changelog({ children }: ChangelogProps) {
62
+ const [searchParams, setSearchParams] = useSearchParams();
63
+
64
+ const allTags = useMemo(() => {
65
+ const seen = new Map<string, string>();
66
+ for (const { tags } of updateInfo(children).values()) {
67
+ for (const tag of tags) {
68
+ const key = tag.toLowerCase();
69
+ if (!seen.has(key)) {
70
+ seen.set(key, tag);
71
+ }
72
+ }
73
+ }
74
+ return [...seen.values()];
75
+ }, [children]);
76
+
77
+ const selected = useMemo(() => {
78
+ const param = searchParams.get('tags');
79
+ if (!param) {
80
+ return [];
81
+ }
82
+ const wanted = new Set(
83
+ param
84
+ .split(',')
85
+ .map(tag => tag.trim().toLowerCase())
86
+ .filter(Boolean),
87
+ );
88
+ return allTags.filter(tag => wanted.has(tag.toLowerCase()));
89
+ }, [searchParams, allTags]);
90
+
91
+ const infoByKey = useMemo(() => updateInfo(children), [children]);
92
+
93
+ if (allTags.length === 0) {
94
+ return <div className={styles.changelog}>{children}</div>;
95
+ }
96
+
97
+ const toggle = (tag: string) => {
98
+ const lower = tag.toLowerCase();
99
+ const next = selected.some(item => item.toLowerCase() === lower)
100
+ ? selected.filter(item => item.toLowerCase() !== lower)
101
+ : [...selected, tag];
102
+
103
+ setSearchParams(next.length ? { tags: next.join(',') } : {}, { preventScrollReset: true });
104
+ };
105
+
106
+ const clear = () => {
107
+ setSearchParams({}, { preventScrollReset: true });
108
+ };
109
+
110
+ const selectedSet = new Set(selected.map(tag => tag.toLowerCase()));
111
+
112
+ const filtered = flatElements(children).map((child, index) => {
113
+ if (child.type !== Update) {
114
+ return child;
115
+ }
116
+ const key = String(child.key ?? `update-${index}`);
117
+ const tags = infoByKey.get(key)?.tags ?? [];
118
+ if (selectedSet.size === 0) {
119
+ return child;
120
+ }
121
+ const lower = tags.map(tag => tag.toLowerCase());
122
+ const matches = [...selectedSet].every(tag => lower.includes(tag));
123
+ return matches ? child : null;
124
+ });
125
+
126
+ const visibleUpdates = filtered.filter(
127
+ child => child && (child as React.ReactElement).type === Update,
128
+ ).length;
129
+
130
+ return (
131
+ <div className={styles.changelog}>
132
+ {/* biome-ignore lint/a11y/useSemanticElements: toggle-button group labelled for the filter */}
133
+ <div className={styles.changelogFilters} role="group" aria-label="Filter updates by tag">
134
+ {allTags.map(tag => {
135
+ const active = selectedSet.has(tag.toLowerCase());
136
+ return (
137
+ <button
138
+ key={tag}
139
+ type="button"
140
+ onClick={() => toggle(tag)}
141
+ aria-pressed={active}
142
+ className={styles.changelogFilterButton}
143
+ data-active={active ? '' : undefined}
144
+ >
145
+ <Badge size="sm" color={active ? 'primary' : undefined}>
146
+ {tag}
147
+ </Badge>
148
+ </button>
149
+ );
150
+ })}
151
+ {selected.length > 0 ? (
152
+ <button type="button" onClick={clear} className={styles.changelogClear}>
153
+ Clear
154
+ </button>
155
+ ) : null}
156
+ </div>
157
+ {visibleUpdates === 0 ? (
158
+ <p className={styles.changelogEmpty}>No updates match the selected tags.</p>
159
+ ) : null}
160
+ {filtered}
161
+ </div>
162
+ );
163
+ }
@@ -39,14 +39,18 @@ export function CodeGroup({ children }: CodeGroupProps) {
39
39
  }
40
40
 
41
41
  if (blocks.length === 1) {
42
- return <div className={styles.tabs}>{blocks[0].content}</div>;
42
+ return (
43
+ <div className={`${styles.tabs} [&_[data-slot=code-block-header]]:hidden`}>
44
+ {blocks[0].content}
45
+ </div>
46
+ );
43
47
  }
44
48
 
45
49
  return (
46
50
  <TabsPrimitive
47
51
  value={selected}
48
52
  onValueChange={setSelectedKey}
49
- className="my-4 gap-0 overflow-hidden rounded-lg bg-card [&_[data-slot=code-block]]:my-0 [&_[data-slot=code-block]]:rounded-none [&_[data-slot=code-block]]:border-0 [&_[data-slot=code-block]]:bg-transparent"
53
+ className="my-4 gap-0 overflow-hidden rounded-lg bg-card [&_[data-slot=code-block]]:my-0 [&_[data-slot=code-block]]:rounded-none [&_[data-slot=code-block]]:border-0 [&_[data-slot=code-block]]:bg-transparent [&_[data-slot=code-block-header]]:hidden"
50
54
  >
51
55
  <TabsList
52
56
  variant="line"
@@ -0,0 +1,164 @@
1
+ import { type ReactNode, useEffect, useId, useRef, useState } from 'react';
2
+ import { styles } from './styles';
3
+
4
+ export type MermaidPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
5
+
6
+ export interface MermaidProps {
7
+ /** Diagram definition. Falls back to string children when omitted. */
8
+ chart?: string;
9
+ title?: ReactNode;
10
+ /**
11
+ * Show zoom/pan/reset controls. Defaults to true when the diagram exceeds
12
+ * 120px in height (Mintlify behavior). Pass false to hide.
13
+ */
14
+ actions?: boolean;
15
+ /** Corner for the controls. Defaults to "bottom-right". */
16
+ placement?: MermaidPlacement;
17
+ children?: ReactNode;
18
+ }
19
+
20
+ function childrenToText(node: ReactNode): string {
21
+ if (typeof node === 'string' || typeof node === 'number') {
22
+ return String(node);
23
+ }
24
+ if (Array.isArray(node)) {
25
+ return node.map(childrenToText).join('');
26
+ }
27
+ if (node && typeof node === 'object' && 'props' in (node as object)) {
28
+ const props = (node as { props?: { children?: ReactNode } }).props;
29
+ return childrenToText(props?.children);
30
+ }
31
+ return '';
32
+ }
33
+
34
+ /** Extracts raw diagram source from MDX children (string or <code> element). */
35
+ export function mermaidSource(chart: string | undefined, children: ReactNode): string {
36
+ const raw = typeof chart === 'string' && chart.trim() ? chart : childrenToText(children);
37
+ return raw.replace(/^\n+|\n+$/g, '').replace(/\\n$/, '');
38
+ }
39
+
40
+ function isDarkMode(): boolean {
41
+ if (typeof document === 'undefined') {
42
+ return false;
43
+ }
44
+ return document.documentElement.classList.contains('dark');
45
+ }
46
+
47
+ const PLACEMENT_CLASS: Record<MermaidPlacement, string> = {
48
+ 'top-left': 'top-2 left-2',
49
+ 'top-right': 'top-2 right-2',
50
+ 'bottom-left': 'bottom-2 left-2',
51
+ 'bottom-right': 'bottom-2 right-2',
52
+ };
53
+
54
+ /**
55
+ * Renders a Mermaid diagram. Server/prerender output is the raw definition in
56
+ * a <pre> so search indexing and no-JS still see the content; the client
57
+ * replaces it with SVG via a lazy `mermaid` import (kept out of the SSR bundle).
58
+ */
59
+ export function Mermaid({
60
+ chart,
61
+ title,
62
+ actions,
63
+ placement = 'bottom-right',
64
+ children,
65
+ }: MermaidProps) {
66
+ const source = mermaidSource(chart, children);
67
+ const containerRef = useRef<HTMLDivElement>(null);
68
+ const svgHostRef = useRef<HTMLDivElement>(null);
69
+ const diagramId = useId().replace(/[^a-zA-Z0-9]/g, '');
70
+ const [svg, setSvg] = useState<string | null>(null);
71
+ const [error, setError] = useState(false);
72
+ const [zoom, setZoom] = useState(1);
73
+ const [dark, setDark] = useState(false);
74
+
75
+ useEffect(() => {
76
+ setDark(isDarkMode());
77
+ const observer = new MutationObserver(() => setDark(isDarkMode()));
78
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
79
+ return () => observer.disconnect();
80
+ }, []);
81
+
82
+ useEffect(() => {
83
+ if (!source.trim() || typeof window === 'undefined') {
84
+ return;
85
+ }
86
+
87
+ let cancelled = false;
88
+ setError(false);
89
+ setSvg(null);
90
+ (async () => {
91
+ try {
92
+ const { default: mermaid } = await import('mermaid');
93
+ if (cancelled) {
94
+ return;
95
+ }
96
+ mermaid.initialize({
97
+ startOnLoad: false,
98
+ theme: dark ? 'dark' : 'neutral',
99
+ securityLevel: 'strict',
100
+ });
101
+ const { svg: rendered } = await mermaid.render(`shiso-mermaid-${diagramId}`, source);
102
+ if (!cancelled) {
103
+ setSvg(rendered);
104
+ }
105
+ } catch {
106
+ if (!cancelled) {
107
+ setError(true);
108
+ }
109
+ }
110
+ })();
111
+
112
+ return () => {
113
+ cancelled = true;
114
+ };
115
+ }, [source, diagramId, dark]);
116
+
117
+ // Pan is native scroll; zoom controls scale the SVG host.
118
+ const showControls = actions ?? true;
119
+
120
+ return (
121
+ <figure className={styles.mermaid} data-dark={dark ? '' : undefined}>
122
+ {title ? <figcaption className={styles.mermaidTitle}>{title}</figcaption> : null}
123
+ <div ref={containerRef} className={styles.mermaidViewport}>
124
+ {svg && !error ? (
125
+ <div
126
+ ref={svgHostRef}
127
+ className={styles.mermaidSvg}
128
+ style={{ transform: `scale(${zoom})` }}
129
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: mermaid generates this SVG from the page's own diagram source.
130
+ dangerouslySetInnerHTML={{ __html: svg }}
131
+ role="img"
132
+ aria-label={title ? String(title) : 'Diagram'}
133
+ />
134
+ ) : (
135
+ <pre className={styles.mermaidFallback} title="Diagram source">
136
+ {source}
137
+ </pre>
138
+ )}
139
+ {error ? <p className={styles.mermaidError}>Could not render this diagram.</p> : null}
140
+ {showControls && svg && !error ? (
141
+ <div className={`${styles.mermaidControls} ${PLACEMENT_CLASS[placement]}`}>
142
+ <button
143
+ type="button"
144
+ aria-label="Zoom out"
145
+ onClick={() => setZoom(z => Math.max(0.5, +(z - 0.25).toFixed(2)))}
146
+ >
147
+ −
148
+ </button>
149
+ <button type="button" aria-label="Reset view" onClick={() => setZoom(1)}>
150
+ ⟳
151
+ </button>
152
+ <button
153
+ type="button"
154
+ aria-label="Zoom in"
155
+ onClick={() => setZoom(z => Math.min(2.5, +(z + 0.25).toFixed(2)))}
156
+ >
157
+ +
158
+ </button>
159
+ </div>
160
+ ) : null}
161
+ </div>
162
+ </figure>
163
+ );
164
+ }
@@ -0,0 +1,23 @@
1
+ import { type ReactNode, useEffect } from 'react';
2
+ import { useSetPanelContent } from './panel-context';
3
+ import { styles } from './styles';
4
+
5
+ export interface PanelProps {
6
+ children?: ReactNode;
7
+ }
8
+
9
+ /**
10
+ * Pins supplementary content to the right rail (replacing the table of
11
+ * contents on desktop). On small screens the content renders inline where it
12
+ * is written. Only one Panel per page is supported; the last one wins.
13
+ */
14
+ export function Panel({ children }: PanelProps) {
15
+ const setContent = useSetPanelContent();
16
+
17
+ useEffect(() => {
18
+ setContent(children ? <div className={styles.panel}>{children}</div> : null);
19
+ return () => setContent(null);
20
+ }, [children, setContent]);
21
+
22
+ return <div className={styles.panelInline}>{children}</div>;
23
+ }
@@ -28,14 +28,26 @@ export function ParamField({
28
28
  const location = query ? 'query' : path ? 'path' : header ? 'header' : body ? 'body' : undefined;
29
29
 
30
30
  return (
31
- <div className={styles.paramField}>
31
+ <div className={styles.paramField} data-slot="field-group-item">
32
32
  <div className={styles.fieldHeader}>
33
33
  <span className={styles.fieldName}>{label}</span>
34
- {location ? <Badge>{location}</Badge> : null}
35
- {type ? <span className={styles.fieldType}>{decodeHtmlEntities(type)}</span> : null}
36
- {required ? <Badge tone="primary">required</Badge> : null}
34
+ {location ? <Badge size="sm">{location}</Badge> : null}
35
+ {type ? (
36
+ <span className={styles.fieldType} data-slot="field-group-type">
37
+ {decodeHtmlEntities(type)}
38
+ </span>
39
+ ) : null}
40
+ {required ? (
41
+ <Badge size="sm" tone="primary">
42
+ required
43
+ </Badge>
44
+ ) : null}
37
45
  </div>
38
- {children ? <div className={styles.fieldBody}>{children}</div> : null}
46
+ {children ? (
47
+ <div className={styles.fieldBody} data-slot="field-group-body">
48
+ {children}
49
+ </div>
50
+ ) : null}
39
51
  </div>
40
52
  );
41
53
  }
@@ -45,11 +45,11 @@ export function PropertiesTable({ children }: PropertiesTableProps) {
45
45
  <span className="inline-flex flex-wrap items-center gap-2">
46
46
  {name}
47
47
  {required ? (
48
- <Badge size="xs" tone="primary">
48
+ <Badge size="sm" tone="primary">
49
49
  required
50
50
  </Badge>
51
51
  ) : null}
52
- {deprecated ? <Badge size="xs">deprecated</Badge> : null}
52
+ {deprecated ? <Badge size="sm">deprecated</Badge> : null}
53
53
  </span>
54
54
  </td>
55
55
  <td>{displayValue(type)}</td>