fumadocs-openapi 10.9.1 → 10.10.1

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.
@@ -7,11 +7,13 @@ import { cn } from "../../utils/cn.js";
7
7
  import { Badge, MethodLabel } from "../components/method-label.js";
8
8
  import { CopyTypeScriptPanel, OperationProvider } from "./client.js";
9
9
  import { Schema } from "../schema/index.js";
10
+ import { AnchorSection } from "../../utils/auto-anchor.client.js";
10
11
  import { AccordionContent, AccordionHeader, AccordionItem, AccordionTrigger, Accordions } from "../components/accordion.js";
11
12
  import { UsageTabs } from "./usage-tabs/index.js";
12
13
  import { RequestTabs } from "./request-tabs.js";
13
14
  import { getExampleRequests } from "./get-example-requests.js";
14
15
  import { SelectTab, SelectTabTrigger, SelectTabs } from "../components/select-tab.js";
16
+ import { Heading } from "../components/heading.js";
15
17
  import { Fragment, use, useMemo } from "react";
16
18
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
19
  import { Callout } from "fumadocs-ui/components/callout";
@@ -40,7 +42,12 @@ function Operation({ type = "operation", path, method, ctx, showTitle, showDescr
40
42
  const title = method.summary || (method.operationId ? idToTitle(method.operationId) : path);
41
43
  headNode = /* @__PURE__ */ jsxs("div", {
42
44
  className: "flex gap-2 items-center justify-between",
43
- children: [ctx.renderHeading(headingLevel, title, { className: "my-0!" }), method.deprecated && /* @__PURE__ */ jsx(Badge, {
45
+ children: [/* @__PURE__ */ jsx(Heading, {
46
+ id: title,
47
+ depth: headingLevel,
48
+ className: "my-0!",
49
+ children: title
50
+ }), method.deprecated && /* @__PURE__ */ jsx(Badge, {
44
51
  color: "yellow",
45
52
  className: "text-xs not-prose",
46
53
  children: /* @__PURE__ */ jsx(I18nLabel, { label: "deprecated" })
@@ -53,22 +60,24 @@ function Operation({ type = "operation", path, method, ctx, showTitle, showDescr
53
60
  className: "mt-0!"
54
61
  });
55
62
  const contentTypes = body?.content ? Object.entries(body.content) : null;
56
- if (body && contentTypes && contentTypes.length > 0) {
57
- const items = contentTypes.map(([key]) => ({
63
+ if (contentTypes && contentTypes.length > 0) {
64
+ const items = contentTypes.map(([type]) => ({
58
65
  label: /* @__PURE__ */ jsx("code", {
59
66
  className: "text-xs",
60
- children: key
67
+ children: type
61
68
  }),
62
- value: key
69
+ value: type
63
70
  }));
64
71
  bodyNode = /* @__PURE__ */ jsxs(SelectTabs, {
65
72
  defaultValue: items[0].value,
66
73
  children: [
67
74
  /* @__PURE__ */ jsxs("div", {
68
75
  className: "flex gap-2 items-center justify-between mt-10",
69
- children: [ctx.renderHeading(headingLevel, /* @__PURE__ */ jsx(I18nLabel, { label: "titleRequestBody" }), {
76
+ children: [/* @__PURE__ */ jsx(Heading, {
70
77
  id: "request-body",
71
- className: "my-0!"
78
+ depth: headingLevel,
79
+ className: "my-0!",
80
+ children: /* @__PURE__ */ jsx(I18nLabel, { label: "titleRequestBody" })
72
81
  }), contentTypes.length > 1 ? /* @__PURE__ */ jsx(SelectTabTrigger, {
73
82
  items,
74
83
  className: "font-medium"
@@ -81,6 +90,7 @@ function Operation({ type = "operation", path, method, ctx, showTitle, showDescr
81
90
  contentTypes.map(([type, content]) => {
82
91
  if (!isMediaTypeSupported(type, ctx.mediaAdapters)) throw new Error(`Media type ${type} is not supported (in ${path})`);
83
92
  return /* @__PURE__ */ jsx(SelectTab, {
93
+ anchorSegments: ["request-body", type],
84
94
  value: type,
85
95
  children: /* @__PURE__ */ jsx(RequestBodyContentItem, {
86
96
  content,
@@ -94,7 +104,11 @@ function Operation({ type = "operation", path, method, ctx, showTitle, showDescr
94
104
  }
95
105
  if (method.responses && ctx.showResponseSchema !== false) {
96
106
  const statuses = Object.keys(method.responses);
97
- responseNode = /* @__PURE__ */ jsxs(Fragment$1, { children: [ctx.renderHeading(headingLevel, /* @__PURE__ */ jsx(I18nLabel, { label: "titleResponseBody" }), { id: "response-body" }), /* @__PURE__ */ jsx(Accordions, {
107
+ responseNode = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Heading, {
108
+ id: "response-body",
109
+ depth: headingLevel,
110
+ children: /* @__PURE__ */ jsx(I18nLabel, { label: "titleResponseBody" })
111
+ }), /* @__PURE__ */ jsx(Accordions, {
98
112
  type: "multiple",
99
113
  children: statuses.map((status) => /* @__PURE__ */ jsx(ResponseAccordion, {
100
114
  status,
@@ -106,22 +120,29 @@ function Operation({ type = "operation", path, method, ctx, showTitle, showDescr
106
120
  const parameterNode = paramTypeKeys.map((type) => {
107
121
  const params = method.parameters?.filter((param) => param.in === type);
108
122
  if (!params || params.length === 0) return;
109
- return /* @__PURE__ */ jsxs(Fragment, { children: [ctx.renderHeading(headingLevel, /* @__PURE__ */ jsx(I18nLabel, { label: `${type}Parameters` }), { id: `parameters-${type}` }), /* @__PURE__ */ jsx("div", {
110
- className: "flex flex-col",
111
- children: params.map((param) => param.schema != null && /* @__PURE__ */ jsx(Schema, {
112
- client: {
113
- name: param.name,
114
- required: param.required
115
- },
116
- root: typeof param.schema === "object" ? {
117
- ...param.schema,
118
- description: param.description ?? param.schema?.description,
119
- deprecated: (param.deprecated ?? false) || (param.schema?.deprecated ?? false)
120
- } : param.schema,
121
- readOnly: method.method === "get",
122
- writeOnly: method.method !== "get",
123
- ctx
124
- }, param.name))
123
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Heading, {
124
+ id: `parameters-${type}`,
125
+ depth: headingLevel,
126
+ children: /* @__PURE__ */ jsx(I18nLabel, { label: `${type}Parameters` })
127
+ }), /* @__PURE__ */ jsx(AnchorSection, {
128
+ segments: ["parameters", type],
129
+ children: /* @__PURE__ */ jsx("div", {
130
+ className: "flex flex-col",
131
+ children: params.map((param) => param.schema != null && /* @__PURE__ */ jsx(Schema, {
132
+ client: {
133
+ name: param.name,
134
+ required: param.required
135
+ },
136
+ root: typeof param.schema === "object" ? {
137
+ ...param.schema,
138
+ description: param.description ?? param.schema?.description,
139
+ deprecated: (param.deprecated ?? false) || (param.schema?.deprecated ?? false)
140
+ } : param.schema,
141
+ readOnly: method.method === "get",
142
+ writeOnly: method.method !== "get",
143
+ ctx
144
+ }, param.name))
145
+ })
125
146
  })] }, type);
126
147
  });
127
148
  const securities = (method.security ?? dereferenced.security ?? []).filter((v) => Object.keys(v).length > 0);
@@ -153,9 +174,11 @@ function Operation({ type = "operation", path, method, ctx, showTitle, showDescr
153
174
  defaultValue: items[0].value,
154
175
  children: [/* @__PURE__ */ jsxs("div", {
155
176
  className: "flex items-start justify-between gap-2 mt-10",
156
- children: [ctx.renderHeading(headingLevel, /* @__PURE__ */ jsx(I18nLabel, { label: "authorization" }), {
177
+ children: [/* @__PURE__ */ jsx(Heading, {
157
178
  id: "authorization",
158
- className: "my-0!"
179
+ depth: headingLevel,
180
+ className: "my-0!",
181
+ children: /* @__PURE__ */ jsx(I18nLabel, { label: "authorization" })
159
182
  }), items.length > 1 ? /* @__PURE__ */ jsx(SelectTabTrigger, { items }) : /* @__PURE__ */ jsx("div", {
160
183
  className: "not-prose",
161
184
  children: items[0].label
@@ -174,39 +197,55 @@ function Operation({ type = "operation", path, method, ctx, showTitle, showDescr
174
197
  }, i))]
175
198
  });
176
199
  }
177
- const callbacks = method.callbacks ? Object.entries(method.callbacks) : null;
178
- if (callbacks && callbacks.length > 0) {
179
- const items = callbacks.map(([key]) => ({
180
- label: /* @__PURE__ */ jsx("code", {
181
- className: "text-xs",
182
- children: key
183
- }),
184
- value: key
185
- }));
186
- callbacksNode = /* @__PURE__ */ jsxs(SelectTabs, {
187
- defaultValue: items[0].value,
188
- children: [/* @__PURE__ */ jsxs("div", {
189
- className: "flex justify-between gap-2 items-end mt-10",
190
- children: [ctx.renderHeading(headingLevel, /* @__PURE__ */ jsx(I18nLabel, { label: "titleCallbacks" }), {
191
- id: "callbacks",
192
- className: "my-0!"
193
- }), callbacks.length > 1 ? /* @__PURE__ */ jsx(SelectTabTrigger, {
194
- items,
195
- className: "font-medium"
196
- }) : /* @__PURE__ */ jsx("p", {
197
- className: "text-fd-muted-foreground not-prose",
198
- children: items[0].label
199
- })]
200
- }), callbacks.map(([name, callback]) => /* @__PURE__ */ jsx(SelectTab, {
201
- value: name,
202
- children: /* @__PURE__ */ jsx(WebhookCallback, {
203
- callback,
204
- ctx,
205
- headingLevel
206
- })
207
- }, name))]
200
+ const webhookCallbacks = [];
201
+ for (const [name, callbacks] of Object.entries(method.callbacks ?? {})) for (const [path, callback] of Object.entries(callbacks)) for (const method of methodKeys) {
202
+ if (!callback[method]) continue;
203
+ webhookCallbacks.push({
204
+ name,
205
+ path,
206
+ method,
207
+ callback,
208
+ operation: callback[method]
208
209
  });
209
210
  }
211
+ if (webhookCallbacks.length > 0) callbacksNode = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Heading, {
212
+ id: "callbacks",
213
+ depth: headingLevel,
214
+ children: /* @__PURE__ */ jsx(I18nLabel, { label: "titleCallbacks" })
215
+ }), /* @__PURE__ */ jsx(Accordions, {
216
+ type: "multiple",
217
+ children: webhookCallbacks.map((item, i) => /* @__PURE__ */ jsxs(AccordionItem, {
218
+ value: `${item.name}\0${item.path}\0${item.method}`,
219
+ anchorSegments: [
220
+ "callbacks",
221
+ item.name,
222
+ item.path,
223
+ item.method
224
+ ],
225
+ children: [/* @__PURE__ */ jsxs(AccordionHeader, {
226
+ className: "flex-col gap-3",
227
+ children: [/* @__PURE__ */ jsx(AccordionTrigger, {
228
+ className: "font-mono",
229
+ children: item.name
230
+ }), /* @__PURE__ */ jsxs("div", {
231
+ className: "flex items-center gap-2 text-xs ps-4.5",
232
+ children: [/* @__PURE__ */ jsx(MethodLabel, { children: item.method }), /* @__PURE__ */ jsx("code", {
233
+ className: "text-fd-muted-foreground",
234
+ children: item.path
235
+ })]
236
+ })]
237
+ }), /* @__PURE__ */ jsx(AccordionContent, { children: /* @__PURE__ */ jsx("div", {
238
+ className: "border p-3 ps-4.5 mb-2 @container prose-no-margin rounded-xl",
239
+ children: /* @__PURE__ */ jsx(Operation, {
240
+ type: "webhook",
241
+ path,
242
+ headingLevel: headingLevel + 1,
243
+ method: createMethod(item.method, item.callback, item.operation),
244
+ ctx
245
+ })
246
+ }) })]
247
+ }, i))
248
+ })] });
210
249
  let { renderOperationLayout, renderWebhookLayout } = ctx.content ?? {};
211
250
  if (type === "operation") {
212
251
  renderOperationLayout ??= (slots) => {
@@ -273,7 +312,7 @@ function Operation({ type = "operation", path, method, ctx, showTitle, showDescr
273
312
  renderWebhookLayout ??= (slots) => /* @__PURE__ */ jsxs("div", {
274
313
  className: "flex flex-col-reverse gap-x-6 gap-y-4 @4xl:flex-row @4xl:items-start",
275
314
  children: [/* @__PURE__ */ jsxs("div", {
276
- className: "min-w-0 flex-1",
315
+ className: "min-w-0 flex-1 prose-no-margin",
277
316
  children: [
278
317
  slots.header,
279
318
  slots.description,
@@ -323,7 +362,7 @@ function RequestBodyContentItem({ content, method, ctx }) {
323
362
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [ts && /* @__PURE__ */ jsx(CopyTypeScriptPanel, {
324
363
  name: "request body",
325
364
  code: ts,
326
- className: "mt-4"
365
+ className: "my-4 last:mb-0"
327
366
  }), content.schema && /* @__PURE__ */ jsx(Schema, {
328
367
  client: {
329
368
  name: "body",
@@ -339,48 +378,44 @@ function RequestBodyContentItem({ content, method, ctx }) {
339
378
  function ResponseAccordion({ status, operation, ctx }) {
340
379
  const response = operation.responses[status];
341
380
  const contentTypes = response.content ? Object.entries(response.content) : [];
342
- let wrapper = (children) => children;
343
- let selectorNode = null;
344
- if (contentTypes.length > 0) {
345
- const items = contentTypes.map(([key]) => ({
346
- label: /* @__PURE__ */ jsx("code", {
347
- className: "text-xs",
348
- children: key
349
- }),
350
- value: key
351
- }));
352
- selectorNode = items.length === 1 ? /* @__PURE__ */ jsx("p", {
353
- className: "text-fd-muted-foreground not-prose",
354
- children: items[0].label
355
- }) : /* @__PURE__ */ jsx(SelectTabTrigger, { items });
356
- wrapper = (children) => /* @__PURE__ */ jsx(SelectTabs, {
357
- defaultValue: items[0].value,
358
- children
359
- });
360
- }
361
- return wrapper(/* @__PURE__ */ jsxs(AccordionItem, {
362
- value: status,
363
- children: [/* @__PURE__ */ jsxs(AccordionHeader, { children: [/* @__PURE__ */ jsx(AccordionTrigger, {
364
- className: "font-mono",
365
- children: status
366
- }), selectorNode] }), /* @__PURE__ */ jsxs(AccordionContent, {
367
- className: "ps-4.5",
368
- children: [response.description && /* @__PURE__ */ jsx("div", {
369
- className: "prose-no-margin mb-2",
370
- children: ctx.renderMarkdown(response.description)
371
- }), contentTypes.map(([type, item]) => /* @__PURE__ */ jsx(SelectTab, {
372
- value: type,
373
- className: "mb-2",
374
- children: /* @__PURE__ */ jsx(RepsonseAccordionItem, {
375
- type,
376
- status,
377
- item,
378
- operation,
379
- ctx
380
- })
381
- }, type))]
382
- })]
381
+ const items = contentTypes.map(([key]) => ({
382
+ label: /* @__PURE__ */ jsx("code", {
383
+ className: "text-xs",
384
+ children: key
385
+ }),
386
+ value: key
383
387
  }));
388
+ return /* @__PURE__ */ jsx(AccordionItem, {
389
+ value: status,
390
+ anchorSegments: ["response", status],
391
+ className: "data-[state=open]:border-b-0",
392
+ children: /* @__PURE__ */ jsxs(SelectTabs, {
393
+ defaultValue: items[0]?.value,
394
+ children: [/* @__PURE__ */ jsxs(AccordionHeader, { children: [/* @__PURE__ */ jsx(AccordionTrigger, {
395
+ className: "font-mono",
396
+ children: status
397
+ }), items.length === 1 ? /* @__PURE__ */ jsx("p", {
398
+ className: "text-fd-muted-foreground not-prose",
399
+ children: items[0].label
400
+ }) : items.length > 0 && /* @__PURE__ */ jsx(SelectTabTrigger, { items })] }), /* @__PURE__ */ jsxs(AccordionContent, {
401
+ className: "ps-4.5 pe-3 border rounded-xl",
402
+ children: [response.description && /* @__PURE__ */ jsx("div", {
403
+ className: "prose-no-margin mt-3 mb-2",
404
+ children: ctx.renderMarkdown(response.description)
405
+ }), contentTypes.map(([type, item]) => /* @__PURE__ */ jsx(SelectTab, {
406
+ value: type,
407
+ anchorSegments: [type],
408
+ children: /* @__PURE__ */ jsx(RepsonseAccordionItem, {
409
+ type,
410
+ status,
411
+ item,
412
+ operation,
413
+ ctx
414
+ })
415
+ }, type))]
416
+ })]
417
+ })
418
+ });
384
419
  }
385
420
  function RepsonseAccordionItem({ type, status, operation, item: { schema }, ctx }) {
386
421
  let ts = useMemo(() => {
@@ -405,49 +440,18 @@ function RepsonseAccordionItem({ type, status, operation, item: { schema }, ctx
405
440
  if (ts instanceof Promise) ts = use(ts);
406
441
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [ts && /* @__PURE__ */ jsx(CopyTypeScriptPanel, {
407
442
  name: "response body",
408
- code: ts
409
- }), schema && /* @__PURE__ */ jsx("div", {
410
- className: "border px-3 py-2 rounded-lg",
411
- children: /* @__PURE__ */ jsx(Schema, {
412
- client: {
413
- name: "response",
414
- as: "body"
415
- },
416
- root: schema,
417
- readOnly: true,
418
- ctx
419
- })
443
+ code: ts,
444
+ className: "mb-2"
445
+ }), schema && /* @__PURE__ */ jsx(Schema, {
446
+ client: {
447
+ name: "response",
448
+ as: "body"
449
+ },
450
+ root: schema,
451
+ readOnly: true,
452
+ ctx
420
453
  })] });
421
454
  }
422
- function WebhookCallback({ callback, ctx, headingLevel }) {
423
- return /* @__PURE__ */ jsx(Accordions, {
424
- type: "single",
425
- collapsible: true,
426
- children: Object.entries(callback).map(([path, pathItem]) => {
427
- const pathNodes = methodKeys.map((method) => {
428
- const operation = pathItem[method];
429
- if (!operation) return null;
430
- return /* @__PURE__ */ jsx("div", {
431
- className: "border p-3 my-2 @container prose-no-margin rounded-lg",
432
- children: /* @__PURE__ */ jsx(Operation, {
433
- type: "webhook",
434
- path,
435
- headingLevel: headingLevel + 1,
436
- method: createMethod(method, pathItem, operation),
437
- ctx
438
- })
439
- }, method);
440
- });
441
- return /* @__PURE__ */ jsxs(AccordionItem, {
442
- value: path,
443
- children: [/* @__PURE__ */ jsx(AccordionHeader, { children: /* @__PURE__ */ jsx(AccordionTrigger, {
444
- className: "font-mono",
445
- children: path
446
- }) }), /* @__PURE__ */ jsx(AccordionContent, { children: pathNodes })]
447
- }, path);
448
- })
449
- });
450
- }
451
455
  function AuthScheme({ scheme, scopes, ctx }) {
452
456
  if (scheme.type === "http" || scheme.type === "oauth2") return /* @__PURE__ */ jsxs(AuthProperty, {
453
457
  name: /* @__PURE__ */ jsx(I18nLabel, { label: "authorization" }),
@@ -73,7 +73,10 @@ function RequestTabsItem({ item, ctx }) {
73
73
  value: k,
74
74
  children: [/* @__PURE__ */ jsx(AccordionHeader, { children: /* @__PURE__ */ jsx(AccordionTrigger, { children: v }) }), /* @__PURE__ */ jsx(AccordionContent, {
75
75
  className: "prose-no-margin",
76
- children: ctx.renderCodeBlock("json", JSON.stringify(data, null, 2))
76
+ children: ctx.renderCodeBlock({
77
+ lang: "json",
78
+ code: JSON.stringify(data, null, 2)
79
+ })
77
80
  })]
78
81
  }, k);
79
82
  })
@@ -45,7 +45,10 @@ function ResponseTabs({ operation, ctx }) {
45
45
  }
46
46
  function renderResponseTabsDefault(tabs, ctx) {
47
47
  function renderExampleContent(example) {
48
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [example.description && ctx.renderMarkdown(example.description), ctx.renderCodeBlock("json", JSON.stringify(example.sample, null, 2))] });
48
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [example.description && ctx.renderMarkdown(example.description), ctx.renderCodeBlock({
49
+ lang: "json",
50
+ code: JSON.stringify(example.sample, null, 2)
51
+ })] });
49
52
  }
50
53
  return /* @__PURE__ */ jsx(Tabs, {
51
54
  groupId: "fumadocs_openapi_responses",
@@ -1,5 +1,4 @@
1
1
  import { createCodeUsageGeneratorRegistry } from "../../../requests/generators/index.js";
2
- import { registerDefault } from "../../../requests/generators/all.js";
3
2
  import { ResponseTabs } from "../response-tabs.js";
4
3
  import { useMemo } from "react";
5
4
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -37,13 +36,8 @@ function UsageTabs({ method, ctx }) {
37
36
  });
38
37
  };
39
38
  const registry = useMemo(() => {
40
- let registry;
41
- if (ctx.codeUsages) registry = createCodeUsageGeneratorRegistry(ctx.codeUsages);
42
- else {
43
- registry = createCodeUsageGeneratorRegistry();
44
- registerDefault(registry);
45
- }
46
- for (const gen of ctx.generateCodeSamples?.(method) ?? []) registry.addInline(gen);
39
+ const registry = createCodeUsageGeneratorRegistry(ctx.codeUsages);
40
+ if (ctx.generateCodeSamples) for (const gen of ctx.generateCodeSamples(method)) registry.addInline(gen);
47
41
  if (method["x-codeSamples"]) for (const sample of method["x-codeSamples"]) registry.addInline(sample);
48
42
  return registry;
49
43
  }, [ctx, method]);