mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 05:23:40 +00:00
OpenAPI: implement collapsible info tag (#2950)
* fix: break long words in openapi examples * OpenAPI: implement collapsible info tag --------- Co-authored-by: Andri <[email protected]>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
Break long words in openapi examples
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'fumadocs-ui/components/tabs';
|
||||
import type { SchemaUIGeneratedData } from '@/ui/schema';
|
||||
import type { InfoTag, SchemaUIGeneratedData } from '@/ui/schema';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
@@ -167,8 +167,8 @@ function SchemaUIProperty({
|
||||
{schema.description}
|
||||
{schema.infoTags && schema.infoTags.length > 0 && (
|
||||
<div className="flex flex-row gap-2 flex-wrap my-2 not-prose empty:hidden">
|
||||
{schema.infoTags.map((tag, i) => (
|
||||
<Fragment key={i}>{tag}</Fragment>
|
||||
{schema.infoTags.map((tag) => (
|
||||
<InfoTag key={tag.label} tag={tag} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -182,6 +182,41 @@ function SchemaUIProperty({
|
||||
);
|
||||
}
|
||||
|
||||
function InfoTag({ tag }: { tag: InfoTag }) {
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
const [isTruncated, setTruncated] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) return;
|
||||
// assume the tag width will never change
|
||||
setTruncated(element.scrollWidth !== element.offsetWidth);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-start gap-2 bg-fd-secondary border rounded-lg text-xs p-1.5 shadow-md max-w-full">
|
||||
<span className="font-medium">{tag.label}</span>
|
||||
<code
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'min-w-0 flex-1 text-fd-muted-foreground',
|
||||
open ? 'wrap-break-word' : 'truncate',
|
||||
)}
|
||||
>
|
||||
{tag.value}
|
||||
</code>
|
||||
{isTruncated && (
|
||||
<button
|
||||
className={cn(buttonVariants({ size: 'icon-xs', variant: 'ghost' }))}
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<ChevronDown />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SchemaUIPopover({ initialPath }: { initialPath: { name: ReactNode; $ref?: string }[] }) {
|
||||
const [path, setPath] = useState(initialPath);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { SchemaUILazy } from '@/ui/schema/lazy';
|
||||
|
||||
export interface FieldBase {
|
||||
description?: ReactNode;
|
||||
infoTags?: ReactNode[];
|
||||
infoTags?: InfoTag[];
|
||||
|
||||
typeName: string;
|
||||
aliasName: string;
|
||||
@@ -16,6 +16,11 @@ export interface FieldBase {
|
||||
deprecated?: boolean;
|
||||
}
|
||||
|
||||
export interface InfoTag {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type SchemaData = FieldBase &
|
||||
(
|
||||
| {
|
||||
@@ -91,31 +96,22 @@ export function generateSchemaUI(
|
||||
const { showExample = false } = ctx.schemaUI ?? {};
|
||||
|
||||
function generateInfoTags(schema: Exclude<ResolvedSchema, boolean>) {
|
||||
const fields: ReactNode[] = [];
|
||||
|
||||
function field(key: string, value: ReactNode) {
|
||||
return (
|
||||
<div className="bg-fd-secondary border rounded-lg text-xs p-1.5 shadow-md">
|
||||
<span className="font-medium me-2">{key}</span>
|
||||
<code className="text-fd-muted-foreground">{value}</code>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const fields: InfoTag[] = [];
|
||||
|
||||
if (schema.default !== undefined) {
|
||||
fields.push(field('Default', JSON.stringify(schema.default)));
|
||||
fields.push({ label: 'Default', value: JSON.stringify(schema.default) });
|
||||
}
|
||||
|
||||
if (schema.pattern) {
|
||||
fields.push(field('Match', schema.pattern));
|
||||
fields.push({ label: 'Match', value: schema.pattern });
|
||||
}
|
||||
|
||||
if (schema.format) {
|
||||
fields.push(field('Format', schema.format));
|
||||
fields.push({ label: 'Format', value: schema.format });
|
||||
}
|
||||
|
||||
if (schema.multipleOf) {
|
||||
fields.push(field('Multiple Of', schema.multipleOf));
|
||||
fields.push({ label: 'Multiple Of', value: schema.multipleOf.toString() });
|
||||
}
|
||||
|
||||
let range = formatRange(
|
||||
@@ -125,10 +121,10 @@ export function generateSchemaUI(
|
||||
schema.maximum,
|
||||
schema.exclusiveMaximum,
|
||||
);
|
||||
if (range) fields.push(field('Range', range));
|
||||
if (range) fields.push({ label: 'Range', value: range });
|
||||
|
||||
range = formatRange('length', schema.minLength, undefined, schema.maxLength, undefined);
|
||||
if (range) fields.push(field('Length', range));
|
||||
if (range) fields.push({ label: 'Length', value: range });
|
||||
|
||||
range = formatRange(
|
||||
'properties',
|
||||
@@ -137,18 +133,21 @@ export function generateSchemaUI(
|
||||
schema.maxProperties,
|
||||
undefined,
|
||||
);
|
||||
if (range) fields.push(field('Properties', range));
|
||||
if (range) fields.push({ label: 'Properties', value: range });
|
||||
|
||||
range = formatRange('items', schema.minItems, undefined, schema.maxItems, undefined);
|
||||
if (range) fields.push(field('Items', range));
|
||||
if (range) fields.push({ label: 'Items', value: range });
|
||||
|
||||
if (schema.enum) {
|
||||
fields.push(field('Value in', schema.enum.map((value) => JSON.stringify(value)).join(' | ')));
|
||||
fields.push({
|
||||
label: 'Value in',
|
||||
value: schema.enum.map((value) => JSON.stringify(value)).join(' | '),
|
||||
});
|
||||
}
|
||||
|
||||
if (showExample && schema.examples) {
|
||||
for (const example of schema.examples) {
|
||||
fields.push(field('Example', JSON.stringify(example, null, 2)));
|
||||
fields.push({ label: 'Example', value: JSON.stringify(example, null, 2) });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ test('test', async () => {
|
||||
",
|
||||
"infoTags": [
|
||||
<div
|
||||
className="bg-fd-secondary border rounded-lg text-xs p-1.5 shadow-md"
|
||||
className="bg-fd-secondary border rounded-lg text-xs p-1.5 shadow-md max-w-full"
|
||||
>
|
||||
<span
|
||||
className="font-medium me-2"
|
||||
@@ -139,7 +139,7 @@ test('test', async () => {
|
||||
Value in
|
||||
</span>
|
||||
<code
|
||||
className="text-fd-muted-foreground"
|
||||
className="text-fd-muted-foreground wrap-break-word"
|
||||
>
|
||||
"NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS"
|
||||
</code>
|
||||
|
||||
Reference in New Issue
Block a user