@usecross/docs 0.5.0 → 0.7.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.
@@ -1,19 +1,28 @@
1
1
  import { Link } from '@inertiajs/react'
2
2
  import { cn } from '../lib/utils'
3
+ import { DocSetSelector } from './DocSetSelector'
3
4
  import type { SidebarProps } from '../types'
4
5
 
5
6
  /**
6
7
  * Documentation sidebar with section-based navigation.
8
+ * In multi-docs mode, includes a dropdown selector at the top.
7
9
  */
8
- export function Sidebar({ nav, currentPath, className }: SidebarProps) {
10
+ export function Sidebar({ nav, currentPath, className, docSets, currentDocSet }: SidebarProps) {
9
11
  return (
10
- <nav className={cn('space-y-8', className)}>
12
+ <nav className={cn('space-y-6', className)}>
13
+ {/* Doc Set Selector - only shown in multi-docs mode */}
14
+ {docSets && docSets.length > 1 && (
15
+ <DocSetSelector docSets={docSets} currentDocSet={currentDocSet ?? ''} className="mb-6" />
16
+ )}
17
+
18
+ {/* Navigation Sections */}
19
+ <div className="space-y-8">
11
20
  {nav.map((section) => (
12
21
  <div key={section.title}>
13
- <h3 className="mb-3 text-xs font-mono uppercase tracking-widest text-gray-500">
22
+ <h3 className="mb-3 text-xs font-mono uppercase tracking-widest text-gray-500 dark:text-gray-400">
14
23
  {section.title}
15
24
  </h3>
16
- <ul className="space-y-1 border-l-2 border-gray-200">
25
+ <ul className="space-y-1 border-l-2 border-gray-200 dark:border-gray-700">
17
26
  {section.items.map((item) => (
18
27
  <li key={item.href}>
19
28
  <Link
@@ -21,8 +30,8 @@ export function Sidebar({ nav, currentPath, className }: SidebarProps) {
21
30
  className={cn(
22
31
  'block border-l-2 py-1.5 pl-4 text-sm transition-colors -ml-0.5',
23
32
  currentPath === item.href
24
- ? 'border-primary-500 text-black font-bold'
25
- : 'border-transparent text-gray-600 hover:border-black hover:text-black'
33
+ ? 'border-primary-500 text-gray-900 dark:text-white font-bold'
34
+ : '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
35
  )}
27
36
  >
28
37
  {item.title}
@@ -32,6 +41,7 @@ export function Sidebar({ nav, currentPath, className }: SidebarProps) {
32
41
  </ul>
33
42
  </div>
34
43
  ))}
44
+ </div>
35
45
  </nav>
36
46
  )
37
47
  }
@@ -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()
@@ -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
+ }
@@ -1,7 +1,10 @@
1
1
  export { CodeBlock, InlineCode } from './CodeBlock'
2
+ export { DocSetSelector } from './DocSetSelector'
2
3
  export { DocsLayout } from './DocsLayout'
3
4
  export { DocsPage } from './DocsPage'
4
5
  export { EmojiConfetti } from './EmojiConfetti'
5
6
  export { HomePage } from './HomePage'
6
7
  export { Markdown } from './Markdown'
7
8
  export { Sidebar } from './Sidebar'
9
+ export { ThemeProvider, useTheme, themeInitScript } from './ThemeProvider'
10
+ export { ThemeToggle } from './ThemeToggle'
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // Components
2
2
  export {
3
3
  CodeBlock,
4
+ DocSetSelector,
4
5
  DocsLayout,
5
6
  DocsPage,
6
7
  EmojiConfetti,
@@ -8,6 +9,10 @@ export {
8
9
  InlineCode,
9
10
  Markdown,
10
11
  Sidebar,
12
+ ThemeProvider,
13
+ ThemeToggle,
14
+ useTheme,
15
+ themeInitScript,
11
16
  } from './components'
12
17
 
13
18
  // HomePage sub-components (for compound component pattern)
@@ -33,6 +38,7 @@ export type {
33
38
  DocContent,
34
39
  DocsAppConfig,
35
40
  DocsLayoutProps,
41
+ DocSetMeta,
36
42
  MarkdownProps,
37
43
  NavItem,
38
44
  NavSection,
@@ -40,6 +46,8 @@ export type {
40
46
  SidebarProps,
41
47
  } from './types'
42
48
 
49
+ export type { Theme, ResolvedTheme } from './components/ThemeProvider'
50
+
43
51
  export type {
44
52
  HomePageProps,
45
53
  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 }) => <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
@@ -13,6 +13,9 @@
13
13
  * Note: The Google Fonts import must come before other imports to avoid CSS ordering warnings.
14
14
  */
15
15
 
16
+ /* Enable class-based dark mode (using .dark class on root element) */
17
+ @custom-variant dark (&:where(.dark, .dark *));
18
+
16
19
  /* Theme customizations for Tailwind v4 */
17
20
  @theme {
18
21
  /* Max width */
@@ -56,6 +59,31 @@
56
59
  --shiki-token-string-expression: #86efac;
57
60
  --shiki-token-punctuation: #94a3b8;
58
61
  --shiki-token-link: #38bdf8;
62
+
63
+ /* Surface colors for light mode */
64
+ --surface-primary: #ffffff;
65
+ --surface-secondary: #f9fafb;
66
+ --surface-tertiary: #f3f4f6;
67
+ --border-primary: #e5e7eb;
68
+ --border-secondary: #d1d5db;
69
+ --text-primary: #111827;
70
+ --text-secondary: #4b5563;
71
+ --text-tertiary: #6b7280;
72
+ }
73
+
74
+ .dark {
75
+ /* Surface colors for dark mode */
76
+ --surface-primary: #0f0f0f;
77
+ --surface-secondary: #171717;
78
+ --surface-tertiary: #262626;
79
+ --border-primary: #262626;
80
+ --border-secondary: #404040;
81
+ --text-primary: #fafafa;
82
+ --text-secondary: #a3a3a3;
83
+ --text-tertiary: #737373;
84
+
85
+ /* Slightly lighter code blocks in dark mode for contrast */
86
+ --shiki-color-background: #1a1a1a;
59
87
  }
60
88
 
61
89
  html {
@@ -71,6 +99,14 @@
71
99
  @apply antialiased bg-white text-gray-800;
72
100
  font-family: var(--font-sans);
73
101
  font-weight: 400;
102
+ transition: background-color 0.2s ease, color 0.2s ease;
103
+ }
104
+
105
+ /* Dark mode body - using direct selector for cross-package compatibility */
106
+ .dark body,
107
+ :root.dark body {
108
+ background-color: #0f0f0f;
109
+ color: #f3f4f6;
74
110
  }
75
111
 
76
112
  /* Headings use heading font */
@@ -129,6 +165,7 @@
129
165
  /* Inline code styling */
130
166
  .prose :not(pre) > code {
131
167
  @apply bg-gray-100 px-1.5 py-0.5 rounded text-sm font-medium text-gray-800;
168
+ @apply dark:bg-gray-800 dark:text-gray-200;
132
169
  }
133
170
 
134
171
  .prose :not(pre) > code::before,
@@ -140,6 +177,24 @@
140
177
  .prose-invert :not(pre) > code {
141
178
  @apply bg-gray-800;
142
179
  }
180
+
181
+ /* Dark mode prose text colors */
182
+ .dark .prose {
183
+ --tw-prose-body: theme(colors.gray.300);
184
+ --tw-prose-headings: theme(colors.white);
185
+ --tw-prose-lead: theme(colors.gray.400);
186
+ --tw-prose-bold: theme(colors.white);
187
+ --tw-prose-counters: theme(colors.gray.400);
188
+ --tw-prose-bullets: theme(colors.gray.600);
189
+ --tw-prose-hr: theme(colors.gray.700);
190
+ --tw-prose-quotes: theme(colors.gray.100);
191
+ --tw-prose-quote-borders: theme(colors.gray.700);
192
+ --tw-prose-captions: theme(colors.gray.400);
193
+ --tw-prose-kbd: theme(colors.white);
194
+ --tw-prose-kbd-shadows: 0 0 0 theme(colors.gray.100);
195
+ --tw-prose-th-borders: theme(colors.gray.600);
196
+ --tw-prose-td-borders: theme(colors.gray.700);
197
+ }
143
198
  }
144
199
 
145
200
  /* Syntax highlighting - rounded corners like Starlight */
@@ -170,6 +225,15 @@
170
225
  @apply bg-gray-400;
171
226
  }
172
227
 
228
+ /* Dark mode scrollbars */
229
+ .dark ::-webkit-scrollbar-thumb {
230
+ @apply bg-gray-700;
231
+ }
232
+
233
+ .dark ::-webkit-scrollbar-thumb:hover {
234
+ @apply bg-gray-600;
235
+ }
236
+
173
237
  /* Emoji confetti animation */
174
238
  @keyframes emojiConfettiBurst {
175
239
  0% {
package/src/types.ts CHANGED
@@ -16,6 +16,18 @@ export interface NavSection {
16
16
  items: NavItem[]
17
17
  }
18
18
 
19
+ /** Documentation set metadata (for multi-docs mode) */
20
+ export interface DocSetMeta {
21
+ name: string
22
+ slug: string
23
+ description: string
24
+ /** Emoji or short text icon (e.g., "🍓") */
25
+ icon?: string
26
+ /** URL to icon image */
27
+ iconUrl?: string
28
+ prefix: string
29
+ }
30
+
19
31
  /** Shared props passed to all pages via Inertia */
20
32
  export interface SharedProps {
21
33
  nav: NavSection[]
@@ -26,10 +38,16 @@ export interface SharedProps {
26
38
  logoInvertedUrl?: string
27
39
  /** Footer logo image URL (from Python backend) */
28
40
  footerLogoUrl?: string
41
+ /** Footer logo image URL for dark mode (from Python backend) */
42
+ footerLogoInvertedUrl?: string
29
43
  /** GitHub repository URL (from Python backend) */
30
44
  githubUrl?: string
31
45
  /** Additional navigation links (from Python backend) */
32
46
  navLinks?: Array<{ label: string; href: string }>
47
+ /** Available documentation sets (multi-docs mode) */
48
+ docSets?: DocSetMeta[]
49
+ /** Current documentation set slug (multi-docs mode) */
50
+ currentDocSet?: string
33
51
  }
34
52
 
35
53
  /** Document content structure */
@@ -65,6 +83,10 @@ export interface SidebarProps {
65
83
  nav: NavSection[]
66
84
  currentPath: string
67
85
  className?: string
86
+ /** Available documentation sets (multi-docs mode) */
87
+ docSets?: DocSetMeta[]
88
+ /** Current documentation set slug (multi-docs mode) */
89
+ currentDocSet?: string
68
90
  }
69
91
 
70
92
  /** Props for Markdown component */