@usecross/docs 0.10.2 → 0.12.0
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/dist/index.d.ts +164 -7
- package/dist/index.js +1304 -44
- package/dist/index.js.map +1 -1
- package/dist/ssr.d.ts +1 -1
- package/dist/types-_anC1UJu.d.ts +320 -0
- package/package.json +1 -1
- package/src/components/DocsLayout.tsx +75 -59
- package/src/components/Sidebar.tsx +171 -28
- package/src/components/TableOfContents.tsx +6 -6
- package/src/components/api/APILayout.tsx +231 -0
- package/src/components/api/APIPage.tsx +216 -0
- package/src/components/api/Breadcrumb.tsx +98 -0
- package/src/components/api/ClassDoc.tsx +257 -0
- package/src/components/api/CodeSpan.tsx +53 -0
- package/src/components/api/Docstring.tsx +269 -0
- package/src/components/api/FunctionDoc.tsx +130 -0
- package/src/components/api/ModuleDoc.tsx +298 -0
- package/src/components/api/ParameterTable.tsx +183 -0
- package/src/components/api/Signature.tsx +317 -0
- package/src/components/api/TableOfContents.tsx +50 -0
- package/src/components/api/index.ts +17 -0
- package/src/components/index.ts +13 -1
- package/src/index.ts +32 -0
- package/src/types.ts +222 -1
- package/dist/types-DlTTA3Dc.d.ts +0 -128
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { APIPageProps, GriffeModule, GriffeClass, GriffeFunction, GriffeMember, GriffeAlias } from '../../types'
|
|
2
|
+
import { APILayout } from './APILayout'
|
|
3
|
+
import { ModuleDoc } from './ModuleDoc'
|
|
4
|
+
import { ClassDoc } from './ClassDoc'
|
|
5
|
+
import { FunctionDoc } from './FunctionDoc'
|
|
6
|
+
import { TableOfContents, generateClassToc, type TocItem } from './TableOfContents'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve an alias to its target in the API data
|
|
10
|
+
*/
|
|
11
|
+
function resolveAlias(alias: GriffeAlias, apiData: GriffeModule): GriffeMember | null {
|
|
12
|
+
const targetPath = alias.target_path
|
|
13
|
+
if (!targetPath) return null
|
|
14
|
+
|
|
15
|
+
// Split the target path into parts
|
|
16
|
+
const parts = targetPath.split('.')
|
|
17
|
+
const packageName = apiData.name
|
|
18
|
+
|
|
19
|
+
let current: GriffeModule | GriffeClass = apiData
|
|
20
|
+
for (let i = 0; i < parts.length; i++) {
|
|
21
|
+
const part = parts[i]
|
|
22
|
+
|
|
23
|
+
// Skip the package name if it matches
|
|
24
|
+
if (i === 0 && part === packageName) continue
|
|
25
|
+
|
|
26
|
+
// Look in members
|
|
27
|
+
if (current.members) {
|
|
28
|
+
const member: GriffeMember | undefined = current.members[part]
|
|
29
|
+
if (member) {
|
|
30
|
+
// Only modules and classes have members to recurse into
|
|
31
|
+
if (member.kind === 'module' || member.kind === 'class') {
|
|
32
|
+
current = member as GriffeModule | GriffeClass
|
|
33
|
+
} else {
|
|
34
|
+
// Found a function, attribute, or alias - return it
|
|
35
|
+
return member
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return current
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generate TOC items based on item type
|
|
50
|
+
*/
|
|
51
|
+
function generateTocItems(item: GriffeMember, apiData: GriffeModule): TocItem[] {
|
|
52
|
+
// Resolve aliases first
|
|
53
|
+
if (item.kind === 'alias') {
|
|
54
|
+
const resolved = resolveAlias(item as GriffeAlias, apiData)
|
|
55
|
+
if (resolved) {
|
|
56
|
+
return generateTocItems(resolved, apiData)
|
|
57
|
+
}
|
|
58
|
+
return []
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (item.kind === 'class') {
|
|
62
|
+
return generateClassToc(item as GriffeClass)
|
|
63
|
+
}
|
|
64
|
+
if (item.kind === 'function') {
|
|
65
|
+
const fn = item as GriffeFunction
|
|
66
|
+
const items: TocItem[] = [{ id: fn.name, title: fn.name, level: 1 }]
|
|
67
|
+
if (fn.parameters && fn.parameters.length > 0) {
|
|
68
|
+
items.push({ id: 'parameters', title: 'Parameters', level: 2 })
|
|
69
|
+
}
|
|
70
|
+
return items
|
|
71
|
+
}
|
|
72
|
+
if (item.kind === 'module') {
|
|
73
|
+
const mod = item as GriffeModule
|
|
74
|
+
const items: TocItem[] = [{ id: mod.name, title: mod.name, level: 1 }]
|
|
75
|
+
if (mod.members) {
|
|
76
|
+
const members = Object.values(mod.members)
|
|
77
|
+
const classes = members.filter(m => m.kind === 'class')
|
|
78
|
+
const functions = members.filter(m => m.kind === 'function')
|
|
79
|
+
|
|
80
|
+
if (classes.length > 0) {
|
|
81
|
+
items.push({ id: 'classes', title: 'Classes', level: 2 })
|
|
82
|
+
}
|
|
83
|
+
if (functions.length > 0) {
|
|
84
|
+
items.push({ id: 'functions', title: 'Functions', level: 2 })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return items
|
|
88
|
+
}
|
|
89
|
+
return []
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Determine what kind of content to render based on the current item
|
|
94
|
+
*/
|
|
95
|
+
function APIContent({
|
|
96
|
+
item,
|
|
97
|
+
prefix,
|
|
98
|
+
currentPath,
|
|
99
|
+
apiData,
|
|
100
|
+
displayPath,
|
|
101
|
+
githubUrl,
|
|
102
|
+
}: {
|
|
103
|
+
item: GriffeMember
|
|
104
|
+
prefix: string
|
|
105
|
+
currentPath: string
|
|
106
|
+
apiData: GriffeModule
|
|
107
|
+
/** Override the display path (used for aliases to show alias name instead of target) */
|
|
108
|
+
displayPath?: string
|
|
109
|
+
/** GitHub repository URL for "Open in GitHub" links */
|
|
110
|
+
githubUrl?: string
|
|
111
|
+
}) {
|
|
112
|
+
// Handle aliases by resolving to target
|
|
113
|
+
if (item.kind === 'alias') {
|
|
114
|
+
const alias = item as GriffeAlias
|
|
115
|
+
const resolved = resolveAlias(alias, apiData)
|
|
116
|
+
if (resolved) {
|
|
117
|
+
// Pass the alias path as displayPath so title shows "strawberry.enum" not "strawberry.types.enum.enum"
|
|
118
|
+
const aliasDisplayPath = alias.path || `${apiData.name}.${alias.name}`
|
|
119
|
+
return <APIContent item={resolved} prefix={prefix} currentPath={currentPath} apiData={apiData} displayPath={aliasDisplayPath} githubUrl={githubUrl} />
|
|
120
|
+
}
|
|
121
|
+
// Could not resolve alias
|
|
122
|
+
return (
|
|
123
|
+
<div className="text-gray-600 dark:text-gray-300">
|
|
124
|
+
<p>Could not resolve alias: {alias.target_path}</p>
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
switch (item.kind) {
|
|
130
|
+
case 'module':
|
|
131
|
+
return <ModuleDoc module={item as GriffeModule} prefix={prefix} showFull displayPath={displayPath} githubUrl={githubUrl} />
|
|
132
|
+
|
|
133
|
+
case 'class':
|
|
134
|
+
return <ClassDoc cls={item as GriffeClass} prefix={prefix} currentPath={currentPath} displayPath={displayPath} githubUrl={githubUrl} />
|
|
135
|
+
|
|
136
|
+
case 'function':
|
|
137
|
+
return <FunctionDoc fn={item as GriffeFunction} displayPath={displayPath} githubUrl={githubUrl} />
|
|
138
|
+
|
|
139
|
+
default:
|
|
140
|
+
return (
|
|
141
|
+
<div className="text-gray-600 dark:text-gray-300">
|
|
142
|
+
<p>Unknown item type: {item.kind}</p>
|
|
143
|
+
<pre className="mt-4 text-xs bg-gray-100 dark:bg-gray-800 p-4 rounded overflow-auto">
|
|
144
|
+
{JSON.stringify(item, null, 2)}
|
|
145
|
+
</pre>
|
|
146
|
+
</div>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Main API documentation page component.
|
|
153
|
+
*
|
|
154
|
+
* Renders API documentation with a separate sidebar navigation.
|
|
155
|
+
* Automatically handles different item types (modules, classes, functions).
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* // In your pages configuration:
|
|
159
|
+
* createDocsApp({
|
|
160
|
+
* pages: {
|
|
161
|
+
* 'api/APIPage': APIPage,
|
|
162
|
+
* // ...
|
|
163
|
+
* }
|
|
164
|
+
* })
|
|
165
|
+
*/
|
|
166
|
+
export function APIPage({
|
|
167
|
+
apiData,
|
|
168
|
+
currentItem,
|
|
169
|
+
currentPath,
|
|
170
|
+
currentModule,
|
|
171
|
+
apiNav,
|
|
172
|
+
prefix,
|
|
173
|
+
logoUrl,
|
|
174
|
+
logoInvertedUrl,
|
|
175
|
+
footerLogoUrl,
|
|
176
|
+
footerLogoInvertedUrl,
|
|
177
|
+
githubUrl,
|
|
178
|
+
navLinks,
|
|
179
|
+
header,
|
|
180
|
+
headerHeight,
|
|
181
|
+
footer,
|
|
182
|
+
}: APIPageProps) {
|
|
183
|
+
// Determine what to render
|
|
184
|
+
const itemToRender = currentItem || apiData
|
|
185
|
+
|
|
186
|
+
// Determine the title
|
|
187
|
+
let title = 'API Reference'
|
|
188
|
+
if (itemToRender) {
|
|
189
|
+
const name = itemToRender.name || currentModule
|
|
190
|
+
const kind = itemToRender.kind
|
|
191
|
+
title = `${name} (${kind}) - API Reference`
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Generate table of contents
|
|
195
|
+
const tocItems = itemToRender ? generateTocItems(itemToRender, apiData) : []
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<APILayout
|
|
199
|
+
title={title}
|
|
200
|
+
apiNav={apiNav}
|
|
201
|
+
currentPath={currentPath}
|
|
202
|
+
logoUrl={logoUrl}
|
|
203
|
+
logoInvertedUrl={logoInvertedUrl}
|
|
204
|
+
footerLogoUrl={footerLogoUrl}
|
|
205
|
+
footerLogoInvertedUrl={footerLogoInvertedUrl}
|
|
206
|
+
githubUrl={githubUrl}
|
|
207
|
+
navLinks={navLinks}
|
|
208
|
+
rightSidebar={tocItems.length > 0 ? <TableOfContents items={tocItems} /> : undefined}
|
|
209
|
+
header={header}
|
|
210
|
+
headerHeight={headerHeight}
|
|
211
|
+
footer={footer}
|
|
212
|
+
>
|
|
213
|
+
<APIContent item={itemToRender} prefix={prefix} currentPath={currentPath} apiData={apiData} githubUrl={githubUrl} />
|
|
214
|
+
</APILayout>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Link } from '@inertiajs/react'
|
|
2
|
+
|
|
3
|
+
interface BreadcrumbItem {
|
|
4
|
+
label: string
|
|
5
|
+
href?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface BreadcrumbProps {
|
|
9
|
+
items: BreadcrumbItem[]
|
|
10
|
+
className?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Breadcrumb navigation for API documentation pages.
|
|
15
|
+
* Shows the path: API > Module > Class
|
|
16
|
+
*/
|
|
17
|
+
export function Breadcrumb({ items, className = '' }: BreadcrumbProps) {
|
|
18
|
+
if (items.length === 0) return null
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<nav className={`flex items-center gap-2 text-sm mb-4 ${className}`}>
|
|
22
|
+
{items.map((item, index) => (
|
|
23
|
+
<span key={index} className="flex items-center gap-2">
|
|
24
|
+
{index > 0 && (
|
|
25
|
+
<ChevronIcon className="text-gray-400 dark:text-gray-500" />
|
|
26
|
+
)}
|
|
27
|
+
{item.href ? (
|
|
28
|
+
<Link
|
|
29
|
+
href={item.href}
|
|
30
|
+
className="text-gray-500 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
|
|
31
|
+
>
|
|
32
|
+
{item.label}
|
|
33
|
+
</Link>
|
|
34
|
+
) : (
|
|
35
|
+
<span className="text-gray-900 dark:text-white font-medium">
|
|
36
|
+
{item.label}
|
|
37
|
+
</span>
|
|
38
|
+
)}
|
|
39
|
+
</span>
|
|
40
|
+
))}
|
|
41
|
+
</nav>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ChevronIcon({ className }: { className?: string }) {
|
|
46
|
+
return (
|
|
47
|
+
<svg
|
|
48
|
+
className={`w-4 h-4 ${className}`}
|
|
49
|
+
fill="none"
|
|
50
|
+
viewBox="0 0 24 24"
|
|
51
|
+
stroke="currentColor"
|
|
52
|
+
>
|
|
53
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
54
|
+
</svg>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate breadcrumb items from a path like "/api/strawberry/schema/schema/Schema"
|
|
60
|
+
* Combines module path into dotted notation: API > strawberry.schema.schema > Schema
|
|
61
|
+
*/
|
|
62
|
+
export function generateBreadcrumb(currentPath: string, prefix: string = '/api'): BreadcrumbItem[] {
|
|
63
|
+
const items: BreadcrumbItem[] = [{ label: 'API', href: prefix }]
|
|
64
|
+
|
|
65
|
+
// Remove prefix from path
|
|
66
|
+
const relativePath = currentPath.startsWith(prefix)
|
|
67
|
+
? currentPath.slice(prefix.length)
|
|
68
|
+
: currentPath
|
|
69
|
+
|
|
70
|
+
// Split path into parts
|
|
71
|
+
const parts = relativePath.split('/').filter(Boolean)
|
|
72
|
+
|
|
73
|
+
if (parts.length === 0) return items
|
|
74
|
+
|
|
75
|
+
// If we have parts, combine all but the last into a module path
|
|
76
|
+
if (parts.length === 1) {
|
|
77
|
+
// Just one part - show it as the current item
|
|
78
|
+
items.push({ label: parts[0] })
|
|
79
|
+
} else {
|
|
80
|
+
// Multiple parts: combine all but last into module path
|
|
81
|
+
const moduleParts = parts.slice(0, -1)
|
|
82
|
+
const finalItem = parts[parts.length - 1]
|
|
83
|
+
|
|
84
|
+
// Build href for module path (link to the parent)
|
|
85
|
+
const moduleHref = prefix + '/' + moduleParts.join('/')
|
|
86
|
+
|
|
87
|
+
// Add module path as dotted notation
|
|
88
|
+
items.push({
|
|
89
|
+
label: moduleParts.join('.'),
|
|
90
|
+
href: moduleHref,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Add final item (class/function name)
|
|
94
|
+
items.push({ label: finalItem })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return items
|
|
98
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import type { GriffeClass, GriffeFunction, GriffeAttribute, GriffeExpression } from '../../types'
|
|
3
|
+
import { Docstring } from './Docstring'
|
|
4
|
+
import { FunctionDoc } from './FunctionDoc'
|
|
5
|
+
import { CodeSpan } from './CodeSpan'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Render a type annotation expression or value to string
|
|
9
|
+
*/
|
|
10
|
+
function renderExpression(expr: GriffeExpression | string | undefined): string {
|
|
11
|
+
if (!expr) return ''
|
|
12
|
+
if (typeof expr === 'string') return expr
|
|
13
|
+
if (expr.str) return expr.str
|
|
14
|
+
if (expr.canonical) return expr.canonical
|
|
15
|
+
|
|
16
|
+
const exprAny = expr as any
|
|
17
|
+
|
|
18
|
+
// Handle ExprName with member reference
|
|
19
|
+
if (expr.name && typeof expr.name === 'string') return expr.name
|
|
20
|
+
|
|
21
|
+
// Handle ExprBoolOp (like `config or StrawberryConfig()`)
|
|
22
|
+
if (exprAny.cls === 'ExprBoolOp' && exprAny.operator && Array.isArray(exprAny.values)) {
|
|
23
|
+
return exprAny.values.map((v: any) => renderExpression(v)).join(` ${exprAny.operator} `)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Handle ExprBinOp (like `type | None`)
|
|
27
|
+
if (exprAny.cls === 'ExprBinOp' && exprAny.left && exprAny.right) {
|
|
28
|
+
const left = renderExpression(exprAny.left)
|
|
29
|
+
const right = renderExpression(exprAny.right)
|
|
30
|
+
const op = exprAny.operator || '|'
|
|
31
|
+
return `${left} ${op} ${right}`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Handle ExprCall (like `StrawberryConfig()`)
|
|
35
|
+
if (exprAny.cls === 'ExprCall' && exprAny.function) {
|
|
36
|
+
const funcName = renderExpression(exprAny.function)
|
|
37
|
+
const args = Array.isArray(exprAny.arguments)
|
|
38
|
+
? exprAny.arguments.map((a: any) => renderExpression(a)).join(', ')
|
|
39
|
+
: ''
|
|
40
|
+
return `${funcName}(${args})`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Handle ExprAttribute (like contextlib.asynccontextmanager)
|
|
44
|
+
if (exprAny.cls === 'ExprAttribute' && Array.isArray(exprAny.values)) {
|
|
45
|
+
return exprAny.values.map((v: any) => renderExpression(v)).join('.')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Handle ExprList and ExprTuple
|
|
49
|
+
if ('elements' in exprAny && Array.isArray(exprAny.elements)) {
|
|
50
|
+
const inner = exprAny.elements.map((el: any) => renderExpression(el)).join(', ')
|
|
51
|
+
return exprAny.cls === 'ExprTuple' ? `(${inner})` : `[${inner}]`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Handle ExprDict
|
|
55
|
+
if (exprAny.cls === 'ExprDict' && Array.isArray(exprAny.keys) && Array.isArray(exprAny.values)) {
|
|
56
|
+
const pairs = exprAny.keys.map((k: any, i: number) =>
|
|
57
|
+
`${renderExpression(k)}: ${renderExpression(exprAny.values[i])}`
|
|
58
|
+
).join(', ')
|
|
59
|
+
return `{${pairs}}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle ExprSubscript (like Dict[str, int])
|
|
63
|
+
if (exprAny.left && exprAny.slice) {
|
|
64
|
+
const left = renderExpression(exprAny.left)
|
|
65
|
+
const slice = renderExpression(exprAny.slice)
|
|
66
|
+
return `${left}[${slice}]`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Handle slice expressions
|
|
70
|
+
if ('slice' in exprAny && exprAny.slice && !exprAny.left) {
|
|
71
|
+
return renderExpression(exprAny.slice)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Fallback for unknown expressions - try to avoid [object Object]
|
|
75
|
+
if (typeof expr === 'object') {
|
|
76
|
+
return JSON.stringify(expr)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return String(expr)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Collapsible method item with arrow indicator (strawberry.rocks style)
|
|
84
|
+
*/
|
|
85
|
+
function CollapsibleMethod({ method }: { method: GriffeFunction }) {
|
|
86
|
+
const [expanded, setExpanded] = useState(false)
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="border-b border-gray-200 dark:border-gray-700 last:border-b-0">
|
|
90
|
+
<button
|
|
91
|
+
onClick={() => setExpanded(!expanded)}
|
|
92
|
+
className="w-full flex items-center gap-2 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors"
|
|
93
|
+
>
|
|
94
|
+
<span className="font-mono text-base font-semibold text-gray-900 dark:text-white">
|
|
95
|
+
{method.name}
|
|
96
|
+
</span>
|
|
97
|
+
<span className="text-gray-400 text-sm">
|
|
98
|
+
{expanded ? '▲' : '▼'}
|
|
99
|
+
</span>
|
|
100
|
+
</button>
|
|
101
|
+
{expanded && (
|
|
102
|
+
<div className="pb-6">
|
|
103
|
+
<FunctionDoc fn={method} isMethod showName={false} />
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface ClassDocProps {
|
|
111
|
+
cls: GriffeClass
|
|
112
|
+
/** URL prefix for links */
|
|
113
|
+
prefix?: string
|
|
114
|
+
/** Current path for breadcrumb */
|
|
115
|
+
currentPath?: string
|
|
116
|
+
/** GitHub repo URL for source links */
|
|
117
|
+
githubUrl?: string
|
|
118
|
+
/** Additional CSS class */
|
|
119
|
+
className?: string
|
|
120
|
+
/** Override display path (e.g., for aliases to show alias name instead of target path) */
|
|
121
|
+
displayPath?: string
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Renders documentation for a class matching strawberry.rocks design.
|
|
126
|
+
* Includes: Title, Constructor, Methods (collapsible), Attributes, and footer.
|
|
127
|
+
*/
|
|
128
|
+
export function ClassDoc({ cls, prefix: _prefix = '/api', currentPath: _currentPath, githubUrl, className = '', displayPath }: ClassDocProps) {
|
|
129
|
+
const members = cls.members ?? {}
|
|
130
|
+
|
|
131
|
+
// Separate members by type
|
|
132
|
+
const methods: GriffeFunction[] = []
|
|
133
|
+
const attributes: GriffeAttribute[] = []
|
|
134
|
+
|
|
135
|
+
for (const member of Object.values(members)) {
|
|
136
|
+
// Skip private members
|
|
137
|
+
if (member.name.startsWith('_') && !member.name.startsWith('__')) continue
|
|
138
|
+
|
|
139
|
+
if (member.kind === 'function') {
|
|
140
|
+
methods.push(member as GriffeFunction)
|
|
141
|
+
} else if (member.kind === 'attribute') {
|
|
142
|
+
attributes.push(member as GriffeAttribute)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Sort methods: __init__ first, then public methods alphabetically, skip other dunders
|
|
147
|
+
const initMethod = methods.find(m => m.name === '__init__')
|
|
148
|
+
const publicMethods = methods
|
|
149
|
+
.filter(m => m.name !== '__init__' && !m.name.startsWith('_'))
|
|
150
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
151
|
+
|
|
152
|
+
// Sort attributes alphabetically, skip private ones
|
|
153
|
+
const publicAttributes = attributes
|
|
154
|
+
.filter(a => !a.name.startsWith('_'))
|
|
155
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
156
|
+
|
|
157
|
+
// Get relative filepath for display (prefer package-relative path)
|
|
158
|
+
const relativeFilepath = cls.relative_package_filepath || cls.relative_filepath || cls.filepath
|
|
159
|
+
|
|
160
|
+
// Build GitHub URL for source link
|
|
161
|
+
const githubSourceUrl = githubUrl && relativeFilepath && cls.lineno
|
|
162
|
+
? `${githubUrl}/blob/main/${relativeFilepath}#L${cls.lineno}-L${cls.endlineno || cls.lineno}`
|
|
163
|
+
: undefined
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div className={className}>
|
|
167
|
+
{/* Title - monospace like strawberry.rocks */}
|
|
168
|
+
<h1 id={cls.name} className="font-mono text-2xl font-normal text-gray-900 dark:text-white mb-8">
|
|
169
|
+
{displayPath || cls.path || cls.name}
|
|
170
|
+
</h1>
|
|
171
|
+
|
|
172
|
+
{/* Constructor section */}
|
|
173
|
+
{initMethod && (
|
|
174
|
+
<section id="constructor" className="mb-8">
|
|
175
|
+
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
|
176
|
+
Constructor:
|
|
177
|
+
</h2>
|
|
178
|
+
|
|
179
|
+
{/* Docstring description */}
|
|
180
|
+
{initMethod.docstring && (
|
|
181
|
+
<div className="mb-6">
|
|
182
|
+
<Docstring docstring={initMethod.docstring} showOnlyText />
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
<FunctionDoc fn={initMethod} isMethod showName={false} />
|
|
187
|
+
</section>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
{/* Class docstring if no __init__ */}
|
|
191
|
+
{!initMethod && cls.docstring && (
|
|
192
|
+
<section className="mb-8">
|
|
193
|
+
<Docstring docstring={cls.docstring} />
|
|
194
|
+
</section>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{/* Methods section - collapsible */}
|
|
198
|
+
{publicMethods.length > 0 && (
|
|
199
|
+
<section id="methods" className="mb-8">
|
|
200
|
+
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
|
201
|
+
Methods:
|
|
202
|
+
</h2>
|
|
203
|
+
<div>
|
|
204
|
+
{publicMethods.map((method) => (
|
|
205
|
+
<CollapsibleMethod key={method.name} method={method} />
|
|
206
|
+
))}
|
|
207
|
+
</div>
|
|
208
|
+
</section>
|
|
209
|
+
)}
|
|
210
|
+
|
|
211
|
+
{/* Attributes section */}
|
|
212
|
+
{publicAttributes.length > 0 && (
|
|
213
|
+
<section id="attributes" className="mb-8">
|
|
214
|
+
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
|
215
|
+
Attributes:
|
|
216
|
+
</h2>
|
|
217
|
+
<div className="space-y-2">
|
|
218
|
+
{publicAttributes.map((attr) => (
|
|
219
|
+
<div key={attr.name} className="flex items-baseline gap-2">
|
|
220
|
+
<CodeSpan>{attr.name}:</CodeSpan>
|
|
221
|
+
{attr.annotation && (
|
|
222
|
+
<span className="text-sm text-gray-600 dark:text-gray-400 font-mono">
|
|
223
|
+
{renderExpression(attr.annotation)}
|
|
224
|
+
</span>
|
|
225
|
+
)}
|
|
226
|
+
</div>
|
|
227
|
+
))}
|
|
228
|
+
</div>
|
|
229
|
+
</section>
|
|
230
|
+
)}
|
|
231
|
+
|
|
232
|
+
{/* Footer with file path and GitHub link */}
|
|
233
|
+
{relativeFilepath && (
|
|
234
|
+
<footer className="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 space-y-4">
|
|
235
|
+
<p className="flex items-center gap-2">
|
|
236
|
+
<span className="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide">
|
|
237
|
+
File path:
|
|
238
|
+
</span>
|
|
239
|
+
<CodeSpan allowCopy>{relativeFilepath}</CodeSpan>
|
|
240
|
+
</p>
|
|
241
|
+
{githubSourceUrl && (
|
|
242
|
+
<p>
|
|
243
|
+
<a
|
|
244
|
+
href={githubSourceUrl}
|
|
245
|
+
target="_blank"
|
|
246
|
+
rel="noopener noreferrer"
|
|
247
|
+
className="text-sm font-semibold text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 uppercase tracking-wide"
|
|
248
|
+
>
|
|
249
|
+
Open in GitHub
|
|
250
|
+
</a>
|
|
251
|
+
</p>
|
|
252
|
+
)}
|
|
253
|
+
</footer>
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
)
|
|
257
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { cn } from '../../lib/utils'
|
|
2
|
+
|
|
3
|
+
interface CodeSpanProps {
|
|
4
|
+
children: React.ReactNode
|
|
5
|
+
/** Visual variant */
|
|
6
|
+
variant?: 'default' | 'simple'
|
|
7
|
+
/** Allow copy on click */
|
|
8
|
+
allowCopy?: boolean
|
|
9
|
+
/** Additional CSS class */
|
|
10
|
+
className?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Styled code span component matching strawberry.rocks design.
|
|
15
|
+
* Uses coral/pink color for code badges.
|
|
16
|
+
*/
|
|
17
|
+
export function CodeSpan({ children, variant = 'default', allowCopy = false, className }: CodeSpanProps) {
|
|
18
|
+
const handleCopy = () => {
|
|
19
|
+
if (allowCopy && typeof children === 'string') {
|
|
20
|
+
navigator.clipboard.writeText(children)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (variant === 'simple') {
|
|
25
|
+
return (
|
|
26
|
+
<code
|
|
27
|
+
onClick={allowCopy ? handleCopy : undefined}
|
|
28
|
+
className={cn(
|
|
29
|
+
'font-mono text-[0.9em] font-semibold text-gray-900 dark:text-white',
|
|
30
|
+
allowCopy && 'cursor-pointer hover:text-primary-600 dark:hover:text-primary-400',
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
{children}
|
|
35
|
+
</code>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<code
|
|
41
|
+
onClick={allowCopy ? handleCopy : undefined}
|
|
42
|
+
className={cn(
|
|
43
|
+
'inline-flex items-center px-2 py-0.5 rounded font-mono text-sm',
|
|
44
|
+
'bg-red-50 text-red-600 border border-red-200',
|
|
45
|
+
'dark:bg-red-900/20 dark:text-red-400 dark:border-red-800/50',
|
|
46
|
+
allowCopy && 'cursor-pointer hover:bg-red-100 dark:hover:bg-red-900/30',
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</code>
|
|
52
|
+
)
|
|
53
|
+
}
|