boltdocs 1.6.0 → 1.7.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/dist/{SearchDialog-J3KNRGNO.mjs → SearchDialog-6Z7CUAYJ.mjs} +8 -1
- package/dist/{SearchDialog-3QICRMWF.css → SearchDialog-GOZ6X53X.css} +385 -113
- package/dist/{chunk-HSPDIRTW.mjs → chunk-SFVOGJ2W.mjs} +955 -737
- package/dist/client/index.css +385 -113
- package/dist/client/index.d.mts +19 -7
- package/dist/client/index.d.ts +19 -7
- package/dist/client/index.js +964 -577
- package/dist/client/index.mjs +118 -1
- package/dist/client/ssr.css +385 -113
- package/dist/client/ssr.d.mts +3 -1
- package/dist/client/ssr.d.ts +3 -1
- package/dist/client/ssr.js +743 -474
- 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 +12 -2
- package/dist/node/index.d.ts +12 -2
- package/dist/node/index.js +48 -21
- package/dist/node/index.mjs +48 -21
- package/dist/{types-DGIo1VKD.d.mts → types-BbceAHA0.d.mts} +15 -0
- package/dist/{types-DGIo1VKD.d.ts → types-BbceAHA0.d.ts} +15 -0
- package/package.json +1 -1
- package/src/client/app/index.tsx +16 -11
- package/src/client/index.ts +2 -0
- package/src/client/ssr.tsx +4 -1
- package/src/client/theme/components/mdx/Table.tsx +151 -0
- package/src/client/theme/components/mdx/index.ts +3 -0
- package/src/client/theme/components/mdx/mdx-components.css +128 -0
- package/src/client/theme/styles/markdown.css +8 -3
- package/src/client/theme/styles/variables.css +34 -9
- package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +46 -0
- package/src/client/theme/ui/ErrorBoundary/index.ts +1 -0
- package/src/client/theme/ui/Layout/Layout.tsx +10 -11
- 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 +99 -0
- package/src/client/theme/ui/Navbar/navbar.css +119 -5
- package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +66 -57
- package/src/client/theme/ui/OnThisPage/toc.css +30 -10
- package/src/client/theme/ui/ProgressBar/ProgressBar.css +17 -0
- package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +51 -0
- package/src/client/theme/ui/ProgressBar/index.ts +1 -0
- package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +11 -1
- 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 +12 -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 +13 -1
- package/src/node/routes/parser.ts +32 -7
- package/src/node/routes/types.ts +11 -1
- package/src/node/ssg/index.ts +2 -1
- package/src/node/ssg/options.ts +2 -0
package/src/client/app/index.tsx
CHANGED
|
@@ -89,6 +89,7 @@ const mdxComponents = {
|
|
|
89
89
|
export function AppShell({
|
|
90
90
|
initialRoutes,
|
|
91
91
|
initialConfig,
|
|
92
|
+
docsDirName,
|
|
92
93
|
modules,
|
|
93
94
|
hot,
|
|
94
95
|
homePage: HomePage,
|
|
@@ -96,6 +97,7 @@ export function AppShell({
|
|
|
96
97
|
}: {
|
|
97
98
|
initialRoutes: ComponentRoute[];
|
|
98
99
|
initialConfig: any;
|
|
100
|
+
docsDirName: string;
|
|
99
101
|
modules: Record<string, () => Promise<any>>;
|
|
100
102
|
hot?: any;
|
|
101
103
|
homePage?: React.ComponentType;
|
|
@@ -110,8 +112,8 @@ export function AppShell({
|
|
|
110
112
|
(route) => !(HomePage && (route.path === "/" || route.path === "")),
|
|
111
113
|
)
|
|
112
114
|
.map((route) => {
|
|
113
|
-
const loaderKey = Object.keys(modules).find(
|
|
114
|
-
k
|
|
115
|
+
const loaderKey = Object.keys(modules).find(
|
|
116
|
+
(k) => k === `/${docsDirName}/${route.filePath}`,
|
|
115
117
|
);
|
|
116
118
|
const loader = loaderKey ? modules[loaderKey] : null;
|
|
117
119
|
|
|
@@ -142,7 +144,7 @@ export function AppShell({
|
|
|
142
144
|
// Sync resolved routes when info or modules change
|
|
143
145
|
useEffect(() => {
|
|
144
146
|
setResolvedRoutes(resolveRoutes(routesInfo));
|
|
145
|
-
}, [routesInfo, modules]);
|
|
147
|
+
}, [routesInfo, modules, docsDirName]);
|
|
146
148
|
|
|
147
149
|
return (
|
|
148
150
|
<ConfigContext.Provider value={config}>
|
|
@@ -214,26 +216,27 @@ function ScrollHandler() {
|
|
|
214
216
|
const { pathname, hash } = useLocation();
|
|
215
217
|
|
|
216
218
|
useLayoutEffect(() => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
+
const container = document.querySelector(".boltdocs-content");
|
|
220
|
+
if (!container) return;
|
|
221
|
+
|
|
219
222
|
if (hash) {
|
|
220
223
|
const id = hash.replace("#", "");
|
|
221
224
|
const element = document.getElementById(id);
|
|
222
225
|
if (element) {
|
|
223
226
|
const offset = 80;
|
|
224
|
-
const
|
|
227
|
+
const containerRect = container.getBoundingClientRect().top;
|
|
225
228
|
const elementRect = element.getBoundingClientRect().top;
|
|
226
|
-
const elementPosition = elementRect -
|
|
227
|
-
const offsetPosition = elementPosition - offset;
|
|
229
|
+
const elementPosition = elementRect - containerRect;
|
|
230
|
+
const offsetPosition = elementPosition - offset + container.scrollTop;
|
|
228
231
|
|
|
229
|
-
|
|
232
|
+
container.scrollTo({
|
|
230
233
|
top: offsetPosition,
|
|
231
234
|
behavior: "smooth",
|
|
232
235
|
});
|
|
233
236
|
return;
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
|
-
|
|
239
|
+
container.scrollTo(0, 0);
|
|
237
240
|
}, [pathname, hash]);
|
|
238
241
|
|
|
239
242
|
return null;
|
|
@@ -302,7 +305,8 @@ function MdxPage({
|
|
|
302
305
|
* ```
|
|
303
306
|
*/
|
|
304
307
|
export function createBoltdocsApp(options: CreateBoltdocsAppOptions) {
|
|
305
|
-
const { target, routes, config, modules, hot, homePage } =
|
|
308
|
+
const { target, routes, docsDirName, config, modules, hot, homePage } =
|
|
309
|
+
options;
|
|
306
310
|
const container = document.querySelector(target);
|
|
307
311
|
if (!container) {
|
|
308
312
|
throw new Error(
|
|
@@ -316,6 +320,7 @@ export function createBoltdocsApp(options: CreateBoltdocsAppOptions) {
|
|
|
316
320
|
<AppShell
|
|
317
321
|
initialRoutes={routes}
|
|
318
322
|
initialConfig={config}
|
|
323
|
+
docsDirName={docsDirName}
|
|
319
324
|
modules={modules}
|
|
320
325
|
hot={hot}
|
|
321
326
|
homePage={homePage}
|
package/src/client/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ export {
|
|
|
29
29
|
InfoBox,
|
|
30
30
|
List,
|
|
31
31
|
FileTree,
|
|
32
|
+
Table,
|
|
32
33
|
} from "./theme/components/mdx";
|
|
33
34
|
export type {
|
|
34
35
|
ButtonProps,
|
|
@@ -40,4 +41,5 @@ export type {
|
|
|
40
41
|
AdmonitionProps,
|
|
41
42
|
ListProps,
|
|
42
43
|
FileTreeProps,
|
|
44
|
+
TableProps,
|
|
43
45
|
} from "./theme/components/mdx";
|
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,151 @@
|
|
|
1
|
+
import React, { useState, useMemo } from "react";
|
|
2
|
+
import { ChevronUp, ChevronDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export interface TableProps {
|
|
5
|
+
headers?: string[];
|
|
6
|
+
data?: (string | React.ReactNode)[][];
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
sortable?: boolean;
|
|
10
|
+
paginated?: boolean;
|
|
11
|
+
pageSize?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Table({
|
|
15
|
+
headers,
|
|
16
|
+
data,
|
|
17
|
+
children,
|
|
18
|
+
className = "",
|
|
19
|
+
sortable = false,
|
|
20
|
+
paginated = false,
|
|
21
|
+
pageSize = 10,
|
|
22
|
+
}: TableProps) {
|
|
23
|
+
const [sortConfig, setSortConfig] = useState<{ key: number; direction: 'asc' | 'desc' } | null>(null);
|
|
24
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
25
|
+
|
|
26
|
+
const processedData = useMemo(() => {
|
|
27
|
+
if (!data) return [];
|
|
28
|
+
let items = [...data];
|
|
29
|
+
|
|
30
|
+
if (sortable && sortConfig !== null) {
|
|
31
|
+
items.sort((a, b) => {
|
|
32
|
+
const aVal = a[sortConfig.key];
|
|
33
|
+
const bVal = b[sortConfig.key];
|
|
34
|
+
|
|
35
|
+
// Simple string comparison for sorting
|
|
36
|
+
const aStr = typeof aVal === 'string' ? aVal : '';
|
|
37
|
+
const bStr = typeof bVal === 'string' ? bVal : '';
|
|
38
|
+
|
|
39
|
+
if (aStr < bStr) return sortConfig.direction === 'asc' ? -1 : 1;
|
|
40
|
+
if (aStr > bStr) return sortConfig.direction === 'asc' ? 1 : -1;
|
|
41
|
+
return 0;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return items;
|
|
46
|
+
}, [data, sortConfig, sortable]);
|
|
47
|
+
|
|
48
|
+
const totalPages = Math.ceil(processedData.length / pageSize);
|
|
49
|
+
const paginatedData = useMemo(() => {
|
|
50
|
+
if (!paginated) return processedData;
|
|
51
|
+
const start = (currentPage - 1) * pageSize;
|
|
52
|
+
return processedData.slice(start, start + pageSize);
|
|
53
|
+
}, [processedData, paginated, currentPage, pageSize]);
|
|
54
|
+
|
|
55
|
+
const requestSort = (index: number) => {
|
|
56
|
+
if (!sortable) return;
|
|
57
|
+
let direction: 'asc' | 'desc' = 'asc';
|
|
58
|
+
if (sortConfig && sortConfig.key === index && sortConfig.direction === 'asc') {
|
|
59
|
+
direction = 'desc';
|
|
60
|
+
}
|
|
61
|
+
setSortConfig({ key: index, direction });
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const renderSortIcon = (index: number) => {
|
|
65
|
+
if (!sortable) return null;
|
|
66
|
+
if (sortConfig?.key !== index) return <ChevronDown size={14} className="ld-table-sort-icon ld-table-sort-icon--hidden" />;
|
|
67
|
+
return sortConfig.direction === 'asc' ? <ChevronUp size={14} className="ld-table-sort-icon" /> : <ChevronDown size={14} className="ld-table-sort-icon" />;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const tableContent = children ? (
|
|
71
|
+
children
|
|
72
|
+
) : (
|
|
73
|
+
<>
|
|
74
|
+
{headers && (
|
|
75
|
+
<thead>
|
|
76
|
+
<tr>
|
|
77
|
+
{headers.map((header, i) => (
|
|
78
|
+
<th
|
|
79
|
+
key={i}
|
|
80
|
+
onClick={() => requestSort(i)}
|
|
81
|
+
className={sortable ? "ld-table-header--sortable" : ""}
|
|
82
|
+
>
|
|
83
|
+
<div className="ld-table-header-content">
|
|
84
|
+
{header}
|
|
85
|
+
{renderSortIcon(i)}
|
|
86
|
+
</div>
|
|
87
|
+
</th>
|
|
88
|
+
))}
|
|
89
|
+
</tr>
|
|
90
|
+
</thead>
|
|
91
|
+
)}
|
|
92
|
+
{paginatedData && (
|
|
93
|
+
<tbody>
|
|
94
|
+
{paginatedData.map((row, i) => (
|
|
95
|
+
<tr key={i}>
|
|
96
|
+
{row.map((cell, j) => (
|
|
97
|
+
<td key={j}>{cell}</td>
|
|
98
|
+
))}
|
|
99
|
+
</tr>
|
|
100
|
+
))}
|
|
101
|
+
</tbody>
|
|
102
|
+
)}
|
|
103
|
+
</>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div className={`ld-table-container ${className}`.trim()}>
|
|
108
|
+
<div className="ld-table-wrapper">
|
|
109
|
+
<table className="ld-table">{tableContent}</table>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{paginated && totalPages > 1 && (
|
|
113
|
+
<div className="ld-table-pagination">
|
|
114
|
+
<div className="ld-table-pagination-info">
|
|
115
|
+
Page {currentPage} of {totalPages}
|
|
116
|
+
</div>
|
|
117
|
+
<div className="ld-table-pagination-controls">
|
|
118
|
+
<button
|
|
119
|
+
onClick={() => setCurrentPage(1)}
|
|
120
|
+
disabled={currentPage === 1}
|
|
121
|
+
className="ld-table-pagination-btn"
|
|
122
|
+
>
|
|
123
|
+
<ChevronsLeft size={16} />
|
|
124
|
+
</button>
|
|
125
|
+
<button
|
|
126
|
+
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
|
|
127
|
+
disabled={currentPage === 1}
|
|
128
|
+
className="ld-table-pagination-btn"
|
|
129
|
+
>
|
|
130
|
+
<ChevronLeft size={16} />
|
|
131
|
+
</button>
|
|
132
|
+
<button
|
|
133
|
+
onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))}
|
|
134
|
+
disabled={currentPage === totalPages}
|
|
135
|
+
className="ld-table-pagination-btn"
|
|
136
|
+
>
|
|
137
|
+
<ChevronRight size={16} />
|
|
138
|
+
</button>
|
|
139
|
+
<button
|
|
140
|
+
onClick={() => setCurrentPage(totalPages)}
|
|
141
|
+
disabled={currentPage === totalPages}
|
|
142
|
+
className="ld-table-pagination-btn"
|
|
143
|
+
>
|
|
144
|
+
<ChevronsRight size={16} />
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -539,3 +539,131 @@
|
|
|
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
|
+
display: flex;
|
|
551
|
+
flex-direction: column;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.ld-table-wrapper {
|
|
555
|
+
overflow-x: auto;
|
|
556
|
+
scrollbar-width: thin;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.ld-table {
|
|
560
|
+
width: 100%;
|
|
561
|
+
border-collapse: collapse;
|
|
562
|
+
text-align: left;
|
|
563
|
+
font-size: 0.875rem;
|
|
564
|
+
line-height: 1.5;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.ld-table thead {
|
|
568
|
+
background: var(--ld-bg-mute);
|
|
569
|
+
border-bottom: 1px solid var(--ld-border-subtle);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.ld-table th {
|
|
573
|
+
padding: 0.75rem 1rem;
|
|
574
|
+
font-weight: 600;
|
|
575
|
+
color: var(--ld-text-main);
|
|
576
|
+
font-size: 0.8125rem;
|
|
577
|
+
text-transform: uppercase;
|
|
578
|
+
letter-spacing: 0.04em;
|
|
579
|
+
white-space: nowrap;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.ld-table-header--sortable {
|
|
583
|
+
cursor: pointer;
|
|
584
|
+
transition: background-color 0.2s;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.ld-table-header--sortable:hover {
|
|
588
|
+
background-color: var(--ld-bg-soft);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.ld-table-header-content {
|
|
592
|
+
display: flex;
|
|
593
|
+
align-items: center;
|
|
594
|
+
gap: 0.5rem;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.ld-table-sort-icon {
|
|
598
|
+
opacity: 0.8;
|
|
599
|
+
color: var(--ld-color-primary);
|
|
600
|
+
transition: opacity 0.2s;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.ld-table-sort-icon--hidden {
|
|
604
|
+
opacity: 0;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.ld-table-header--sortable:hover .ld-table-sort-icon--hidden {
|
|
608
|
+
opacity: 0.3;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.ld-table td {
|
|
612
|
+
padding: 0.875rem 1rem;
|
|
613
|
+
color: var(--ld-text-muted);
|
|
614
|
+
border-bottom: 1px solid var(--ld-border-subtle);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.ld-table tr:last-child td {
|
|
618
|
+
border-bottom: none;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.ld-table code {
|
|
622
|
+
font-size: 0.8rem;
|
|
623
|
+
padding: 0.2rem 0.4rem;
|
|
624
|
+
background: rgba(255, 255, 255, 0.05);
|
|
625
|
+
border-radius: 4px;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* Pagination */
|
|
629
|
+
.ld-table-pagination {
|
|
630
|
+
display: flex;
|
|
631
|
+
align-items: center;
|
|
632
|
+
justify-content: space-between;
|
|
633
|
+
padding: 0.75rem 1rem;
|
|
634
|
+
background: var(--ld-bg-mute);
|
|
635
|
+
border-top: 1px solid var(--ld-border-subtle);
|
|
636
|
+
font-size: 0.75rem;
|
|
637
|
+
color: var(--ld-text-dim);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.ld-table-pagination-controls {
|
|
641
|
+
display: flex;
|
|
642
|
+
align-items: center;
|
|
643
|
+
gap: 0.25rem;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.ld-table-pagination-btn {
|
|
647
|
+
display: flex;
|
|
648
|
+
align-items: center;
|
|
649
|
+
justify-content: center;
|
|
650
|
+
width: 1.75rem;
|
|
651
|
+
height: 1.75rem;
|
|
652
|
+
border-radius: var(--ld-radius-md);
|
|
653
|
+
border: 1px solid var(--ld-border-subtle);
|
|
654
|
+
background: var(--ld-bg-soft);
|
|
655
|
+
color: var(--ld-text-muted);
|
|
656
|
+
cursor: pointer;
|
|
657
|
+
transition: all 0.2s;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.ld-table-pagination-btn:hover:not(:disabled) {
|
|
661
|
+
background: var(--ld-bg-mute);
|
|
662
|
+
color: var(--ld-text-main);
|
|
663
|
+
border-color: var(--ld-border-strong);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.ld-table-pagination-btn:disabled {
|
|
667
|
+
opacity: 0.4;
|
|
668
|
+
cursor: not-allowed;
|
|
669
|
+
}
|
|
@@ -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;
|
|
@@ -159,4 +160,28 @@
|
|
|
159
160
|
--ld-radius-md: 8px;
|
|
160
161
|
--ld-radius-lg: 12px;
|
|
161
162
|
--ld-radius-full: 9999px;
|
|
163
|
+
|
|
164
|
+
/* ─ Custom Scrollbar ─ */
|
|
165
|
+
scrollbar-width: thin;
|
|
166
|
+
scrollbar-color: var(--ld-border-strong) transparent;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Chrome, Edge, and Safari */
|
|
170
|
+
*::-webkit-scrollbar {
|
|
171
|
+
width: 6px;
|
|
172
|
+
height: 6px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
*::-webkit-scrollbar-track {
|
|
176
|
+
background: transparent;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
*::-webkit-scrollbar-thumb {
|
|
180
|
+
background-color: var(--ld-border-strong);
|
|
181
|
+
border-radius: 20px;
|
|
182
|
+
border: transparent;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
*::-webkit-scrollbar-thumb:hover {
|
|
186
|
+
background-color: var(--ld-text-dim);
|
|
162
187
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { Component, ErrorInfo, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
fallback?: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface State {
|
|
9
|
+
hasError: boolean;
|
|
10
|
+
error?: Error;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
14
|
+
public state: State = {
|
|
15
|
+
hasError: false
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
public static getDerivedStateFromError(error: Error): State {
|
|
19
|
+
return { hasError: true, error };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
23
|
+
console.error("Uncaught error in Boltdocs Layout:", error, errorInfo);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public render() {
|
|
27
|
+
if (this.state.hasError) {
|
|
28
|
+
return this.props.fallback || (
|
|
29
|
+
<div className="boltdocs-error-boundary">
|
|
30
|
+
<div className="boltdocs-error-title">Something went wrong</div>
|
|
31
|
+
<p className="boltdocs-error-message">
|
|
32
|
+
{this.state.error?.message || "An unexpected error occurred while rendering this page."}
|
|
33
|
+
</p>
|
|
34
|
+
<button
|
|
35
|
+
className="boltdocs-error-retry"
|
|
36
|
+
onClick={() => this.setState({ hasError: false })}
|
|
37
|
+
>
|
|
38
|
+
Try again
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return this.props.children;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./ErrorBoundary";
|
|
@@ -18,6 +18,8 @@ import { OnThisPage } from "../OnThisPage";
|
|
|
18
18
|
import { Head } from "../Head";
|
|
19
19
|
import { Breadcrumbs } from "../Breadcrumbs";
|
|
20
20
|
import { BackgroundGradient } from "../BackgroundGradient";
|
|
21
|
+
import { ProgressBar } from "../ProgressBar";
|
|
22
|
+
import { ErrorBoundary } from "../ErrorBoundary";
|
|
21
23
|
import "../../styles.css";
|
|
22
24
|
|
|
23
25
|
export interface ThemeLayoutProps {
|
|
@@ -63,7 +65,6 @@ export function ThemeLayout({
|
|
|
63
65
|
className = "",
|
|
64
66
|
style,
|
|
65
67
|
}: ThemeLayoutProps) {
|
|
66
|
-
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
|
67
68
|
const siteTitle = config.themeConfig?.title || "Boltdocs";
|
|
68
69
|
const siteDescription = config.themeConfig?.description || "";
|
|
69
70
|
const location = useLocation();
|
|
@@ -109,6 +110,7 @@ export function ThemeLayout({
|
|
|
109
110
|
|
|
110
111
|
return (
|
|
111
112
|
<div className={`boltdocs-layout ${className}`} style={style}>
|
|
113
|
+
<ProgressBar />
|
|
112
114
|
{background !== undefined ? background : <BackgroundGradient />}
|
|
113
115
|
{head !== undefined ? (
|
|
114
116
|
head
|
|
@@ -130,18 +132,11 @@ export function ThemeLayout({
|
|
|
130
132
|
currentVersion={currentVersion}
|
|
131
133
|
/>
|
|
132
134
|
)}
|
|
133
|
-
<div
|
|
134
|
-
className={`boltdocs-main-container ${!isSidebarOpen ? "sidebar-collapsed" : ""}`}
|
|
135
|
-
>
|
|
135
|
+
<div className="boltdocs-main-container">
|
|
136
136
|
{sidebar !== undefined ? (
|
|
137
137
|
sidebar
|
|
138
138
|
) : (
|
|
139
|
-
<Sidebar
|
|
140
|
-
routes={filteredRoutes}
|
|
141
|
-
config={config}
|
|
142
|
-
isCollapsed={!isSidebarOpen}
|
|
143
|
-
onToggle={() => setIsSidebarOpen(!isSidebarOpen)}
|
|
144
|
-
/>
|
|
139
|
+
<Sidebar routes={filteredRoutes} config={config} />
|
|
145
140
|
)}
|
|
146
141
|
|
|
147
142
|
<main className="boltdocs-content">
|
|
@@ -150,7 +145,11 @@ export function ThemeLayout({
|
|
|
150
145
|
) : (
|
|
151
146
|
<Breadcrumbs routes={filteredRoutes} config={config} />
|
|
152
147
|
)}
|
|
153
|
-
<div className="boltdocs-page">
|
|
148
|
+
<div className="boltdocs-page">
|
|
149
|
+
<ErrorBoundary>
|
|
150
|
+
{children}
|
|
151
|
+
</ErrorBoundary>
|
|
152
|
+
</div>
|
|
154
153
|
|
|
155
154
|
{/* Prev / Next Navigation */}
|
|
156
155
|
{(prevPage || nextPage) && (
|
|
@@ -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 ────────────────────────────────────── */
|