@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.
- package/LICENSE +21 -0
- package/package.json +68 -0
- package/src/components/CodeCopy.astro +173 -0
- package/src/components/DocHeader.tsx +166 -0
- package/src/components/DocsSidebar.tsx +84 -0
- package/src/components/Header.astro +32 -0
- package/src/components/LanguageSwitcher.astro +77 -0
- package/src/components/MobileNav.astro +61 -0
- package/src/components/MobileNavSheet.tsx +73 -0
- package/src/components/Search.tsx +210 -0
- package/src/components/SearchDialog.tsx +83 -0
- package/src/components/Sidebar.astro +56 -0
- package/src/components/SidebarWrapper.tsx +24 -0
- package/src/components/TableOfContents.astro +98 -0
- package/src/components/ThemeScript.astro +11 -0
- package/src/components/ThemeToggle.tsx +57 -0
- package/src/components/api/ApiEndpoint.astro +36 -0
- package/src/components/api/ApiParam.astro +26 -0
- package/src/components/api/ApiParams.astro +16 -0
- package/src/components/api/ApiResponse.astro +35 -0
- package/src/components/index.ts +30 -0
- package/src/components/mdx/Accordion.tsx +61 -0
- package/src/components/mdx/Badge.tsx +33 -0
- package/src/components/mdx/Callout.astro +79 -0
- package/src/components/mdx/Card.astro +66 -0
- package/src/components/mdx/CardGroup.astro +18 -0
- package/src/components/mdx/CodeGroup.astro +63 -0
- package/src/components/mdx/CodeGroup.tsx +51 -0
- package/src/components/mdx/Columns.tsx +31 -0
- package/src/components/mdx/DocAccordion.tsx +87 -0
- package/src/components/mdx/DocCallout.tsx +65 -0
- package/src/components/mdx/DocCard.tsx +70 -0
- package/src/components/mdx/DocTabs.tsx +48 -0
- package/src/components/mdx/Expandable.tsx +107 -0
- package/src/components/mdx/FileTree.tsx +72 -0
- package/src/components/mdx/Frame.tsx +23 -0
- package/src/components/mdx/Icon.tsx +59 -0
- package/src/components/mdx/Mermaid.tsx +94 -0
- package/src/components/mdx/ParamField.tsx +76 -0
- package/src/components/mdx/ResponseField.tsx +62 -0
- package/src/components/mdx/Step.astro +14 -0
- package/src/components/mdx/Steps.astro +37 -0
- package/src/components/mdx/Steps.tsx +49 -0
- package/src/components/mdx/Tabs.tsx +67 -0
- package/src/components/mdx/Tooltip.tsx +36 -0
- package/src/components/ui/accordion.tsx +54 -0
- package/src/components/ui/alert.tsx +60 -0
- package/src/components/ui/button.tsx +57 -0
- package/src/components/ui/card.tsx +75 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/dialog.tsx +119 -0
- package/src/components/ui/index.ts +11 -0
- package/src/components/ui/scroll-area.tsx +45 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +137 -0
- package/src/components/ui/tabs.tsx +52 -0
- package/src/components/ui/tooltip.tsx +29 -0
- package/src/index.ts +74 -0
- package/src/layouts/BaseLayout.astro +28 -0
- package/src/layouts/DocsLayout.astro +121 -0
- package/src/lib/utils.ts +6 -0
- package/src/pages/docs/[...slug].astro +116 -0
- package/src/pages/index.astro +217 -0
- package/src/styles/global.css +342 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Barocss
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@barodoc/theme-docs",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Documentation theme for Barodoc",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts",
|
|
8
|
+
"./components": "./src/components/index.ts",
|
|
9
|
+
"./components/*": "./src/components/*",
|
|
10
|
+
"./layouts/*": "./src/layouts/*",
|
|
11
|
+
"./pages/*": "./src/pages/*",
|
|
12
|
+
"./styles/*": "./src/styles/*",
|
|
13
|
+
"./lib/*": "./src/lib/*"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@radix-ui/react-accordion": "^1.2.12",
|
|
20
|
+
"@radix-ui/react-collapsible": "^1.1.12",
|
|
21
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
22
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
23
|
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
24
|
+
"@radix-ui/react-separator": "^1.1.8",
|
|
25
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
26
|
+
"@radix-ui/react-tabs": "^1.1.13",
|
|
27
|
+
"@radix-ui/react-tooltip": "^1.2.8",
|
|
28
|
+
"class-variance-authority": "^0.7.1",
|
|
29
|
+
"clsx": "^2.1.1",
|
|
30
|
+
"lucide-react": "^0.563.0",
|
|
31
|
+
"mermaid": "^11.12.2",
|
|
32
|
+
"tailwind-merge": "^3.4.0",
|
|
33
|
+
"@barodoc/core": "0.0.1"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"astro": "^5.0.0",
|
|
37
|
+
"react": "^19.0.0",
|
|
38
|
+
"react-dom": "^19.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@astrojs/mdx": "^4.0.0",
|
|
42
|
+
"@astrojs/react": "^4.0.0",
|
|
43
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
44
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
45
|
+
"@types/node": "^22.0.0",
|
|
46
|
+
"@types/react": "^19.0.0",
|
|
47
|
+
"@types/react-dom": "^19.0.0",
|
|
48
|
+
"astro": "^5.0.0",
|
|
49
|
+
"tailwindcss": "^4.0.0",
|
|
50
|
+
"typescript": "^5.7.0"
|
|
51
|
+
},
|
|
52
|
+
"keywords": [
|
|
53
|
+
"astro",
|
|
54
|
+
"astro-integration",
|
|
55
|
+
"documentation",
|
|
56
|
+
"barodoc",
|
|
57
|
+
"theme"
|
|
58
|
+
],
|
|
59
|
+
"license": "MIT",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/barocss/barodoc.git",
|
|
63
|
+
"directory": "packages/theme-docs"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"typecheck": "tsc --noEmit"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
// This component adds copy functionality and filename display to code blocks
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
function initCodeCopy() {
|
|
7
|
+
document.querySelectorAll('pre').forEach((pre) => {
|
|
8
|
+
// Skip if already processed
|
|
9
|
+
if (pre.closest('.code-block-wrapper')) return;
|
|
10
|
+
|
|
11
|
+
const code = pre.querySelector('code');
|
|
12
|
+
if (!code) return;
|
|
13
|
+
|
|
14
|
+
// Get filename from data attribute or class (e.g., language-js:filename.js)
|
|
15
|
+
let filename = pre.getAttribute('data-filename') || '';
|
|
16
|
+
const langClass = code.className.match(/language-(\S+)/);
|
|
17
|
+
const lang = langClass ? langClass[1].split(':')[0] : '';
|
|
18
|
+
|
|
19
|
+
// Check if filename is in the language class (e.g., language-js:app.js)
|
|
20
|
+
if (!filename && langClass && langClass[1].includes(':')) {
|
|
21
|
+
filename = langClass[1].split(':')[1];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Create wrapper
|
|
25
|
+
const wrapper = document.createElement('div');
|
|
26
|
+
wrapper.className = 'code-block-wrapper';
|
|
27
|
+
|
|
28
|
+
// Create header if filename exists
|
|
29
|
+
if (filename) {
|
|
30
|
+
const header = document.createElement('div');
|
|
31
|
+
header.className = 'code-block-header';
|
|
32
|
+
header.innerHTML = `
|
|
33
|
+
<span class="code-block-filename">${filename}</span>
|
|
34
|
+
`;
|
|
35
|
+
wrapper.appendChild(header);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create copy button
|
|
39
|
+
const button = document.createElement('button');
|
|
40
|
+
button.className = 'copy-button';
|
|
41
|
+
button.innerHTML = `
|
|
42
|
+
<span class="copy-text">Copy</span>
|
|
43
|
+
<svg class="copy-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
44
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
45
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
46
|
+
</svg>
|
|
47
|
+
<svg class="check-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
48
|
+
<polyline points="20 6 9 17 4 12"></polyline>
|
|
49
|
+
</svg>
|
|
50
|
+
`;
|
|
51
|
+
button.setAttribute('aria-label', 'Copy code');
|
|
52
|
+
|
|
53
|
+
// Add click handler
|
|
54
|
+
button.addEventListener('click', async () => {
|
|
55
|
+
try {
|
|
56
|
+
await navigator.clipboard.writeText(code.textContent || '');
|
|
57
|
+
|
|
58
|
+
// Show check state
|
|
59
|
+
button.classList.add('copied');
|
|
60
|
+
const copyText = button.querySelector('.copy-text');
|
|
61
|
+
if (copyText) copyText.textContent = 'Copied!';
|
|
62
|
+
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
button.classList.remove('copied');
|
|
65
|
+
if (copyText) copyText.textContent = 'Copy';
|
|
66
|
+
}, 2000);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error('Failed to copy:', err);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Insert wrapper
|
|
73
|
+
pre.parentNode?.insertBefore(wrapper, pre);
|
|
74
|
+
wrapper.appendChild(pre);
|
|
75
|
+
wrapper.appendChild(button);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Run on page load
|
|
80
|
+
initCodeCopy();
|
|
81
|
+
|
|
82
|
+
// Re-run on Astro page transitions
|
|
83
|
+
document.addEventListener('astro:page-load', initCodeCopy);
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<style is:global>
|
|
87
|
+
.code-block-wrapper {
|
|
88
|
+
position: relative;
|
|
89
|
+
margin: 1rem 0;
|
|
90
|
+
border-radius: 0.5rem;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
background: var(--color-bg-secondary);
|
|
93
|
+
border: 1px solid var(--color-border);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.code-block-wrapper pre {
|
|
97
|
+
margin: 0 !important;
|
|
98
|
+
border: none !important;
|
|
99
|
+
border-radius: 0 !important;
|
|
100
|
+
background: transparent !important;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.code-block-header {
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: space-between;
|
|
107
|
+
padding: 0.5rem 1rem;
|
|
108
|
+
background: var(--color-bg-tertiary);
|
|
109
|
+
border-bottom: 1px solid var(--color-border);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.code-block-filename {
|
|
113
|
+
font-size: 0.8125rem;
|
|
114
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
115
|
+
color: var(--color-text-secondary);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.copy-button {
|
|
119
|
+
position: absolute;
|
|
120
|
+
top: 0.5rem;
|
|
121
|
+
right: 0.5rem;
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
gap: 0.375rem;
|
|
125
|
+
padding: 0.375rem 0.625rem;
|
|
126
|
+
font-size: 0.75rem;
|
|
127
|
+
font-weight: 500;
|
|
128
|
+
background: var(--color-bg);
|
|
129
|
+
border: 1px solid var(--color-border);
|
|
130
|
+
border-radius: 0.375rem;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
opacity: 0;
|
|
133
|
+
transition: opacity 150ms ease, background 150ms ease;
|
|
134
|
+
color: var(--color-text-muted);
|
|
135
|
+
z-index: 10;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.code-block-wrapper:hover .copy-button {
|
|
139
|
+
opacity: 1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.copy-button:hover {
|
|
143
|
+
background: var(--color-bg-secondary);
|
|
144
|
+
color: var(--color-text);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.copy-button .copy-icon,
|
|
148
|
+
.copy-button .check-icon {
|
|
149
|
+
flex-shrink: 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.copy-button .check-icon {
|
|
153
|
+
display: none;
|
|
154
|
+
color: #22c55e;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.copy-button.copied .copy-icon {
|
|
158
|
+
display: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.copy-button.copied .check-icon {
|
|
162
|
+
display: block;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.copy-button.copied {
|
|
166
|
+
color: #22c55e;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Adjust button position when header exists */
|
|
170
|
+
.code-block-wrapper:has(.code-block-header) .copy-button {
|
|
171
|
+
top: 0.375rem;
|
|
172
|
+
}
|
|
173
|
+
</style>
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Search, Menu, Moon, Sun, Github } from "lucide-react";
|
|
3
|
+
import { Button } from "./ui/button";
|
|
4
|
+
import { Separator } from "./ui/separator";
|
|
5
|
+
import {
|
|
6
|
+
Tooltip,
|
|
7
|
+
TooltipContent,
|
|
8
|
+
TooltipProvider,
|
|
9
|
+
TooltipTrigger,
|
|
10
|
+
} from "./ui/tooltip";
|
|
11
|
+
import { cn } from "../lib/utils";
|
|
12
|
+
|
|
13
|
+
interface DocHeaderProps {
|
|
14
|
+
siteName: string;
|
|
15
|
+
logo?: string;
|
|
16
|
+
githubUrl?: string;
|
|
17
|
+
hasMultipleLocales?: boolean;
|
|
18
|
+
currentLocale?: string;
|
|
19
|
+
localeLabels?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function DocHeader({
|
|
23
|
+
siteName,
|
|
24
|
+
logo,
|
|
25
|
+
githubUrl,
|
|
26
|
+
hasMultipleLocales,
|
|
27
|
+
currentLocale,
|
|
28
|
+
localeLabels,
|
|
29
|
+
}: DocHeaderProps) {
|
|
30
|
+
const [theme, setTheme] = React.useState<"light" | "dark">("light");
|
|
31
|
+
|
|
32
|
+
React.useEffect(() => {
|
|
33
|
+
const isDark = document.documentElement.classList.contains("dark");
|
|
34
|
+
setTheme(isDark ? "dark" : "light");
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const toggleTheme = () => {
|
|
38
|
+
const newTheme = theme === "light" ? "dark" : "light";
|
|
39
|
+
setTheme(newTheme);
|
|
40
|
+
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
|
41
|
+
localStorage.setItem("theme", newTheme);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const openSearch = () => {
|
|
45
|
+
document.dispatchEvent(new CustomEvent("toggle-search"));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const openMobileNav = () => {
|
|
49
|
+
document.dispatchEvent(new CustomEvent("toggle-mobile-nav"));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<TooltipProvider>
|
|
54
|
+
<header className="sticky top-0 z-50 w-full border-b border-[var(--color-border)] bg-[var(--color-bg)]/95 backdrop-blur-md supports-[backdrop-filter]:bg-[var(--color-bg)]/80">
|
|
55
|
+
<div className="flex h-14 items-center justify-between px-4 max-w-[1120px] mx-auto">
|
|
56
|
+
{/* Logo */}
|
|
57
|
+
<div className="flex items-center gap-6">
|
|
58
|
+
<a
|
|
59
|
+
href="/"
|
|
60
|
+
className="flex items-center gap-2.5 font-semibold text-[var(--color-text)] hover:opacity-80 transition-opacity"
|
|
61
|
+
>
|
|
62
|
+
{logo && <img src={logo} alt={siteName} className="h-7 w-7" />}
|
|
63
|
+
<span className="text-lg">{siteName}</span>
|
|
64
|
+
</a>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{/* Right side actions */}
|
|
68
|
+
<div className="flex items-center gap-1">
|
|
69
|
+
{/* Search button */}
|
|
70
|
+
<Button
|
|
71
|
+
variant="outline"
|
|
72
|
+
className="hidden md:flex items-center gap-3 px-4 py-2 h-10 min-w-[200px] justify-start rounded-xl"
|
|
73
|
+
onClick={openSearch}
|
|
74
|
+
>
|
|
75
|
+
<Search className="h-4 w-4 shrink-0 text-[var(--color-text-muted)]" />
|
|
76
|
+
<span className="flex-1 text-left text-[var(--color-text-muted)]">
|
|
77
|
+
Search...
|
|
78
|
+
</span>
|
|
79
|
+
<kbd className="hidden lg:inline-flex items-center gap-0.5 px-2 py-1 text-xs font-medium bg-[var(--color-bg)] border border-[var(--color-border)] rounded-md text-[var(--color-text-muted)]">
|
|
80
|
+
<span>⌘</span>K
|
|
81
|
+
</kbd>
|
|
82
|
+
</Button>
|
|
83
|
+
|
|
84
|
+
{/* Mobile search button */}
|
|
85
|
+
<Tooltip>
|
|
86
|
+
<TooltipTrigger asChild>
|
|
87
|
+
<Button
|
|
88
|
+
variant="ghost"
|
|
89
|
+
size="icon"
|
|
90
|
+
className="md:hidden rounded-xl"
|
|
91
|
+
onClick={openSearch}
|
|
92
|
+
>
|
|
93
|
+
<Search className="h-5 w-5" />
|
|
94
|
+
<span className="sr-only">Search</span>
|
|
95
|
+
</Button>
|
|
96
|
+
</TooltipTrigger>
|
|
97
|
+
<TooltipContent>Search</TooltipContent>
|
|
98
|
+
</Tooltip>
|
|
99
|
+
|
|
100
|
+
{/* Divider */}
|
|
101
|
+
<Separator orientation="vertical" className="hidden md:block h-6 mx-2" />
|
|
102
|
+
|
|
103
|
+
{/* GitHub link */}
|
|
104
|
+
{githubUrl && (
|
|
105
|
+
<Tooltip>
|
|
106
|
+
<TooltipTrigger asChild>
|
|
107
|
+
<Button
|
|
108
|
+
variant="ghost"
|
|
109
|
+
size="icon"
|
|
110
|
+
className="rounded-xl"
|
|
111
|
+
asChild
|
|
112
|
+
>
|
|
113
|
+
<a
|
|
114
|
+
href={githubUrl}
|
|
115
|
+
target="_blank"
|
|
116
|
+
rel="noopener noreferrer"
|
|
117
|
+
>
|
|
118
|
+
<Github className="h-5 w-5" />
|
|
119
|
+
<span className="sr-only">GitHub</span>
|
|
120
|
+
</a>
|
|
121
|
+
</Button>
|
|
122
|
+
</TooltipTrigger>
|
|
123
|
+
<TooltipContent>GitHub</TooltipContent>
|
|
124
|
+
</Tooltip>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
{/* Theme toggle */}
|
|
128
|
+
<Tooltip>
|
|
129
|
+
<TooltipTrigger asChild>
|
|
130
|
+
<Button
|
|
131
|
+
variant="ghost"
|
|
132
|
+
size="icon"
|
|
133
|
+
className="rounded-xl"
|
|
134
|
+
onClick={toggleTheme}
|
|
135
|
+
>
|
|
136
|
+
{theme === "light" ? (
|
|
137
|
+
<Moon className="h-5 w-5" />
|
|
138
|
+
) : (
|
|
139
|
+
<Sun className="h-5 w-5" />
|
|
140
|
+
)}
|
|
141
|
+
<span className="sr-only">Toggle theme</span>
|
|
142
|
+
</Button>
|
|
143
|
+
</TooltipTrigger>
|
|
144
|
+
<TooltipContent>
|
|
145
|
+
{theme === "light" ? "Dark mode" : "Light mode"}
|
|
146
|
+
</TooltipContent>
|
|
147
|
+
</Tooltip>
|
|
148
|
+
|
|
149
|
+
{/* Mobile menu button */}
|
|
150
|
+
<Button
|
|
151
|
+
variant="ghost"
|
|
152
|
+
size="icon"
|
|
153
|
+
className="lg:hidden rounded-xl"
|
|
154
|
+
onClick={openMobileNav}
|
|
155
|
+
>
|
|
156
|
+
<Menu className="h-5 w-5" />
|
|
157
|
+
<span className="sr-only">Menu</span>
|
|
158
|
+
</Button>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</header>
|
|
162
|
+
</TooltipProvider>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export default DocHeader;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ChevronRight } from "lucide-react";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
import { ScrollArea } from "./ui/scroll-area";
|
|
5
|
+
import {
|
|
6
|
+
Collapsible,
|
|
7
|
+
CollapsibleContent,
|
|
8
|
+
CollapsibleTrigger,
|
|
9
|
+
} from "./ui/collapsible";
|
|
10
|
+
|
|
11
|
+
interface NavItem {
|
|
12
|
+
title: string;
|
|
13
|
+
href: string;
|
|
14
|
+
isActive?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface NavGroup {
|
|
18
|
+
title: string;
|
|
19
|
+
items: NavItem[];
|
|
20
|
+
defaultOpen?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface DocsSidebarProps {
|
|
24
|
+
groups: NavGroup[];
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function DocsSidebar({ groups, className }: DocsSidebarProps) {
|
|
29
|
+
return (
|
|
30
|
+
<ScrollArea className={cn("h-full", className)}>
|
|
31
|
+
<nav className="space-y-4">
|
|
32
|
+
{groups.map((group, index) => (
|
|
33
|
+
<SidebarGroup key={index} group={group} />
|
|
34
|
+
))}
|
|
35
|
+
</nav>
|
|
36
|
+
</ScrollArea>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function SidebarGroup({ group }: { group: NavGroup }) {
|
|
41
|
+
const [isOpen, setIsOpen] = React.useState(group.defaultOpen ?? true);
|
|
42
|
+
const hasActiveItem = group.items.some((item) => item.isActive);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
46
|
+
<CollapsibleTrigger className="group flex w-full items-center gap-1.5 py-1 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition-colors">
|
|
47
|
+
<ChevronRight
|
|
48
|
+
className={cn(
|
|
49
|
+
"h-3 w-3 transition-transform duration-200",
|
|
50
|
+
isOpen && "rotate-90"
|
|
51
|
+
)}
|
|
52
|
+
/>
|
|
53
|
+
{group.title}
|
|
54
|
+
</CollapsibleTrigger>
|
|
55
|
+
<CollapsibleContent className="overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
|
|
56
|
+
<ul className="mt-1 space-y-0.5 border-l border-[var(--color-border)] ml-1.5">
|
|
57
|
+
{group.items.map((item, index) => (
|
|
58
|
+
<SidebarItem key={index} item={item} />
|
|
59
|
+
))}
|
|
60
|
+
</ul>
|
|
61
|
+
</CollapsibleContent>
|
|
62
|
+
</Collapsible>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function SidebarItem({ item }: { item: NavItem }) {
|
|
67
|
+
return (
|
|
68
|
+
<li className="relative">
|
|
69
|
+
<a
|
|
70
|
+
href={item.href}
|
|
71
|
+
className={cn(
|
|
72
|
+
"block pl-4 pr-2 py-1.5 text-sm transition-all duration-150 -ml-px border-l-2",
|
|
73
|
+
item.isActive
|
|
74
|
+
? "border-primary-500 text-primary-600 dark:text-primary-400 font-medium bg-primary-50/50 dark:bg-primary-950/30"
|
|
75
|
+
: "border-transparent text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:border-[var(--color-border)]"
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
<span className="truncate">{item.title}</span>
|
|
79
|
+
</a>
|
|
80
|
+
</li>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default DocsSidebar;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { DocHeader } from "./DocHeader";
|
|
3
|
+
import LanguageSwitcher from "./LanguageSwitcher.astro";
|
|
4
|
+
import config from "virtual:barodoc/config";
|
|
5
|
+
import { locales, defaultLocale } from "virtual:barodoc/i18n";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
currentLocale?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { currentLocale = defaultLocale } = Astro.props;
|
|
12
|
+
const hasMultipleLocales = locales.length > 1;
|
|
13
|
+
|
|
14
|
+
// Build locale labels from config
|
|
15
|
+
const localeLabels: Record<string, string> = config.i18n?.labels || {};
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<DocHeader
|
|
19
|
+
client:load
|
|
20
|
+
siteName={config.name}
|
|
21
|
+
logo={config.logo}
|
|
22
|
+
githubUrl={config.topbar?.github}
|
|
23
|
+
hasMultipleLocales={hasMultipleLocales}
|
|
24
|
+
currentLocale={currentLocale}
|
|
25
|
+
localeLabels={localeLabels}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
{hasMultipleLocales && (
|
|
29
|
+
<div class="hidden">
|
|
30
|
+
<LanguageSwitcher currentLocale={currentLocale} />
|
|
31
|
+
</div>
|
|
32
|
+
)}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { i18n, locales, defaultLocale } from "virtual:barodoc/i18n";
|
|
3
|
+
import { getLocaleLabel, getLocalizedPath } from "@barodoc/core";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
currentLocale: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { currentLocale } = Astro.props;
|
|
10
|
+
const currentPath = Astro.url.pathname;
|
|
11
|
+
|
|
12
|
+
function getLocalizedUrl(locale: string): string {
|
|
13
|
+
// Remove current locale from path if present
|
|
14
|
+
let path = currentPath;
|
|
15
|
+
for (const loc of locales) {
|
|
16
|
+
if (path.startsWith(`/${loc}/`) || path === `/${loc}`) {
|
|
17
|
+
path = path.slice(loc.length + 1) || "/";
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Add new locale
|
|
23
|
+
if (locale === defaultLocale) {
|
|
24
|
+
return path;
|
|
25
|
+
}
|
|
26
|
+
return `/${locale}${path}`;
|
|
27
|
+
}
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<div class="relative">
|
|
31
|
+
<button
|
|
32
|
+
id="lang-switcher-btn"
|
|
33
|
+
class="flex items-center gap-1 px-2 py-1.5 text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)] rounded-lg hover:bg-[var(--color-bg-secondary)] transition-colors"
|
|
34
|
+
aria-label="Select language"
|
|
35
|
+
>
|
|
36
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
37
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"></path>
|
|
38
|
+
</svg>
|
|
39
|
+
<span class="hidden sm:inline">{getLocaleLabel(currentLocale, i18n?.labels)}</span>
|
|
40
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
41
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
42
|
+
</svg>
|
|
43
|
+
</button>
|
|
44
|
+
|
|
45
|
+
<div
|
|
46
|
+
id="lang-switcher-menu"
|
|
47
|
+
class="hidden absolute right-0 mt-1 py-1 w-32 bg-[var(--color-bg)] border border-[var(--color-border)] rounded-lg shadow-lg z-50"
|
|
48
|
+
>
|
|
49
|
+
{locales.map((locale) => (
|
|
50
|
+
<a
|
|
51
|
+
href={getLocalizedUrl(locale)}
|
|
52
|
+
class:list={[
|
|
53
|
+
"block px-3 py-1.5 text-sm transition-colors",
|
|
54
|
+
locale === currentLocale
|
|
55
|
+
? "bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300"
|
|
56
|
+
: "text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]"
|
|
57
|
+
]}
|
|
58
|
+
>
|
|
59
|
+
{getLocaleLabel(locale, i18n?.labels)}
|
|
60
|
+
</a>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<script>
|
|
66
|
+
const btn = document.getElementById('lang-switcher-btn');
|
|
67
|
+
const menu = document.getElementById('lang-switcher-menu');
|
|
68
|
+
|
|
69
|
+
btn?.addEventListener('click', (e) => {
|
|
70
|
+
e.stopPropagation();
|
|
71
|
+
menu?.classList.toggle('hidden');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
document.addEventListener('click', () => {
|
|
75
|
+
menu?.classList.add('hidden');
|
|
76
|
+
});
|
|
77
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { MobileNavSheet } from "./MobileNavSheet";
|
|
3
|
+
import config from "virtual:barodoc/config";
|
|
4
|
+
import { defaultLocale } from "virtual:barodoc/i18n";
|
|
5
|
+
import { getLocalizedNavGroup } from "@barodoc/core";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
currentPath: string;
|
|
9
|
+
currentLocale?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { currentPath, currentLocale = defaultLocale } = Astro.props;
|
|
13
|
+
|
|
14
|
+
// Normalize path for comparison
|
|
15
|
+
function normalizePath(path: string): string {
|
|
16
|
+
return path.replace(/\/$/, '').replace(/^\//, '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isActive(page: string): boolean {
|
|
20
|
+
const normalized = normalizePath(currentPath);
|
|
21
|
+
const pagePath = normalizePath(page);
|
|
22
|
+
return normalized === pagePath ||
|
|
23
|
+
normalized === `docs/${pagePath}` ||
|
|
24
|
+
normalized === `${currentLocale}/docs/${pagePath}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getPageHref(page: string): string {
|
|
28
|
+
if (currentLocale === defaultLocale) {
|
|
29
|
+
return `/docs/${page}`;
|
|
30
|
+
}
|
|
31
|
+
return `/${currentLocale}/docs/${page}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Get page title from the page slug
|
|
35
|
+
function getPageTitle(page: string): string {
|
|
36
|
+
const parts = page.split('/');
|
|
37
|
+
const name = parts[parts.length - 1];
|
|
38
|
+
return name
|
|
39
|
+
.split('-')
|
|
40
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
41
|
+
.join(' ');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Transform config.navigation to the format expected by MobileNavSheet
|
|
45
|
+
const groups = config.navigation.map((group) => ({
|
|
46
|
+
title: getLocalizedNavGroup(group, currentLocale, defaultLocale),
|
|
47
|
+
defaultOpen: true,
|
|
48
|
+
items: group.pages.map((page) => ({
|
|
49
|
+
title: getPageTitle(page),
|
|
50
|
+
href: getPageHref(page),
|
|
51
|
+
isActive: isActive(page),
|
|
52
|
+
})),
|
|
53
|
+
}));
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
<MobileNavSheet
|
|
57
|
+
client:load
|
|
58
|
+
groups={groups}
|
|
59
|
+
siteName={config.name}
|
|
60
|
+
logo={config.logo}
|
|
61
|
+
/>
|