@doccov/fumadocs-adapter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@doccov/fumadocs-adapter",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "exports": {
7
+ ".": "./src/index.ts",
8
+ "./server": "./src/server.ts",
9
+ "./components": "./src/components/index.ts",
10
+ "./css": "./src/styles/docskit.css"
11
+ },
12
+ "scripts": {
13
+ "build": "bunup",
14
+ "lint": "biome check src/",
15
+ "lint:fix": "biome check --write src/",
16
+ "typecheck": "tsc --noEmit"
17
+ },
18
+ "dependencies": {
19
+ "@openpkg-ts/spec": "../spec"
20
+ },
21
+ "peerDependencies": {
22
+ "@doccov/ui": "../ui",
23
+ "codehike": "^1.0.0",
24
+ "react": "^19.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "^19.0.0",
28
+ "bunup": "^0.11.5",
29
+ "codehike": "^1.0.7",
30
+ "typescript": "^5.0.0"
31
+ }
32
+ }
33
+
@@ -0,0 +1,90 @@
1
+ 'use client';
2
+
3
+ import type { OpenPkg, SpecExport } from '@openpkg-ts/spec';
4
+ import type { OpenPkgInstance } from '../server';
5
+ import { FunctionPage } from './function-page';
6
+ import { ClassPage } from './class-page';
7
+ import { InterfacePage } from './interface-page';
8
+ import { EnumPage } from './enum-page';
9
+ import { VariablePage } from './variable-page';
10
+
11
+ export interface APIPageProps {
12
+ /** Direct spec object */
13
+ spec?: OpenPkg;
14
+ /** Or server instance from createOpenPkg() */
15
+ instance?: OpenPkgInstance;
16
+ /** Export ID to render */
17
+ id: string;
18
+ }
19
+
20
+ function NotFound({ id }: { id: string }) {
21
+ return (
22
+ <div className="rounded-lg border border-fd-border bg-fd-card p-6 text-center">
23
+ <p className="text-fd-muted-foreground">
24
+ Export <code className="font-mono text-fd-primary">{id}</code> not found in spec.
25
+ </p>
26
+ </div>
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Main API page component that renders documentation for a single export.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * import { APIPage } from '@doccov/fumadocs-adapter';
36
+ * import spec from './openpkg.json';
37
+ *
38
+ * <APIPage spec={spec} id="createClient" />
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * // With server instance
44
+ * import { APIPage } from '@doccov/fumadocs-adapter';
45
+ * import { openpkg } from '@/lib/openpkg';
46
+ *
47
+ * <APIPage instance={openpkg} id="createClient" />
48
+ * ```
49
+ */
50
+ export function APIPage({ spec, instance, id }: APIPageProps) {
51
+ const resolvedSpec = spec ?? instance?.spec;
52
+
53
+ if (!resolvedSpec) {
54
+ return (
55
+ <div className="rounded-lg border border-red-500/20 bg-red-500/10 p-6 text-center">
56
+ <p className="text-red-600 dark:text-red-400">
57
+ No spec provided. Pass either <code>spec</code> or <code>instance</code> prop.
58
+ </p>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ const exp = resolvedSpec.exports.find((e) => e.id === id);
64
+
65
+ if (!exp) {
66
+ return <NotFound id={id} />;
67
+ }
68
+
69
+ const pageProps = { export: exp, spec: resolvedSpec };
70
+
71
+ switch (exp.kind) {
72
+ case 'function':
73
+ return <FunctionPage {...pageProps} />;
74
+ case 'class':
75
+ return <ClassPage {...pageProps} />;
76
+ case 'interface':
77
+ case 'type':
78
+ return <InterfacePage {...pageProps} />;
79
+ case 'enum':
80
+ return <EnumPage {...pageProps} />;
81
+ case 'variable':
82
+ case 'namespace':
83
+ case 'module':
84
+ case 'reference':
85
+ case 'external':
86
+ default:
87
+ return <VariablePage {...pageProps} />;
88
+ }
89
+ }
90
+
@@ -0,0 +1,165 @@
1
+ 'use client';
2
+
3
+ import type { OpenPkg, SpecExport, SpecMember } from '@openpkg-ts/spec';
4
+ import { CodeExample } from './code-example';
5
+ import { CoverageBadge } from './coverage-badge';
6
+ import { ExpandableProperty } from './expandable-property';
7
+ import { CollapsibleMethod } from './collapsible-method';
8
+
9
+ export interface ClassPageProps {
10
+ export: SpecExport;
11
+ spec: OpenPkg;
12
+ }
13
+
14
+ function formatSchema(schema: unknown): string {
15
+ if (!schema) return 'unknown';
16
+ if (typeof schema === 'string') return schema;
17
+ if (typeof schema === 'object' && schema !== null) {
18
+ const s = schema as Record<string, unknown>;
19
+ if (s.$ref && typeof s.$ref === 'string') {
20
+ return s.$ref.replace('#/types/', '');
21
+ }
22
+ if (s.tsType) return String(s.tsType);
23
+ if (s.type) return String(s.type);
24
+ }
25
+ return 'unknown';
26
+ }
27
+
28
+ /**
29
+ * Compact property display for class properties (not method params)
30
+ */
31
+ function PropertyItem({ member }: { member: SpecMember }) {
32
+ const visibility = member.visibility ?? 'public';
33
+ const flags = member.flags as Record<string, boolean> | undefined;
34
+ const isStatic = flags?.static;
35
+ const isReadonly = flags?.readonly;
36
+ const type = formatSchema(member.schema);
37
+
38
+ return (
39
+ <div className="py-3 border-b border-fd-border last:border-0">
40
+ <div className="flex items-baseline gap-2 flex-wrap">
41
+ <span className="font-semibold text-fd-foreground">{member.name}:</span>
42
+ <span className="text-fd-muted-foreground font-mono text-sm">{type}</span>
43
+ {visibility !== 'public' && (
44
+ <span className="text-xs px-1.5 py-0.5 rounded bg-fd-muted text-fd-muted-foreground">
45
+ {visibility}
46
+ </span>
47
+ )}
48
+ {isStatic && (
49
+ <span className="text-xs px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">
50
+ static
51
+ </span>
52
+ )}
53
+ {isReadonly && (
54
+ <span className="text-xs px-1.5 py-0.5 rounded bg-purple-500/10 text-purple-600 dark:text-purple-400">
55
+ readonly
56
+ </span>
57
+ )}
58
+ </div>
59
+ {member.description && (
60
+ <p className="text-sm text-fd-muted-foreground mt-1">{member.description}</p>
61
+ )}
62
+ </div>
63
+ );
64
+ }
65
+
66
+ export function ClassPage({ export: exp, spec }: ClassPageProps) {
67
+ const hasExamples = exp.examples && exp.examples.length > 0;
68
+
69
+ // Group members
70
+ const constructors = exp.members?.filter((m) => m.kind === 'constructor') ?? [];
71
+ const properties = exp.members?.filter((m) => m.kind === 'property' || m.kind === 'field') ?? [];
72
+ const methods = exp.members?.filter((m) => m.kind === 'method') ?? [];
73
+
74
+ // Get constructor parameters
75
+ const constructorSig = constructors[0]?.signatures?.[0];
76
+ const constructorParams = constructorSig?.parameters ?? [];
77
+
78
+ return (
79
+ <div className="space-y-8">
80
+ {/* Description */}
81
+ {exp.description && (
82
+ <p className="text-fd-muted-foreground text-lg leading-relaxed">
83
+ {exp.description}
84
+ </p>
85
+ )}
86
+
87
+ {/* Declaration */}
88
+ <div className="rounded-lg border border-fd-border bg-fd-muted/30 p-4 overflow-x-auto">
89
+ <code className="font-mono text-sm text-fd-foreground whitespace-pre">
90
+ class {exp.name}
91
+ {exp.extends ? ` extends ${exp.extends}` : ''}
92
+ {exp.implements?.length ? ` implements ${exp.implements.join(', ')}` : ''}
93
+ </code>
94
+ </div>
95
+
96
+ {/* Two-column layout for content + example */}
97
+ <div className={`grid gap-8 ${hasExamples ? 'lg:grid-cols-2' : 'grid-cols-1'}`}>
98
+ {/* Left column: Constructor, Methods, Properties */}
99
+ <div className="space-y-8">
100
+ {/* Constructor */}
101
+ {constructorParams.length > 0 && (
102
+ <section>
103
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-fd-muted-foreground mb-4">
104
+ Constructor
105
+ </h3>
106
+ <div className="ml-2 border-l-2 border-fd-border pl-4">
107
+ {constructorParams.map((param, index) => (
108
+ <ExpandableProperty key={param.name ?? index} param={param} />
109
+ ))}
110
+ </div>
111
+ </section>
112
+ )}
113
+
114
+ {/* Methods - collapsible sections */}
115
+ {methods.length > 0 && (
116
+ <section>
117
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-fd-muted-foreground mb-4">
118
+ Methods
119
+ </h3>
120
+ <div className="rounded-lg border border-fd-border overflow-hidden">
121
+ {methods.map((member, index) => (
122
+ <CollapsibleMethod
123
+ key={member.name ?? index}
124
+ member={member}
125
+ defaultExpanded={index === 0}
126
+ />
127
+ ))}
128
+ </div>
129
+ </section>
130
+ )}
131
+
132
+ {/* Properties - compact list */}
133
+ {properties.length > 0 && (
134
+ <section>
135
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-fd-muted-foreground mb-4">
136
+ Properties
137
+ </h3>
138
+ <div className="rounded-lg border border-fd-border bg-fd-card px-4">
139
+ {properties.map((member, index) => (
140
+ <PropertyItem key={member.name ?? index} member={member} />
141
+ ))}
142
+ </div>
143
+ </section>
144
+ )}
145
+ </div>
146
+
147
+ {/* Right column: Examples */}
148
+ {hasExamples && (
149
+ <div className="lg:sticky lg:top-20 lg:self-start">
150
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-fd-muted-foreground mb-4">
151
+ Example
152
+ </h3>
153
+ <CodeExample
154
+ code={exp.examples![0]}
155
+ filename={`${exp.name.toLowerCase()}.ts`}
156
+ />
157
+ </div>
158
+ )}
159
+ </div>
160
+
161
+ {/* Coverage */}
162
+ {exp.docs && <CoverageBadge docs={exp.docs} />}
163
+ </div>
164
+ );
165
+ }
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+
3
+ import { ClientDocsKitCode } from '@doccov/ui/docskit';
4
+ import type { RawCode } from 'codehike/code';
5
+
6
+ export interface CodeExampleProps {
7
+ code: string;
8
+ filename?: string;
9
+ language?: string;
10
+ }
11
+
12
+ /**
13
+ * Cleans up code by removing markdown code fence markers if present.
14
+ */
15
+ function cleanCode(code: string): string {
16
+ let cleaned = code.trim();
17
+ if (cleaned.startsWith('```')) {
18
+ const lines = cleaned.split('\n');
19
+ lines.shift(); // Remove opening ```ts or ```typescript
20
+ if (lines[lines.length - 1] === '```') {
21
+ lines.pop(); // Remove closing ```
22
+ }
23
+ cleaned = lines.join('\n');
24
+ }
25
+ return cleaned;
26
+ }
27
+
28
+ export function CodeExample({ code, filename = 'example.ts', language = 'typescript' }: CodeExampleProps) {
29
+ const cleaned = cleanCode(code);
30
+
31
+ // Build RawCode object for ClientDocsKitCode
32
+ // The meta field uses flags: 'c' for copyButton, 'n' for lineNumbers
33
+ const codeblock: RawCode = {
34
+ value: cleaned,
35
+ lang: language,
36
+ meta: `${filename} -cn`, // title + copyButton + lineNumbers flags (must prefix with -)
37
+ };
38
+
39
+ return <ClientDocsKitCode codeblock={codeblock} className="not-fumadocs-codeblock" />;
40
+ }
@@ -0,0 +1,185 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import type { SpecMember, SpecSchema } from '@openpkg-ts/spec';
5
+ import { ExpandableProperty } from './expandable-property';
6
+
7
+ export interface CollapsibleMethodProps {
8
+ member: SpecMember;
9
+ defaultExpanded?: boolean;
10
+ }
11
+
12
+ // Chevron icon component
13
+ function ChevronIcon({ expanded }: { expanded: boolean }) {
14
+ return (
15
+ <svg
16
+ width="16"
17
+ height="16"
18
+ viewBox="0 0 16 16"
19
+ fill="none"
20
+ className={`transition-transform duration-200 ${expanded ? 'rotate-90' : ''}`}
21
+ >
22
+ <path
23
+ d="M6 4L10 8L6 12"
24
+ stroke="currentColor"
25
+ strokeWidth="1.5"
26
+ strokeLinecap="round"
27
+ strokeLinejoin="round"
28
+ />
29
+ </svg>
30
+ );
31
+ }
32
+
33
+ function formatSchema(schema: unknown): string {
34
+ if (!schema) return 'unknown';
35
+ if (typeof schema === 'string') return schema;
36
+ if (typeof schema === 'object' && schema !== null) {
37
+ const s = schema as Record<string, unknown>;
38
+ if (s.$ref && typeof s.$ref === 'string') {
39
+ return s.$ref.replace('#/types/', '');
40
+ }
41
+ if (s.tsType) {
42
+ const tsType = String(s.tsType);
43
+ if (tsType.length > 40) return tsType.slice(0, 37) + '...';
44
+ return tsType;
45
+ }
46
+ if (s.type) return String(s.type);
47
+ }
48
+ return 'unknown';
49
+ }
50
+
51
+ function formatReturnType(returns: { schema?: SpecSchema; tsType?: string } | undefined): string {
52
+ if (!returns) return 'void';
53
+ if (returns.tsType) {
54
+ const t = returns.tsType;
55
+ if (t.length > 40) return t.slice(0, 37) + '...';
56
+ return t;
57
+ }
58
+ return formatSchema(returns.schema);
59
+ }
60
+
61
+ function formatParamPreview(params: { name?: string }[] | undefined): string {
62
+ if (!params || params.length === 0) return '';
63
+ if (params.length === 1) return params[0].name || 'arg';
64
+ return `${params[0].name || 'arg'}, ...`;
65
+ }
66
+
67
+ /**
68
+ * Collapsible method section with expand/collapse behavior
69
+ * Shows compact signature when collapsed, full details when expanded
70
+ */
71
+ export function CollapsibleMethod({ member, defaultExpanded = false }: CollapsibleMethodProps) {
72
+ const [expanded, setExpanded] = useState(defaultExpanded);
73
+
74
+ const sig = member.signatures?.[0];
75
+ const hasParams = sig?.parameters && sig.parameters.length > 0;
76
+ const visibility = member.visibility ?? 'public';
77
+ const flags = member.flags as Record<string, boolean> | undefined;
78
+ const isStatic = flags?.static;
79
+ const isAsync = flags?.async;
80
+
81
+ const returnType = formatReturnType(sig?.returns);
82
+ const returnDescription = sig?.returns?.description;
83
+ const paramPreview = formatParamPreview(sig?.parameters);
84
+
85
+ // Auto-expand if URL hash matches this method
86
+ useEffect(() => {
87
+ if (typeof window !== 'undefined' && window.location.hash === `#${member.name}`) {
88
+ setExpanded(true);
89
+ }
90
+ }, [member.name]);
91
+
92
+ return (
93
+ <div
94
+ id={member.name}
95
+ className="scroll-mt-20 border-b border-fd-border last:border-0"
96
+ >
97
+ {/* Clickable header */}
98
+ <button
99
+ onClick={() => setExpanded(!expanded)}
100
+ className="w-full flex items-center gap-3 py-4 px-1 text-left hover:bg-fd-muted/30 transition-colors cursor-pointer group"
101
+ >
102
+ {/* Chevron */}
103
+ <span className="text-fd-muted-foreground group-hover:text-fd-foreground transition-colors">
104
+ <ChevronIcon expanded={expanded} />
105
+ </span>
106
+
107
+ {/* Method signature preview */}
108
+ <div className="flex-1 min-w-0 flex items-baseline gap-2 flex-wrap">
109
+ <span className="font-mono text-sm font-semibold text-fd-foreground">
110
+ {member.name}
111
+ <span className="text-fd-muted-foreground font-normal">({paramPreview})</span>
112
+ </span>
113
+ <span className="text-fd-muted-foreground">→</span>
114
+ <span className="font-mono text-sm text-fd-muted-foreground truncate">
115
+ {returnType}
116
+ </span>
117
+ </div>
118
+
119
+ {/* Badges */}
120
+ <div className="flex gap-1.5 shrink-0">
121
+ {visibility !== 'public' && (
122
+ <span className="text-xs px-1.5 py-0.5 rounded bg-fd-muted text-fd-muted-foreground">
123
+ {visibility}
124
+ </span>
125
+ )}
126
+ {isStatic && (
127
+ <span className="text-xs px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-600 dark:text-blue-400">
128
+ static
129
+ </span>
130
+ )}
131
+ {isAsync && (
132
+ <span className="text-xs px-1.5 py-0.5 rounded bg-purple-500/10 text-purple-600 dark:text-purple-400">
133
+ async
134
+ </span>
135
+ )}
136
+ </div>
137
+ </button>
138
+
139
+ {/* Expanded content */}
140
+ {expanded && (
141
+ <div className="pb-6 pl-8 pr-4">
142
+ {/* Description */}
143
+ {member.description && (
144
+ <p className="text-fd-muted-foreground mb-4 leading-relaxed">
145
+ {member.description}
146
+ </p>
147
+ )}
148
+
149
+ {/* Parameters */}
150
+ {hasParams && (
151
+ <div className="mb-4">
152
+ <span className="text-xs uppercase tracking-wide text-fd-muted-foreground font-medium block mb-2">
153
+ Parameters
154
+ </span>
155
+ <div className="border-l-2 border-fd-border pl-4">
156
+ {sig.parameters!.map((param, index) => (
157
+ <ExpandableProperty key={param.name ?? index} param={param} />
158
+ ))}
159
+ </div>
160
+ </div>
161
+ )}
162
+
163
+ {/* Returns */}
164
+ {sig?.returns && returnType !== 'void' && (
165
+ <div>
166
+ <span className="text-xs uppercase tracking-wide text-fd-muted-foreground font-medium block mb-2">
167
+ Returns
168
+ </span>
169
+ <div className="border-l-2 border-fd-border pl-4 py-2">
170
+ <span className="font-mono text-sm text-fd-muted-foreground">
171
+ {sig.returns.tsType || formatSchema(sig.returns.schema)}
172
+ </span>
173
+ {returnDescription && (
174
+ <p className="text-sm text-fd-muted-foreground mt-1 leading-relaxed">
175
+ {returnDescription}
176
+ </p>
177
+ )}
178
+ </div>
179
+ </div>
180
+ )}
181
+ </div>
182
+ )}
183
+ </div>
184
+ );
185
+ }
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+
3
+ import type { SpecDocsMetadata } from '@openpkg-ts/spec';
4
+
5
+ export interface CoverageBadgeProps {
6
+ docs: SpecDocsMetadata;
7
+ showMissing?: boolean;
8
+ showDrift?: boolean;
9
+ }
10
+
11
+ function getScoreColor(score: number): string {
12
+ if (score >= 80) return 'text-green-600 dark:text-green-400 bg-green-500/10 border-green-500/20';
13
+ if (score >= 60) return 'text-yellow-600 dark:text-yellow-400 bg-yellow-500/10 border-yellow-500/20';
14
+ return 'text-red-600 dark:text-red-400 bg-red-500/10 border-red-500/20';
15
+ }
16
+
17
+ function formatSignal(signal: string): string {
18
+ return signal.charAt(0).toUpperCase() + signal.slice(1);
19
+ }
20
+
21
+ export function CoverageBadge({ docs, showMissing = true, showDrift = true }: CoverageBadgeProps) {
22
+ const score = docs.coverageScore;
23
+ const hasMissing = showMissing && docs.missing && docs.missing.length > 0;
24
+ const hasDrift = showDrift && docs.drift && docs.drift.length > 0;
25
+
26
+ if (score == null && !hasMissing && !hasDrift) return null;
27
+
28
+ return (
29
+ <div className="my-6 space-y-3">
30
+ {score != null && (
31
+ <div
32
+ className={`inline-flex items-center gap-2 px-3 py-1.5 rounded-md border text-sm font-medium ${getScoreColor(score)}`}
33
+ >
34
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
35
+ <path
36
+ strokeLinecap="round"
37
+ strokeLinejoin="round"
38
+ strokeWidth={2}
39
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
40
+ />
41
+ </svg>
42
+ Coverage: {score}%
43
+ </div>
44
+ )}
45
+
46
+ {hasMissing && (
47
+ <div className="rounded-md bg-yellow-500/10 border border-yellow-500/20 px-3 py-2">
48
+ <p className="text-sm font-medium text-yellow-600 dark:text-yellow-400 mb-1">
49
+ Missing Documentation
50
+ </p>
51
+ <ul className="text-sm text-yellow-600/80 dark:text-yellow-400/80 list-disc list-inside">
52
+ {docs.missing!.map((signal) => (
53
+ <li key={signal}>{formatSignal(signal)}</li>
54
+ ))}
55
+ </ul>
56
+ </div>
57
+ )}
58
+
59
+ {hasDrift && (
60
+ <div className="rounded-md bg-red-500/10 border border-red-500/20 px-3 py-2">
61
+ <p className="text-sm font-medium text-red-600 dark:text-red-400 mb-1">Documentation Drift</p>
62
+ <ul className="text-sm text-red-600/80 dark:text-red-400/80 space-y-1">
63
+ {docs.drift!.map((drift, index) => (
64
+ <li key={index} className="flex flex-col">
65
+ <span className="font-medium">{drift.type}</span>
66
+ <span className="text-xs opacity-80">{drift.issue}</span>
67
+ {drift.suggestion && (
68
+ <span className="text-xs text-fd-muted-foreground mt-0.5">
69
+ Suggestion: {drift.suggestion}
70
+ </span>
71
+ )}
72
+ </li>
73
+ ))}
74
+ </ul>
75
+ </div>
76
+ )}
77
+ </div>
78
+ );
79
+ }
80
+
@@ -0,0 +1,86 @@
1
+ 'use client';
2
+
3
+ import type { OpenPkg, SpecExport } from '@openpkg-ts/spec';
4
+ import { Signature } from './signature';
5
+ import { ExamplesSection } from './examples';
6
+ import { CoverageBadge } from './coverage-badge';
7
+
8
+ export interface EnumPageProps {
9
+ export: SpecExport;
10
+ spec: OpenPkg;
11
+ }
12
+
13
+ export function EnumPage({ export: exp, spec }: EnumPageProps) {
14
+ const members = exp.members ?? [];
15
+
16
+ return (
17
+ <div className="space-y-6">
18
+ {/* Description */}
19
+ {exp.description && (
20
+ <p className="text-fd-muted-foreground text-base leading-relaxed">{exp.description}</p>
21
+ )}
22
+
23
+ {/* Signature */}
24
+ <section>
25
+ <h2 className="text-xl font-semibold mb-2">Declaration</h2>
26
+ <Signature export={exp} />
27
+ </section>
28
+
29
+ {/* Enum Members */}
30
+ {members.length > 0 && (
31
+ <section>
32
+ <h2 className="text-xl font-semibold mb-2">Members</h2>
33
+ <div className="overflow-x-auto">
34
+ <table className="w-full text-sm border-collapse">
35
+ <thead>
36
+ <tr className="border-b border-fd-border">
37
+ <th className="text-left py-2 px-3 font-medium text-fd-muted-foreground">Name</th>
38
+ <th className="text-left py-2 px-3 font-medium text-fd-muted-foreground">Value</th>
39
+ <th className="text-left py-2 px-3 font-medium text-fd-muted-foreground">
40
+ Description
41
+ </th>
42
+ </tr>
43
+ </thead>
44
+ <tbody>
45
+ {members.map((member, index) => {
46
+ // For enum members, the value might be in schema or flags
47
+ const value =
48
+ member.schema !== undefined
49
+ ? typeof member.schema === 'object' && member.schema !== null
50
+ ? (member.schema as any).const ?? (member.schema as any).default ?? '-'
51
+ : member.schema
52
+ : '-';
53
+
54
+ return (
55
+ <tr key={member.name ?? index} className="border-b border-fd-border last:border-0">
56
+ <td className="py-2 px-3 align-top">
57
+ <code className="text-fd-primary font-mono text-xs bg-fd-secondary px-1.5 py-0.5 rounded">
58
+ {member.name}
59
+ </code>
60
+ </td>
61
+ <td className="py-2 px-3 align-top">
62
+ <code className="font-mono text-xs text-fd-muted-foreground">
63
+ {String(value)}
64
+ </code>
65
+ </td>
66
+ <td className="py-2 px-3 align-top text-fd-muted-foreground">
67
+ {member.description ?? ''}
68
+ </td>
69
+ </tr>
70
+ );
71
+ })}
72
+ </tbody>
73
+ </table>
74
+ </div>
75
+ </section>
76
+ )}
77
+
78
+ {/* Examples */}
79
+ {exp.examples && exp.examples.length > 0 && <ExamplesSection examples={exp.examples} />}
80
+
81
+ {/* Coverage */}
82
+ {exp.docs && <CoverageBadge docs={exp.docs} />}
83
+ </div>
84
+ );
85
+ }
86
+