fumadocs-openapi 10.9.1 → 10.10.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.
@@ -2,115 +2,234 @@
2
2
  import { useTranslations } from "../client/i18n.js";
3
3
  import { cn } from "../../utils/cn.js";
4
4
  import { Badge } from "../components/method-label.js";
5
- import { Fragment, Suspense, createContext, use, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/select.js";
6
+ import { useAnchorId } from "../../utils/auto-anchor.client.js";
7
+ import { mergeRefs } from "../../utils/merge-refs.js";
8
+ import { Fragment, Suspense, createContext, use, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
6
9
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
- import { ChevronDown, FilterIcon } from "lucide-react";
10
+ import { CheckIcon, FilterIcon, LinkIcon } from "lucide-react";
8
11
  import { buttonVariants } from "fumadocs-ui/components/ui/button";
9
12
  import { cva } from "class-variance-authority";
10
- import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "fumadocs-ui/components/ui/collapsible";
11
- import { Tabs, TabsContent, TabsList, TabsTrigger } from "fumadocs-ui/components/tabs";
13
+ import { useCopyButton } from "fumadocs-ui/utils/use-copy-button";
12
14
  import { Popover, PopoverContent, PopoverTrigger } from "fumadocs-ui/components/ui/popover";
13
15
  //#region src/ui/schema/client.tsx
14
16
  const typeVariants = cva("text-sm text-start text-fd-muted-foreground font-mono", { variants: { variant: { trigger: "underline hover:text-fd-accent-foreground data-[state=open]:text-fd-accent-foreground" } } });
15
- const PopoverContext = createContext({ renderTrigger: (props) => /* @__PURE__ */ jsx(RootPopoverTrigger, { ...props }) });
16
- const DataContext = createContext(null);
17
- function useData() {
18
- return use(DataContext);
19
- }
20
- function usePopover() {
21
- return use(PopoverContext);
17
+ const Context = createContext(null);
18
+ function useStates() {
19
+ return use(Context);
22
20
  }
23
21
  function SchemaUI({ name, required = false, as = "property", generated }) {
24
- return /* @__PURE__ */ jsx(DataContext, {
25
- value: generated,
26
- children: /* @__PURE__ */ jsx(SchemaUIProperty, {
22
+ const rootId = useAnchorId([name]);
23
+ const [path, _setPath] = useState(() => [{
24
+ $ref: generated.$root,
25
+ name
26
+ }]);
27
+ const ref = useRef(null);
28
+ const popoverRef = useRef(null);
29
+ const setPath = useCallback((v) => {
30
+ for (const item of v) delete item.highlighted;
31
+ _setPath((current) => {
32
+ if (popoverRef.current && current.length > 0) current[current.length - 1].scrollTop = popoverRef.current.scrollTop;
33
+ return v;
34
+ });
35
+ }, []);
36
+ useEffect(() => {
37
+ const element = popoverRef.current;
38
+ if (!element) return;
39
+ element.scrollTop = path.at(-1).scrollTop ?? 0;
40
+ const current = parseFloat(element.style.getPropertyValue("--min-height") || "0");
41
+ element.style.setProperty("--min-height", Math.max(element.clientHeight + 2, current) + "px");
42
+ }, [path]);
43
+ useEffect(() => {
44
+ const url = new URL(window.location.href);
45
+ const param = url.searchParams.get("path");
46
+ if (url.hash !== `#${rootId}` || !param) return;
47
+ const decoded = decodePath(param, url.searchParams.get("s-highlight"));
48
+ if (!decoded || decoded.length === 0 || decoded.some((item) => !generated.refs[item.$ref])) return;
49
+ _setPath(decoded);
50
+ if (!decoded.at(-1).highlighted) ref.current?.scrollIntoView({ behavior: "smooth" });
51
+ }, [rootId]);
52
+ return /* @__PURE__ */ jsx(Context, {
53
+ value: useMemo(() => ({
54
+ rootId,
55
+ path,
56
+ generated,
57
+ setPath,
58
+ renderTypeInfoTrigger: ({ $ref, children, pathName }) => /* @__PURE__ */ jsxs(Popover, {
59
+ open: path[1] && path[1].$ref === $ref && path[1].name === pathName && !path.at(-1).closed,
60
+ onOpenChange: (v) => {
61
+ if (v) setPath([path[0], {
62
+ name: pathName,
63
+ $ref
64
+ }]);
65
+ else {
66
+ const next = [...path];
67
+ next.at(-1).closed = true;
68
+ setPath(next);
69
+ }
70
+ },
71
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
72
+ className: cn(typeVariants({ variant: "trigger" })),
73
+ children
74
+ }), /* @__PURE__ */ jsx(PopoverContent, {
75
+ ref: popoverRef,
76
+ className: "w-[600px] min-h-(--min-height,0) max-h-[460px] px-2 pt-0",
77
+ onOpenAutoFocus: (e) => {
78
+ const input = e.target.querySelector("input[data-object-search-input]");
79
+ if (!(input instanceof HTMLInputElement)) return;
80
+ input.focus({ preventScroll: true });
81
+ e.preventDefault();
82
+ },
83
+ children: /* @__PURE__ */ jsx(SchemaUIPopover, {})
84
+ })]
85
+ })
86
+ }), [
87
+ generated,
88
+ path,
89
+ rootId,
90
+ setPath
91
+ ]),
92
+ children: as === "property" || generated.refs[generated.$root].type === "primitive" ? /* @__PURE__ */ jsx(ObjectProperty, {
93
+ ref,
94
+ id: rootId,
27
95
  name,
28
96
  $type: generated.$root,
29
- overrides: { required },
30
- variant: as === "property" || generated.refs[generated.$root].type === "primitive" ? "default" : "expand"
97
+ required
98
+ }) : /* @__PURE__ */ jsx("div", {
99
+ id: rootId,
100
+ ref,
101
+ children: /* @__PURE__ */ jsx(PathItemBody, { pathIndex: 0 })
31
102
  })
32
103
  });
33
104
  }
34
- function SchemaUIProperty({ name, $type, variant = "default", overrides, objectSearchOverrides }) {
35
- const { refs } = useData();
105
+ function SchemaDescription({ schema, ...props }) {
106
+ return /* @__PURE__ */ jsxs("div", {
107
+ ...props,
108
+ className: cn("prose-no-margin py-4 empty:hidden", props.className),
109
+ children: [schema.description, schema.infoTags && schema.infoTags.length > 0 && /* @__PURE__ */ jsx("div", {
110
+ className: "flex flex-row gap-2 flex-wrap mt-2 not-prose empty:hidden",
111
+ children: schema.infoTags.map((tag, i) => /* @__PURE__ */ jsx(InfoTag, { tag }, i))
112
+ })]
113
+ });
114
+ }
115
+ function ObjectProperty({ name, $type, required, ...props }) {
116
+ const t = useTranslations();
117
+ const { path, generated: { refs }, rootId } = useStates();
36
118
  const schema = refs[$type];
37
- const renderRef = useRenderRef();
38
- let type = schema.aliasName;
119
+ const highlighted = path.at(-1).highlighted === name;
120
+ const [isChecked, onClick] = useCopyButton(() => {
121
+ const url = new URL(window.location.href);
122
+ url.hash = `#${rootId}`;
123
+ url.searchParams.set("s-highlight", name);
124
+ url.searchParams.set("path", encodePath(path));
125
+ return navigator.clipboard.writeText(url.href);
126
+ });
127
+ return /* @__PURE__ */ jsxs("div", {
128
+ ...props,
129
+ ref: mergeRefs(props.ref, useCallback((element) => {
130
+ if (element && highlighted) element.scrollIntoView();
131
+ }, [highlighted])),
132
+ className: cn("group/property text-sm border-t py-4 first:border-t-0", props.className),
133
+ children: [/* @__PURE__ */ jsxs("div", {
134
+ className: "flex flex-wrap items-center gap-3 not-prose",
135
+ children: [
136
+ /* @__PURE__ */ jsxs("span", {
137
+ className: "font-medium font-mono",
138
+ children: [/* @__PURE__ */ jsx("span", {
139
+ className: cn(highlighted ? "bg-fd-primary text-fd-primary-foreground rounded-sm" : "text-fd-primary", schema.deprecated && "line-through opacity-80"),
140
+ children: name
141
+ }), required ? /* @__PURE__ */ jsx("span", {
142
+ className: "text-red-400",
143
+ children: "*"
144
+ }) : /* @__PURE__ */ jsx("span", {
145
+ className: "text-fd-muted-foreground",
146
+ children: "?"
147
+ })]
148
+ }),
149
+ schema.type === "primitive" ? /* @__PURE__ */ jsx("span", {
150
+ className: cn(typeVariants()),
151
+ children: schema.aliasName
152
+ }) : /* @__PURE__ */ jsx(TypeInfoTrigger, {
153
+ pathName: name,
154
+ $ref: $type,
155
+ children: schema.aliasName
156
+ }),
157
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
158
+ schema.deprecated && /* @__PURE__ */ jsx(Badge, {
159
+ color: "yellow",
160
+ className: "text-xs",
161
+ children: t.deprecated
162
+ }),
163
+ /* @__PURE__ */ jsx("button", {
164
+ className: cn(buttonVariants({
165
+ size: "icon-xs",
166
+ variant: "ghost"
167
+ }), "text-fd-muted-foreground"),
168
+ onClick,
169
+ children: isChecked ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(LinkIcon, {})
170
+ })
171
+ ]
172
+ }), /* @__PURE__ */ jsx(SchemaDescription, {
173
+ schema,
174
+ className: "pb-0"
175
+ })]
176
+ });
177
+ }
178
+ function PathItemBody({ pathIndex, asSchema, tabDepth = 0, objectSearchOverrides }) {
179
+ const { path, setPath, generated: { refs } } = useStates();
180
+ const schema = asSchema ?? refs[path[pathIndex].$ref];
39
181
  if ((schema.type === "or" || schema.type === "and") && schema.items.length > 0) {
40
- if (variant === "expand") return /* @__PURE__ */ jsxs(Tabs, {
41
- defaultValue: schema.items[0].$type,
42
- children: [/* @__PURE__ */ jsx(TabsList, { children: schema.items.map((item) => /* @__PURE__ */ jsx(TabsTrigger, {
43
- value: item.$type,
182
+ const value = path[pathIndex].tabValues?.[tabDepth] ?? schema.items[0].$type;
183
+ const items = schema.items.map((item) => ({
184
+ label: /* @__PURE__ */ jsx("code", {
185
+ className: "text-xs font-medium",
44
186
  children: item.name
45
- }, item.$type)) }), schema.items.map((item) => /* @__PURE__ */ jsx(TabsContent, {
46
- value: item.$type,
47
- className: "pt-2 pb-0",
48
- children: /* @__PURE__ */ jsx(SchemaUIProperty, {
49
- ...item,
50
- variant: "expand"
51
- })
52
- }, item.$type))]
53
- });
54
- type = renderRef({
55
- pathName: name,
56
- $ref: $type,
57
- text: schema.aliasName
58
- });
59
- } else if (schema.type === "object" && schema.props.length > 0) {
60
- if (variant === "expand") return /* @__PURE__ */ jsx(ObjectSearch, {
61
- properties: schema.props,
62
- ...objectSearchOverrides
63
- });
64
- type = renderRef({
65
- pathName: name,
66
- $ref: $type,
67
- text: schema.aliasName
68
- });
69
- } else if (schema.type === "array") {
70
- if (variant === "expand") return /* @__PURE__ */ jsx(ArrayItemCollapsible, { schema });
71
- type = renderRef({
72
- pathName: name,
73
- $ref: $type,
74
- text: schema.aliasName
187
+ }),
188
+ value: item.$type
189
+ }));
190
+ return /* @__PURE__ */ jsxs(Select, {
191
+ value,
192
+ onValueChange: (v) => {
193
+ const next = [...path];
194
+ (next[pathIndex].tabValues ??= []).splice(tabDepth, 1, v);
195
+ setPath(next);
196
+ },
197
+ children: [/* @__PURE__ */ jsxs("div", {
198
+ className: "flex flex-row my-2 gap-2 items-center",
199
+ children: [
200
+ /* @__PURE__ */ jsx(SchemaDescription, {
201
+ schema,
202
+ className: "flex-1 py-0"
203
+ }),
204
+ /* @__PURE__ */ jsx(SelectTrigger, {
205
+ className: "not-prose w-fit min-w-0 mb-auto *:min-w-0",
206
+ children: /* @__PURE__ */ jsx(SelectValue, { children: items.find((item) => item.value === value)?.label })
207
+ }),
208
+ /* @__PURE__ */ jsx(SelectContent, { children: items.map(({ label, value }) => /* @__PURE__ */ jsx(SelectItem, {
209
+ value,
210
+ children: label
211
+ }, value)) })
212
+ ]
213
+ }), /* @__PURE__ */ jsx(PathItemBody, {
214
+ asSchema: refs[value],
215
+ pathIndex,
216
+ tabDepth: tabDepth + 1
217
+ })]
75
218
  });
76
219
  }
77
- const child = /* @__PURE__ */ jsxs(Fragment$1, { children: [schema.description, schema.infoTags && schema.infoTags.length > 0 && /* @__PURE__ */ jsx("div", {
78
- className: "flex flex-row gap-2 flex-wrap my-2 not-prose empty:hidden",
79
- children: schema.infoTags.map((tag, i) => /* @__PURE__ */ jsx(InfoTag, { tag }, i))
80
- })] });
81
- if (variant === "expand") return child;
82
- return /* @__PURE__ */ jsx(Property, {
83
- name,
84
- type,
85
- deprecated: schema.deprecated,
86
- ...overrides,
87
- children: child
88
- });
89
- }
90
- function ArrayItemCollapsible({ schema }) {
91
- const [open, setOpen] = useState(false);
92
- const t = useTranslations();
93
- return /* @__PURE__ */ jsxs(Collapsible, {
94
- className: "my-2",
95
- open,
96
- onOpenChange: setOpen,
97
- children: [/* @__PURE__ */ jsxs(CollapsibleTrigger, {
98
- className: cn(buttonVariants({
99
- color: "secondary",
100
- size: "sm"
101
- }), "group px-3 py-2 data-[state=open]:rounded-b-none"),
102
- children: [open ? t.schemaHideArray : t.schemaShowArray, /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 text-fd-muted-foreground group-data-[state=open]:rotate-180" })]
103
- }), /* @__PURE__ */ jsx(CollapsibleContent, {
104
- className: "-mt-px bg-fd-card px-3 rounded-lg rounded-tl-none border shadow-sm",
105
- children: /* @__PURE__ */ jsx(SchemaUIProperty, {
106
- name: "",
107
- $type: schema.item.$type,
108
- variant: "expand"
109
- })
110
- })]
220
+ if (schema.type === "object" && schema.props.length > 0) return /* @__PURE__ */ jsx(ObjectSearch, {
221
+ properties: schema.props,
222
+ ...objectSearchOverrides,
223
+ children: /* @__PURE__ */ jsx(SchemaDescription, { schema })
111
224
  });
225
+ if (schema.type === "array") return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(SchemaDescription, { schema }), /* @__PURE__ */ jsx(ObjectProperty, {
226
+ name: "[index: integer]",
227
+ $type: schema.item.$type
228
+ })] });
229
+ return /* @__PURE__ */ jsx(SchemaDescription, { schema });
112
230
  }
113
- function ObjectSearch({ properties, container, open }) {
231
+ function ObjectSearch({ variant = "secondary", properties, inputContainer, children }) {
232
+ const { path, setPath } = useStates();
114
233
  const [search, setSearch] = useState("");
115
234
  const deferredValue = useDeferredValue(search);
116
235
  const firstItemRef = useRef(null);
@@ -120,27 +239,35 @@ function ObjectSearch({ properties, container, open }) {
120
239
  prevProperties.current = properties;
121
240
  setSearch("");
122
241
  }
123
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
124
- ...container,
125
- className: cn("flex items-center border my-2 rounded-md bg-fd-secondary text-fd-secondary-foreground transition-colors shadow-sm focus-within:ring-2 focus-within:ring-fd-ring", container?.className),
126
- children: [/* @__PURE__ */ jsx(FilterIcon, { className: "text-fd-muted-foreground ms-2 size-3.5" }), /* @__PURE__ */ jsx("input", {
127
- value: search,
128
- "data-object-search-input": "",
129
- onChange: (e) => setSearch(e.target.value),
130
- placeholder: t.schemaFilterPropertiesPlaceholder,
131
- className: "text-sm ps-2 py-2 flex-1 outline-none placeholder:text-fd-muted-foreground",
132
- onKeyDown: (e) => {
133
- if (e.key === "Enter" && open) {
134
- if (firstItemRef.current) open(firstItemRef.current);
135
- e.preventDefault();
242
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
243
+ /* @__PURE__ */ jsxs("div", {
244
+ ...inputContainer,
245
+ className: cn("flex items-center bg-fd-secondary text-fd-secondary-foreground transition-colors", variant === "ghost" && "border-b focus-within:[&_svg]:text-fd-primary", variant === "secondary" && "border bg-fd-secondary rounded-md shadow-sm focus-within:ring-2 focus-within:ring-fd-ring", inputContainer?.className),
246
+ children: [/* @__PURE__ */ jsx(FilterIcon, { className: "text-fd-muted-foreground ms-2 size-3.5 transition-colors" }), /* @__PURE__ */ jsx("input", {
247
+ value: search,
248
+ "data-object-search-input": "",
249
+ onChange: (e) => setSearch(e.target.value),
250
+ placeholder: t.schemaFilterPropertiesPlaceholder,
251
+ className: "peer text-sm ps-2 py-2 flex-1 outline-none placeholder:text-fd-muted-foreground",
252
+ onKeyDown: (e) => {
253
+ if (e.key === "Enter") {
254
+ const item = firstItemRef.current;
255
+ if (item) setPath([...path, {
256
+ name: item.name,
257
+ $ref: item.$type
258
+ }]);
259
+ e.preventDefault();
260
+ }
136
261
  }
137
- }
138
- })]
139
- }), /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(ObjectSearchContent, {
140
- search: deferredValue,
141
- properties,
142
- firstItemRef
143
- }) })] });
262
+ })]
263
+ }),
264
+ children,
265
+ /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(ObjectSearchContent, {
266
+ search: deferredValue,
267
+ properties,
268
+ firstItemRef
269
+ }) })
270
+ ] });
144
271
  }
145
272
  function ObjectSearchContent({ search: rawSearch, firstItemRef, properties }) {
146
273
  const t = useTranslations();
@@ -148,9 +275,9 @@ function ObjectSearchContent({ search: rawSearch, firstItemRef, properties }) {
148
275
  const search = rawSearch.trim().toLowerCase();
149
276
  return search.length > 0 ? properties.filter((prop) => prop.name.toLowerCase().includes(search)) : properties;
150
277
  }, [properties, rawSearch]);
151
- firstItemRef.current = filtered.length > 0 ? filtered[0] : null;
278
+ firstItemRef.current = filtered[0] ?? null;
152
279
  if (filtered.length === 0) return /* @__PURE__ */ jsxs("p", {
153
- className: "text-fd-muted-foreground text-sm px-2",
280
+ className: "text-fd-muted-foreground text-sm",
154
281
  children: [
155
282
  t.schemaFilterPropertiesEmpty,
156
283
  " ",
@@ -160,200 +287,123 @@ function ObjectSearchContent({ search: rawSearch, firstItemRef, properties }) {
160
287
  })
161
288
  ]
162
289
  });
163
- return filtered.map((prop) => /* @__PURE__ */ jsx(SchemaUIProperty, {
290
+ return filtered.map((prop) => /* @__PURE__ */ jsx(ObjectProperty, {
164
291
  name: prop.name,
165
292
  $type: prop.$type,
166
- overrides: { required: prop.required }
293
+ required: prop.required
167
294
  }, prop.name));
168
295
  }
169
296
  function InfoTag({ tag }) {
170
297
  const ref = useRef(null);
171
- const [isTruncated, setTruncated] = useState(false);
172
298
  const [open, setOpen] = useState(false);
173
- useEffect(() => {
174
- const element = ref.current;
175
- if (!element) return;
176
- setTruncated(element.scrollWidth !== element.offsetWidth);
177
- }, []);
178
- return /* @__PURE__ */ jsxs("div", {
179
- className: "flex flex-row items-start gap-2 bg-fd-secondary border rounded-lg text-xs p-1.5 shadow-md max-w-full",
180
- children: [
181
- /* @__PURE__ */ jsx("span", {
182
- className: "font-medium",
183
- children: tag.label
184
- }),
185
- /* @__PURE__ */ jsx("code", {
186
- ref,
187
- className: cn("min-w-0 flex-1 text-fd-muted-foreground", open ? "wrap-break-word" : "truncate"),
188
- children: tag.value
189
- }),
190
- isTruncated && /* @__PURE__ */ jsx("button", {
191
- className: cn(buttonVariants({
192
- size: "icon-xs",
193
- variant: "ghost"
194
- })),
195
- onClick: () => setOpen((prev) => !prev),
196
- children: /* @__PURE__ */ jsx(ChevronDown, {})
197
- })
198
- ]
299
+ return /* @__PURE__ */ jsxs("button", {
300
+ className: "inline-flex text-start items-start gap-2 bg-fd-secondary border rounded-lg text-xs p-1.5 shadow-md max-w-full",
301
+ onClick: () => setOpen((prev) => !prev),
302
+ children: [/* @__PURE__ */ jsx("span", {
303
+ className: "font-medium",
304
+ children: tag.label
305
+ }), /* @__PURE__ */ jsx("code", {
306
+ ref,
307
+ className: cn("min-w-0 flex-1 text-fd-muted-foreground", open ? "wrap-break-word" : "truncate"),
308
+ children: tag.value
309
+ })]
199
310
  });
200
311
  }
201
- function SchemaUIPopover({ containerRef, initialPath }) {
202
- const [path, setPath] = useState(initialPath);
203
- useLayoutEffect(() => {
204
- const last = path[0];
205
- const element = containerRef.current;
206
- if (!element || !last || !element.parentElement) return;
207
- element.parentElement.scrollTop = last.scrollTop ?? 0;
208
- return () => {
209
- if (element.parentElement) last.scrollTop = element.parentElement.scrollTop;
210
- };
211
- }, [containerRef, path]);
212
- const context = useMemo(() => ({ renderTrigger: ({ $ref, pathName, children }) => /* @__PURE__ */ jsx("button", {
213
- className: cn(typeVariants({ variant: "trigger" })),
214
- onClick: () => setPath((path) => [...path, {
215
- name: pathName,
216
- $ref
217
- }]),
218
- children
219
- }) }), []);
220
- const currentRef = path.findLast((item) => item.$ref !== void 0);
221
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
222
- className: "sticky top-0 -mx-2 flex flex-row flex-wrap items-center text-sm font-medium font-mono bg-fd-popover px-2 h-8 border-b",
223
- children: path.map((item, i) => {
224
- const className = cn(path.some((other, j) => j < i && other.$ref === item.$ref) && "text-orange-400", item.$ref && "hover:underline hover:text-fd-accent-foreground");
225
- const node = item.$ref ? /* @__PURE__ */ jsx("button", {
226
- onClick: () => setPath((path) => path.slice(0, i + 1)),
227
- className,
228
- children: item.name
229
- }) : /* @__PURE__ */ jsx("span", {
230
- className,
231
- children: item.name
232
- });
233
- return /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && ".", node] }, i);
234
- })
235
- }), /* @__PURE__ */ jsx(PopoverContext, {
236
- value: context,
237
- children: currentRef?.$ref && /* @__PURE__ */ jsx(SchemaUIProperty, {
238
- name: "",
239
- $type: currentRef.$ref,
240
- variant: "expand",
312
+ function SchemaUIPopover() {
313
+ const states = useStates();
314
+ const { path, setPath } = states;
315
+ return /* @__PURE__ */ jsxs(Context, {
316
+ value: useMemo(() => ({
317
+ ...states,
318
+ renderTypeInfoTrigger: ({ $ref, pathName, children }) => /* @__PURE__ */ jsx("button", {
319
+ className: cn(typeVariants({ variant: "trigger" })),
320
+ onClick: () => setPath([...path, {
321
+ name: pathName,
322
+ $ref
323
+ }]),
324
+ children
325
+ })
326
+ }), [states]),
327
+ children: [/* @__PURE__ */ jsx("div", {
328
+ className: "sticky top-0 -mx-2 flex flex-row overflow-x-auto overflow-y-hidden items-center text-sm font-medium font-mono bg-fd-popover px-2 h-10 border-b z-20",
329
+ children: path.map((item, i) => {
330
+ if (i === 0) return;
331
+ const isDuplicated = path.some((other, j) => j !== i && other.$ref === item.$ref);
332
+ let text;
333
+ const indexItemMatch = /^\[(\w+): (\w+)]$/.exec(item.name);
334
+ if (indexItemMatch) text = `[${indexItemMatch[1]}]`;
335
+ else if (i > 1) text = `.${item.name}`;
336
+ else text = item.name;
337
+ return /* @__PURE__ */ jsx("button", {
338
+ onClick: () => setPath(path.slice(0, i + 1)),
339
+ className: cn("hover:underline hover:text-fd-accent-foreground", isDuplicated && "text-orange-400"),
340
+ children: text
341
+ }, i);
342
+ })
343
+ }), /* @__PURE__ */ jsx(PathItemBody, {
344
+ pathIndex: path.length - 1,
241
345
  objectSearchOverrides: {
242
- container: { className: "sticky top-10" },
243
- open(item) {
244
- setPath((path) => [...path, {
245
- name: item.name,
246
- $ref: item.$type
247
- }]);
248
- }
346
+ variant: "ghost",
347
+ inputContainer: { className: "sticky top-10 -mx-2" }
249
348
  }
250
- })
251
- })] });
349
+ })]
350
+ });
252
351
  }
253
- function useRenderRef() {
254
- const { refs } = useData();
255
- const { renderTrigger } = usePopover();
256
- return function renderRef({ pathName, $ref, text }) {
257
- const schema = refs[$ref];
258
- if (!isExpandable(schema)) return /* @__PURE__ */ jsx("span", {
259
- className: cn(typeVariants()),
260
- children: text
261
- });
262
- if (schema.type === "and" || schema.type === "or") {
263
- const sep = schema.type === "and" ? "&" : "|";
264
- return /* @__PURE__ */ jsx("span", {
265
- className: cn(typeVariants(), "flex flex-row gap-2 items-center flex-wrap"),
266
- children: schema.items.map((item, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && /* @__PURE__ */ jsx("span", { children: sep }), renderRef({
267
- pathName,
268
- text: item.name,
269
- $ref: item.$type
270
- })] }, item.$type))
271
- });
272
- }
273
- if (schema.type === "array") return /* @__PURE__ */ jsxs("span", {
274
- className: cn(typeVariants(), "flex flex-row items-center flex-wrap"),
275
- children: [
276
- "array<",
277
- renderRef({
278
- pathName: /* @__PURE__ */ jsxs(Fragment$1, { children: [pathName, "[]"] }),
279
- text: refs[schema.item.$type].aliasName,
280
- $ref: schema.item.$type
281
- }),
282
- ">"
283
- ]
284
- });
285
- return renderTrigger({
286
- $ref,
287
- pathName,
288
- children: text
352
+ function TypeInfoTrigger({ pathName, $ref, children }) {
353
+ const { generated: { refs }, renderTypeInfoTrigger } = useStates();
354
+ const schema = refs[$ref];
355
+ if (schema.type === "primitive" && !schema.description && (!schema.infoTags || schema.infoTags.length === 0)) return /* @__PURE__ */ jsx("span", {
356
+ className: cn(typeVariants()),
357
+ children
358
+ });
359
+ if (schema.type === "and" || schema.type === "or") {
360
+ const sep = schema.type === "and" ? "&" : "|";
361
+ return /* @__PURE__ */ jsx("span", {
362
+ className: cn(typeVariants(), "flex flex-row gap-2 items-center flex-wrap"),
363
+ children: schema.items.map((item, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && /* @__PURE__ */ jsx("span", { children: sep }), /* @__PURE__ */ jsx(TypeInfoTrigger, {
364
+ pathName,
365
+ $ref: item.$type,
366
+ children: item.name
367
+ })] }, item.$type))
289
368
  });
290
- };
291
- }
292
- function RootPopoverTrigger({ $ref, pathName, children }) {
293
- const ref = useRef(null);
294
- const refCallback = useCallback((element) => {
295
- ref.current = element;
296
- if (!element || element.style.getPropertyValue("--initial-height")) return;
297
- element.style.setProperty("--initial-height", `${element.clientHeight + 2}px`);
298
- }, []);
299
- return /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
300
- className: cn(typeVariants({ variant: "trigger" })),
369
+ }
370
+ if (schema.type === "array") return /* @__PURE__ */ jsxs("span", {
371
+ className: cn(typeVariants(), "flex flex-row items-center flex-wrap"),
372
+ children: [
373
+ "array<",
374
+ /* @__PURE__ */ jsx(TypeInfoTrigger, {
375
+ pathName: `${pathName}[]`,
376
+ $ref: schema.item.$type,
377
+ children: refs[schema.item.$type].aliasName
378
+ }),
379
+ ">"
380
+ ]
381
+ });
382
+ return renderTypeInfoTrigger({
383
+ $ref,
384
+ pathName,
301
385
  children
302
- }), /* @__PURE__ */ jsx(PopoverContent, {
303
- ref: refCallback,
304
- onOpenAutoFocus: (e) => {
305
- if (!ref.current) return;
306
- const input = ref.current.querySelector("input[data-object-search-input]");
307
- if (!(input instanceof HTMLInputElement)) return;
308
- input.focus({ preventScroll: true });
309
- e.preventDefault();
310
- },
311
- className: "w-[600px] min-h-(--initial-height,0) max-h-[460px] px-2 py-0",
312
- children: /* @__PURE__ */ jsx(SchemaUIPopover, {
313
- containerRef: ref,
314
- initialPath: [{
315
- name: pathName,
316
- $ref
317
- }]
318
- })
319
- })] });
320
- }
321
- function Property({ name, type, required, deprecated, nested = false, className, ...props }) {
322
- const t = useTranslations();
323
- return /* @__PURE__ */ jsxs("div", {
324
- className: cn("text-sm border-t", nested ? "p-3 border-x bg-fd-card last:rounded-b-xl first:rounded-tr-xl last:border-b" : "py-4 first:border-t-0", className),
325
- children: [/* @__PURE__ */ jsxs("div", {
326
- className: "flex flex-wrap items-center gap-3 not-prose",
327
- children: [
328
- /* @__PURE__ */ jsxs("span", {
329
- className: "font-medium font-mono text-fd-primary",
330
- children: [name, required ? /* @__PURE__ */ jsx("span", {
331
- className: "text-red-400",
332
- children: "*"
333
- }) : /* @__PURE__ */ jsx("span", {
334
- className: "text-fd-muted-foreground",
335
- children: "?"
336
- })]
337
- }),
338
- typeof type === "string" ? /* @__PURE__ */ jsx("span", {
339
- className: "text-sm font-mono text-fd-muted-foreground",
340
- children: type
341
- }) : type,
342
- deprecated && /* @__PURE__ */ jsx(Badge, {
343
- color: "yellow",
344
- className: "ms-auto text-xs",
345
- children: t.deprecated
346
- })
347
- ]
348
- }), /* @__PURE__ */ jsx("div", {
349
- className: "prose-no-margin pt-2.5 empty:hidden",
350
- children: props.children
351
- })]
352
386
  });
353
387
  }
354
- function isExpandable(data) {
355
- if (data.type !== "primitive") return true;
356
- return Boolean(data.description || data.infoTags && data.infoTags.length > 0);
388
+ function encodePath(path) {
389
+ return path.map((item) => [
390
+ item.name,
391
+ item.$ref,
392
+ ...item.tabValues ?? []
393
+ ].join("\0").replaceAll("|", "")).join("|");
394
+ }
395
+ function decodePath(path, highlighted) {
396
+ const out = [];
397
+ for (const part of path.split("|")) {
398
+ const [name, $ref, ...tabValues] = part.split("\0");
399
+ out.push({
400
+ name,
401
+ $ref,
402
+ tabValues
403
+ });
404
+ }
405
+ if (highlighted && out.length > 0) out[out.length - 1].highlighted = highlighted;
406
+ return out;
357
407
  }
358
408
  //#endregion
359
409
  export { SchemaUI };