mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
OpenAPI: add unset button to non-primitive fields
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
add unset button to non-primitive fields
|
||||
@@ -17,14 +17,9 @@ import { FormatFlags, schemaToString } from '@/utils/schema-to-string';
|
||||
import { anyFields, useFieldInfo, useResolvedSchema, useSchemaScope } from '@/playground/schema';
|
||||
import type { ParsedSchema } from '@/utils/schema';
|
||||
import { stringifyFieldKey } from '@fumari/stf/lib/utils';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
function FieldLabel(props: ComponentProps<'label'>) {
|
||||
return (
|
||||
<label {...props} className={cn('w-full inline-flex items-center gap-0.5', props.className)}>
|
||||
{props.children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
const fieldLabelVariants = cva('w-full inline-flex items-center gap-0.5');
|
||||
|
||||
function FieldLabelType(props: ComponentProps<'code'>) {
|
||||
return (
|
||||
@@ -152,30 +147,12 @@ export function FieldInput({
|
||||
isRequired?: boolean;
|
||||
fieldName: FieldKey;
|
||||
}) {
|
||||
const engine = useDataEngine();
|
||||
const [value, setValue] = useFieldValue(fieldName);
|
||||
const id = stringifyFieldKey(fieldName);
|
||||
if (field.type === 'null') return;
|
||||
|
||||
function renderUnset(children: ReactNode) {
|
||||
return (
|
||||
<div {...props} className={cn('flex flex-row gap-2', props.className)}>
|
||||
{children}
|
||||
{value !== undefined && !isRequired && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => engine.delete(fieldName)}
|
||||
className="text-fd-muted-foreground"
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type === 'string' && field.format === 'binary') {
|
||||
return renderUnset(
|
||||
return (
|
||||
<>
|
||||
<label
|
||||
htmlFor={id}
|
||||
@@ -205,7 +182,7 @@ export function FieldInput({
|
||||
}}
|
||||
hidden
|
||||
/>
|
||||
</>,
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -248,7 +225,7 @@ export function FieldInput({
|
||||
}
|
||||
|
||||
const isNumber = field.type === 'integer' || field.type === 'number';
|
||||
return renderUnset(
|
||||
return (
|
||||
<Input
|
||||
id={id}
|
||||
placeholder="Enter value"
|
||||
@@ -262,7 +239,7 @@ export function FieldInput({
|
||||
setValue(e.target.value);
|
||||
}
|
||||
}}
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -293,11 +270,18 @@ export function FieldSet({
|
||||
const { info, updateInfo } = useFieldInfo(fieldName, field);
|
||||
const id = stringifyFieldKey(fieldName);
|
||||
const dataEngine = useDataEngine();
|
||||
const [isDefined] = useFieldValue(fieldName, {
|
||||
compute(currentValue) {
|
||||
return currentValue !== undefined;
|
||||
},
|
||||
});
|
||||
|
||||
if (_field === false) return;
|
||||
if (field.readOnly && !readOnly) return;
|
||||
if (field.writeOnly && !writeOnly) return;
|
||||
|
||||
if (collapsible && !isDefined && show) setShow(false);
|
||||
|
||||
function renderLabelTrigger(schema = field) {
|
||||
if (!collapsible) return renderLabelName();
|
||||
|
||||
@@ -326,6 +310,18 @@ export function FieldSet({
|
||||
);
|
||||
}
|
||||
|
||||
function renderUnsetButton() {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => dataEngine.delete(fieldName)}
|
||||
className="text-fd-muted-foreground hover:text-fd-accent-foreground"
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
if (info.unionField && field[info.unionField]) {
|
||||
const union = field[info.unionField]!;
|
||||
const showSelect = union.length > 1;
|
||||
@@ -417,11 +413,12 @@ export function FieldSet({
|
||||
{...props}
|
||||
className={cn('flex flex-col gap-1.5 col-span-full @container', props.className)}
|
||||
>
|
||||
<FieldLabel htmlFor={id}>
|
||||
<div className={fieldLabelVariants()}>
|
||||
{renderLabelTrigger(schema)}
|
||||
{slotType ?? <FieldLabelType>{schemaToString(field)}</FieldLabelType>}
|
||||
{toolbar}
|
||||
</FieldLabel>
|
||||
{!isRequired && isDefined && renderUnsetButton()}
|
||||
</div>
|
||||
{show && (
|
||||
<ObjectInput
|
||||
field={schema}
|
||||
@@ -436,11 +433,12 @@ export function FieldSet({
|
||||
if (field.type === 'array') {
|
||||
return (
|
||||
<fieldset {...props} className={cn('flex flex-col gap-1.5 col-span-full', props.className)}>
|
||||
<FieldLabel htmlFor={id}>
|
||||
<div className={fieldLabelVariants()}>
|
||||
{renderLabelTrigger()}
|
||||
{slotType ?? <FieldLabelType>{schemaToString(field)}</FieldLabelType>}
|
||||
{toolbar}
|
||||
</FieldLabel>
|
||||
{!isRequired && isDefined && renderUnsetButton()}
|
||||
</div>
|
||||
{show && (
|
||||
<ArrayInput
|
||||
fieldName={fieldName}
|
||||
@@ -451,13 +449,15 @@ export function FieldSet({
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<fieldset {...props} className={cn('flex flex-col gap-1.5', props.className)}>
|
||||
<FieldLabel htmlFor={id}>
|
||||
<label className={fieldLabelVariants()} htmlFor={id}>
|
||||
{renderLabelName()}
|
||||
{slotType ?? <FieldLabelType>{schemaToString(field)}</FieldLabelType>}
|
||||
{toolbar}
|
||||
</FieldLabel>
|
||||
{!isRequired && isDefined && renderUnsetButton()}
|
||||
</label>
|
||||
<FieldInput field={field} fieldName={fieldName} isRequired={isRequired} />
|
||||
</fieldset>
|
||||
);
|
||||
|
||||
@@ -166,5 +166,12 @@ function Field({
|
||||
);
|
||||
}
|
||||
|
||||
return <Input id={fieldName} value={value} onChange={(e) => setValue(e.target.value)} />;
|
||||
return (
|
||||
<Input
|
||||
id={fieldName}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Enter Value"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,15 +15,17 @@ export interface DataEngineListener {
|
||||
*/
|
||||
field?: FieldKey;
|
||||
/**
|
||||
* when field value is changed
|
||||
* when field value is changed.
|
||||
*/
|
||||
onUpdate?: (key: FieldKey, ctx: OnUpdateContext) => void;
|
||||
/**
|
||||
* when `init(field)` is called
|
||||
* fired on `init(field)`.
|
||||
*/
|
||||
onInit?: (key: FieldKey, ctx: OnInitContext) => void;
|
||||
/**
|
||||
* when `delete(field)` is called
|
||||
* fired on `delete(field)`.
|
||||
*
|
||||
* when `field` is specified, this is also fired when parent is deleted.
|
||||
*/
|
||||
onDelete?: (key: FieldKey, ctx: OnDeleteContext) => void;
|
||||
}
|
||||
@@ -94,9 +96,14 @@ class ListenerManager {
|
||||
}
|
||||
|
||||
onDelete(field: FieldKey, ctx: OnDeleteContext) {
|
||||
const fieldKey = stringifyFieldKey(field);
|
||||
for (const v of this.unindexed) v.onDelete?.(field, ctx);
|
||||
const set = this.indexed.get(stringifyFieldKey(field));
|
||||
if (set) for (const v of set) v.onDelete?.(field, ctx);
|
||||
|
||||
for (const [k, listeners] of this.indexed.entries()) {
|
||||
if (!fieldKeyStartsWith(k, fieldKey)) continue;
|
||||
|
||||
for (const v of listeners) v.onDelete?.(field, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,15 +176,16 @@ export class DataEngine {
|
||||
if (Array.isArray(parent) && typeof prop === 'number') {
|
||||
const deleted: unknown[] = parent.splice(prop, 1);
|
||||
if (deleted.length === 0) return;
|
||||
this.listeners.onUpdate(parentKey, { swallow: false, ...ctx });
|
||||
this.listeners.onDelete(key, ctx);
|
||||
|
||||
this.listeners.onDelete(key, ctx);
|
||||
// it will change children's field value when removed at middle
|
||||
this.listeners.onUpdate(parentKey, { swallow: false, ...ctx });
|
||||
return deleted[0];
|
||||
} else if (typeof parent === 'object' && parent !== null) {
|
||||
const temp = (parent as Record<string, unknown>)[prop];
|
||||
delete parent[prop as never];
|
||||
this.listeners.onUpdate(parentKey, { swallow: true, ...ctx });
|
||||
this.listeners.onDelete(key, ctx);
|
||||
this.listeners.onUpdate(parentKey, { swallow: true, ...ctx });
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
@@ -242,13 +250,17 @@ export function useFieldValue<V = unknown>(
|
||||
useListener({
|
||||
field: key,
|
||||
stf: options.stf,
|
||||
onInit() {
|
||||
const computed = compute(engine.get(key));
|
||||
setValue((prev) => (isChanged(prev, computed) ? computed : prev));
|
||||
},
|
||||
onUpdate() {
|
||||
const computed = compute(engine.get(key));
|
||||
if (isChanged(value, computed)) setValue(computed);
|
||||
setValue((prev) => (isChanged(prev, computed) ? computed : prev));
|
||||
},
|
||||
onDelete() {
|
||||
const computed = compute(undefined);
|
||||
if (isChanged(value, computed)) setValue(computed);
|
||||
setValue((prev) => (isChanged(prev, computed) ? computed : prev));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user