@usecross/docs 0.1.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 +178 -0
- package/dist/index.js +616 -0
- package/dist/index.js.map +1 -0
- package/dist/ssr.d.ts +21 -0
- package/dist/ssr.js +27 -0
- package/dist/ssr.js.map +1 -0
- package/dist/types-Vodb50Bj.d.ts +91 -0
- package/package.json +71 -0
- package/src/app.tsx +46 -0
- package/src/components/CodeBlock.tsx +87 -0
- package/src/components/DocsLayout.tsx +178 -0
- package/src/components/DocsPage.tsx +19 -0
- package/src/components/HomePage.tsx +434 -0
- package/src/components/Markdown.tsx +93 -0
- package/src/components/Sidebar.tsx +37 -0
- package/src/components/index.ts +6 -0
- package/src/index.ts +49 -0
- package/src/lib/shiki.ts +59 -0
- package/src/lib/utils.ts +9 -0
- package/src/ssr.tsx +39 -0
- package/src/styles.css +128 -0
- package/src/tailwind.preset.cjs +118 -0
- package/src/types.ts +91 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DocsLayout } from './DocsLayout'
|
|
2
|
+
import { Markdown } from './Markdown'
|
|
3
|
+
import type { DocContent, DocsLayoutProps } from '../types'
|
|
4
|
+
|
|
5
|
+
interface DocsPageProps extends Omit<DocsLayoutProps, 'children' | 'title'> {
|
|
6
|
+
content: DocContent
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default documentation page component.
|
|
11
|
+
* Renders markdown content within the DocsLayout.
|
|
12
|
+
*/
|
|
13
|
+
export function DocsPage({ content, ...layoutProps }: DocsPageProps) {
|
|
14
|
+
return (
|
|
15
|
+
<DocsLayout title={content?.title ?? ''} description={content?.description} {...layoutProps}>
|
|
16
|
+
<Markdown content={content?.body ?? ''} />
|
|
17
|
+
</DocsLayout>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { Head, Link } from '@inertiajs/react'
|
|
2
|
+
import { createContext, useContext, useState, type ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Types
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
export interface HomeFeature {
|
|
9
|
+
title: string
|
|
10
|
+
description: ReactNode
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HomePageContextValue {
|
|
14
|
+
title: string
|
|
15
|
+
tagline: string
|
|
16
|
+
description: string
|
|
17
|
+
installCommand: string
|
|
18
|
+
ctaText: string
|
|
19
|
+
ctaHref: string
|
|
20
|
+
features: HomeFeature[]
|
|
21
|
+
logoUrl?: string
|
|
22
|
+
heroLogoUrl?: string
|
|
23
|
+
footerLogoUrl?: string
|
|
24
|
+
githubUrl?: string
|
|
25
|
+
navLinks: Array<{ label: string; href: string }>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface HomePageProps extends Omit<HomePageContextValue, 'navLinks'> {
|
|
29
|
+
navLinks?: Array<{ label: string; href: string }>
|
|
30
|
+
children?: ReactNode
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface HomeHeaderProps {
|
|
34
|
+
renderLogo?: () => ReactNode
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface HomeFeaturesProps {
|
|
38
|
+
renderFeature?: (
|
|
39
|
+
feature: HomeFeature,
|
|
40
|
+
index: number,
|
|
41
|
+
DefaultFeature: typeof HomeFeatureItem
|
|
42
|
+
) => ReactNode
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface HomeFeatureItemProps {
|
|
46
|
+
feature: HomeFeature
|
|
47
|
+
index: number
|
|
48
|
+
totalFeatures: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Context
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
const HomePageContext = createContext<HomePageContextValue | null>(null)
|
|
56
|
+
|
|
57
|
+
function useHomePage(): HomePageContextValue {
|
|
58
|
+
const context = useContext(HomePageContext)
|
|
59
|
+
if (!context) {
|
|
60
|
+
throw new Error('HomePage sub-components must be used within <HomePage>')
|
|
61
|
+
}
|
|
62
|
+
return context
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Utility Components
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
function InstallCommand({ command }: { command: string }) {
|
|
70
|
+
const [copied, setCopied] = useState(false)
|
|
71
|
+
|
|
72
|
+
const copyToClipboard = async () => {
|
|
73
|
+
await navigator.clipboard.writeText(command)
|
|
74
|
+
setCopied(true)
|
|
75
|
+
setTimeout(() => setCopied(false), 2000)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<button
|
|
80
|
+
onClick={copyToClipboard}
|
|
81
|
+
className="group relative flex items-center bg-black border border-black px-4 h-14 font-mono text-sm text-white hover:bg-white hover:text-black transition-colors cursor-pointer"
|
|
82
|
+
>
|
|
83
|
+
<span className="text-primary-500 mr-2">$</span>
|
|
84
|
+
<span>{command}</span>
|
|
85
|
+
<svg
|
|
86
|
+
className={`ml-4 w-4 h-4 transition ${copied ? 'text-green-400' : 'opacity-50 group-hover:opacity-100'}`}
|
|
87
|
+
fill="none"
|
|
88
|
+
stroke="currentColor"
|
|
89
|
+
viewBox="0 0 24 24"
|
|
90
|
+
>
|
|
91
|
+
<path
|
|
92
|
+
strokeLinecap="round"
|
|
93
|
+
strokeLinejoin="round"
|
|
94
|
+
strokeWidth={2}
|
|
95
|
+
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"
|
|
96
|
+
/>
|
|
97
|
+
</svg>
|
|
98
|
+
<span
|
|
99
|
+
className={`absolute -top-8 left-1/2 -translate-x-1/2 bg-black text-white text-xs py-1 px-2 rounded transition-opacity duration-300 whitespace-nowrap ${
|
|
100
|
+
copied ? 'opacity-100' : 'opacity-0'
|
|
101
|
+
}`}
|
|
102
|
+
>
|
|
103
|
+
Copied!
|
|
104
|
+
</span>
|
|
105
|
+
</button>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function GitHubIcon() {
|
|
110
|
+
return (
|
|
111
|
+
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
112
|
+
<path
|
|
113
|
+
fillRule="evenodd"
|
|
114
|
+
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
|
115
|
+
clipRule="evenodd"
|
|
116
|
+
/>
|
|
117
|
+
</svg>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// Sub-Components
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Default logo component that renders an image or text.
|
|
127
|
+
*/
|
|
128
|
+
function DefaultLogo() {
|
|
129
|
+
const { title, logoUrl } = useHomePage()
|
|
130
|
+
|
|
131
|
+
if (logoUrl) {
|
|
132
|
+
return (
|
|
133
|
+
<Link href="/" className="flex items-center">
|
|
134
|
+
<img src={logoUrl} alt={title} className="h-8" />
|
|
135
|
+
</Link>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<Link href="/" className="font-bold text-lg">
|
|
141
|
+
{title}
|
|
142
|
+
</Link>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Navigation header for the homepage.
|
|
148
|
+
* Accepts an optional renderLogo prop for custom logo rendering.
|
|
149
|
+
*/
|
|
150
|
+
export function HomeHeader({ renderLogo }: HomeHeaderProps = {}) {
|
|
151
|
+
const { navLinks, githubUrl } = useHomePage()
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<nav className="fixed w-full z-50 bg-white border-b border-gray-200">
|
|
155
|
+
<div className="px-4 lg:px-10">
|
|
156
|
+
<div className="flex justify-between h-16 items-center">
|
|
157
|
+
{renderLogo ? renderLogo() : <DefaultLogo />}
|
|
158
|
+
<div className="flex items-center space-x-8">
|
|
159
|
+
{navLinks.map((link) => (
|
|
160
|
+
<Link
|
|
161
|
+
key={link.href}
|
|
162
|
+
href={link.href}
|
|
163
|
+
className="text-black font-medium hover:text-primary-500 transition-colors"
|
|
164
|
+
>
|
|
165
|
+
{link.label}
|
|
166
|
+
</Link>
|
|
167
|
+
))}
|
|
168
|
+
{githubUrl && (
|
|
169
|
+
<a
|
|
170
|
+
href={githubUrl}
|
|
171
|
+
target="_blank"
|
|
172
|
+
rel="noopener noreferrer"
|
|
173
|
+
className="text-black hover:text-primary-500 transition-colors"
|
|
174
|
+
>
|
|
175
|
+
<GitHubIcon />
|
|
176
|
+
</a>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</nav>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Hero section with title, tagline, description, and CTA.
|
|
187
|
+
* If heroLogoUrl is provided, displays an image instead of text title.
|
|
188
|
+
*/
|
|
189
|
+
export function HomeHero() {
|
|
190
|
+
const { title, tagline, description, ctaText, ctaHref, installCommand, heroLogoUrl } = useHomePage()
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<section className="pt-16">
|
|
194
|
+
<div className="px-4 lg:px-10 py-16 lg:py-24">
|
|
195
|
+
<div className="max-w-4xl">
|
|
196
|
+
<div className="mb-4 text-sm font-mono uppercase tracking-widest text-gray-500">
|
|
197
|
+
{tagline}
|
|
198
|
+
</div>
|
|
199
|
+
{heroLogoUrl ? (
|
|
200
|
+
<h1 className="mb-6 lg:mb-8">
|
|
201
|
+
<img
|
|
202
|
+
src={heroLogoUrl}
|
|
203
|
+
alt={title}
|
|
204
|
+
className="h-auto w-auto max-w-[580px]"
|
|
205
|
+
/>
|
|
206
|
+
</h1>
|
|
207
|
+
) : (
|
|
208
|
+
<h1 className="text-5xl lg:text-7xl font-bold tracking-tight mb-6">
|
|
209
|
+
{title}
|
|
210
|
+
</h1>
|
|
211
|
+
)}
|
|
212
|
+
<p className="text-xl lg:text-2xl text-gray-700 max-w-2xl leading-relaxed mb-8">
|
|
213
|
+
{description}
|
|
214
|
+
</p>
|
|
215
|
+
|
|
216
|
+
<div className="flex flex-col sm:flex-row gap-3">
|
|
217
|
+
<Link
|
|
218
|
+
href={ctaHref}
|
|
219
|
+
className="inline-flex items-center justify-center px-8 h-14 bg-black text-white font-bold text-lg hover:bg-primary-500 transition-colors border border-black"
|
|
220
|
+
>
|
|
221
|
+
{ctaText}
|
|
222
|
+
</Link>
|
|
223
|
+
{installCommand && <InstallCommand command={installCommand} />}
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</section>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Single feature item within the features grid.
|
|
233
|
+
*/
|
|
234
|
+
export function HomeFeatureItem({ feature, index, totalFeatures }: HomeFeatureItemProps) {
|
|
235
|
+
return (
|
|
236
|
+
<div
|
|
237
|
+
className={`p-4 lg:p-10 border-b sm:border-b border-gray-200 ${
|
|
238
|
+
index % 2 === 0 ? 'sm:border-r' : ''
|
|
239
|
+
} ${index >= totalFeatures - 2 ? 'sm:border-b-0' : ''} ${
|
|
240
|
+
index === totalFeatures - 1 && totalFeatures % 2 === 1 ? 'border-b-0' : ''
|
|
241
|
+
}`}
|
|
242
|
+
>
|
|
243
|
+
<div className="text-5xl font-bold text-primary-500 mb-4">
|
|
244
|
+
{String(index + 1).padStart(2, '0')}
|
|
245
|
+
</div>
|
|
246
|
+
<h3 className="text-xl font-bold mb-2">{feature.title}</h3>
|
|
247
|
+
<p className="text-gray-600">{feature.description}</p>
|
|
248
|
+
</div>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Features section with customizable feature rendering.
|
|
254
|
+
*/
|
|
255
|
+
export function HomeFeatures({ renderFeature }: HomeFeaturesProps = {}) {
|
|
256
|
+
const { title, features } = useHomePage()
|
|
257
|
+
|
|
258
|
+
if (features.length === 0) {
|
|
259
|
+
return null
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<section className="border-t border-gray-200">
|
|
264
|
+
<div className="grid grid-cols-12">
|
|
265
|
+
<div className="col-span-12 lg:col-span-4 p-4 lg:p-10 border-b lg:border-b-0 lg:border-r border-gray-200">
|
|
266
|
+
<div className="text-sm font-mono uppercase tracking-widest text-gray-500 mb-4">
|
|
267
|
+
Features
|
|
268
|
+
</div>
|
|
269
|
+
<h2 className="text-4xl lg:text-5xl font-bold tracking-tight">
|
|
270
|
+
Why {title}?
|
|
271
|
+
</h2>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
<div className="col-span-12 lg:col-span-8 grid grid-cols-1 sm:grid-cols-2">
|
|
275
|
+
{features.map((feature, index) =>
|
|
276
|
+
renderFeature ? (
|
|
277
|
+
<div key={index}>
|
|
278
|
+
{renderFeature(feature, index, HomeFeatureItem)}
|
|
279
|
+
</div>
|
|
280
|
+
) : (
|
|
281
|
+
<HomeFeatureItem
|
|
282
|
+
key={index}
|
|
283
|
+
feature={feature}
|
|
284
|
+
index={index}
|
|
285
|
+
totalFeatures={features.length}
|
|
286
|
+
/>
|
|
287
|
+
)
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
</section>
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Call-to-action section.
|
|
297
|
+
*/
|
|
298
|
+
export function HomeCTA() {
|
|
299
|
+
const { ctaHref } = useHomePage()
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<section className="border-t border-gray-200">
|
|
303
|
+
<div className="grid grid-cols-12 items-center">
|
|
304
|
+
<div className="col-span-12 lg:col-span-8 p-4 lg:p-10">
|
|
305
|
+
<h2 className="text-4xl lg:text-6xl font-bold tracking-tight mb-4">
|
|
306
|
+
Ready to start?
|
|
307
|
+
</h2>
|
|
308
|
+
<p className="text-xl text-gray-600 mb-8 max-w-2xl">
|
|
309
|
+
Get up and running in minutes. Check out our documentation to learn more.
|
|
310
|
+
</p>
|
|
311
|
+
<Link
|
|
312
|
+
href={ctaHref}
|
|
313
|
+
className="inline-flex items-center justify-center px-8 py-4 bg-primary-500 text-white font-bold text-lg hover:bg-black transition-colors border border-primary-500 hover:border-black"
|
|
314
|
+
>
|
|
315
|
+
Read the Docs
|
|
316
|
+
</Link>
|
|
317
|
+
</div>
|
|
318
|
+
<Link
|
|
319
|
+
href={ctaHref}
|
|
320
|
+
className="col-span-12 lg:col-span-4 h-full bg-primary-500 hidden lg:flex items-center justify-center p-4 lg:p-10 hover:bg-black transition-colors min-h-[200px]"
|
|
321
|
+
>
|
|
322
|
+
<div className="text-white text-8xl font-bold">→</div>
|
|
323
|
+
</Link>
|
|
324
|
+
</div>
|
|
325
|
+
</section>
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Footer section.
|
|
331
|
+
*/
|
|
332
|
+
export function HomeFooter() {
|
|
333
|
+
const { title, logoUrl, footerLogoUrl, navLinks, githubUrl } = useHomePage()
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<footer className="border-t border-gray-200 py-8">
|
|
337
|
+
<div className="px-4 lg:px-10 flex flex-col md:flex-row justify-between items-center gap-6">
|
|
338
|
+
{(footerLogoUrl || logoUrl) && (
|
|
339
|
+
<Link href="/">
|
|
340
|
+
<img src={footerLogoUrl || logoUrl} alt={title} className="h-6" />
|
|
341
|
+
</Link>
|
|
342
|
+
)}
|
|
343
|
+
<div className="flex gap-8 text-sm text-gray-600">
|
|
344
|
+
{navLinks.map((link) => (
|
|
345
|
+
<Link key={link.href} href={link.href} className="hover:text-black transition-colors">
|
|
346
|
+
{link.label}
|
|
347
|
+
</Link>
|
|
348
|
+
))}
|
|
349
|
+
{githubUrl && (
|
|
350
|
+
<a
|
|
351
|
+
href={githubUrl}
|
|
352
|
+
target="_blank"
|
|
353
|
+
rel="noopener noreferrer"
|
|
354
|
+
className="hover:text-black transition-colors"
|
|
355
|
+
>
|
|
356
|
+
GitHub
|
|
357
|
+
</a>
|
|
358
|
+
)}
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</footer>
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Default layout when no children are provided.
|
|
367
|
+
*/
|
|
368
|
+
function DefaultHomeLayout() {
|
|
369
|
+
return (
|
|
370
|
+
<>
|
|
371
|
+
<HomeHeader />
|
|
372
|
+
<HomeHero />
|
|
373
|
+
<HomeFeatures />
|
|
374
|
+
<HomeCTA />
|
|
375
|
+
<HomeFooter />
|
|
376
|
+
</>
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// Main Component
|
|
382
|
+
// ============================================================================
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Homepage component for documentation sites.
|
|
386
|
+
*
|
|
387
|
+
* Can be used in two ways:
|
|
388
|
+
*
|
|
389
|
+
* 1. Simple - everything from config:
|
|
390
|
+
* ```tsx
|
|
391
|
+
* <HomePage {...props} />
|
|
392
|
+
* ```
|
|
393
|
+
*
|
|
394
|
+
* 2. Composable - full control via children:
|
|
395
|
+
* ```tsx
|
|
396
|
+
* <HomePage {...props}>
|
|
397
|
+
* <HomePage.Header />
|
|
398
|
+
* <HomePage.Hero />
|
|
399
|
+
* <MyCustomSection />
|
|
400
|
+
* <HomePage.Features renderFeature={(feature, i, Default) => (
|
|
401
|
+
* <Default feature={feature} index={i} totalFeatures={4} />
|
|
402
|
+
* )} />
|
|
403
|
+
* <HomePage.CTA />
|
|
404
|
+
* <HomePage.Footer />
|
|
405
|
+
* </HomePage>
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
export function HomePage({
|
|
409
|
+
children,
|
|
410
|
+
navLinks = [],
|
|
411
|
+
...props
|
|
412
|
+
}: HomePageProps) {
|
|
413
|
+
const contextValue: HomePageContextValue = {
|
|
414
|
+
...props,
|
|
415
|
+
navLinks,
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return (
|
|
419
|
+
<HomePageContext.Provider value={contextValue}>
|
|
420
|
+
<div className="min-h-screen bg-white">
|
|
421
|
+
<Head title={props.title} />
|
|
422
|
+
{children || <DefaultHomeLayout />}
|
|
423
|
+
</div>
|
|
424
|
+
</HomePageContext.Provider>
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Attach sub-components for compound component pattern
|
|
429
|
+
HomePage.Header = HomeHeader
|
|
430
|
+
HomePage.Hero = HomeHero
|
|
431
|
+
HomePage.Features = HomeFeatures
|
|
432
|
+
HomePage.Feature = HomeFeatureItem
|
|
433
|
+
HomePage.CTA = HomeCTA
|
|
434
|
+
HomePage.Footer = HomeFooter
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import ReactMarkdown from 'react-markdown'
|
|
2
|
+
import remarkGfm from 'remark-gfm'
|
|
3
|
+
import rehypeRaw from 'rehype-raw'
|
|
4
|
+
import { CodeBlock } from './CodeBlock'
|
|
5
|
+
import type { MarkdownProps } from '../types'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Markdown renderer with syntax highlighting and GFM support.
|
|
9
|
+
*/
|
|
10
|
+
export function Markdown({ content, components }: MarkdownProps) {
|
|
11
|
+
return (
|
|
12
|
+
<ReactMarkdown
|
|
13
|
+
remarkPlugins={[remarkGfm]}
|
|
14
|
+
rehypePlugins={[rehypeRaw]}
|
|
15
|
+
components={{
|
|
16
|
+
// Override pre to avoid double wrapping with CodeBlock
|
|
17
|
+
pre({ children }) {
|
|
18
|
+
return <>{children}</>
|
|
19
|
+
},
|
|
20
|
+
// Custom code block rendering with syntax highlighting
|
|
21
|
+
code({ node, className, children, ...props }) {
|
|
22
|
+
const match = /language-(\w+)/.exec(className || '')
|
|
23
|
+
const isInline = !match && !className
|
|
24
|
+
|
|
25
|
+
if (isInline) {
|
|
26
|
+
return (
|
|
27
|
+
<code
|
|
28
|
+
className="rounded bg-gray-100 px-1.5 py-0.5 text-sm font-medium text-gray-800 dark:bg-gray-800 dark:text-gray-200"
|
|
29
|
+
{...props}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</code>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Parse meta string from the code fence (e.g., ```python title="app.py" showLineNumbers)
|
|
37
|
+
const meta = (node?.data?.meta as string) || ''
|
|
38
|
+
const titleMatch = /title="([^"]+)"/.exec(meta)
|
|
39
|
+
const filename = titleMatch ? titleMatch[1] : undefined
|
|
40
|
+
const showLineNumbers = meta.includes('showLineNumbers')
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<CodeBlock
|
|
44
|
+
code={String(children).replace(/\n$/, '')}
|
|
45
|
+
language={match ? match[1] : 'text'}
|
|
46
|
+
filename={filename}
|
|
47
|
+
showLineNumbers={showLineNumbers}
|
|
48
|
+
/>
|
|
49
|
+
)
|
|
50
|
+
},
|
|
51
|
+
// Custom link styling
|
|
52
|
+
a({ href, children }) {
|
|
53
|
+
const isExternal = href?.startsWith('http')
|
|
54
|
+
return (
|
|
55
|
+
<a
|
|
56
|
+
href={href}
|
|
57
|
+
className="text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300"
|
|
58
|
+
{...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
|
|
59
|
+
>
|
|
60
|
+
{children}
|
|
61
|
+
</a>
|
|
62
|
+
)
|
|
63
|
+
},
|
|
64
|
+
// Tables
|
|
65
|
+
table({ children }) {
|
|
66
|
+
return (
|
|
67
|
+
<div className="overflow-x-auto">
|
|
68
|
+
<table className="w-full text-left text-sm">{children}</table>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
},
|
|
72
|
+
th({ children }) {
|
|
73
|
+
return (
|
|
74
|
+
<th className="border-b border-gray-200 bg-gray-50 px-4 py-2 font-semibold dark:border-gray-700 dark:bg-gray-800">
|
|
75
|
+
{children}
|
|
76
|
+
</th>
|
|
77
|
+
)
|
|
78
|
+
},
|
|
79
|
+
td({ children }) {
|
|
80
|
+
return (
|
|
81
|
+
<td className="border-b border-gray-200 px-4 py-2 dark:border-gray-700">
|
|
82
|
+
{children}
|
|
83
|
+
</td>
|
|
84
|
+
)
|
|
85
|
+
},
|
|
86
|
+
// Allow component overrides
|
|
87
|
+
...components,
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{content}
|
|
91
|
+
</ReactMarkdown>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Link } from '@inertiajs/react'
|
|
2
|
+
import { cn } from '../lib/utils'
|
|
3
|
+
import type { SidebarProps } from '../types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Documentation sidebar with section-based navigation.
|
|
7
|
+
*/
|
|
8
|
+
export function Sidebar({ nav, currentPath, className }: SidebarProps) {
|
|
9
|
+
return (
|
|
10
|
+
<nav className={cn('space-y-8', className)}>
|
|
11
|
+
{nav.map((section) => (
|
|
12
|
+
<div key={section.title}>
|
|
13
|
+
<h3 className="mb-3 text-xs font-mono uppercase tracking-widest text-gray-500">
|
|
14
|
+
{section.title}
|
|
15
|
+
</h3>
|
|
16
|
+
<ul className="space-y-1 border-l-2 border-gray-200">
|
|
17
|
+
{section.items.map((item) => (
|
|
18
|
+
<li key={item.href}>
|
|
19
|
+
<Link
|
|
20
|
+
href={item.href}
|
|
21
|
+
className={cn(
|
|
22
|
+
'block border-l-2 py-1.5 pl-4 text-sm transition-colors -ml-0.5',
|
|
23
|
+
currentPath === item.href
|
|
24
|
+
? 'border-primary-500 text-black font-bold'
|
|
25
|
+
: 'border-transparent text-gray-600 hover:border-black hover:text-black'
|
|
26
|
+
)}
|
|
27
|
+
>
|
|
28
|
+
{item.title}
|
|
29
|
+
</Link>
|
|
30
|
+
</li>
|
|
31
|
+
))}
|
|
32
|
+
</ul>
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
</nav>
|
|
36
|
+
)
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export {
|
|
3
|
+
CodeBlock,
|
|
4
|
+
DocsLayout,
|
|
5
|
+
DocsPage,
|
|
6
|
+
HomePage,
|
|
7
|
+
InlineCode,
|
|
8
|
+
Markdown,
|
|
9
|
+
Sidebar,
|
|
10
|
+
} from './components'
|
|
11
|
+
|
|
12
|
+
// HomePage sub-components (for compound component pattern)
|
|
13
|
+
export {
|
|
14
|
+
HomeHeader,
|
|
15
|
+
HomeHero,
|
|
16
|
+
HomeFeatures,
|
|
17
|
+
HomeFeatureItem,
|
|
18
|
+
HomeCTA,
|
|
19
|
+
HomeFooter,
|
|
20
|
+
} from './components/HomePage'
|
|
21
|
+
|
|
22
|
+
// App factory (client-side only)
|
|
23
|
+
export { createDocsApp } from './app'
|
|
24
|
+
|
|
25
|
+
// Utilities
|
|
26
|
+
export { cn } from './lib/utils'
|
|
27
|
+
export { getHighlighter, configureHighlighter } from './lib/shiki'
|
|
28
|
+
|
|
29
|
+
// Types
|
|
30
|
+
export type {
|
|
31
|
+
CodeBlockProps,
|
|
32
|
+
DocContent,
|
|
33
|
+
DocsAppConfig,
|
|
34
|
+
DocsLayoutProps,
|
|
35
|
+
MarkdownProps,
|
|
36
|
+
NavItem,
|
|
37
|
+
NavSection,
|
|
38
|
+
SharedProps,
|
|
39
|
+
SidebarProps,
|
|
40
|
+
} from './types'
|
|
41
|
+
|
|
42
|
+
export type {
|
|
43
|
+
HomePageProps,
|
|
44
|
+
HomePageContextValue,
|
|
45
|
+
HomeHeaderProps,
|
|
46
|
+
HomeFeaturesProps,
|
|
47
|
+
HomeFeatureItemProps,
|
|
48
|
+
HomeFeature,
|
|
49
|
+
} from './components/HomePage'
|
package/src/lib/shiki.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createHighlighterCore, type HighlighterCore } from 'shiki/core'
|
|
2
|
+
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
|
|
3
|
+
|
|
4
|
+
let highlighterPromise: Promise<HighlighterCore> | null = null
|
|
5
|
+
|
|
6
|
+
// Default languages to load
|
|
7
|
+
const defaultLangs = [
|
|
8
|
+
import('shiki/langs/python.mjs'),
|
|
9
|
+
import('shiki/langs/javascript.mjs'),
|
|
10
|
+
import('shiki/langs/typescript.mjs'),
|
|
11
|
+
import('shiki/langs/tsx.mjs'),
|
|
12
|
+
import('shiki/langs/jsx.mjs'),
|
|
13
|
+
import('shiki/langs/bash.mjs'),
|
|
14
|
+
import('shiki/langs/shellscript.mjs'),
|
|
15
|
+
import('shiki/langs/json.mjs'),
|
|
16
|
+
import('shiki/langs/html.mjs'),
|
|
17
|
+
import('shiki/langs/css.mjs'),
|
|
18
|
+
import('shiki/langs/yaml.mjs'),
|
|
19
|
+
import('shiki/langs/toml.mjs'),
|
|
20
|
+
import('shiki/langs/markdown.mjs'),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
// Default theme
|
|
24
|
+
const defaultTheme = import('shiki/themes/github-dark-dimmed.mjs')
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get or create a Shiki highlighter instance.
|
|
28
|
+
* Uses a singleton pattern to avoid creating multiple highlighters.
|
|
29
|
+
*/
|
|
30
|
+
export function getHighlighter(): Promise<HighlighterCore> {
|
|
31
|
+
if (!highlighterPromise) {
|
|
32
|
+
highlighterPromise = createHighlighterCore({
|
|
33
|
+
themes: [defaultTheme],
|
|
34
|
+
langs: defaultLangs,
|
|
35
|
+
engine: createJavaScriptRegexEngine(),
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
return highlighterPromise
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Configure the highlighter with custom themes and languages.
|
|
43
|
+
* Must be called before getHighlighter() is first called.
|
|
44
|
+
*/
|
|
45
|
+
export function configureHighlighter(options: {
|
|
46
|
+
theme?: Promise<any>
|
|
47
|
+
langs?: Promise<any>[]
|
|
48
|
+
}): void {
|
|
49
|
+
if (highlighterPromise) {
|
|
50
|
+
console.warn('configureHighlighter called after highlighter was created')
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
highlighterPromise = createHighlighterCore({
|
|
55
|
+
themes: [options.theme ?? defaultTheme],
|
|
56
|
+
langs: options.langs ?? defaultLangs,
|
|
57
|
+
engine: createJavaScriptRegexEngine(),
|
|
58
|
+
})
|
|
59
|
+
}
|