@barodoc/theme-docs 0.0.1

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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +68 -0
  3. package/src/components/CodeCopy.astro +173 -0
  4. package/src/components/DocHeader.tsx +166 -0
  5. package/src/components/DocsSidebar.tsx +84 -0
  6. package/src/components/Header.astro +32 -0
  7. package/src/components/LanguageSwitcher.astro +77 -0
  8. package/src/components/MobileNav.astro +61 -0
  9. package/src/components/MobileNavSheet.tsx +73 -0
  10. package/src/components/Search.tsx +210 -0
  11. package/src/components/SearchDialog.tsx +83 -0
  12. package/src/components/Sidebar.astro +56 -0
  13. package/src/components/SidebarWrapper.tsx +24 -0
  14. package/src/components/TableOfContents.astro +98 -0
  15. package/src/components/ThemeScript.astro +11 -0
  16. package/src/components/ThemeToggle.tsx +57 -0
  17. package/src/components/api/ApiEndpoint.astro +36 -0
  18. package/src/components/api/ApiParam.astro +26 -0
  19. package/src/components/api/ApiParams.astro +16 -0
  20. package/src/components/api/ApiResponse.astro +35 -0
  21. package/src/components/index.ts +30 -0
  22. package/src/components/mdx/Accordion.tsx +61 -0
  23. package/src/components/mdx/Badge.tsx +33 -0
  24. package/src/components/mdx/Callout.astro +79 -0
  25. package/src/components/mdx/Card.astro +66 -0
  26. package/src/components/mdx/CardGroup.astro +18 -0
  27. package/src/components/mdx/CodeGroup.astro +63 -0
  28. package/src/components/mdx/CodeGroup.tsx +51 -0
  29. package/src/components/mdx/Columns.tsx +31 -0
  30. package/src/components/mdx/DocAccordion.tsx +87 -0
  31. package/src/components/mdx/DocCallout.tsx +65 -0
  32. package/src/components/mdx/DocCard.tsx +70 -0
  33. package/src/components/mdx/DocTabs.tsx +48 -0
  34. package/src/components/mdx/Expandable.tsx +107 -0
  35. package/src/components/mdx/FileTree.tsx +72 -0
  36. package/src/components/mdx/Frame.tsx +23 -0
  37. package/src/components/mdx/Icon.tsx +59 -0
  38. package/src/components/mdx/Mermaid.tsx +94 -0
  39. package/src/components/mdx/ParamField.tsx +76 -0
  40. package/src/components/mdx/ResponseField.tsx +62 -0
  41. package/src/components/mdx/Step.astro +14 -0
  42. package/src/components/mdx/Steps.astro +37 -0
  43. package/src/components/mdx/Steps.tsx +49 -0
  44. package/src/components/mdx/Tabs.tsx +67 -0
  45. package/src/components/mdx/Tooltip.tsx +36 -0
  46. package/src/components/ui/accordion.tsx +54 -0
  47. package/src/components/ui/alert.tsx +60 -0
  48. package/src/components/ui/button.tsx +57 -0
  49. package/src/components/ui/card.tsx +75 -0
  50. package/src/components/ui/collapsible.tsx +9 -0
  51. package/src/components/ui/dialog.tsx +119 -0
  52. package/src/components/ui/index.ts +11 -0
  53. package/src/components/ui/scroll-area.tsx +45 -0
  54. package/src/components/ui/separator.tsx +28 -0
  55. package/src/components/ui/sheet.tsx +137 -0
  56. package/src/components/ui/tabs.tsx +52 -0
  57. package/src/components/ui/tooltip.tsx +29 -0
  58. package/src/index.ts +74 -0
  59. package/src/layouts/BaseLayout.astro +28 -0
  60. package/src/layouts/DocsLayout.astro +121 -0
  61. package/src/lib/utils.ts +6 -0
  62. package/src/pages/docs/[...slug].astro +116 -0
  63. package/src/pages/index.astro +217 -0
  64. package/src/styles/global.css +342 -0
@@ -0,0 +1,137 @@
1
+ import * as React from "react";
2
+ import * as SheetPrimitive from "@radix-ui/react-dialog";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { X } from "lucide-react";
5
+ import { cn } from "../../lib/utils";
6
+
7
+ const Sheet = SheetPrimitive.Root;
8
+
9
+ const SheetTrigger = SheetPrimitive.Trigger;
10
+
11
+ const SheetClose = SheetPrimitive.Close;
12
+
13
+ const SheetPortal = SheetPrimitive.Portal;
14
+
15
+ const SheetOverlay = React.forwardRef<
16
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
17
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
18
+ >(({ className, ...props }, ref) => (
19
+ <SheetPrimitive.Overlay
20
+ className={cn(
21
+ "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
22
+ className
23
+ )}
24
+ {...props}
25
+ ref={ref}
26
+ />
27
+ ));
28
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
29
+
30
+ const sheetVariants = cva(
31
+ "fixed z-50 gap-4 bg-[var(--color-bg)] p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
32
+ {
33
+ variants: {
34
+ side: {
35
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
36
+ bottom:
37
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
38
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
39
+ right:
40
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
41
+ },
42
+ },
43
+ defaultVariants: {
44
+ side: "right",
45
+ },
46
+ }
47
+ );
48
+
49
+ interface SheetContentProps
50
+ extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
51
+ VariantProps<typeof sheetVariants> {}
52
+
53
+ const SheetContent = React.forwardRef<
54
+ React.ElementRef<typeof SheetPrimitive.Content>,
55
+ SheetContentProps
56
+ >(({ side = "right", className, children, ...props }, ref) => (
57
+ <SheetPortal>
58
+ <SheetOverlay />
59
+ <SheetPrimitive.Content
60
+ ref={ref}
61
+ className={cn(sheetVariants({ side }), className)}
62
+ {...props}
63
+ >
64
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
65
+ <X className="h-4 w-4" />
66
+ <span className="sr-only">Close</span>
67
+ </SheetPrimitive.Close>
68
+ {children}
69
+ </SheetPrimitive.Content>
70
+ </SheetPortal>
71
+ ));
72
+ SheetContent.displayName = SheetPrimitive.Content.displayName;
73
+
74
+ const SheetHeader = ({
75
+ className,
76
+ ...props
77
+ }: React.HTMLAttributes<HTMLDivElement>) => (
78
+ <div
79
+ className={cn(
80
+ "flex flex-col space-y-2 text-center sm:text-left",
81
+ className
82
+ )}
83
+ {...props}
84
+ />
85
+ );
86
+ SheetHeader.displayName = "SheetHeader";
87
+
88
+ const SheetFooter = ({
89
+ className,
90
+ ...props
91
+ }: React.HTMLAttributes<HTMLDivElement>) => (
92
+ <div
93
+ className={cn(
94
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
95
+ className
96
+ )}
97
+ {...props}
98
+ />
99
+ );
100
+ SheetFooter.displayName = "SheetFooter";
101
+
102
+ const SheetTitle = React.forwardRef<
103
+ React.ElementRef<typeof SheetPrimitive.Title>,
104
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
105
+ >(({ className, ...props }, ref) => (
106
+ <SheetPrimitive.Title
107
+ ref={ref}
108
+ className={cn("text-lg font-semibold text-[var(--color-text)]", className)}
109
+ {...props}
110
+ />
111
+ ));
112
+ SheetTitle.displayName = SheetPrimitive.Title.displayName;
113
+
114
+ const SheetDescription = React.forwardRef<
115
+ React.ElementRef<typeof SheetPrimitive.Description>,
116
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
117
+ >(({ className, ...props }, ref) => (
118
+ <SheetPrimitive.Description
119
+ ref={ref}
120
+ className={cn("text-sm text-[var(--color-text-secondary)]", className)}
121
+ {...props}
122
+ />
123
+ ));
124
+ SheetDescription.displayName = SheetPrimitive.Description.displayName;
125
+
126
+ export {
127
+ Sheet,
128
+ SheetPortal,
129
+ SheetOverlay,
130
+ SheetTrigger,
131
+ SheetClose,
132
+ SheetContent,
133
+ SheetHeader,
134
+ SheetFooter,
135
+ SheetTitle,
136
+ SheetDescription,
137
+ };
@@ -0,0 +1,52 @@
1
+ import * as React from "react";
2
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
3
+ import { cn } from "../../lib/utils";
4
+
5
+ const Tabs = TabsPrimitive.Root;
6
+
7
+ const TabsList = React.forwardRef<
8
+ React.ElementRef<typeof TabsPrimitive.List>,
9
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
10
+ >(({ className, ...props }, ref) => (
11
+ <TabsPrimitive.List
12
+ ref={ref}
13
+ className={cn(
14
+ "inline-flex h-10 items-center justify-center rounded-lg bg-[var(--color-bg-secondary)] p-1 text-[var(--color-text-secondary)]",
15
+ className
16
+ )}
17
+ {...props}
18
+ />
19
+ ));
20
+ TabsList.displayName = TabsPrimitive.List.displayName;
21
+
22
+ const TabsTrigger = React.forwardRef<
23
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
24
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
25
+ >(({ className, ...props }, ref) => (
26
+ <TabsPrimitive.Trigger
27
+ ref={ref}
28
+ className={cn(
29
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-[var(--color-bg)] data-[state=active]:text-[var(--color-text)] data-[state=active]:shadow",
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ ));
35
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
36
+
37
+ const TabsContent = React.forwardRef<
38
+ React.ElementRef<typeof TabsPrimitive.Content>,
39
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
40
+ >(({ className, ...props }, ref) => (
41
+ <TabsPrimitive.Content
42
+ ref={ref}
43
+ className={cn(
44
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
45
+ className
46
+ )}
47
+ {...props}
48
+ />
49
+ ));
50
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
51
+
52
+ export { Tabs, TabsList, TabsTrigger, TabsContent };
@@ -0,0 +1,29 @@
1
+ import * as React from "react";
2
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
+ import { cn } from "../../lib/utils";
4
+
5
+ const TooltipProvider = TooltipPrimitive.Provider;
6
+
7
+ const Tooltip = TooltipPrimitive.Root;
8
+
9
+ const TooltipTrigger = TooltipPrimitive.Trigger;
10
+
11
+ const TooltipContent = React.forwardRef<
12
+ React.ElementRef<typeof TooltipPrimitive.Content>,
13
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
14
+ >(({ className, sideOffset = 4, ...props }, ref) => (
15
+ <TooltipPrimitive.Portal>
16
+ <TooltipPrimitive.Content
17
+ ref={ref}
18
+ sideOffset={sideOffset}
19
+ className={cn(
20
+ "z-50 overflow-hidden rounded-md bg-[var(--color-text)] px-3 py-1.5 text-xs text-[var(--color-bg)] animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ </TooltipPrimitive.Portal>
26
+ ));
27
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
28
+
29
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
package/src/index.ts ADDED
@@ -0,0 +1,74 @@
1
+ import type { AstroIntegration } from "astro";
2
+ import type { ThemeExport } from "@barodoc/core";
3
+ import mdx from "@astrojs/mdx";
4
+ import react from "@astrojs/react";
5
+ import tailwindcss from "@tailwindcss/vite";
6
+
7
+ export interface DocsThemeOptions {
8
+ customCss?: string[];
9
+ }
10
+
11
+ function createThemeIntegration(options?: DocsThemeOptions): AstroIntegration {
12
+ return {
13
+ name: "@barodoc/theme-docs",
14
+ hooks: {
15
+ "astro:config:setup": async ({ updateConfig, injectRoute, logger }) => {
16
+ logger.info("Setting up Barodoc docs theme...");
17
+
18
+ // Inject routes
19
+ injectRoute({
20
+ pattern: "/",
21
+ entrypoint: "@barodoc/theme-docs/pages/index.astro",
22
+ });
23
+
24
+ injectRoute({
25
+ pattern: "/docs/[...slug]",
26
+ entrypoint: "@barodoc/theme-docs/pages/docs/[...slug].astro",
27
+ });
28
+
29
+ // Inject localized routes for non-default locales
30
+ injectRoute({
31
+ pattern: "/[locale]",
32
+ entrypoint: "@barodoc/theme-docs/pages/index.astro",
33
+ });
34
+
35
+ injectRoute({
36
+ pattern: "/[locale]/docs/[...slug]",
37
+ entrypoint: "@barodoc/theme-docs/pages/docs/[...slug].astro",
38
+ });
39
+
40
+ // Update Astro config with integrations and Vite plugins
41
+ updateConfig({
42
+ integrations: [mdx(), react()],
43
+ vite: {
44
+ plugins: [tailwindcss()],
45
+ resolve: {
46
+ dedupe: ["react", "react-dom"],
47
+ },
48
+ },
49
+ markdown: {
50
+ shikiConfig: {
51
+ themes: {
52
+ light: "github-light",
53
+ dark: "github-dark",
54
+ },
55
+ },
56
+ },
57
+ });
58
+
59
+ logger.info("Docs theme routes injected");
60
+ },
61
+ },
62
+ };
63
+ }
64
+
65
+ export default function docsTheme(options?: DocsThemeOptions): ThemeExport {
66
+ return {
67
+ name: "@barodoc/theme-docs",
68
+ integration: () => createThemeIntegration(options),
69
+ styles: options?.customCss || [],
70
+ };
71
+ }
72
+
73
+ // Re-export components for easy access
74
+ export * from "./components/index.ts";
@@ -0,0 +1,28 @@
1
+ ---
2
+ import ThemeScript from "../components/ThemeScript.astro";
3
+ import { SearchDialog } from "../components/SearchDialog";
4
+ import "@barodoc/theme-docs/styles/global.css";
5
+
6
+ interface Props {
7
+ title: string;
8
+ description?: string;
9
+ }
10
+
11
+ const { title, description = "Documentation" } = Astro.props;
12
+ ---
13
+
14
+ <!doctype html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="UTF-8" />
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
19
+ <meta name="description" content={description} />
20
+ <link rel="icon" type="image/x-icon" href="/favicon.ico" />
21
+ <title>{title}</title>
22
+ <ThemeScript />
23
+ </head>
24
+ <body class="min-h-screen bg-[var(--color-bg)] text-[var(--color-text)] antialiased">
25
+ <slot />
26
+ <SearchDialog client:load />
27
+ </body>
28
+ </html>
@@ -0,0 +1,121 @@
1
+ ---
2
+ import BaseLayout from "./BaseLayout.astro";
3
+ import Header from "../components/Header.astro";
4
+ import Sidebar from "../components/Sidebar.astro";
5
+ import TableOfContents from "../components/TableOfContents.astro";
6
+ import MobileNav from "../components/MobileNav.astro";
7
+ import CodeCopy from "../components/CodeCopy.astro";
8
+ import { defaultLocale } from "virtual:barodoc/i18n";
9
+ import { getLocaleFromPath } from "@barodoc/core";
10
+
11
+ interface PageLink {
12
+ title: string;
13
+ href: string;
14
+ }
15
+
16
+ interface Props {
17
+ title: string;
18
+ description?: string;
19
+ headings?: { depth: number; slug: string; text: string }[];
20
+ prevPage?: PageLink | null;
21
+ nextPage?: PageLink | null;
22
+ }
23
+
24
+ const { title, description, headings = [], prevPage, nextPage } = Astro.props;
25
+ const currentPath = Astro.url.pathname;
26
+
27
+ // Get locale from path
28
+ const i18nConfig = { defaultLocale, locales: [defaultLocale] };
29
+ const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
30
+ ---
31
+
32
+ <BaseLayout title={title} description={description}>
33
+ <Header currentLocale={currentLocale} />
34
+
35
+ <!-- Centered container for docs layout -->
36
+ <div class="w-full min-h-[calc(100vh-3.5rem)] flex justify-center">
37
+ <div class="flex w-full max-w-[1120px]">
38
+ <!-- Desktop Sidebar -->
39
+ <aside class="hidden lg:block w-[220px] shrink-0">
40
+ <div class="sticky top-14 h-[calc(100vh-3.5rem)] overflow-y-auto py-6 pr-4">
41
+ <Sidebar currentPath={currentPath} currentLocale={currentLocale} />
42
+ </div>
43
+ </aside>
44
+
45
+ <!-- Main Content -->
46
+ <main class="flex-1 min-w-0 min-w-[650px] max-w-[720px]">
47
+ <div class="px-8 py-8">
48
+ <article class="prose prose-gray dark:prose-invert max-w-none">
49
+ <slot />
50
+ </article>
51
+
52
+ <!-- Page Navigation -->
53
+ {(prevPage || nextPage) && (
54
+ <nav class="mt-8 pt-6 border-t border-[var(--color-border)]" aria-label="Page navigation">
55
+ <div class="grid grid-cols-2 gap-4">
56
+ <div class="col-span-1">
57
+ {prevPage && (
58
+ <a
59
+ href={prevPage.href}
60
+ class="group flex flex-col gap-1 p-3 rounded-lg border border-[var(--color-border)] hover:border-primary-300 dark:hover:border-primary-700 hover:bg-[var(--color-bg-secondary)] transition-all"
61
+ >
62
+ <span class="text-xs text-[var(--color-text-muted)] flex items-center gap-1">
63
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
64
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
65
+ </svg>
66
+ Previous
67
+ </span>
68
+ <span class="text-sm font-medium text-[var(--color-text)] group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors">
69
+ {prevPage.title}
70
+ </span>
71
+ </a>
72
+ )}
73
+ </div>
74
+ <div class="col-span-1">
75
+ {nextPage && (
76
+ <a
77
+ href={nextPage.href}
78
+ class="group flex flex-col gap-1 p-3 rounded-lg border border-[var(--color-border)] hover:border-primary-300 dark:hover:border-primary-700 hover:bg-[var(--color-bg-secondary)] transition-all text-right"
79
+ >
80
+ <span class="text-xs text-[var(--color-text-muted)] flex items-center gap-1 justify-end">
81
+ Next
82
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
83
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
84
+ </svg>
85
+ </span>
86
+ <span class="text-sm font-medium text-[var(--color-text)] group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors">
87
+ {nextPage.title}
88
+ </span>
89
+ </a>
90
+ )}
91
+ </div>
92
+ </div>
93
+ </nav>
94
+ )}
95
+
96
+ <!-- Page footer -->
97
+ <footer class="mt-6 pt-4 border-t border-[var(--color-border-light)]">
98
+ <p class="text-sm text-[var(--color-text-muted)]">
99
+ Was this page helpful? <a href="#" class="text-primary-600 dark:text-primary-400 hover:underline">Give us feedback</a>
100
+ </p>
101
+ </footer>
102
+ </div>
103
+ </main>
104
+
105
+ <!-- Table of Contents -->
106
+ {headings.length > 0 && (
107
+ <aside class="hidden xl:block w-[180px] shrink-0">
108
+ <div class="sticky top-14 h-[calc(100vh-3.5rem)] overflow-y-auto py-6 pl-6">
109
+ <TableOfContents headings={headings} />
110
+ </div>
111
+ </aside>
112
+ )}
113
+ </div>
114
+ </div>
115
+
116
+ <!-- Mobile Navigation -->
117
+ <MobileNav currentPath={currentPath} currentLocale={currentLocale} />
118
+
119
+ <!-- Code copy functionality -->
120
+ <CodeCopy />
121
+ </BaseLayout>
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,116 @@
1
+ ---
2
+ import { getCollection } from "astro:content";
3
+ import DocsLayout from "../../layouts/DocsLayout.astro";
4
+ import config from "virtual:barodoc/config";
5
+ import { defaultLocale, locales } from "virtual:barodoc/i18n";
6
+ import { getLocalizedNavGroup } from "@barodoc/core";
7
+
8
+ export async function getStaticPaths() {
9
+ const docs = await getCollection("docs");
10
+
11
+ return docs.map((doc) => {
12
+ // Handle locale in slug
13
+ const slugParts = doc.slug.split("/");
14
+ const hasLocale = locales.includes(slugParts[0]);
15
+
16
+ let locale = defaultLocale;
17
+ let cleanSlug = doc.slug;
18
+
19
+ if (hasLocale) {
20
+ locale = slugParts[0];
21
+ cleanSlug = slugParts.slice(1).join("/");
22
+ }
23
+
24
+ // Build the path
25
+ const path = locale === defaultLocale
26
+ ? cleanSlug
27
+ : `${locale}/${cleanSlug}`;
28
+
29
+ return {
30
+ params: { slug: path || undefined },
31
+ props: { doc, locale, cleanSlug },
32
+ };
33
+ });
34
+ }
35
+
36
+ interface Props {
37
+ doc: Awaited<ReturnType<typeof getCollection<"docs">>>[number];
38
+ locale: string;
39
+ cleanSlug: string;
40
+ }
41
+
42
+ const { doc, locale, cleanSlug } = Astro.props;
43
+ const { Content, headings } = await doc.render();
44
+
45
+ // Find the category (navigation group) for this page
46
+ function findCategory(slug: string): string | null {
47
+ for (const group of config.navigation) {
48
+ if (group.pages.includes(slug)) {
49
+ return getLocalizedNavGroup(group, locale, defaultLocale);
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+
55
+ // Get all pages in order from navigation
56
+ function getAllPages(): string[] {
57
+ const pages: string[] = [];
58
+ for (const group of config.navigation) {
59
+ pages.push(...group.pages);
60
+ }
61
+ return pages;
62
+ }
63
+
64
+ // Get page title from slug
65
+ function getPageTitle(slug: string): string {
66
+ const parts = slug.split('/');
67
+ const name = parts[parts.length - 1];
68
+ return name
69
+ .split('-')
70
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
71
+ .join(' ');
72
+ }
73
+
74
+ // Get page href
75
+ function getPageHref(slug: string): string {
76
+ if (locale === defaultLocale) {
77
+ return `/docs/${slug}`;
78
+ }
79
+ return `/${locale}/docs/${slug}`;
80
+ }
81
+
82
+ // Find prev/next pages
83
+ const allPages = getAllPages();
84
+ const currentIndex = allPages.indexOf(cleanSlug);
85
+
86
+ const prevPage = currentIndex > 0
87
+ ? { title: getPageTitle(allPages[currentIndex - 1]), href: getPageHref(allPages[currentIndex - 1]) }
88
+ : null;
89
+
90
+ const nextPage = currentIndex < allPages.length - 1
91
+ ? { title: getPageTitle(allPages[currentIndex + 1]), href: getPageHref(allPages[currentIndex + 1]) }
92
+ : null;
93
+
94
+ const category = findCategory(cleanSlug);
95
+ ---
96
+
97
+ <DocsLayout
98
+ title={doc.data.title}
99
+ description={doc.data.description}
100
+ headings={headings}
101
+ prevPage={prevPage}
102
+ nextPage={nextPage}
103
+ >
104
+ {category && (
105
+ <p class="text-xs font-medium uppercase tracking-wider text-primary-600 dark:text-primary-400 mb-2">
106
+ {category}
107
+ </p>
108
+ )}
109
+ <h1>{doc.data.title}</h1>
110
+ {doc.data.description && (
111
+ <p class="lead text-sm text-[var(--color-text-secondary)] mt-1 mb-4">
112
+ {doc.data.description}
113
+ </p>
114
+ )}
115
+ <Content />
116
+ </DocsLayout>