mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
OpenAPI: Make Next.js dependency optional
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
Make Next.js dependency optional
|
||||
@@ -82,7 +82,6 @@
|
||||
"@types/react": "^19.1.9",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"json-schema-typed": "^8.0.1",
|
||||
"next": "15.4.5",
|
||||
"openapi-types": "^12.1.3",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tsc-alias": "^1.8.16",
|
||||
@@ -91,7 +90,6 @@
|
||||
"peerDependencies": {
|
||||
"@scalar/api-client-react": "*",
|
||||
"@types/react": "*",
|
||||
"next": "14.x.x || 15.x.x",
|
||||
"react": "18.x.x || 19.x.x",
|
||||
"react-dom": "18.x.x || 19.x.x"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { MethodInformation, RenderContext } from '@/types';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { lazy } from 'react';
|
||||
|
||||
const Client = dynamic(() => import('./client'));
|
||||
const Client = lazy(() => import('./client'));
|
||||
|
||||
export function APIPlayground({
|
||||
path,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
const keys = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'] as const;
|
||||
|
||||
type Proxy = {
|
||||
[K in (typeof keys)[number]]: (req: NextRequest) => Promise<Response>;
|
||||
[K in (typeof keys)[number]]: (req: Request) => Promise<Response>;
|
||||
};
|
||||
|
||||
interface CreateProxyOptions {
|
||||
@@ -48,26 +46,23 @@ export function createProxy(options: CreateProxyOptions = {}): Proxy {
|
||||
} = options;
|
||||
const handlers: Partial<Proxy> = {};
|
||||
|
||||
async function handler(req: NextRequest): Promise<Response> {
|
||||
const url = req.nextUrl.searchParams.get('url');
|
||||
async function handler(req: Request): Promise<Response> {
|
||||
const searchParams = new URL(req.url).searchParams;
|
||||
const url = searchParams.get('url');
|
||||
|
||||
if (!url) {
|
||||
if (!url)
|
||||
return Response.json(
|
||||
'[Proxy] A `url` query parameter is required for proxy url',
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let parsedUrl;
|
||||
try {
|
||||
parsedUrl = new URL(url);
|
||||
} catch {
|
||||
const parsedUrl = URL.parse(url);
|
||||
if (!parsedUrl)
|
||||
return Response.json('[Proxy] Invalid `url` parameter value.', {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (allowedOrigins && !allowedOrigins.includes(parsedUrl.origin)) {
|
||||
return Response.json(
|
||||
|
||||
@@ -21,6 +21,7 @@ export type PathItemObject = V3_1.PathItemObject;
|
||||
export type TagObject = V3_1.TagObject;
|
||||
export type ServerObject = NoReference<V3_1.ServerObject>;
|
||||
export type CallbackObject = NoReference<V3_1.CallbackObject>;
|
||||
export type ServerVariableObject = NoReference<V3_1.ServerVariableObject>;
|
||||
|
||||
export type MethodInformation = NoReference<OperationObject> & {
|
||||
method: string;
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { lazy } from 'react';
|
||||
|
||||
export const CodeExampleProvider = dynamic(() =>
|
||||
import('./contexts/code-example').then((mod) => mod.CodeExampleProvider),
|
||||
export const CodeExampleProvider = lazy(() =>
|
||||
import('./contexts/code-example').then((mod) => ({
|
||||
default: mod.CodeExampleProvider,
|
||||
})),
|
||||
);
|
||||
export const CodeExample = dynamic(() =>
|
||||
import('./contexts/code-example').then((mod) => mod.CodeExample),
|
||||
export const CodeExample = lazy(() =>
|
||||
import('./contexts/code-example').then((mod) => ({
|
||||
default: mod.CodeExample,
|
||||
})),
|
||||
);
|
||||
export const CodeExampleSelector = dynamic(() =>
|
||||
import('./contexts/code-example').then((mod) => mod.CodeExampleSelector),
|
||||
export const CodeExampleSelector = lazy(() =>
|
||||
import('./contexts/code-example').then((mod) => ({
|
||||
default: mod.CodeExampleSelector,
|
||||
})),
|
||||
);
|
||||
|
||||
export const ClientLazy = dynamic(() => import('@/playground/client'));
|
||||
export const ClientLazy = lazy(() => import('@/playground/client'));
|
||||
|
||||
export const ApiProvider = dynamic(() =>
|
||||
import('./contexts/api').then((mod) => mod.ApiProvider),
|
||||
export const ApiProvider = lazy(() =>
|
||||
import('./contexts/api').then((mod) => ({ default: mod.ApiProvider })),
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
type ReactNode,
|
||||
use,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
@@ -43,8 +43,8 @@ export function SelectTab({
|
||||
}: ComponentProps<'div'> & {
|
||||
value: string;
|
||||
}) {
|
||||
const { type } = use(Context)!;
|
||||
if (value !== type) return;
|
||||
const ctx = useContext(Context);
|
||||
if (value !== ctx?.type) return;
|
||||
|
||||
return <div {...props}>{props.children}</div>;
|
||||
}
|
||||
@@ -53,7 +53,7 @@ export function SelectTabTrigger({
|
||||
items,
|
||||
...props
|
||||
}: ComponentProps<typeof SelectTrigger> & { items: string[] }) {
|
||||
const { type, setType } = use(Context)!;
|
||||
const { type, setType } = useContext(Context)!;
|
||||
|
||||
return (
|
||||
<Select value={type ?? ''} onValueChange={setType}>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
'use client';
|
||||
import {
|
||||
type SelectedServer,
|
||||
useApiContext,
|
||||
useServerSelectContext,
|
||||
} from '@/ui/contexts/api';
|
||||
import { useApiContext, useServerSelectContext } from '@/ui/contexts/api';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -24,12 +20,12 @@ import {
|
||||
} from '@/ui/components/dialog';
|
||||
import { resolveServerUrl, withBase } from '@/utils/url';
|
||||
import { FormProvider, useController, useForm } from 'react-hook-form';
|
||||
import type { OpenAPIV3_1 } from 'openapi-types';
|
||||
import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
|
||||
import type { ServerVariableObject } from '@/types';
|
||||
|
||||
export default function ServerSelect(props: HTMLAttributes<HTMLDivElement>) {
|
||||
const { servers } = useApiContext();
|
||||
const { server, setServer } = useServerSelectContext();
|
||||
const { server, setServer, setServerVariables } = useServerSelectContext();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
|
||||
@@ -38,6 +34,9 @@ export default function ServerSelect(props: HTMLAttributes<HTMLDivElement>) {
|
||||
}, []);
|
||||
|
||||
if (servers.length <= 0) return;
|
||||
const serverSchema = server
|
||||
? servers.find((obj) => obj.url === server.url)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
@@ -69,26 +68,33 @@ export default function ServerSelect(props: HTMLAttributes<HTMLDivElement>) {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{server && server.variables && (
|
||||
<ServerSelectContent key={server.url} server={server} />
|
||||
{server?.variables && serverSchema?.variables && (
|
||||
<ServerSelectContent
|
||||
key={server.url}
|
||||
defaultValues={server.variables}
|
||||
schema={serverSchema.variables}
|
||||
onChange={setServerVariables}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function ServerSelectContent({ server }: { server: SelectedServer }) {
|
||||
const { servers } = useApiContext();
|
||||
const { setServerVariables } = useServerSelectContext();
|
||||
const schema = servers.find((item) => item.url === server.url);
|
||||
function ServerSelectContent({
|
||||
defaultValues,
|
||||
onChange,
|
||||
schema,
|
||||
}: {
|
||||
defaultValues: Record<string, string>;
|
||||
onChange: (values: Record<string, string>) => void;
|
||||
schema: Record<string, ServerVariableObject>;
|
||||
}) {
|
||||
const form = useForm({
|
||||
defaultValues: server.variables,
|
||||
});
|
||||
|
||||
const onChangeDebounced = useEffectEvent((values: Record<string, string>) => {
|
||||
setServerVariables(values);
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const onChangeDebounced = useEffectEvent(onChange);
|
||||
useEffect(() => {
|
||||
let timer: number | null = null;
|
||||
|
||||
@@ -105,12 +111,10 @@ function ServerSelectContent({ server }: { server: SelectedServer }) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- `form` shouldn't be included
|
||||
}, []);
|
||||
|
||||
if (!schema?.variables) return;
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<div className="flex flex-col gap-4">
|
||||
{Object.entries(schema.variables).map(([key, variable]) => {
|
||||
{Object.entries(schema).map(([key, variable]) => {
|
||||
return (
|
||||
<fieldset key={key} className="flex flex-col gap-1">
|
||||
<label className={cn(labelVariants())} htmlFor={key}>
|
||||
@@ -119,10 +123,7 @@ function ServerSelectContent({ server }: { server: SelectedServer }) {
|
||||
<p className="text-xs text-fd-muted-foreground empty:hidden">
|
||||
{variable.description}
|
||||
</p>
|
||||
<Field
|
||||
fieldName={key}
|
||||
variable={variable as OpenAPIV3_1.ServerVariableObject}
|
||||
/>
|
||||
<Field fieldName={key} variable={variable} />
|
||||
</fieldset>
|
||||
);
|
||||
})}
|
||||
@@ -135,7 +136,7 @@ function Field({
|
||||
fieldName,
|
||||
variable,
|
||||
}: {
|
||||
variable: OpenAPIV3_1.ServerVariableObject;
|
||||
variable: ServerVariableObject;
|
||||
fieldName: string;
|
||||
}) {
|
||||
const { field } = useController({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type JSONSchema } from 'json-schema-typed/draft-2020-12';
|
||||
import { ReferenceObject } from '@/types';
|
||||
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
||||
import type { ReferenceObject } from '@/types';
|
||||
|
||||
export type NoReference<T> = T extends (infer I)[]
|
||||
? NoReference<I>[]
|
||||
|
||||
Generated
-3
@@ -1348,9 +1348,6 @@ importers:
|
||||
json-schema-typed:
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
next:
|
||||
specifier: 15.4.5
|
||||
version: 15.4.5(@babel/[email protected])(@opentelemetry/[email protected])([email protected])([email protected]([email protected]))([email protected])
|
||||
openapi-types:
|
||||
specifier: ^12.1.3
|
||||
version: 12.1.3
|
||||
|
||||
Reference in New Issue
Block a user