@djangocfg/ui-tools 2.1.284 → 2.1.286

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 (71) hide show
  1. package/dist/DocsLayout-BCVU6TTX.cjs +2027 -0
  2. package/dist/DocsLayout-BCVU6TTX.cjs.map +1 -0
  3. package/dist/DocsLayout-ERETJLLV.mjs +2020 -0
  4. package/dist/DocsLayout-ERETJLLV.mjs.map +1 -0
  5. package/dist/{PlaygroundLayout-O52C6HK5.css → DocsLayout-MBFIB4NO.css} +1 -1
  6. package/dist/{PrettyCode.client-SGDGQTYT.cjs → PrettyCode.client-5GABIN2I.cjs} +57 -35
  7. package/dist/PrettyCode.client-5GABIN2I.cjs.map +1 -0
  8. package/dist/{PrettyCode.client-DW5LTG47.mjs → PrettyCode.client-IZTXXYHG.mjs} +57 -35
  9. package/dist/PrettyCode.client-IZTXXYHG.mjs.map +1 -0
  10. package/dist/chunk-IULI4XII.cjs +1129 -0
  11. package/dist/chunk-IULI4XII.cjs.map +1 -0
  12. package/dist/chunk-VZGQC3NG.mjs +1100 -0
  13. package/dist/chunk-VZGQC3NG.mjs.map +1 -0
  14. package/dist/index.cjs +88 -552
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +18 -6
  17. package/dist/index.d.ts +18 -6
  18. package/dist/index.mjs +25 -496
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +6 -6
  21. package/src/tools/OpenapiViewer/.claude/.sidecar/activity.jsonl +4 -0
  22. package/src/tools/OpenapiViewer/.claude/.sidecar/map_cache.json +30 -0
  23. package/src/tools/OpenapiViewer/.claude/.sidecar/usage.json +5 -0
  24. package/src/tools/OpenapiViewer/.claude/project-map.md +23 -0
  25. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +28 -2
  26. package/src/tools/OpenapiViewer/README.md +104 -51
  27. package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +64 -0
  28. package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +137 -0
  29. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +268 -0
  30. package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +139 -0
  31. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +211 -0
  32. package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +101 -0
  33. package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +57 -0
  34. package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +11 -0
  35. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +71 -0
  36. package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +166 -0
  37. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +121 -0
  38. package/src/tools/OpenapiViewer/components/DocsLayout/sidebarLabel.ts +60 -0
  39. package/src/tools/OpenapiViewer/components/index.ts +5 -2
  40. package/src/tools/OpenapiViewer/components/shared/BodyFormEditor.tsx +422 -0
  41. package/src/tools/OpenapiViewer/components/shared/EndpointDraftSync.tsx +108 -0
  42. package/src/tools/OpenapiViewer/components/shared/EndpointResetButton.tsx +50 -0
  43. package/src/tools/OpenapiViewer/components/{PlaygroundLayout → shared}/RequestPanel.tsx +174 -87
  44. package/src/tools/OpenapiViewer/components/shared/SendButton.tsx +91 -0
  45. package/src/tools/OpenapiViewer/components/{PlaygroundLayout → shared}/ui.tsx +5 -4
  46. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +82 -8
  47. package/src/tools/OpenapiViewer/hooks/useEndpointDraft.ts +142 -0
  48. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +126 -13
  49. package/src/tools/OpenapiViewer/index.tsx +3 -7
  50. package/src/tools/OpenapiViewer/lazy.tsx +6 -27
  51. package/src/tools/OpenapiViewer/types.ts +44 -0
  52. package/src/tools/OpenapiViewer/utils/formatters.ts +2 -23
  53. package/src/tools/OpenapiViewer/utils/index.ts +3 -1
  54. package/src/tools/OpenapiViewer/utils/schemaExport.ts +206 -0
  55. package/src/tools/OpenapiViewer/utils/url.ts +202 -0
  56. package/src/tools/PrettyCode/PrettyCode.client.tsx +42 -8
  57. package/src/tools/PrettyCode/index.tsx +6 -0
  58. package/dist/PlaygroundLayout-DHUATCHB.cjs +0 -798
  59. package/dist/PlaygroundLayout-DHUATCHB.cjs.map +0 -1
  60. package/dist/PlaygroundLayout-NONWOVQR.mjs +0 -791
  61. package/dist/PlaygroundLayout-NONWOVQR.mjs.map +0 -1
  62. package/dist/PrettyCode.client-DW5LTG47.mjs.map +0 -1
  63. package/dist/PrettyCode.client-SGDGQTYT.cjs.map +0 -1
  64. package/dist/chunk-5FKE7OME.cjs +0 -369
  65. package/dist/chunk-5FKE7OME.cjs.map +0 -1
  66. package/dist/chunk-BKWDHJKF.mjs +0 -356
  67. package/dist/chunk-BKWDHJKF.mjs.map +0 -1
  68. package/src/tools/OpenapiViewer/components/PlaygroundLayout/EndpointList.tsx +0 -228
  69. package/src/tools/OpenapiViewer/components/PlaygroundLayout/index.tsx +0 -107
  70. /package/dist/{PlaygroundLayout-O52C6HK5.css.map → DocsLayout-MBFIB4NO.css.map} +0 -0
  71. /package/src/tools/OpenapiViewer/components/{PlaygroundLayout → shared}/ResponsePanel.tsx +0 -0
@@ -1,791 +0,0 @@
1
- import { usePlaygroundContext, deduplicateEndpoints, isValidJson, findApiKeyById, parseRequestHeaders, PrettyCode_default } from './chunk-BKWDHJKF.mjs';
2
- import { JsonTree_default } from './chunk-LFWQ36LJ.mjs';
3
- import './chunk-SSUOENAZ.mjs';
4
- import { __name } from './chunk-CGILA3WO.mjs';
5
- import React4, { useState, useMemo, useEffect, useCallback } from 'react';
6
- import { cn } from '@djangocfg/ui-core/lib';
7
- import { useIsMobile } from '@djangocfg/ui-core/hooks';
8
- import { ChevronRight, Search, Filter, Send, Loader2, Sparkles, Key, Terminal, WifiOff } from 'lucide-react';
9
- import { Skeleton, Combobox, Input, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DownloadButton, Button, Textarea, CopyButton } from '@djangocfg/ui-core/components';
10
- import consola from 'consola';
11
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
-
13
- var useMobile = /* @__PURE__ */ __name(() => {
14
- const isMobile = useIsMobile();
15
- return {
16
- isMobile,
17
- isDesktop: !isMobile
18
- };
19
- }, "useMobile");
20
- var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
21
- var extractEndpoints = /* @__PURE__ */ __name((schema) => {
22
- const endpoints = [];
23
- if (!schema.paths) return [];
24
- const baseUrl = schema.servers && schema.servers.length > 0 ? schema.servers[0].url : "";
25
- for (const [path, methods] of Object.entries(schema.paths)) {
26
- for (const method of HTTP_METHODS) {
27
- const op = methods[method];
28
- if (!op) continue;
29
- const methodUpper = method.toUpperCase();
30
- const description = op.description || op.summary || `${methodUpper} ${path}`;
31
- const category = op.tags?.[0] || "Other";
32
- const parameters = [];
33
- const allParams = [...methods.parameters || [], ...op.parameters || []];
34
- for (const param of allParams) {
35
- parameters.push({
36
- name: param.name,
37
- type: param.schema?.type || "string",
38
- required: param.required || false,
39
- description: param.description
40
- });
41
- }
42
- const responses = [];
43
- if (op.responses) {
44
- for (const [code, response] of Object.entries(op.responses)) {
45
- responses.push({
46
- code,
47
- description: response.description || `Response ${code}`
48
- });
49
- }
50
- }
51
- let requestBody;
52
- if (op.requestBody) {
53
- const content = op.requestBody.content;
54
- const mediaType = content?.["application/json"] || content?.[Object.keys(content || {})[0]];
55
- requestBody = {
56
- type: mediaType?.schema?.type || "object",
57
- description: op.requestBody.description
58
- };
59
- }
60
- const endpoint = {
61
- name: path.split("/").pop() || path,
62
- method: methodUpper,
63
- path: baseUrl + path,
64
- description,
65
- category,
66
- parameters: parameters.length > 0 ? parameters : void 0,
67
- requestBody,
68
- responses: responses.length > 0 ? responses : void 0
69
- };
70
- endpoints.push(endpoint);
71
- }
72
- }
73
- return endpoints;
74
- }, "extractEndpoints");
75
- var getCategories = /* @__PURE__ */ __name((endpoints) => {
76
- const categories = /* @__PURE__ */ new Set();
77
- endpoints.forEach((endpoint) => categories.add(endpoint.category));
78
- return Array.from(categories).sort();
79
- }, "getCategories");
80
- var fetchSchema = /* @__PURE__ */ __name(async (url) => {
81
- const response = await fetch(url, {
82
- headers: {
83
- "Accept": "application/json"
84
- }
85
- });
86
- if (!response.ok) {
87
- throw new Error(`Failed to fetch schema: ${response.statusText}`);
88
- }
89
- return response.json();
90
- }, "fetchSchema");
91
- function useOpenApiSchema({
92
- schemas,
93
- defaultSchemaId
94
- }) {
95
- const [loading, setLoading] = useState(true);
96
- const [error, setError] = useState(null);
97
- const [currentSchemaId, setCurrentSchemaId] = useState(
98
- defaultSchemaId || schemas[0]?.id
99
- );
100
- const [loadedSchemas, setLoadedSchemas] = useState(
101
- /* @__PURE__ */ new Map()
102
- );
103
- const currentSchema = useMemo(
104
- () => schemas.find((s) => s.id === currentSchemaId) || null,
105
- [schemas, currentSchemaId]
106
- );
107
- const currentOpenApiSchema = useMemo(
108
- () => currentSchemaId ? loadedSchemas.get(currentSchemaId) : null,
109
- [loadedSchemas, currentSchemaId]
110
- );
111
- const endpoints = useMemo(
112
- () => currentOpenApiSchema ? extractEndpoints(currentOpenApiSchema) : [],
113
- [currentOpenApiSchema]
114
- );
115
- const categories = useMemo(() => getCategories(endpoints), [endpoints]);
116
- useEffect(() => {
117
- if (!currentSchema) return;
118
- if (loadedSchemas.has(currentSchema.id)) {
119
- setLoading(false);
120
- return;
121
- }
122
- setLoading(true);
123
- setError(null);
124
- fetchSchema(currentSchema.url).then((schema) => {
125
- setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
126
- consola.success(`Schema loaded: ${currentSchema.name}`);
127
- setLoading(false);
128
- }).catch((err) => {
129
- consola.error(`Error loading schema from ${currentSchema.url}:`, err);
130
- setError(err instanceof Error ? err.message : "Failed to load schema");
131
- setLoading(false);
132
- });
133
- }, [currentSchema, loadedSchemas]);
134
- const setCurrentSchema = useCallback((schemaId) => {
135
- setCurrentSchemaId(schemaId);
136
- }, []);
137
- const refresh = useCallback(() => {
138
- if (!currentSchema) return;
139
- setLoading(true);
140
- setError(null);
141
- setLoadedSchemas((prev) => {
142
- const next = new Map(prev);
143
- next.delete(currentSchema.id);
144
- return next;
145
- });
146
- fetchSchema(currentSchema.url).then((schema) => {
147
- setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
148
- consola.success(`Schema refreshed: ${currentSchema.name}`);
149
- setLoading(false);
150
- }).catch((err) => {
151
- consola.error(`Error refreshing schema from ${currentSchema.url}:`, err);
152
- setError(err instanceof Error ? err.message : "Failed to refresh schema");
153
- setLoading(false);
154
- });
155
- }, [currentSchema]);
156
- return {
157
- loading,
158
- error,
159
- endpoints,
160
- categories,
161
- schemas,
162
- currentSchema,
163
- setCurrentSchema,
164
- refresh
165
- };
166
- }
167
- __name(useOpenApiSchema, "useOpenApiSchema");
168
- var METHOD_STYLES = {
169
- GET: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25",
170
- POST: "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25",
171
- PUT: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25",
172
- PATCH: "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/25",
173
- DELETE: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25"
174
- };
175
- var METHOD_FALLBACK = "bg-muted text-muted-foreground border-border";
176
- function getMethodStyle(method) {
177
- return METHOD_STYLES[method.toUpperCase()] ?? METHOD_FALLBACK;
178
- }
179
- __name(getMethodStyle, "getMethodStyle");
180
- function getStatusStyle(status) {
181
- if (status >= 500) return "bg-red-500/10 text-red-500 dark:text-red-400 border-red-500/25";
182
- if (status >= 400) return "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25";
183
- if (status >= 300) return "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25";
184
- return "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25";
185
- }
186
- __name(getStatusStyle, "getStatusStyle");
187
- function relativePath(full) {
188
- try {
189
- return new URL(full).pathname;
190
- } catch {
191
- return full;
192
- }
193
- }
194
- __name(relativePath, "relativePath");
195
- function MethodBadge({ method }) {
196
- return /* @__PURE__ */ jsx("span", { className: cn(
197
- "inline-flex shrink-0 items-center rounded border px-1.5 py-px",
198
- "font-mono text-[10px] font-bold uppercase tracking-wider leading-none",
199
- getMethodStyle(method)
200
- ), children: method });
201
- }
202
- __name(MethodBadge, "MethodBadge");
203
- function StatusBadge({ status }) {
204
- return /* @__PURE__ */ jsx("span", { className: cn(
205
- "inline-flex items-center rounded border px-1.5 py-px",
206
- "font-mono text-[11px] font-bold leading-none",
207
- getStatusStyle(status)
208
- ), children: status });
209
- }
210
- __name(StatusBadge, "StatusBadge");
211
- function SectionLabel({ children }) {
212
- return /* @__PURE__ */ jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 select-none", children });
213
- }
214
- __name(SectionLabel, "SectionLabel");
215
- function Panel({ children, className }) {
216
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col min-h-0 overflow-hidden", className), children });
217
- }
218
- __name(Panel, "Panel");
219
- function ScrollArea({ children, className }) {
220
- return /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-y-auto min-h-0", className), children });
221
- }
222
- __name(ScrollArea, "ScrollArea");
223
- function PanelHeader({ title }) {
224
- return /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 h-10 flex items-center", children: /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-widest text-muted-foreground/50", children: title }) });
225
- }
226
- __name(PanelHeader, "PanelHeader");
227
- function EmptyState({
228
- icon: Icon,
229
- text,
230
- className
231
- }) {
232
- return /* @__PURE__ */ jsxs(
233
- "div",
234
- {
235
- className: cn(
236
- "flex flex-col items-center justify-center h-full gap-3 px-6 text-center",
237
- className
238
- ),
239
- children: [
240
- /* @__PURE__ */ jsx(Icon, { className: "h-7 w-7 text-muted-foreground/25" }),
241
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: text })
242
- ]
243
- }
244
- );
245
- }
246
- __name(EmptyState, "EmptyState");
247
- function CollapsibleSection({
248
- label,
249
- action,
250
- children,
251
- defaultOpen = false
252
- }) {
253
- const [open, setOpen] = React4.useState(defaultOpen);
254
- return /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
255
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
256
- /* @__PURE__ */ jsxs(
257
- "button",
258
- {
259
- type: "button",
260
- onClick: () => setOpen((v) => !v),
261
- className: "flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 hover:text-muted-foreground transition-colors py-1",
262
- children: [
263
- /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-3 w-3 transition-transform", open && "rotate-90") }),
264
- label
265
- ]
266
- }
267
- ),
268
- action && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: action })
269
- ] }),
270
- open && /* @__PURE__ */ jsx("div", { children })
271
- ] });
272
- }
273
- __name(CollapsibleSection, "CollapsibleSection");
274
- function EndpointRow({
275
- method,
276
- path,
277
- description,
278
- isActive,
279
- onClick
280
- }) {
281
- const displayPath = relativePath(path);
282
- const rowCls = cn(
283
- "group w-full text-left flex items-start gap-2.5 px-3 py-2.5 transition-colors hover:bg-muted/40",
284
- isActive && "bg-primary/[0.06] hover:bg-primary/[0.09]"
285
- );
286
- const arrowCls = cn(
287
- "h-3.5 w-3.5 shrink-0 mt-px transition-opacity",
288
- isActive ? "text-primary opacity-100" : "opacity-0 group-hover:opacity-30"
289
- );
290
- return /* @__PURE__ */ jsxs("button", { className: rowCls, onClick, children: [
291
- /* @__PURE__ */ jsx(MethodBadge, { method }),
292
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
293
- /* @__PURE__ */ jsx("p", { className: "font-mono text-[11px] text-foreground/75 truncate leading-tight", children: displayPath }),
294
- description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/60 truncate leading-tight mt-0.5", children: description })
295
- ] }),
296
- /* @__PURE__ */ jsx(ChevronRight, { className: arrowCls })
297
- ] });
298
- }
299
- __name(EndpointRow, "EndpointRow");
300
- function EndpointList() {
301
- const { state, config, setSelectedEndpoint, setSelectedCategory, setSearchTerm } = usePlaygroundContext();
302
- const { endpoints, categories, loading, error, schemas, currentSchema, setCurrentSchema } = useOpenApiSchema({ schemas: config.schemas, defaultSchemaId: config.defaultSchemaId });
303
- const [debouncedSearch, setDebouncedSearch] = useState(state.searchTerm);
304
- useEffect(() => {
305
- const id = setTimeout(() => setDebouncedSearch(state.searchTerm), 150);
306
- return () => clearTimeout(id);
307
- }, [state.searchTerm]);
308
- const schemaOptions = useMemo(
309
- () => schemas.map((s) => ({ value: s.id, label: s.name })),
310
- [schemas]
311
- );
312
- const filtered = useMemo(() => {
313
- let list = deduplicateEndpoints(endpoints, state.selectedVersion);
314
- if (state.selectedCategory !== "All") {
315
- list = list.filter((e) => e.category === state.selectedCategory);
316
- }
317
- if (debouncedSearch) {
318
- const q = debouncedSearch.toLowerCase();
319
- list = list.filter(
320
- (e) => e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
321
- );
322
- }
323
- return list;
324
- }, [endpoints, state.selectedCategory, debouncedSearch, state.selectedVersion]);
325
- const isFiltered = state.selectedCategory !== "All";
326
- const hasCategories = categories.length > 0;
327
- const hasMultipleSchemas = schemas.length > 1;
328
- const endpointLabel = `${filtered.length} endpoint${filtered.length !== 1 ? "s" : ""}`;
329
- const downloadFilename = currentSchema ? `${currentSchema.id}-openapi.json` : "openapi.json";
330
- if (loading) {
331
- return /* @__PURE__ */ jsx("div", { className: "p-3 space-y-1.5", children: Array.from({ length: 12 }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full rounded" }, i)) });
332
- }
333
- if (error) {
334
- return /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-destructive", children: [
335
- "Failed to load schema: ",
336
- error
337
- ] }) });
338
- }
339
- return /* @__PURE__ */ jsxs(Fragment, { children: [
340
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-2.5 py-2 space-y-2", children: [
341
- hasMultipleSchemas && /* @__PURE__ */ jsx(
342
- Combobox,
343
- {
344
- options: schemaOptions,
345
- value: currentSchema?.id ?? "",
346
- onValueChange: (id) => id && setCurrentSchema(id),
347
- placeholder: "Select API",
348
- searchPlaceholder: "Search APIs\u2026",
349
- emptyText: "No APIs found",
350
- className: "w-full h-8 text-xs"
351
- }
352
- ),
353
- /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
354
- /* @__PURE__ */ jsxs("div", { className: "relative flex-1 min-w-0", children: [
355
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none" }),
356
- /* @__PURE__ */ jsx(
357
- Input,
358
- {
359
- placeholder: "Search endpoints\u2026",
360
- value: state.searchTerm,
361
- onChange: (e) => setSearchTerm(e.target.value),
362
- className: "pl-8 h-8 text-xs"
363
- }
364
- )
365
- ] }),
366
- hasCategories && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
367
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("button", { className: cn(
368
- "relative shrink-0 flex items-center justify-center h-8 w-8 rounded-md border transition-colors",
369
- isFiltered ? "border-primary bg-primary/10 text-primary" : "border-input bg-background text-muted-foreground hover:text-foreground hover:bg-muted/50"
370
- ), children: [
371
- /* @__PURE__ */ jsx(Filter, { className: "h-3.5 w-3.5" }),
372
- isFiltered && /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 h-2 w-2 rounded-full bg-primary" })
373
- ] }) }),
374
- /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", className: "min-w-[160px] max-h-72 overflow-y-auto", children: ["All", ...categories].map((c) => /* @__PURE__ */ jsx(
375
- DropdownMenuItem,
376
- {
377
- onClick: () => setSelectedCategory(c),
378
- className: cn("text-xs", state.selectedCategory === c && "bg-accent font-medium"),
379
- children: c
380
- },
381
- c
382
- )) })
383
- ] })
384
- ] })
385
- ] }),
386
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center justify-between px-3 py-1 border-b bg-muted/20", children: [
387
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums", children: endpointLabel }),
388
- currentSchema && /* @__PURE__ */ jsx(
389
- DownloadButton,
390
- {
391
- url: currentSchema.url,
392
- filename: downloadFilename,
393
- variant: "ghost",
394
- size: "sm",
395
- className: "h-6 px-2 text-[10px] text-muted-foreground/50 hover:text-foreground",
396
- children: "JSON"
397
- }
398
- )
399
- ] }),
400
- /* @__PURE__ */ jsx(ScrollArea, { children: filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "No endpoints found" }) : /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/40", children: filtered.map((ep) => /* @__PURE__ */ jsx(
401
- EndpointRow,
402
- {
403
- method: ep.method,
404
- path: ep.path,
405
- description: ep.description,
406
- isActive: state.selectedEndpoint?.path === ep.path && state.selectedEndpoint?.method === ep.method,
407
- onClick: () => setSelectedEndpoint(ep)
408
- },
409
- `${ep.method}-${ep.path}`
410
- )) }) })
411
- ] });
412
- }
413
- __name(EndpointList, "EndpointList");
414
- function ParamFields({ label, params }) {
415
- const { state, setParameters } = usePlaygroundContext();
416
- function handleChange(name, value) {
417
- setParameters({ ...state.parameters, [name]: value });
418
- }
419
- __name(handleChange, "handleChange");
420
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
421
- /* @__PURE__ */ jsx(SectionLabel, { children: label }),
422
- /* @__PURE__ */ jsx("div", { className: "space-y-2", children: params.map((p) => {
423
- const value = state.parameters[p.name] ?? "";
424
- const placeholder = p.description || p.name;
425
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
426
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
427
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: p.name }),
428
- p.required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
429
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: p.type })
430
- ] }),
431
- /* @__PURE__ */ jsx(
432
- Input,
433
- {
434
- value,
435
- onChange: (e) => handleChange(p.name, e.target.value),
436
- placeholder,
437
- className: "h-8 text-xs font-mono"
438
- }
439
- )
440
- ] }, p.name);
441
- }) })
442
- ] });
443
- }
444
- __name(ParamFields, "ParamFields");
445
- function RequestPanel() {
446
- const {
447
- state,
448
- apiKeys,
449
- apiKeysLoading,
450
- setRequestBody,
451
- setRequestHeaders,
452
- setSelectedApiKey,
453
- setManualApiToken,
454
- sendRequest
455
- } = usePlaygroundContext();
456
- const apiKeyOptions = useMemo(
457
- () => apiKeys.map((k) => ({
458
- value: k.id,
459
- label: k.name || "Unnamed key",
460
- // Surface the first 8 chars of the secret so the user
461
- // can tell two similarly-named keys apart at a glance.
462
- description: k.secret ? `${k.secret.slice(0, 8)}\u2026` : void 0
463
- })),
464
- [apiKeys]
465
- );
466
- const hasApiKeys = apiKeyOptions.length > 0;
467
- const ep = state.selectedEndpoint;
468
- const isJsonValid = state.requestBody ? isValidJson(state.requestBody) : true;
469
- const curlCommand = useMemo(() => {
470
- if (!state.requestUrl) return "";
471
- const apiKey = state.selectedApiKey ? findApiKeyById(apiKeys, state.selectedApiKey) : null;
472
- const hdrs = parseRequestHeaders(state.requestHeaders);
473
- if (apiKey) hdrs["X-API-Key"] = apiKey.secret || apiKey.id;
474
- let cmd = `curl -X ${state.requestMethod} "${state.requestUrl}"`;
475
- Object.entries(hdrs).forEach(([k, v]) => {
476
- cmd += ` \\
477
- -H "${k}: ${v}"`;
478
- });
479
- if (state.requestBody && state.requestMethod !== "GET" && isJsonValid) {
480
- cmd += ` \\
481
- -d '${state.requestBody}'`;
482
- }
483
- return cmd;
484
- }, [state, apiKeys, isJsonValid]);
485
- const pathParams = useMemo(
486
- () => ep?.parameters?.filter((p) => ep.path.includes(`{${p.name}}`)) ?? [],
487
- [ep]
488
- );
489
- const queryParams = useMemo(
490
- () => ep?.parameters?.filter((p) => !ep.path.includes(`{${p.name}}`)) ?? [],
491
- [ep]
492
- );
493
- const isSendDisabled = state.loading || !state.requestUrl || !isJsonValid;
494
- const displayUrl = state.requestUrl || ep?.path || "";
495
- const hasBody = ep?.method !== "GET";
496
- const bodyType = ep?.requestBody?.type ?? "";
497
- const hasPathParams = pathParams.length > 0;
498
- const hasQueryParams = queryParams.length > 0;
499
- const hasCurl = Boolean(curlCommand);
500
- const epPath = ep ? relativePath(ep.path) : "";
501
- const urlChanged = displayUrl !== epPath;
502
- if (!ep) {
503
- return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: "Select an endpoint to build a request" });
504
- }
505
- return /* @__PURE__ */ jsxs(Fragment, { children: [
506
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-3 bg-muted/20 space-y-1.5", children: [
507
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
508
- /* @__PURE__ */ jsx(MethodBadge, { method: ep.method }),
509
- /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-foreground/70 truncate min-w-0 flex-1", children: epPath }),
510
- /* @__PURE__ */ jsx(
511
- Button,
512
- {
513
- onClick: sendRequest,
514
- disabled: isSendDisabled,
515
- size: "sm",
516
- className: "shrink-0 gap-1.5 h-7 text-xs px-3",
517
- children: state.loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
518
- /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
519
- " Sending\u2026"
520
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
521
- /* @__PURE__ */ jsx(Send, { className: "h-3 w-3" }),
522
- " Send"
523
- ] })
524
- }
525
- )
526
- ] }),
527
- urlChanged && /* @__PURE__ */ jsx("div", { className: "font-mono text-[10px] text-muted-foreground/50 break-all leading-snug pl-0.5", children: displayUrl })
528
- ] }),
529
- /* @__PURE__ */ jsxs(ScrollArea, { className: "px-4 py-3 space-y-3", children: [
530
- hasPathParams && /* @__PURE__ */ jsx(ParamFields, { label: "Path Parameters", params: pathParams }),
531
- hasQueryParams && /* @__PURE__ */ jsx(ParamFields, { label: "Query Parameters", params: queryParams }),
532
- hasBody && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
533
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
534
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
535
- /* @__PURE__ */ jsx(SectionLabel, { children: "Body" }),
536
- bodyType && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/40 font-mono", children: bodyType })
537
- ] }),
538
- isJsonValid && state.requestBody && /* @__PURE__ */ jsxs(
539
- "button",
540
- {
541
- type: "button",
542
- onClick: () => {
543
- try {
544
- setRequestBody(JSON.stringify(JSON.parse(state.requestBody), null, 2));
545
- } catch {
546
- }
547
- },
548
- className: "inline-flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors",
549
- children: [
550
- /* @__PURE__ */ jsx(Sparkles, { className: "h-2.5 w-2.5" }),
551
- "Format"
552
- ]
553
- }
554
- )
555
- ] }),
556
- /* @__PURE__ */ jsx(
557
- Textarea,
558
- {
559
- placeholder: '{\n "key": "value"\n}',
560
- value: state.requestBody,
561
- onChange: (e) => setRequestBody(e.target.value),
562
- className: cn(
563
- "font-mono text-[11px] min-h-[90px] resize-y",
564
- !isJsonValid && "border-destructive focus-visible:ring-destructive/30"
565
- ),
566
- rows: 4
567
- }
568
- ),
569
- !isJsonValid && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-destructive", children: "Invalid JSON" })
570
- ] }),
571
- /* @__PURE__ */ jsx(
572
- CollapsibleSection,
573
- {
574
- label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
575
- /* @__PURE__ */ jsx(Key, { className: "h-2.5 w-2.5" }),
576
- "Auth & Headers"
577
- ] }),
578
- children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 pt-2", children: [
579
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
580
- /* @__PURE__ */ jsx(SectionLabel, { children: "API Key" }),
581
- /* @__PURE__ */ jsx(
582
- Combobox,
583
- {
584
- options: apiKeyOptions,
585
- value: state.selectedApiKey ?? "",
586
- onValueChange: (v) => setSelectedApiKey(v || null),
587
- placeholder: apiKeysLoading ? "Loading keys\u2026" : hasApiKeys ? "Select an API key" : "No API keys yet",
588
- searchPlaceholder: "Search keys\u2026",
589
- emptyText: "No matching key",
590
- disabled: apiKeysLoading || !hasApiKeys,
591
- className: "h-8"
592
- }
593
- ),
594
- /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
595
- "Picks are sent via the",
596
- " ",
597
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: "X-API-Key" }),
598
- " header."
599
- ] })
600
- ] }),
601
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
602
- /* @__PURE__ */ jsx(SectionLabel, { children: "Bearer Token" }),
603
- /* @__PURE__ */ jsx(
604
- Input,
605
- {
606
- type: "password",
607
- placeholder: "Leave empty to use JWT from localStorage",
608
- value: state.manualApiToken,
609
- onChange: (e) => setManualApiToken(e.target.value),
610
- className: "font-mono text-xs h-8"
611
- }
612
- )
613
- ] }),
614
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
615
- /* @__PURE__ */ jsx(SectionLabel, { children: "Headers" }),
616
- /* @__PURE__ */ jsx(
617
- Textarea,
618
- {
619
- value: state.requestHeaders,
620
- onChange: (e) => setRequestHeaders(e.target.value),
621
- className: "font-mono text-[11px] min-h-[60px] resize-y",
622
- rows: 3
623
- }
624
- )
625
- ] })
626
- ] })
627
- }
628
- ),
629
- hasCurl && /* @__PURE__ */ jsx(
630
- CollapsibleSection,
631
- {
632
- label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
633
- /* @__PURE__ */ jsx(Terminal, { className: "h-2.5 w-2.5" }),
634
- "cURL"
635
- ] }),
636
- action: /* @__PURE__ */ jsx(CopyButton, { value: curlCommand, variant: "ghost", size: "sm", className: "h-5 px-2 text-[10px] text-muted-foreground", children: "Copy" }),
637
- children: /* @__PURE__ */ jsx("div", { className: "rounded-md overflow-hidden mt-2", children: /* @__PURE__ */ jsx(PrettyCode_default, { data: curlCommand, language: "bash", isCompact: true }) })
638
- }
639
- ),
640
- /* @__PURE__ */ jsx("div", { className: "h-1" })
641
- ] }),
642
- /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background/95 backdrop-blur-sm", children: /* @__PURE__ */ jsx(Button, { onClick: sendRequest, disabled: isSendDisabled, size: "sm", className: "w-full gap-2 h-9", children: state.loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
643
- /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
644
- " Sending\u2026"
645
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
646
- /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
647
- " Send Request"
648
- ] }) }) })
649
- ] });
650
- }
651
- __name(RequestPanel, "RequestPanel");
652
- var JSON_TREE_CONFIG = {
653
- maxAutoExpandDepth: 2,
654
- maxAutoExpandArrayItems: 10,
655
- maxAutoExpandObjectKeys: 5,
656
- maxStringLength: 200,
657
- collectionLimit: 50,
658
- showCollectionInfo: true,
659
- showExpandControls: true,
660
- showActionButtons: false,
661
- preserveKeyOrder: true,
662
- className: "border-0 rounded-none"
663
- };
664
- function ResponsePanel() {
665
- const { state } = usePlaygroundContext();
666
- const { response, loading, selectedEndpoint } = state;
667
- const { treeData, rawText } = useMemo(() => {
668
- const d = response?.data;
669
- if (d == null) return { treeData: null, rawText: "" };
670
- if (typeof d === "string") {
671
- try {
672
- return { treeData: JSON.parse(d), rawText: d };
673
- } catch {
674
- return { treeData: null, rawText: d };
675
- }
676
- }
677
- return { treeData: d, rawText: JSON.stringify(d, null, 2) };
678
- }, [response?.data]);
679
- const sizeKb = rawText ? `${(rawText.length / 1024).toFixed(1)} KB` : "";
680
- const duration = response?.duration != null ? `${response.duration}ms` : "";
681
- const hasError = Boolean(response?.error);
682
- const hasStatus = response?.status != null;
683
- const hasCopy = Boolean(rawText);
684
- if (loading) {
685
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center h-full gap-2", children: [
686
- /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }),
687
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Sending\u2026" })
688
- ] });
689
- }
690
- if (!selectedEndpoint) return /* @__PURE__ */ jsx(EmptyState, { icon: Terminal, text: "Response will appear here" });
691
- if (!response) return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: 'Press "Send Request" to see the response' });
692
- if (hasError && !hasStatus) {
693
- return /* @__PURE__ */ jsx(
694
- EmptyState,
695
- {
696
- icon: WifiOff,
697
- text: response.error,
698
- className: "text-destructive [&_svg]:text-destructive"
699
- }
700
- );
701
- }
702
- return /* @__PURE__ */ jsxs(Fragment, { children: [
703
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-2 flex items-center justify-between gap-3 bg-muted/20", children: [
704
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
705
- hasStatus && /* @__PURE__ */ jsx(StatusBadge, { status: response.status }),
706
- response.statusText && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: response.statusText }),
707
- sizeKb && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: sizeKb }),
708
- duration && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: duration })
709
- ] }),
710
- hasCopy && /* @__PURE__ */ jsx(CopyButton, { value: rawText, variant: "ghost", size: "sm", className: "h-6 px-2 text-[10px] text-muted-foreground shrink-0", children: "Copy" })
711
- ] }),
712
- hasError && /* @__PURE__ */ jsx("div", { className: "shrink-0 mx-4 mt-3 rounded border border-destructive/20 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: response.error }) }),
713
- /* @__PURE__ */ jsx(ScrollArea, { children: treeData != null ? /* @__PURE__ */ jsx(JsonTree_default, { title: "Response Body", data: treeData, config: JSON_TREE_CONFIG }) : rawText ? /* @__PURE__ */ jsx("pre", { className: "p-4 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: rawText }) : /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" }) })
714
- ] });
715
- }
716
- __name(ResponsePanel, "ResponsePanel");
717
- var MOBILE_TABS = [
718
- { id: "endpoints", label: "Endpoints" },
719
- { id: "request", label: "Request" },
720
- { id: "response", label: "Response" }
721
- ];
722
- function MobileView() {
723
- const { state } = usePlaygroundContext();
724
- const [tab, setTab] = React4.useState("endpoints");
725
- React4.useEffect(() => {
726
- if (state.selectedEndpoint) setTab("request");
727
- }, [state.selectedEndpoint?.path, state.selectedEndpoint?.method]);
728
- React4.useEffect(() => {
729
- if (state.response && !state.loading) setTab("response");
730
- }, [state.response, state.loading]);
731
- const hasResponse = Boolean(state.response) && !state.loading;
732
- return /* @__PURE__ */ jsxs(Panel, { className: "h-full", children: [
733
- /* @__PURE__ */ jsx("div", { className: "shrink-0 flex border-b", children: MOBILE_TABS.map((t) => {
734
- const isActive = tab === t.id;
735
- const showDot = t.id === "response" && hasResponse;
736
- return /* @__PURE__ */ jsxs(
737
- "button",
738
- {
739
- onClick: () => setTab(t.id),
740
- className: cn(
741
- "flex-1 py-2.5 text-xs font-medium transition-colors border-b-2 -mb-px relative",
742
- isActive ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
743
- ),
744
- children: [
745
- t.label,
746
- showDot && /* @__PURE__ */ jsx("span", { className: "absolute top-2 right-[calc(50%-16px)] h-1.5 w-1.5 rounded-full bg-primary" })
747
- ]
748
- },
749
- t.id
750
- );
751
- }) }),
752
- /* @__PURE__ */ jsxs(Panel, { className: "flex-1", children: [
753
- tab === "endpoints" && /* @__PURE__ */ jsx(EndpointList, {}),
754
- tab === "request" && /* @__PURE__ */ jsx(RequestPanel, {}),
755
- tab === "response" && /* @__PURE__ */ jsx(ResponsePanel, {})
756
- ] })
757
- ] });
758
- }
759
- __name(MobileView, "MobileView");
760
- function DesktopView() {
761
- return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[260px_1fr_1fr] divide-x h-full min-h-0 overflow-hidden", children: [
762
- /* @__PURE__ */ jsxs(Panel, { children: [
763
- /* @__PURE__ */ jsx(PanelHeader, { title: "Endpoints" }),
764
- /* @__PURE__ */ jsx(EndpointList, {})
765
- ] }),
766
- /* @__PURE__ */ jsxs(Panel, { children: [
767
- /* @__PURE__ */ jsx(PanelHeader, { title: "Request" }),
768
- /* @__PURE__ */ jsx(RequestPanel, {})
769
- ] }),
770
- /* @__PURE__ */ jsxs(Panel, { children: [
771
- /* @__PURE__ */ jsx(PanelHeader, { title: "Response" }),
772
- /* @__PURE__ */ jsx(ResponsePanel, {})
773
- ] })
774
- ] });
775
- }
776
- __name(DesktopView, "DesktopView");
777
- var PlaygroundLayout = /* @__PURE__ */ __name(() => {
778
- const { isMobile } = useMobile();
779
- return /* @__PURE__ */ jsx(
780
- "div",
781
- {
782
- className: "flex flex-col overflow-hidden",
783
- style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
784
- children: isMobile ? /* @__PURE__ */ jsx(MobileView, {}) : /* @__PURE__ */ jsx(DesktopView, {})
785
- }
786
- );
787
- }, "PlaygroundLayout");
788
-
789
- export { PlaygroundLayout };
790
- //# sourceMappingURL=PlaygroundLayout-NONWOVQR.mjs.map
791
- //# sourceMappingURL=PlaygroundLayout-NONWOVQR.mjs.map