fumadocs-openapi 10.6.5 → 10.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/css/generated/shared.css +59 -6
  2. package/dist/generate-file.js +3 -2
  3. package/dist/i18n.d.ts +3 -1
  4. package/dist/i18n.js +3 -1
  5. package/dist/playground/auth.js +92 -0
  6. package/dist/playground/client.d.ts +5 -12
  7. package/dist/playground/client.js +257 -217
  8. package/dist/playground/components/inputs.js +2 -2
  9. package/dist/playground/components/oauth-dialog.js +126 -163
  10. package/dist/playground/components/result-display.d.ts +16 -0
  11. package/dist/playground/components/result-display.js +141 -0
  12. package/dist/playground/components/server-select.js +1 -1
  13. package/dist/playground/components/spinner.js +14 -0
  14. package/dist/playground/fetcher.d.ts +9 -3
  15. package/dist/playground/fetcher.js +7 -18
  16. package/dist/playground/status-info.js +27 -23
  17. package/dist/requests/generators/csharp.js +2 -1
  18. package/dist/requests/generators/curl.js +5 -5
  19. package/dist/requests/generators/go.js +5 -5
  20. package/dist/requests/generators/java.js +4 -4
  21. package/dist/requests/generators/javascript.js +3 -3
  22. package/dist/requests/generators/python.js +5 -4
  23. package/dist/requests/media/adapter.js +7 -7
  24. package/dist/requests/string-utils.js +25 -4
  25. package/dist/requests/types.d.ts +3 -2
  26. package/dist/ui/api-page.js +3 -1
  27. package/dist/ui/base.d.ts +8 -4
  28. package/dist/ui/base.js +6 -1
  29. package/dist/ui/client/boundary.lazy.js +2 -1
  30. package/dist/ui/client/i18n.js +6 -4
  31. package/dist/ui/client/storage-key.js +1 -1
  32. package/dist/ui/components/dialog.js +1 -1
  33. package/dist/ui/contexts/api.d.ts +6 -3
  34. package/dist/ui/contexts/api.js +7 -2
  35. package/dist/ui/create-client.js +5 -0
  36. package/dist/ui/operation/client.js +1 -1
  37. package/dist/ui/operation/index.js +1 -1
  38. package/dist/ui/operation/request-tabs.d.ts +10 -0
  39. package/dist/ui/operation/request-tabs.js +44 -38
  40. package/dist/ui/operation/usage-tabs/client.js +1 -1
  41. package/dist/ui/schema/client.js +2 -2
  42. package/dist/ui/schema/index.js +1 -1
  43. package/dist/utils/pages/to-text.js +5 -4
  44. package/dist/utils/schema/index.d.ts +3 -4
  45. package/dist/utils/schema/index.js +4 -9
  46. package/dist/utils/use-query.js +2 -1
  47. package/package.json +11 -10
@@ -1,15 +1,26 @@
1
+ import { useApiContext } from "../../ui/contexts/api.js";
2
+ import { useTranslations } from "../../ui/client/i18n.js";
1
3
  import { cn } from "../../utils/cn.js";
2
4
  import { useQuery } from "../../utils/use-query.js";
3
5
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/components/select.js";
4
6
  import { Input, labelVariants } from "../../ui/components/input.js";
5
- import { useTranslations } from "../../ui/client/i18n.js";
6
7
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "../../ui/components/dialog.js";
7
- import { useEffect, useMemo, useState } from "react";
8
+ import { useAuth } from "../auth.js";
9
+ import { useMemo, useState } from "react";
8
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
11
  import { buttonVariants } from "fumadocs-ui/components/ui/button";
10
12
  import { useForm } from "react-hook-form";
11
13
  //#region src/playground/components/oauth-dialog.tsx
12
- function OauthDialog({ scheme, scopes, setToken, children, open, setOpen }) {
14
+ const OAuthDialog = Dialog;
15
+ function OAuthDialogContent(props) {
16
+ const t = useTranslations();
17
+ return /* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: t.authorization }), /* @__PURE__ */ jsx(DialogDescription, { children: t.obtainAccessToken })] }), /* @__PURE__ */ jsx(Content, { ...props })] });
18
+ }
19
+ function Content({ schemeId, scopes, setToken, setOpen }) {
20
+ const { schemes } = useApiContext();
21
+ const tokenInfo = useAuth().store[schemeId];
22
+ const scheme = schemes[schemeId];
23
+ if (!scheme || scheme.type !== "oauth2") throw new Error("unexpected schemaId: must be type oauth2");
13
24
  const [type, setType] = useState(() => {
14
25
  return Object.keys(scheme.flows)[0];
15
26
  });
@@ -41,58 +52,14 @@ function OauthDialog({ scheme, scopes, setToken, children, open, setOpen }) {
41
52
  supported: false
42
53
  }
43
54
  }), [t]);
44
- const form = useForm({ defaultValues: {
45
- clientId: "",
46
- clientSecret: "",
47
- username: "",
48
- password: ""
49
- } });
50
- const authCodeCallback = useQuery(async (code, state) => {
51
- const value = scheme.flows.authorizationCode;
52
- const res = await fetch(value.tokenUrl, {
53
- method: "POST",
54
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
55
- body: new URLSearchParams({
56
- grant_type: "authorization_code",
57
- code,
58
- redirect_uri: state.redirect_uri,
59
- client_id: state.client_id,
60
- client_secret: state.client_secret
61
- })
62
- });
63
- if (!res.ok) throw new Error(await res.text());
64
- const { access_token, token_type = "Bearer" } = await res.json();
65
- setToken(`${token_type} ${access_token}`);
66
- setOpen(false);
67
- });
68
- useEffect(() => {
69
- if (scheme.flows.authorizationCode) {
70
- const params = new URLSearchParams(window.location.search);
71
- const state = params.get("state");
72
- const code = params.get("code");
73
- if (state && code) {
74
- const parsedState = JSON.parse(state);
75
- setOpen(true);
76
- form.setValue("clientId", parsedState.client_id);
77
- form.setValue("clientSecret", parsedState.client_secret);
78
- authCodeCallback.start(code, parsedState);
79
- window.history.replaceState(null, "", window.location.pathname);
80
- return;
81
- }
82
- }
83
- if (scheme.flows.implicit && window.location.hash.length > 1) {
84
- const params = new URLSearchParams(window.location.hash.slice(1));
85
- const state = params.get("state");
86
- const token = params.get("access_token");
87
- const type = params.get("token_type") ?? "Bearer";
88
- if (state && token) {
89
- const parsedState = JSON.parse(state);
90
- form.setValue("clientId", parsedState.client_id);
91
- setToken(`${type} ${token}`);
92
- window.history.replaceState(null, "", window.location.pathname);
93
- }
94
- }
95
- }, []);
55
+ const form = useForm({ defaultValues: useMemo(() => {
56
+ return {
57
+ clientId: tokenInfo?.client_id ?? "",
58
+ clientSecret: tokenInfo?.type === "authorization_code" ? tokenInfo.client_secret : "",
59
+ username: "",
60
+ password: ""
61
+ };
62
+ }, [tokenInfo]) });
96
63
  const authorize = useQuery(async (values) => {
97
64
  if (type === "implicit") {
98
65
  const value = scheme.flows[type];
@@ -102,6 +69,7 @@ function OauthDialog({ scheme, scopes, setToken, children, open, setOpen }) {
102
69
  params.set("redirect_uri", window.location.href);
103
70
  params.set("scope", scopes.join("+"));
104
71
  params.set("state", JSON.stringify({
72
+ scheme: schemeId,
105
73
  client_id: values.clientId,
106
74
  redirect_uri: window.location.href
107
75
  }));
@@ -118,7 +86,8 @@ function OauthDialog({ scheme, scopes, setToken, children, open, setOpen }) {
118
86
  params.set("state", JSON.stringify({
119
87
  client_id: values.clientId,
120
88
  client_secret: values.clientSecret,
121
- redirect_uri: window.location.href
89
+ redirect_uri: window.location.href,
90
+ scheme: schemeId
122
91
  }));
123
92
  window.location.replace(`${value.authorizationUrl}?${params.toString()}`);
124
93
  return;
@@ -157,127 +126,121 @@ function OauthDialog({ scheme, scopes, setToken, children, open, setOpen }) {
157
126
  setOpen(false);
158
127
  }
159
128
  });
129
+ const isLoading = authorize.isLoading;
160
130
  const onSubmit = form.handleSubmit((values) => {
161
131
  return authorize.start(values);
162
132
  });
163
- const isLoading = authorize.isLoading || authCodeCallback.isLoading;
164
- const error = authCodeCallback.error ?? authorize.error;
165
- return /* @__PURE__ */ jsxs(Dialog, {
166
- open,
167
- onOpenChange: setOpen,
168
- children: [children, /* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: t.authorization }), /* @__PURE__ */ jsx(DialogDescription, { children: t.obtainAccessToken })] }), /* @__PURE__ */ jsxs("form", {
169
- className: "flex flex-col gap-6",
170
- onSubmit: (e) => {
171
- onSubmit(e);
172
- e.stopPropagation();
173
- },
174
- children: [
175
- /* @__PURE__ */ jsxs(Select, {
176
- value: type,
177
- onValueChange: setType,
178
- children: [/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }), /* @__PURE__ */ jsx(SelectContent, { children: Object.keys(scheme.flows).map((key) => {
179
- const { name, description } = allFlows[key];
180
- return /* @__PURE__ */ jsxs(SelectItem, {
181
- value: key,
182
- children: [/* @__PURE__ */ jsx("p", {
183
- className: "font-medium",
184
- children: name
185
- }), /* @__PURE__ */ jsx("p", {
186
- className: "text-fd-muted-foreground",
187
- children: description
188
- })]
189
- }, key);
190
- }) })]
191
- }),
192
- (type === "authorizationCode" || type === "clientCredentials" || type === "implicit") && /* @__PURE__ */ jsxs("fieldset", {
193
- className: "flex flex-col gap-1.5",
194
- children: [
195
- /* @__PURE__ */ jsx("label", {
196
- htmlFor: "client_id",
197
- className: cn(labelVariants()),
198
- children: t.clientId
199
- }),
200
- /* @__PURE__ */ jsx("p", {
201
- className: "text-fd-muted-foreground text-sm",
202
- children: t.clientIdHint
203
- }),
204
- /* @__PURE__ */ jsx(Input, {
205
- id: "client_id",
206
- placeholder: t.inputPlaceholder,
207
- type: "text",
208
- autoComplete: "off",
209
- disabled: isLoading,
210
- ...form.register("clientId", { required: true })
211
- })
212
- ]
213
- }),
214
- (type === "authorizationCode" || type === "clientCredentials") && /* @__PURE__ */ jsxs("fieldset", {
215
- className: "flex flex-col gap-1.5",
216
- children: [
217
- /* @__PURE__ */ jsx("label", {
218
- htmlFor: "client_secret",
219
- className: cn(labelVariants()),
220
- children: t.clientSecret
221
- }),
222
- /* @__PURE__ */ jsx("p", {
223
- className: "text-fd-muted-foreground text-sm",
224
- children: t.clientSecretHint
225
- }),
226
- /* @__PURE__ */ jsx(Input, {
227
- id: "client_secret",
228
- placeholder: t.inputPlaceholder,
229
- type: "password",
230
- autoComplete: "off",
231
- disabled: isLoading,
232
- ...form.register("clientSecret", { required: true })
233
- })
234
- ]
235
- }),
236
- type === "password" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("fieldset", {
237
- className: "flex flex-col gap-1.5",
238
- children: [/* @__PURE__ */ jsx("label", {
239
- htmlFor: "username",
133
+ return /* @__PURE__ */ jsxs("form", {
134
+ className: "flex flex-col gap-6",
135
+ onSubmit: (e) => {
136
+ onSubmit(e);
137
+ e.stopPropagation();
138
+ },
139
+ children: [
140
+ /* @__PURE__ */ jsxs(Select, {
141
+ value: type ?? "",
142
+ onValueChange: setType,
143
+ children: [/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select a flow" }) }), /* @__PURE__ */ jsx(SelectContent, { children: Object.keys(scheme.flows).map((key) => {
144
+ const { name, description } = allFlows[key];
145
+ return /* @__PURE__ */ jsxs(SelectItem, {
146
+ value: key,
147
+ children: [/* @__PURE__ */ jsx("p", {
148
+ className: "font-medium",
149
+ children: name
150
+ }), /* @__PURE__ */ jsx("p", {
151
+ className: "text-fd-muted-foreground",
152
+ children: description
153
+ })]
154
+ }, key);
155
+ }) })]
156
+ }),
157
+ (type === "authorizationCode" || type === "clientCredentials" || type === "implicit") && /* @__PURE__ */ jsxs("fieldset", {
158
+ className: "flex flex-col gap-1.5",
159
+ children: [
160
+ /* @__PURE__ */ jsx("label", {
161
+ htmlFor: "client_id",
240
162
  className: cn(labelVariants()),
241
- children: t.usernameField
242
- }), /* @__PURE__ */ jsx(Input, {
243
- id: "username",
163
+ children: t.clientId
164
+ }),
165
+ /* @__PURE__ */ jsx("p", {
166
+ className: "text-fd-muted-foreground text-sm",
167
+ children: t.clientIdHint
168
+ }),
169
+ /* @__PURE__ */ jsx(Input, {
170
+ id: "client_id",
244
171
  placeholder: t.inputPlaceholder,
245
172
  type: "text",
246
- disabled: isLoading,
247
173
  autoComplete: "off",
248
- ...form.register("username", { required: true })
249
- })]
250
- }), /* @__PURE__ */ jsxs("fieldset", {
251
- className: "flex flex-col gap-1.5",
252
- children: [/* @__PURE__ */ jsx("label", {
253
- htmlFor: "password",
174
+ disabled: isLoading,
175
+ ...form.register("clientId", { required: true })
176
+ })
177
+ ]
178
+ }),
179
+ (type === "authorizationCode" || type === "clientCredentials") && /* @__PURE__ */ jsxs("fieldset", {
180
+ className: "flex flex-col gap-1.5",
181
+ children: [
182
+ /* @__PURE__ */ jsx("label", {
183
+ htmlFor: "client_secret",
254
184
  className: cn(labelVariants()),
255
185
  children: t.clientSecret
256
- }), /* @__PURE__ */ jsx(Input, {
257
- id: "password",
186
+ }),
187
+ /* @__PURE__ */ jsx("p", {
188
+ className: "text-fd-muted-foreground text-sm",
189
+ children: t.clientSecretHint
190
+ }),
191
+ /* @__PURE__ */ jsx(Input, {
192
+ id: "client_secret",
258
193
  placeholder: t.inputPlaceholder,
259
194
  type: "password",
260
195
  autoComplete: "off",
261
196
  disabled: isLoading,
262
- ...form.register("password", { required: true })
263
- })]
264
- })] }),
265
- allFlows[type].supported ? /* @__PURE__ */ jsxs(Fragment$1, { children: [error ? /* @__PURE__ */ jsx("p", {
266
- className: "text-red-400 font-medium text-sm",
267
- children: String(error)
268
- }) : null, /* @__PURE__ */ jsx("button", {
269
- type: "submit",
197
+ ...form.register("clientSecret", { required: true })
198
+ })
199
+ ]
200
+ }),
201
+ type === "password" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("fieldset", {
202
+ className: "flex flex-col gap-1.5",
203
+ children: [/* @__PURE__ */ jsx("label", {
204
+ htmlFor: "username",
205
+ className: cn(labelVariants()),
206
+ children: t.usernameField
207
+ }), /* @__PURE__ */ jsx(Input, {
208
+ id: "username",
209
+ placeholder: t.inputPlaceholder,
210
+ type: "text",
211
+ autoComplete: "off",
270
212
  disabled: isLoading,
271
- className: cn(buttonVariants({ color: "primary" })),
272
- children: authCodeCallback.isLoading ? t.fetchingToken : t.submit
273
- })] }) : /* @__PURE__ */ jsx("p", {
274
- className: "text-fd-muted-foreground bg-fd-muted p-2 rounded-lg border",
275
- children: t.unsupported
276
- })
277
- ]
278
- })] })]
213
+ ...form.register("username", { required: true })
214
+ })]
215
+ }), /* @__PURE__ */ jsxs("fieldset", {
216
+ className: "flex flex-col gap-1.5",
217
+ children: [/* @__PURE__ */ jsx("label", {
218
+ htmlFor: "password",
219
+ className: cn(labelVariants()),
220
+ children: t.clientSecret
221
+ }), /* @__PURE__ */ jsx(Input, {
222
+ id: "password",
223
+ placeholder: t.inputPlaceholder,
224
+ type: "password",
225
+ autoComplete: "off",
226
+ disabled: isLoading,
227
+ ...form.register("password", { required: true })
228
+ })]
229
+ })] }),
230
+ type && allFlows[type].supported ? /* @__PURE__ */ jsxs(Fragment$1, { children: [authorize.error ? /* @__PURE__ */ jsx("p", {
231
+ className: "text-red-400 font-medium text-sm",
232
+ children: String(authorize.error)
233
+ }) : null, /* @__PURE__ */ jsx("button", {
234
+ type: "submit",
235
+ className: cn(buttonVariants({ color: "primary" })),
236
+ children: t.submit
237
+ })] }) : /* @__PURE__ */ jsx("p", {
238
+ className: "text-fd-muted-foreground bg-fd-muted p-2 rounded-lg border",
239
+ children: t.unsupported
240
+ })
241
+ ]
279
242
  });
280
243
  }
281
- const OauthDialogTrigger = DialogTrigger;
244
+ const OAuthDialogTrigger = DialogTrigger;
282
245
  //#endregion
283
- export { OauthDialog, OauthDialogTrigger };
246
+ export { OAuthDialog, OAuthDialogContent, OAuthDialogTrigger };
@@ -0,0 +1,16 @@
1
+ import { FetchResult } from "../fetcher.js";
2
+ import { ComponentProps } from "react";
3
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
4
+
5
+ //#region src/playground/components/result-display.d.ts
6
+ interface ResultDisplayProps extends ComponentProps<'div'> {
7
+ data: FetchResult;
8
+ reset: () => void;
9
+ }
10
+ declare function DefaultResultDisplay({
11
+ data,
12
+ reset,
13
+ ...rest
14
+ }: ResultDisplayProps): _$react_jsx_runtime0.JSX.Element;
15
+ //#endregion
16
+ export { DefaultResultDisplay, ResultDisplayProps };
@@ -0,0 +1,141 @@
1
+ "use client";
2
+ import { useTranslations, withReplacements } from "../../ui/client/i18n.js";
3
+ import { useStatusInfo } from "../status-info.js";
4
+ import { cn } from "../../utils/cn.js";
5
+ import { ClientCodeBlock } from "../../ui/components/codeblock.js";
6
+ import { useEffect, useMemo, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { CircleX } from "lucide-react";
9
+ import { buttonVariants } from "fumadocs-ui/components/ui/button";
10
+ import { safeParse } from "fast-content-type-parse";
11
+ import { cva } from "class-variance-authority";
12
+ //#region src/playground/components/result-display.tsx
13
+ const panelVariants = cva("flex flex-col gap-2 mt-2 px-3 py-2 border-y bg-fd-secondary text-fd-secondary-foreground");
14
+ function DefaultResultDisplay({ data, reset, ...rest }) {
15
+ const t = useTranslations();
16
+ if (data.type === "client_error") return /* @__PURE__ */ jsxs("div", {
17
+ ...rest,
18
+ className: cn(panelVariants(), rest.className),
19
+ children: [/* @__PURE__ */ jsxs("div", {
20
+ className: "flex gap-1.5 items-center",
21
+ children: [
22
+ /* @__PURE__ */ jsx(CircleX, { className: "size-4 text-red-500" }),
23
+ /* @__PURE__ */ jsx("p", {
24
+ className: "text-sm font-medium me-auto",
25
+ children: t.statusClientError
26
+ }),
27
+ /* @__PURE__ */ jsx("button", {
28
+ type: "button",
29
+ className: cn(buttonVariants({
30
+ size: "sm",
31
+ variant: "outline"
32
+ })),
33
+ onClick: () => reset(),
34
+ children: t.close
35
+ })
36
+ ]
37
+ }), /* @__PURE__ */ jsx("p", { children: data.message })]
38
+ });
39
+ return /* @__PURE__ */ jsx(ResponseResult, {
40
+ data,
41
+ reset,
42
+ ...rest
43
+ });
44
+ }
45
+ function getTextFormat(mime) {
46
+ switch (mime) {
47
+ case "application/json": return "json";
48
+ case "text/html": return "html";
49
+ case "text/css": return "css";
50
+ case "text/csv": return "csv";
51
+ case "application/javascript":
52
+ case "application/x-javascript": return "js";
53
+ case "application/xml": return "xml";
54
+ }
55
+ if (mime.endsWith("+json")) return "json";
56
+ if (mime.endsWith("+xml")) return "xml";
57
+ if (mime.startsWith("text/")) return "text";
58
+ return null;
59
+ }
60
+ function ResponseResult({ data, reset, ...rest }) {
61
+ const t = useTranslations();
62
+ const statusInfo = useStatusInfo(data.status);
63
+ const { parameters, type } = useMemo(() => safeParse(data.headers.get("Content-Type") ?? "text/plain"), [data.headers]);
64
+ let content;
65
+ if (type.startsWith("image/")) content = /* @__PURE__ */ jsx(ImageResult, {
66
+ mime: type,
67
+ buffer: data.body
68
+ });
69
+ else if (data.body.byteLength > 0) {
70
+ const lang = getTextFormat(type);
71
+ if (lang) content = /* @__PURE__ */ jsx(TextResult, {
72
+ lang,
73
+ charset: parameters.charset,
74
+ data
75
+ });
76
+ else content = /* @__PURE__ */ jsx("p", {
77
+ className: "p-2 border rounded-lg bg-fd-card text-fd-card-foreground",
78
+ children: withReplacements(t.statusBinaryBody, { length: String(data.body.byteLength) })
79
+ });
80
+ }
81
+ return /* @__PURE__ */ jsxs("div", {
82
+ ...rest,
83
+ className: cn(panelVariants(), rest.className),
84
+ children: [/* @__PURE__ */ jsxs("div", {
85
+ className: "flex items-center gap-1.5",
86
+ children: [
87
+ /* @__PURE__ */ jsx(statusInfo.icon, { className: cn("size-4 shrink-0", statusInfo.color) }),
88
+ /* @__PURE__ */ jsxs("p", {
89
+ className: "text-sm font-medium text-nowrap",
90
+ children: [
91
+ data.status,
92
+ " ",
93
+ statusInfo.description
94
+ ]
95
+ }),
96
+ /* @__PURE__ */ jsx("code", {
97
+ className: "ms-auto text-xs text-fd-muted-foreground truncate",
98
+ children: type
99
+ }),
100
+ /* @__PURE__ */ jsx("button", {
101
+ type: "button",
102
+ className: cn(buttonVariants({
103
+ size: "sm",
104
+ variant: "outline"
105
+ })),
106
+ onClick: () => reset(),
107
+ children: t.close
108
+ })
109
+ ]
110
+ }), content]
111
+ });
112
+ }
113
+ function TextResult({ lang, charset, data }) {
114
+ const code = useMemo(() => {
115
+ if (charset) try {
116
+ return new TextDecoder(charset).decode(data.body);
117
+ } catch {}
118
+ return new TextDecoder("utf-8").decode(data.body);
119
+ }, [charset, data.body]);
120
+ return /* @__PURE__ */ jsx(ClientCodeBlock, {
121
+ lang: code.length > 5e3 ? "text" : lang,
122
+ code
123
+ });
124
+ }
125
+ function ImageResult({ mime, buffer }) {
126
+ const [objectUrl, setObjectUrl] = useState(null);
127
+ useEffect(() => {
128
+ const blob = new Blob([buffer], { type: mime });
129
+ const url = URL.createObjectURL(blob);
130
+ setObjectUrl(url);
131
+ return () => URL.revokeObjectURL(url);
132
+ }, [mime, buffer]);
133
+ if (!objectUrl) return;
134
+ return /* @__PURE__ */ jsx("img", {
135
+ src: objectUrl,
136
+ alt: "",
137
+ className: "w-full rounded-md border border-fd-border"
138
+ });
139
+ }
140
+ //#endregion
141
+ export { DefaultResultDisplay };
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import { resolveServerUrl, withBase } from "../../utils/url.js";
3
3
  import { useServerContext } from "../../ui/contexts/api.js";
4
+ import { useTranslations } from "../../ui/client/i18n.js";
4
5
  import { cn } from "../../utils/cn.js";
5
6
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/components/select.js";
6
7
  import { Input, labelVariants } from "../../ui/components/input.js";
7
- import { useTranslations } from "../../ui/client/i18n.js";
8
8
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "../../ui/components/dialog.js";
9
9
  import { useEffect, useRef, useState } from "react";
10
10
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -0,0 +1,14 @@
1
+ import { cn } from "../../utils/cn.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { Loader2Icon } from "lucide-react";
4
+ //#region src/playground/components/spinner.tsx
5
+ function Spinner({ className, ...props }) {
6
+ return /* @__PURE__ */ jsx(Loader2Icon, {
7
+ role: "status",
8
+ "aria-label": "Loading",
9
+ className: cn("size-4 animate-spin", className),
10
+ ...props
11
+ });
12
+ }
13
+ //#endregion
14
+ export { Spinner };
@@ -1,10 +1,16 @@
1
1
  import { Awaitable } from "../types.js";
2
2
 
3
3
  //#region src/playground/fetcher.d.ts
4
- interface FetchResult {
4
+ type FetchResult = FetchResponseResult | FetchErrorResult;
5
+ interface FetchErrorResult {
6
+ type: 'client_error';
7
+ message: string;
8
+ }
9
+ interface FetchResponseResult {
10
+ type: 'response';
5
11
  status: number;
6
- type: 'json' | 'html' | 'text';
7
- data: unknown;
12
+ headers: Headers;
13
+ body: ArrayBuffer;
8
14
  }
9
15
  interface BrowserFetcherOptions {
10
16
  /**
@@ -21,9 +21,8 @@ function createBrowserFetcher(adapters, { proxyUrl, proxyForwardCookie = true, r
21
21
  if (data.bodyMediaType && data.body) {
22
22
  const adapter = resolveMediaAdapter(data.bodyMediaType, adapters);
23
23
  if (!adapter) return {
24
- status: 400,
25
- type: "text",
26
- data: `[Fumadocs] No adapter for ${data.bodyMediaType}, you need to specify one from 'createOpenAPI()'.`
24
+ type: "client_error",
25
+ message: `[Fumadocs] No adapter for ${data.bodyMediaType}, you need to specify one from 'createOpenAPI()'.`
27
26
  };
28
27
  if (data.bodyMediaType !== "multipart/form-data") headers.append("Content-Type", data.bodyMediaType);
29
28
  requestInit.body = adapter.encode(data);
@@ -41,26 +40,16 @@ function createBrowserFetcher(adapters, { proxyUrl, proxyForwardCookie = true, r
41
40
  }
42
41
  if (onRequestInit) requestInit = await onRequestInit(requestInit);
43
42
  return fetch(requestUrl, requestInit).then(async (res) => {
44
- const contentType = res.headers.get("Content-Type") ?? "";
45
- let type;
46
- let data;
47
- if (contentType.startsWith("application/json")) {
48
- type = "json";
49
- data = await res.json();
50
- } else {
51
- type = contentType.startsWith("text/html") ? "html" : "text";
52
- data = await res.text();
53
- }
54
43
  return {
44
+ type: "response",
55
45
  status: res.status,
56
- type,
57
- data
46
+ headers: res.headers,
47
+ body: await res.arrayBuffer()
58
48
  };
59
49
  }).catch((e) => {
60
50
  return {
61
- status: 400,
62
- type: "text",
63
- data: `Client side error: ${e instanceof Error ? `[${e.name}] ${e.message}` : e.toString()}`
51
+ type: "client_error",
52
+ message: `Client side error: ${e instanceof Error ? `[${e.name}] ${e.message}` : e.toString()}`
64
53
  };
65
54
  });
66
55
  } };
@@ -1,3 +1,5 @@
1
+ import { useTranslations } from "../ui/client/i18n.js";
2
+ import { useMemo } from "react";
1
3
  import { CircleCheck, CircleX } from "lucide-react";
2
4
  //#region src/playground/status-info.tsx
3
5
  const statusKeys = {
@@ -27,30 +29,32 @@ const statusKeys = {
27
29
  icon: CircleX
28
30
  }
29
31
  };
30
- function getStatusInfo(status, t) {
31
- if (status in statusKeys) {
32
- const { key, color, icon } = statusKeys[status];
32
+ function useStatusInfo(status) {
33
+ const t = useTranslations();
34
+ return useMemo(() => {
35
+ if (status in statusKeys) {
36
+ const { key, color, icon } = statusKeys[status];
37
+ return {
38
+ description: t[key],
39
+ color,
40
+ icon
41
+ };
42
+ }
43
+ if (status >= 200 && status < 300) return {
44
+ description: t.statusSuccessful,
45
+ color: "text-green-500",
46
+ icon: CircleCheck
47
+ };
48
+ if (status >= 400) return {
49
+ description: t.statusError,
50
+ color: "text-red-500",
51
+ icon: CircleX
52
+ };
33
53
  return {
34
- description: t[key],
35
- color,
36
- icon
54
+ color: "text-fd-muted-foreground",
55
+ icon: CircleX
37
56
  };
38
- }
39
- if (status >= 200 && status < 300) return {
40
- description: t.statusSuccessful,
41
- color: "text-green-500",
42
- icon: CircleCheck
43
- };
44
- if (status >= 400) return {
45
- description: t.statusError,
46
- color: "text-red-500",
47
- icon: CircleX
48
- };
49
- return {
50
- description: t.statusNoDescription,
51
- color: "text-fd-muted-foreground",
52
- icon: CircleX
53
- };
57
+ }, [t, status]);
54
58
  }
55
59
  //#endregion
56
- export { getStatusInfo };
60
+ export { useStatusInfo };
@@ -1,3 +1,4 @@
1
+ import { doubleQuote } from "../string-utils.js";
1
2
  import { resolveMediaAdapter } from "../media/resolve-adapter.js";
2
3
  import "../media/adapter.js";
3
4
  //#region src/requests/generators/csharp.ts
@@ -25,7 +26,7 @@ const csharp = {
25
26
  s.push("var client = new HttpClient();");
26
27
  const headerLines = [];
27
28
  function addHeader(key, value) {
28
- headerLines.push(`client.DefaultRequestHeaders.Add("${key}", ${JSON.stringify(value)});`);
29
+ headerLines.push(`client.DefaultRequestHeaders.Add("${key}", ${doubleQuote(value)});`);
29
30
  }
30
31
  for (const k in headers) addHeader(k, headers[k].value);
31
32
  if (Object.keys(data.cookie).length > 0) addHeader("cookie", Object.entries(data.cookie).map(([key, param]) => `${key}=${param.value}`).join("; "));