boltdocs 1.6.0 → 1.7.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/dist/{SearchDialog-3QICRMWF.css → SearchDialog-UOAW6IR3.css} +270 -113
- package/dist/{SearchDialog-J3KNRGNO.mjs → SearchDialog-YOXMFGH6.mjs} +1 -1
- package/dist/{chunk-HSPDIRTW.mjs → chunk-MULKZFVN.mjs} +872 -758
- package/dist/client/index.css +270 -113
- package/dist/client/index.d.mts +21 -7
- package/dist/client/index.d.ts +21 -7
- package/dist/client/index.js +637 -499
- package/dist/client/index.mjs +17 -1
- package/dist/client/ssr.css +270 -113
- package/dist/client/ssr.d.mts +3 -1
- package/dist/client/ssr.d.ts +3 -1
- package/dist/client/ssr.js +533 -412
- package/dist/client/ssr.mjs +3 -2
- package/dist/{config-DkZg5aCf.d.ts → config-D68h41CA.d.mts} +21 -2
- package/dist/{config-DkZg5aCf.d.mts → config-D68h41CA.d.ts} +21 -2
- package/dist/node/index.d.mts +10 -2
- package/dist/node/index.d.ts +10 -2
- package/dist/node/index.js +45 -21
- package/dist/node/index.mjs +45 -21
- package/dist/{types-DGIo1VKD.d.mts → types-CviV0GbX.d.mts} +13 -0
- package/dist/{types-DGIo1VKD.d.ts → types-CviV0GbX.d.ts} +13 -0
- package/package.json +1 -1
- package/src/client/app/index.tsx +8 -4
- package/src/client/index.ts +2 -0
- package/src/client/ssr.tsx +4 -1
- package/src/client/theme/components/mdx/Table.tsx +53 -0
- package/src/client/theme/components/mdx/index.ts +3 -0
- package/src/client/theme/components/mdx/mdx-components.css +49 -0
- package/src/client/theme/styles/markdown.css +8 -3
- package/src/client/theme/styles/variables.css +10 -9
- package/src/client/theme/ui/Layout/Layout.tsx +2 -10
- package/src/client/theme/ui/Layout/base.css +15 -3
- package/src/client/theme/ui/Link/Link.tsx +2 -2
- package/src/client/theme/ui/Link/LinkPreview.tsx +9 -14
- package/src/client/theme/ui/Link/link-preview.css +30 -27
- package/src/client/theme/ui/Navbar/Navbar.tsx +65 -17
- package/src/client/theme/ui/Navbar/Tabs.tsx +74 -0
- package/src/client/theme/ui/Navbar/navbar.css +111 -5
- package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +65 -49
- package/src/client/theme/ui/OnThisPage/toc.css +30 -10
- package/src/client/theme/ui/Sidebar/Sidebar.tsx +97 -57
- package/src/client/theme/ui/Sidebar/sidebar.css +61 -67
- package/src/client/types.ts +10 -0
- package/src/node/config.ts +19 -1
- package/src/node/plugin/entry.ts +5 -1
- package/src/node/plugin/index.ts +2 -1
- package/src/node/routes/index.ts +12 -1
- package/src/node/routes/parser.ts +21 -7
- package/src/node/routes/types.ts +9 -1
- package/src/node/ssg/index.ts +2 -1
- package/src/node/ssg/options.ts +2 -0
package/src/client/ssr.tsx
CHANGED
|
@@ -14,6 +14,8 @@ export interface RenderOptions {
|
|
|
14
14
|
routes: ComponentRoute[];
|
|
15
15
|
/** Site configuration (`virtual:boltdocs-config`) */
|
|
16
16
|
config: any;
|
|
17
|
+
/** The name of the documentation directory (e.g. 'docs') */
|
|
18
|
+
docsDirName: string;
|
|
17
19
|
/** Optional custom React component to render when visiting the root path ('/') */
|
|
18
20
|
homePage?: React.ComponentType;
|
|
19
21
|
/** Preloaded modules (since SSR cannot use dynamic imports easily) */
|
|
@@ -25,7 +27,7 @@ export interface RenderOptions {
|
|
|
25
27
|
* This is called by the Node SSG script during the Vite build process.
|
|
26
28
|
*/
|
|
27
29
|
export async function render(options: RenderOptions): Promise<string> {
|
|
28
|
-
const { path, routes, config, modules, homePage } = options;
|
|
30
|
+
const { path, routes, config, modules, homePage, docsDirName } = options;
|
|
29
31
|
|
|
30
32
|
// For SSR, we must resolve modules synchronously. We create a mock 'loader'
|
|
31
33
|
// that instantly returns the module since the SSG script already loaded it.
|
|
@@ -40,6 +42,7 @@ export async function render(options: RenderOptions): Promise<string> {
|
|
|
40
42
|
<AppShell
|
|
41
43
|
initialRoutes={routes}
|
|
42
44
|
initialConfig={config}
|
|
45
|
+
docsDirName={docsDirName}
|
|
43
46
|
modules={resolvedModules}
|
|
44
47
|
homePage={homePage}
|
|
45
48
|
/>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface TableProps {
|
|
4
|
+
headers?: string[];
|
|
5
|
+
data?: (string | React.ReactNode)[][];
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A consistent, themed table component for documentation.
|
|
12
|
+
* Can be used by passing structured 'headers' and 'data' props,
|
|
13
|
+
* or by wrapping standard <thead>/<tbody> elements.
|
|
14
|
+
*/
|
|
15
|
+
export function Table({
|
|
16
|
+
headers,
|
|
17
|
+
data,
|
|
18
|
+
children,
|
|
19
|
+
className = "",
|
|
20
|
+
}: TableProps) {
|
|
21
|
+
const tableContent = children ? (
|
|
22
|
+
children
|
|
23
|
+
) : (
|
|
24
|
+
<>
|
|
25
|
+
{headers && (
|
|
26
|
+
<thead>
|
|
27
|
+
<tr>
|
|
28
|
+
{headers.map((header, i) => (
|
|
29
|
+
<th key={i}>{header}</th>
|
|
30
|
+
))}
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
)}
|
|
34
|
+
{data && (
|
|
35
|
+
<tbody>
|
|
36
|
+
{data.map((row, i) => (
|
|
37
|
+
<tr key={i}>
|
|
38
|
+
{row.map((cell, j) => (
|
|
39
|
+
<td key={j}>{cell}</td>
|
|
40
|
+
))}
|
|
41
|
+
</tr>
|
|
42
|
+
))}
|
|
43
|
+
</tbody>
|
|
44
|
+
)}
|
|
45
|
+
</>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className={`ld-table-container ${className}`.trim()}>
|
|
50
|
+
<table className="ld-table">{tableContent}</table>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -539,3 +539,52 @@
|
|
|
539
539
|
.ld-file-tree__icon--spacer {
|
|
540
540
|
width: 14px;
|
|
541
541
|
}
|
|
542
|
+
|
|
543
|
+
/* ─── Table ───────────────────────────────────────────────── */
|
|
544
|
+
.ld-table-container {
|
|
545
|
+
margin: 1.5rem 0;
|
|
546
|
+
border: 1px solid var(--ld-border-subtle);
|
|
547
|
+
border-radius: var(--ld-radius-lg);
|
|
548
|
+
overflow: hidden;
|
|
549
|
+
background: var(--ld-bg-soft);
|
|
550
|
+
overflow-x: auto;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.ld-table {
|
|
554
|
+
width: 100%;
|
|
555
|
+
border-collapse: collapse;
|
|
556
|
+
text-align: left;
|
|
557
|
+
font-size: 0.875rem;
|
|
558
|
+
line-height: 1.5;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.ld-table thead {
|
|
562
|
+
background: var(--ld-bg-mute);
|
|
563
|
+
border-bottom: 1px solid var(--ld-border-subtle);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.ld-table th {
|
|
567
|
+
padding: 0.75rem 1rem;
|
|
568
|
+
font-weight: 600;
|
|
569
|
+
color: var(--ld-text-main);
|
|
570
|
+
font-size: 0.8125rem;
|
|
571
|
+
text-transform: uppercase;
|
|
572
|
+
letter-spacing: 0.04em;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.ld-table td {
|
|
576
|
+
padding: 0.875rem 1rem;
|
|
577
|
+
color: var(--ld-text-muted);
|
|
578
|
+
border-bottom: 1px solid var(--ld-border-subtle);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.ld-table tr:last-child td {
|
|
582
|
+
border-bottom: none;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.ld-table code {
|
|
586
|
+
font-size: 0.8rem;
|
|
587
|
+
padding: 0.2rem 0.4rem;
|
|
588
|
+
background: rgba(255, 255, 255, 0.05);
|
|
589
|
+
border-radius: 4px;
|
|
590
|
+
}
|
|
@@ -3,19 +3,24 @@
|
|
|
3
3
|
═══════════════════════════════════════════════════════════ */
|
|
4
4
|
.boltdocs-content {
|
|
5
5
|
flex: 1;
|
|
6
|
-
padding: 2rem 2.5rem
|
|
6
|
+
padding: 2rem 2.5rem 6rem; /* Extra padding bottom for fixed layout */
|
|
7
7
|
max-width: var(--ld-content-max-width);
|
|
8
8
|
margin: 0 auto;
|
|
9
9
|
min-width: 0;
|
|
10
|
+
height: 100%;
|
|
11
|
+
overflow-y: auto;
|
|
12
|
+
scrollbar-width: none;
|
|
13
|
+
scroll-behavior: smooth;
|
|
10
14
|
transition:
|
|
11
15
|
max-width 0.3s cubic-bezier(0.16, 1, 0.3, 1),
|
|
12
16
|
padding 0.3s ease;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
.boltdocs-
|
|
16
|
-
|
|
19
|
+
.boltdocs-content::-webkit-scrollbar {
|
|
20
|
+
display: none;
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
|
|
19
24
|
/* ─── Breadcrumbs ────────────────────────────────────────── */
|
|
20
25
|
.boltdocs-breadcrumbs {
|
|
21
26
|
margin-bottom: 1.5rem;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
:root[data-theme="light"],
|
|
9
9
|
:root.theme-light {
|
|
10
10
|
--ld-bg-main: #ffffff;
|
|
11
|
-
--ld-bg-soft: #
|
|
11
|
+
--ld-bg-soft: #eeeeee;
|
|
12
12
|
--ld-bg-mute: #f3f4f6;
|
|
13
13
|
|
|
14
14
|
--ld-surface: #ffffff;
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
--ld-sidebar-bg: transparent;
|
|
47
47
|
--ld-sidebar-blur: 0px;
|
|
48
48
|
--ld-glow-1-bg: var(--ld-color-primary-glow);
|
|
49
|
-
--ld-glow-2-bg: rgba(
|
|
49
|
+
--ld-glow-2-bg: rgba(215, 59, 246, 0.15);
|
|
50
50
|
|
|
51
51
|
/* ─ UI Components (overridable independently from layout) ─ */
|
|
52
52
|
--ld-ui-btn-primary-bg: var(--ld-btn-primary-bg);
|
|
@@ -76,10 +76,10 @@
|
|
|
76
76
|
|
|
77
77
|
:root {
|
|
78
78
|
/* ─ Base palette ─ */
|
|
79
|
-
--ld-bg-main: #
|
|
80
|
-
--ld-bg-soft:
|
|
81
|
-
--ld-bg-mute: #
|
|
82
|
-
--ld-surface: #
|
|
79
|
+
--ld-bg-main: #000;
|
|
80
|
+
--ld-bg-soft: #12121272;
|
|
81
|
+
--ld-bg-mute: #090909;
|
|
82
|
+
--ld-surface: #1a1a1a;
|
|
83
83
|
--ld-border-subtle: rgba(255, 255, 255, 0.06);
|
|
84
84
|
--ld-border-strong: rgba(255, 255, 255, 0.12);
|
|
85
85
|
|
|
@@ -107,17 +107,17 @@
|
|
|
107
107
|
--ld-gradient-to: rgba(255, 255, 255, 0.7);
|
|
108
108
|
|
|
109
109
|
/* ─ Code ─ */
|
|
110
|
-
--ld-code-bg: #
|
|
110
|
+
--ld-code-bg: #050505;
|
|
111
111
|
--ld-code-header: #111119;
|
|
112
112
|
--ld-code-text: #d4d4d4;
|
|
113
113
|
|
|
114
114
|
/* ─ Customization ─ */
|
|
115
|
-
--ld-navbar-bg:
|
|
115
|
+
--ld-navbar-bg: #000;
|
|
116
116
|
--ld-navbar-blur: 12px;
|
|
117
117
|
--ld-sidebar-bg: transparent;
|
|
118
118
|
--ld-sidebar-blur: 0px;
|
|
119
119
|
--ld-glow-1-bg: var(--ld-color-primary-glow);
|
|
120
|
-
--ld-glow-2-bg: rgba(
|
|
120
|
+
--ld-glow-2-bg: rgba(246, 59, 187, 0.15);
|
|
121
121
|
|
|
122
122
|
/* ─ UI Components (dark) ─ */
|
|
123
123
|
--ld-ui-btn-primary-bg: var(--ld-btn-primary-bg);
|
|
@@ -152,6 +152,7 @@
|
|
|
152
152
|
|
|
153
153
|
/* ─ Dimensions ─ */
|
|
154
154
|
--ld-navbar-height: 3.5rem;
|
|
155
|
+
--ld-header-height: var(--ld-navbar-height); /* Default sync */
|
|
155
156
|
--ld-sidebar-width: 14.5rem;
|
|
156
157
|
--ld-toc-width: 13rem;
|
|
157
158
|
--ld-content-max-width: 820px;
|
|
@@ -63,7 +63,6 @@ export function ThemeLayout({
|
|
|
63
63
|
className = "",
|
|
64
64
|
style,
|
|
65
65
|
}: ThemeLayoutProps) {
|
|
66
|
-
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
|
67
66
|
const siteTitle = config.themeConfig?.title || "Boltdocs";
|
|
68
67
|
const siteDescription = config.themeConfig?.description || "";
|
|
69
68
|
const location = useLocation();
|
|
@@ -130,18 +129,11 @@ export function ThemeLayout({
|
|
|
130
129
|
currentVersion={currentVersion}
|
|
131
130
|
/>
|
|
132
131
|
)}
|
|
133
|
-
<div
|
|
134
|
-
className={`boltdocs-main-container ${!isSidebarOpen ? "sidebar-collapsed" : ""}`}
|
|
135
|
-
>
|
|
132
|
+
<div className="boltdocs-main-container">
|
|
136
133
|
{sidebar !== undefined ? (
|
|
137
134
|
sidebar
|
|
138
135
|
) : (
|
|
139
|
-
<Sidebar
|
|
140
|
-
routes={filteredRoutes}
|
|
141
|
-
config={config}
|
|
142
|
-
isCollapsed={!isSidebarOpen}
|
|
143
|
-
onToggle={() => setIsSidebarOpen(!isSidebarOpen)}
|
|
144
|
-
/>
|
|
136
|
+
<Sidebar routes={filteredRoutes} config={config} />
|
|
145
137
|
)}
|
|
146
138
|
|
|
147
139
|
<main className="boltdocs-content">
|
|
@@ -5,8 +5,15 @@
|
|
|
5
5
|
box-sizing: border-box;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
html,
|
|
8
9
|
body {
|
|
9
10
|
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
height: 100%;
|
|
13
|
+
overflow: hidden; /* Lock the window scroll */
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
10
17
|
font-family: var(--ld-font-sans);
|
|
11
18
|
background-color: var(--ld-bg-main);
|
|
12
19
|
color: var(--ld-text-main);
|
|
@@ -27,7 +34,9 @@ a {
|
|
|
27
34
|
.boltdocs-layout {
|
|
28
35
|
display: flex;
|
|
29
36
|
flex-direction: column;
|
|
30
|
-
|
|
37
|
+
height: 100vh;
|
|
38
|
+
width: 100vw;
|
|
39
|
+
overflow: hidden;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
.boltdocs-main-container {
|
|
@@ -36,8 +45,11 @@ a {
|
|
|
36
45
|
width: 100%;
|
|
37
46
|
max-width: 1440px;
|
|
38
47
|
margin: 0 auto;
|
|
39
|
-
padding: 0
|
|
40
|
-
/*
|
|
48
|
+
padding: 0 0.5rem;
|
|
49
|
+
/* Fixed height layout */
|
|
50
|
+
height: calc(100vh - var(--ld-header-height));
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
align-items: stretch; /* Sidebars fill height naturally */
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
/* ─── Background Glow ────────────────────────────────────── */
|
|
@@ -232,7 +232,7 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
|
|
|
232
232
|
onClick={handleClick}
|
|
233
233
|
{...rest}
|
|
234
234
|
/>
|
|
235
|
-
{shouldShowPreview && (
|
|
235
|
+
{preview.visible && shouldShowPreview && (
|
|
236
236
|
<LinkPreview
|
|
237
237
|
isVisible={preview.visible}
|
|
238
238
|
title={preview.title}
|
|
@@ -376,7 +376,7 @@ export const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(
|
|
|
376
376
|
onClick={handleClick}
|
|
377
377
|
{...rest}
|
|
378
378
|
/>
|
|
379
|
-
{shouldShowPreview && (
|
|
379
|
+
{preview.visible && shouldShowPreview && (
|
|
380
380
|
<LinkPreview
|
|
381
381
|
isVisible={preview.visible}
|
|
382
382
|
title={preview.title}
|
|
@@ -17,35 +17,28 @@ export function LinkPreview({
|
|
|
17
17
|
x,
|
|
18
18
|
y,
|
|
19
19
|
}: LinkPreviewProps) {
|
|
20
|
-
const [mounted, setMounted] = useState(false);
|
|
21
20
|
const ref = useRef<HTMLDivElement>(null);
|
|
22
21
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
23
22
|
|
|
24
23
|
useEffect(() => {
|
|
25
|
-
|
|
26
|
-
}, []);
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
if (isVisible && ref.current) {
|
|
24
|
+
if (ref.current) {
|
|
30
25
|
const rect = ref.current.getBoundingClientRect();
|
|
31
|
-
const padding =
|
|
26
|
+
const padding = 12;
|
|
32
27
|
|
|
33
28
|
let top = y + padding;
|
|
34
29
|
let left = x + padding;
|
|
35
30
|
|
|
36
31
|
// Keep within viewport
|
|
37
|
-
if (left + rect.width > window.innerWidth) {
|
|
32
|
+
if (left + rect.width > window.innerWidth - 20) {
|
|
38
33
|
left = x - rect.width - padding;
|
|
39
34
|
}
|
|
40
|
-
if (top + rect.height > window.innerHeight) {
|
|
35
|
+
if (top + rect.height > window.innerHeight - 20) {
|
|
41
36
|
top = y - rect.height - padding;
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
setPosition({ top, left });
|
|
45
40
|
}
|
|
46
|
-
}, [
|
|
47
|
-
|
|
48
|
-
if (!mounted) return null;
|
|
41
|
+
}, [x, y, isVisible]);
|
|
49
42
|
|
|
50
43
|
return createPortal(
|
|
51
44
|
<div
|
|
@@ -56,8 +49,10 @@ export function LinkPreview({
|
|
|
56
49
|
left: position.left,
|
|
57
50
|
}}
|
|
58
51
|
>
|
|
59
|
-
<
|
|
60
|
-
|
|
52
|
+
<div className="boltdocs-link-preview-content">
|
|
53
|
+
<span className="boltdocs-link-preview-title">{title}</span>
|
|
54
|
+
{summary && <p className="boltdocs-link-preview-summary">{summary}</p>}
|
|
55
|
+
</div>
|
|
61
56
|
</div>,
|
|
62
57
|
document.body,
|
|
63
58
|
);
|
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
.boltdocs-link-preview {
|
|
2
2
|
position: fixed;
|
|
3
3
|
z-index: 1000;
|
|
4
|
-
width:
|
|
5
|
-
padding: 1rem;
|
|
6
|
-
background-color: var(--ld-navbar-bg);
|
|
7
|
-
backdrop-filter: blur(var(--ld-navbar-blur));
|
|
8
|
-
-webkit-backdrop-filter: blur(var(--ld-navbar-blur));
|
|
9
|
-
border: 1px solid var(--ld-border-subtle);
|
|
10
|
-
border-radius: var(--ld-radius-md);
|
|
11
|
-
box-shadow:
|
|
12
|
-
0 10px 25px -5px rgba(0, 0, 0, 0.1),
|
|
13
|
-
0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
4
|
+
width: 260px;
|
|
14
5
|
pointer-events: none;
|
|
15
6
|
opacity: 0;
|
|
16
|
-
transform: translateY(
|
|
7
|
+
transform: translateY(8px) scale(0.98);
|
|
17
8
|
transition:
|
|
18
|
-
opacity 0.
|
|
19
|
-
transform 0.
|
|
9
|
+
opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1),
|
|
10
|
+
transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
|
20
11
|
font-family: var(--ld-font-sans);
|
|
21
12
|
}
|
|
22
13
|
|
|
@@ -25,38 +16,50 @@
|
|
|
25
16
|
transform: translateY(0) scale(1);
|
|
26
17
|
}
|
|
27
18
|
|
|
19
|
+
.boltdocs-link-preview-content {
|
|
20
|
+
padding: 0.85rem 1rem;
|
|
21
|
+
background-color: var(--ld-navbar-bg);
|
|
22
|
+
backdrop-filter: blur(20px);
|
|
23
|
+
-webkit-backdrop-filter: blur(20px);
|
|
24
|
+
border: 1px solid var(--ld-border-subtle);
|
|
25
|
+
border-radius: var(--ld-radius-lg);
|
|
26
|
+
box-shadow:
|
|
27
|
+
0 10px 30px -10px rgba(0, 0, 0, 0.2),
|
|
28
|
+
0 4px 10px -5px rgba(0, 0, 0, 0.1);
|
|
29
|
+
}
|
|
30
|
+
|
|
28
31
|
.boltdocs-link-preview-title {
|
|
29
32
|
display: block;
|
|
30
33
|
font-weight: 600;
|
|
31
|
-
font-size: 0.
|
|
34
|
+
font-size: 0.875rem;
|
|
32
35
|
color: var(--ld-text-main);
|
|
33
|
-
margin-bottom: 0.
|
|
34
|
-
line-height: 1.
|
|
36
|
+
margin-bottom: 0.35rem;
|
|
37
|
+
line-height: 1.3;
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
.boltdocs-link-preview-summary {
|
|
38
41
|
display: block;
|
|
39
|
-
font-size: 0.
|
|
42
|
+
font-size: 0.775rem;
|
|
40
43
|
color: var(--ld-text-muted);
|
|
41
44
|
line-height: 1.5;
|
|
42
45
|
display: -webkit-box;
|
|
43
|
-
-webkit-line-clamp:
|
|
44
|
-
line-clamp:
|
|
46
|
+
-webkit-line-clamp: 3;
|
|
47
|
+
line-clamp: 3;
|
|
45
48
|
-webkit-box-orient: vertical;
|
|
46
49
|
overflow: hidden;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
/* Dark mode
|
|
50
|
-
[data-theme="dark"] .boltdocs-link-preview {
|
|
51
|
-
background-color:
|
|
52
|
-
border-color:
|
|
53
|
-
box-shadow:
|
|
54
|
-
0 20px
|
|
55
|
-
0
|
|
52
|
+
/* Dark mode refinements */
|
|
53
|
+
[data-theme="dark"] .boltdocs-link-preview-content {
|
|
54
|
+
background-color: rgba(15, 15, 20, 0.8);
|
|
55
|
+
border-color: rgba(255, 255, 255, 0.08);
|
|
56
|
+
box-shadow:
|
|
57
|
+
0 20px 40px -15px rgba(0, 0, 0, 0.5),
|
|
58
|
+
0 8px 16px -8px rgba(0, 0, 0, 0.3);
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
[data-theme="dark"] .boltdocs-link-preview-title {
|
|
59
|
-
color: #
|
|
62
|
+
color: #fff;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
[data-theme="dark"] .boltdocs-link-preview-summary {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { Link } from "
|
|
3
|
-
import {
|
|
1
|
+
import React, { Suspense, lazy } from "react";
|
|
2
|
+
import { Link, useLocation } from "react-router-dom";
|
|
3
|
+
import { ChevronDown } from "lucide-react";
|
|
4
4
|
import { BoltdocsConfig } from "../../../../node/config";
|
|
5
5
|
import { ComponentRoute } from "../../../types";
|
|
6
6
|
import { LanguageSwitcher } from "../LanguageSwitcher";
|
|
7
7
|
import { VersionSwitcher } from "../VersionSwitcher";
|
|
8
8
|
import { ThemeToggle } from "../ThemeToggle";
|
|
9
|
-
import { getStarsRepo } from "../../../utils";
|
|
10
9
|
import { Discord } from "../../icons/discord";
|
|
11
10
|
import { XformerlyTwitter } from "../../icons/twitter";
|
|
12
11
|
import { GithubStars } from "./GithubStars";
|
|
12
|
+
import { Tabs } from "./Tabs";
|
|
13
13
|
|
|
14
|
-
const SearchDialog =
|
|
14
|
+
const SearchDialog = lazy(() =>
|
|
15
15
|
import("../SearchDialog").then((m) => ({ default: m.SearchDialog })),
|
|
16
16
|
);
|
|
17
17
|
|
|
@@ -43,13 +43,56 @@ export function Navbar({
|
|
|
43
43
|
currentLocale?: string;
|
|
44
44
|
currentVersion?: string;
|
|
45
45
|
}) {
|
|
46
|
+
const location = useLocation();
|
|
47
|
+
const isHomePage = location.pathname === "/";
|
|
46
48
|
const title = config.themeConfig?.title || "Boltdocs";
|
|
47
49
|
const navItems = config.themeConfig?.navbar || [];
|
|
48
50
|
const socialLinks = config.themeConfig?.socialLinks || [];
|
|
51
|
+
const hasTabs =
|
|
52
|
+
!isHomePage &&
|
|
53
|
+
config.themeConfig?.tabs &&
|
|
54
|
+
config.themeConfig.tabs.length > 0;
|
|
55
|
+
|
|
56
|
+
const leftItems = navItems.filter((item) => item.position !== "right");
|
|
57
|
+
const rightItems = navItems.filter((item) => item.position === "right");
|
|
58
|
+
|
|
59
|
+
const renderNavItem = (item: any, i: number) => {
|
|
60
|
+
const text = item.label || item.text || "";
|
|
61
|
+
const href = item.to || item.href || item.link || "";
|
|
62
|
+
const isExternal =
|
|
63
|
+
href.startsWith("http") || href.startsWith("//") || href.includes("://");
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Link key={i} to={href} target={isExternal ? "_blank" : undefined}>
|
|
67
|
+
{text}
|
|
68
|
+
{isExternal && (
|
|
69
|
+
<span className="navbar-external-icon">
|
|
70
|
+
<svg
|
|
71
|
+
viewBox="0 0 24 24"
|
|
72
|
+
width="13"
|
|
73
|
+
height="13"
|
|
74
|
+
stroke="currentColor"
|
|
75
|
+
strokeWidth="2"
|
|
76
|
+
fill="none"
|
|
77
|
+
strokeLinecap="round"
|
|
78
|
+
strokeLinejoin="round"
|
|
79
|
+
>
|
|
80
|
+
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
|
81
|
+
<polyline points="15 3 21 3 21 9"></polyline>
|
|
82
|
+
<line x1="10" y1="14" x2="21" y2="3"></line>
|
|
83
|
+
</svg>
|
|
84
|
+
</span>
|
|
85
|
+
)}
|
|
86
|
+
</Link>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
49
89
|
|
|
50
90
|
return (
|
|
51
|
-
<header className=
|
|
52
|
-
<div
|
|
91
|
+
<header className={`boltdocs-navbar ${hasTabs ? "has-tabs" : ""}`}>
|
|
92
|
+
<div
|
|
93
|
+
className="navbar-container"
|
|
94
|
+
style={{ height: "var(--ld-navbar-height)" }}
|
|
95
|
+
>
|
|
53
96
|
{/* LEFT SECTION */}
|
|
54
97
|
<div className="navbar-left">
|
|
55
98
|
<div className="navbar-logo">
|
|
@@ -87,22 +130,20 @@ export function Navbar({
|
|
|
87
130
|
</div>
|
|
88
131
|
) : null}
|
|
89
132
|
|
|
90
|
-
<nav className="navbar-links" aria-label="Top Navigation">
|
|
91
|
-
{
|
|
92
|
-
<Link key={i} to={item.link}>
|
|
93
|
-
{item.text}
|
|
94
|
-
</Link>
|
|
95
|
-
))}
|
|
133
|
+
<nav className="navbar-links" aria-label="Top Navigation Left">
|
|
134
|
+
{leftItems.map(renderNavItem)}
|
|
96
135
|
</nav>
|
|
97
136
|
</div>
|
|
98
137
|
|
|
99
138
|
{/* RIGHT SECTION */}
|
|
100
139
|
<div className="navbar-right">
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
>
|
|
140
|
+
<nav className="navbar-links" aria-label="Top Navigation Right">
|
|
141
|
+
{rightItems.map(renderNavItem)}
|
|
142
|
+
</nav>
|
|
143
|
+
|
|
144
|
+
<Suspense fallback={<div className="navbar-search-placeholder" />}>
|
|
104
145
|
<SearchDialog routes={routes || []} />
|
|
105
|
-
</
|
|
146
|
+
</Suspense>
|
|
106
147
|
|
|
107
148
|
{config.i18n && currentLocale && allRoutes && (
|
|
108
149
|
<LanguageSwitcher
|
|
@@ -140,6 +181,13 @@ export function Navbar({
|
|
|
140
181
|
</div>
|
|
141
182
|
</div>
|
|
142
183
|
</div>
|
|
184
|
+
|
|
185
|
+
{hasTabs && config.themeConfig?.tabs && (
|
|
186
|
+
<Tabs
|
|
187
|
+
tabs={config.themeConfig.tabs}
|
|
188
|
+
routes={allRoutes || routes || []}
|
|
189
|
+
/>
|
|
190
|
+
)}
|
|
143
191
|
</header>
|
|
144
192
|
);
|
|
145
193
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useLocation } from "react-router-dom";
|
|
3
|
+
import { Link } from "../Link";
|
|
4
|
+
import * as Icons from "lucide-react";
|
|
5
|
+
|
|
6
|
+
interface TabConfig {
|
|
7
|
+
id: string;
|
|
8
|
+
text: string;
|
|
9
|
+
icon?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface TabsProps {
|
|
13
|
+
tabs: TabConfig[];
|
|
14
|
+
routes: any[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Tabs({ tabs, routes }: TabsProps) {
|
|
18
|
+
const location = useLocation();
|
|
19
|
+
const currentRoute = routes.find((r) => r.path === location.pathname);
|
|
20
|
+
const currentTabId = currentRoute?.tab?.toLowerCase();
|
|
21
|
+
|
|
22
|
+
if (!tabs || tabs.length === 0) return null;
|
|
23
|
+
|
|
24
|
+
const renderTabIcon = (iconName?: string) => {
|
|
25
|
+
if (!iconName) return null;
|
|
26
|
+
|
|
27
|
+
// 1. Raw SVG
|
|
28
|
+
if (iconName.trim().startsWith("<svg")) {
|
|
29
|
+
return (
|
|
30
|
+
<span
|
|
31
|
+
className="tab-icon svg-icon"
|
|
32
|
+
dangerouslySetInnerHTML={{ __html: iconName }}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Lucide Icon
|
|
38
|
+
const LucideIcon = (Icons as any)[iconName];
|
|
39
|
+
if (LucideIcon) {
|
|
40
|
+
return <LucideIcon size={16} className="tab-icon lucide-icon" />;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 3. Fallback to image URL
|
|
44
|
+
return <img src={iconName} alt="" className="tab-icon img-icon" />;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="boltdocs-tabs-container">
|
|
49
|
+
<div className="boltdocs-tabs">
|
|
50
|
+
{tabs.map((tab, index) => {
|
|
51
|
+
// If no tab is detected (e.g. root home page), default to the first tab (usually "Guides")
|
|
52
|
+
const isActive = currentTabId
|
|
53
|
+
? currentTabId === tab.id.toLowerCase()
|
|
54
|
+
: index === 0;
|
|
55
|
+
|
|
56
|
+
// Find the first route for this tab to link to it
|
|
57
|
+
const firstRoute = routes.find(r => r.tab && r.tab.toLowerCase() === tab.id.toLowerCase());
|
|
58
|
+
const linkTo = firstRoute ? firstRoute.path : "#";
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Link
|
|
62
|
+
key={tab.id}
|
|
63
|
+
to={linkTo}
|
|
64
|
+
className={`boltdocs-tab-item ${isActive ? "active" : ""}`}
|
|
65
|
+
>
|
|
66
|
+
{renderTabIcon(tab.icon)}
|
|
67
|
+
<span>{tab.text}</span>
|
|
68
|
+
</Link>
|
|
69
|
+
);
|
|
70
|
+
})}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|