@barodoc/theme-docs 3.0.0 → 6.0.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.
Files changed (63) hide show
  1. package/package.json +6 -2
  2. package/src/components/Breadcrumb.astro +8 -8
  3. package/src/components/CodeCopy.astro +14 -13
  4. package/src/components/Contributors.astro +71 -0
  5. package/src/components/DocHeader.tsx +24 -24
  6. package/src/components/DocsSidebar.tsx +11 -15
  7. package/src/components/KeyboardShortcuts.astro +108 -0
  8. package/src/components/LanguageSwitcher.astro +3 -3
  9. package/src/components/MobileNavSheet.tsx +1 -1
  10. package/src/components/Search.tsx +10 -10
  11. package/src/components/SearchDialog.tsx +8 -8
  12. package/src/components/TableOfContents.astro +5 -5
  13. package/src/components/ThemeToggle.tsx +4 -4
  14. package/src/components/VersionSwitcher.tsx +79 -0
  15. package/src/components/api/ApiEndpoint.astro +5 -5
  16. package/src/components/api/ApiParam.astro +4 -4
  17. package/src/components/api/ApiParams.astro +3 -3
  18. package/src/components/api/ApiResponse.astro +2 -2
  19. package/src/components/index.ts +5 -0
  20. package/src/components/mdx/Accordion.tsx +6 -6
  21. package/src/components/mdx/ApiPlayground.tsx +200 -0
  22. package/src/components/mdx/Badge.tsx +1 -1
  23. package/src/components/mdx/Callout.astro +20 -20
  24. package/src/components/mdx/Card.astro +5 -5
  25. package/src/components/mdx/CodeGroup.astro +23 -20
  26. package/src/components/mdx/CodeGroup.tsx +6 -6
  27. package/src/components/mdx/DocAccordion.tsx +5 -5
  28. package/src/components/mdx/DocCard.tsx +2 -2
  29. package/src/components/mdx/DocTabs.tsx +3 -3
  30. package/src/components/mdx/Expandable.tsx +11 -11
  31. package/src/components/mdx/FileTree.tsx +6 -6
  32. package/src/components/mdx/Frame.tsx +2 -2
  33. package/src/components/mdx/ImageZoom.tsx +35 -0
  34. package/src/components/mdx/Mermaid.tsx +3 -3
  35. package/src/components/mdx/ParamField.tsx +7 -7
  36. package/src/components/mdx/ResponseField.tsx +6 -6
  37. package/src/components/mdx/Step.astro +2 -2
  38. package/src/components/mdx/Steps.astro +3 -3
  39. package/src/components/mdx/Steps.tsx +10 -19
  40. package/src/components/mdx/Tabs.tsx +5 -8
  41. package/src/components/mdx/Tooltip.tsx +20 -19
  42. package/src/components/mdx/Video.tsx +71 -0
  43. package/src/components/ui/accordion.tsx +2 -2
  44. package/src/components/ui/alert.tsx +1 -1
  45. package/src/components/ui/button.tsx +3 -3
  46. package/src/components/ui/card.tsx +2 -2
  47. package/src/components/ui/dialog.tsx +2 -2
  48. package/src/components/ui/scroll-area.tsx +1 -1
  49. package/src/components/ui/separator.tsx +1 -1
  50. package/src/components/ui/sheet.tsx +3 -3
  51. package/src/components/ui/tabs.tsx +2 -2
  52. package/src/components/ui/tooltip.tsx +1 -1
  53. package/src/index.ts +33 -1
  54. package/src/layouts/BaseLayout.astro +10 -1
  55. package/src/layouts/BlogLayout.astro +93 -0
  56. package/src/layouts/DocsLayout.astro +72 -23
  57. package/src/pages/404.astro +5 -5
  58. package/src/pages/blog/[...slug].astro +39 -0
  59. package/src/pages/blog/index.astro +92 -0
  60. package/src/pages/changelog/index.astro +72 -0
  61. package/src/pages/docs/[...slug].astro +6 -2
  62. package/src/pages/index.astro +21 -21
  63. package/src/styles/global.css +1041 -166
@@ -1,6 +1,5 @@
1
1
  import * as React from "react";
2
2
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
- import { cn } from "../../lib/utils.js";
4
3
 
5
4
  interface TooltipProps {
6
5
  children: React.ReactNode;
@@ -8,28 +7,30 @@ interface TooltipProps {
8
7
  }
9
8
 
10
9
  export function Tooltip({ children, tip }: TooltipProps) {
10
+ const [container, setContainer] = React.useState<HTMLElement | null>(null);
11
+
12
+ React.useEffect(() => {
13
+ setContainer(document.body);
14
+ }, []);
15
+
11
16
  return (
12
- <TooltipPrimitive.Provider delayDuration={200}>
17
+ <TooltipPrimitive.Provider delayDuration={150}>
13
18
  <TooltipPrimitive.Root>
14
19
  <TooltipPrimitive.Trigger asChild>
15
- <span className="cursor-help border-b border-dashed border-[var(--color-text-muted)] text-[var(--color-text)]">
16
- {children}
17
- </span>
20
+ <span className="bd-tooltip-trigger">{children}</span>
18
21
  </TooltipPrimitive.Trigger>
19
- <TooltipPrimitive.Portal>
20
- <TooltipPrimitive.Content
21
- className={cn(
22
- "z-50 overflow-hidden rounded-md bg-[var(--color-bg-tertiary)] px-3 py-1.5 text-sm text-[var(--color-text)] shadow-md",
23
- "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",
24
- "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",
25
- "max-w-xs"
26
- )}
27
- sideOffset={4}
28
- >
29
- {tip}
30
- <TooltipPrimitive.Arrow className="fill-[var(--color-bg-tertiary)]" />
31
- </TooltipPrimitive.Content>
32
- </TooltipPrimitive.Portal>
22
+ {container && (
23
+ <TooltipPrimitive.Portal container={container}>
24
+ <TooltipPrimitive.Content
25
+ className="bd-tooltip-content"
26
+ sideOffset={6}
27
+ style={{ zIndex: 99999 }}
28
+ >
29
+ {tip}
30
+ <TooltipPrimitive.Arrow className="bd-tooltip-arrow" />
31
+ </TooltipPrimitive.Content>
32
+ </TooltipPrimitive.Portal>
33
+ )}
33
34
  </TooltipPrimitive.Root>
34
35
  </TooltipPrimitive.Provider>
35
36
  );
@@ -0,0 +1,71 @@
1
+ import * as React from "react";
2
+ import { cn } from "../../lib/utils.js";
3
+
4
+ interface VideoProps {
5
+ url: string;
6
+ title?: string;
7
+ caption?: string;
8
+ className?: string;
9
+ }
10
+
11
+ function parseVideoUrl(url: string): { provider: string; embedUrl: string } | null {
12
+ // YouTube: youtube.com/watch?v=ID, youtu.be/ID, youtube.com/embed/ID
13
+ const ytMatch = url.match(
14
+ /(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
15
+ );
16
+ if (ytMatch) {
17
+ return {
18
+ provider: "youtube",
19
+ embedUrl: `https://www.youtube-nocookie.com/embed/${ytMatch[1]}?rel=0`,
20
+ };
21
+ }
22
+
23
+ // Vimeo: vimeo.com/ID, player.vimeo.com/video/ID
24
+ const vimeoMatch = url.match(/(?:vimeo\.com\/|player\.vimeo\.com\/video\/)(\d+)/);
25
+ if (vimeoMatch) {
26
+ return {
27
+ provider: "vimeo",
28
+ embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}?dnt=1`,
29
+ };
30
+ }
31
+
32
+ // Loom: loom.com/share/ID, loom.com/embed/ID
33
+ const loomMatch = url.match(/loom\.com\/(?:share|embed)\/([a-f0-9]+)/);
34
+ if (loomMatch) {
35
+ return {
36
+ provider: "loom",
37
+ embedUrl: `https://www.loom.com/embed/${loomMatch[1]}`,
38
+ };
39
+ }
40
+
41
+ return null;
42
+ }
43
+
44
+ export function Video({ url, title, caption, className }: VideoProps) {
45
+ const parsed = parseVideoUrl(url);
46
+
47
+ if (!parsed) {
48
+ return (
49
+ <div className={cn("bd-video", className)} style={{ paddingTop: 0, padding: "2rem" }}>
50
+ <p style={{ color: "var(--bd-text-muted)", margin: 0, textAlign: "center" }}>
51
+ Unsupported video URL: {url}
52
+ </p>
53
+ </div>
54
+ );
55
+ }
56
+
57
+ return (
58
+ <figure className={className}>
59
+ <div className="bd-video">
60
+ <iframe
61
+ src={parsed.embedUrl}
62
+ title={title || `${parsed.provider} video`}
63
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
64
+ allowFullScreen
65
+ loading="lazy"
66
+ />
67
+ </div>
68
+ {caption && <figcaption className="bd-video-caption">{caption}</figcaption>}
69
+ </figure>
70
+ );
71
+ }
@@ -11,7 +11,7 @@ const AccordionItem = React.forwardRef<
11
11
  >(({ className, ...props }, ref) => (
12
12
  <AccordionPrimitive.Item
13
13
  ref={ref}
14
- className={cn("border-b border-[var(--color-border)]", className)}
14
+ className={cn("border-b border-[var(--bd-border)]", className)}
15
15
  {...props}
16
16
  />
17
17
  ));
@@ -31,7 +31,7 @@ const AccordionTrigger = React.forwardRef<
31
31
  {...props}
32
32
  >
33
33
  {children}
34
- <ChevronDown className="h-4 w-4 shrink-0 text-[var(--color-text-secondary)] transition-transform duration-200" />
34
+ <ChevronDown className="h-4 w-4 shrink-0 text-[var(--bd-text-secondary)] transition-transform duration-200" />
35
35
  </AccordionPrimitive.Trigger>
36
36
  </AccordionPrimitive.Header>
37
37
  ));
@@ -7,7 +7,7 @@ const alertVariants = cva(
7
7
  {
8
8
  variants: {
9
9
  variant: {
10
- default: "bg-[var(--color-bg-secondary)] border-l-[var(--color-border)] text-[var(--color-text)]",
10
+ default: "bg-[var(--bd-bg-subtle)] border-l-[var(--bd-border)] text-[var(--bd-text)]",
11
11
  info: "bg-blue-50/70 dark:bg-blue-950/30 border-l-blue-500 text-blue-900 dark:text-blue-100 [&>svg]:text-blue-600",
12
12
  warning: "bg-orange-50/70 dark:bg-orange-950/30 border-l-orange-500 text-orange-900 dark:text-orange-100 [&>svg]:text-orange-600",
13
13
  success: "bg-green-50/70 dark:bg-green-950/30 border-l-green-500 text-green-900 dark:text-green-100 [&>svg]:text-green-600",
@@ -13,11 +13,11 @@ const buttonVariants = cva(
13
13
  destructive:
14
14
  "bg-red-600 text-white shadow-sm hover:bg-red-700",
15
15
  outline:
16
- "border border-[var(--color-border)] bg-transparent shadow-sm hover:bg-[var(--color-bg-secondary)] hover:text-[var(--color-text)]",
16
+ "border border-[var(--bd-border)] bg-transparent shadow-sm hover:bg-[var(--bd-bg-subtle)] hover:text-[var(--bd-text)]",
17
17
  secondary:
18
- "bg-[var(--color-bg-secondary)] text-[var(--color-text)] shadow-sm hover:bg-[var(--color-bg-tertiary)]",
18
+ "bg-[var(--bd-bg-subtle)] text-[var(--bd-text)] shadow-sm hover:bg-[var(--bd-bg-muted)]",
19
19
  ghost:
20
- "hover:bg-[var(--color-bg-secondary)] hover:text-[var(--color-text)]",
20
+ "hover:bg-[var(--bd-bg-subtle)] hover:text-[var(--bd-text)]",
21
21
  link: "text-primary-600 underline-offset-4 hover:underline",
22
22
  },
23
23
  size: {
@@ -8,7 +8,7 @@ const Card = React.forwardRef<
8
8
  <div
9
9
  ref={ref}
10
10
  className={cn(
11
- "rounded-xl border border-[var(--color-border)] bg-[var(--color-bg)] text-[var(--color-text)] shadow-sm",
11
+ "rounded-xl border border-[var(--bd-border)] bg-[var(--bd-bg)] text-[var(--bd-text)] shadow-sm",
12
12
  className
13
13
  )}
14
14
  {...props}
@@ -46,7 +46,7 @@ const CardDescription = React.forwardRef<
46
46
  >(({ className, ...props }, ref) => (
47
47
  <div
48
48
  ref={ref}
49
- className={cn("text-sm text-[var(--color-text-secondary)]", className)}
49
+ className={cn("text-sm text-[var(--bd-text-secondary)]", className)}
50
50
  {...props}
51
51
  />
52
52
  ));
@@ -35,7 +35,7 @@ const DialogContent = React.forwardRef<
35
35
  <DialogPrimitive.Content
36
36
  ref={ref}
37
37
  className={cn(
38
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-[var(--color-border)] bg-[var(--color-bg)] p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
38
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-[var(--bd-border)] bg-[var(--bd-bg)] p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
39
39
  className
40
40
  )}
41
41
  {...props}
@@ -99,7 +99,7 @@ const DialogDescription = React.forwardRef<
99
99
  >(({ className, ...props }, ref) => (
100
100
  <DialogPrimitive.Description
101
101
  ref={ref}
102
- className={cn("text-sm text-[var(--color-text-secondary)]", className)}
102
+ className={cn("text-sm text-[var(--bd-text-secondary)]", className)}
103
103
  {...props}
104
104
  />
105
105
  ));
@@ -37,7 +37,7 @@ const ScrollBar = React.forwardRef<
37
37
  )}
38
38
  {...props}
39
39
  >
40
- <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-[var(--color-border)]" />
40
+ <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-[var(--bd-border)]" />
41
41
  </ScrollAreaPrimitive.ScrollAreaScrollbar>
42
42
  ));
43
43
  ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
@@ -15,7 +15,7 @@ const Separator = React.forwardRef<
15
15
  decorative={decorative}
16
16
  orientation={orientation}
17
17
  className={cn(
18
- "shrink-0 bg-[var(--color-border)]",
18
+ "shrink-0 bg-[var(--bd-border)]",
19
19
  orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
20
20
  className
21
21
  )}
@@ -28,7 +28,7 @@ const SheetOverlay = React.forwardRef<
28
28
  SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
29
29
 
30
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",
31
+ "fixed z-50 gap-4 bg-[var(--bd-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
32
  {
33
33
  variants: {
34
34
  side: {
@@ -105,7 +105,7 @@ const SheetTitle = React.forwardRef<
105
105
  >(({ className, ...props }, ref) => (
106
106
  <SheetPrimitive.Title
107
107
  ref={ref}
108
- className={cn("text-lg font-semibold text-[var(--color-text)]", className)}
108
+ className={cn("text-lg font-semibold text-[var(--bd-text)]", className)}
109
109
  {...props}
110
110
  />
111
111
  ));
@@ -117,7 +117,7 @@ const SheetDescription = React.forwardRef<
117
117
  >(({ className, ...props }, ref) => (
118
118
  <SheetPrimitive.Description
119
119
  ref={ref}
120
- className={cn("text-sm text-[var(--color-text-secondary)]", className)}
120
+ className={cn("text-sm text-[var(--bd-text-secondary)]", className)}
121
121
  {...props}
122
122
  />
123
123
  ));
@@ -11,7 +11,7 @@ const TabsList = React.forwardRef<
11
11
  <TabsPrimitive.List
12
12
  ref={ref}
13
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)]",
14
+ "inline-flex h-10 items-center justify-center rounded-lg bg-[var(--bd-bg-subtle)] p-1 text-[var(--bd-text-secondary)]",
15
15
  className
16
16
  )}
17
17
  {...props}
@@ -26,7 +26,7 @@ const TabsTrigger = React.forwardRef<
26
26
  <TabsPrimitive.Trigger
27
27
  ref={ref}
28
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",
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(--bd-bg)] data-[state=active]:text-[var(--bd-text)] data-[state=active]:shadow",
30
30
  className
31
31
  )}
32
32
  {...props}
@@ -17,7 +17,7 @@ const TooltipContent = React.forwardRef<
17
17
  ref={ref}
18
18
  sideOffset={sideOffset}
19
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",
20
+ "z-50 overflow-hidden rounded-md bg-[var(--bd-text)] px-3 py-1.5 text-xs text-[var(--bd-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
21
  className
22
22
  )}
23
23
  {...props}
package/src/index.ts CHANGED
@@ -3,6 +3,8 @@ import type { ThemeExport, ResolvedBarodocConfig } from "@barodoc/core";
3
3
  import mdx from "@astrojs/mdx";
4
4
  import react from "@astrojs/react";
5
5
  import tailwindcss from "@tailwindcss/vite";
6
+ import remarkMath from "remark-math";
7
+ import rehypeKatex from "rehype-katex";
6
8
 
7
9
  export interface DocsThemeOptions {
8
10
  customCss?: string[];
@@ -79,11 +81,41 @@ function createThemeIntegration(
79
81
  entrypoint: "@barodoc/theme-docs/pages/docs/[...slug].astro",
80
82
  });
81
83
 
84
+ // Blog routes
85
+ if (config?.blog?.enabled !== false) {
86
+ injectRoute({
87
+ pattern: "/blog",
88
+ entrypoint: "@barodoc/theme-docs/pages/blog/index.astro",
89
+ });
90
+ injectRoute({
91
+ pattern: "/blog/[...slug]",
92
+ entrypoint: "@barodoc/theme-docs/pages/blog/[...slug].astro",
93
+ });
94
+ }
95
+
96
+ // Changelog route
97
+ injectRoute({
98
+ pattern: "/changelog",
99
+ entrypoint: "@barodoc/theme-docs/pages/changelog/index.astro",
100
+ });
101
+
82
102
  // Update Astro config with integrations and Vite plugins
83
103
  updateConfig({
84
- integrations: [mdx(), react()],
104
+ integrations: [
105
+ mdx({
106
+ remarkPlugins: [remarkMath],
107
+ rehypePlugins: [rehypeKatex],
108
+ }),
109
+ react(),
110
+ ],
85
111
  vite: {
86
112
  plugins: [tailwindcss()],
113
+ optimizeDeps: {
114
+ include: ["mermaid"],
115
+ },
116
+ ssr: {
117
+ noExternal: [],
118
+ },
87
119
  resolve: {
88
120
  dedupe: ["react", "react-dom"],
89
121
  },
@@ -1,7 +1,9 @@
1
1
  ---
2
2
  import ThemeScript from "../components/ThemeScript.astro";
3
3
  import { SearchDialog } from "../components/SearchDialog";
4
+ import { ClientRouter } from "astro:transitions";
4
5
  import config from "virtual:barodoc/config";
6
+ import { generateThemeCSS } from "@barodoc/core";
5
7
  import "@barodoc/theme-docs/styles/global.css";
6
8
 
7
9
  interface Props {
@@ -16,6 +18,8 @@ const canonicalUrl = config.site ? new URL(Astro.url.pathname, config.site).href
16
18
  const ogImageUrl = ogImage
17
19
  ? (config.site ? new URL(ogImage, config.site).href : ogImage)
18
20
  : undefined;
21
+
22
+ const themeCSS = config.theme?.colors ? generateThemeCSS(config.theme.colors) : "";
19
23
  ---
20
24
 
21
25
  <!doctype html>
@@ -41,10 +45,15 @@ const ogImageUrl = ogImage
41
45
  <meta name="twitter:description" content={description} />
42
46
  {ogImageUrl && <meta name="twitter:image" content={ogImageUrl} />}
43
47
 
48
+ <!-- KaTeX math rendering -->
49
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css" crossorigin="anonymous" />
50
+
44
51
  <title>{title}</title>
45
52
  <ThemeScript />
53
+ <ClientRouter />
54
+ {themeCSS && <style set:html={themeCSS} />}
46
55
  </head>
47
- <body class="min-h-screen bg-[var(--color-bg)] text-[var(--color-text)] antialiased">
56
+ <body class="min-h-screen bg-[var(--bd-bg)] text-[var(--bd-text)] antialiased">
48
57
  <slot />
49
58
  <SearchDialog client:load />
50
59
  </body>
@@ -0,0 +1,93 @@
1
+ ---
2
+ import BaseLayout from "./BaseLayout.astro";
3
+ import Header from "../components/Header.astro";
4
+ import Banner from "../components/Banner.astro";
5
+ import { defaultLocale } from "virtual:barodoc/i18n";
6
+ import { getLocaleFromPath } from "@barodoc/core";
7
+
8
+ interface Props {
9
+ title: string;
10
+ description?: string;
11
+ date?: Date;
12
+ author?: string;
13
+ image?: string;
14
+ tags?: string[];
15
+ readingTime?: string;
16
+ }
17
+
18
+ const { title, description, date, author, image, tags = [], readingTime } = Astro.props;
19
+ const currentPath = Astro.url.pathname;
20
+ const i18nConfig = { defaultLocale, locales: [defaultLocale] };
21
+ const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
22
+ ---
23
+
24
+ <BaseLayout title={title} description={description} ogImage={image}>
25
+ <Banner />
26
+ <Header currentLocale={currentLocale} currentPath={currentPath} />
27
+
28
+ <div class="w-full min-w-0 min-h-[calc(100vh-3.5rem)] flex justify-center">
29
+ <article class="w-full max-w-[720px] px-4 py-10 sm:px-6 lg:px-8">
30
+ <!-- Blog post header -->
31
+ <header class="mb-10">
32
+ {tags.length > 0 && (
33
+ <div class="flex flex-wrap gap-2 mb-4">
34
+ {tags.map((tag) => (
35
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-50 text-primary-700 dark:bg-primary-950 dark:text-primary-300">
36
+ {tag}
37
+ </span>
38
+ ))}
39
+ </div>
40
+ )}
41
+ <h1 class="text-3xl sm:text-4xl font-bold tracking-tight text-[var(--bd-text-heading)] leading-tight">
42
+ {title}
43
+ </h1>
44
+ {description && (
45
+ <p class="mt-3 text-lg text-[var(--bd-text-secondary)] leading-relaxed">
46
+ {description}
47
+ </p>
48
+ )}
49
+ <div class="mt-4 flex items-center gap-3 text-sm text-[var(--bd-text-muted)]">
50
+ {author && <span>{author}</span>}
51
+ {author && date && <span class="text-[var(--bd-border)]">&middot;</span>}
52
+ {date && (
53
+ <time datetime={date.toISOString()}>
54
+ {date.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })}
55
+ </time>
56
+ )}
57
+ {readingTime && (
58
+ <>
59
+ <span class="text-[var(--bd-border)]">&middot;</span>
60
+ <span>{readingTime}</span>
61
+ </>
62
+ )}
63
+ </div>
64
+ </header>
65
+
66
+ {image && (
67
+ <img
68
+ src={image}
69
+ alt={title}
70
+ class="w-full rounded-lg mb-10 bd-no-zoom"
71
+ loading="eager"
72
+ />
73
+ )}
74
+
75
+ <div class="prose prose-gray dark:prose-invert max-w-none">
76
+ <slot />
77
+ </div>
78
+
79
+ <!-- Back to blog -->
80
+ <div class="mt-12 pt-8 border-t border-[var(--bd-border)]">
81
+ <a
82
+ href="/blog"
83
+ class="inline-flex items-center gap-1.5 text-sm text-[var(--bd-text-secondary)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
84
+ >
85
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
86
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
87
+ </svg>
88
+ Back to Blog
89
+ </a>
90
+ </div>
91
+ </article>
92
+ </div>
93
+ </BaseLayout>