@usecross/docs 0.5.0 → 0.6.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 +38 -3
- package/dist/index.js +390 -135
- package/dist/index.js.map +1 -1
- package/dist/ssr.d.ts +1 -1
- package/dist/ssr.js +79 -1
- package/dist/ssr.js.map +1 -1
- package/dist/{types-CCdOzu28.d.ts → types-CR-kx8KP.d.ts} +2 -0
- package/package.json +1 -1
- package/src/app.tsx +6 -3
- package/src/components/DocsLayout.tsx +28 -19
- package/src/components/HomePage.tsx +59 -36
- package/src/components/Sidebar.tsx +4 -4
- package/src/components/ThemeProvider.tsx +125 -0
- package/src/components/ThemeToggle.tsx +188 -0
- package/src/components/index.ts +2 -0
- package/src/index.ts +6 -0
- package/src/ssr.tsx +6 -1
- package/src/styles.css +61 -0
- package/src/types.ts +2 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { Head, Link, usePage } from '@inertiajs/react'
|
|
2
2
|
import { useState } from 'react'
|
|
3
3
|
import { Sidebar } from './Sidebar'
|
|
4
|
+
import { ThemeToggle } from './ThemeToggle'
|
|
5
|
+
import { useTheme } from './ThemeProvider'
|
|
4
6
|
import type { DocsLayoutProps, SharedProps } from '../types'
|
|
5
7
|
|
|
6
8
|
function MobileMenuButton({ onClick, isOpen }: { onClick: () => void; isOpen: boolean }) {
|
|
7
9
|
return (
|
|
8
10
|
<button
|
|
9
11
|
onClick={onClick}
|
|
10
|
-
className="inline-flex items-center justify-center p-2 -ml-2 text-
|
|
12
|
+
className="inline-flex items-center justify-center p-2 -ml-2 text-gray-700 hover:text-primary-500 dark:text-gray-300 dark:hover:text-primary-400 lg:hidden transition-colors"
|
|
11
13
|
aria-expanded={isOpen}
|
|
12
14
|
>
|
|
13
15
|
<span className="sr-only">{isOpen ? 'Close menu' : 'Open menu'}</span>
|
|
@@ -54,6 +56,7 @@ export function DocsLayout({
|
|
|
54
56
|
const sharedProps = usePage<{ props: SharedProps }>().props as unknown as SharedProps
|
|
55
57
|
const { nav, currentPath } = sharedProps
|
|
56
58
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
|
59
|
+
const { resolvedTheme } = useTheme()
|
|
57
60
|
|
|
58
61
|
// Merge props - component props take precedence over shared props from Python
|
|
59
62
|
const logoUrl = propLogoUrl ?? sharedProps.logoUrl
|
|
@@ -68,18 +71,20 @@ export function DocsLayout({
|
|
|
68
71
|
<img src={logoUrl} alt="Logo" className="h-8" />
|
|
69
72
|
) : null)
|
|
70
73
|
|
|
71
|
-
// Determine which logo to display in footer (
|
|
74
|
+
// Determine which logo to display in footer (theme-aware)
|
|
72
75
|
const footerLogoUrl = sharedProps.footerLogoUrl || logoUrl
|
|
73
|
-
const
|
|
74
|
-
|
|
76
|
+
const footerLogoInvertedUrl = sharedProps.footerLogoInvertedUrl || logoInvertedUrl
|
|
77
|
+
const currentFooterLogoUrl = resolvedTheme === 'dark' ? (footerLogoInvertedUrl || footerLogoUrl) : footerLogoUrl
|
|
78
|
+
const footerLogo = logo || (currentFooterLogoUrl ? (
|
|
79
|
+
<img src={currentFooterLogoUrl} alt="Logo" className="h-6" />
|
|
75
80
|
) : null)
|
|
76
81
|
|
|
77
82
|
return (
|
|
78
|
-
<div className="min-h-screen bg-white flex flex-col">
|
|
83
|
+
<div className="min-h-screen bg-white dark:bg-[#0f0f0f] flex flex-col transition-colors duration-200">
|
|
79
84
|
<Head title={title} />
|
|
80
85
|
|
|
81
86
|
{/* Fixed navigation */}
|
|
82
|
-
<nav className="fixed w-full z-50 bg-white border-b border-gray-200">
|
|
87
|
+
<nav className="fixed w-full z-50 bg-white/95 dark:bg-[#0f0f0f]/95 backdrop-blur-sm border-b border-gray-200 dark:border-gray-800 transition-colors">
|
|
83
88
|
<div className="px-4 lg:px-10">
|
|
84
89
|
<div className="flex justify-between h-16 items-center">
|
|
85
90
|
<div className="flex items-center gap-2">
|
|
@@ -89,17 +94,21 @@ export function DocsLayout({
|
|
|
89
94
|
{headerLogo}
|
|
90
95
|
</Link>
|
|
91
96
|
) : (
|
|
92
|
-
<Link href="/" className="font-bold text-lg">
|
|
97
|
+
<Link href="/" className="font-bold text-lg text-gray-900 dark:text-white">
|
|
93
98
|
Docs
|
|
94
99
|
</Link>
|
|
95
100
|
)}
|
|
96
101
|
</div>
|
|
97
|
-
<div className="flex items-center
|
|
102
|
+
<div className="flex items-center gap-6">
|
|
103
|
+
<div className="-mr-2">
|
|
104
|
+
<ThemeToggle size="sm" />
|
|
105
|
+
</div>
|
|
106
|
+
|
|
98
107
|
{navLinks.map((link) => (
|
|
99
108
|
<Link
|
|
100
109
|
key={link.href}
|
|
101
110
|
href={link.href}
|
|
102
|
-
className="text-
|
|
111
|
+
className="hidden sm:block text-gray-700 dark:text-gray-300 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
|
|
103
112
|
>
|
|
104
113
|
{link.label}
|
|
105
114
|
</Link>
|
|
@@ -109,7 +118,7 @@ export function DocsLayout({
|
|
|
109
118
|
href={githubUrl}
|
|
110
119
|
target="_blank"
|
|
111
120
|
rel="noopener noreferrer"
|
|
112
|
-
className="text-
|
|
121
|
+
className="text-gray-700 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
|
|
113
122
|
>
|
|
114
123
|
<GitHubIcon />
|
|
115
124
|
</a>
|
|
@@ -122,18 +131,18 @@ export function DocsLayout({
|
|
|
122
131
|
{/* Mobile sidebar */}
|
|
123
132
|
{mobileMenuOpen && (
|
|
124
133
|
<div className="fixed inset-0 z-40 lg:hidden">
|
|
125
|
-
<div className="fixed inset-0 bg-black/50" onClick={() => setMobileMenuOpen(false)} />
|
|
126
|
-
<div className="fixed inset-y-0 left-0 w-72 overflow-y-auto bg-white px-4 lg:px-10 py-6 pt-20 border-r border-gray-200">
|
|
134
|
+
<div className="fixed inset-0 bg-black/50 dark:bg-black/70" onClick={() => setMobileMenuOpen(false)} />
|
|
135
|
+
<div className="fixed inset-y-0 left-0 w-72 overflow-y-auto bg-white dark:bg-[#0f0f0f] px-4 lg:px-10 py-6 pt-20 border-r border-gray-200 dark:border-gray-800 transition-colors">
|
|
127
136
|
<Sidebar nav={nav} currentPath={currentPath} />
|
|
128
137
|
</div>
|
|
129
138
|
</div>
|
|
130
139
|
)}
|
|
131
140
|
|
|
132
141
|
{/* Main content area */}
|
|
133
|
-
<div className="bg-white pt-16 w-full flex-1">
|
|
142
|
+
<div className="bg-white dark:bg-[#0f0f0f] pt-16 w-full flex-1 transition-colors">
|
|
134
143
|
<div className="grid grid-cols-12">
|
|
135
144
|
{/* Desktop sidebar */}
|
|
136
|
-
<aside className="hidden lg:block lg:col-span-3 xl:col-span-2 border-r border-gray-200 min-h-[calc(100vh-4rem)]">
|
|
145
|
+
<aside className="hidden lg:block lg:col-span-3 xl:col-span-2 border-r border-gray-200 dark:border-gray-800 min-h-[calc(100vh-4rem)] transition-colors">
|
|
137
146
|
<nav className="sticky top-16 px-4 lg:px-10 py-6 max-h-[calc(100vh-4rem)] overflow-y-auto">
|
|
138
147
|
<Sidebar nav={nav} currentPath={currentPath} />
|
|
139
148
|
</nav>
|
|
@@ -141,7 +150,7 @@ export function DocsLayout({
|
|
|
141
150
|
|
|
142
151
|
{/* Main content */}
|
|
143
152
|
<main className="col-span-12 lg:col-span-9 xl:col-span-10 p-4 lg:px-10 lg:py-6">
|
|
144
|
-
<article className="prose prose-lg max-w-3xl prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-3xl prose-h1:mb-4 prose-h2:text-2xl prose-h2:mt-10 first:prose-h2:mt-0 prose-h3:text-xl prose-a:text-primary-600 prose-a:no-underline hover:prose-a:underline prose-code:bg-gray-100 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none">
|
|
153
|
+
<article className="prose prose-lg max-w-3xl prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-3xl prose-h1:mb-4 prose-h2:text-2xl prose-h2:mt-10 first:prose-h2:mt-0 prose-h3:text-xl prose-a:text-primary-600 dark:prose-a:text-primary-400 prose-a:no-underline hover:prose-a:underline prose-code:bg-gray-100 dark:prose-code:bg-gray-800 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none dark:prose-headings:text-white dark:prose-strong:text-white dark:text-gray-300">
|
|
145
154
|
{children}
|
|
146
155
|
</article>
|
|
147
156
|
</main>
|
|
@@ -150,12 +159,12 @@ export function DocsLayout({
|
|
|
150
159
|
|
|
151
160
|
{/* Footer */}
|
|
152
161
|
{footer || (
|
|
153
|
-
<footer className="border-t border-gray-200 py-8">
|
|
162
|
+
<footer className="border-t border-gray-200 dark:border-gray-800 py-8 bg-white dark:bg-[#0f0f0f] transition-colors">
|
|
154
163
|
<div className="px-4 lg:px-10 flex flex-col md:flex-row justify-between items-center gap-6">
|
|
155
164
|
{footerLogo && <Link href="/">{footerLogo}</Link>}
|
|
156
|
-
<div className="flex gap-8 text-sm text-gray-600">
|
|
165
|
+
<div className="flex gap-8 text-sm text-gray-600 dark:text-gray-400">
|
|
157
166
|
{navLinks.map((link) => (
|
|
158
|
-
<Link key={link.href} href={link.href} className="hover:text-black transition-colors">
|
|
167
|
+
<Link key={link.href} href={link.href} className="hover:text-black dark:hover:text-white transition-colors">
|
|
159
168
|
{link.label}
|
|
160
169
|
</Link>
|
|
161
170
|
))}
|
|
@@ -164,7 +173,7 @@ export function DocsLayout({
|
|
|
164
173
|
href={githubUrl}
|
|
165
174
|
target="_blank"
|
|
166
175
|
rel="noopener noreferrer"
|
|
167
|
-
className="hover:text-black transition-colors"
|
|
176
|
+
className="hover:text-black dark:hover:text-white transition-colors"
|
|
168
177
|
>
|
|
169
178
|
GitHub
|
|
170
179
|
</a>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Head, Link } from '@inertiajs/react'
|
|
2
2
|
import { createContext, useContext, useState, type ReactNode } from 'react'
|
|
3
|
+
import { ThemeToggle } from './ThemeToggle'
|
|
4
|
+
import { useTheme } from './ThemeProvider'
|
|
3
5
|
|
|
4
6
|
// ============================================================================
|
|
5
7
|
// Types
|
|
@@ -21,6 +23,7 @@ export interface HomePageContextValue {
|
|
|
21
23
|
logoUrl?: string
|
|
22
24
|
heroLogoUrl?: string
|
|
23
25
|
footerLogoUrl?: string
|
|
26
|
+
footerLogoInvertedUrl?: string
|
|
24
27
|
githubUrl?: string
|
|
25
28
|
navLinks: Array<{ label: string; href: string }>
|
|
26
29
|
}
|
|
@@ -78,9 +81,9 @@ function InstallCommand({ command }: { command: string }) {
|
|
|
78
81
|
return (
|
|
79
82
|
<button
|
|
80
83
|
onClick={copyToClipboard}
|
|
81
|
-
className="group relative flex items-center bg-
|
|
84
|
+
className="group relative flex items-center bg-gray-900 dark:bg-white border border-gray-900 dark:border-white px-4 h-14 font-mono text-sm text-white dark:text-gray-900 hover:bg-white dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer"
|
|
82
85
|
>
|
|
83
|
-
<span className="text-primary-500 mr-2">$</span>
|
|
86
|
+
<span className="text-primary-500 dark:text-primary-600 mr-2">$</span>
|
|
84
87
|
<span>{command}</span>
|
|
85
88
|
<svg
|
|
86
89
|
className={`ml-4 w-4 h-4 transition ${copied ? 'text-green-400' : 'opacity-50 group-hover:opacity-100'}`}
|
|
@@ -96,7 +99,7 @@ function InstallCommand({ command }: { command: string }) {
|
|
|
96
99
|
/>
|
|
97
100
|
</svg>
|
|
98
101
|
<span
|
|
99
|
-
className={`absolute -top-8 left-1/2 -translate-x-1/2 bg-
|
|
102
|
+
className={`absolute -top-8 left-1/2 -translate-x-1/2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 text-xs py-1 px-2 rounded transition-opacity duration-300 whitespace-nowrap ${
|
|
100
103
|
copied ? 'opacity-100' : 'opacity-0'
|
|
101
104
|
}`}
|
|
102
105
|
>
|
|
@@ -137,7 +140,7 @@ function DefaultLogo() {
|
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
return (
|
|
140
|
-
<Link href="/" className="font-bold text-lg">
|
|
143
|
+
<Link href="/" className="font-bold text-lg text-gray-900 dark:text-white">
|
|
141
144
|
{title}
|
|
142
145
|
</Link>
|
|
143
146
|
)
|
|
@@ -151,16 +154,20 @@ export function HomeHeader({ renderLogo }: HomeHeaderProps = {}) {
|
|
|
151
154
|
const { navLinks, githubUrl } = useHomePage()
|
|
152
155
|
|
|
153
156
|
return (
|
|
154
|
-
<nav className="fixed w-full z-50 bg-white border-b border-gray-200">
|
|
157
|
+
<nav className="fixed w-full z-50 bg-white/95 dark:bg-[#0f0f0f]/95 backdrop-blur-sm border-b border-gray-200 dark:border-gray-800 transition-colors">
|
|
155
158
|
<div className="px-4 lg:px-10">
|
|
156
159
|
<div className="flex justify-between h-16 items-center">
|
|
157
160
|
{renderLogo ? renderLogo() : <DefaultLogo />}
|
|
158
|
-
<div className="flex items-center
|
|
161
|
+
<div className="flex items-center gap-6">
|
|
162
|
+
<div className="-mr-2">
|
|
163
|
+
<ThemeToggle size="sm" />
|
|
164
|
+
</div>
|
|
165
|
+
|
|
159
166
|
{navLinks.map((link) => (
|
|
160
167
|
<Link
|
|
161
168
|
key={link.href}
|
|
162
169
|
href={link.href}
|
|
163
|
-
className="text-
|
|
170
|
+
className="hidden sm:block text-gray-700 dark:text-gray-300 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
|
|
164
171
|
>
|
|
165
172
|
{link.label}
|
|
166
173
|
</Link>
|
|
@@ -170,7 +177,7 @@ export function HomeHeader({ renderLogo }: HomeHeaderProps = {}) {
|
|
|
170
177
|
href={githubUrl}
|
|
171
178
|
target="_blank"
|
|
172
179
|
rel="noopener noreferrer"
|
|
173
|
-
className="text-
|
|
180
|
+
className="text-gray-700 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
|
|
174
181
|
>
|
|
175
182
|
<GitHubIcon />
|
|
176
183
|
</a>
|
|
@@ -193,7 +200,7 @@ export function HomeHero() {
|
|
|
193
200
|
<section className="pt-16">
|
|
194
201
|
<div className="px-4 lg:px-10 py-16 lg:py-24">
|
|
195
202
|
<div className="max-w-4xl">
|
|
196
|
-
<div className="mb-4 text-sm font-mono uppercase tracking-widest text-gray-500">
|
|
203
|
+
<div className="mb-4 text-sm font-mono uppercase tracking-widest text-gray-500 dark:text-gray-400">
|
|
197
204
|
{tagline}
|
|
198
205
|
</div>
|
|
199
206
|
{heroLogoUrl ? (
|
|
@@ -205,18 +212,18 @@ export function HomeHero() {
|
|
|
205
212
|
/>
|
|
206
213
|
</h1>
|
|
207
214
|
) : (
|
|
208
|
-
<h1 className="text-5xl lg:text-7xl font-bold tracking-tight mb-6">
|
|
215
|
+
<h1 className="text-5xl lg:text-7xl font-bold tracking-tight mb-6 text-gray-900 dark:text-white">
|
|
209
216
|
{title}
|
|
210
217
|
</h1>
|
|
211
218
|
)}
|
|
212
|
-
<p className="text-xl lg:text-2xl text-gray-
|
|
219
|
+
<p className="text-xl lg:text-2xl text-gray-600 dark:text-gray-300 max-w-2xl leading-relaxed mb-8">
|
|
213
220
|
{description}
|
|
214
221
|
</p>
|
|
215
222
|
|
|
216
223
|
<div className="flex flex-col sm:flex-row gap-3">
|
|
217
224
|
<Link
|
|
218
225
|
href={ctaHref}
|
|
219
|
-
className="inline-flex items-center justify-center px-8 h-14 bg-
|
|
226
|
+
className="inline-flex items-center justify-center px-8 h-14 bg-gray-900 dark:bg-white text-white dark:text-gray-900 font-bold text-lg hover:bg-primary-500 dark:hover:bg-primary-500 dark:hover:text-white transition-colors border border-gray-900 dark:border-white hover:border-primary-500 dark:hover:border-primary-500"
|
|
220
227
|
>
|
|
221
228
|
{ctaText}
|
|
222
229
|
</Link>
|
|
@@ -234,17 +241,17 @@ export function HomeHero() {
|
|
|
234
241
|
export function HomeFeatureItem({ feature, index, totalFeatures }: HomeFeatureItemProps) {
|
|
235
242
|
return (
|
|
236
243
|
<div
|
|
237
|
-
className={`p-4 lg:p-10 border-b sm:border-b border-gray-200 ${
|
|
244
|
+
className={`p-4 lg:p-10 border-b sm:border-b border-gray-200 dark:border-gray-800 ${
|
|
238
245
|
index % 2 === 0 ? 'sm:border-r' : ''
|
|
239
246
|
} ${index >= totalFeatures - 2 ? 'sm:border-b-0' : ''} ${
|
|
240
247
|
index === totalFeatures - 1 && totalFeatures % 2 === 1 ? 'border-b-0' : ''
|
|
241
|
-
}`}
|
|
248
|
+
} transition-colors`}
|
|
242
249
|
>
|
|
243
|
-
<div className="text-5xl font-bold text-primary-500 mb-4">
|
|
250
|
+
<div className="text-5xl font-bold text-primary-500 dark:text-primary-400 mb-4">
|
|
244
251
|
{String(index + 1).padStart(2, '0')}
|
|
245
252
|
</div>
|
|
246
|
-
<h3 className="text-xl font-bold mb-2">{feature.title}</h3>
|
|
247
|
-
<p className="text-gray-600">{feature.description}</p>
|
|
253
|
+
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">{feature.title}</h3>
|
|
254
|
+
<p className="text-gray-600 dark:text-gray-300">{feature.description}</p>
|
|
248
255
|
</div>
|
|
249
256
|
)
|
|
250
257
|
}
|
|
@@ -260,13 +267,13 @@ export function HomeFeatures({ renderFeature }: HomeFeaturesProps = {}) {
|
|
|
260
267
|
}
|
|
261
268
|
|
|
262
269
|
return (
|
|
263
|
-
<section className="border-t border-gray-200">
|
|
270
|
+
<section className="border-t border-gray-200 dark:border-gray-800 transition-colors">
|
|
264
271
|
<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">
|
|
272
|
+
<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 dark:border-gray-800 transition-colors">
|
|
273
|
+
<div className="text-sm font-mono uppercase tracking-widest text-gray-500 dark:text-gray-400 mb-4">
|
|
267
274
|
Features
|
|
268
275
|
</div>
|
|
269
|
-
<h2 className="text-4xl lg:text-5xl font-bold tracking-tight">
|
|
276
|
+
<h2 className="text-4xl lg:text-5xl font-bold tracking-tight text-gray-900 dark:text-white">
|
|
270
277
|
Why {title}?
|
|
271
278
|
</h2>
|
|
272
279
|
</div>
|
|
@@ -299,27 +306,27 @@ export function HomeCTA() {
|
|
|
299
306
|
const { ctaHref } = useHomePage()
|
|
300
307
|
|
|
301
308
|
return (
|
|
302
|
-
<section className="border-t border-gray-200">
|
|
309
|
+
<section className="border-t border-gray-200 dark:border-gray-800 transition-colors">
|
|
303
310
|
<div className="grid grid-cols-12 items-center">
|
|
304
311
|
<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">
|
|
312
|
+
<h2 className="text-4xl lg:text-6xl font-bold tracking-tight mb-4 text-gray-900 dark:text-white">
|
|
306
313
|
Ready to start?
|
|
307
314
|
</h2>
|
|
308
|
-
<p className="text-xl text-gray-600 mb-8 max-w-2xl">
|
|
315
|
+
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 max-w-2xl">
|
|
309
316
|
Get up and running in minutes. Check out our documentation to learn more.
|
|
310
317
|
</p>
|
|
311
318
|
<Link
|
|
312
319
|
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-
|
|
320
|
+
className="inline-flex items-center justify-center px-8 py-4 bg-primary-500 text-white font-bold text-lg hover:bg-gray-900 dark:hover:bg-white dark:hover:text-gray-900 transition-colors border border-primary-500 hover:border-gray-900 dark:hover:border-white"
|
|
314
321
|
>
|
|
315
322
|
Read the Docs
|
|
316
323
|
</Link>
|
|
317
324
|
</div>
|
|
318
325
|
<Link
|
|
319
326
|
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-
|
|
327
|
+
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-gray-900 dark:hover:bg-white transition-colors min-h-[200px] group"
|
|
321
328
|
>
|
|
322
|
-
<div className="text-white text-8xl font-bold">→</div>
|
|
329
|
+
<div className="text-white group-hover:text-white dark:group-hover:text-gray-900 text-8xl font-bold transition-colors">→</div>
|
|
323
330
|
</Link>
|
|
324
331
|
</div>
|
|
325
332
|
</section>
|
|
@@ -330,19 +337,25 @@ export function HomeCTA() {
|
|
|
330
337
|
* Footer section.
|
|
331
338
|
*/
|
|
332
339
|
export function HomeFooter() {
|
|
333
|
-
const { title, logoUrl, footerLogoUrl, navLinks, githubUrl } = useHomePage()
|
|
340
|
+
const { title, logoUrl, footerLogoUrl, footerLogoInvertedUrl, navLinks, githubUrl } = useHomePage()
|
|
341
|
+
const { resolvedTheme } = useTheme()
|
|
342
|
+
|
|
343
|
+
// Select appropriate logo based on theme
|
|
344
|
+
const currentLogoUrl = resolvedTheme === 'dark'
|
|
345
|
+
? (footerLogoInvertedUrl || footerLogoUrl || logoUrl)
|
|
346
|
+
: (footerLogoUrl || logoUrl)
|
|
334
347
|
|
|
335
348
|
return (
|
|
336
|
-
<footer className="border-t border-gray-200 py-8">
|
|
349
|
+
<footer className="border-t border-gray-200 dark:border-gray-800 py-8 bg-white dark:bg-[#0f0f0f] transition-colors">
|
|
337
350
|
<div className="px-4 lg:px-10 flex flex-col md:flex-row justify-between items-center gap-6">
|
|
338
|
-
{
|
|
351
|
+
{currentLogoUrl && (
|
|
339
352
|
<Link href="/">
|
|
340
|
-
<img src={
|
|
353
|
+
<img src={currentLogoUrl} alt={title} className="h-6" />
|
|
341
354
|
</Link>
|
|
342
355
|
)}
|
|
343
|
-
<div className="flex gap-8 text-sm text-gray-600">
|
|
356
|
+
<div className="flex gap-8 text-sm text-gray-600 dark:text-gray-300">
|
|
344
357
|
{navLinks.map((link) => (
|
|
345
|
-
<Link key={link.href} href={link.href} className="hover:text-black transition-colors">
|
|
358
|
+
<Link key={link.href} href={link.href} className="hover:text-black dark:hover:text-white transition-colors">
|
|
346
359
|
{link.label}
|
|
347
360
|
</Link>
|
|
348
361
|
))}
|
|
@@ -351,7 +364,7 @@ export function HomeFooter() {
|
|
|
351
364
|
href={githubUrl}
|
|
352
365
|
target="_blank"
|
|
353
366
|
rel="noopener noreferrer"
|
|
354
|
-
className="hover:text-black transition-colors"
|
|
367
|
+
className="hover:text-black dark:hover:text-white transition-colors"
|
|
355
368
|
>
|
|
356
369
|
GitHub
|
|
357
370
|
</a>
|
|
@@ -369,12 +382,21 @@ export function HomeFooter() {
|
|
|
369
382
|
function HomeSpacer() {
|
|
370
383
|
return (
|
|
371
384
|
<div
|
|
372
|
-
className="grow"
|
|
385
|
+
className="grow dark:hidden"
|
|
373
386
|
style={{ backgroundImage: 'linear-gradient(rgb(229, 231, 235) 1px, transparent 1px)' }}
|
|
374
387
|
/>
|
|
375
388
|
)
|
|
376
389
|
}
|
|
377
390
|
|
|
391
|
+
function HomeSpacerDark() {
|
|
392
|
+
return (
|
|
393
|
+
<div
|
|
394
|
+
className="grow hidden dark:block"
|
|
395
|
+
style={{ backgroundImage: 'linear-gradient(rgb(38, 38, 38) 1px, transparent 1px)' }}
|
|
396
|
+
/>
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
378
400
|
function DefaultHomeLayout() {
|
|
379
401
|
return (
|
|
380
402
|
<>
|
|
@@ -383,6 +405,7 @@ function DefaultHomeLayout() {
|
|
|
383
405
|
<HomeFeatures />
|
|
384
406
|
<HomeCTA />
|
|
385
407
|
<HomeSpacer />
|
|
408
|
+
<HomeSpacerDark />
|
|
386
409
|
<HomeFooter />
|
|
387
410
|
</>
|
|
388
411
|
)
|
|
@@ -428,7 +451,7 @@ export function HomePage({
|
|
|
428
451
|
|
|
429
452
|
return (
|
|
430
453
|
<HomePageContext.Provider value={contextValue}>
|
|
431
|
-
<div className="min-h-screen bg-white flex flex-col">
|
|
454
|
+
<div className="min-h-screen bg-white dark:bg-[#0f0f0f] flex flex-col transition-colors duration-200">
|
|
432
455
|
<Head title={props.title} />
|
|
433
456
|
{children || <DefaultHomeLayout />}
|
|
434
457
|
</div>
|
|
@@ -10,10 +10,10 @@ export function Sidebar({ nav, currentPath, className }: SidebarProps) {
|
|
|
10
10
|
<nav className={cn('space-y-8', className)}>
|
|
11
11
|
{nav.map((section) => (
|
|
12
12
|
<div key={section.title}>
|
|
13
|
-
<h3 className="mb-3 text-xs font-mono uppercase tracking-widest text-gray-500">
|
|
13
|
+
<h3 className="mb-3 text-xs font-mono uppercase tracking-widest text-gray-500 dark:text-gray-400">
|
|
14
14
|
{section.title}
|
|
15
15
|
</h3>
|
|
16
|
-
<ul className="space-y-1 border-l-2 border-gray-200">
|
|
16
|
+
<ul className="space-y-1 border-l-2 border-gray-200 dark:border-gray-700">
|
|
17
17
|
{section.items.map((item) => (
|
|
18
18
|
<li key={item.href}>
|
|
19
19
|
<Link
|
|
@@ -21,8 +21,8 @@ export function Sidebar({ nav, currentPath, className }: SidebarProps) {
|
|
|
21
21
|
className={cn(
|
|
22
22
|
'block border-l-2 py-1.5 pl-4 text-sm transition-colors -ml-0.5',
|
|
23
23
|
currentPath === item.href
|
|
24
|
-
? 'border-primary-500 text-
|
|
25
|
-
: 'border-transparent text-gray-600 hover:border-
|
|
24
|
+
? 'border-primary-500 text-gray-900 dark:text-white font-bold'
|
|
25
|
+
: 'border-transparent text-gray-600 dark:text-gray-300 hover:border-gray-900 dark:hover:border-white hover:text-gray-900 dark:hover:text-white'
|
|
26
26
|
)}
|
|
27
27
|
>
|
|
28
28
|
{item.title}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export type Theme = 'light' | 'dark' | 'system'
|
|
4
|
+
export type ResolvedTheme = 'light' | 'dark'
|
|
5
|
+
|
|
6
|
+
interface ThemeContextValue {
|
|
7
|
+
theme: Theme
|
|
8
|
+
resolvedTheme: ResolvedTheme
|
|
9
|
+
setTheme: (theme: Theme) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const ThemeContext = createContext<ThemeContextValue | null>(null)
|
|
13
|
+
|
|
14
|
+
const STORAGE_KEY = 'cross-docs-theme'
|
|
15
|
+
|
|
16
|
+
function getSystemTheme(): ResolvedTheme {
|
|
17
|
+
if (typeof window === 'undefined') return 'light'
|
|
18
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getStoredTheme(): Theme | null {
|
|
22
|
+
if (typeof window === 'undefined') return null
|
|
23
|
+
const stored = localStorage.getItem(STORAGE_KEY)
|
|
24
|
+
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
|
25
|
+
return stored
|
|
26
|
+
}
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ThemeProviderProps {
|
|
31
|
+
children: ReactNode
|
|
32
|
+
/** Default theme if no preference is stored. Defaults to 'system'. */
|
|
33
|
+
defaultTheme?: Theme
|
|
34
|
+
/** Force a specific theme, ignoring user preference */
|
|
35
|
+
forcedTheme?: ResolvedTheme
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function ThemeProvider({
|
|
39
|
+
children,
|
|
40
|
+
defaultTheme = 'system',
|
|
41
|
+
forcedTheme,
|
|
42
|
+
}: ThemeProviderProps) {
|
|
43
|
+
const [theme, setThemeState] = useState<Theme>(() => {
|
|
44
|
+
// During SSR, use defaultTheme
|
|
45
|
+
if (typeof window === 'undefined') return defaultTheme
|
|
46
|
+
return getStoredTheme() ?? defaultTheme
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>(() => {
|
|
50
|
+
if (forcedTheme) return forcedTheme
|
|
51
|
+
if (typeof window === 'undefined') return 'light'
|
|
52
|
+
if (theme === 'system') return getSystemTheme()
|
|
53
|
+
return theme
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Update resolved theme when theme changes or system preference changes
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (forcedTheme) {
|
|
59
|
+
setResolvedTheme(forcedTheme)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const updateResolvedTheme = () => {
|
|
64
|
+
if (theme === 'system') {
|
|
65
|
+
setResolvedTheme(getSystemTheme())
|
|
66
|
+
} else {
|
|
67
|
+
setResolvedTheme(theme)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
updateResolvedTheme()
|
|
72
|
+
|
|
73
|
+
// Listen for system preference changes
|
|
74
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
|
75
|
+
const handleChange = () => {
|
|
76
|
+
if (theme === 'system') {
|
|
77
|
+
setResolvedTheme(getSystemTheme())
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
mediaQuery.addEventListener('change', handleChange)
|
|
82
|
+
return () => mediaQuery.removeEventListener('change', handleChange)
|
|
83
|
+
}, [theme, forcedTheme])
|
|
84
|
+
|
|
85
|
+
// Apply theme class to document
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const root = document.documentElement
|
|
88
|
+
root.classList.remove('light', 'dark')
|
|
89
|
+
root.classList.add(resolvedTheme)
|
|
90
|
+
}, [resolvedTheme])
|
|
91
|
+
|
|
92
|
+
const setTheme = (newTheme: Theme) => {
|
|
93
|
+
setThemeState(newTheme)
|
|
94
|
+
localStorage.setItem(STORAGE_KEY, newTheme)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
|
|
99
|
+
{children}
|
|
100
|
+
</ThemeContext.Provider>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function useTheme(): ThemeContextValue {
|
|
105
|
+
const context = useContext(ThemeContext)
|
|
106
|
+
if (!context) {
|
|
107
|
+
throw new Error('useTheme must be used within a ThemeProvider')
|
|
108
|
+
}
|
|
109
|
+
return context
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Script to prevent flash of unstyled content (FOUC) during page load.
|
|
114
|
+
* Include this in your HTML <head> before any stylesheets.
|
|
115
|
+
*/
|
|
116
|
+
export const themeInitScript = `
|
|
117
|
+
(function() {
|
|
118
|
+
try {
|
|
119
|
+
var stored = localStorage.getItem('${STORAGE_KEY}');
|
|
120
|
+
var theme = stored === 'light' || stored === 'dark' ? stored :
|
|
121
|
+
(stored === 'system' || !stored) && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
122
|
+
document.documentElement.classList.add(theme);
|
|
123
|
+
} catch (e) {}
|
|
124
|
+
})();
|
|
125
|
+
`.trim()
|