mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
OpenAPI: support JSON mode for body input
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
support JSON mode for body input
|
||||
@@ -63,7 +63,7 @@ export function OauthDialog({
|
||||
redirect_uri: state.redirect_uri,
|
||||
client_id: state.client_id,
|
||||
client_secret: state.client_secret,
|
||||
}).toString(),
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
@@ -125,7 +125,7 @@ export function OauthDialog({
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'client_credentials',
|
||||
scope: Object.keys(value.scopes).join('+'),
|
||||
}).toString(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
'use client';
|
||||
import {
|
||||
type ReactElement,
|
||||
type HTMLAttributes,
|
||||
useMemo,
|
||||
useRef,
|
||||
useEffect,
|
||||
createContext,
|
||||
type FC,
|
||||
Fragment,
|
||||
type HTMLAttributes,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type {
|
||||
ControllerFieldState,
|
||||
ControllerRenderProps,
|
||||
FieldPath,
|
||||
UseFormStateReturn,
|
||||
} from 'react-hook-form';
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useForm,
|
||||
useFormContext,
|
||||
} from 'react-hook-form';
|
||||
import type {
|
||||
FieldPath,
|
||||
UseFormStateReturn,
|
||||
ControllerFieldState,
|
||||
ControllerRenderProps,
|
||||
} from 'react-hook-form';
|
||||
import { useApiContext, useServerSelectContext } from '@/ui/contexts/api';
|
||||
import type { FetchResult } from '@/playground/fetcher';
|
||||
import { FieldSet, ObjectInput } from './inputs';
|
||||
import { FieldSet, JsonInput, ObjectInput } from './inputs';
|
||||
import type {
|
||||
PrimitiveRequestField,
|
||||
ReferenceSchema,
|
||||
@@ -55,6 +55,7 @@ import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
|
||||
import type { RequestData } from '@/requests/_shared';
|
||||
import { buttonVariants } from 'fumadocs-ui/components/ui/button';
|
||||
import { cn } from 'fumadocs-ui/utils/cn';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
interface FormValues {
|
||||
authorization:
|
||||
@@ -245,7 +246,7 @@ export default function Client({
|
||||
<form
|
||||
{...rest}
|
||||
className={cn(
|
||||
'not-prose flex flex-col rounded-xl border p-3 gap-3 shadow-md overflow-hidden',
|
||||
'not-prose flex flex-col rounded-xl border p-3 gap-4 shadow-md overflow-hidden bg-fd-card text-fd-card-foreground',
|
||||
rest.className,
|
||||
)}
|
||||
onSubmit={onSubmit}
|
||||
@@ -396,20 +397,62 @@ function FormBody({
|
||||
})}
|
||||
|
||||
{body ? (
|
||||
<CollapsiblePanel title="Body">
|
||||
{fields.body ? (
|
||||
renderCustomField('body', body, fields.body)
|
||||
) : body.type === 'object' ? (
|
||||
<ObjectInput field={body} fieldName="body" />
|
||||
) : (
|
||||
<FieldSet field={body} fieldName="body" />
|
||||
)}
|
||||
</CollapsiblePanel>
|
||||
fields.body ? (
|
||||
<CollapsiblePanel title="Body">
|
||||
{renderCustomField('body', body, fields.body)}
|
||||
</CollapsiblePanel>
|
||||
) : (
|
||||
<BodyInput field={body} />
|
||||
)
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const bodyTypeVariants = cva(
|
||||
'p-1 rounded-lg font-medium text-xs transition-colors',
|
||||
{
|
||||
variants: {
|
||||
active: {
|
||||
true: 'bg-fd-primary/10 text-fd-primary',
|
||||
false: 'text-fd-muted-foreground',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function BodyInput({ field }: { field: RequestSchema }) {
|
||||
const [isJson, setIsJson] = useState(false);
|
||||
|
||||
return (
|
||||
<CollapsiblePanel title="Body">
|
||||
<div className="grid grid-cols-2 border bg-fd-muted rounded-lg">
|
||||
<button
|
||||
className={cn(bodyTypeVariants({ active: !isJson }))}
|
||||
onClick={() => setIsJson(false)}
|
||||
type="button"
|
||||
>
|
||||
Simple
|
||||
</button>
|
||||
<button
|
||||
className={cn(bodyTypeVariants({ active: isJson }))}
|
||||
onClick={() => setIsJson(true)}
|
||||
type="button"
|
||||
>
|
||||
JSON Mode
|
||||
</button>
|
||||
</div>
|
||||
{isJson ? (
|
||||
<JsonInput fieldName="body" />
|
||||
) : field.type === 'object' ? (
|
||||
<ObjectInput field={field} fieldName="body" />
|
||||
) : (
|
||||
<FieldSet field={field} fieldName="body" />
|
||||
)}
|
||||
</CollapsiblePanel>
|
||||
);
|
||||
}
|
||||
|
||||
function renderCustomField(
|
||||
fieldName: string,
|
||||
info: RequestSchema & { name?: string },
|
||||
@@ -546,20 +589,17 @@ function CollapsiblePanel({
|
||||
title,
|
||||
children,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLDivElement> & {
|
||||
}: Omit<HTMLAttributes<HTMLDivElement>, 'title'> & {
|
||||
title: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Collapsible
|
||||
{...props}
|
||||
className="rounded-xl bg-fd-card border text-fd-card-foreground"
|
||||
>
|
||||
<CollapsibleTrigger className="group w-full inline-flex items-center gap-2 p-3 text-sm font-medium">
|
||||
<Collapsible {...props} className="border-b -m-2 last:border-b-0">
|
||||
<CollapsibleTrigger className="group w-full flex items-center gap-2 p-2 text-sm font-medium">
|
||||
{title}
|
||||
<ChevronDown className="ms-auto size-4 text-fd-muted-foreground group-data-[state=open]:rotate-180" />
|
||||
<ChevronDown className="ms-auto size-3.5 text-fd-muted-foreground group-data-[state=open]:rotate-180" />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-3 p-3">{children}</div>
|
||||
<div className="flex flex-col gap-3 p-2">{children}</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,12 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import {
|
||||
Controller,
|
||||
useController,
|
||||
useFieldArray,
|
||||
useFormContext,
|
||||
} from 'react-hook-form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -81,6 +86,31 @@ export function ObjectInput({
|
||||
);
|
||||
}
|
||||
|
||||
export function JsonInput({ fieldName }: { fieldName: string }) {
|
||||
const controller = useController({
|
||||
name: fieldName,
|
||||
});
|
||||
const [value, setValue] = useState(() =>
|
||||
JSON.stringify(controller.field.value, null, 2),
|
||||
);
|
||||
|
||||
return (
|
||||
<textarea
|
||||
{...controller.field}
|
||||
value={value}
|
||||
className="w-full h-[400px] resize-none rounded-lg border p-2 bg-fd-secondary text-fd-secondary-foreground focus-visible:outline-none"
|
||||
onChange={(v) => {
|
||||
setValue(v.target.value);
|
||||
try {
|
||||
controller.field.onChange(JSON.parse(v.target.value));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function AdditionalProperties({
|
||||
fieldName,
|
||||
type,
|
||||
|
||||
@@ -14,10 +14,7 @@ export function getSampleRequest(url: string, data: RequestData): string {
|
||||
}
|
||||
|
||||
if (Object.keys(headers).length > 0) {
|
||||
options.set(
|
||||
'headers',
|
||||
JSON.stringify(headers, null, 2).replaceAll('\n', '\n '),
|
||||
);
|
||||
options.set('headers', JSON.stringify(headers, null, 2));
|
||||
}
|
||||
|
||||
if (data.bodyMediaType === 'multipart/form-data' && data.body) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cva } from 'class-variance-authority';
|
||||
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
export const labelVariants = cva(
|
||||
'text-[13px] text-fd-card-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
'text-xs font-medium text-fd-foreground/80 peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
);
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
|
||||
@@ -101,16 +101,15 @@ const SelectItem = forwardRef<
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex select-none flex-row items-center rounded-md py-1.5 pe-2 ps-6 text-[13px] outline-none focus:bg-fd-accent focus:text-fd-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'flex select-none flex-row items-center rounded-md py-1.5 px-2 text-[13px] outline-none focus:bg-fd-accent focus:text-fd-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.ItemIndicator className="absolute start-2">
|
||||
<Check className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
<SelectPrimitive.ItemIndicator className="ms-auto">
|
||||
<Check className="size-3.5 text-fd-primary" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
Reference in New Issue
Block a user