fumadocs-openapi 4.4.2 → 5.0.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.
@@ -0,0 +1,1033 @@
1
+ 'use client';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { forwardRef, useId, createContext, useContext, useState, useCallback, useLayoutEffect, useRef, useMemo } from 'react';
5
+ import { FormProvider, Controller, useFormContext, useFieldArray, useForm, useWatch } from 'react-hook-form';
6
+ import useSWRImmutable from 'swr/immutable';
7
+ import { Accordions, Accordion } from 'fumadocs-ui/components/accordion';
8
+ import { cn, buttonVariants } from 'fumadocs-ui/components/api';
9
+ import { u as useSchemaContext, a as useApiContext, S as SchemaContext } from './client-client-CMzXLiVt.js';
10
+ import { Slot } from '@radix-ui/react-slot';
11
+ import { cva } from 'class-variance-authority';
12
+ import { CircleCheckIcon, CircleXIcon, ChevronDown, ChevronUp, Check, Trash2, Plus } from 'lucide-react';
13
+ import { useOnChange } from 'fumadocs-core/utils/use-on-change';
14
+ import * as SelectPrimitive from '@radix-ui/react-select';
15
+ import * as Base from 'fumadocs-ui/components/codeblock';
16
+
17
+ const Form = FormProvider;
18
+ const FormFieldContext = /*#__PURE__*/ createContext({
19
+ name: ''
20
+ });
21
+ const FormItemContext = /*#__PURE__*/ createContext({
22
+ id: ''
23
+ });
24
+ function FormField({ ...props }) {
25
+ return /*#__PURE__*/ jsx(FormFieldContext.Provider, {
26
+ value: {
27
+ name: props.name
28
+ },
29
+ children: /*#__PURE__*/ jsx(Controller, {
30
+ ...props
31
+ })
32
+ });
33
+ }
34
+ function useFormField() {
35
+ const fieldContext = useContext(FormFieldContext);
36
+ const { id } = useContext(FormItemContext);
37
+ const { getFieldState, formState } = useFormContext();
38
+ const fieldState = getFieldState(fieldContext.name, formState);
39
+ return {
40
+ id,
41
+ name: fieldContext.name,
42
+ formItemId: `${id}-form-item`,
43
+ formDescriptionId: `${id}-form-item-description`,
44
+ isError: Boolean(fieldState.error)
45
+ };
46
+ }
47
+ const FormItem = /*#__PURE__*/ forwardRef(({ className, ...props }, ref)=>{
48
+ const id = useId();
49
+ return /*#__PURE__*/ jsx(FormItemContext.Provider, {
50
+ value: {
51
+ id
52
+ },
53
+ children: /*#__PURE__*/ jsx("div", {
54
+ ref: ref,
55
+ className: cn('flex flex-col gap-1', className),
56
+ ...props
57
+ })
58
+ });
59
+ });
60
+ FormItem.displayName = 'FormItem';
61
+ const labelVariants = cva('text-xs font-medium text-fd-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70');
62
+ const FormLabel = /*#__PURE__*/ forwardRef(({ className, ...props }, ref)=>{
63
+ const { isError, formItemId } = useFormField();
64
+ return /*#__PURE__*/ jsx("label", {
65
+ ref: ref,
66
+ className: cn(labelVariants(), isError && 'text-red-500', className),
67
+ htmlFor: formItemId,
68
+ ...props
69
+ });
70
+ });
71
+ FormLabel.displayName = 'FormLabel';
72
+ const FormControl = /*#__PURE__*/ forwardRef(({ ...props }, ref)=>{
73
+ const { isError, formItemId, formDescriptionId } = useFormField();
74
+ return /*#__PURE__*/ jsx(Slot, {
75
+ ref: ref,
76
+ id: formItemId,
77
+ "aria-describedby": formDescriptionId,
78
+ "aria-invalid": isError,
79
+ ...props
80
+ });
81
+ });
82
+ FormControl.displayName = 'FormControl';
83
+ const FormDescription = /*#__PURE__*/ forwardRef(({ className, ...props }, ref)=>{
84
+ const { formDescriptionId } = useFormField();
85
+ return /*#__PURE__*/ jsx("p", {
86
+ ref: ref,
87
+ id: formDescriptionId,
88
+ className: cn('text-xs text-fd-muted-foreground', className),
89
+ ...props
90
+ });
91
+ });
92
+ FormDescription.displayName = 'FormDescription';
93
+
94
+ /**
95
+ * Resolve reference
96
+ */ function resolve(schema, references) {
97
+ if (typeof schema === 'string') return references[schema];
98
+ if (schema.type !== 'ref') return schema;
99
+ return {
100
+ ...references[schema.schema],
101
+ description: schema.description,
102
+ isRequired: schema.isRequired
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Create request body from value
108
+ */ function createBodyFromValue(type, value, schema, references, dynamic) {
109
+ const result = convertValue('body', value, schema, references, dynamic);
110
+ if (type === 'json') {
111
+ return JSON.stringify(result);
112
+ }
113
+ const formData = new FormData();
114
+ if (typeof result !== 'object' || !result) {
115
+ throw new Error(`Unsupported body type: ${typeof result}, expected: object`);
116
+ }
117
+ for (const key of Object.keys(result)){
118
+ const prop = result[key];
119
+ if (typeof prop === 'object' && prop instanceof File) {
120
+ formData.set(key, prop);
121
+ }
122
+ if (Array.isArray(prop) && prop.every((item)=>item instanceof File)) {
123
+ for (const item of prop){
124
+ formData.append(key, item);
125
+ }
126
+ }
127
+ formData.set(key, JSON.stringify(prop));
128
+ }
129
+ return formData;
130
+ }
131
+ /**
132
+ * Convert a value (object or string) to the corresponding type of schema
133
+ *
134
+ * @param fieldName - field name of value
135
+ * @param value - the original value
136
+ * @param schema - the schema of field
137
+ * @param references - schema references
138
+ * @param dynamic - Dynamic references
139
+ */ function convertValue(fieldName, value, schema, references, dynamic) {
140
+ const isEmpty = value === '' || value === undefined || value === null;
141
+ if (isEmpty && schema.isRequired) return schema.type === 'boolean' ? false : '';
142
+ else if (isEmpty) return undefined;
143
+ if (Array.isArray(value) && schema.type === 'array') {
144
+ return value.map((item, index)=>convertValue(`${fieldName}.${String(index)}`, item, resolve(schema.items, references), references, dynamic));
145
+ }
146
+ if (schema.type === 'switcher') {
147
+ return convertDynamicValue(fieldName, value, references, dynamic);
148
+ }
149
+ if (typeof value === 'object' && schema.type === 'object') {
150
+ const entries = Object.keys(value).map((key)=>{
151
+ const prop = value[key];
152
+ const propFieldName = `${fieldName}.${key}`;
153
+ if (key in schema.properties) {
154
+ return [
155
+ key,
156
+ convertValue(propFieldName, prop, resolve(schema.properties[key], references), references, dynamic)
157
+ ];
158
+ }
159
+ if (schema.additionalProperties) {
160
+ return [
161
+ key,
162
+ convertDynamicValue(propFieldName, prop, references, dynamic)
163
+ ];
164
+ }
165
+ console.warn('Could not resolve field', propFieldName, dynamic);
166
+ return [
167
+ key,
168
+ prop
169
+ ];
170
+ });
171
+ return Object.fromEntries(entries);
172
+ }
173
+ switch(schema.type){
174
+ case 'number':
175
+ return Number(value);
176
+ case 'boolean':
177
+ return value === 'null' ? undefined : value === 'true';
178
+ case 'file':
179
+ return value; // file
180
+ default:
181
+ return String(value);
182
+ }
183
+ }
184
+ function convertDynamicValue(fieldName, value, references, dynamic) {
185
+ const fieldDynamic = dynamic.get(fieldName);
186
+ return convertValue(fieldName, value, fieldDynamic?.type === 'field' ? resolve(fieldDynamic.schema, references) : {
187
+ type: 'null',
188
+ isRequired: false
189
+ }, references, dynamic);
190
+ }
191
+ const statusMap = {
192
+ 400: {
193
+ description: 'Bad Request',
194
+ color: 'text-red-500',
195
+ icon: CircleXIcon
196
+ },
197
+ 401: {
198
+ description: 'Unauthorized',
199
+ color: 'text-red-500',
200
+ icon: CircleXIcon
201
+ },
202
+ 403: {
203
+ description: 'Forbidden',
204
+ color: 'text-red-500',
205
+ icon: CircleXIcon
206
+ },
207
+ 404: {
208
+ description: 'Not Found',
209
+ color: 'text-fd-muted-foreground',
210
+ icon: CircleXIcon
211
+ },
212
+ 500: {
213
+ description: 'Internal Server Error',
214
+ color: 'text-red-500',
215
+ icon: CircleXIcon
216
+ }
217
+ };
218
+ function getStatusInfo(status) {
219
+ if (status in statusMap) {
220
+ return statusMap[status];
221
+ }
222
+ if (status >= 200 && status < 300) {
223
+ return {
224
+ description: 'Successful',
225
+ color: 'text-green-500',
226
+ icon: CircleCheckIcon
227
+ };
228
+ }
229
+ if (status >= 400) {
230
+ return {
231
+ description: 'Error',
232
+ color: 'text-red-500',
233
+ icon: CircleXIcon
234
+ };
235
+ }
236
+ return {
237
+ description: 'No Description',
238
+ color: 'text-fd-muted-foreground',
239
+ icon: CircleXIcon
240
+ };
241
+ }
242
+
243
+ function getDefaultValue(item, references) {
244
+ if (item.type === 'object') return Object.fromEntries(Object.entries(item.properties).map(([key, prop])=>[
245
+ key,
246
+ getDefaultValue(references[prop.schema], references)
247
+ ]));
248
+ if (item.type === 'array') return [];
249
+ if (item.type === 'null') return null;
250
+ if (item.type === 'switcher') return getDefaultValue(resolve(Object.values(item.items)[0], references), references);
251
+ if (item.type === 'file') return undefined;
252
+ return String(item.defaultValue);
253
+ }
254
+ function getDefaultValues(field, context) {
255
+ return Object.fromEntries(field.map((p)=>[
256
+ p.name,
257
+ getDefaultValue(p, context)
258
+ ]));
259
+ }
260
+
261
+ const Select = SelectPrimitive.Root;
262
+ const SelectValue = SelectPrimitive.Value;
263
+ const SelectTrigger = /*#__PURE__*/ forwardRef(({ className, children, ...props }, ref)=>/*#__PURE__*/ jsxs(SelectPrimitive.Trigger, {
264
+ ref: ref,
265
+ className: cn('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', className),
266
+ ...props,
267
+ children: [
268
+ children,
269
+ /*#__PURE__*/ jsx(SelectPrimitive.Icon, {
270
+ asChild: true,
271
+ children: /*#__PURE__*/ jsx(ChevronDown, {
272
+ className: "size-4 text-fd-muted-foreground"
273
+ })
274
+ })
275
+ ]
276
+ }));
277
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
278
+ const SelectScrollUpButton = /*#__PURE__*/ forwardRef(({ className, ...props }, ref)=>/*#__PURE__*/ jsx(SelectPrimitive.ScrollUpButton, {
279
+ ref: ref,
280
+ className: cn('flex items-center justify-center py-1', className),
281
+ ...props,
282
+ children: /*#__PURE__*/ jsx(ChevronUp, {
283
+ className: "size-4"
284
+ })
285
+ }));
286
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
287
+ const SelectScrollDownButton = /*#__PURE__*/ forwardRef(({ className, ...props }, ref)=>/*#__PURE__*/ jsx(SelectPrimitive.ScrollDownButton, {
288
+ ref: ref,
289
+ className: cn('flex items-center justify-center py-1', className),
290
+ ...props,
291
+ children: /*#__PURE__*/ jsx(ChevronDown, {
292
+ className: "size-4"
293
+ })
294
+ }));
295
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
296
+ const SelectContent = /*#__PURE__*/ forwardRef(({ className, children, position = 'popper', ...props }, ref)=>/*#__PURE__*/ jsx(SelectPrimitive.Portal, {
297
+ children: /*#__PURE__*/ jsxs(SelectPrimitive.Content, {
298
+ ref: ref,
299
+ className: cn('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', className),
300
+ position: position,
301
+ ...props,
302
+ children: [
303
+ /*#__PURE__*/ jsx(SelectScrollUpButton, {}),
304
+ /*#__PURE__*/ jsx(SelectPrimitive.Viewport, {
305
+ className: cn('p-1', position === 'popper' && 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'),
306
+ children: children
307
+ }),
308
+ /*#__PURE__*/ jsx(SelectScrollDownButton, {})
309
+ ]
310
+ })
311
+ }));
312
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
313
+ const SelectLabel = /*#__PURE__*/ forwardRef(({ className, ...props }, ref)=>/*#__PURE__*/ jsx(SelectPrimitive.Label, {
314
+ ref: ref,
315
+ className: cn('py-1.5 pe-2 ps-6 text-sm font-semibold', className),
316
+ ...props
317
+ }));
318
+ SelectLabel.displayName = SelectPrimitive.Label.displayName;
319
+ const SelectItem = /*#__PURE__*/ forwardRef(({ className, children, ...props }, ref)=>/*#__PURE__*/ jsxs(SelectPrimitive.Item, {
320
+ ref: ref,
321
+ className: cn('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', className),
322
+ ...props,
323
+ children: [
324
+ /*#__PURE__*/ jsx(SelectPrimitive.ItemIndicator, {
325
+ className: "absolute start-2",
326
+ children: /*#__PURE__*/ jsx(Check, {
327
+ className: "size-4"
328
+ })
329
+ }),
330
+ /*#__PURE__*/ jsx(SelectPrimitive.ItemText, {
331
+ children: children
332
+ })
333
+ ]
334
+ }));
335
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
336
+ const SelectSeparator = /*#__PURE__*/ forwardRef(({ className, ...props }, ref)=>/*#__PURE__*/ jsx(SelectPrimitive.Separator, {
337
+ ref: ref,
338
+ className: cn('my-1 h-px bg-fd-muted', className),
339
+ ...props
340
+ }));
341
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
342
+
343
+ const Input = /*#__PURE__*/ React.forwardRef(({ className, type, ...props }, ref)=>{
344
+ return /*#__PURE__*/ jsx("input", {
345
+ type: type,
346
+ className: cn('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', className),
347
+ ref: ref,
348
+ ...props
349
+ });
350
+ });
351
+ Input.displayName = 'Input';
352
+
353
+ function renderInner({ field, ...props }) {
354
+ if (field.type === 'object') return /*#__PURE__*/ jsx(ObjectInput, {
355
+ field: field,
356
+ ...props,
357
+ className: cn('rounded-lg border bg-fd-accent/20 p-3', props.className)
358
+ });
359
+ if (field.type === 'switcher') return /*#__PURE__*/ jsx(Switcher, {
360
+ inline: true,
361
+ field: field,
362
+ ...props
363
+ });
364
+ if (field.type === 'array') return /*#__PURE__*/ jsx(ArrayInput, {
365
+ field: field,
366
+ ...props,
367
+ className: cn('rounded-lg border bg-fd-background p-3', props.className)
368
+ });
369
+ if (field.type === 'null') return null;
370
+ return /*#__PURE__*/ jsx(NormalInput, {
371
+ field: field,
372
+ ...props
373
+ });
374
+ }
375
+ function InputContainer({ toolbar, name, required, type, description, inline = false, ...props }) {
376
+ return /*#__PURE__*/ jsxs("div", {
377
+ ...props,
378
+ className: cn('flex flex-col gap-2', props.className),
379
+ children: [
380
+ /*#__PURE__*/ jsxs("div", {
381
+ className: cn(labelVariants(), 'inline-flex items-center gap-1'),
382
+ children: [
383
+ name,
384
+ required ? /*#__PURE__*/ jsx("span", {
385
+ className: "text-red-500",
386
+ children: "*"
387
+ }) : null,
388
+ /*#__PURE__*/ jsx("div", {
389
+ className: "flex-1"
390
+ }),
391
+ type ? /*#__PURE__*/ jsx("code", {
392
+ className: "text-xs text-fd-muted-foreground",
393
+ children: type
394
+ }) : null,
395
+ toolbar
396
+ ]
397
+ }),
398
+ !inline ? /*#__PURE__*/ jsx("p", {
399
+ className: "text-xs",
400
+ children: description
401
+ }) : null,
402
+ props.children
403
+ ]
404
+ });
405
+ }
406
+ function ObjectInput({ field, fieldName, ...props }) {
407
+ const { references } = useSchemaContext();
408
+ return /*#__PURE__*/ jsxs("div", {
409
+ ...props,
410
+ className: cn('flex flex-col gap-4', props.className),
411
+ children: [
412
+ Object.entries(field.properties).map(([key, child])=>/*#__PURE__*/ jsx(InputField, {
413
+ name: key,
414
+ field: resolve(child, references),
415
+ fieldName: `${fieldName}.${key}`
416
+ }, key)),
417
+ field.additionalProperties ? /*#__PURE__*/ jsx(AdditionalProperties, {
418
+ fieldName: fieldName,
419
+ type: field.additionalProperties
420
+ }) : null
421
+ ]
422
+ });
423
+ }
424
+ function AdditionalProperties({ fieldName, type }) {
425
+ const { control, setValue } = useFormContext();
426
+ const { references, dynamic } = useSchemaContext();
427
+ const [nextName, setNextName] = useState('');
428
+ const [properties, setProperties] = useState(()=>{
429
+ const d = dynamic.current.get(`additional_${fieldName}`);
430
+ if (d?.type === 'object') return d.properties;
431
+ return [];
432
+ });
433
+ useOnChange(properties, ()=>{
434
+ dynamic.current.set(`additional_${fieldName}`, {
435
+ type: 'object',
436
+ properties
437
+ });
438
+ });
439
+ const onAppend = useCallback(()=>{
440
+ const name = nextName.trim();
441
+ if (name.length === 0) return;
442
+ setProperties((p)=>{
443
+ if (p.includes(name)) return p;
444
+ setValue(`${fieldName}.${name}`, '');
445
+ setNextName('');
446
+ return [
447
+ ...p,
448
+ name
449
+ ];
450
+ });
451
+ }, [
452
+ nextName,
453
+ setValue,
454
+ fieldName
455
+ ]);
456
+ const types = typeof type === 'string' ? resolveDynamicTypes(references[type], references) : undefined;
457
+ return /*#__PURE__*/ jsxs(Fragment, {
458
+ children: [
459
+ properties.map((item)=>/*#__PURE__*/ jsx(Switcher, {
460
+ name: item,
461
+ field: {
462
+ type: 'switcher',
463
+ items: types ?? anyFields,
464
+ isRequired: false
465
+ },
466
+ fieldName: `${fieldName}.${item}`,
467
+ toolbar: /*#__PURE__*/ jsx("button", {
468
+ type: "button",
469
+ "aria-label": "Remove Item",
470
+ className: cn(buttonVariants({
471
+ color: 'secondary',
472
+ size: 'sm'
473
+ })),
474
+ onClick: ()=>{
475
+ setProperties((p)=>p.filter((prop)=>prop !== item));
476
+ control.unregister(`${fieldName}.${item}`);
477
+ },
478
+ children: /*#__PURE__*/ jsx(Trash2, {
479
+ className: "size-4"
480
+ })
481
+ })
482
+ }, item)),
483
+ /*#__PURE__*/ jsxs("div", {
484
+ className: "flex flex-row gap-1",
485
+ children: [
486
+ /*#__PURE__*/ jsx(Input, {
487
+ value: nextName,
488
+ placeholder: "Enter Property Name",
489
+ onChange: useCallback((e)=>{
490
+ setNextName(e.target.value);
491
+ }, []),
492
+ onKeyDown: useCallback((e)=>{
493
+ if (e.key === 'Enter') {
494
+ onAppend();
495
+ e.preventDefault();
496
+ }
497
+ }, [
498
+ onAppend
499
+ ])
500
+ }),
501
+ /*#__PURE__*/ jsx("button", {
502
+ type: "button",
503
+ className: cn(buttonVariants({
504
+ color: 'secondary'
505
+ })),
506
+ onClick: onAppend,
507
+ children: "New"
508
+ })
509
+ ]
510
+ })
511
+ ]
512
+ });
513
+ }
514
+ function resolveDynamicTypes(schema, references) {
515
+ if (schema.type !== 'switcher') return {
516
+ [schema.type]: schema
517
+ };
518
+ return Object.fromEntries(Object.entries(schema.items).map(([key, value])=>[
519
+ key,
520
+ resolve(value, references)
521
+ ]));
522
+ }
523
+ const anyFields = {
524
+ string: {
525
+ type: 'string',
526
+ isRequired: false,
527
+ defaultValue: ''
528
+ },
529
+ boolean: {
530
+ type: 'boolean',
531
+ isRequired: false,
532
+ defaultValue: ''
533
+ },
534
+ number: {
535
+ type: 'number',
536
+ isRequired: false,
537
+ defaultValue: ''
538
+ },
539
+ object: {
540
+ type: 'object',
541
+ properties: {},
542
+ additionalProperties: true,
543
+ isRequired: false
544
+ }
545
+ };
546
+ anyFields.array = {
547
+ type: 'array',
548
+ isRequired: false,
549
+ items: {
550
+ type: 'switcher',
551
+ isRequired: false,
552
+ items: anyFields
553
+ }
554
+ };
555
+ function Switcher({ field, fieldName, ...props }) {
556
+ const { references, dynamic } = useSchemaContext();
557
+ const items = Object.keys(field.items);
558
+ const [value, setValue] = useState(()=>{
559
+ const d = dynamic.current.get(fieldName);
560
+ if (d?.type === 'field') {
561
+ // schemas are passed from server components, they shouldn't be re-constructed
562
+ const cached = items.find((item)=>d.schema === field.items[item]);
563
+ if (cached) return cached;
564
+ }
565
+ return items[0];
566
+ });
567
+ useOnChange(value, ()=>{
568
+ if (!value) return;
569
+ dynamic.current.set(fieldName, {
570
+ type: 'field',
571
+ schema: field.items[value]
572
+ });
573
+ });
574
+ return /*#__PURE__*/ jsx(InputContainer, {
575
+ required: field.isRequired,
576
+ description: field.description,
577
+ ...props,
578
+ toolbar: /*#__PURE__*/ jsxs(Fragment, {
579
+ children: [
580
+ /*#__PURE__*/ jsxs(Select, {
581
+ value: value,
582
+ onValueChange: setValue,
583
+ children: [
584
+ /*#__PURE__*/ jsx(SelectTrigger, {
585
+ className: "h-auto p-1 text-xs",
586
+ children: /*#__PURE__*/ jsx(SelectValue, {})
587
+ }),
588
+ /*#__PURE__*/ jsx(SelectContent, {
589
+ children: items.map((item)=>/*#__PURE__*/ jsx(SelectItem, {
590
+ value: item,
591
+ children: item
592
+ }, item))
593
+ })
594
+ ]
595
+ }),
596
+ props.toolbar
597
+ ]
598
+ }),
599
+ children: renderInner({
600
+ field: resolve(field.items[value], references),
601
+ fieldName
602
+ })
603
+ });
604
+ }
605
+ function InputField({ field, fieldName, ...props }) {
606
+ const { references } = useSchemaContext();
607
+ if (field.type === 'null') return null;
608
+ if (field.type === 'object') {
609
+ return /*#__PURE__*/ jsx(InputContainer, {
610
+ required: field.isRequired,
611
+ type: field.type,
612
+ description: field.description,
613
+ ...props,
614
+ children: /*#__PURE__*/ jsx(ObjectInput, {
615
+ field: field,
616
+ fieldName: fieldName,
617
+ className: "rounded-lg border bg-fd-accent/20 p-3"
618
+ })
619
+ });
620
+ }
621
+ if (field.type === 'array') {
622
+ return /*#__PURE__*/ jsx(InputContainer, {
623
+ required: field.isRequired,
624
+ description: field.description ?? resolve(field.items, references).description,
625
+ type: "array",
626
+ ...props,
627
+ children: /*#__PURE__*/ jsx(ArrayInput, {
628
+ fieldName: fieldName,
629
+ field: field,
630
+ className: "rounded-lg border bg-fd-background p-3"
631
+ })
632
+ });
633
+ }
634
+ if (field.type === 'switcher') {
635
+ return /*#__PURE__*/ jsx(Switcher, {
636
+ field: field,
637
+ fieldName: fieldName,
638
+ ...props
639
+ });
640
+ }
641
+ const { toolbar, inline = false, name, ...rest } = props;
642
+ return /*#__PURE__*/ jsx(NormalInput, {
643
+ field: field,
644
+ fieldName: fieldName,
645
+ header: /*#__PURE__*/ jsxs(Fragment, {
646
+ children: [
647
+ /*#__PURE__*/ jsxs(FormLabel, {
648
+ className: "inline-flex items-center gap-1",
649
+ children: [
650
+ name,
651
+ field.isRequired ? /*#__PURE__*/ jsx("span", {
652
+ className: "text-red-500",
653
+ children: "*"
654
+ }) : null,
655
+ /*#__PURE__*/ jsx("code", {
656
+ className: "ms-auto text-xs text-fd-muted-foreground",
657
+ children: field.type
658
+ }),
659
+ toolbar
660
+ ]
661
+ }),
662
+ !inline ? /*#__PURE__*/ jsx(FormDescription, {
663
+ className: "text-xs",
664
+ children: field.description
665
+ }) : null
666
+ ]
667
+ }),
668
+ ...rest
669
+ });
670
+ }
671
+ function NormalInput({ fieldName, header, field, ...props }) {
672
+ const { control } = useFormContext();
673
+ if (field.type === 'file') {
674
+ return /*#__PURE__*/ jsx(FormField, {
675
+ control: control,
676
+ name: fieldName,
677
+ render: ({ field: { value: _value, onChange, ...restField } })=>/*#__PURE__*/ jsxs(FormItem, {
678
+ ...props,
679
+ children: [
680
+ header,
681
+ /*#__PURE__*/ jsx(FormControl, {
682
+ children: /*#__PURE__*/ jsx("input", {
683
+ type: "file",
684
+ multiple: false,
685
+ onChange: (e)=>{
686
+ if (!e.target.files) return;
687
+ onChange(e.target.files.item(0));
688
+ },
689
+ ...restField
690
+ })
691
+ })
692
+ ]
693
+ })
694
+ });
695
+ }
696
+ if (field.type === 'boolean') {
697
+ return /*#__PURE__*/ jsx(FormField, {
698
+ control: control,
699
+ name: fieldName,
700
+ render: ({ field: { value, onChange, ...restField } })=>/*#__PURE__*/ jsxs(FormItem, {
701
+ ...props,
702
+ children: [
703
+ header,
704
+ /*#__PURE__*/ jsxs(Select, {
705
+ value: value,
706
+ onValueChange: onChange,
707
+ disabled: restField.disabled,
708
+ children: [
709
+ /*#__PURE__*/ jsx(FormControl, {
710
+ children: /*#__PURE__*/ jsx(SelectTrigger, {
711
+ ...restField,
712
+ children: /*#__PURE__*/ jsx(SelectValue, {})
713
+ })
714
+ }),
715
+ /*#__PURE__*/ jsxs(SelectContent, {
716
+ children: [
717
+ /*#__PURE__*/ jsx(SelectItem, {
718
+ value: "true",
719
+ children: "True"
720
+ }),
721
+ /*#__PURE__*/ jsx(SelectItem, {
722
+ value: "false",
723
+ children: "False"
724
+ }),
725
+ field.isRequired ? null : /*#__PURE__*/ jsx(SelectItem, {
726
+ value: "null",
727
+ children: "Null"
728
+ })
729
+ ]
730
+ })
731
+ ]
732
+ })
733
+ ]
734
+ })
735
+ });
736
+ }
737
+ return /*#__PURE__*/ jsx(FormField, {
738
+ control: control,
739
+ name: fieldName,
740
+ render: ({ field: { value, ...restField } })=>/*#__PURE__*/ jsxs(FormItem, {
741
+ ...props,
742
+ children: [
743
+ header,
744
+ /*#__PURE__*/ jsx(FormControl, {
745
+ children: /*#__PURE__*/ jsx(Input, {
746
+ placeholder: "Enter value",
747
+ type: field.type === 'string' ? 'text' : 'number',
748
+ value: value,
749
+ ...restField
750
+ })
751
+ })
752
+ ]
753
+ })
754
+ });
755
+ }
756
+ function ArrayInput({ fieldName, field, ...props }) {
757
+ const { references } = useSchemaContext();
758
+ const items = resolve(field.items, references);
759
+ const { fields, append, remove } = useFieldArray({
760
+ name: fieldName
761
+ });
762
+ const handleAppend = useCallback(()=>{
763
+ append(getDefaultValue(items, references));
764
+ }, [
765
+ append,
766
+ references,
767
+ items
768
+ ]);
769
+ return /*#__PURE__*/ jsxs("div", {
770
+ ...props,
771
+ className: cn('flex flex-col gap-4', props.className),
772
+ children: [
773
+ fields.map((item, index)=>/*#__PURE__*/ jsx(InputField, {
774
+ inline: true,
775
+ name: `Item ${String(index + 1)}`,
776
+ field: items,
777
+ fieldName: `${fieldName}.${String(index)}`,
778
+ className: "flex-1",
779
+ toolbar: /*#__PURE__*/ jsx("button", {
780
+ type: "button",
781
+ "aria-label": "Remove Item",
782
+ className: cn(buttonVariants({
783
+ color: 'secondary',
784
+ size: 'sm'
785
+ })),
786
+ onClick: ()=>{
787
+ remove(index);
788
+ },
789
+ children: /*#__PURE__*/ jsx(Trash2, {
790
+ className: "size-4"
791
+ })
792
+ })
793
+ }, item.id)),
794
+ /*#__PURE__*/ jsxs("button", {
795
+ type: "button",
796
+ className: cn(buttonVariants({
797
+ color: 'secondary',
798
+ className: 'gap-1.5'
799
+ })),
800
+ onClick: handleAppend,
801
+ children: [
802
+ /*#__PURE__*/ jsx(Plus, {
803
+ className: "size-4"
804
+ }),
805
+ "New Item"
806
+ ]
807
+ })
808
+ ]
809
+ });
810
+ }
811
+
812
+ function CodeBlock({ code, lang = 'json', ...props }) {
813
+ const { highlighter } = useApiContext();
814
+ const [html, setHtml] = useState('');
815
+ useLayoutEffect(()=>{
816
+ if (!highlighter) return;
817
+ const themedHtml = highlighter.codeToHtml(code, {
818
+ lang,
819
+ defaultColor: false,
820
+ themes: {
821
+ light: 'github-light',
822
+ dark: 'github-dark'
823
+ }
824
+ });
825
+ setHtml(themedHtml);
826
+ }, [
827
+ code,
828
+ lang,
829
+ highlighter
830
+ ]);
831
+ return /*#__PURE__*/ jsx(Base.CodeBlock, {
832
+ className: "my-0",
833
+ children: /*#__PURE__*/ jsx(Base.Pre, {
834
+ ...props,
835
+ dangerouslySetInnerHTML: {
836
+ __html: html
837
+ }
838
+ })
839
+ });
840
+ }
841
+
842
+ function APIPlayground({ route, method = 'GET', bodyType, authorization, path = [], header = [], query = [], body, fields = {}, schemas }) {
843
+ const { baseUrl } = useApiContext();
844
+ const dynamicRef = useRef(new Map());
845
+ const [input, setInput] = useState();
846
+ const form = useForm({
847
+ defaultValues: {
848
+ authorization: authorization?.defaultValue,
849
+ path: getDefaultValues(path, schemas),
850
+ query: getDefaultValues(query, schemas),
851
+ header: getDefaultValues(header, schemas),
852
+ body: body ? getDefaultValue(body, schemas) : undefined
853
+ }
854
+ });
855
+ const testQuery = useSWRImmutable(input ? [
856
+ baseUrl,
857
+ route,
858
+ method,
859
+ input,
860
+ bodyType
861
+ ] : null, async ()=>{
862
+ if (!input) return;
863
+ const url = new URL(`${baseUrl ?? window.location.origin}${createPathnameFromInput(route, input.path)}`);
864
+ Object.keys(input.query).forEach((key)=>{
865
+ const paramValue = input.query[key];
866
+ if (typeof paramValue === 'string' && paramValue.length > 0) url.searchParams.append(key, paramValue);
867
+ });
868
+ const headers = new Headers({
869
+ 'Content-Type': 'application/json'
870
+ });
871
+ if (input.authorization) {
872
+ headers.append('Authorization', input.authorization);
873
+ }
874
+ Object.keys(input.header).forEach((key)=>{
875
+ const paramValue = input.header[key];
876
+ if (typeof paramValue === 'string' && paramValue.length > 0) headers.append(key, paramValue);
877
+ });
878
+ const bodyValue = body && input.body && Object.keys(input.body).length > 0 ? createBodyFromValue(bodyType, input.body, body, schemas, dynamicRef.current) : undefined;
879
+ const response = await fetch(url, {
880
+ method,
881
+ headers,
882
+ body: bodyValue
883
+ });
884
+ const data = await response.json().catch(()=>undefined);
885
+ return {
886
+ status: response.status,
887
+ data
888
+ };
889
+ }, {
890
+ shouldRetryOnError: false
891
+ });
892
+ const onSubmit = form.handleSubmit((value)=>{
893
+ setInput(value);
894
+ });
895
+ function renderCustomField(fieldName, info, field, key) {
896
+ if (field) {
897
+ return /*#__PURE__*/ jsx(Controller, {
898
+ control: form.control,
899
+ render: (props)=>field.render({
900
+ ...props,
901
+ info
902
+ }),
903
+ name: fieldName
904
+ }, key);
905
+ }
906
+ return /*#__PURE__*/ jsx(InputField, {
907
+ name: info.name,
908
+ fieldName: fieldName,
909
+ field: info
910
+ }, key);
911
+ }
912
+ return /*#__PURE__*/ jsx(Form, {
913
+ ...form,
914
+ children: /*#__PURE__*/ jsx(SchemaContext.Provider, {
915
+ value: useMemo(()=>({
916
+ references: schemas,
917
+ dynamic: dynamicRef
918
+ }), [
919
+ schemas
920
+ ]),
921
+ children: /*#__PURE__*/ jsxs("form", {
922
+ className: "not-prose flex flex-col gap-5 rounded-lg border bg-fd-card p-4",
923
+ onSubmit: onSubmit,
924
+ children: [
925
+ /*#__PURE__*/ jsxs("div", {
926
+ className: "flex flex-row gap-2",
927
+ children: [
928
+ /*#__PURE__*/ jsx(RouteDisplay, {
929
+ route: route
930
+ }),
931
+ /*#__PURE__*/ jsx("button", {
932
+ type: "submit",
933
+ className: cn(buttonVariants({
934
+ color: 'secondary'
935
+ })),
936
+ disabled: testQuery.isLoading,
937
+ children: "Send"
938
+ })
939
+ ]
940
+ }),
941
+ authorization ? renderCustomField('authorization', authorization, fields.auth) : null,
942
+ /*#__PURE__*/ jsxs(Accordions, {
943
+ type: "multiple",
944
+ className: cn('-m-4 border-0 text-sm', path.length === 0 && query.length === 0 && header.length === 0 && !body && 'hidden'),
945
+ children: [
946
+ path.length > 0 ? /*#__PURE__*/ jsx(Accordion, {
947
+ title: "Path",
948
+ children: /*#__PURE__*/ jsx("div", {
949
+ className: "flex flex-col gap-4",
950
+ children: path.map((field)=>renderCustomField(`path.${field.name}`, field, fields.path, field.name))
951
+ })
952
+ }) : null,
953
+ query.length > 0 ? /*#__PURE__*/ jsx(Accordion, {
954
+ title: "Query",
955
+ children: /*#__PURE__*/ jsx("div", {
956
+ className: "flex flex-col gap-4",
957
+ children: query.map((field)=>renderCustomField(`query.${field.name}`, field, fields.query, field.name))
958
+ })
959
+ }) : null,
960
+ header.length > 0 ? /*#__PURE__*/ jsx(Accordion, {
961
+ title: "Headers",
962
+ children: /*#__PURE__*/ jsx("div", {
963
+ className: "flex flex-col gap-4",
964
+ children: header.map((field)=>renderCustomField(`header.${field.name}`, field, fields.header, field.name))
965
+ })
966
+ }) : null,
967
+ body ? /*#__PURE__*/ jsx(Accordion, {
968
+ title: "Body",
969
+ children: body.type === 'object' && !fields.body ? /*#__PURE__*/ jsx(ObjectInput, {
970
+ field: body,
971
+ fieldName: "body"
972
+ }) : renderCustomField('body', body, fields.body)
973
+ }) : null
974
+ ]
975
+ }),
976
+ testQuery.data ? /*#__PURE__*/ jsx(ResultDisplay, {
977
+ data: testQuery.data
978
+ }) : null
979
+ ]
980
+ })
981
+ })
982
+ });
983
+ }
984
+ function createPathnameFromInput(route, input) {
985
+ let pathname = route;
986
+ Object.keys(input).forEach((key)=>{
987
+ const paramValue = input[key];
988
+ if (typeof paramValue === 'string' && paramValue.length > 0) pathname = pathname.replace(`{${key}}`, paramValue);
989
+ });
990
+ return pathname;
991
+ }
992
+ function RouteDisplay({ route }) {
993
+ const pathInput = useWatch({
994
+ name: 'path'
995
+ });
996
+ const pathname = useMemo(()=>createPathnameFromInput(route, pathInput), [
997
+ route,
998
+ pathInput
999
+ ]);
1000
+ return /*#__PURE__*/ jsx("code", {
1001
+ className: "flex-1 overflow-auto text-nowrap rounded-lg border bg-fd-muted px-3 py-1.5 text-sm text-fd-muted-foreground",
1002
+ children: pathname
1003
+ });
1004
+ }
1005
+ function ResultDisplay({ data }) {
1006
+ const statusInfo = useMemo(()=>getStatusInfo(data.status), [
1007
+ data.status
1008
+ ]);
1009
+ return /*#__PURE__*/ jsxs("div", {
1010
+ className: "flex flex-col gap-3 rounded-lg border bg-fd-card p-4",
1011
+ children: [
1012
+ /*#__PURE__*/ jsxs("div", {
1013
+ className: "inline-flex items-center gap-1.5 text-sm font-medium text-fd-foreground",
1014
+ children: [
1015
+ /*#__PURE__*/ jsx(statusInfo.icon, {
1016
+ className: cn('size-4', statusInfo.color)
1017
+ }),
1018
+ statusInfo.description
1019
+ ]
1020
+ }),
1021
+ /*#__PURE__*/ jsx("p", {
1022
+ className: "text-sm text-fd-muted-foreground",
1023
+ children: data.status
1024
+ }),
1025
+ data.data ? /*#__PURE__*/ jsx(CodeBlock, {
1026
+ code: JSON.stringify(data.data, null, 2),
1027
+ className: "max-h-[288px]"
1028
+ }) : null
1029
+ ]
1030
+ });
1031
+ }
1032
+
1033
+ export { APIPlayground };