@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.
- package/bin/shiso.mjs +20 -1
- package/dist/chunks/App.js +412 -51
- package/dist/chunks/abnfDiagram-VCTEODGH.js +109 -0
- package/dist/chunks/arc.js +130 -0
- package/dist/chunks/architecture-7GRP2DOG.js +4 -0
- package/dist/chunks/architectureDiagram-5GKGNRK7.js +7466 -0
- package/dist/chunks/array.js +8 -0
- package/dist/chunks/blockDiagram-I7D4REHJ.js +2546 -0
- package/dist/chunks/c4Diagram-7LVT6UL2.js +3654 -0
- package/dist/chunks/channel.js +9 -0
- package/dist/chunks/chunk-2Q5K7J3B.js +21 -0
- package/dist/chunks/chunk-4HAMMTFA.js +6106 -0
- package/dist/chunks/chunk-5VM5RSS4.js +21 -0
- package/dist/chunks/chunk-75Z2AOVW.js +2185 -0
- package/dist/chunks/chunk-DU6HZSFF.js +8729 -0
- package/dist/chunks/chunk-F27PBJKO.js +95 -0
- package/dist/chunks/chunk-FOHPRMQF.js +27162 -0
- package/dist/chunks/chunk-GMAD6QVW.js +2369 -0
- package/dist/chunks/chunk-GVQU2GXP.js +58 -0
- package/dist/chunks/chunk-IMKFNOWR.js +2593 -0
- package/dist/chunks/chunk-JWPE2WC7.js +12 -0
- package/dist/chunks/chunk-L3NEJ4N5.js +420 -0
- package/dist/chunks/chunk-OSK3NFVY.js +1029 -0
- package/dist/chunks/chunk-P2QGCYS3.js +95 -0
- package/dist/chunks/chunk-POPQ4Y6H.js +33 -0
- package/dist/chunks/chunk-PWAF6VOD.js +467 -0
- package/dist/chunks/chunk-SHT3W25Y.js +4813 -0
- package/dist/chunks/chunk-SVP7TREG.js +686 -0
- package/dist/chunks/chunk-TICWLB2K.js +3800 -0
- package/dist/chunks/chunk-XXDRQBXY.js +12 -0
- package/dist/chunks/chunk-Y2CYZVJY.js +15 -0
- package/dist/chunks/classDiagram-ZZMXUADV.js +33 -0
- package/dist/chunks/classDiagram-v2-VYDZK3BY.js +33 -0
- package/dist/chunks/cose-bilkent-JH36ORCC.js +4268 -0
- package/dist/chunks/cynefin-OW5HDTMX.js +4 -0
- package/dist/chunks/cynefinDiagram-5FMLGOSQ.js +482 -0
- package/dist/chunks/cytoscape.esm.js +28282 -0
- package/dist/chunks/dagre-GXQ25YYZ.js +583 -0
- package/dist/chunks/dagre.js +4235 -0
- package/dist/chunks/defaultLocale.js +246 -0
- package/dist/chunks/diagram-S7CK7UJ4.js +402 -0
- package/dist/chunks/diagram-UQ7AKVKN.js +291 -0
- package/dist/chunks/diagram-VSXAHHWV.js +553 -0
- package/dist/chunks/diagram-VX7I27RA.js +798 -0
- package/dist/chunks/diagram-Z3DM3KII.js +186 -0
- package/dist/chunks/dist.js +92 -0
- package/dist/chunks/docs.js +989 -93
- package/dist/chunks/ebnfDiagram-PWID7BFC.js +123 -0
- package/dist/chunks/erDiagram-RLTQ6QDP.js +2295 -0
- package/dist/chunks/eventmodeling-NTZA5JFV.js +4 -0
- package/dist/chunks/flowDiagram-HODETNUW.js +17 -0
- package/dist/chunks/ganttDiagram-EL5Y4UJY.js +3928 -0
- package/dist/chunks/gitGraph-4MIJSDKK.js +4 -0
- package/dist/chunks/gitGraphDiagram-WWUBYQGX.js +1153 -0
- package/dist/chunks/graphlib.js +4194 -0
- package/dist/chunks/info-A6RAGUB7.js +4 -0
- package/dist/chunks/infoDiagram-27XIBGKW.js +25 -0
- package/dist/chunks/init.js +14 -0
- package/dist/chunks/ishikawaDiagram-5VMMS53U.js +971 -0
- package/dist/chunks/journeyDiagram-3NMN7TZE.js +1226 -0
- package/dist/chunks/kanban-definition-UXKFOSKX.js +1241 -0
- package/dist/chunks/katex.js +26463 -0
- package/dist/chunks/line.js +48 -0
- package/dist/chunks/linear.js +391 -0
- package/dist/chunks/mermaid-parser.core.js +754 -0
- package/dist/chunks/mermaid.core.js +4898 -0
- package/dist/chunks/mindmap-definition-YA3MSWOX.js +1309 -0
- package/dist/chunks/ordinal.js +84 -0
- package/dist/chunks/packet-AYTQ26CC.js +4 -0
- package/dist/chunks/path.js +106 -0
- package/dist/chunks/pegDiagram-XKGWAZYB.js +115 -0
- package/dist/chunks/pie-WAS4IAKB.js +4 -0
- package/dist/chunks/pieDiagram-E7YTZNPT.js +297 -0
- package/dist/chunks/quadrantDiagram-AXDQQJYC.js +2234 -0
- package/dist/chunks/radar-RG4KPBEZ.js +4 -0
- package/dist/chunks/railroad-74A4TZTK.js +4 -0
- package/dist/chunks/railroad-abnf-HS5TGJTU.js +4 -0
- package/dist/chunks/railroad-ebnf-LZEXJU2U.js +4 -0
- package/dist/chunks/railroad-peg-WCYAUIDC.js +4 -0
- package/dist/chunks/railroadDiagram-O6MQD6OU.js +89 -0
- package/dist/chunks/requirementDiagram-BXWQKSXE.js +2464 -0
- package/dist/chunks/rough.esm.js +1396 -0
- package/dist/chunks/sankeyDiagram-P5KCCOFB.js +1308 -0
- package/dist/chunks/sequenceDiagram-WJ2MYXX4.js +5445 -0
- package/dist/chunks/sizeCapture-INFHLROL.js +56 -0
- package/dist/chunks/src.js +2653 -0
- package/dist/chunks/stateDiagram-D77RDMKH.js +368 -0
- package/dist/chunks/stateDiagram-v2-MP3YSRHH.js +33 -0
- package/dist/chunks/swimlanes-42K2YHIH.js +6925 -0
- package/dist/chunks/swimlanesDiagram-VR7AAH4N.js +32 -0
- package/dist/chunks/timeline-definition-24CTP7MA.js +1507 -0
- package/dist/chunks/treeView-Q6P3EWNA.js +4 -0
- package/dist/chunks/treemap-WGGIJYW6.js +4 -0
- package/dist/chunks/vennDiagram-4TSXK5OY.js +2803 -0
- package/dist/chunks/wardley-WFR3VGLG.js +4 -0
- package/dist/chunks/wardleyDiagram-VM6X3IG4.js +877 -0
- package/dist/chunks/xychartDiagram-S5SC5T6Z.js +2698 -0
- package/dist/components.js +2 -2
- package/dist/entry-client.js +2 -2
- package/dist/entry-server.js +31 -3
- package/docs.schema.json +90 -0
- package/mdx.config.ts +13 -97
- package/package.json +6 -4
- package/scripts/build-runtime.mjs +1 -0
- package/scripts/check-content.mjs +358 -0
- package/scripts/expand-navigation-globs.mjs +277 -0
- package/scripts/expand-openapi-navigation.mjs +94 -0
- package/scripts/generate-openapi.mjs +117 -0
- package/scripts/generate-search-index.mjs +31 -1
- package/scripts/lib/openapi.mjs +653 -0
- package/scripts/load-docs-config.mjs +52 -6
- package/scripts/load-shiso-config.mjs +45 -3
- package/scripts/prerender.mjs +83 -3
- package/scripts/vite-docs-config.mjs +27 -6
- package/src/App.tsx +0 -1
- package/src/components/CodeBlock.tsx +108 -8
- package/src/components/DocContent.tsx +18 -0
- package/src/components/Docs.tsx +45 -17
- package/src/components/OpenApiOperation.tsx +205 -0
- package/src/components/SideNav.tsx +13 -2
- package/src/components/docs/Changelog.tsx +163 -0
- package/src/components/docs/CodeGroup.tsx +6 -2
- package/src/components/docs/Mermaid.tsx +164 -0
- package/src/components/docs/Panel.tsx +23 -0
- package/src/components/docs/ParamField.tsx +17 -5
- package/src/components/docs/PropertiesTable.tsx +2 -2
- package/src/components/docs/ResponseField.tsx +17 -5
- package/src/components/docs/Tiles.tsx +59 -0
- package/src/components/docs/Tree.tsx +359 -0
- package/src/components/docs/Update.tsx +72 -0
- package/src/components/docs/index.ts +7 -0
- package/src/components/docs/panel-context.tsx +25 -0
- package/src/components/docs/styles.ts +49 -0
- package/src/components/icons/index.ts +4 -0
- package/src/entry-server.tsx +50 -0
- package/src/lib/code-blocks.ts +18 -0
- package/src/lib/code-meta.ts +87 -0
- package/src/lib/docs-config.ts +4 -1
- package/src/lib/openapi.generated.ts +4 -0
- package/src/lib/openapi.ts +63 -0
- package/src/lib/rehype-shiki.ts +213 -0
- package/src/lib/site-model.ts +2 -0
- package/src/lib/types.ts +116 -2
- package/src/styles/global.css +63 -73
- package/types/components.d.ts +79 -0
- package/types/config.d.ts +11 -4
- package/vite.config.ts +49 -3
- package/CHANGELOG.md +0 -171
|
@@ -23,14 +23,26 @@ export function ResponseField({ name, type, required, deprecated, children }: Re
|
|
|
23
23
|
: String(type);
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
|
-
<div className={styles.field}>
|
|
26
|
+
<div className={styles.field} data-slot="field-group-item">
|
|
27
27
|
<div className={styles.fieldHeader}>
|
|
28
28
|
<span className={styles.fieldName}>{name}</span>
|
|
29
|
-
{normalizedType ?
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
{normalizedType ? (
|
|
30
|
+
<span className={styles.fieldType} data-slot="field-group-type">
|
|
31
|
+
{normalizedType}
|
|
32
|
+
</span>
|
|
33
|
+
) : null}
|
|
34
|
+
{required ? (
|
|
35
|
+
<Badge size="sm" tone="primary">
|
|
36
|
+
required
|
|
37
|
+
</Badge>
|
|
38
|
+
) : null}
|
|
39
|
+
{deprecated ? <Badge size="sm">deprecated</Badge> : null}
|
|
32
40
|
</div>
|
|
33
|
-
{children ?
|
|
41
|
+
{children ? (
|
|
42
|
+
<div className={styles.fieldBody} data-slot="field-group-body">
|
|
43
|
+
{children}
|
|
44
|
+
</div>
|
|
45
|
+
) : null}
|
|
34
46
|
</div>
|
|
35
47
|
);
|
|
36
48
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Link } from 'react-router';
|
|
3
|
+
import { styles } from './styles';
|
|
4
|
+
import { gridColsClass } from './utils';
|
|
5
|
+
|
|
6
|
+
export interface TileProps {
|
|
7
|
+
/** URL to navigate to when the tile is clicked. */
|
|
8
|
+
href: string;
|
|
9
|
+
title?: ReactNode;
|
|
10
|
+
description?: ReactNode;
|
|
11
|
+
/** Preview content, typically light/dark image variants. */
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
target?: '_self' | '_blank';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Tile({ href, title, description, children, target }: TileProps) {
|
|
17
|
+
const external = /^https?:\/\//i.test(href);
|
|
18
|
+
const resolvedTarget = target ?? (external ? '_blank' : undefined);
|
|
19
|
+
|
|
20
|
+
const content = (
|
|
21
|
+
<>
|
|
22
|
+
<div className={styles.tilePreview} data-slot="tile-preview">
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
{title || description ? (
|
|
26
|
+
<div className={styles.tileBody} data-slot="tile-body">
|
|
27
|
+
{title ? <div className={styles.tileTitle}>{title}</div> : null}
|
|
28
|
+
{description ? <div className={styles.tileDescription}>{description}</div> : null}
|
|
29
|
+
</div>
|
|
30
|
+
) : null}
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const className = `${styles.tile} no-underline hover:no-underline active:no-underline`;
|
|
35
|
+
|
|
36
|
+
if (external) {
|
|
37
|
+
return (
|
|
38
|
+
<a href={href} className={className} target={resolvedTarget} rel="noreferrer">
|
|
39
|
+
{content}
|
|
40
|
+
</a>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Link to={href} className={className} target={resolvedTarget}>
|
|
46
|
+
{content}
|
|
47
|
+
</Link>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TilesProps {
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
cols?: 1 | 2 | 3 | 4;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Grid wrapper for <Tile> when <Columns> is not used directly. */
|
|
57
|
+
export function Tiles({ children, cols = 2 }: TilesProps) {
|
|
58
|
+
return <div className={`${styles.grid} ${styles[gridColsClass(cols)]}`}>{children}</div>;
|
|
59
|
+
}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
type CSSProperties,
|
|
4
|
+
isValidElement,
|
|
5
|
+
type KeyboardEvent,
|
|
6
|
+
type ReactElement,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import {
|
|
12
|
+
ChevronRight,
|
|
13
|
+
File as FileIcon,
|
|
14
|
+
Folder as FolderIcon,
|
|
15
|
+
FolderOpen,
|
|
16
|
+
} from '@/components/icons';
|
|
17
|
+
import { styles } from './styles';
|
|
18
|
+
|
|
19
|
+
export interface TreeFolderProps {
|
|
20
|
+
name: string;
|
|
21
|
+
defaultOpen?: boolean;
|
|
22
|
+
/** When false the folder renders statically (no toggle). Defaults to true. */
|
|
23
|
+
openable?: boolean;
|
|
24
|
+
highlight?: boolean;
|
|
25
|
+
children?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TreeFileProps {
|
|
29
|
+
name: string;
|
|
30
|
+
highlight?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TreeProps {
|
|
34
|
+
children?: ReactNode;
|
|
35
|
+
style?: CSSProperties;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function elementText(node: ReactNode): string {
|
|
40
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
41
|
+
return String(node);
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(node)) {
|
|
44
|
+
return node.map(elementText).join('');
|
|
45
|
+
}
|
|
46
|
+
if (isValidElement<{ children?: ReactNode }>(node)) {
|
|
47
|
+
return elementText(node.props.children);
|
|
48
|
+
}
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isListElement(node: ReactNode): node is ReactElement<{ children?: ReactNode }> {
|
|
53
|
+
return isValidElement(node) && (node.type === 'ul' || node.type === 'ol');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Stable key for MDX-generated list wrappers (static content, never reordered). */
|
|
57
|
+
function listFingerprint(node: ReactNode): string {
|
|
58
|
+
const text = elementText(node).replace(/\s+/g, ' ').trim().slice(0, 64);
|
|
59
|
+
let hash = 0;
|
|
60
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
61
|
+
hash = (hash * 31 + text.charCodeAt(i)) | 0;
|
|
62
|
+
}
|
|
63
|
+
return `mdx-list-${text.length}-${hash}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Split an MDX <li> into its label (inline content) and an optional nested list. */
|
|
67
|
+
function splitListItem(li: ReactElement<{ children?: ReactNode }>): {
|
|
68
|
+
label: string;
|
|
69
|
+
nested: ReactNode;
|
|
70
|
+
} {
|
|
71
|
+
let nested: ReactNode = null;
|
|
72
|
+
const labelParts: ReactNode[] = [];
|
|
73
|
+
|
|
74
|
+
Children.forEach(li.props.children, child => {
|
|
75
|
+
if (isListElement(child)) {
|
|
76
|
+
nested = child;
|
|
77
|
+
} else {
|
|
78
|
+
labelParts.push(child);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return { label: elementText(labelParts).trim(), nested };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ListItemsAsNodes({ items }: { items: ReactNode }): ReactNode {
|
|
86
|
+
const nodes: ReactNode[] = [];
|
|
87
|
+
let index = 0;
|
|
88
|
+
|
|
89
|
+
Children.forEach(items, li => {
|
|
90
|
+
if (!isValidElement<{ children?: ReactNode }>(li) || li.type !== 'li') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const { label, nested } = splitListItem(li);
|
|
94
|
+
if (!label && !nested) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const name = label || 'untitled';
|
|
98
|
+
const isFolder = name.endsWith('/') || Boolean(nested);
|
|
99
|
+
index += 1;
|
|
100
|
+
|
|
101
|
+
if (isFolder) {
|
|
102
|
+
nodes.push(
|
|
103
|
+
<TreeFolder key={`${name}-${index}`} name={name} defaultOpen={Boolean(nested)}>
|
|
104
|
+
{nested ? (
|
|
105
|
+
<ListItemsAsNodes
|
|
106
|
+
items={(nested as ReactElement<{ children?: ReactNode }>).props.children}
|
|
107
|
+
/>
|
|
108
|
+
) : null}
|
|
109
|
+
</TreeFolder>,
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
nodes.push(<TreeFile key={`${name}-${index}`} name={name} />);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return <>{nodes}</>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function containsList(children: ReactNode): boolean {
|
|
120
|
+
let found = false;
|
|
121
|
+
Children.forEach(children, child => {
|
|
122
|
+
if (isListElement(child)) {
|
|
123
|
+
found = true;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
return found;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function TreeFolder({
|
|
130
|
+
name,
|
|
131
|
+
defaultOpen = false,
|
|
132
|
+
openable = true,
|
|
133
|
+
highlight = false,
|
|
134
|
+
children,
|
|
135
|
+
}: TreeFolderProps) {
|
|
136
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
137
|
+
const expanded = openable ? open : true;
|
|
138
|
+
const Icon = expanded ? FolderOpen : FolderIcon;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
// biome-ignore lint/a11y/useFocusableInteractive: keyboard focus is delegated to the row button inside this treeitem
|
|
142
|
+
<li
|
|
143
|
+
role="treeitem"
|
|
144
|
+
aria-expanded={openable ? expanded : undefined}
|
|
145
|
+
data-highlight={highlight ? '' : undefined}
|
|
146
|
+
className={styles.treeItem}
|
|
147
|
+
>
|
|
148
|
+
{openable ? (
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
data-tree-focus
|
|
152
|
+
onClick={() => setOpen(value => !value)}
|
|
153
|
+
className={`${styles.treeRow} ${highlight ? styles.treeHighlight : ''}`}
|
|
154
|
+
>
|
|
155
|
+
<ChevronRight
|
|
156
|
+
size={14}
|
|
157
|
+
className={styles.treeChevron}
|
|
158
|
+
data-open={expanded ? '' : undefined}
|
|
159
|
+
aria-hidden={true}
|
|
160
|
+
/>
|
|
161
|
+
<Icon size={15} className={styles.treeIcon} aria-hidden={true} />
|
|
162
|
+
<span className={styles.treeName}>{name}</span>
|
|
163
|
+
</button>
|
|
164
|
+
) : (
|
|
165
|
+
<span
|
|
166
|
+
data-tree-focus
|
|
167
|
+
tabIndex={-1}
|
|
168
|
+
className={`${styles.treeRow} ${highlight ? styles.treeHighlight : ''}`}
|
|
169
|
+
>
|
|
170
|
+
<Icon size={15} className={styles.treeIcon} aria-hidden={true} />
|
|
171
|
+
<span className={styles.treeName}>{name}</span>
|
|
172
|
+
</span>
|
|
173
|
+
)}
|
|
174
|
+
{children ? (
|
|
175
|
+
// biome-ignore lint/a11y/useSemanticElements: group is the required ARIA role for nested tree levels
|
|
176
|
+
<ul
|
|
177
|
+
role="group"
|
|
178
|
+
hidden={openable && !expanded ? true : undefined}
|
|
179
|
+
className={styles.treeGroup}
|
|
180
|
+
>
|
|
181
|
+
{children}
|
|
182
|
+
</ul>
|
|
183
|
+
) : null}
|
|
184
|
+
</li>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function TreeFile({ name, highlight = false }: TreeFileProps) {
|
|
189
|
+
return (
|
|
190
|
+
// biome-ignore lint/a11y/useFocusableInteractive: file rows are arrow-key targets in the tree widget
|
|
191
|
+
<li role="treeitem" data-highlight={highlight ? '' : undefined} className={styles.treeItem}>
|
|
192
|
+
<span
|
|
193
|
+
data-tree-focus
|
|
194
|
+
tabIndex={-1}
|
|
195
|
+
className={`${styles.treeRow} ${highlight ? styles.treeHighlight : ''}`}
|
|
196
|
+
>
|
|
197
|
+
<FileIcon size={15} className={styles.treeIcon} aria-hidden={true} />
|
|
198
|
+
<span className={styles.treeName}>{name}</span>
|
|
199
|
+
</span>
|
|
200
|
+
</li>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function visibleFocusables(root: HTMLElement): HTMLElement[] {
|
|
205
|
+
return [...root.querySelectorAll<HTMLElement>('[data-tree-focus]')].filter(
|
|
206
|
+
element => !element.closest('[hidden]'),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function TreeRoot({ children, style, className }: TreeProps) {
|
|
211
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
212
|
+
const searchRef = useRef({ text: '', timer: 0 });
|
|
213
|
+
|
|
214
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
215
|
+
const root = rootRef.current;
|
|
216
|
+
const target = event.target as HTMLElement;
|
|
217
|
+
if (!root || !target.closest('[data-tree-focus]')) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const items = visibleFocusables(root);
|
|
222
|
+
const index = items.indexOf(target as HTMLElement);
|
|
223
|
+
|
|
224
|
+
const focusAt = (next: number) => {
|
|
225
|
+
event.preventDefault();
|
|
226
|
+
items[(next + items.length) % items.length]?.focus();
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
switch (event.key) {
|
|
230
|
+
case 'ArrowDown':
|
|
231
|
+
focusAt(index + 1);
|
|
232
|
+
break;
|
|
233
|
+
case 'ArrowUp':
|
|
234
|
+
focusAt(index - 1);
|
|
235
|
+
break;
|
|
236
|
+
case 'Home':
|
|
237
|
+
focusAt(0);
|
|
238
|
+
break;
|
|
239
|
+
case 'End':
|
|
240
|
+
focusAt(items.length - 1);
|
|
241
|
+
break;
|
|
242
|
+
case 'ArrowRight': {
|
|
243
|
+
const row = target.closest('[role="treeitem"]');
|
|
244
|
+
const toggle = row?.querySelector<HTMLElement>(':scope > [data-tree-focus]');
|
|
245
|
+
if (
|
|
246
|
+
toggle &&
|
|
247
|
+
toggle.tagName === 'BUTTON' &&
|
|
248
|
+
toggle.getAttribute('aria-expanded') === 'false'
|
|
249
|
+
) {
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
toggle.click();
|
|
252
|
+
} else {
|
|
253
|
+
const firstChild = row?.querySelector<HTMLElement>(':scope > ul [data-tree-focus]');
|
|
254
|
+
if (firstChild) {
|
|
255
|
+
event.preventDefault();
|
|
256
|
+
firstChild.focus();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case 'ArrowLeft': {
|
|
262
|
+
const row = target.closest('[role="treeitem"]');
|
|
263
|
+
const toggle = row?.querySelector<HTMLElement>(':scope > [data-tree-focus]');
|
|
264
|
+
if (
|
|
265
|
+
toggle &&
|
|
266
|
+
toggle.tagName === 'BUTTON' &&
|
|
267
|
+
toggle.getAttribute('aria-expanded') === 'true'
|
|
268
|
+
) {
|
|
269
|
+
event.preventDefault();
|
|
270
|
+
toggle.click();
|
|
271
|
+
} else {
|
|
272
|
+
const parent = row?.parentElement?.closest('[role="treeitem"]');
|
|
273
|
+
const parentFocus = parent?.querySelector<HTMLElement>(':scope > [data-tree-focus]');
|
|
274
|
+
if (parentFocus) {
|
|
275
|
+
event.preventDefault();
|
|
276
|
+
parentFocus.focus();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case '*': {
|
|
282
|
+
// Expand all sibling folders at the current level.
|
|
283
|
+
const group = target.closest('ul');
|
|
284
|
+
group
|
|
285
|
+
?.querySelectorAll<HTMLElement>(
|
|
286
|
+
':scope > [role="treeitem"] > button[aria-expanded="false"]',
|
|
287
|
+
)
|
|
288
|
+
.forEach(button => {
|
|
289
|
+
button.click();
|
|
290
|
+
});
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
default: {
|
|
294
|
+
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
|
|
295
|
+
const state = searchRef.current;
|
|
296
|
+
window.clearTimeout(state.timer);
|
|
297
|
+
state.text += event.key.toLowerCase();
|
|
298
|
+
state.timer = window.setTimeout(() => {
|
|
299
|
+
state.text = '';
|
|
300
|
+
}, 500);
|
|
301
|
+
const match = items.find(item =>
|
|
302
|
+
(item.textContent || '').trim().toLowerCase().startsWith(state.text),
|
|
303
|
+
);
|
|
304
|
+
if (match) {
|
|
305
|
+
event.preventDefault();
|
|
306
|
+
(match as HTMLElement).focus();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const content = containsList(children)
|
|
314
|
+
? Children.map(children, child =>
|
|
315
|
+
isListElement(child) ? (
|
|
316
|
+
<ListItemsAsNodes
|
|
317
|
+
key={listFingerprint(child)}
|
|
318
|
+
items={(child as ReactElement<{ children?: ReactNode }>).props.children}
|
|
319
|
+
/>
|
|
320
|
+
) : (
|
|
321
|
+
child
|
|
322
|
+
),
|
|
323
|
+
)
|
|
324
|
+
: children;
|
|
325
|
+
|
|
326
|
+
// Buttons (folders) stay in the tab order natively; file rows are reached
|
|
327
|
+
// with arrow keys.
|
|
328
|
+
return (
|
|
329
|
+
// biome-ignore lint/a11y/noStaticElementInteractions: key handling for the tree widget lives on its root
|
|
330
|
+
<div
|
|
331
|
+
ref={rootRef}
|
|
332
|
+
style={style}
|
|
333
|
+
className={`${styles.tree} ${className || ''}`}
|
|
334
|
+
onKeyDown={onKeyDown}
|
|
335
|
+
>
|
|
336
|
+
{/* biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: tree is the required ARIA role for this widget */}
|
|
337
|
+
<ul role="tree" aria-label="File tree" className={styles.treeList}>
|
|
338
|
+
{content}
|
|
339
|
+
</ul>
|
|
340
|
+
</div>
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function Tree(props: TreeProps) {
|
|
345
|
+
return <TreeRoot {...props} />;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Alias: <Tree> and <FileTree> are interchangeable, like Mintlify. */
|
|
349
|
+
export function FileTree(props: TreeProps) {
|
|
350
|
+
return <TreeRoot {...props} />;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
Tree.Folder = TreeFolder;
|
|
354
|
+
Tree.File = TreeFile;
|
|
355
|
+
FileTree.Folder = TreeFolder;
|
|
356
|
+
FileTree.File = TreeFile;
|
|
357
|
+
|
|
358
|
+
export const FileTreeFolder = TreeFolder;
|
|
359
|
+
export const FileTreeFile = TreeFile;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Badge } from './Badge';
|
|
3
|
+
import { styles } from './styles';
|
|
4
|
+
import { slugify } from './utils';
|
|
5
|
+
|
|
6
|
+
export interface UpdateRss {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UpdateProps {
|
|
12
|
+
/** Left-hand label. Also creates the anchor id (slugified, like headings). */
|
|
13
|
+
label: string;
|
|
14
|
+
description?: ReactNode;
|
|
15
|
+
tags?: string[];
|
|
16
|
+
/**
|
|
17
|
+
* Mintlify compatibility: custom RSS title/description for this entry.
|
|
18
|
+
* Shiso does not generate an RSS feed yet; the prop is accepted and ignored.
|
|
19
|
+
*/
|
|
20
|
+
rss?: UpdateRss;
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeTags(tags: UpdateProps['tags']): string[] {
|
|
25
|
+
if (!Array.isArray(tags)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const seen = new Set<string>();
|
|
30
|
+
for (const tag of tags) {
|
|
31
|
+
if (typeof tag !== 'string') {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const trimmed = tag.trim();
|
|
35
|
+
if (trimmed && !seen.has(trimmed)) {
|
|
36
|
+
seen.add(trimmed);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return [...seen];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function Update({ label, description, tags, rss: _rss, children }: UpdateProps) {
|
|
44
|
+
const id = slugify(label, 'update');
|
|
45
|
+
const normalizedTags = normalizeTags(tags);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<section
|
|
49
|
+
id={id}
|
|
50
|
+
aria-label={label}
|
|
51
|
+
className={styles.update}
|
|
52
|
+
data-tags={normalizedTags.join(',')}
|
|
53
|
+
>
|
|
54
|
+
<div className={styles.updateMeta}>
|
|
55
|
+
<a href={`#${id}`} className={styles.updateLabel}>
|
|
56
|
+
{label}
|
|
57
|
+
</a>
|
|
58
|
+
{description ? <div className={styles.updateDescription}>{description}</div> : null}
|
|
59
|
+
{normalizedTags.length > 0 ? (
|
|
60
|
+
<div className={styles.updateTags}>
|
|
61
|
+
{normalizedTags.map(tag => (
|
|
62
|
+
<Badge key={tag} size="sm">
|
|
63
|
+
{tag}
|
|
64
|
+
</Badge>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
) : null}
|
|
68
|
+
</div>
|
|
69
|
+
<div className={styles.updateBody}>{children}</div>
|
|
70
|
+
</section>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -3,16 +3,23 @@ export * from './Badge';
|
|
|
3
3
|
export * from './Button';
|
|
4
4
|
export * from './Callout';
|
|
5
5
|
export * from './Card';
|
|
6
|
+
export * from './Changelog';
|
|
6
7
|
export * from './CodeGroup';
|
|
7
8
|
export * from './Columns';
|
|
8
9
|
export * from './Expandable';
|
|
9
10
|
export * from './Frame';
|
|
10
11
|
export * from './Icon';
|
|
11
12
|
export * from './Link';
|
|
13
|
+
export * from './Mermaid';
|
|
14
|
+
export * from './Panel';
|
|
15
|
+
export * from './panel-context';
|
|
12
16
|
export * from './ParamField';
|
|
13
17
|
export * from './PropertiesTable';
|
|
14
18
|
export * from './ResponseField';
|
|
15
19
|
export * from './Steps';
|
|
16
20
|
export * from './Tabs';
|
|
21
|
+
export * from './Tiles';
|
|
17
22
|
export * from './Tooltip';
|
|
23
|
+
export * from './Tree';
|
|
24
|
+
export * from './Update';
|
|
18
25
|
export * from './ZoomableImage';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createContext, type ReactNode, useContext, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface PanelContextValue {
|
|
4
|
+
content: ReactNode;
|
|
5
|
+
setContent: (content: ReactNode) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const PanelContext = createContext<PanelContextValue>({
|
|
9
|
+
content: null,
|
|
10
|
+
setContent: () => {},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export function PanelProvider({ children }: { children: ReactNode }) {
|
|
14
|
+
const [content, setContent] = useState<ReactNode>(null);
|
|
15
|
+
|
|
16
|
+
return <PanelContext.Provider value={{ content, setContent }}>{children}</PanelContext.Provider>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function usePanelContent(): ReactNode {
|
|
20
|
+
return useContext(PanelContext).content;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useSetPanelContent(): (content: ReactNode) => void {
|
|
24
|
+
return useContext(PanelContext).setContent;
|
|
25
|
+
}
|
|
@@ -68,6 +68,8 @@ export const styles = {
|
|
|
68
68
|
'relative mt-3 px-4 pb-1 text-center text-sm leading-5 text-muted-foreground [&_p]:m-0 [&_a]:font-semibold',
|
|
69
69
|
|
|
70
70
|
field: 'my-3',
|
|
71
|
+
fieldGroup:
|
|
72
|
+
'my-4 divide-y divide-border overflow-hidden rounded-lg border border-border text-sm leading-6 [&>[data-slot=field-group-item]]:my-0 [&>[data-slot=field-group-item]]:px-4 [&>[data-slot=field-group-item]]:py-3 [&_[data-slot=field-group-body]]:mt-2 [&_[data-slot=field-group-type]]:text-xs',
|
|
71
73
|
paramField: 'my-3',
|
|
72
74
|
fieldHeader: 'flex flex-wrap items-center gap-2',
|
|
73
75
|
fieldBody: 'mt-3',
|
|
@@ -84,4 +86,51 @@ export const styles = {
|
|
|
84
86
|
|
|
85
87
|
tooltipTrigger:
|
|
86
88
|
'peer cursor-help border-0 border-muted-foreground border-b border-dotted bg-transparent p-0 text-inherit [font:inherit]',
|
|
89
|
+
|
|
90
|
+
update:
|
|
91
|
+
'my-8 grid scroll-mt-24 grid-cols-1 gap-4 border-border border-t pt-6 md:grid-cols-[220px_minmax(0,1fr)]',
|
|
92
|
+
updateMeta: 'flex min-w-0 flex-col items-start gap-2 md:sticky md:top-24 md:self-start',
|
|
93
|
+
updateLabel:
|
|
94
|
+
'w-fit text-lg font-semibold text-foreground hover:text-primary hover:underline [&:hover]:decoration-primary',
|
|
95
|
+
updateDescription: 'text-sm text-muted-foreground leading-6',
|
|
96
|
+
updateTags: 'flex flex-wrap gap-1.5',
|
|
97
|
+
updateBody: 'min-w-0 [&>:first-child]:mt-0 [&>:last-child]:mb-0 [&_:where(p,ul,ol)]:leading-7',
|
|
98
|
+
changelog: 'my-4 flex flex-col',
|
|
99
|
+
changelogFilters: 'mb-2 flex flex-wrap items-center gap-1',
|
|
100
|
+
changelogFilterButton:
|
|
101
|
+
'rounded-md p-0.5 outline-none focus-visible:ring-2 focus-visible:ring-ring [&>[data-slot=badge]]:cursor-pointer',
|
|
102
|
+
changelogClear:
|
|
103
|
+
'ml-1 text-xs font-medium text-muted-foreground hover:text-foreground hover:underline',
|
|
104
|
+
changelogEmpty: 'my-6 text-sm text-muted-foreground',
|
|
105
|
+
|
|
106
|
+
mermaid: 'my-4',
|
|
107
|
+
mermaidTitle: 'mb-2 text-sm font-semibold text-foreground',
|
|
108
|
+
mermaidViewport:
|
|
109
|
+
'relative overflow-auto rounded-lg border border-border bg-card p-4 [&_svg]:mx-auto [&_svg]:max-w-full [&_svg]:shrink-0',
|
|
110
|
+
mermaidSvg: 'origin-top-left transition-transform duration-150 [&_svg]:h-auto [&_svg]:max-w-none',
|
|
111
|
+
mermaidFallback: 'bg-transparent p-0 font-mono text-xs text-muted-foreground whitespace-pre-wrap',
|
|
112
|
+
mermaidError: 'mt-2 text-sm text-destructive',
|
|
113
|
+
mermaidControls:
|
|
114
|
+
'absolute flex items-center gap-1 rounded-md border border-border bg-background/90 p-1 shadow-sm [&_button]:flex [&_button]:size-6 [&_button]:items-center [&_button]:justify-center [&_button]:rounded [&_button]:text-sm [&_button]:text-muted-foreground [&_button:hover]:bg-muted [&_button:hover]:text-foreground',
|
|
115
|
+
|
|
116
|
+
panel: 'flex min-w-0 flex-col gap-4 text-sm [&>:first-child]:mt-0 [&>:last-child]:mb-0',
|
|
117
|
+
panelInline: 'my-4 lg:hidden',
|
|
118
|
+
|
|
119
|
+
tile: 'block h-full overflow-hidden rounded-lg border border-border bg-card hover:border-primary',
|
|
120
|
+
tilePreview:
|
|
121
|
+
'flex h-36 items-center justify-center gap-2 overflow-hidden border-border border-b bg-muted/40 bg-[radial-gradient(circle_at_1px_1px,var(--border)_1px,transparent_0)] bg-[size:16px_16px] p-4 [&_img]:max-h-full [&_img]:w-auto [&_img]:max-w-full [&_img]:object-contain',
|
|
122
|
+
tileBody: 'flex flex-col gap-1 p-4',
|
|
123
|
+
tileTitle: 'text-sm font-semibold text-foreground',
|
|
124
|
+
tileDescription: 'text-sm text-muted-foreground leading-6',
|
|
125
|
+
|
|
126
|
+
tree: 'not-prose my-4 overflow-hidden rounded-lg border border-border bg-card text-sm',
|
|
127
|
+
treeList: 'flex flex-col gap-0.5 p-3',
|
|
128
|
+
treeGroup: 'my-0 ml-[15px] flex list-none flex-col gap-px border-border border-l pl-1',
|
|
129
|
+
treeItem: 'my-0 min-w-0 list-none p-0 [&::marker]:content-none',
|
|
130
|
+
treeRow:
|
|
131
|
+
'flex w-full min-w-0 items-center gap-1.5 rounded-md px-2 py-1 text-left text-foreground hover:bg-muted [&:focus-visible]:bg-muted [&:focus-visible]:ring-2 [&:focus-visible]:ring-ring [&:focus-visible]:outline-none',
|
|
132
|
+
treeChevron: 'shrink-0 text-muted-foreground transition-transform [&[data-open]]:rotate-90',
|
|
133
|
+
treeIcon: 'shrink-0 text-muted-foreground',
|
|
134
|
+
treeName: 'min-w-0 flex-1 truncate font-mono text-[0.85rem]',
|
|
135
|
+
treeHighlight: 'bg-primary/10 text-primary [&_svg]:text-primary',
|
|
87
136
|
} as const;
|