diff --git a/mdx-components.js b/mdx-components.js
index 7b4c98a4..a0e986c2 100644
--- a/mdx-components.js
+++ b/mdx-components.js
@@ -1,12 +1,25 @@
-import { useMDXComponents as getThemeComponents } from "nextra-theme-docs"; // nextra-theme-blog or your custom theme
+import { DocsLayout } from './src/components/docs/DocsLayout';
+import { buildPageMap } from './src/app/docs/page-map';
-// Get the default MDX components
-const themeComponents = getThemeComponents();
-
-// Merge components
+// Custom MDX components without nextra-theme-docs
export function useMDXComponents(components) {
return {
- ...themeComponents,
+ // Wrapper component that wraps the entire MDX content with DocsLayout
+ wrapper: ({ children, toc, metadata, sourceCode, ...props }) => {
+ const { pageMap } = buildPageMap();
+
+ return (
+
+ {children}
+
+ );
+ },
+ // You can add custom component mappings here
+ // Example:
+ // h1: (props) =>
,
+ // h2: (props) => ,
+ // a: (props) => ,
...components,
};
}
+
diff --git a/package.json b/package.json
index 69910105..b62948fc 100644
--- a/package.json
+++ b/package.json
@@ -22,14 +22,15 @@
"dependencies": {
"@react-three/drei": "^10.7.6",
"@react-three/fiber": "^9.4.0",
+ "@theguild/remark-mermaid": "^0.3.0",
"dotenv": "^17.2.3",
"framer-motion": "^12.23.22",
"lucide-react": "^0.545.0",
"mermaid": "^11.12.1",
"next": "^15.5.9",
"next-intl": "^4.3.12",
- "nextra": "^4.6.0",
- "nextra-theme-docs": "^4.6.0",
+ "next-themes": "^0.4.6",
+ "nextra": "^4.6.1",
"postmark": "^4.0.5",
"react": "^19.2.0",
"react-dom": "^19.2.0",
diff --git a/src/app/docs/layout.tsx b/src/app/docs/layout.tsx
index 374d7e81..d1db4a4b 100644
--- a/src/app/docs/layout.tsx
+++ b/src/app/docs/layout.tsx
@@ -1,6 +1,5 @@
-import { Layout } from 'nextra-theme-docs'
-import 'nextra-theme-docs/style.css'
import { DocsNavbar, DocsFooter, DocsBanner } from '@/components/docs/index'
+import { DocsProvider } from '@/components/docs/DocsProvider'
import { Inter, JetBrains_Mono } from "next/font/google"
import { Suspense } from 'react'
import { ThemeProvider } from "next-themes"
@@ -23,44 +22,30 @@ export const metadata = {
description: 'Official documentation for KubeStellar - Multi-cluster orchestration platform',
}
-const banner =
-const navbar = (
- }>
-
-
-)
-const footer =
-
type Props = {
children: React.ReactNode
}
export default async function DocsLayout({ children }: Props) {
// Build page map from local docs
- const { pageMap } = buildPageMap()
+ buildPageMap();
return (
-
- {children}
-
+
+
+
+ }>
+
+
+
+ {children}
+
+
+
+
diff --git a/src/app/globals.css b/src/app/globals.css
index b2d881d3..aae62256 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -59,6 +59,33 @@ html:not(.dark) .nextra-toc {
border-right-color: rgba(0, 0, 0, 0.1);
}
+/* Light mode: Improve MDX content readability */
+html:not(.dark) .nextra-content, html:not(.dark) .prose {
+ color: #222;
+ background: transparent;
+}
+html:not(.dark) .nextra-content h1, html:not(.dark) .prose h1,
+html:not(.dark) .nextra-content h2, html:not(.dark) .prose h2,
+html:not(.dark) .nextra-content h3, html:not(.dark) .prose h3,
+html:not(.dark) .nextra-content h4, html:not(.dark) .prose h4 {
+ color: #1a202c;
+}
+html:not(.dark) .nextra-content p, html:not(.dark) .prose p {
+ color: #222;
+}
+html:not(.dark) .nextra-content table, html:not(.dark) .prose table {
+ color: #222;
+ background: #fff;
+}
+html:not(.dark) .nextra-content th, html:not(.dark) .prose th {
+ color: #1a202c;
+ background: #f3f4f6;
+}
+html:not(.dark) .nextra-content td, html:not(.dark) .prose td {
+ color: #222;
+ background: #fff;
+}
+
/* Mobile responsive - remove separators */
@media (max-width: 1024px) {
.nextra-toc {
@@ -86,7 +113,7 @@ html:not(.dark) .nextra-toc {
}
body {
- background: var(--background);
+ background: var(--color-bg-primary);
color: var(--foreground);
font-family: var(--font-inter);
transition:
@@ -2109,3 +2136,282 @@ html:not(.dark) .contributor-card:hover {
z-index: 44 !important;
}
}
+
+/* Custom Documentation Styling */
+.prose {
+ color: #1f2937;
+ max-width: none !important;
+}
+
+html.dark .prose {
+ color: #e5e7eb;
+}
+
+.prose h1 {
+ font-size: 2.25rem;
+ font-weight: 700;
+ margin-bottom: 1.5rem;
+ margin-top: 2rem;
+ color: #111827;
+ border-bottom: 1px solid #e5e7eb;
+ padding-bottom: 0.5rem;
+}
+
+html.dark .prose h1 {
+ color: #f3f4f6;
+ border-bottom-color: #1f2937;
+}
+
+.prose h2 {
+ font-size: 1.875rem;
+ font-weight: 700;
+ margin-top: 2.5rem;
+ margin-bottom: 1rem;
+ color: #111827;
+ border-bottom: 1px solid #e5e7eb;
+ padding-bottom: 0.5rem;
+ scroll-margin-top: 5rem;
+}
+
+html.dark .prose h2 {
+ color: #f3f4f6;
+ border-bottom-color: #1f2937;
+}
+
+.prose h3 {
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin-top: 2rem;
+ margin-bottom: 0.75rem;
+ color: #111827;
+ scroll-margin-top: 5rem;
+}
+
+html.dark .prose h3 {
+ color: #f3f4f6;
+}
+
+.prose h4 {
+ font-size: 1.25rem;
+ font-weight: 700;
+ margin-top: 1.5rem;
+ margin-bottom: 0.5rem;
+ color: #111827;
+ scroll-margin-top: 5rem;
+}
+
+html.dark .prose h4 {
+ color: #f3f4f6;
+}
+
+.prose h5 {
+ font-size: 1.125rem;
+ font-weight: 700;
+ margin-top: 1rem;
+ margin-bottom: 0.5rem;
+ color: #111827;
+}
+
+html.dark .prose h5 {
+ color: #f3f4f6;
+}
+
+.prose h6 {
+ font-size: 1rem;
+ font-weight: 700;
+ margin-top: 0.75rem;
+ margin-bottom: 0.5rem;
+ color: #111827;
+}
+
+html.dark .prose h6 {
+ color: #f3f4f6;
+}
+
+.prose p {
+ margin-bottom: 1rem;
+ line-height: 1.75;
+}
+
+.prose a {
+ color: #2563eb;
+ text-decoration: underline;
+ text-decoration-thickness: 2px;
+ text-underline-offset: 2px;
+}
+
+.prose a:hover {
+ text-decoration: underline;
+}
+
+html.dark .prose a {
+ color: #60a5fa;
+}
+
+.prose code {
+ background: #f3f4f6;
+ color: #1a202c;
+ padding: 0.125rem 0.375rem;
+ border-radius: 0.375rem;
+}
+
+html.dark .prose code {
+ background: #1f2937;
+ color: #f3f4f6;
+}
+
+.prose pre {
+ background: #f9fafb;
+ border-radius: 0.5rem;
+ padding: 1rem;
+ overflow-x: auto;
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+ border: 1px solid #e5e7eb;
+}
+
+html.dark .prose pre {
+ background: #111827;
+ border-color: #374151;
+}
+
+.prose pre code {
+ background: transparent !important;
+ padding: 0;
+ border-radius: 0;
+ color: #1f2937;
+}
+
+html.dark .prose pre code {
+ background: transparent !important;
+ color: #f3f4f6;
+}
+
+.prose ul, .prose ol {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ padding-left: 1.5rem;
+}
+
+.prose ul {
+ list-style-type: disc;
+}
+
+.prose ol {
+ list-style-type: decimal;
+}
+
+.prose ul ul, .prose ol ul {
+ list-style-type: circle;
+}
+
+.prose ul ul ul, .prose ol ul ul, .prose ol ol ul {
+ list-style-type: square;
+}
+
+.prose li {
+ margin-bottom: 0.5rem;
+ padding-left: 0.25rem;
+}
+
+.prose blockquote {
+ border-left: 4px solid #3b82f6;
+ padding-left: 1rem;
+ font-style: italic;
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+ color: #374151;
+}
+
+html.dark .prose blockquote {
+ color: #d1d5db;
+}
+
+.prose table {
+ width: 100%;
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+ border-collapse: collapse;
+}
+
+.prose thead {
+ background: #f3f4f6;
+}
+
+html.dark .prose thead {
+ background: #1f2937;
+}
+
+.prose th {
+ border: 1px solid #d1d5db;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ text-align: left;
+ font-weight: 600;
+}
+
+html.dark .prose th {
+ border-color: #374151;
+}
+
+.prose td {
+ border: 1px solid #d1d5db;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+html.dark .prose td {
+ border-color: #374151;
+}
+
+.prose img {
+ border-radius: 0.5rem;
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.prose hr {
+ margin-top: 2rem;
+ margin-bottom: 2rem;
+ border-color: #d1d5db;
+}
+
+html.dark .prose hr {
+ border-color: #374151;
+}
+
+/* Sidebar Scrollbar Styling */
+aside::-webkit-scrollbar {
+ width: 6px;
+}
+
+aside::-webkit-scrollbar-track {
+ background: #f3f4f6;
+}
+
+html.dark aside::-webkit-scrollbar-track {
+ background: #111827;
+}
+
+aside::-webkit-scrollbar-thumb {
+ background: #d1d5db;
+ border-radius: 9999px;
+}
+
+html.dark aside::-webkit-scrollbar-thumb {
+ background: #374151;
+}
+
+aside::-webkit-scrollbar-thumb:hover {
+ background: #9ca3af;
+}
+
+html.dark aside::-webkit-scrollbar-thumb:hover {
+ background: #4b5563;
+}
diff --git a/src/components/docs/DocsBanner.tsx b/src/components/docs/DocsBanner.tsx
index 57af8560..77cd066e 100644
--- a/src/components/docs/DocsBanner.tsx
+++ b/src/components/docs/DocsBanner.tsx
@@ -1,42 +1,60 @@
'use client'
-import { Banner } from 'nextra/components'
import Link from 'next/link'
import { useTheme } from 'next-themes'
import { useState, useEffect } from 'react'
import { getLocalizedUrl } from '@/lib/url'
+import { useDocsMenu } from './DocsProvider'
export function DocsBanner() {
const { resolvedTheme } = useTheme()
const [mounted, setMounted] = useState(false)
+ const { bannerDismissed, dismissBanner } = useDocsMenu()
useEffect(() => {
setMounted(true)
}, [])
- if (!mounted) {
+ if (!mounted || bannerDismissed) {
return null
}
const isDark = resolvedTheme === 'dark'
return (
-
-
- 🚀 🚀 🚀 ATTENTION: KubeStellar needs your help - please take our 2-minute survey{' '}
-
- {getLocalizedUrl("https://kubestellar.io/survey")}
-
- {' '}🚀 🚀 🚀
-
-
+
+
+
+
+
+ 🚀 🚀 🚀 ATTENTION: KubeStellar needs your help - please take our 2-minute survey{' '}
+
+ {getLocalizedUrl("https://kubestellar.io/survey")}
+
+ {' '}🚀 🚀 🚀
+
+
+
+
+
+
)
}
diff --git a/src/components/docs/DocsLayout.tsx b/src/components/docs/DocsLayout.tsx
new file mode 100644
index 00000000..38537369
--- /dev/null
+++ b/src/components/docs/DocsLayout.tsx
@@ -0,0 +1,77 @@
+"use client";
+
+import { ReactNode } from 'react';
+import { DocsSidebar } from './DocsSidebar';
+import { TableOfContents } from './TableOfContents';
+import { MobileTOC } from './MobileTOC';
+import { MobileHeader } from './MobileSidebarToggle';
+import { useDocsMenu } from './DocsProvider';
+
+interface TOCItem {
+ id: string;
+ value: string;
+ depth: number;
+}
+
+interface PageMapItem {
+ name: string;
+ route?: string;
+ title?: string;
+ children?: PageMapItem[];
+ frontMatter?: Record;
+ kind?: string;
+}
+
+interface Metadata {
+ title?: string;
+ description?: string;
+ [key: string]: unknown;
+}
+
+interface DocsLayoutProps {
+ children: ReactNode;
+ pageMap: PageMapItem[];
+ toc?: TOCItem[];
+ metadata?: Metadata;
+}
+
+export function DocsLayout({ children, pageMap, toc, metadata }: DocsLayoutProps) {
+ const { menuOpen, toggleMenu } = useDocsMenu();
+
+ return (
+
+ {/* Sidebar - Self-contained with all logic */}
+
+
+ {/* Mobile overlay */}
+ {menuOpen && (
+
+ )}
+
+ {/* Main content area */}
+
+
+ {/* Mobile Header with Sidebar Toggle - Only visible on mobile/tablet */}
+
+
+ {/* Mobile TOC Accordion - Only visible on mobile/tablet */}
+
+
+ {/* Article content */}
+
+ {children}
+
+
+
+
+ {/* Table of Contents - Right sidebar on desktop */}
+
+
+ );
+}
diff --git a/src/components/docs/DocsNavbar.tsx b/src/components/docs/DocsNavbar.tsx
index 1ac5c627..eba0c7a8 100644
--- a/src/components/docs/DocsNavbar.tsx
+++ b/src/components/docs/DocsNavbar.tsx
@@ -7,14 +7,12 @@ import { useTheme } from "next-themes";
// import { useSearchParams, usePathname, useRouter } from 'next/navigation'
import { VERSIONS } from '@/config/versions'
import { getLocalizedUrl } from "@/lib/url";
-import { useMenu, setMenu } from 'nextra-theme-docs'
type DropdownType = "contribute" | "community" | "language" | "github" | null;
export default function DocsNavbar() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [openDropdown, setOpenDropdown] = useState(null);
- const menuOpen = useMenu();
const [searchQuery, setSearchQuery] = useState("");
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [searchResults, setSearchResults] = useState
+
- {/* Sidebar toggle button for mobile - integrates with Nextra */}
-