@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/bunup.config.ts +10 -0
- package/dist/components/index.d.ts +160 -0
- package/dist/components/index.js +237 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +10 -0
- package/dist/server.d.ts +34 -0
- package/dist/server.js +38 -0
- package/dist/shared/chunk-pqaj3kdh.js +1488 -0
- package/package.json +33 -0
- package/src/components/api-page.tsx +90 -0
- package/src/components/class-page.tsx +165 -0
- package/src/components/code-example.tsx +40 -0
- package/src/components/collapsible-method.tsx +185 -0
- package/src/components/coverage-badge.tsx +80 -0
- package/src/components/enum-page.tsx +86 -0
- package/src/components/examples.tsx +84 -0
- package/src/components/expandable-property.tsx +240 -0
- package/src/components/function-page.tsx +93 -0
- package/src/components/index.ts +51 -0
- package/src/components/interface-page.tsx +94 -0
- package/src/components/members-section.tsx +193 -0
- package/src/components/method-section.tsx +18 -0
- package/src/components/parameter-card.tsx +53 -0
- package/src/components/signature.tsx +108 -0
- package/src/components/type-table.tsx +80 -0
- package/src/components/variable-page.tsx +78 -0
- package/src/index.ts +8 -0
- package/src/server.ts +80 -0
- package/src/styles/docskit.css +130 -0
- package/tsconfig.json +21 -0
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
|
+
|