fumadocs-openapi 4.0.6 → 4.1.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.
@@ -104,6 +104,17 @@ function ApiProvider({
104
104
  return /* @__PURE__ */ jsx(ApiContext.Provider, { value: { baseUrl, setBaseUrl, highlighter }, children });
105
105
  }
106
106
 
107
+ // src/ui/contexts/schema.tsx
108
+ import { createContext as createContext2, useContext as useContext2 } from "react";
109
+ var SchemaContext = createContext2(
110
+ void 0
111
+ );
112
+ function useSchemaContext() {
113
+ const ctx = useContext2(SchemaContext);
114
+ if (!ctx) throw new Error("Missing provider");
115
+ return ctx;
116
+ }
117
+
107
118
  export {
108
119
  badgeVariants,
109
120
  getBadgeColor,
@@ -111,5 +122,7 @@ export {
111
122
  getDefaultValues,
112
123
  resolve,
113
124
  useApiContext,
114
- ApiProvider
125
+ ApiProvider,
126
+ SchemaContext,
127
+ useSchemaContext
115
128
  };
package/dist/index.d.ts CHANGED
@@ -51,6 +51,25 @@ interface Renderer {
51
51
 
52
52
  declare const defaultRenderer: Renderer;
53
53
 
54
+ interface Endpoint {
55
+ /**
56
+ * URL, including path and query parameters
57
+ */
58
+ url: string;
59
+ method: string;
60
+ body?: unknown;
61
+ responses: Record<string, Response>;
62
+ parameters: Parameter[];
63
+ }
64
+ interface Response {
65
+ schema: OpenAPIV3.SchemaObject;
66
+ }
67
+ interface Parameter {
68
+ name: string;
69
+ in: string;
70
+ schema: OpenAPIV3.SchemaObject;
71
+ }
72
+
54
73
  interface CodeSample {
55
74
  lang: string;
56
75
  label: string;
@@ -67,33 +86,27 @@ interface MethodInformation extends OpenAPIV3.OperationObject {
67
86
  parameters: OpenAPIV3.ParameterObject[];
68
87
  method: string;
69
88
  }
89
+ type Awaitable<T> = T | Promise<T>;
70
90
  interface RenderContext {
71
91
  renderer: Renderer;
72
92
  document: OpenAPIV3.Document;
73
93
  baseUrl: string;
74
- generateCodeSamples?: (endpoint: Endpoint) => CodeSample[];
75
- }
76
-
77
- interface Endpoint {
78
94
  /**
79
- * URL, including path and query parameters
95
+ * Generate TypeScript definitions from response schema.
96
+ *
97
+ * Pass `false` to disable it.
98
+ *
99
+ * @param endpoint - the API endpoint
100
+ * @param code - status code
80
101
  */
81
- url: string;
82
- method: string;
83
- body?: unknown;
84
- responses: Record<string, Response>;
85
- parameters: Parameter[];
86
- }
87
- interface Response {
88
- schema: OpenAPIV3.SchemaObject;
89
- }
90
- interface Parameter {
91
- name: string;
92
- in: string;
93
- schema: OpenAPIV3.SchemaObject;
102
+ generateTypeScriptSchema?: ((endpoint: Endpoint, code: string) => Awaitable<string>) | false;
103
+ /**
104
+ * Generate code samples for endpoint.
105
+ */
106
+ generateCodeSamples?: (endpoint: Endpoint) => Awaitable<CodeSample[]>;
94
107
  }
95
108
 
96
- interface GenerateOptions {
109
+ interface GenerateOptions extends Pick<RenderContext, 'generateCodeSamples' | 'generateTypeScriptSchema'> {
97
110
  /**
98
111
  * The imports of your MDX components.
99
112
  *
@@ -109,10 +122,6 @@ interface GenerateOptions {
109
122
  * A `full: true` property will be added by default.
110
123
  */
111
124
  frontmatter?: (title: string, description: string | undefined) => Record<string, unknown>;
112
- /**
113
- * Generate code samples for endpoint
114
- */
115
- generateCodeSamples?: (endpoint: Endpoint) => CodeSample[];
116
125
  renderer?: Partial<Renderer>;
117
126
  }
118
127
  interface GenerateTagOutput {
package/dist/index.js CHANGED
@@ -621,6 +621,7 @@ async function renderOperation(path, method, ctx, noTitle = false) {
621
621
  level++;
622
622
  }
623
623
  if (method.description) info.push(p(method.description));
624
+ info.push(renderPlayground(path, method, ctx));
624
625
  if (security) {
625
626
  info.push(heading(level, "Authorization"));
626
627
  info.push(getAuthSection(security, ctx));
@@ -676,7 +677,6 @@ async function renderOperation(path, method, ctx, noTitle = false) {
676
677
  info.push(heading(level, group), ...parameters);
677
678
  }
678
679
  info.push(getResponseTable(method));
679
- info.push(renderPlayground(path, method, ctx));
680
680
  const samples = dedupe([
681
681
  {
682
682
  label: "cURL",
@@ -688,7 +688,7 @@ async function renderOperation(path, method, ctx, noTitle = false) {
688
688
  source: getSampleRequest2(endpoint),
689
689
  lang: "js"
690
690
  },
691
- ...ctx.generateCodeSamples?.(endpoint) ?? [],
691
+ ...ctx.generateCodeSamples ? await ctx.generateCodeSamples(endpoint) : [],
692
692
  ...method["x-codeSamples"] ?? []
693
693
  ]);
694
694
  example.push(
@@ -792,21 +792,26 @@ function getResponseTable(operation) {
792
792
  });
793
793
  return table.join("\n");
794
794
  }
795
- async function getResponseTabs(endpoint, operation, { renderer }) {
795
+ async function getResponseTabs(endpoint, operation, { renderer, generateTypeScriptSchema }) {
796
796
  const items = [];
797
797
  const child = [];
798
798
  for (const code of Object.keys(operation.responses)) {
799
799
  const example = getExampleResponse(endpoint, code);
800
- const ts = await getTypescriptSchema(endpoint, code);
800
+ let ts;
801
+ if (generateTypeScriptSchema) {
802
+ ts = await generateTypeScriptSchema(endpoint, code);
803
+ } else if (generateTypeScriptSchema === void 0) {
804
+ ts = await getTypescriptSchema(endpoint, code);
805
+ }
801
806
  const description = code in endpoint.responses ? endpoint.responses[code].schema.description : void 0;
802
- if (example && ts) {
807
+ if (example) {
803
808
  items.push(code);
804
809
  child.push(
805
810
  renderer.Response({ value: code }, [
806
811
  p(description),
807
812
  renderer.ResponseTypes([
808
813
  renderer.ExampleResponse(example),
809
- renderer.TypeScriptResponse(ts)
814
+ ...ts ? [renderer.TypeScriptResponse(ts)] : []
810
815
  ])
811
816
  ])
812
817
  );
@@ -896,6 +901,8 @@ function getContext(document, options) {
896
901
  ...defaultRenderer,
897
902
  ...options.renderer
898
903
  },
904
+ generateTypeScriptSchema: options.generateTypeScriptSchema,
905
+ generateCodeSamples: options.generateCodeSamples,
899
906
  baseUrl: document.servers?.[0].url ?? "https://example.com"
900
907
  };
901
908
  }
@@ -1,13 +1,19 @@
1
1
  import {
2
+ SchemaContext,
2
3
  getDefaultValue,
3
4
  getDefaultValues,
4
5
  resolve,
5
- useApiContext
6
- } from "./chunk-RSFOKBAM.js";
6
+ useApiContext,
7
+ useSchemaContext
8
+ } from "./chunk-BYW6XCQ2.js";
7
9
 
8
10
  // src/ui/playground.tsx
9
- import { useMemo, useRef, useState as useState3 } from "react";
10
- import { useForm } from "react-hook-form";
11
+ import {
12
+ useMemo,
13
+ useRef,
14
+ useState as useState3
15
+ } from "react";
16
+ import { Controller as Controller2, useForm } from "react-hook-form";
11
17
  import useSWRImmutable from "swr/immutable";
12
18
  import { Accordion, Accordions } from "fumadocs-ui/components/accordion";
13
19
  import { cn as cn5, buttonVariants as buttonVariants2 } from "fumadocs-ui/components/api";
@@ -57,7 +63,7 @@ var FormItem = forwardRef(({ className, ...props }, ref) => {
57
63
  });
58
64
  FormItem.displayName = "FormItem";
59
65
  var labelVariants = cva(
60
- "text-xs font-medium text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
66
+ "text-xs font-medium text-fd-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
61
67
  );
62
68
  var FormLabel = forwardRef(({ className, ...props }, ref) => {
63
69
  const { isError, formItemId } = useFormField();
@@ -93,7 +99,7 @@ var FormDescription = forwardRef(({ className, ...props }, ref) => {
93
99
  {
94
100
  ref,
95
101
  id: formDescriptionId,
96
- className: cn("text-xs text-muted-foreground", className),
102
+ className: cn("text-xs text-fd-muted-foreground", className),
97
103
  ...props
98
104
  }
99
105
  );
@@ -180,7 +186,7 @@ var statusMap = {
180
186
  403: { description: "Forbidden", color: "text-red-500", icon: CircleXIcon },
181
187
  404: {
182
188
  description: "Not Found",
183
- color: "text-muted-foreground",
189
+ color: "text-fd-muted-foreground",
184
190
  icon: CircleXIcon
185
191
  },
186
192
  500: {
@@ -205,7 +211,7 @@ function getStatusInfo(status) {
205
211
  }
206
212
  return {
207
213
  description: "No Description",
208
- color: "text-muted-foreground",
214
+ color: "text-fd-muted-foreground",
209
215
  icon: CircleXIcon
210
216
  };
211
217
  }
@@ -233,13 +239,13 @@ var SelectTrigger = forwardRef2(({ className, children, ...props }, ref) => /* @
233
239
  {
234
240
  ref,
235
241
  className: cn2(
236
- "flex h-10 items-center justify-between rounded-md border px-3 py-2 text-sm text-foreground hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
242
+ "flex h-10 items-center justify-between rounded-md border px-3 py-2 text-sm text-fd-foreground hover:bg-fd-accent focus:outline-none focus:ring-2 focus:ring-fd-ring disabled:cursor-not-allowed disabled:opacity-50",
237
243
  className
238
244
  ),
239
245
  ...props,
240
246
  children: [
241
247
  children,
242
- /* @__PURE__ */ jsx2(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx2(ChevronDown, { className: "size-4 text-muted-foreground" }) })
248
+ /* @__PURE__ */ jsx2(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx2(ChevronDown, { className: "size-4 text-fd-muted-foreground" }) })
243
249
  ]
244
250
  }
245
251
  ));
@@ -269,7 +275,7 @@ var SelectContent = forwardRef2(({ className, children, position = "popper", ...
269
275
  {
270
276
  ref,
271
277
  className: cn2(
272
- "z-50 overflow-hidden rounded-lg border bg-popover text-popover-foreground shadow-md data-[state=closed]:animate-popover-out data-[state=open]:animate-popover-in",
278
+ "z-50 overflow-hidden rounded-lg border bg-fd-popover text-fd-popover-foreground shadow-md data-[state=closed]:animate-fd-popover-out data-[state=open]:animate-fd-popover-in",
273
279
  className
274
280
  ),
275
281
  position,
@@ -305,7 +311,7 @@ var SelectItem = forwardRef2(({ className, children, ...props }, ref) => /* @__P
305
311
  {
306
312
  ref,
307
313
  className: cn2(
308
- "flex select-none flex-row items-center rounded-md py-1.5 pe-2 ps-6 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
314
+ "flex select-none flex-row items-center rounded-md py-1.5 pe-2 ps-6 text-sm outline-none focus:bg-fd-accent focus:text-fd-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
309
315
  className
310
316
  ),
311
317
  ...props,
@@ -320,7 +326,7 @@ var SelectSeparator = forwardRef2(({ className, ...props }, ref) => /* @__PURE__
320
326
  SelectPrimitive.Separator,
321
327
  {
322
328
  ref,
323
- className: cn2("my-1 h-px bg-muted", className),
329
+ className: cn2("my-1 h-px bg-fd-muted", className),
324
330
  ...props
325
331
  }
326
332
  ));
@@ -337,7 +343,7 @@ var Input = React.forwardRef(
337
343
  {
338
344
  type,
339
345
  className: cn3(
340
- "flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-sm text-foreground transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
346
+ "flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-sm text-fd-foreground transition-colors placeholder:text-fd-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-fd-ring disabled:cursor-not-allowed disabled:opacity-50",
341
347
  className
342
348
  ),
343
349
  ref,
@@ -348,17 +354,6 @@ var Input = React.forwardRef(
348
354
  );
349
355
  Input.displayName = "Input";
350
356
 
351
- // src/ui/contexts/schema.tsx
352
- import { createContext as createContext2, useContext as useContext2 } from "react";
353
- var SchemaContext = createContext2(
354
- void 0
355
- );
356
- function useSchemaContext() {
357
- const ctx = useContext2(SchemaContext);
358
- if (!ctx) throw new Error("Missing provider");
359
- return ctx;
360
- }
361
-
362
357
  // src/ui/inputs.tsx
363
358
  import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
364
359
  function renderInner({ field, ...props }) {
@@ -368,7 +363,7 @@ function renderInner({ field, ...props }) {
368
363
  {
369
364
  field,
370
365
  ...props,
371
- className: cn4("rounded-lg border bg-accent/20 p-3", props.className)
366
+ className: cn4("rounded-lg border bg-fd-accent/20 p-3", props.className)
372
367
  }
373
368
  );
374
369
  if (field.type === "switcher")
@@ -379,7 +374,10 @@ function renderInner({ field, ...props }) {
379
374
  {
380
375
  field,
381
376
  ...props,
382
- className: cn4("rounded-lg border bg-background p-3", props.className)
377
+ className: cn4(
378
+ "rounded-lg border bg-fd-background p-3",
379
+ props.className
380
+ )
383
381
  }
384
382
  );
385
383
  if (field.type === "null") return null;
@@ -399,7 +397,7 @@ function InputContainer({
399
397
  name,
400
398
  required ? /* @__PURE__ */ jsx4("span", { className: "text-red-500", children: "*" }) : null,
401
399
  /* @__PURE__ */ jsx4("div", { className: "flex-1" }),
402
- type ? /* @__PURE__ */ jsx4("code", { className: "text-xs text-muted-foreground", children: type }) : null,
400
+ type ? /* @__PURE__ */ jsx4("code", { className: "text-xs text-fd-muted-foreground", children: type }) : null,
403
401
  toolbar
404
402
  ] }),
405
403
  !inline ? /* @__PURE__ */ jsx4("p", { className: "text-xs", children: description }) : null,
@@ -627,7 +625,7 @@ function InputField({
627
625
  {
628
626
  field,
629
627
  fieldName,
630
- className: "rounded-lg border bg-accent/20 p-3"
628
+ className: "rounded-lg border bg-fd-accent/20 p-3"
631
629
  }
632
630
  )
633
631
  }
@@ -646,7 +644,7 @@ function InputField({
646
644
  {
647
645
  fieldName,
648
646
  field,
649
- className: "rounded-lg border bg-background p-3"
647
+ className: "rounded-lg border bg-fd-background p-3"
650
648
  }
651
649
  )
652
650
  }
@@ -665,7 +663,7 @@ function InputField({
665
663
  /* @__PURE__ */ jsxs2(FormLabel, { className: "inline-flex items-center gap-1", children: [
666
664
  name,
667
665
  field.isRequired ? /* @__PURE__ */ jsx4("span", { className: "text-red-500", children: "*" }) : null,
668
- /* @__PURE__ */ jsx4("code", { className: "ms-auto text-xs text-muted-foreground", children: field.type }),
666
+ /* @__PURE__ */ jsx4("code", { className: "ms-auto text-xs text-fd-muted-foreground", children: field.type }),
669
667
  toolbar
670
668
  ] }),
671
669
  !inline ? /* @__PURE__ */ jsx4(FormDescription, { className: "text-xs", children: field.description }) : null
@@ -821,6 +819,7 @@ function APIPlayground({
821
819
  header = [],
822
820
  query = [],
823
821
  body,
822
+ fields = {},
824
823
  schemas
825
824
  }) {
826
825
  const { baseUrl } = useApiContext();
@@ -840,14 +839,16 @@ function APIPlayground({
840
839
  async () => {
841
840
  if (!input) return;
842
841
  let pathname = route;
843
- Object.keys(input.path ?? {}).forEach((key) => {
844
- const paramValue = input.path?.[key];
845
- if (paramValue) pathname = pathname.replace(`{${key}}`, paramValue);
842
+ Object.keys(input.path).forEach((key) => {
843
+ const paramValue = input.path[key];
844
+ if (typeof paramValue === "string")
845
+ pathname = pathname.replace(`{${key}}`, paramValue);
846
846
  });
847
847
  const url = new URL(pathname, baseUrl ?? window.location.origin);
848
- Object.keys(input.query ?? {}).forEach((key) => {
849
- const paramValue = input.query?.[key];
850
- if (paramValue) url.searchParams.append(key, paramValue);
848
+ Object.keys(input.query).forEach((key) => {
849
+ const paramValue = input.query[key];
850
+ if (typeof paramValue === "string")
851
+ url.searchParams.append(key, paramValue);
851
852
  });
852
853
  const headers = new Headers({
853
854
  "Content-Type": "application/json"
@@ -855,9 +856,9 @@ function APIPlayground({
855
856
  if (input.authorization) {
856
857
  headers.append("Authorization", input.authorization);
857
858
  }
858
- Object.keys(input.header ?? {}).forEach((key) => {
859
- const paramValue = input.header?.[key];
860
- if (paramValue) headers.append(key, paramValue);
859
+ Object.keys(input.header).forEach((key) => {
860
+ const paramValue = input.header[key];
861
+ if (typeof paramValue === "string") headers.append(key, paramValue);
861
862
  });
862
863
  const bodyValue = body && input.body && Object.keys(input.body).length > 0 ? createBodyFromValue(input.body, body, schemas, dynamicRef.current) : void 0;
863
864
  const response = await fetch(url, {
@@ -872,10 +873,31 @@ function APIPlayground({
872
873
  shouldRetryOnError: false
873
874
  }
874
875
  );
875
- const statusInfo = testQuery.data ? getStatusInfo(testQuery.data.status) : void 0;
876
876
  const onSubmit = form.handleSubmit((value) => {
877
877
  setInput(value);
878
878
  });
879
+ function renderCustomField(fieldName, info, field, key) {
880
+ if (field) {
881
+ return /* @__PURE__ */ jsx6(
882
+ Controller2,
883
+ {
884
+ control: form.control,
885
+ render: (props) => field.render({ ...props, info }),
886
+ name: fieldName
887
+ },
888
+ key
889
+ );
890
+ }
891
+ return /* @__PURE__ */ jsx6(
892
+ InputField,
893
+ {
894
+ name: info.name,
895
+ fieldName,
896
+ field: info
897
+ },
898
+ key
899
+ );
900
+ }
879
901
  return /* @__PURE__ */ jsx6(Form, { ...form, children: /* @__PURE__ */ jsx6(
880
902
  SchemaContext.Provider,
881
903
  {
@@ -886,11 +908,11 @@ function APIPlayground({
886
908
  children: /* @__PURE__ */ jsxs3(
887
909
  "form",
888
910
  {
889
- className: "not-prose flex flex-col gap-4 rounded-lg border bg-card p-4",
911
+ className: "not-prose flex flex-col gap-4 rounded-lg border bg-fd-card p-4",
890
912
  onSubmit,
891
913
  children: [
892
914
  /* @__PURE__ */ jsxs3("div", { className: "flex flex-row gap-2", children: [
893
- /* @__PURE__ */ jsx6("code", { className: "flex-1 overflow-auto rounded-lg border bg-secondary px-3 py-1.5 text-sm", children: route }),
915
+ /* @__PURE__ */ jsx6("code", { className: "flex-1 overflow-auto rounded-lg border bg-fd-secondary px-3 py-1.5 text-sm", children: route }),
894
916
  /* @__PURE__ */ jsx6(
895
917
  "button",
896
918
  {
@@ -901,64 +923,70 @@ function APIPlayground({
901
923
  }
902
924
  )
903
925
  ] }),
904
- /* @__PURE__ */ jsxs3(Accordions, { type: "multiple", className: "-m-4 mt-2 border-0 text-sm", children: [
905
- authorization ? /* @__PURE__ */ jsx6(Accordion, { title: "Authorization", children: /* @__PURE__ */ jsx6(
906
- InputField,
907
- {
908
- name: "Authorization",
909
- fieldName: "authorization",
910
- field: authorization
911
- }
912
- ) }) : null,
913
- path.length > 0 ? /* @__PURE__ */ jsx6(Accordion, { title: "Path", children: path.map((field) => /* @__PURE__ */ jsx6(
914
- InputField,
915
- {
916
- field,
917
- name: field.name,
918
- fieldName: `path.${field.name}`
919
- },
920
- field.name
921
- )) }) : null,
922
- query.length > 0 ? /* @__PURE__ */ jsx6(Accordion, { title: "Query", children: /* @__PURE__ */ jsx6("div", { className: "flex flex-col gap-2", children: query.map((field) => /* @__PURE__ */ jsx6(
923
- InputField,
924
- {
925
- field,
926
- name: field.name,
927
- fieldName: `query.${field.name}`
928
- },
929
- field.name
930
- )) }) }) : null,
931
- header.length > 0 ? /* @__PURE__ */ jsx6(Accordion, { title: "Headers", children: header.map((field) => /* @__PURE__ */ jsx6(
932
- InputField,
933
- {
934
- field,
935
- name: field.name,
936
- fieldName: `header.${field.name}`
937
- },
938
- field.name
939
- )) }) : null,
940
- body ? /* @__PURE__ */ jsx6(Accordion, { title: "Body", children: body.type === "object" ? /* @__PURE__ */ jsx6(ObjectInput, { field: body, fieldName: "body" }) : /* @__PURE__ */ jsx6(InputField, { name: "Body", field: body, fieldName: "body" }) }) : null
941
- ] }),
942
- testQuery.data && statusInfo ? /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-3 rounded-lg border bg-card p-4", children: [
943
- /* @__PURE__ */ jsxs3("div", { className: "inline-flex items-center gap-1.5 text-sm font-medium text-foreground", children: [
944
- /* @__PURE__ */ jsx6(statusInfo.icon, { className: cn5("size-4", statusInfo.color) }),
945
- statusInfo.description
946
- ] }),
947
- /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: testQuery.data.status }),
948
- testQuery.data.data ? /* @__PURE__ */ jsx6(
949
- CodeBlock2,
950
- {
951
- code: JSON.stringify(testQuery.data.data, null, 2),
952
- className: "max-h-[288px]"
953
- }
954
- ) : null
955
- ] }) : null
926
+ authorization ? renderCustomField("authorization", authorization, fields.auth) : null,
927
+ /* @__PURE__ */ jsxs3(
928
+ Accordions,
929
+ {
930
+ type: "multiple",
931
+ className: cn5(
932
+ "-m-4 border-0 text-sm",
933
+ path.length === 0 && query.length === 0 && header.length === 0 && !body && "hidden"
934
+ ),
935
+ children: [
936
+ path.length > 0 ? /* @__PURE__ */ jsx6(Accordion, { title: "Path", children: path.map(
937
+ (field) => renderCustomField(
938
+ `path.${field.name}`,
939
+ field,
940
+ fields.path,
941
+ field.name
942
+ )
943
+ ) }) : null,
944
+ query.length > 0 ? /* @__PURE__ */ jsx6(Accordion, { title: "Query", children: query.map(
945
+ (field) => renderCustomField(
946
+ `query.${field.name}`,
947
+ field,
948
+ fields.query,
949
+ field.name
950
+ )
951
+ ) }) : null,
952
+ header.length > 0 ? /* @__PURE__ */ jsx6(Accordion, { title: "Headers", children: header.map(
953
+ (field) => renderCustomField(
954
+ `header.${field.name}`,
955
+ field,
956
+ fields.header,
957
+ field.name
958
+ )
959
+ ) }) : null,
960
+ body ? /* @__PURE__ */ jsx6(Accordion, { title: "Body", children: body.type === "object" && !fields.body ? /* @__PURE__ */ jsx6(ObjectInput, { field: body, fieldName: "body" }) : renderCustomField("body", body, fields.body) }) : null
961
+ ]
962
+ }
963
+ ),
964
+ testQuery.data ? /* @__PURE__ */ jsx6(ResultDisplay, { data: testQuery.data }) : null
956
965
  ]
957
966
  }
958
967
  )
959
968
  }
960
969
  ) });
961
970
  }
971
+ function ResultDisplay({
972
+ data
973
+ }) {
974
+ const statusInfo = useMemo(() => getStatusInfo(data.status), [data.status]);
975
+ return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-3 rounded-lg border bg-fd-card p-4", children: [
976
+ /* @__PURE__ */ jsxs3("div", { className: "inline-flex items-center gap-1.5 text-sm font-medium text-fd-foreground", children: [
977
+ /* @__PURE__ */ jsx6(statusInfo.icon, { className: cn5("size-4", statusInfo.color) }),
978
+ statusInfo.description
979
+ ] }),
980
+ /* @__PURE__ */ jsx6("p", { className: "text-sm text-fd-muted-foreground", children: data.status }),
981
+ data.data ? /* @__PURE__ */ jsx6(
982
+ CodeBlock2,
983
+ {
984
+ code: JSON.stringify(data.data, null, 2),
985
+ className: "max-h-[288px]"
986
+ }
987
+ ) : null
988
+ ] });
989
+ }
962
990
  export {
963
991
  APIPlayground
964
992
  };
@@ -1,8 +1,28 @@
1
1
  import * as react from 'react';
2
- import { HTMLAttributes, ReactNode } from 'react';
3
- import { A as APIPlaygroundProps } from '../playground-D8pYn13F.js';
2
+ import { ReactElement, HTMLAttributes, ReactNode, MutableRefObject } from 'react';
3
+ import { FieldPath, ControllerRenderProps, ControllerFieldState, UseFormStateReturn } from 'react-hook-form';
4
+ import { R as RequestSchema, a as ReferenceSchema, A as APIPlaygroundProps, P as PrimitiveRequestField } from '../playground-D8pYn13F.js';
4
5
  import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
5
6
 
7
+ interface FormValues {
8
+ authorization: string;
9
+ path: Record<string, unknown>;
10
+ query: Record<string, unknown>;
11
+ header: Record<string, unknown>;
12
+ body: unknown;
13
+ }
14
+ interface CustomField<TName extends FieldPath<FormValues>, Info> {
15
+ render: (props: {
16
+ /**
17
+ * Field Info
18
+ */
19
+ info: Info;
20
+ field: ControllerRenderProps<FormValues, TName>;
21
+ fieldState: ControllerFieldState;
22
+ formState: UseFormStateReturn<FormValues>;
23
+ }) => ReactElement;
24
+ }
25
+
6
26
  interface RootProps extends HTMLAttributes<HTMLDivElement> {
7
27
  baseUrl?: string;
8
28
  }
@@ -37,10 +57,31 @@ declare function ObjectCollapsible(props: {
37
57
  children: ReactNode;
38
58
  }): React.ReactElement;
39
59
 
40
- declare const APIPlayground: react.ComponentType<APIPlaygroundProps & react.HTMLAttributes<HTMLFormElement>>;
60
+ interface SchemaContextType {
61
+ references: Record<string, RequestSchema>;
62
+ dynamic: MutableRefObject<Map<string, DynamicField>>;
63
+ }
64
+ type DynamicField = {
65
+ type: 'object';
66
+ properties: string[];
67
+ } | {
68
+ type: 'field';
69
+ schema: RequestSchema | ReferenceSchema;
70
+ };
71
+ declare function useSchemaContext(): SchemaContextType;
72
+
73
+ declare const APIPlayground: react.ComponentType<APIPlaygroundProps & {
74
+ fields?: {
75
+ auth?: CustomField<"authorization", PrimitiveRequestField>;
76
+ path?: CustomField<`path.${string}`, PrimitiveRequestField>;
77
+ query?: CustomField<`query.${string}`, PrimitiveRequestField>;
78
+ header?: CustomField<`header.${string}`, PrimitiveRequestField>;
79
+ body?: CustomField<"body", RequestSchema>;
80
+ };
81
+ } & react.HTMLAttributes<HTMLFormElement>>;
41
82
  declare const Responses: typeof Tabs;
42
83
  declare const Response: typeof Tab;
43
84
  declare const Requests: typeof Tabs;
44
85
  declare const Request: typeof Tab;
45
86
 
46
- export { API, APIExample, APIInfo, type APIInfoProps, APIPlayground, ExampleResponse, ObjectCollapsible, Property, Request, Requests, Response, ResponseTypes, Responses, Root, type RootProps, TypeScriptResponse };
87
+ export { API, APIExample, APIInfo, type APIInfoProps, APIPlayground, ExampleResponse, ObjectCollapsible, Property, Request, Requests, Response, ResponseTypes, Responses, Root, type RootProps, TypeScriptResponse, useSchemaContext };
package/dist/ui/index.js CHANGED
@@ -3,8 +3,9 @@ import {
3
3
  ApiProvider,
4
4
  badgeVariants,
5
5
  getBadgeColor,
6
- useApiContext
7
- } from "../chunk-RSFOKBAM.js";
6
+ useApiContext,
7
+ useSchemaContext
8
+ } from "../chunk-BYW6XCQ2.js";
8
9
 
9
10
  // src/ui/index.ts
10
11
  import { Tab, Tabs } from "fumadocs-ui/components/tabs";
@@ -28,7 +29,7 @@ function Root({
28
29
  "div",
29
30
  {
30
31
  className: cn(
31
- "flex flex-col gap-24 text-sm text-muted-foreground",
32
+ "flex flex-col gap-24 text-sm text-fd-muted-foreground",
32
33
  className
33
34
  ),
34
35
  ...props,
@@ -55,9 +56,9 @@ function API({
55
56
  }
56
57
  function Route({ route }) {
57
58
  const segments = route.split("/").filter((part) => part.length > 0);
58
- return /* @__PURE__ */ jsx("div", { className: "flex flex-row items-center gap-1 text-sm", children: segments.map((part, index) => /* @__PURE__ */ jsxs(Fragment, { children: [
59
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "/" }),
60
- /* @__PURE__ */ jsx("span", { className: "text-foreground", children: part })
59
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-row flex-wrap items-center gap-1 text-sm", children: segments.map((part, index) => /* @__PURE__ */ jsxs(Fragment, { children: [
60
+ /* @__PURE__ */ jsx("span", { className: "text-fd-muted-foreground", children: "/" }),
61
+ part.startsWith("{") && part.endsWith("}") ? /* @__PURE__ */ jsx("code", { className: "text-fd-primary", children: part }) : /* @__PURE__ */ jsx("span", { className: "text-fd-foreground", children: part })
61
62
  ] }, index)) });
62
63
  }
63
64
  function APIInfo({
@@ -73,7 +74,7 @@ function APIInfo({
73
74
  "div",
74
75
  {
75
76
  className: cn(
76
- "sticky top-24 z-[2] flex flex-row items-center gap-2 rounded-lg border bg-card p-3 md:top-10"
77
+ "sticky top-24 z-[2] flex flex-row items-center gap-2 rounded-lg border bg-fd-card p-3 md:top-10"
77
78
  ),
78
79
  children: [
79
80
  /* @__PURE__ */ jsx(
@@ -101,12 +102,12 @@ function Property({
101
102
  deprecated,
102
103
  children
103
104
  }) {
104
- return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-col rounded-lg border bg-card p-3 prose-no-margin", children: [
105
+ return /* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-col rounded-lg border bg-fd-card p-3 prose-no-margin", children: [
105
106
  /* @__PURE__ */ jsxs("h4", { className: "inline-flex items-center gap-4", children: [
106
107
  /* @__PURE__ */ jsx("code", { children: name }),
107
108
  required ? /* @__PURE__ */ jsx("div", { className: cn(badgeVariants({ color: "red" })), children: "Required" }) : null,
108
109
  deprecated ? /* @__PURE__ */ jsx("div", { className: cn(badgeVariants({ color: "yellow" })), children: "Deprecated" }) : null,
109
- /* @__PURE__ */ jsx("span", { className: "ms-auto font-mono text-[13px] text-muted-foreground", children: type })
110
+ /* @__PURE__ */ jsx("span", { className: "ms-auto font-mono text-[13px] text-fd-muted-foreground", children: type })
110
111
  ] }),
111
112
  children
112
113
  ] });
@@ -173,7 +174,7 @@ function CopyRouteButton({
173
174
 
174
175
  // src/ui/index.ts
175
176
  var APIPlayground = dynamic(
176
- () => import("../playground-7UGUJXDH.js").then((mod) => mod.APIPlayground)
177
+ () => import("../playground-EVJUDTB3.js").then((mod) => mod.APIPlayground)
177
178
  );
178
179
  var Responses = Tabs;
179
180
  var Response = Tab;
@@ -193,5 +194,6 @@ export {
193
194
  ResponseTypes,
194
195
  Responses,
195
196
  Root,
196
- TypeScriptResponse
197
+ TypeScriptResponse,
198
+ useSchemaContext
197
199
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-openapi",
3
- "version": "4.0.6",
3
+ "version": "4.1.1",
4
4
  "description": "Generate MDX docs for your OpenAPI spec",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -47,14 +47,14 @@
47
47
  "lucide-react": "^0.414.0",
48
48
  "openapi-sampler": "^1.5.1",
49
49
  "react-hook-form": "^7.52.1",
50
- "shiki": "^1.11.0",
50
+ "shiki": "^1.11.1",
51
51
  "swr": "^2.2.5",
52
- "fumadocs-core": "12.5.6",
53
- "fumadocs-ui": "12.5.6"
52
+ "fumadocs-core": "13.0.1",
53
+ "fumadocs-ui": "13.0.1"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/js-yaml": "^4.0.9",
57
- "@types/node": "20.14.11",
57
+ "@types/node": "20.14.12",
58
58
  "@types/openapi-sampler": "^1.0.3",
59
59
  "@types/react": "^18.3.3",
60
60
  "next": "^14.2.5",