fumadocs-openapi
Version:
Generate MDX docs for your OpenAPI spec
231 lines (228 loc) • 9.09 kB
JavaScript
'use client';
import { cn } from "../../utils/cn.js";
import { Badge } from "../components/method-label.js";
import { Fragment, createContext, use, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
import { ChevronDown } from "lucide-react";
import { cva } from "class-variance-authority";
import { buttonVariants } from "fumadocs-ui/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "fumadocs-ui/components/ui/collapsible";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "fumadocs-ui/components/tabs";
import { Popover, PopoverContent, PopoverTrigger } from "fumadocs-ui/components/ui/popover";
//#region src/ui/schema/client.tsx
const typeVariants = cva("text-sm text-start text-fd-muted-foreground font-mono", { variants: { variant: { trigger: "underline hover:text-fd-accent-foreground data-[state=open]:text-fd-accent-foreground" } } });
const PropertyContext = createContext({ renderRef: (props) => /* @__PURE__ */ jsx(RootRef, { ...props }) });
const DataContext = createContext(null);
function useData() {
return use(DataContext);
}
function useProperty() {
return use(PropertyContext);
}
function SchemaUI({ name, required = false, as = "property", generated }) {
const schema = generated.refs[generated.$root];
return /* @__PURE__ */ jsx(DataContext, {
value: useMemo(() => generated, [generated]),
children: as === "property" || !isExpandable(schema) ? /* @__PURE__ */ jsx(SchemaUIProperty, {
name,
$type: generated.$root,
overrides: { required }
}) : /* @__PURE__ */ jsx(SchemaUIContent, { $type: generated.$root })
});
}
function SchemaUIContent({ $type }) {
const { refs } = useData();
const schema = refs[$type];
let child = null;
if (schema.type === "or" && schema.items.length > 0) child = /* @__PURE__ */ jsxs(Fragment$1, { children: [child, /* @__PURE__ */ jsxs(Tabs, {
defaultValue: schema.items[0].$type,
children: [/* @__PURE__ */ jsx(TabsList, { children: schema.items.map((item) => /* @__PURE__ */ jsx(TabsTrigger, {
value: item.$type,
children: item.name
}, item.$type)) }), schema.items.map((item) => /* @__PURE__ */ jsx(TabsContent, {
value: item.$type,
forceMount: void 0,
className: "py-0",
children: /* @__PURE__ */ jsx(SchemaUIContent, { ...item })
}, item.$type))]
})] });
if (schema.type === "object" && schema.props.length > 0) child = /* @__PURE__ */ jsxs(Fragment$1, { children: [child, schema.props.map((prop) => /* @__PURE__ */ jsx(SchemaUIProperty, {
name: prop.name,
$type: prop.$type,
overrides: { required: prop.required }
}, prop.name))] });
if (schema.type === "array") child = /* @__PURE__ */ jsxs(Fragment$1, { children: [child, /* @__PURE__ */ jsxs(Collapsible, {
className: "my-2",
children: [/* @__PURE__ */ jsxs(CollapsibleTrigger, {
className: cn(buttonVariants({
color: "secondary",
size: "sm"
}), "group px-3 py-2 data-[state=open]:rounded-b-none"),
children: ["Array Item", /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 text-fd-muted-foreground group-data-[state=open]:rotate-180" })]
}), /* @__PURE__ */ jsx(CollapsibleContent, {
className: "-mt-px bg-fd-card px-3 rounded-lg rounded-tl-none border shadow-sm",
children: /* @__PURE__ */ jsx(SchemaUIContent, { $type: schema.item.$type })
})]
})] });
return child;
}
function SchemaUIProperty({ name, $type, overrides }) {
const { renderRef } = useProperty();
const { refs } = useData();
const schema = refs[$type];
let type = schema.typeName;
if ((schema.type === "or" || schema.type === "and") && schema.items.length > 0) type = renderRef({
text: schema.aliasName,
pathName: name,
$ref: $type,
inlineUnion: true
});
if (schema.type === "object" && schema.props.length > 0) type = renderRef({
text: schema.aliasName,
pathName: name,
$ref: $type
});
if (schema.type === "array") type = renderRef({
text: schema.aliasName,
pathName: name,
$ref: schema.item.$type
});
return /* @__PURE__ */ jsxs(Property, {
name,
type,
deprecated: schema.deprecated,
...overrides,
children: [schema.description, schema.infoTags && schema.infoTags.length > 0 && /* @__PURE__ */ jsx("div", {
className: "flex flex-row gap-2 flex-wrap my-2 not-prose empty:hidden",
children: schema.infoTags.map((tag, i) => /* @__PURE__ */ jsx(Fragment, { children: tag }, i))
})]
});
}
function SchemaUIPopover({ initialPath }) {
const [path, setPath] = useState(initialPath);
const ref = useRef(null);
const last = path.findLast((item) => item.$ref !== void 0);
useEffect(() => {
const element = ref.current;
if (!element || !element.parentElement) return;
element.parentElement.scrollTop = 0;
}, [last?.$ref]);
const context = useMemo(() => ({ renderRef: (props) => /* @__PURE__ */ jsx(LinkRef, {
...props,
onInsert: (name, $ref) => {
setPath((path$1) => [...path$1, {
name,
$ref
}]);
}
}) }), []);
if (!last) return;
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
className: "sticky top-0 flex flex-row flex-wrap items-center text-sm font-medium font-mono bg-fd-muted p-2",
children: path.map((item, i) => {
const className = cn(path.some((other, j) => j < i && other.$ref === item.$ref) && "text-orange-400", item.$ref && "hover:underline hover:text-fd-accent-foreground");
const node = item.$ref ? /* @__PURE__ */ jsx("button", {
onClick: () => setPath((path$1) => path$1.slice(0, i + 1)),
className,
children: item.name
}) : /* @__PURE__ */ jsx("span", {
className,
children: item.name
});
return /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && ".", node] }, i);
})
}), /* @__PURE__ */ jsx(PropertyContext, {
value: context,
children: /* @__PURE__ */ jsx("div", {
ref,
className: "px-2",
children: /* @__PURE__ */ jsx(SchemaUIContent, { $type: last.$ref })
})
})] });
}
function RootRef({ text, $ref, pathName, inlineUnion }) {
const { refs } = useData();
const ref = useCallback((element) => {
if (!element || element.style.getPropertyValue("--initial-height")) return;
element.style.setProperty("--initial-height", `${element.clientHeight}px`);
}, []);
const schema = refs[$ref];
if (inlineUnion && (schema.type === "and" || schema.type === "or")) {
const sep = schema.type === "and" ? "&" : "|";
return /* @__PURE__ */ jsx("span", {
className: cn(typeVariants(), "flex flex-row gap-2 items-center flex-wrap"),
children: schema.items.map((item, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && /* @__PURE__ */ jsx("span", { children: sep }), /* @__PURE__ */ jsx(RootRef, {
pathName,
text: item.name,
$ref: item.$type
})] }, item.$type))
});
}
if (!isExpandable(refs[$ref])) return /* @__PURE__ */ jsx("span", {
className: cn(typeVariants()),
children: text
});
return /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
className: cn(typeVariants({ variant: "trigger" })),
children: text
}), /* @__PURE__ */ jsx(PopoverContent, {
ref,
className: "w-[600px] min-h-(--initial-height,0) max-h-[460px] p-0",
children: /* @__PURE__ */ jsx(SchemaUIPopover, { initialPath: [{
name: /* @__PURE__ */ jsxs(Fragment$1, { children: [pathName, schema.type === "array" && "[]"] }),
$ref
}] })
})] });
}
function LinkRef({ $ref, pathName, onInsert, text }) {
const { refs } = useData();
if (!isExpandable(refs[$ref])) return /* @__PURE__ */ jsx("span", {
className: cn(typeVariants()),
children: text
});
return /* @__PURE__ */ jsx("button", {
className: cn(typeVariants({ variant: "trigger" })),
onClick: () => {
onInsert(pathName, $ref);
},
children: text
});
}
function Property({ name, type, required, deprecated, nested = false, className, ...props }) {
return /* @__PURE__ */ jsxs("div", {
className: cn("text-sm border-t", nested ? "p-3 border-x bg-fd-card last:rounded-b-xl first:rounded-tr-xl last:border-b" : "py-4 first:border-t-0", className),
children: [/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-3 not-prose",
children: [
/* @__PURE__ */ jsxs("span", {
className: "font-medium font-mono text-fd-primary",
children: [name, required ? /* @__PURE__ */ jsx("span", {
className: "text-red-400",
children: "*"
}) : /* @__PURE__ */ jsx("span", {
className: "text-fd-muted-foreground",
children: "?"
})]
}),
typeof type === "string" ? /* @__PURE__ */ jsx("span", {
className: "text-sm font-mono text-fd-muted-foreground",
children: type
}) : type,
deprecated && /* @__PURE__ */ jsx(Badge, {
color: "yellow",
className: "ms-auto text-xs",
children: "Deprecated"
})
]
}), /* @__PURE__ */ jsx("div", {
className: "prose-no-margin pt-2.5 empty:hidden",
children: props.children
})]
});
}
function isExpandable(schema) {
return schema.type !== "primitive";
}
//#endregion
export { SchemaUI };
//# sourceMappingURL=client.js.map