@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
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react'
|
|
2
|
+
import { useTheme, type Theme } from './ThemeProvider'
|
|
3
|
+
import { cn } from '../lib/utils'
|
|
4
|
+
|
|
5
|
+
interface ThemeToggleProps {
|
|
6
|
+
/** Additional CSS classes */
|
|
7
|
+
className?: string
|
|
8
|
+
/** Size variant */
|
|
9
|
+
size?: 'sm' | 'md' | 'lg'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Refined sun icon with balanced proportions
|
|
13
|
+
const SunIcon = ({ className }: { className?: string }) => (
|
|
14
|
+
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
15
|
+
<circle cx="12" cy="12" r="4" stroke="currentColor" strokeWidth="1.5" />
|
|
16
|
+
<path d="M12 5V3M12 21v-2M5 12H3m18 0h-2M7.05 7.05 5.636 5.636m12.728 12.728L16.95 16.95M7.05 16.95l-1.414 1.414M18.364 5.636 16.95 7.05" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
17
|
+
</svg>
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// Refined moon icon - elegant crescent
|
|
21
|
+
const MoonIcon = ({ className }: { className?: string }) => (
|
|
22
|
+
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
23
|
+
<path d="M21.752 15.002A9.718 9.718 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
24
|
+
</svg>
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// Refined monitor icon - clean display shape
|
|
28
|
+
const MonitorIcon = ({ className }: { className?: string }) => (
|
|
29
|
+
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
30
|
+
<rect x="2" y="3" width="20" height="14" rx="2" stroke="currentColor" strokeWidth="1.5" />
|
|
31
|
+
<path d="M8 21h8m-4-4v4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
32
|
+
</svg>
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const themeOptions: { value: Theme; label: string; icon: typeof SunIcon }[] = [
|
|
36
|
+
{ value: 'light', label: 'Light', icon: SunIcon },
|
|
37
|
+
{ value: 'dark', label: 'Dark', icon: MoonIcon },
|
|
38
|
+
{ value: 'system', label: 'System', icon: MonitorIcon },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Theme toggle dropdown with Light, Dark, and System options.
|
|
43
|
+
* Refined design with smooth animations and premium feel.
|
|
44
|
+
*/
|
|
45
|
+
export function ThemeToggle({ className, size = 'md' }: ThemeToggleProps) {
|
|
46
|
+
const { theme, resolvedTheme, setTheme } = useTheme()
|
|
47
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
48
|
+
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
49
|
+
|
|
50
|
+
// Close dropdown when clicking outside
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
53
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
54
|
+
setIsOpen(false)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (isOpen) {
|
|
59
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
60
|
+
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
61
|
+
}
|
|
62
|
+
}, [isOpen])
|
|
63
|
+
|
|
64
|
+
// Close on escape key
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const handleEscape = (event: KeyboardEvent) => {
|
|
67
|
+
if (event.key === 'Escape') setIsOpen(false)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (isOpen) {
|
|
71
|
+
document.addEventListener('keydown', handleEscape)
|
|
72
|
+
return () => document.removeEventListener('keydown', handleEscape)
|
|
73
|
+
}
|
|
74
|
+
}, [isOpen])
|
|
75
|
+
|
|
76
|
+
const iconSizes = {
|
|
77
|
+
sm: 'w-[18px] h-[18px]',
|
|
78
|
+
md: 'w-5 h-5',
|
|
79
|
+
lg: 'w-[22px] h-[22px]',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className="relative" ref={dropdownRef}>
|
|
84
|
+
{/* Toggle Button */}
|
|
85
|
+
<button
|
|
86
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
87
|
+
className={cn(
|
|
88
|
+
'relative inline-flex items-center justify-center',
|
|
89
|
+
'rounded-full p-4',
|
|
90
|
+
'text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white',
|
|
91
|
+
'hover:bg-gray-100 dark:hover:bg-white/10',
|
|
92
|
+
'transition-all duration-200 ease-out',
|
|
93
|
+
'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-[#0f0f0f]',
|
|
94
|
+
iconSizes[size],
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
aria-label="Toggle theme"
|
|
98
|
+
aria-expanded={isOpen}
|
|
99
|
+
aria-haspopup="listbox"
|
|
100
|
+
>
|
|
101
|
+
{/* Sun icon - visible in light mode */}
|
|
102
|
+
<SunIcon
|
|
103
|
+
className={cn(
|
|
104
|
+
iconSizes[size],
|
|
105
|
+
'absolute inset-0 m-auto transition-all duration-300 ease-out',
|
|
106
|
+
resolvedTheme === 'light'
|
|
107
|
+
? 'rotate-0 scale-100 opacity-100'
|
|
108
|
+
: 'rotate-90 scale-75 opacity-0'
|
|
109
|
+
)}
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
{/* Moon icon - visible in dark mode */}
|
|
113
|
+
<MoonIcon
|
|
114
|
+
className={cn(
|
|
115
|
+
iconSizes[size],
|
|
116
|
+
'absolute inset-0 m-auto transition-all duration-300 ease-out',
|
|
117
|
+
resolvedTheme === 'dark'
|
|
118
|
+
? 'rotate-0 scale-100 opacity-100'
|
|
119
|
+
: '-rotate-90 scale-75 opacity-0'
|
|
120
|
+
)}
|
|
121
|
+
/>
|
|
122
|
+
</button>
|
|
123
|
+
|
|
124
|
+
{/* Dropdown Menu */}
|
|
125
|
+
<div
|
|
126
|
+
className={cn(
|
|
127
|
+
'absolute right-0 mt-2 min-w-[140px]',
|
|
128
|
+
'p-1',
|
|
129
|
+
'bg-white dark:bg-[#171717]',
|
|
130
|
+
'border border-gray-200 dark:border-[#262626]',
|
|
131
|
+
'rounded-xl',
|
|
132
|
+
'shadow-lg shadow-black/5 dark:shadow-black/40',
|
|
133
|
+
'z-50',
|
|
134
|
+
'transition-all duration-200 ease-out origin-top-right',
|
|
135
|
+
isOpen
|
|
136
|
+
? 'opacity-100 scale-100 translate-y-0'
|
|
137
|
+
: 'opacity-0 scale-95 -translate-y-1 pointer-events-none'
|
|
138
|
+
)}
|
|
139
|
+
role="listbox"
|
|
140
|
+
aria-label="Select theme"
|
|
141
|
+
>
|
|
142
|
+
{themeOptions.map((option, index) => {
|
|
143
|
+
const Icon = option.icon
|
|
144
|
+
const isSelected = theme === option.value
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<button
|
|
148
|
+
key={option.value}
|
|
149
|
+
onClick={() => {
|
|
150
|
+
setTheme(option.value)
|
|
151
|
+
setIsOpen(false)
|
|
152
|
+
}}
|
|
153
|
+
className={cn(
|
|
154
|
+
'w-full flex items-center gap-2.5 px-3 py-2',
|
|
155
|
+
'rounded-lg',
|
|
156
|
+
'text-[13px] font-medium',
|
|
157
|
+
'transition-all duration-150 ease-out',
|
|
158
|
+
'focus:outline-none',
|
|
159
|
+
isSelected
|
|
160
|
+
? 'text-gray-900 dark:text-white bg-gray-100 dark:bg-[#262626]'
|
|
161
|
+
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-[#1f1f1f]'
|
|
162
|
+
)}
|
|
163
|
+
role="option"
|
|
164
|
+
aria-selected={isSelected}
|
|
165
|
+
style={{
|
|
166
|
+
animationDelay: isOpen ? `${index * 25}ms` : '0ms'
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<Icon className={cn(
|
|
170
|
+
'w-4 h-4 flex-shrink-0',
|
|
171
|
+
'transition-transform duration-150',
|
|
172
|
+
isSelected ? 'scale-110' : 'scale-100'
|
|
173
|
+
)} />
|
|
174
|
+
<span className="flex-1 text-left">{option.label}</span>
|
|
175
|
+
<div className={cn(
|
|
176
|
+
'w-1.5 h-1.5 rounded-full',
|
|
177
|
+
'transition-all duration-200',
|
|
178
|
+
isSelected
|
|
179
|
+
? 'bg-primary-500 scale-100 opacity-100'
|
|
180
|
+
: 'bg-transparent scale-0 opacity-0'
|
|
181
|
+
)} />
|
|
182
|
+
</button>
|
|
183
|
+
)
|
|
184
|
+
})}
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
)
|
|
188
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -5,3 +5,5 @@ export { EmojiConfetti } from './EmojiConfetti'
|
|
|
5
5
|
export { HomePage } from './HomePage'
|
|
6
6
|
export { Markdown } from './Markdown'
|
|
7
7
|
export { Sidebar } from './Sidebar'
|
|
8
|
+
export { ThemeProvider, useTheme, themeInitScript } from './ThemeProvider'
|
|
9
|
+
export { ThemeToggle } from './ThemeToggle'
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,10 @@ export {
|
|
|
8
8
|
InlineCode,
|
|
9
9
|
Markdown,
|
|
10
10
|
Sidebar,
|
|
11
|
+
ThemeProvider,
|
|
12
|
+
ThemeToggle,
|
|
13
|
+
useTheme,
|
|
14
|
+
themeInitScript,
|
|
11
15
|
} from './components'
|
|
12
16
|
|
|
13
17
|
// HomePage sub-components (for compound component pattern)
|
|
@@ -40,6 +44,8 @@ export type {
|
|
|
40
44
|
SidebarProps,
|
|
41
45
|
} from './types'
|
|
42
46
|
|
|
47
|
+
export type { Theme, ResolvedTheme } from './components/ThemeProvider'
|
|
48
|
+
|
|
43
49
|
export type {
|
|
44
50
|
HomePageProps,
|
|
45
51
|
HomePageContextValue,
|
package/src/ssr.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { createInertiaApp } from '@inertiajs/react'
|
|
|
2
2
|
import createServer from '@inertiajs/react/server'
|
|
3
3
|
import ReactDOMServer from 'react-dom/server'
|
|
4
4
|
import type { DocsAppConfig } from './types'
|
|
5
|
+
import { ThemeProvider } from './components/ThemeProvider'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Create an SSR server for documentation.
|
|
@@ -33,7 +34,11 @@ export function createDocsServer(config: DocsAppConfig): void {
|
|
|
33
34
|
}
|
|
34
35
|
return pageComponent
|
|
35
36
|
},
|
|
36
|
-
setup: ({ App, props }) =>
|
|
37
|
+
setup: ({ App, props }) => (
|
|
38
|
+
<ThemeProvider>
|
|
39
|
+
<App {...props} />
|
|
40
|
+
</ThemeProvider>
|
|
41
|
+
),
|
|
37
42
|
})
|
|
38
43
|
)
|
|
39
44
|
}
|
package/src/styles.css
CHANGED
|
@@ -56,6 +56,31 @@
|
|
|
56
56
|
--shiki-token-string-expression: #86efac;
|
|
57
57
|
--shiki-token-punctuation: #94a3b8;
|
|
58
58
|
--shiki-token-link: #38bdf8;
|
|
59
|
+
|
|
60
|
+
/* Surface colors for light mode */
|
|
61
|
+
--surface-primary: #ffffff;
|
|
62
|
+
--surface-secondary: #f9fafb;
|
|
63
|
+
--surface-tertiary: #f3f4f6;
|
|
64
|
+
--border-primary: #e5e7eb;
|
|
65
|
+
--border-secondary: #d1d5db;
|
|
66
|
+
--text-primary: #111827;
|
|
67
|
+
--text-secondary: #4b5563;
|
|
68
|
+
--text-tertiary: #6b7280;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.dark {
|
|
72
|
+
/* Surface colors for dark mode */
|
|
73
|
+
--surface-primary: #0f0f0f;
|
|
74
|
+
--surface-secondary: #171717;
|
|
75
|
+
--surface-tertiary: #262626;
|
|
76
|
+
--border-primary: #262626;
|
|
77
|
+
--border-secondary: #404040;
|
|
78
|
+
--text-primary: #fafafa;
|
|
79
|
+
--text-secondary: #a3a3a3;
|
|
80
|
+
--text-tertiary: #737373;
|
|
81
|
+
|
|
82
|
+
/* Slightly lighter code blocks in dark mode for contrast */
|
|
83
|
+
--shiki-color-background: #1a1a1a;
|
|
59
84
|
}
|
|
60
85
|
|
|
61
86
|
html {
|
|
@@ -71,6 +96,14 @@
|
|
|
71
96
|
@apply antialiased bg-white text-gray-800;
|
|
72
97
|
font-family: var(--font-sans);
|
|
73
98
|
font-weight: 400;
|
|
99
|
+
transition: background-color 0.2s ease, color 0.2s ease;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Dark mode body - using direct selector for cross-package compatibility */
|
|
103
|
+
.dark body,
|
|
104
|
+
:root.dark body {
|
|
105
|
+
background-color: #0f0f0f;
|
|
106
|
+
color: #f3f4f6;
|
|
74
107
|
}
|
|
75
108
|
|
|
76
109
|
/* Headings use heading font */
|
|
@@ -129,6 +162,7 @@
|
|
|
129
162
|
/* Inline code styling */
|
|
130
163
|
.prose :not(pre) > code {
|
|
131
164
|
@apply bg-gray-100 px-1.5 py-0.5 rounded text-sm font-medium text-gray-800;
|
|
165
|
+
@apply dark:bg-gray-800 dark:text-gray-200;
|
|
132
166
|
}
|
|
133
167
|
|
|
134
168
|
.prose :not(pre) > code::before,
|
|
@@ -140,6 +174,24 @@
|
|
|
140
174
|
.prose-invert :not(pre) > code {
|
|
141
175
|
@apply bg-gray-800;
|
|
142
176
|
}
|
|
177
|
+
|
|
178
|
+
/* Dark mode prose text colors */
|
|
179
|
+
.dark .prose {
|
|
180
|
+
--tw-prose-body: theme(colors.gray.300);
|
|
181
|
+
--tw-prose-headings: theme(colors.white);
|
|
182
|
+
--tw-prose-lead: theme(colors.gray.400);
|
|
183
|
+
--tw-prose-bold: theme(colors.white);
|
|
184
|
+
--tw-prose-counters: theme(colors.gray.400);
|
|
185
|
+
--tw-prose-bullets: theme(colors.gray.600);
|
|
186
|
+
--tw-prose-hr: theme(colors.gray.700);
|
|
187
|
+
--tw-prose-quotes: theme(colors.gray.100);
|
|
188
|
+
--tw-prose-quote-borders: theme(colors.gray.700);
|
|
189
|
+
--tw-prose-captions: theme(colors.gray.400);
|
|
190
|
+
--tw-prose-kbd: theme(colors.white);
|
|
191
|
+
--tw-prose-kbd-shadows: 0 0 0 theme(colors.gray.100);
|
|
192
|
+
--tw-prose-th-borders: theme(colors.gray.600);
|
|
193
|
+
--tw-prose-td-borders: theme(colors.gray.700);
|
|
194
|
+
}
|
|
143
195
|
}
|
|
144
196
|
|
|
145
197
|
/* Syntax highlighting - rounded corners like Starlight */
|
|
@@ -170,6 +222,15 @@
|
|
|
170
222
|
@apply bg-gray-400;
|
|
171
223
|
}
|
|
172
224
|
|
|
225
|
+
/* Dark mode scrollbars */
|
|
226
|
+
.dark ::-webkit-scrollbar-thumb {
|
|
227
|
+
@apply bg-gray-700;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.dark ::-webkit-scrollbar-thumb:hover {
|
|
231
|
+
@apply bg-gray-600;
|
|
232
|
+
}
|
|
233
|
+
|
|
173
234
|
/* Emoji confetti animation */
|
|
174
235
|
@keyframes emojiConfettiBurst {
|
|
175
236
|
0% {
|
package/src/types.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface SharedProps {
|
|
|
26
26
|
logoInvertedUrl?: string
|
|
27
27
|
/** Footer logo image URL (from Python backend) */
|
|
28
28
|
footerLogoUrl?: string
|
|
29
|
+
/** Footer logo image URL for dark mode (from Python backend) */
|
|
30
|
+
footerLogoInvertedUrl?: string
|
|
29
31
|
/** GitHub repository URL (from Python backend) */
|
|
30
32
|
githubUrl?: string
|
|
31
33
|
/** Additional navigation links (from Python backend) */
|