mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
OpenAPI: Improve experience to customise API Playground
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
Improve experience to customise API Playground
|
||||
@@ -137,7 +137,7 @@ export function Rate({
|
||||
autoFocus
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
className="border rounded-lg bg-fd-secondary text-fd-secondary-foreground p-3 text-sm resize-none focus-visible:outline-none placeholder:text-fd-muted-foreground"
|
||||
className="border rounded-lg bg-fd-secondary text-fd-secondary-foreground p-3 resize-none focus-visible:outline-none placeholder:text-fd-muted-foreground"
|
||||
placeholder="Leave your feedback..."
|
||||
onKeyDown={(e) => {
|
||||
if (!e.shiftKey && e.key === 'Enter') {
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
"import": "./dist/ui/client.js",
|
||||
"types": "./dist/ui/client.d.ts"
|
||||
},
|
||||
"./playground": {
|
||||
"import": "./dist/playground/index.js",
|
||||
"types": "./dist/playground/index.d.ts"
|
||||
},
|
||||
"./scalar": {
|
||||
"import": "./dist/scalar/index.js",
|
||||
"types": "./dist/scalar/index.d.ts"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
export const Client = dynamic(() =>
|
||||
import('./client').then((mod) => mod.Client),
|
||||
);
|
||||
+63
-23
@@ -8,6 +8,9 @@ import {
|
||||
type FC,
|
||||
Fragment,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
createContext,
|
||||
useContext,
|
||||
} from 'react';
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||
import { cn, buttonVariants } from 'fumadocs-ui/components/api';
|
||||
@@ -18,19 +21,18 @@ import type {
|
||||
ControllerRenderProps,
|
||||
} from 'react-hook-form';
|
||||
import { useApiContext } from '@/ui/contexts/api';
|
||||
import type { FetchResult } from '@/ui/playground/fetcher';
|
||||
import type { FetchResult } from '@/playground/fetcher';
|
||||
import {
|
||||
getDefaultValue,
|
||||
getDefaultValues,
|
||||
} from '@/ui/playground/get-default-values';
|
||||
import { FieldSet, ObjectInput } from '@/ui/playground/inputs';
|
||||
} from '@/playground/get-default-values';
|
||||
import { FieldSet, ObjectInput } from '@/playground/inputs';
|
||||
import type {
|
||||
APIPlaygroundProps,
|
||||
PrimitiveRequestField,
|
||||
ReferenceSchema,
|
||||
RequestSchema,
|
||||
} from '@/render/operation/playground';
|
||||
import { type DynamicField, SchemaContext } from '../contexts/schema';
|
||||
import { getStatusInfo } from '@/ui/playground/status-info';
|
||||
} from '@/playground/index';
|
||||
import { getStatusInfo } from '@/playground/status-info';
|
||||
import { getUrl } from '@/utils/server-url';
|
||||
import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock';
|
||||
import { MethodLabel } from '@/ui/components/method-label';
|
||||
@@ -42,6 +44,7 @@ import {
|
||||
CollapsibleTrigger,
|
||||
} from 'fumadocs-ui/components/ui/collapsible';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import type { Security } from '@/utils/get-security';
|
||||
|
||||
interface FormValues {
|
||||
authorization:
|
||||
@@ -68,7 +71,56 @@ export interface CustomField<TName extends FieldPath<FormValues>, Info> {
|
||||
}) => ReactElement;
|
||||
}
|
||||
|
||||
function defaultAuthValue(auth: APIPlaygroundProps['authorization']) {
|
||||
export type ClientProps = HTMLAttributes<HTMLFormElement> & {
|
||||
route: string;
|
||||
method: string;
|
||||
authorization?: Security;
|
||||
path?: PrimitiveRequestField[];
|
||||
query?: PrimitiveRequestField[];
|
||||
header?: PrimitiveRequestField[];
|
||||
body?: RequestSchema & {
|
||||
mediaType: string;
|
||||
};
|
||||
schemas: Record<string, RequestSchema>;
|
||||
proxyUrl?: string;
|
||||
|
||||
fields?: {
|
||||
auth?: CustomField<'authorization', RequestSchema>;
|
||||
path?: CustomField<`path.${string}`, PrimitiveRequestField>;
|
||||
query?: CustomField<`query.${string}`, PrimitiveRequestField>;
|
||||
header?: CustomField<`header.${string}`, PrimitiveRequestField>;
|
||||
body?: CustomField<'body', RequestSchema>;
|
||||
};
|
||||
|
||||
components?: Partial<{
|
||||
ResultDisplay: FC<{ data: FetchResult }>;
|
||||
}>;
|
||||
};
|
||||
|
||||
interface SchemaContextType {
|
||||
references: Record<string, RequestSchema>;
|
||||
dynamic: RefObject<Map<string, DynamicField>>;
|
||||
}
|
||||
|
||||
export type DynamicField =
|
||||
| {
|
||||
type: 'object';
|
||||
properties: string[];
|
||||
}
|
||||
| {
|
||||
type: 'field';
|
||||
schema: RequestSchema | ReferenceSchema;
|
||||
};
|
||||
|
||||
const SchemaContext = createContext<SchemaContextType | undefined>(undefined);
|
||||
|
||||
export function useSchemaContext(): SchemaContextType {
|
||||
const ctx = useContext(SchemaContext);
|
||||
if (!ctx) throw new Error('Missing provider');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function defaultAuthValue(auth: ClientProps['authorization']) {
|
||||
if (!auth || auth.type === 'apiKey') return '';
|
||||
if (auth.type === 'http' && auth.scheme === 'basic') {
|
||||
return {
|
||||
@@ -80,7 +132,7 @@ function defaultAuthValue(auth: APIPlaygroundProps['authorization']) {
|
||||
return 'Bearer';
|
||||
}
|
||||
|
||||
export function APIPlayground({
|
||||
export function Client({
|
||||
route,
|
||||
method = 'GET',
|
||||
authorization,
|
||||
@@ -93,19 +145,7 @@ export function APIPlayground({
|
||||
proxyUrl,
|
||||
components: { ResultDisplay = DefaultResultDisplay } = {},
|
||||
...props
|
||||
}: APIPlaygroundProps & {
|
||||
fields?: {
|
||||
auth?: CustomField<'authorization', RequestSchema>;
|
||||
path?: CustomField<`path.${string}`, PrimitiveRequestField>;
|
||||
query?: CustomField<`query.${string}`, PrimitiveRequestField>;
|
||||
header?: CustomField<`header.${string}`, PrimitiveRequestField>;
|
||||
body?: CustomField<'body', RequestSchema>;
|
||||
};
|
||||
|
||||
components?: Partial<{
|
||||
ResultDisplay: FC<{ data: FetchResult }>;
|
||||
}>;
|
||||
} & HTMLAttributes<HTMLFormElement>) {
|
||||
}: ClientProps) {
|
||||
const { serverRef, servers } = useApiContext();
|
||||
const dynamicRef = useRef(new Map<string, DynamicField>());
|
||||
const form = useForm<FormValues>({
|
||||
@@ -453,7 +493,7 @@ function DefaultResultDisplay({ data }: { data: FetchResult }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function CollapsiblePanel({
|
||||
function CollapsiblePanel({
|
||||
title,
|
||||
children,
|
||||
...props
|
||||
+3
-6
@@ -1,9 +1,6 @@
|
||||
import { resolve } from '@/ui/playground/resolve';
|
||||
import {
|
||||
type ReferenceSchema,
|
||||
type RequestSchema,
|
||||
} from '@/render/operation/playground';
|
||||
import { type DynamicField } from '@/ui/contexts/schema';
|
||||
import { resolve } from '@/playground/resolve';
|
||||
import { type ReferenceSchema, type RequestSchema } from '@/playground/index';
|
||||
import type { DynamicField } from '@/playground/client';
|
||||
|
||||
export interface FetchOptions {
|
||||
url: string;
|
||||
+2
-5
@@ -1,8 +1,5 @@
|
||||
import type {
|
||||
PrimitiveRequestField,
|
||||
RequestSchema,
|
||||
} from '@/render/operation/playground';
|
||||
import { resolve } from '@/ui/playground/resolve';
|
||||
import type { PrimitiveRequestField, RequestSchema } from '@/playground/index';
|
||||
import { resolve } from '@/playground/resolve';
|
||||
|
||||
export function getDefaultValue(
|
||||
item: RequestSchema,
|
||||
+17
-22
@@ -10,7 +10,8 @@ import {
|
||||
type ParsedSchema,
|
||||
} from '@/utils/schema';
|
||||
import { getSecurities, type Security } from '@/utils/get-security';
|
||||
import { APIPlayground } from '@/ui/client';
|
||||
import { Client } from './client.lazy';
|
||||
import { type ClientProps } from './client';
|
||||
|
||||
interface BaseRequestField {
|
||||
name: string;
|
||||
@@ -82,28 +83,21 @@ interface Context {
|
||||
}
|
||||
|
||||
export interface APIPlaygroundProps {
|
||||
route: string;
|
||||
method: string;
|
||||
authorization?: Security;
|
||||
path?: PrimitiveRequestField[];
|
||||
query?: PrimitiveRequestField[];
|
||||
header?: PrimitiveRequestField[];
|
||||
body?: RequestSchema & {
|
||||
mediaType: string;
|
||||
};
|
||||
schemas: Record<string, RequestSchema>;
|
||||
proxyUrl?: string;
|
||||
}
|
||||
|
||||
export function Playground({
|
||||
path,
|
||||
method,
|
||||
ctx,
|
||||
}: {
|
||||
path: string;
|
||||
method: MethodInformation;
|
||||
ctx: RenderContext;
|
||||
}): ReactNode {
|
||||
|
||||
client?: Partial<ClientProps>;
|
||||
}
|
||||
|
||||
export type { ClientProps, CustomField } from './client';
|
||||
|
||||
export function APIPlayground({
|
||||
path,
|
||||
method,
|
||||
ctx,
|
||||
client,
|
||||
}: APIPlaygroundProps): ReactNode {
|
||||
let currentId = 0;
|
||||
const bodyContent = method.requestBody?.content;
|
||||
const mediaType = bodyContent ? getPreferredType(bodyContent) : undefined;
|
||||
@@ -123,7 +117,7 @@ export function Playground({
|
||||
? toSchema(bodyContent[mediaType].schema, true, context)
|
||||
: undefined;
|
||||
|
||||
const props: APIPlaygroundProps = {
|
||||
const props: ClientProps = {
|
||||
authorization: getAuthorizationField(method, ctx),
|
||||
method: method.method,
|
||||
route: path,
|
||||
@@ -145,9 +139,10 @@ export function Playground({
|
||||
: undefined,
|
||||
schemas: context.references,
|
||||
proxyUrl: ctx.proxyUrl,
|
||||
...client,
|
||||
};
|
||||
|
||||
return <APIPlayground {...props} />;
|
||||
return <Client {...props} />;
|
||||
}
|
||||
|
||||
function getAuthorizationField(
|
||||
+5
-4
@@ -1,3 +1,4 @@
|
||||
'use client';
|
||||
import {
|
||||
type HTMLAttributes,
|
||||
type LabelHTMLAttributes,
|
||||
@@ -14,11 +15,11 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/ui/components/select';
|
||||
import type { RequestSchema } from '@/render/operation/playground';
|
||||
import { resolve } from '@/ui/playground/resolve';
|
||||
import type { RequestSchema } from '@/playground/index';
|
||||
import { resolve } from '@/playground/resolve';
|
||||
import { Input, labelVariants } from '@/ui/components/input';
|
||||
import { getDefaultValue } from '@/ui/playground/get-default-values';
|
||||
import { useSchemaContext } from '@/ui/contexts/schema';
|
||||
import { getDefaultValue } from '@/playground/get-default-values';
|
||||
import { useSchemaContext } from '@/playground/client';
|
||||
|
||||
type FieldOfType<Type> = Extract<RequestSchema, { type: Type }>;
|
||||
interface InputHeaderProps {
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
type ReferenceSchema,
|
||||
type RequestSchema,
|
||||
} from '@/render/operation/playground';
|
||||
import { type ReferenceSchema, type RequestSchema } from '@/playground/index';
|
||||
|
||||
/**
|
||||
* Resolve reference
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
Property,
|
||||
ObjectCollapsible,
|
||||
} from '@/ui';
|
||||
import type { MethodInformation, RenderContext, ServerObject } from '@/types';
|
||||
import { Playground } from '@/render/operation/playground';
|
||||
import type { RenderContext, ServerObject } from '@/types';
|
||||
import { APIPlayground, type APIPlaygroundProps } from '@/playground';
|
||||
import { Sample, Samples } from '@/ui/client';
|
||||
|
||||
export interface ResponsesProps {
|
||||
@@ -99,20 +99,9 @@ export interface Renderer {
|
||||
*/
|
||||
ObjectCollapsible: ComponentType<ObjectCollapsibleProps>;
|
||||
Property: ComponentType<PropertyProps>;
|
||||
APIPlayground: ComponentType<{
|
||||
path: string;
|
||||
method: MethodInformation;
|
||||
ctx: RenderContext;
|
||||
}>;
|
||||
APIPlayground: ComponentType<APIPlaygroundProps>;
|
||||
}
|
||||
|
||||
export type {
|
||||
APIPlaygroundProps,
|
||||
RequestSchema,
|
||||
PrimitiveRequestField,
|
||||
ReferenceSchema,
|
||||
} from '@/render/operation/playground';
|
||||
|
||||
export function createRenders(
|
||||
shikiOptions: RenderContext['shikiOptions'],
|
||||
): Renderer {
|
||||
@@ -161,6 +150,6 @@ export function createRenders(
|
||||
/>
|
||||
</Tab>
|
||||
),
|
||||
APIPlayground: Playground,
|
||||
APIPlayground,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
export const APIPlayground = dynamic(() =>
|
||||
import('./playground').then((mod) => mod.APIPlayground),
|
||||
);
|
||||
|
||||
export const Samples = dynamic(() =>
|
||||
import('./sample-select').then((mod) => mod.Samples),
|
||||
);
|
||||
@@ -13,4 +9,5 @@ export const Sample = dynamic(() =>
|
||||
import('./sample-select').then((mod) => mod.Sample),
|
||||
);
|
||||
|
||||
export { useSchemaContext } from './contexts/schema';
|
||||
// for compatibility
|
||||
export { Client as APIPlayground } from '@/playground/client.lazy';
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { createContext, type RefObject, useContext } from 'react';
|
||||
import {
|
||||
type ReferenceSchema,
|
||||
type RequestSchema,
|
||||
} from '@/render/operation/playground';
|
||||
|
||||
interface SchemaContextType {
|
||||
references: Record<string, RequestSchema>;
|
||||
dynamic: RefObject<Map<string, DynamicField>>;
|
||||
}
|
||||
|
||||
export type DynamicField =
|
||||
| {
|
||||
type: 'object';
|
||||
properties: string[];
|
||||
}
|
||||
| {
|
||||
type: 'field';
|
||||
schema: RequestSchema | ReferenceSchema;
|
||||
};
|
||||
|
||||
export const SchemaContext = createContext<SchemaContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export function useSchemaContext(): SchemaContextType {
|
||||
const ctx = useContext(SchemaContext);
|
||||
if (!ctx) throw new Error('Missing provider');
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user