@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.
- package/package.json +6 -2
- package/src/components/Breadcrumb.astro +8 -8
- package/src/components/CodeCopy.astro +14 -13
- package/src/components/Contributors.astro +71 -0
- package/src/components/DocHeader.tsx +24 -24
- package/src/components/DocsSidebar.tsx +11 -15
- package/src/components/KeyboardShortcuts.astro +108 -0
- package/src/components/LanguageSwitcher.astro +3 -3
- package/src/components/MobileNavSheet.tsx +1 -1
- package/src/components/Search.tsx +10 -10
- package/src/components/SearchDialog.tsx +8 -8
- package/src/components/TableOfContents.astro +5 -5
- package/src/components/ThemeToggle.tsx +4 -4
- package/src/components/VersionSwitcher.tsx +79 -0
- package/src/components/api/ApiEndpoint.astro +5 -5
- package/src/components/api/ApiParam.astro +4 -4
- package/src/components/api/ApiParams.astro +3 -3
- package/src/components/api/ApiResponse.astro +2 -2
- package/src/components/index.ts +5 -0
- package/src/components/mdx/Accordion.tsx +6 -6
- package/src/components/mdx/ApiPlayground.tsx +200 -0
- package/src/components/mdx/Badge.tsx +1 -1
- package/src/components/mdx/Callout.astro +20 -20
- package/src/components/mdx/Card.astro +5 -5
- package/src/components/mdx/CodeGroup.astro +23 -20
- package/src/components/mdx/CodeGroup.tsx +6 -6
- package/src/components/mdx/DocAccordion.tsx +5 -5
- package/src/components/mdx/DocCard.tsx +2 -2
- package/src/components/mdx/DocTabs.tsx +3 -3
- package/src/components/mdx/Expandable.tsx +11 -11
- package/src/components/mdx/FileTree.tsx +6 -6
- package/src/components/mdx/Frame.tsx +2 -2
- package/src/components/mdx/ImageZoom.tsx +35 -0
- package/src/components/mdx/Mermaid.tsx +3 -3
- package/src/components/mdx/ParamField.tsx +7 -7
- package/src/components/mdx/ResponseField.tsx +6 -6
- package/src/components/mdx/Step.astro +2 -2
- package/src/components/mdx/Steps.astro +3 -3
- package/src/components/mdx/Steps.tsx +10 -19
- package/src/components/mdx/Tabs.tsx +5 -8
- package/src/components/mdx/Tooltip.tsx +20 -19
- package/src/components/mdx/Video.tsx +71 -0
- package/src/components/ui/accordion.tsx +2 -2
- package/src/components/ui/alert.tsx +1 -1
- package/src/components/ui/button.tsx +3 -3
- package/src/components/ui/card.tsx +2 -2
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/ui/scroll-area.tsx +1 -1
- package/src/components/ui/separator.tsx +1 -1
- package/src/components/ui/sheet.tsx +3 -3
- package/src/components/ui/tabs.tsx +2 -2
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/index.ts +33 -1
- package/src/layouts/BaseLayout.astro +10 -1
- package/src/layouts/BlogLayout.astro +93 -0
- package/src/layouts/DocsLayout.astro +72 -23
- package/src/pages/404.astro +5 -5
- package/src/pages/blog/[...slug].astro +39 -0
- package/src/pages/blog/index.astro +92 -0
- package/src/pages/changelog/index.astro +72 -0
- package/src/pages/docs/[...slug].astro +6 -2
- package/src/pages/index.astro +21 -21
- package/src/styles/global.css +1041 -166
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barodoc/theme-docs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Documentation theme for Barodoc",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -28,9 +28,13 @@
|
|
|
28
28
|
"class-variance-authority": "^0.7.1",
|
|
29
29
|
"clsx": "^2.1.1",
|
|
30
30
|
"lucide-react": "^0.563.0",
|
|
31
|
+
"medium-zoom": "^1.1.0",
|
|
31
32
|
"mermaid": "^11.12.2",
|
|
33
|
+
"reading-time": "^1.5.0",
|
|
34
|
+
"rehype-katex": "^7.0.1",
|
|
35
|
+
"remark-math": "^6.0.0",
|
|
32
36
|
"tailwind-merge": "^3.4.0",
|
|
33
|
-
"@barodoc/core": "
|
|
37
|
+
"@barodoc/core": "6.0.0"
|
|
34
38
|
},
|
|
35
39
|
"peerDependencies": {
|
|
36
40
|
"astro": "^5.0.0",
|
|
@@ -11,20 +11,20 @@ interface Props {
|
|
|
11
11
|
const { items } = Astro.props;
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
<nav aria-label="Breadcrumb" class="mb-
|
|
15
|
-
<ol class="flex flex-wrap items-center gap-1 text-
|
|
16
|
-
<li class="flex items-center gap-1">
|
|
17
|
-
<a href="/docs" class="hover:text-[var(--
|
|
14
|
+
<nav aria-label="Breadcrumb" class="mb-4">
|
|
15
|
+
<ol class="flex flex-wrap items-center gap-1.5 text-[13px] text-[var(--bd-text-muted)]">
|
|
16
|
+
<li class="flex items-center gap-1.5">
|
|
17
|
+
<a href="/docs" class="hover:text-[var(--bd-text)] transition-colors">Docs</a>
|
|
18
18
|
</li>
|
|
19
19
|
{items.map((item, i) => (
|
|
20
|
-
<li class="flex items-center gap-1">
|
|
21
|
-
<svg class="w-3 h-3 text-[var(--
|
|
20
|
+
<li class="flex items-center gap-1.5">
|
|
21
|
+
<svg class="w-3 h-3 text-[var(--bd-text-muted)] opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
22
22
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
23
23
|
</svg>
|
|
24
24
|
{item.href && i < items.length - 1 ? (
|
|
25
|
-
<a href={item.href} class="hover:text-[var(--
|
|
25
|
+
<a href={item.href} class="hover:text-[var(--bd-text)] transition-colors">{item.label}</a>
|
|
26
26
|
) : (
|
|
27
|
-
<span class="text-[var(--
|
|
27
|
+
<span class="text-[var(--bd-text-secondary)] font-medium">{item.label}</span>
|
|
28
28
|
)}
|
|
29
29
|
</li>
|
|
30
30
|
))}
|
|
@@ -25,12 +25,13 @@
|
|
|
25
25
|
const wrapper = document.createElement('div');
|
|
26
26
|
wrapper.className = 'code-block-wrapper';
|
|
27
27
|
|
|
28
|
-
// Create header
|
|
29
|
-
|
|
28
|
+
// Create header for filename or language label
|
|
29
|
+
const displayLabel = filename || lang;
|
|
30
|
+
if (displayLabel) {
|
|
30
31
|
const header = document.createElement('div');
|
|
31
32
|
header.className = 'code-block-header';
|
|
32
33
|
header.innerHTML = `
|
|
33
|
-
<span class="code-block-filename">${
|
|
34
|
+
<span class="code-block-filename">${displayLabel}</span>
|
|
34
35
|
`;
|
|
35
36
|
wrapper.appendChild(header);
|
|
36
37
|
}
|
|
@@ -89,8 +90,8 @@
|
|
|
89
90
|
margin: 1rem 0;
|
|
90
91
|
border-radius: 0.5rem;
|
|
91
92
|
overflow: hidden;
|
|
92
|
-
background: var(--
|
|
93
|
-
border: 1px solid var(--
|
|
93
|
+
background: var(--bd-bg-subtle);
|
|
94
|
+
border: 1px solid var(--bd-border);
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
.code-block-wrapper pre {
|
|
@@ -145,14 +146,14 @@
|
|
|
145
146
|
align-items: center;
|
|
146
147
|
justify-content: space-between;
|
|
147
148
|
padding: 0.5rem 1rem;
|
|
148
|
-
background: var(--
|
|
149
|
-
border-bottom: 1px solid var(--
|
|
149
|
+
background: var(--bd-bg-muted);
|
|
150
|
+
border-bottom: 1px solid var(--bd-border);
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
.code-block-filename {
|
|
153
154
|
font-size: 0.8125rem;
|
|
154
155
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
155
|
-
color: var(--
|
|
156
|
+
color: var(--bd-text-secondary);
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
.copy-button {
|
|
@@ -165,13 +166,13 @@
|
|
|
165
166
|
padding: 0.375rem 0.625rem;
|
|
166
167
|
font-size: 0.75rem;
|
|
167
168
|
font-weight: 500;
|
|
168
|
-
background: var(--
|
|
169
|
-
border: 1px solid var(--
|
|
169
|
+
background: var(--bd-bg);
|
|
170
|
+
border: 1px solid var(--bd-border);
|
|
170
171
|
border-radius: 0.375rem;
|
|
171
172
|
cursor: pointer;
|
|
172
173
|
opacity: 0;
|
|
173
174
|
transition: opacity 150ms ease, background 150ms ease;
|
|
174
|
-
color: var(--
|
|
175
|
+
color: var(--bd-text-muted);
|
|
175
176
|
z-index: 10;
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -180,8 +181,8 @@
|
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
.copy-button:hover {
|
|
183
|
-
background: var(--
|
|
184
|
-
color: var(--
|
|
184
|
+
background: var(--bd-bg-subtle);
|
|
185
|
+
color: var(--bd-text);
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
.copy-button .copy-icon,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
filePath?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Contributor {
|
|
9
|
+
name: string;
|
|
10
|
+
email: string;
|
|
11
|
+
avatarUrl: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { filePath } = Astro.props;
|
|
15
|
+
|
|
16
|
+
let contributors: Contributor[] = [];
|
|
17
|
+
|
|
18
|
+
if (filePath) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = execSync(
|
|
21
|
+
`git log --format='%aN|%aE' -- "${filePath}"`,
|
|
22
|
+
{ encoding: "utf-8", timeout: 5000 }
|
|
23
|
+
).trim();
|
|
24
|
+
|
|
25
|
+
if (raw) {
|
|
26
|
+
const seen = new Map<string, Contributor>();
|
|
27
|
+
for (const line of raw.split("\n")) {
|
|
28
|
+
const [name, email] = line.split("|");
|
|
29
|
+
if (!name || seen.has(email)) continue;
|
|
30
|
+
const hash = email.trim().toLowerCase();
|
|
31
|
+
seen.set(email, {
|
|
32
|
+
name: name.trim(),
|
|
33
|
+
email: email.trim(),
|
|
34
|
+
avatarUrl: `https://gravatar.com/avatar/${await computeHash(hash)}?s=64&d=mp`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
contributors = Array.from(seen.values());
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// git not available or file not tracked
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function computeHash(input: string): Promise<string> {
|
|
45
|
+
const encoder = new TextEncoder();
|
|
46
|
+
const data = encoder.encode(input);
|
|
47
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
48
|
+
return Array.from(new Uint8Array(hashBuffer))
|
|
49
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
50
|
+
.join("");
|
|
51
|
+
}
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
{contributors.length > 0 && (
|
|
55
|
+
<div class="bd-contributors">
|
|
56
|
+
<span class="bd-contributors-label">Contributors</span>
|
|
57
|
+
<div class="bd-contributors-avatars">
|
|
58
|
+
{contributors.map((c) => (
|
|
59
|
+
<img
|
|
60
|
+
src={c.avatarUrl}
|
|
61
|
+
alt={c.name}
|
|
62
|
+
title={c.name}
|
|
63
|
+
class="bd-contributor-avatar"
|
|
64
|
+
loading="lazy"
|
|
65
|
+
width="28"
|
|
66
|
+
height="28"
|
|
67
|
+
/>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
@@ -90,32 +90,32 @@ export function DocHeader({
|
|
|
90
90
|
|
|
91
91
|
return (
|
|
92
92
|
<TooltipProvider>
|
|
93
|
-
<header className="sticky top-0 z-50 w-full min-w-0 border-b border-[var(--
|
|
94
|
-
<div className="flex h-14 items-center justify-between gap-2 px-
|
|
93
|
+
<header className="sticky top-0 z-50 w-full min-w-0 border-b border-[var(--bd-border)] bg-[var(--bd-bg)]/95 backdrop-blur-md supports-[backdrop-filter]:bg-[var(--bd-bg)]/80">
|
|
94
|
+
<div className="flex h-14 items-center justify-between gap-2 px-4 sm:px-6 max-w-[1280px] mx-auto min-w-0">
|
|
95
95
|
{/* Logo */}
|
|
96
96
|
<div className="flex items-center gap-6 min-w-0 shrink">
|
|
97
97
|
<a
|
|
98
98
|
href="/"
|
|
99
|
-
className="flex items-center gap-2 min-w-0 shrink overflow-hidden font-semibold text-[var(--
|
|
99
|
+
className="flex items-center gap-2.5 min-w-0 shrink overflow-hidden font-semibold text-[var(--bd-text-heading)] hover:opacity-80 transition-opacity"
|
|
100
100
|
>
|
|
101
101
|
{logo && <img src={logo} alt={siteName} className="h-7 w-7 shrink-0" />}
|
|
102
|
-
<span className="text-
|
|
102
|
+
<span className="text-[15px] tracking-tight truncate">{siteName}</span>
|
|
103
103
|
</a>
|
|
104
104
|
</div>
|
|
105
105
|
|
|
106
106
|
{/* Right side actions */}
|
|
107
|
-
<div className="flex items-center gap-1 shrink-0">
|
|
107
|
+
<div className="flex items-center gap-1.5 shrink-0">
|
|
108
108
|
{/* Search button */}
|
|
109
109
|
<Button
|
|
110
110
|
variant="outline"
|
|
111
|
-
className="hidden md:flex items-center gap-3 px-
|
|
111
|
+
className="hidden md:flex items-center gap-3 px-3 py-1.5 h-9 min-w-[220px] justify-start rounded-lg border-[var(--bd-border)]"
|
|
112
112
|
onClick={openSearch}
|
|
113
113
|
>
|
|
114
|
-
<Search className="h-4 w-4 shrink-0 text-[var(--
|
|
115
|
-
<span className="flex-1 text-left text-[var(--
|
|
114
|
+
<Search className="h-4 w-4 shrink-0 text-[var(--bd-text-muted)]" />
|
|
115
|
+
<span className="flex-1 text-left text-sm text-[var(--bd-text-muted)]">
|
|
116
116
|
Search...
|
|
117
117
|
</span>
|
|
118
|
-
<kbd className="hidden lg:inline-flex items-center gap-0.5 px-
|
|
118
|
+
<kbd className="hidden lg:inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] font-medium bg-[var(--bd-bg-subtle)] border border-[var(--bd-border)] rounded text-[var(--bd-text-muted)]">
|
|
119
119
|
<span>⌘</span>K
|
|
120
120
|
</kbd>
|
|
121
121
|
</Button>
|
|
@@ -126,7 +126,7 @@ export function DocHeader({
|
|
|
126
126
|
<Button
|
|
127
127
|
variant="ghost"
|
|
128
128
|
size="icon"
|
|
129
|
-
className="md:hidden
|
|
129
|
+
className="md:hidden"
|
|
130
130
|
onClick={openSearch}
|
|
131
131
|
>
|
|
132
132
|
<Search className="h-5 w-5" />
|
|
@@ -137,7 +137,7 @@ export function DocHeader({
|
|
|
137
137
|
</Tooltip>
|
|
138
138
|
|
|
139
139
|
{/* Divider */}
|
|
140
|
-
<Separator orientation="vertical" className="hidden md:block h-
|
|
140
|
+
<Separator orientation="vertical" className="hidden md:block h-5 mx-1.5" />
|
|
141
141
|
|
|
142
142
|
{/* GitHub link */}
|
|
143
143
|
{githubUrl && (
|
|
@@ -146,7 +146,7 @@ export function DocHeader({
|
|
|
146
146
|
<Button
|
|
147
147
|
variant="ghost"
|
|
148
148
|
size="icon"
|
|
149
|
-
className="
|
|
149
|
+
className="h-8 w-8 text-[var(--bd-text-secondary)] hover:text-[var(--bd-text)]"
|
|
150
150
|
asChild
|
|
151
151
|
>
|
|
152
152
|
<a
|
|
@@ -154,7 +154,7 @@ export function DocHeader({
|
|
|
154
154
|
target="_blank"
|
|
155
155
|
rel="noopener noreferrer"
|
|
156
156
|
>
|
|
157
|
-
<Github className="h-
|
|
157
|
+
<Github className="h-[18px] w-[18px]" />
|
|
158
158
|
<span className="sr-only">GitHub</span>
|
|
159
159
|
</a>
|
|
160
160
|
</Button>
|
|
@@ -166,20 +166,20 @@ export function DocHeader({
|
|
|
166
166
|
{/* Language switcher */}
|
|
167
167
|
{hasMultipleLocales && locales.length > 0 && (
|
|
168
168
|
<>
|
|
169
|
-
<Separator orientation="vertical" className="hidden md:block h-
|
|
169
|
+
<Separator orientation="vertical" className="hidden md:block h-5 mx-1" />
|
|
170
170
|
<div className="relative" ref={langRef}>
|
|
171
171
|
<Tooltip>
|
|
172
172
|
<TooltipTrigger asChild>
|
|
173
173
|
<Button
|
|
174
174
|
variant="ghost"
|
|
175
|
-
className="
|
|
175
|
+
className="h-8 gap-1 px-2 text-[var(--bd-text-secondary)] hover:text-[var(--bd-text)]"
|
|
176
176
|
onClick={(e) => {
|
|
177
177
|
e.stopPropagation();
|
|
178
178
|
setLangOpen((o) => !o);
|
|
179
179
|
}}
|
|
180
180
|
>
|
|
181
181
|
<Globe className="h-4 w-4" />
|
|
182
|
-
<span className="text-
|
|
182
|
+
<span className="text-[13px] hidden sm:inline">
|
|
183
183
|
{localeLabels[currentLocale] ?? currentLocale}
|
|
184
184
|
</span>
|
|
185
185
|
<ChevronDown className="h-3 w-3" />
|
|
@@ -190,7 +190,7 @@ export function DocHeader({
|
|
|
190
190
|
</Tooltip>
|
|
191
191
|
{langOpen && (
|
|
192
192
|
<div
|
|
193
|
-
className="absolute right-0 mt-1 py-1 min-w-[8rem] rounded-lg border border-[var(--
|
|
193
|
+
className="absolute right-0 mt-1 py-1 min-w-[8rem] rounded-lg border border-[var(--bd-border)] bg-[var(--bd-bg)] shadow-[var(--bd-shadow-lg)] z-50"
|
|
194
194
|
role="menu"
|
|
195
195
|
>
|
|
196
196
|
{locales.map((locale) => (
|
|
@@ -198,10 +198,10 @@ export function DocHeader({
|
|
|
198
198
|
key={locale}
|
|
199
199
|
href={getLocalizedUrl(currentPath, locale, defaultLocale)}
|
|
200
200
|
className={cn(
|
|
201
|
-
"block px-3 py-
|
|
201
|
+
"block px-3 py-1.5 text-[13px] transition-colors",
|
|
202
202
|
locale === currentLocale
|
|
203
|
-
? "bg-primary-
|
|
204
|
-
: "text-[var(--
|
|
203
|
+
? "bg-primary-50 dark:bg-primary-950/50 text-primary-600 dark:text-primary-400 font-medium"
|
|
204
|
+
: "text-[var(--bd-text)] hover:bg-[var(--bd-bg-subtle)]"
|
|
205
205
|
)}
|
|
206
206
|
role="menuitem"
|
|
207
207
|
>
|
|
@@ -220,13 +220,13 @@ export function DocHeader({
|
|
|
220
220
|
<Button
|
|
221
221
|
variant="ghost"
|
|
222
222
|
size="icon"
|
|
223
|
-
className="
|
|
223
|
+
className="h-8 w-8 text-[var(--bd-text-secondary)] hover:text-[var(--bd-text)]"
|
|
224
224
|
onClick={toggleTheme}
|
|
225
225
|
>
|
|
226
226
|
{theme === "light" ? (
|
|
227
|
-
<Moon className="h-
|
|
227
|
+
<Moon className="h-[18px] w-[18px]" />
|
|
228
228
|
) : (
|
|
229
|
-
<Sun className="h-
|
|
229
|
+
<Sun className="h-[18px] w-[18px]" />
|
|
230
230
|
)}
|
|
231
231
|
<span className="sr-only">Toggle theme</span>
|
|
232
232
|
</Button>
|
|
@@ -240,7 +240,7 @@ export function DocHeader({
|
|
|
240
240
|
<Button
|
|
241
241
|
variant="ghost"
|
|
242
242
|
size="icon"
|
|
243
|
-
className="lg:hidden
|
|
243
|
+
className="lg:hidden h-8 w-8 text-[var(--bd-text-secondary)] hover:text-[var(--bd-text)]"
|
|
244
244
|
onClick={openMobileNav}
|
|
245
245
|
>
|
|
246
246
|
<Menu className="h-5 w-5" />
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { ChevronRight } from "lucide-react";
|
|
3
3
|
import { cn } from "../lib/utils";
|
|
4
|
-
import { ScrollArea } from "./ui/scroll-area";
|
|
5
4
|
import {
|
|
6
5
|
Collapsible,
|
|
7
6
|
CollapsibleContent,
|
|
@@ -27,33 +26,30 @@ interface DocsSidebarProps {
|
|
|
27
26
|
|
|
28
27
|
export function DocsSidebar({ groups, className }: DocsSidebarProps) {
|
|
29
28
|
return (
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
</nav>
|
|
36
|
-
</ScrollArea>
|
|
29
|
+
<nav className={cn("space-y-5", className)}>
|
|
30
|
+
{groups.map((group, index) => (
|
|
31
|
+
<SidebarGroup key={index} group={group} />
|
|
32
|
+
))}
|
|
33
|
+
</nav>
|
|
37
34
|
);
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
function SidebarGroup({ group }: { group: NavGroup }) {
|
|
41
38
|
const [isOpen, setIsOpen] = React.useState(group.defaultOpen ?? true);
|
|
42
|
-
const hasActiveItem = group.items.some((item) => item.isActive);
|
|
43
39
|
|
|
44
40
|
return (
|
|
45
41
|
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
46
|
-
<CollapsibleTrigger className="group flex w-full items-center gap-1.5 py-1 text-
|
|
42
|
+
<CollapsibleTrigger className="group flex w-full items-center gap-1.5 py-1.5 text-[13px] font-semibold text-[var(--bd-text)] hover:text-[var(--bd-text-heading)] transition-colors">
|
|
47
43
|
<ChevronRight
|
|
48
44
|
className={cn(
|
|
49
|
-
"h-3 w-3 transition-transform duration-200",
|
|
45
|
+
"h-3.5 w-3.5 text-[var(--bd-text-muted)] transition-transform duration-200",
|
|
50
46
|
isOpen && "rotate-90"
|
|
51
47
|
)}
|
|
52
48
|
/>
|
|
53
49
|
{group.title}
|
|
54
50
|
</CollapsibleTrigger>
|
|
55
51
|
<CollapsibleContent className="overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
|
|
56
|
-
<ul className="mt-1 space-y-
|
|
52
|
+
<ul className="mt-1 space-y-px border-l border-[var(--bd-border)] ml-2">
|
|
57
53
|
{group.items.map((item, index) => (
|
|
58
54
|
<SidebarItem key={index} item={item} />
|
|
59
55
|
))}
|
|
@@ -69,10 +65,10 @@ function SidebarItem({ item }: { item: NavItem }) {
|
|
|
69
65
|
<a
|
|
70
66
|
href={item.href}
|
|
71
67
|
className={cn(
|
|
72
|
-
"block pl-4 pr-2 py-1.5 text-
|
|
68
|
+
"block pl-4 pr-2 py-1.5 text-[13px] rounded-r-md transition-all duration-150 -ml-px border-l-2",
|
|
73
69
|
item.isActive
|
|
74
|
-
? "border-
|
|
75
|
-
: "border-transparent text-[var(--
|
|
70
|
+
? "border-[var(--bd-sidebar-active-border)] text-[var(--bd-sidebar-active-text)] font-medium bg-[var(--bd-sidebar-active-bg)]"
|
|
71
|
+
: "border-transparent text-[var(--bd-text-secondary)] hover:text-[var(--bd-text)] hover:border-[var(--bd-text-muted)]"
|
|
76
72
|
)}
|
|
77
73
|
>
|
|
78
74
|
<span className="truncate">{item.title}</span>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Keyboard shortcuts: Cmd/Ctrl+K → search, ←/→ → prev/next, ? → help modal
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
function initKeyboardShortcuts() {
|
|
7
|
+
if (window.__bdShortcutsInit) return;
|
|
8
|
+
window.__bdShortcutsInit = true;
|
|
9
|
+
|
|
10
|
+
const isMac = navigator.platform.toUpperCase().includes('MAC');
|
|
11
|
+
const modLabel = isMac ? '⌘' : 'Ctrl';
|
|
12
|
+
|
|
13
|
+
function isInputFocused(): boolean {
|
|
14
|
+
const el = document.activeElement;
|
|
15
|
+
if (!el) return false;
|
|
16
|
+
const tag = el.tagName;
|
|
17
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || (el as HTMLElement).isContentEditable;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function showShortcutsModal() {
|
|
21
|
+
if (document.querySelector('.bd-shortcuts-overlay')) return;
|
|
22
|
+
|
|
23
|
+
const overlay = document.createElement('div');
|
|
24
|
+
overlay.className = 'bd-shortcuts-overlay';
|
|
25
|
+
overlay.addEventListener('click', (e) => {
|
|
26
|
+
if (e.target === overlay) overlay.remove();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
overlay.innerHTML = `
|
|
30
|
+
<div class="bd-shortcuts-modal">
|
|
31
|
+
<h3>Keyboard Shortcuts</h3>
|
|
32
|
+
<div class="bd-shortcut-row">
|
|
33
|
+
<span class="bd-shortcut-desc">Search</span>
|
|
34
|
+
<span class="bd-shortcut-keys"><kbd class="bd-kbd">${modLabel}</kbd><kbd class="bd-kbd">K</kbd></span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="bd-shortcut-row">
|
|
37
|
+
<span class="bd-shortcut-desc">Previous page</span>
|
|
38
|
+
<span class="bd-shortcut-keys"><kbd class="bd-kbd">←</kbd></span>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="bd-shortcut-row">
|
|
41
|
+
<span class="bd-shortcut-desc">Next page</span>
|
|
42
|
+
<span class="bd-shortcut-keys"><kbd class="bd-kbd">→</kbd></span>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="bd-shortcut-row">
|
|
45
|
+
<span class="bd-shortcut-desc">Show shortcuts</span>
|
|
46
|
+
<span class="bd-shortcut-keys"><kbd class="bd-kbd">?</kbd></span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
document.body.appendChild(overlay);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
55
|
+
// Close modal on Escape
|
|
56
|
+
if (e.key === 'Escape') {
|
|
57
|
+
const modal = document.querySelector('.bd-shortcuts-overlay');
|
|
58
|
+
if (modal) { modal.remove(); return; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Cmd/Ctrl+K → open search
|
|
62
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
const searchBtn = document.querySelector('[data-search-trigger]') as HTMLButtonElement;
|
|
65
|
+
if (searchBtn) searchBtn.click();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isInputFocused()) return;
|
|
70
|
+
|
|
71
|
+
// ? → show shortcuts
|
|
72
|
+
if (e.key === '?' || (e.shiftKey && e.key === '/')) {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
showShortcutsModal();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ← → prev page
|
|
79
|
+
if (e.key === 'ArrowLeft' && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
80
|
+
const prev = document.querySelector('nav[aria-label="Page navigation"] a:first-of-type') as HTMLAnchorElement;
|
|
81
|
+
if (prev) { prev.click(); return; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// → → next page
|
|
85
|
+
if (e.key === 'ArrowRight' && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
86
|
+
const links = document.querySelectorAll('nav[aria-label="Page navigation"] a');
|
|
87
|
+
const next = links[links.length - 1] as HTMLAnchorElement;
|
|
88
|
+
if (next && links.length > 1) { next.click(); return; }
|
|
89
|
+
if (next && links.length === 1) {
|
|
90
|
+
const parent = next.closest('.col-span-1');
|
|
91
|
+
if (parent && parent === parent.parentElement?.lastElementChild) { next.click(); }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
initKeyboardShortcuts();
|
|
98
|
+
document.addEventListener('astro:page-load', () => {
|
|
99
|
+
window.__bdShortcutsInit = false;
|
|
100
|
+
initKeyboardShortcuts();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
declare global {
|
|
104
|
+
interface Window {
|
|
105
|
+
__bdShortcutsInit?: boolean;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
@@ -32,7 +32,7 @@ function getLocalizedUrl(locale: string): string {
|
|
|
32
32
|
<div class="relative">
|
|
33
33
|
<button
|
|
34
34
|
id="lang-switcher-btn"
|
|
35
|
-
class="flex items-center gap-1 px-2 py-1.5 text-sm text-[var(--
|
|
35
|
+
class="flex items-center gap-1 px-2 py-1.5 text-sm text-[var(--bd-text-secondary)] hover:text-[var(--bd-text)] rounded-lg hover:bg-[var(--bd-bg-subtle)] transition-colors"
|
|
36
36
|
aria-label="Select language"
|
|
37
37
|
>
|
|
38
38
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -46,7 +46,7 @@ function getLocalizedUrl(locale: string): string {
|
|
|
46
46
|
|
|
47
47
|
<div
|
|
48
48
|
id="lang-switcher-menu"
|
|
49
|
-
class="hidden absolute right-0 mt-1 py-1 w-32 bg-[var(--
|
|
49
|
+
class="hidden absolute right-0 mt-1 py-1 w-32 bg-[var(--bd-bg)] border border-[var(--bd-border)] rounded-lg shadow-lg z-50"
|
|
50
50
|
>
|
|
51
51
|
{locales.map((locale) => (
|
|
52
52
|
<a
|
|
@@ -55,7 +55,7 @@ function getLocalizedUrl(locale: string): string {
|
|
|
55
55
|
"block px-3 py-1.5 text-sm transition-colors",
|
|
56
56
|
locale === currentLocale
|
|
57
57
|
? "bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300"
|
|
58
|
-
: "text-[var(--
|
|
58
|
+
: "text-[var(--bd-text-secondary)] hover:text-[var(--bd-text)] hover:bg-[var(--bd-bg-subtle)]"
|
|
59
59
|
]}
|
|
60
60
|
>
|
|
61
61
|
{getLocaleLabel(locale, i18n?.labels)}
|
|
@@ -54,7 +54,7 @@ export function MobileNavSheet({ groups, siteName, logo }: MobileNavSheetProps)
|
|
|
54
54
|
return (
|
|
55
55
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
56
56
|
<SheetContent side="left" className="w-80 p-0">
|
|
57
|
-
<SheetHeader className="px-6 py-4 border-b border-[var(--
|
|
57
|
+
<SheetHeader className="px-6 py-4 border-b border-[var(--bd-border)]">
|
|
58
58
|
<SheetTitle className="flex items-center gap-2">
|
|
59
59
|
{logo && <img src={logo} alt={siteName} className="h-6 w-6" />}
|
|
60
60
|
<span>{siteName}</span>
|
|
@@ -133,10 +133,10 @@ export function Search() {
|
|
|
133
133
|
>
|
|
134
134
|
<div className="fixed inset-0 bg-black/50" aria-hidden="true" />
|
|
135
135
|
<div className="fixed inset-x-4 top-[15%] md:inset-x-auto md:left-1/2 md:-translate-x-1/2 md:w-full md:max-w-lg">
|
|
136
|
-
<div className="bg-[var(--
|
|
137
|
-
<div className="flex items-center px-4 border-b border-[var(--
|
|
136
|
+
<div className="bg-[var(--bd-bg)] rounded-xl shadow-2xl border border-[var(--bd-border)] overflow-hidden">
|
|
137
|
+
<div className="flex items-center px-4 border-b border-[var(--bd-border)]">
|
|
138
138
|
<svg
|
|
139
|
-
className="w-5 h-5 text-[var(--
|
|
139
|
+
className="w-5 h-5 text-[var(--bd-text-secondary)]"
|
|
140
140
|
fill="none"
|
|
141
141
|
stroke="currentColor"
|
|
142
142
|
viewBox="0 0 24 24"
|
|
@@ -155,11 +155,11 @@ export function Search() {
|
|
|
155
155
|
onChange={(e) => setQuery(e.target.value)}
|
|
156
156
|
onKeyDown={handleKeyDown}
|
|
157
157
|
placeholder="Search documentation..."
|
|
158
|
-
className="flex-1 px-4 py-4 bg-transparent text-[var(--
|
|
158
|
+
className="flex-1 px-4 py-4 bg-transparent text-[var(--bd-text)] placeholder-[var(--bd-text-secondary)] focus:outline-none"
|
|
159
159
|
/>
|
|
160
160
|
<button
|
|
161
161
|
onClick={() => setIsOpen(false)}
|
|
162
|
-
className="px-2 py-1 text-xs text-[var(--
|
|
162
|
+
className="px-2 py-1 text-xs text-[var(--bd-text-secondary)] bg-[var(--bd-bg-subtle)] border border-[var(--bd-border)] rounded"
|
|
163
163
|
>
|
|
164
164
|
ESC
|
|
165
165
|
</button>
|
|
@@ -168,7 +168,7 @@ export function Search() {
|
|
|
168
168
|
{query.trim() && (
|
|
169
169
|
<div className="max-h-[60vh] overflow-y-auto">
|
|
170
170
|
{results.length === 0 ? (
|
|
171
|
-
<div className="px-4 py-8 text-center text-[var(--
|
|
171
|
+
<div className="px-4 py-8 text-center text-[var(--bd-text-secondary)]">
|
|
172
172
|
{pagefind ? "No results found" : "Search index not available"}
|
|
173
173
|
</div>
|
|
174
174
|
) : (
|
|
@@ -180,14 +180,14 @@ export function Search() {
|
|
|
180
180
|
className={`block px-4 py-3 transition-colors ${
|
|
181
181
|
index === selectedIndex
|
|
182
182
|
? "bg-primary-50 dark:bg-primary-900/30"
|
|
183
|
-
: "hover:bg-[var(--
|
|
183
|
+
: "hover:bg-[var(--bd-bg-subtle)]"
|
|
184
184
|
}`}
|
|
185
185
|
>
|
|
186
|
-
<div className="font-medium text-[var(--
|
|
186
|
+
<div className="font-medium text-[var(--bd-text)]">
|
|
187
187
|
{result.meta.title}
|
|
188
188
|
</div>
|
|
189
189
|
<div
|
|
190
|
-
className="text-sm text-[var(--
|
|
190
|
+
className="text-sm text-[var(--bd-text-secondary)] line-clamp-2 mt-0.5"
|
|
191
191
|
dangerouslySetInnerHTML={{ __html: result.excerpt }}
|
|
192
192
|
/>
|
|
193
193
|
</a>
|
|
@@ -199,7 +199,7 @@ export function Search() {
|
|
|
199
199
|
)}
|
|
200
200
|
|
|
201
201
|
{!query.trim() && (
|
|
202
|
-
<div className="px-4 py-6 text-center text-[var(--
|
|
202
|
+
<div className="px-4 py-6 text-center text-[var(--bd-text-secondary)] text-sm">
|
|
203
203
|
Start typing to search...
|
|
204
204
|
</div>
|
|
205
205
|
)}
|