@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.
@@ -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">&rarr;</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
+ }
@@ -0,0 +1,6 @@
1
+ export { CodeBlock, InlineCode } from './CodeBlock'
2
+ export { DocsLayout } from './DocsLayout'
3
+ export { DocsPage } from './DocsPage'
4
+ export { HomePage } from './HomePage'
5
+ export { Markdown } from './Markdown'
6
+ export { Sidebar } from './Sidebar'
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'
@@ -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
+ }