UNPKG

fumadocs-openapi

Version:

Generate MDX docs for your OpenAPI spec

409 lines (406 loc) 13.4 kB
'use client'; import { cn } from "../../utils/cn.js"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/components/select.js"; import { Input, labelVariants } from "../../ui/components/input.js"; import { getDefaultValue } from "../get-default-values.js"; import { FormatFlags, schemaToString } from "../../utils/schema-to-string.js"; import { anyFields, useFieldInfo, useResolvedSchema, useSchemaScope } from "../schema.js"; import { useState } from "react"; import { useController, useFieldArray, useFormContext } from "react-hook-form"; import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime"; import { ChevronDown, Plus, Trash2, X } from "lucide-react"; import { buttonVariants } from "fumadocs-ui/components/ui/button"; //#region src/playground/components/inputs.tsx function FieldLabel(props) { return /* @__PURE__ */ jsx("label", { ...props, className: cn("w-full inline-flex items-center gap-0.5", props.className), children: props.children }); } function FieldLabelName({ required = false, ...props }) { return /* @__PURE__ */ jsxs("span", { ...props, className: cn(labelVariants(), "font-mono me-auto", props.className), children: [props.children, required && /* @__PURE__ */ jsx("span", { className: "text-red-400/80 mx-1", children: "*" })] }); } function FieldLabelType(props) { return /* @__PURE__ */ jsx("code", { ...props, className: cn("text-xs text-fd-muted-foreground", props.className), children: props.children }); } function ObjectInput({ field: _field, fieldName, ...props }) { const field = useResolvedSchema(_field); return /* @__PURE__ */ jsxs("div", { ...props, className: cn("grid grid-cols-1 gap-4 @md:grid-cols-2", props.className), children: [Object.entries(field.properties ?? {}).map(([key, child]) => /* @__PURE__ */ jsx(FieldSet, { name: key, field: child, fieldName: `${fieldName}.${key}`, isRequired: field.required?.includes(key) }, key)), (field.additionalProperties || field.patternProperties) && /* @__PURE__ */ jsx(DynamicProperties, { fieldName, filterKey: (v) => !field.properties || !Object.keys(field.properties).includes(v), getType: (key) => { for (const pattern in field.patternProperties) if (key.match(RegExp(pattern))) return field.patternProperties[pattern]; if (field.additionalProperties) return field.additionalProperties; return anyFields; } })] }); } function JsonInput({ fieldName }) { const controller = useController({ name: fieldName }); const [error, setError] = useState(null); const [value, setValue] = useState(() => JSON.stringify(controller.field.value, null, 2)); return /* @__PURE__ */ jsxs("div", { className: "flex flex-col bg-fd-secondary text-fd-secondary-foreground overflow-hidden border rounded-lg", children: [/* @__PURE__ */ jsx("textarea", { ...controller.field, value, className: "p-2 h-[240px] text-sm font-mono resize-none focus-visible:outline-none", onChange: (v) => { setValue(v.target.value); try { controller.field.onChange(JSON.parse(v.target.value)); setError(null); } catch (e) { if (e instanceof Error) setError(e.message); } } }), /* @__PURE__ */ jsx("p", { className: "p-2 text-xs font-mono border-t text-red-400 empty:hidden", children: error })] }); } function DynamicProperties({ fieldName, filterKey = () => true, getType = () => anyFields }) { const { control, setValue, getValues } = useFormContext(); const [nextName, setNextName] = useState(""); const [properties, setProperties] = useState(() => { const value = getValues(fieldName); if (value) return Object.keys(value).filter(filterKey); return []; }); const onAppend = () => { const name = nextName.trim(); if (name.length === 0) return; setProperties((p) => { if (p.includes(name) || !filterKey(name)) return p; const type = getType(name); setValue(`${fieldName}.${name}`, getDefaultValue(type)); setNextName(""); return [...p, name]; }); }; return /* @__PURE__ */ jsxs(Fragment$1, { children: [properties.map((item) => { return /* @__PURE__ */ jsx(FieldSet, { name: item, field: getType(item), fieldName: `${fieldName}.${item}`, toolbar: /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Remove Item", className: cn(buttonVariants({ color: "outline", size: "icon-xs" })), onClick: () => { setProperties((p) => p.filter((prop) => prop !== item)); control.unregister(`${fieldName}.${item}`); }, children: /* @__PURE__ */ jsx(Trash2, {}) }) }, item); }), /* @__PURE__ */ jsxs("div", { className: "flex gap-2 col-span-full", children: [/* @__PURE__ */ jsx(Input, { value: nextName, placeholder: "Enter Property Name", onChange: (e) => setNextName(e.target.value), onKeyDown: (e) => { if (e.key === "Enter") { onAppend(); e.preventDefault(); } } }), /* @__PURE__ */ jsx("button", { type: "button", className: cn(buttonVariants({ color: "secondary", size: "sm" }), "px-4"), onClick: onAppend, children: "New" })] })] }); } function FieldInput({ field, fieldName, isRequired, ...props }) { const form = useFormContext(); const { field: { value, onChange, ...restField }, fieldState } = useController({ control: form.control, name: fieldName }); if (field.type === "null") return; if (field.type === "string" && field.format === "binary") return /* @__PURE__ */ jsxs("div", { ...props, children: [/* @__PURE__ */ jsx("label", { htmlFor: fieldName, className: cn(buttonVariants({ color: "secondary", className: "w-full h-9 gap-2 truncate" })), children: value instanceof File ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", { className: "text-fd-muted-foreground text-xs", children: "Selected" }), /* @__PURE__ */ jsx("span", { className: "truncate w-0 flex-1 text-end", children: value.name })] }) : /* @__PURE__ */ jsx("span", { className: "text-fd-muted-foreground", children: "Upload" }) }), /* @__PURE__ */ jsx("input", { id: fieldName, type: "file", multiple: false, onChange: (e) => { if (!e.target.files) return; onChange(e.target.files.item(0)); }, hidden: true, ...restField })] }); if (field.type === "boolean") return /* @__PURE__ */ jsxs(Select, { value: String(value), onValueChange: (value$1) => onChange(value$1 === "undefined" ? void 0 : value$1 === "true"), disabled: restField.disabled, children: [/* @__PURE__ */ jsx(SelectTrigger, { id: fieldName, className: props.className, ...restField, children: /* @__PURE__ */ jsx(SelectValue, {}) }), /* @__PURE__ */ jsxs(SelectContent, { children: [ /* @__PURE__ */ jsx(SelectItem, { value: "true", children: "True" }), /* @__PURE__ */ jsx(SelectItem, { value: "false", children: "False" }), !isRequired && /* @__PURE__ */ jsx(SelectItem, { value: "undefined", children: "Unset" }) ] })] }); const isNumber = field.type === "integer" || field.type === "number"; return /* @__PURE__ */ jsxs("div", { ...props, className: cn("flex flex-row gap-2", props.className), children: [/* @__PURE__ */ jsx(Input, { id: fieldName, placeholder: "Enter value", type: isNumber ? "number" : "text", step: field.type === "integer" ? 1 : void 0, value: value ?? "", onChange: (e) => { if (isNumber && !Number.isNaN(e.target.valueAsNumber)) onChange(e.target.valueAsNumber); else if (!isNumber) onChange(e.target.value); }, ...restField }), fieldState.isDirty && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => form.resetField(fieldName), className: "text-fd-muted-foreground", children: /* @__PURE__ */ jsx(X, { className: "size-4" }) })] }); } function FieldSet({ field: _field, fieldName, toolbar, name, isRequired, depth = 0, slotType, collapsible = true, ...props }) { const { readOnly, writeOnly } = useSchemaScope(); const field = useResolvedSchema(_field); const [show, setShow] = useState(!collapsible); const { info, updateInfo } = useFieldInfo(fieldName, field, depth); if (_field === false) return; if (field.readOnly && !readOnly) return; if (field.writeOnly && !writeOnly) return; if (info.unionField) { const union = field[info.unionField]; const showSelect = union.length > 1; return /* @__PURE__ */ jsx(FieldSet, { ...props, name, fieldName, isRequired, field: union[info.oneOf], depth: depth + 1, slotType: showSelect ? false : slotType, toolbar: /* @__PURE__ */ jsxs(Fragment$1, { children: [showSelect && /* @__PURE__ */ jsx("select", { className: "text-xs font-mono", value: info.oneOf, onChange: (e) => { updateInfo({ oneOf: Number(e.target.value) }); }, children: union.map((item, i) => /* @__PURE__ */ jsx("option", { value: i, className: "bg-fd-popover text-fd-popover-foreground", children: schemaToString(item, void 0, FormatFlags.UseAlias) }, i)) }), toolbar] }) }); } if (Array.isArray(field.type)) { const showSelect = field.type.length > 1; return /* @__PURE__ */ jsx(FieldSet, { ...props, name, fieldName, isRequired, field: { ...field, type: info.selectedType }, depth: depth + 1, slotType: showSelect ? false : slotType, toolbar: /* @__PURE__ */ jsxs(Fragment$1, { children: [showSelect && /* @__PURE__ */ jsx("select", { className: "text-xs font-mono", value: info.selectedType, onChange: (e) => { updateInfo({ selectedType: e.target.value }); }, children: field.type.map((item) => /* @__PURE__ */ jsx("option", { value: item, className: "bg-fd-popover text-fd-popover-foreground", children: item }, item)) }), toolbar] }) }); } const showBn = collapsible && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setShow((prev) => !prev), className: cn(buttonVariants({ size: "icon-xs", color: "ghost", className: "text-fd-muted-foreground -ms-1" })), children: /* @__PURE__ */ jsx(ChevronDown, { className: cn(show && "rotate-180") }) }); if (field.type === "object" || info.intersection) return /* @__PURE__ */ jsxs("fieldset", { ...props, className: cn("flex flex-col gap-1.5 col-span-full @container", props.className), children: [/* @__PURE__ */ jsxs(FieldLabel, { htmlFor: fieldName, children: [ showBn, /* @__PURE__ */ jsx(FieldLabelName, { required: isRequired, children: name }), slotType ?? /* @__PURE__ */ jsx(FieldLabelType, { children: schemaToString(field) }), toolbar ] }), show && /* @__PURE__ */ jsx(ObjectInput, { field: info.intersection?.merged ?? field, fieldName, ...props, className: cn("rounded-lg border border-fd-primary/20 bg-fd-background/50 p-2 shadow-sm", props.className) })] }); if (field.type === "array") return /* @__PURE__ */ jsxs("fieldset", { ...props, className: cn("flex flex-col gap-1.5 col-span-full", props.className), children: [/* @__PURE__ */ jsxs(FieldLabel, { htmlFor: fieldName, children: [ showBn, /* @__PURE__ */ jsx(FieldLabelName, { required: isRequired, children: name }), slotType ?? /* @__PURE__ */ jsx(FieldLabelType, { children: schemaToString(field) }), toolbar ] }), show && /* @__PURE__ */ jsx(ArrayInput, { fieldName, items: field.items ?? anyFields, ...props, className: cn("rounded-lg border border-fd-primary/20 bg-fd-background/50 p-2 shadow-sm", props.className) })] }); return /* @__PURE__ */ jsxs("fieldset", { ...props, className: cn("flex flex-col gap-1.5", props.className), children: [/* @__PURE__ */ jsxs(FieldLabel, { htmlFor: fieldName, children: [ /* @__PURE__ */ jsx(FieldLabelName, { required: isRequired, children: name }), slotType ?? /* @__PURE__ */ jsx(FieldLabelType, { children: schemaToString(field) }), toolbar ] }), /* @__PURE__ */ jsx(FieldInput, { field, fieldName, isRequired })] }); } function ArrayInput({ fieldName, items, ...props }) { const name = fieldName.split(".").at(-1) ?? ""; const { fields, append, remove } = useFieldArray({ name: fieldName }); return /* @__PURE__ */ jsxs("div", { ...props, className: cn("flex flex-col gap-2", props.className), children: [fields.map((item, index) => /* @__PURE__ */ jsx(FieldSet, { name: /* @__PURE__ */ jsxs("span", { className: "text-fd-muted-foreground", children: [ name, "[", index, "]" ] }), field: items, isRequired: true, fieldName: `${fieldName}.${index}`, toolbar: /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Remove Item", className: cn(buttonVariants({ color: "outline", size: "icon-xs" })), onClick: () => remove(index), children: /* @__PURE__ */ jsx(Trash2, {}) }) }, item.id)), /* @__PURE__ */ jsxs("button", { type: "button", className: cn(buttonVariants({ color: "secondary", className: "gap-1.5 py-2", size: "sm" })), onClick: () => { append(getDefaultValue(items)); }, children: [/* @__PURE__ */ jsx(Plus, { className: "size-4" }), "New Item"] })] }); } //#endregion export { FieldInput, FieldSet, JsonInput, ObjectInput }; //# sourceMappingURL=inputs.js.map