@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.
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ export interface ExamplesSectionProps {
6
+ examples: string[];
7
+ }
8
+
9
+ function CopyButton({ text }: { text: string }) {
10
+ const [copied, setCopied] = useState(false);
11
+
12
+ const handleCopy = async () => {
13
+ await navigator.clipboard.writeText(text);
14
+ setCopied(true);
15
+ setTimeout(() => setCopied(false), 2000);
16
+ };
17
+
18
+ return (
19
+ <button
20
+ type="button"
21
+ onClick={handleCopy}
22
+ className="absolute top-2 right-2 p-1.5 rounded-md bg-fd-secondary hover:bg-fd-accent text-fd-muted-foreground hover:text-fd-foreground transition-colors opacity-0 group-hover:opacity-100"
23
+ aria-label="Copy code"
24
+ >
25
+ {copied ? (
26
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
27
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
28
+ </svg>
29
+ ) : (
30
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
31
+ <path
32
+ strokeLinecap="round"
33
+ strokeLinejoin="round"
34
+ strokeWidth={2}
35
+ d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
36
+ />
37
+ </svg>
38
+ )}
39
+ </button>
40
+ );
41
+ }
42
+
43
+ export function ExamplesSection({ examples }: ExamplesSectionProps) {
44
+ const [activeIndex, setActiveIndex] = useState(0);
45
+
46
+ if (!examples?.length) return null;
47
+
48
+ const showTabs = examples.length > 1;
49
+
50
+ return (
51
+ <div className="my-6">
52
+ <h3 className="text-lg font-semibold mb-3">Examples</h3>
53
+
54
+ {showTabs && (
55
+ <div className="flex gap-1 mb-2 border-b border-fd-border">
56
+ {examples.map((_, index) => (
57
+ <button
58
+ key={index}
59
+ type="button"
60
+ onClick={() => setActiveIndex(index)}
61
+ className={`px-3 py-1.5 text-sm font-medium transition-colors ${
62
+ activeIndex === index
63
+ ? 'text-fd-primary border-b-2 border-fd-primary -mb-px'
64
+ : 'text-fd-muted-foreground hover:text-fd-foreground'
65
+ }`}
66
+ >
67
+ Example {index + 1}
68
+ </button>
69
+ ))}
70
+ </div>
71
+ )}
72
+
73
+ <div className="group relative">
74
+ <pre className="overflow-x-auto rounded-lg border border-fd-border bg-fd-secondary p-4">
75
+ <code className="font-mono text-sm text-fd-foreground whitespace-pre">
76
+ {examples[activeIndex]}
77
+ </code>
78
+ </pre>
79
+ <CopyButton text={examples[activeIndex]} />
80
+ </div>
81
+ </div>
82
+ );
83
+ }
84
+
@@ -0,0 +1,240 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import type { SpecSignatureParameter, SpecSchema } from '@openpkg-ts/spec';
5
+
6
+ export interface ExpandablePropertyProps {
7
+ param: SpecSignatureParameter;
8
+ depth?: number;
9
+ }
10
+
11
+ export interface NestedPropertyProps {
12
+ name: string;
13
+ schema: SpecSchema;
14
+ required?: boolean;
15
+ depth?: number;
16
+ }
17
+
18
+ // Chevron icon component
19
+ function ChevronIcon({ expanded }: { expanded: boolean }) {
20
+ return (
21
+ <svg
22
+ width="12"
23
+ height="12"
24
+ viewBox="0 0 12 12"
25
+ fill="none"
26
+ className={`transition-transform duration-200 ${expanded ? 'rotate-90' : ''}`}
27
+ >
28
+ <path
29
+ d="M4.5 2.5L8 6L4.5 9.5"
30
+ stroke="currentColor"
31
+ strokeWidth="1.5"
32
+ strokeLinecap="round"
33
+ strokeLinejoin="round"
34
+ />
35
+ </svg>
36
+ );
37
+ }
38
+
39
+ function formatType(schema: SpecSchema): string {
40
+ if (!schema) return 'unknown';
41
+ if (typeof schema === 'string') return schema;
42
+ if (typeof schema === 'object' && schema !== null) {
43
+ const s = schema as Record<string, unknown>;
44
+
45
+ // Use tsType if available (most readable)
46
+ if (s.tsType && typeof s.tsType === 'string') {
47
+ const tsType = s.tsType as string;
48
+ if (tsType.length > 80) {
49
+ return tsType.slice(0, 77) + '...';
50
+ }
51
+ return tsType;
52
+ }
53
+
54
+ // Handle refs
55
+ if (s.$ref && typeof s.$ref === 'string') {
56
+ return (s.$ref as string).replace('#/types/', '');
57
+ }
58
+
59
+ // Handle enums
60
+ if (s.enum && Array.isArray(s.enum)) {
61
+ const enumVals = (s.enum as unknown[]).map(v => JSON.stringify(v)).join(' | ');
62
+ if (enumVals.length > 50) return enumVals.slice(0, 47) + '...';
63
+ return enumVals;
64
+ }
65
+
66
+ // Handle anyOf/oneOf
67
+ if (s.anyOf && Array.isArray(s.anyOf)) {
68
+ return (s.anyOf as SpecSchema[]).map(formatType).join(' | ');
69
+ }
70
+ if (s.oneOf && Array.isArray(s.oneOf)) {
71
+ return (s.oneOf as SpecSchema[]).map(formatType).join(' | ');
72
+ }
73
+
74
+ // Handle arrays
75
+ if (s.type === 'array' && s.items) {
76
+ return `${formatType(s.items as SpecSchema)}[]`;
77
+ }
78
+
79
+ // Handle basic types
80
+ if (s.type) return String(s.type);
81
+ }
82
+ return 'unknown';
83
+ }
84
+
85
+ function getNestedProperties(schema: SpecSchema): Record<string, SpecSchema> | null {
86
+ if (!schema || typeof schema !== 'object') return null;
87
+ const s = schema as Record<string, unknown>;
88
+ if (s.type === 'object' && s.properties && typeof s.properties === 'object') {
89
+ return s.properties as Record<string, SpecSchema>;
90
+ }
91
+ return null;
92
+ }
93
+
94
+ function getRequiredFields(schema: SpecSchema): string[] {
95
+ if (!schema || typeof schema !== 'object') return [];
96
+ const s = schema as Record<string, unknown>;
97
+ if (Array.isArray(s.required)) {
98
+ return s.required as string[];
99
+ }
100
+ return [];
101
+ }
102
+
103
+ function countProperties(schema: SpecSchema): number {
104
+ const props = getNestedProperties(schema);
105
+ return props ? Object.keys(props).length : 0;
106
+ }
107
+
108
+ /**
109
+ * Nested property row with expandable nested objects
110
+ */
111
+ export function NestedProperty({ name, schema, required = false, depth = 0 }: NestedPropertyProps) {
112
+ const [expanded, setExpanded] = useState(false);
113
+ const type = formatType(schema);
114
+ const nestedProps = getNestedProperties(schema);
115
+ const nestedCount = countProperties(schema);
116
+ const hasNested = nestedCount > 0;
117
+
118
+ // Get description from schema
119
+ const schemaObj = schema as Record<string, unknown> | null;
120
+ const description = schemaObj?.description as string | undefined;
121
+
122
+ return (
123
+ <div className="flex flex-col border-b border-fd-border last:border-0">
124
+ {/* Property row */}
125
+ <div className="flex flex-row items-start gap-2 py-2.5 px-3">
126
+ {/* Name and type */}
127
+ <div className="flex-1 min-w-0">
128
+ <div className="flex items-baseline gap-2 flex-wrap">
129
+ <span className="font-mono text-sm font-medium text-fd-foreground">
130
+ {name}
131
+ {!required && '?'}:
132
+ </span>
133
+ <span className="font-mono text-sm text-fd-muted-foreground">
134
+ {hasNested ? 'object' : type}
135
+ </span>
136
+ </div>
137
+ {description && (
138
+ <p className="text-sm text-fd-muted-foreground mt-0.5 leading-relaxed">
139
+ {description}
140
+ </p>
141
+ )}
142
+ </div>
143
+
144
+ {/* Expand badge for nested objects */}
145
+ {hasNested && (
146
+ <button
147
+ onClick={() => setExpanded(!expanded)}
148
+ className="flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded-md
149
+ bg-fd-muted text-fd-muted-foreground hover:bg-fd-accent hover:text-fd-accent-foreground
150
+ transition-colors cursor-pointer shrink-0"
151
+ >
152
+ <ChevronIcon expanded={expanded} />
153
+ <span>{nestedCount} properties</span>
154
+ </button>
155
+ )}
156
+ </div>
157
+
158
+ {/* Expanded nested properties */}
159
+ {hasNested && expanded && nestedProps && (
160
+ <div className="mx-3 mb-3 rounded-lg border border-fd-border bg-fd-card/50 overflow-hidden">
161
+ {Object.entries(nestedProps).map(([propName, propSchema]) => (
162
+ <NestedProperty
163
+ key={propName}
164
+ name={propName}
165
+ schema={propSchema}
166
+ required={getRequiredFields(schema).includes(propName)}
167
+ depth={depth + 1}
168
+ />
169
+ ))}
170
+ </div>
171
+ )}
172
+ </div>
173
+ );
174
+ }
175
+
176
+ /**
177
+ * Top-level expandable property for method parameters
178
+ * Entry point for rendering a parameter with progressive disclosure
179
+ */
180
+ export function ExpandableProperty({ param, depth = 0 }: ExpandablePropertyProps) {
181
+ const [expanded, setExpanded] = useState(false);
182
+ const type = formatType(param.schema);
183
+ const isOptional = param.required === false;
184
+ const nestedProps = getNestedProperties(param.schema);
185
+ const nestedCount = countProperties(param.schema);
186
+ const hasNested = nestedCount > 0;
187
+
188
+ return (
189
+ <div className="flex flex-col border-b border-fd-border last:border-0">
190
+ {/* Parameter row */}
191
+ <div className="flex flex-row items-start gap-2 py-3">
192
+ {/* Name and type */}
193
+ <div className="flex-1 min-w-0">
194
+ <div className="flex items-baseline gap-2 flex-wrap">
195
+ <span className="font-mono text-sm font-medium text-fd-foreground">
196
+ {param.name}
197
+ {isOptional && '?'}:
198
+ </span>
199
+ <span className="font-mono text-sm text-fd-muted-foreground">
200
+ {hasNested ? 'object' : type}
201
+ </span>
202
+ </div>
203
+ {param.description && (
204
+ <p className="text-sm text-fd-muted-foreground mt-1 leading-relaxed">
205
+ {param.description}
206
+ </p>
207
+ )}
208
+ </div>
209
+
210
+ {/* Expand badge for nested objects */}
211
+ {hasNested && (
212
+ <button
213
+ onClick={() => setExpanded(!expanded)}
214
+ className="flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded-md
215
+ bg-fd-muted text-fd-muted-foreground hover:bg-fd-accent hover:text-fd-accent-foreground
216
+ transition-colors cursor-pointer shrink-0"
217
+ >
218
+ <ChevronIcon expanded={expanded} />
219
+ <span>{nestedCount} properties</span>
220
+ </button>
221
+ )}
222
+ </div>
223
+
224
+ {/* Expanded nested properties */}
225
+ {hasNested && expanded && nestedProps && (
226
+ <div className="ml-4 mb-3 rounded-lg border border-fd-border bg-fd-card/50 overflow-hidden">
227
+ {Object.entries(nestedProps).map(([propName, propSchema]) => (
228
+ <NestedProperty
229
+ key={propName}
230
+ name={propName}
231
+ schema={propSchema}
232
+ required={getRequiredFields(param.schema).includes(propName)}
233
+ depth={depth + 1}
234
+ />
235
+ ))}
236
+ </div>
237
+ )}
238
+ </div>
239
+ );
240
+ }
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+
3
+ import type { OpenPkg, SpecExport } from '@openpkg-ts/spec';
4
+ import { ParameterCard } from './parameter-card';
5
+ import { CodeExample } from './code-example';
6
+ import { CoverageBadge } from './coverage-badge';
7
+
8
+ export interface FunctionPageProps {
9
+ export: SpecExport;
10
+ spec: OpenPkg;
11
+ }
12
+
13
+ function formatSchema(schema: unknown): string {
14
+ if (!schema) return 'unknown';
15
+ if (typeof schema === 'string') return schema;
16
+ if (typeof schema === 'object' && schema !== null) {
17
+ const s = schema as Record<string, unknown>;
18
+ if (s.$ref && typeof s.$ref === 'string') {
19
+ return s.$ref.replace('#/types/', '');
20
+ }
21
+ if (s.tsType) return String(s.tsType);
22
+ if (s.type) return String(s.type);
23
+ }
24
+ return 'unknown';
25
+ }
26
+
27
+ export function FunctionPage({ export: exp, spec }: FunctionPageProps) {
28
+ const sig = exp.signatures?.[0];
29
+ const hasExamples = exp.examples && exp.examples.length > 0;
30
+ const hasParams = sig?.parameters && sig.parameters.length > 0;
31
+
32
+ return (
33
+ <div className="space-y-6 not-prose">
34
+ {/* Description */}
35
+ {exp.description && (
36
+ <p className="text-fd-muted-foreground leading-relaxed">
37
+ {exp.description}
38
+ </p>
39
+ )}
40
+
41
+ {/* Returns */}
42
+ {sig?.returns && (
43
+ <p className="text-fd-muted-foreground text-sm">
44
+ <span className="font-medium text-fd-foreground">Returns:</span>{' '}
45
+ {sig.returns.description || `A ${sig.returns.tsType ?? formatSchema(sig.returns.schema)}`}
46
+ </p>
47
+ )}
48
+
49
+ {/* Two-column layout - using inline styles to override prose */}
50
+ <div
51
+ className="not-prose"
52
+ style={{
53
+ display: hasExamples ? 'grid' : 'block',
54
+ gridTemplateColumns: hasExamples ? 'repeat(2, minmax(0, 1fr))' : undefined,
55
+ gap: '2rem',
56
+ alignItems: 'start'
57
+ }}
58
+ >
59
+ {/* Left column: Parameters */}
60
+ <div className="space-y-6">
61
+ {hasParams && (
62
+ <div>
63
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-fd-muted-foreground mb-4">
64
+ Parameters
65
+ </h3>
66
+ <div className="space-y-3">
67
+ {sig.parameters!.map((param, index) => (
68
+ <ParameterCard key={param.name ?? index} param={param} spec={spec} />
69
+ ))}
70
+ </div>
71
+ </div>
72
+ )}
73
+ </div>
74
+
75
+ {/* Right column: Examples */}
76
+ {hasExamples && (
77
+ <div style={{ position: 'sticky', top: '5rem' }}>
78
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-fd-muted-foreground mb-4">
79
+ Example
80
+ </h3>
81
+ <CodeExample
82
+ code={exp.examples![0]}
83
+ filename={`${exp.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}.ts`}
84
+ />
85
+ </div>
86
+ )}
87
+ </div>
88
+
89
+ {/* Coverage */}
90
+ {exp.docs && <CoverageBadge docs={exp.docs} />}
91
+ </div>
92
+ );
93
+ }
@@ -0,0 +1,51 @@
1
+ // Main component
2
+ export { APIPage } from './api-page';
3
+ export type { APIPageProps } from './api-page';
4
+
5
+ // Page components
6
+ export { FunctionPage } from './function-page';
7
+ export type { FunctionPageProps } from './function-page';
8
+
9
+ export { ClassPage } from './class-page';
10
+ export type { ClassPageProps } from './class-page';
11
+
12
+ export { InterfacePage } from './interface-page';
13
+ export type { InterfacePageProps } from './interface-page';
14
+
15
+ export { EnumPage } from './enum-page';
16
+ export type { EnumPageProps } from './enum-page';
17
+
18
+ export { VariablePage } from './variable-page';
19
+ export type { VariablePageProps } from './variable-page';
20
+
21
+ // Shared components
22
+ export { TypeTable } from './type-table';
23
+ export type { TypeTableProps } from './type-table';
24
+
25
+ export { Signature } from './signature';
26
+ export type { SignatureProps } from './signature';
27
+
28
+ export { ExamplesSection } from './examples';
29
+ export type { ExamplesSectionProps } from './examples';
30
+
31
+ export { CoverageBadge } from './coverage-badge';
32
+ export type { CoverageBadgeProps } from './coverage-badge';
33
+
34
+ export { MembersSection } from './members-section';
35
+ export type { MembersSectionProps } from './members-section';
36
+
37
+ export { ParameterCard } from './parameter-card';
38
+ export type { ParameterCardProps } from './parameter-card';
39
+
40
+ export { CodeExample } from './code-example';
41
+ export type { CodeExampleProps } from './code-example';
42
+
43
+ export { ExpandableProperty, NestedProperty } from './expandable-property';
44
+ export type { ExpandablePropertyProps, NestedPropertyProps } from './expandable-property';
45
+
46
+ export { CollapsibleMethod } from './collapsible-method';
47
+ export type { CollapsibleMethodProps } from './collapsible-method';
48
+
49
+ export { MethodSection } from './method-section';
50
+ export type { MethodSectionProps } from './method-section';
51
+
@@ -0,0 +1,94 @@
1
+ 'use client';
2
+
3
+ import type { OpenPkg, SpecExport } from '@openpkg-ts/spec';
4
+ import { Signature } from './signature';
5
+ import { TypeTable } from './type-table';
6
+ import { ExamplesSection } from './examples';
7
+ import { CoverageBadge } from './coverage-badge';
8
+
9
+ export interface InterfacePageProps {
10
+ export: SpecExport;
11
+ spec: OpenPkg;
12
+ }
13
+
14
+ export function InterfacePage({ export: exp, spec }: InterfacePageProps) {
15
+ // For interfaces/types, members are the properties
16
+ const properties = exp.members?.filter(
17
+ (m) => m.kind === 'property' || m.kind === 'field' || !m.kind
18
+ );
19
+ const methods = exp.members?.filter((m) => m.kind === 'method' || m.kind === 'function');
20
+
21
+ return (
22
+ <div className="space-y-6">
23
+ {/* Description */}
24
+ {exp.description && (
25
+ <p className="text-fd-muted-foreground text-base leading-relaxed">{exp.description}</p>
26
+ )}
27
+
28
+ {/* Signature */}
29
+ <section>
30
+ <h2 className="text-xl font-semibold mb-2">Declaration</h2>
31
+ <Signature export={exp} />
32
+ </section>
33
+
34
+ {/* Extends */}
35
+ {exp.extends && (
36
+ <section>
37
+ <h2 className="text-xl font-semibold mb-2">Extends</h2>
38
+ <div className="rounded-lg border border-fd-border bg-fd-card p-4">
39
+ <code className="font-mono text-sm text-fd-primary">{exp.extends}</code>
40
+ </div>
41
+ </section>
42
+ )}
43
+
44
+ {/* Properties */}
45
+ {properties && properties.length > 0 && (
46
+ <section>
47
+ <h2 className="text-xl font-semibold mb-2">Properties</h2>
48
+ <TypeTable items={properties} spec={spec} showRequired={true} />
49
+ </section>
50
+ )}
51
+
52
+ {/* Methods */}
53
+ {methods && methods.length > 0 && (
54
+ <section>
55
+ <h2 className="text-xl font-semibold mb-2">Methods</h2>
56
+ <div className="space-y-4">
57
+ {methods.map((method, index) => {
58
+ const sig = method.signatures?.[0];
59
+ const params = sig?.parameters ?? [];
60
+ const returnType = sig?.returns?.tsType ?? 'void';
61
+
62
+ return (
63
+ <div key={method.name ?? index} className="rounded-lg border border-fd-border p-4">
64
+ <code className="font-mono text-sm text-fd-primary">
65
+ {method.name}(
66
+ {params
67
+ .map((p) => {
68
+ const optional = p.required === false ? '?' : '';
69
+ const type =
70
+ typeof p.schema === 'string' ? p.schema : (p.schema as any)?.tsType ?? 'any';
71
+ return `${p.name}${optional}: ${type}`;
72
+ })
73
+ .join(', ')}
74
+ ): {returnType}
75
+ </code>
76
+ {method.description && (
77
+ <p className="text-sm text-fd-muted-foreground mt-2">{method.description}</p>
78
+ )}
79
+ </div>
80
+ );
81
+ })}
82
+ </div>
83
+ </section>
84
+ )}
85
+
86
+ {/* Examples */}
87
+ {exp.examples && exp.examples.length > 0 && <ExamplesSection examples={exp.examples} />}
88
+
89
+ {/* Coverage */}
90
+ {exp.docs && <CoverageBadge docs={exp.docs} />}
91
+ </div>
92
+ );
93
+ }
94
+