TypeScript & UI: Support displaying parameters & return types in Type Table

This commit is contained in:
Fuma Nama
2025-09-05 19:51:22 +08:00
parent 2d7d2f9e50
commit 45c7531656
15 changed files with 415 additions and 172 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'fumadocs-typescript': patch
'fumadocs-ui': patch
---
Type Table: Support displaying parameters & return types
@@ -11,7 +11,14 @@ description: Auto-generated type table
*
* See https://fumadocs.vercel.app/docs/ui/components/type-table
*/
name: string;
name: boolean | null;
/**
* @param name - user name.
* @param allowNull - is null value allowed.
* @returns user ID
**/
fn: (name: string, allowNull?: boolean) => string
/**
* We love Shiki.
@@ -21,7 +28,7 @@ description: Auto-generated type table
* \`\`\`
* @default { a: "test" }
*/
options: Partial<{ a: unknown }>;
options?: Partial<{ a: unknown }>;
}`} />
+31 -31
View File
@@ -28,11 +28,16 @@ export interface DocEntry {
type: string;
simplifiedType: string;
tags: Record<string, string>;
tags: RawTag[];
required: boolean;
deprecated: boolean;
}
export interface RawTag {
name: string;
text: string;
}
interface EntryContext {
program: Project;
transform?: Transformer;
@@ -201,16 +206,21 @@ function getDocEntry(
.getTypeChecker()
.getTypeOfSymbolAtLocation(prop, context.declaration);
const isOptional = prop.isOptional();
const tags = Object.fromEntries(
prop
.getJsDocTags()
.map((tag) => [tag.getName(), ts.displayPartsToString(tag.getText())]),
const tags = prop.getJsDocTags().map(
(tag) =>
({
name: tag.getName(),
text: ts.displayPartsToString(tag.getText()),
}) satisfies RawTag,
);
let type = getFullType(subType, isOptional);
let type = getFullType(subType);
if ('remarks' in tags) {
type = /^`(?<name>.+)`/.exec(tags.remarks)?.[1] ?? type;
for (const tag of tags) {
if (tag.name !== 'remarks') continue;
// replace type with @remarks
type = /^`(?<name>.+)`/.exec(tag.text)?.[1] ?? type;
}
const entry: DocEntry = {
@@ -222,11 +232,13 @@ function getDocEntry(
),
tags,
type,
simplifiedType: getSimpleForm(subType, program.getTypeChecker()),
simplifiedType: getSimpleForm(
subType,
program.getTypeChecker(),
isOptional,
),
required: !isOptional,
deprecated: prop
.getJsDocTags()
.some((tag) => tag.getName() === 'deprecated'),
deprecated: tags.some((tag) => tag.name === 'deprecated'),
};
transform?.call(context, entry, subType, prop);
@@ -234,25 +246,13 @@ function getDocEntry(
return entry;
}
function getFullType(type: Type, isOptional: boolean): string {
if (type.isUnion() && isOptional) {
const t = type.compilerType as ts.UnionType;
const originalTypes = t.types;
t.types = t.types.filter((v) => v.flags !== ts.TypeFlags.Undefined);
const result = getFullType(type, false);
t.types = originalTypes;
return result;
}
let typeName = type
.getNonNullableType()
.getText(
undefined,
ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope &
ts.TypeFormatFlags.NoTruncation,
);
function getFullType(type: Type): string {
let typeName = type.getText(
undefined,
ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope |
ts.TypeFormatFlags.NoTruncation |
ts.TypeFormatFlags.InTypeAlias,
);
if (type.getAliasSymbol() && type.getAliasTypeArguments().length === 0) {
typeName = type.getAliasSymbol()?.getEscapedName() ?? typeName;
+26 -18
View File
@@ -1,22 +1,35 @@
import * as ts from 'ts-morph';
function simplifyType(type: ts.Type, checker: ts.TypeChecker): string {
// Handle union types
export function getSimpleForm(
type: ts.Type,
checker: ts.TypeChecker,
noUndefined = false,
): string {
if (type.isUndefined() && noUndefined) return '';
const alias = type.getAliasSymbol();
if (alias && type.getAliasTypeArguments().length === 0) {
return alias.getEscapedName();
}
if (type.isUnion()) {
const types: string[] = [];
for (const t of type.getUnionTypes()) {
const str = simplifyType(t, checker);
if (str !== 'never') types.unshift(str);
const str = getSimpleForm(t, checker, noUndefined);
if (str.length > 0 && str !== 'never') types.unshift(str);
}
return types.length > 0 ? types.join(' | ') : 'never';
return types.length > 0
? // boolean | null will become true | false | null, need to ensure it's still returned as boolean
types.join(' | ').replace('true | false', 'boolean')
: 'never';
}
// Handle intersection types
if (type.isIntersection()) {
const types: string[] = [];
for (const t of type.getIntersectionTypes()) {
types.unshift(simplifyType(t, checker));
const str = getSimpleForm(t, checker, noUndefined);
if (str.length > 0 && str !== 'never') types.unshift(str);
}
return types.join(' & ');
@@ -25,17 +38,12 @@ function simplifyType(type: ts.Type, checker: ts.TypeChecker): string {
if (type.isTuple()) {
const elements = type
.getTupleElements()
.map((t) => simplifyType(t, checker))
.map((t) => getSimpleForm(t, checker))
.join(', ');
return `[${elements}]`;
}
const alias = type.getAliasSymbol();
if (alias && type.getAliasTypeArguments().length === 0) {
return alias.getEscapedName();
}
if (type.isArray() || type.isReadonlyArray()) {
return 'array';
}
@@ -48,9 +56,9 @@ function simplifyType(type: ts.Type, checker: ts.TypeChecker): string {
return 'object';
}
return type.getText();
}
export function getSimpleForm(type: ts.Type, checker: ts.TypeChecker): string {
return simplifyType(type, checker);
return type.getText(
undefined,
ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope |
ts.TypeFormatFlags.InTypeAlias,
);
}
+3 -3
View File
@@ -2,8 +2,8 @@ import * as path from 'node:path';
import {
type DocEntry,
type GeneratedDoc,
type Generator,
type GenerateOptions,
type Generator,
} from './base';
interface Templates {
@@ -39,8 +39,8 @@ ${doc.description}
${c.description || 'No Description'}
${Object.entries(c.tags)
.map(([tag, value]) => `- ${tag}:\n${replaceJsDocLinks(value)}`)
${c.tags
.map((tag) => `- ${tag.name}:\n${replaceJsDocLinks(tag.text)}`)
.join('\n')}
</div>`,
+43
View File
@@ -0,0 +1,43 @@
import { RawTag } from '@/lib/base';
export interface ParameterTag {
name: string;
description?: string;
}
export interface TypedTags {
default?: string;
params?: ParameterTag[];
returns?: string;
}
/**
* Parse tags, only returns recognized fields.
*/
export function parseTags(tags: RawTag[]): TypedTags {
const typed: TypedTags = {};
for (const { name: key, text } of tags) {
if (key === 'default' || key === 'defaultValue') {
typed.default = text;
continue;
}
if (key === 'param') {
const [param, description] = text.split('-', 2);
typed.params ??= [];
typed.params.push({
name: param.trim(),
description: description.trim(),
});
continue;
}
if (key === 'returns') {
typed.returns = text;
}
}
return typed;
}
@@ -2,10 +2,10 @@ import type { Root } from 'mdast';
import type { Nodes } from 'hast';
import type { Transformer } from 'unified';
import type {
Expression,
ExpressionStatement,
ObjectExpression,
Program,
Property,
} from 'estree';
import { createGenerator, type DocEntry, type Generator } from '@/lib/base';
import { renderMarkdownToHast, renderTypeToHast } from '@/markdown';
@@ -17,58 +17,104 @@ import {
} from '@/lib/type-table';
import { toEstree } from 'hast-util-to-estree';
import { dirname } from 'node:path';
import { type ParameterTag, parseTags } from '@/lib/parse-tags';
async function mapProperty(
entry: DocEntry,
function objectBuilder() {
const out: ObjectExpression = {
type: 'ObjectExpression',
properties: [],
};
return {
addExpressionNode(key: string, expression: Expression) {
out.properties.push({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: {
type: 'Identifier',
name: key,
},
kind: 'init',
value: expression,
});
},
addJsxProperty(key: string, hast: Nodes) {
const estree = toEstree(hast, {
elementAttributeNameCase: 'react',
}).body[0] as ExpressionStatement;
this.addExpressionNode(key, estree.expression);
},
build() {
return out;
},
};
}
async function buildTypeProp(
entries: DocEntry[],
{
renderMarkdown = renderMarkdownToHast,
renderType = renderTypeToHast,
}: RemarkAutoTypeTableOptions,
): Promise<Property> {
const value = valueToEstree({
required: entry.required,
}) as ObjectExpression;
): Promise<ObjectExpression> {
async function onItem(entry: DocEntry) {
const node = objectBuilder();
node.addJsxProperty('type', await renderType(entry.simplifiedType));
node.addJsxProperty('typeDescription', await renderType(entry.type));
node.addExpressionNode('required', valueToEstree(entry.required));
function addJsxProperty(key: string, hast: Nodes) {
const estree = toEstree(hast, {
elementAttributeNameCase: 'react',
}).body[0] as ExpressionStatement;
const tags = parseTags(entry.tags);
if (tags.default)
node.addJsxProperty('default', await renderType(tags.default));
value.properties.push({
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: {
type: 'Identifier',
name: key,
},
kind: 'init',
value: estree.expression,
});
if (tags.returns)
node.addJsxProperty('returns', await renderMarkdown(tags.returns));
if (tags.params) {
node.addExpressionNode('parameters', {
type: 'ArrayExpression',
elements: await Promise.all(tags.params.map(onParam)),
});
}
if (entry.description) {
node.addJsxProperty(
'description',
await renderMarkdown(entry.description),
);
}
return node.build();
}
addJsxProperty('type', await renderType(entry.simplifiedType));
addJsxProperty('typeDescription', await renderType(entry.type));
const defaultValue = entry.tags.default ?? entry.tags.defaultValue;
if (defaultValue) addJsxProperty('default', await renderType(defaultValue));
async function onParam(param: ParameterTag) {
const node = objectBuilder();
node.addExpressionNode('name', valueToEstree(param.name));
if (param.description)
node.addJsxProperty(
'description',
await renderMarkdown(param.description),
);
if (entry.description) {
addJsxProperty('description', await renderMarkdown(entry.description));
return node.build();
}
return {
type: 'Property',
method: false,
shorthand: false,
computed: false,
key: {
type: 'Literal',
value: entry.name,
},
kind: 'init',
value,
};
const prop = objectBuilder();
const output = await Promise.all(
entries.map(async (entry) => ({
name: entry.name,
node: await onItem(entry),
})),
);
for (const node of output) {
prop.addExpressionNode(node.name, node.node);
}
return prop.build();
}
export interface RemarkAutoTypeTableOptions {
@@ -141,10 +187,6 @@ export function remarkAutoTypeTable(
);
const rendered = output.map(async (doc) => {
const properties = await Promise.all(
doc.entries.map((entry) => mapProperty(entry, config)),
);
return {
type: 'mdxJsxFlowElement',
name: outputName,
@@ -162,10 +204,7 @@ export function remarkAutoTypeTable(
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'ObjectExpression',
properties,
},
expression: await buildTypeProp(doc.entries, config),
},
],
} satisfies Program,
+22 -3
View File
@@ -1,4 +1,8 @@
import { type TypeNode, TypeTable } from 'fumadocs-ui/components/type-table';
import {
type ParameterNode,
type TypeNode,
TypeTable,
} from 'fumadocs-ui/components/type-table';
import { type Jsx, toJsxRuntime } from 'hast-util-to-jsx-runtime';
import * as runtime from 'react/jsx-runtime';
import defaultMdxComponents from 'fumadocs-ui/mdx';
@@ -11,6 +15,7 @@ import {
} from '@/lib/type-table';
import { type Generator } from '@/lib/base';
import type { Nodes } from 'hast';
import { parseTags } from '@/lib/parse-tags';
export type AutoTypeTableProps = BaseTypeTableProps;
@@ -31,7 +36,17 @@ export async function AutoTypeTable({
return output.map(async (item) => {
const entries = item.entries.map(async (entry) => {
const defaultValue = entry.tags.default ?? entry.tags.defaultValue;
const tags = parseTags(entry.tags);
const paramNodes: ParameterNode[] = [];
for (const param of tags.params ?? []) {
paramNodes.push({
name: param.name,
description: param.description
? await renderMarkdown(param.description)
: undefined,
});
}
return [
entry.name,
@@ -39,9 +54,13 @@ export async function AutoTypeTable({
type: await renderType(entry.simplifiedType),
typeDescription: await renderType(entry.type),
description: await renderMarkdown(entry.description),
default: defaultValue ? await renderType(defaultValue) : undefined,
default: tags.default ? await renderType(tags.default) : undefined,
parameters: paramNodes,
required: entry.required,
deprecated: entry.deprecated,
returns: tags.returns
? await renderMarkdown(tags.returns)
: undefined,
} as TypeNode,
];
});
+44 -7
View File
@@ -11,9 +11,7 @@ function _createMdxContent(props) {
children: _jsx(_Fragment, {
children: _jsx(TypeTable, {
type: {
"name": {
"default": "Henry",
"required": true,
name: {
type: _jsx(_components.span, {
className: "shiki",
children: _jsx(_components.code, {
@@ -38,15 +36,26 @@ function _createMdxContent(props) {
})
})
}),
required: true,
default: _jsx(_components.span, {
className: "shiki",
children: _jsx(_components.code, {
children: _jsx(_components.span, {
style: {
"--shiki-light": "#24292E",
"--shiki-dark": "#E1E4E8"
},
children: "Henry"
})
})
}),
description: _jsx(_Fragment, {
children: _jsx(_components.p, {
children: "The name of player"
})
})
},
"age": {
"default": undefined,
"required": true,
age: {
type: _jsx(_components.span, {
className: "shiki",
children: _jsx(_components.code, {
@@ -70,7 +79,35 @@ function _createMdxContent(props) {
children: "timestamp"
})
})
})
}),
required: true
},
privateValue: {
type: _jsx(_components.span, {
className: "shiki",
children: _jsx(_components.code, {
children: _jsx(_components.span, {
style: {
"--shiki-light": "#24292E",
"--shiki-dark": "#E1E4E8"
},
children: "string"
})
})
}),
typeDescription: _jsx(_components.span, {
className: "shiki",
children: _jsx(_components.code, {
children: _jsx(_components.span, {
style: {
"--shiki-light": "#24292E",
"--shiki-dark": "#E1E4E8"
},
children: "string"
})
})
}),
required: true
}
}
})
+53 -21
View File
@@ -6,7 +6,7 @@
{
"name": "name",
"description": "",
"tags": {},
"tags": [],
"type": "string",
"simplifiedType": "string",
"required": true,
@@ -15,11 +15,14 @@
{
"name": "age",
"description": "",
"tags": {
"defaultValue": "4"
},
"type": "number",
"simplifiedType": "number | undefined",
"tags": [
{
"name": "defaultValue",
"text": "4"
}
],
"type": "number | undefined",
"simplifiedType": "number",
"required": false,
"deprecated": false
}
@@ -32,7 +35,7 @@
{
"name": "name",
"description": "",
"tags": {},
"tags": [],
"type": "string",
"simplifiedType": "string",
"required": true,
@@ -41,18 +44,21 @@
{
"name": "age",
"description": "",
"tags": {
"defaultValue": "4"
},
"type": "number",
"simplifiedType": "number | undefined",
"tags": [
{
"name": "defaultValue",
"text": "4"
}
],
"type": "number | undefined",
"simplifiedType": "number",
"required": false,
"deprecated": false
},
{
"name": "generic",
"description": "",
"tags": {},
"tags": [],
"type": "GenericType<string, string, string>",
"simplifiedType": "object",
"required": true,
@@ -67,10 +73,16 @@
{
"name": "name",
"description": "The name of player",
"tags": {
"see": "https://fumadocs.vercel.app",
"defaultValue": "Henry"
},
"tags": [
{
"name": "see",
"text": "https://fumadocs.vercel.app"
},
{
"name": "defaultValue",
"text": "Henry"
}
],
"type": "string",
"simplifiedType": "string",
"required": true,
@@ -79,14 +91,34 @@
{
"name": "age",
"description": "",
"tags": {
"example": "```js\nconsole.log(\"Hello World\")\n```",
"remarks": "`timestamp`\nReturned by API"
},
"tags": [
{
"name": "example",
"text": "```js\nconsole.log(\"Hello World\")\n```"
},
{
"name": "remarks",
"text": "`timestamp`\nReturned by API"
}
],
"type": "timestamp",
"simplifiedType": "number",
"required": true,
"deprecated": false
},
{
"name": "privateValue",
"description": "",
"tags": [
{
"name": "internal",
"text": ""
}
],
"type": "string",
"simplifiedType": "string",
"required": true,
"deprecated": false
}
]
}
+4 -4
View File
@@ -43,7 +43,7 @@ test('class members', () => {
"name": "test",
"required": true,
"simplifiedType": "string",
"tags": {},
"tags": [],
"type": "string",
},
{
@@ -52,7 +52,7 @@ test('class members', () => {
"name": "age",
"required": true,
"simplifiedType": "number",
"tags": {},
"tags": [],
"type": "number",
},
],
@@ -87,7 +87,7 @@ test('interface members', () => {
"name": "#name",
"required": true,
"simplifiedType": "string",
"tags": {},
"tags": [],
"type": "string",
},
{
@@ -96,7 +96,7 @@ test('interface members', () => {
"name": "age",
"required": true,
"simplifiedType": "number",
"tags": {},
"tags": [],
"type": "number",
},
],
+1 -1
View File
@@ -7,7 +7,7 @@
--color-fd-popover-foreground: hsl(0, 0%, 15.1%);
--color-fd-card: hsl(0, 0%, 94.7%);
--color-fd-card-foreground: hsl(0, 0%, 3.9%);
--color-fd-border: hsla(0, 0%, 62%, 20%);
--color-fd-border: hsla(0, 0%, 80%, 50%);
--color-fd-primary: hsl(0, 0%, 9%);
--color-fd-primary-foreground: hsl(0, 0%, 98%);
--color-fd-secondary: hsl(0, 0%, 93.1%);
+2 -2
View File
@@ -100,7 +100,7 @@ export function CodeBlock({
inTab ? 'bg-fd-secondary' : 'my-4 bg-fd-card',
keepBackground && 'bg-(--shiki-light-bg) dark:bg-(--shiki-dark-bg)',
'shiki relative border shadow-md rounded-xl outline-none not-prose overflow-hidden text-sm',
'shiki relative border shadow-sm rounded-xl outline-none not-prose overflow-hidden text-sm',
props.className,
)}
>
@@ -201,7 +201,7 @@ export function CodeBlockTabs({ ref, ...props }: ComponentProps<typeof Tabs>) {
{...props}
className={cn(
'bg-fd-card rounded-xl border',
nested ? 'shadow-sm' : 'my-4',
!nested && 'my-4',
props.className,
)}
>
@@ -68,7 +68,7 @@ interface InternalContext {
}
const itemVariants = cva(
'relative flex flex-row items-center gap-2 rounded-xl p-2 ps-(--sidebar-item-offset) text-start text-fd-muted-foreground [overflow-wrap:anywhere] [&_svg]:size-4 [&_svg]:shrink-0',
'relative flex flex-row items-center gap-2 rounded-lg p-2 ps-(--sidebar-item-offset) text-start text-fd-muted-foreground [overflow-wrap:anywhere] [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
active: {
@@ -125,7 +125,7 @@ export function SidebarContent(props: ComponentProps<'aside'>) {
{...props}
data-collapsed={collapsed}
className={cn(
'fixed left-0 rtl:left-auto rtl:right-(--removed-body-scroll-bar-size,0) flex flex-col items-end top-(--fd-sidebar-top) bottom-(--fd-sidebar-margin) z-20 bg-fd-card text-sm border-e transition-[top,opacity,translate,width] max-md:hidden *:w-(--fd-sidebar-width)',
'fixed left-0 rtl:left-auto rtl:right-(--removed-body-scroll-bar-size,0) flex flex-col items-end top-(--fd-sidebar-top) bottom-(--fd-sidebar-margin) z-20 bg-fd-card text-sm border-e transition-[top,opacity,translate,width] duration-200 max-md:hidden *:w-(--fd-sidebar-width)',
collapsed && [
'rounded-xl border translate-x-(--fd-sidebar-offset) rtl:-translate-x-(--fd-sidebar-offset)',
hover ? 'z-50 shadow-lg' : 'opacity-0',
+81 -29
View File
@@ -4,13 +4,18 @@ import { ChevronDown } from 'lucide-react';
import Link from 'fumadocs-core/link';
import { cva } from 'class-variance-authority';
import { cn } from '@/utils/cn';
import { type ReactNode } from 'react';
import { type ReactNode, useState } from 'react';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
export interface ParameterNode {
name: string;
description: ReactNode;
}
export interface TypeNode {
/**
* Additional description of the field
@@ -36,6 +41,10 @@ export interface TypeNode {
required?: boolean;
deprecated?: boolean;
parameters?: ParameterNode[];
returns?: ReactNode;
}
const keyVariants = cva('text-fd-primary', {
@@ -46,10 +55,12 @@ const keyVariants = cva('text-fd-primary', {
},
});
const fieldVariants = cva('text-fd-muted-foreground not-prose');
export function TypeTable({ type }: { type: Record<string, TypeNode> }) {
return (
<div className="@container flex flex-col p-1 bg-fd-card text-fd-card-foreground rounded-2xl border my-6 text-sm overflow-hidden">
<div className="flex flex-row font-medium items-center px-4 py-1 not-prose text-fd-muted-foreground">
<div className="flex font-medium items-center px-3 py-1 not-prose text-fd-muted-foreground">
<p className="w-[25%]">Prop</p>
<p className="@max-xl:hidden">Type</p>
</div>
@@ -60,58 +71,99 @@ export function TypeTable({ type }: { type: Record<string, TypeNode> }) {
);
}
function Item({ name, item }: { name: string; item: TypeNode }) {
function Item({
name,
item: {
parameters = [],
description,
required = false,
deprecated,
typeDescription,
default: defaultValue,
type,
typeDescriptionLink,
returns,
},
}: {
name: string;
item: TypeNode;
}) {
const [open, setOpen] = useState(false);
return (
<Collapsible className="rounded-xl border shadow-sm overflow-hidden transition-all data-[state=open]:bg-fd-muted data-[state=closed]:border-transparent not-last:data-[state=open]:mb-4">
<Collapsible
open={open}
onOpenChange={setOpen}
className={cn(
'rounded-xl border overflow-hidden transition-all',
open
? 'shadow-sm bg-fd-background not-last:mb-2'
: 'border-transparent',
)}
>
<CollapsibleTrigger className="relative flex flex-row items-center w-full group text-start px-3 py-2 not-prose hover:bg-fd-accent">
<span className="pe-2 min-w-fit font-medium w-[25%]">
<code
className={cn(
keyVariants({
deprecated: item.deprecated,
deprecated,
}),
)}
>
{name}
{!item.required && '?'}
{!required && '?'}
</code>
</span>
{item.typeDescriptionLink ? (
<Link
href={item.typeDescriptionLink}
className="underline @max-xl:hidden"
>
{item.type}
{typeDescriptionLink ? (
<Link href={typeDescriptionLink} className="underline @max-xl:hidden">
{type}
</Link>
) : (
<span className="@max-xl:hidden">{item.type}</span>
<span className="@max-xl:hidden">{type}</span>
)}
<ChevronDown className="absolute end-2 size-4 text-fd-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
</CollapsibleTrigger>
<CollapsibleContent>
<div className="grid grid-cols-1 gap-y-4 text-sm p-3 overflow-auto fd-scroll-container prose-no-margin @xl:grid-cols-[1fr_3fr]">
{item.description && (
<div className="grid grid-cols-[1fr_3fr] gap-y-4 text-sm p-3 overflow-auto fd-scroll-container border-t">
<div className="text-sm prose col-span-full prose-no-margin empty:hidden">
{description}
</div>
{typeDescription && (
<>
<p className="text-fd-muted-foreground font-medium not-prose">
Description
</p>
<div className="text-sm my-auto prose">{item.description}</div>
<p className={cn(fieldVariants())}>Type</p>
<p className="my-auto not-prose">{typeDescription}</p>
</>
)}
{item.typeDescription && (
{defaultValue && (
<>
<p className="text-fd-muted-foreground font-medium not-prose">
Type
</p>
<p className="my-auto not-prose">{item.typeDescription}</p>
<p className={cn(fieldVariants())}>Default</p>
<p className="my-auto not-prose">{defaultValue}</p>
</>
)}
{item.default && (
{parameters.length > 0 && (
<>
<p className="text-fd-muted-foreground font-medium not-prose">
Default
</p>
<p className="my-auto not-prose">{item.default}</p>
<p className={cn(fieldVariants())}>Parameters</p>
<div className="flex flex-col gap-2">
{parameters.map((param) => (
<div
key={param.name}
className="inline-flex items-center gap-1"
>
<p className="font-medium not-prose">{param.name} -</p>
<div className="text-sm prose prose-no-margin">
{param.description}
</div>
</div>
))}
</div>
</>
)}
{returns && (
<>
<p className={cn(fieldVariants())}>Returns</p>
<div className="my-auto text-sm prose prose-no-margin">
{returns}
</div>
</>
)}
</div>