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.
Files changed (51) hide show
  1. package/dist/{SearchDialog-3QICRMWF.css → SearchDialog-UOAW6IR3.css} +270 -113
  2. package/dist/{SearchDialog-J3KNRGNO.mjs → SearchDialog-YOXMFGH6.mjs} +1 -1
  3. package/dist/{chunk-HSPDIRTW.mjs → chunk-MULKZFVN.mjs} +872 -758
  4. package/dist/client/index.css +270 -113
  5. package/dist/client/index.d.mts +21 -7
  6. package/dist/client/index.d.ts +21 -7
  7. package/dist/client/index.js +637 -499
  8. package/dist/client/index.mjs +17 -1
  9. package/dist/client/ssr.css +270 -113
  10. package/dist/client/ssr.d.mts +3 -1
  11. package/dist/client/ssr.d.ts +3 -1
  12. package/dist/client/ssr.js +533 -412
  13. package/dist/client/ssr.mjs +3 -2
  14. package/dist/{config-DkZg5aCf.d.ts → config-D68h41CA.d.mts} +21 -2
  15. package/dist/{config-DkZg5aCf.d.mts → config-D68h41CA.d.ts} +21 -2
  16. package/dist/node/index.d.mts +10 -2
  17. package/dist/node/index.d.ts +10 -2
  18. package/dist/node/index.js +45 -21
  19. package/dist/node/index.mjs +45 -21
  20. package/dist/{types-DGIo1VKD.d.mts → types-CviV0GbX.d.mts} +13 -0
  21. package/dist/{types-DGIo1VKD.d.ts → types-CviV0GbX.d.ts} +13 -0
  22. package/package.json +1 -1
  23. package/src/client/app/index.tsx +8 -4
  24. package/src/client/index.ts +2 -0
  25. package/src/client/ssr.tsx +4 -1
  26. package/src/client/theme/components/mdx/Table.tsx +53 -0
  27. package/src/client/theme/components/mdx/index.ts +3 -0
  28. package/src/client/theme/components/mdx/mdx-components.css +49 -0
  29. package/src/client/theme/styles/markdown.css +8 -3
  30. package/src/client/theme/styles/variables.css +10 -9
  31. package/src/client/theme/ui/Layout/Layout.tsx +2 -10
  32. package/src/client/theme/ui/Layout/base.css +15 -3
  33. package/src/client/theme/ui/Link/Link.tsx +2 -2
  34. package/src/client/theme/ui/Link/LinkPreview.tsx +9 -14
  35. package/src/client/theme/ui/Link/link-preview.css +30 -27
  36. package/src/client/theme/ui/Navbar/Navbar.tsx +65 -17
  37. package/src/client/theme/ui/Navbar/Tabs.tsx +74 -0
  38. package/src/client/theme/ui/Navbar/navbar.css +111 -5
  39. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +65 -49
  40. package/src/client/theme/ui/OnThisPage/toc.css +30 -10
  41. package/src/client/theme/ui/Sidebar/Sidebar.tsx +97 -57
  42. package/src/client/theme/ui/Sidebar/sidebar.css +61 -67
  43. package/src/client/types.ts +10 -0
  44. package/src/node/config.ts +19 -1
  45. package/src/node/plugin/entry.ts +5 -1
  46. package/src/node/plugin/index.ts +2 -1
  47. package/src/node/routes/index.ts +12 -1
  48. package/src/node/routes/parser.ts +21 -7
  49. package/src/node/routes/types.ts +9 -1
  50. package/src/node/ssg/index.ts +2 -1
  51. package/src/node/ssg/options.ts +2 -0
@@ -8,72 +8,44 @@
8
8
  backdrop-filter: blur(var(--ld-sidebar-blur));
9
9
  -webkit-backdrop-filter: blur(var(--ld-sidebar-blur));
10
10
  border-right: 1px solid var(--ld-border-subtle);
11
- padding: 1rem 0.6rem;
11
+ padding: 1.5rem 0.6rem;
12
12
  overflow-y: auto;
13
- position: sticky;
14
- top: var(--ld-navbar-height);
15
- height: calc(100vh - var(--ld-navbar-height));
16
- scrollbar-width: thin;
17
- scrollbar-color: var(--ld-bg-mute) transparent;
13
+ height: 100%;
14
+ scrollbar-width: none;
18
15
  display: flex;
19
16
  flex-direction: column;
20
- transition:
21
- width 0.3s cubic-bezier(0.16, 1, 0.3, 1),
22
- padding 0.3s cubic-bezier(0.16, 1, 0.3, 1),
23
- opacity 0.2s ease;
17
+ /* Hardware acceleration */
18
+ transform: translate3d(0, 0, 0);
19
+ backface-visibility: hidden;
24
20
  }
25
21
 
26
- .boltdocs-sidebar > nav {
27
- flex: 1;
28
- }
29
-
30
- /* ─── Collapsible Sidebar ────────────────────────────────── */
31
- .boltdocs-main-container.sidebar-collapsed .boltdocs-sidebar {
32
- width: 54px;
33
- padding: 1rem 0;
34
- border-right: 1px solid var(--ld-border-subtle);
35
- opacity: 1;
36
- pointer-events: auto;
37
- overflow: hidden;
38
- }
39
-
40
- .sidebar-collapse {
41
- width: 100%;
22
+ .sidebar-icon {
23
+ flex-shrink: 0;
42
24
  display: flex;
43
25
  align-items: center;
44
- justify-content: flex-end;
45
- padding: 0 0.75rem 1rem;
46
- transition: justify-content 0.3s ease;
47
- }
48
-
49
- .boltdocs-main-container.sidebar-collapsed .sidebar-collapse {
50
26
  justify-content: center;
51
- padding: 0 0 1rem;
52
27
  }
53
28
 
54
- .sidebar-collapse-btn {
29
+ .sidebar-icon.lucide-icon,
30
+ .sidebar-icon.svg-icon svg {
55
31
  color: var(--ld-text-muted);
56
- border: none;
57
- box-shadow: none;
58
- background-color: transparent;
59
- cursor: pointer;
60
- display: flex;
61
- align-items: center;
62
- justify-content: center;
63
- width: 32px;
64
- height: 32px;
65
- border-radius: var(--ld-radius-md);
66
- transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
32
+ width: 18px;
33
+ height: 18px;
34
+ stroke-width: 2px;
35
+ transition: color 0.2s ease;
67
36
  }
68
37
 
69
- .sidebar-collapse-btn:hover {
70
- background-color: var(--ld-bg-mute);
71
- color: var(--ld-text-main);
72
- transform: scale(1.05);
38
+ .sidebar-link:hover .sidebar-icon.lucide-icon,
39
+ .sidebar-link.active .sidebar-icon.lucide-icon {
40
+ color: var(--ld-color-primary);
41
+ }
42
+
43
+ .boltdocs-sidebar > nav {
44
+ flex: 1;
73
45
  }
74
46
 
75
47
  .boltdocs-sidebar::-webkit-scrollbar {
76
- width: 4px;
48
+ display: none;
77
49
  }
78
50
 
79
51
  .boltdocs-sidebar::-webkit-scrollbar-track {
@@ -92,23 +64,29 @@
92
64
  }
93
65
 
94
66
  .sidebar-list li {
95
- margin-bottom: 1px;
67
+ margin-bottom: 2px;
96
68
  }
97
69
 
98
70
  .sidebar-link {
99
71
  display: flex;
100
72
  align-items: center;
101
73
  justify-content: space-between;
102
- padding: 0.45rem 0.75rem;
74
+ padding: 0.5rem 0.75rem;
103
75
  color: var(--ld-text-muted);
104
76
  text-decoration: none;
105
- border-radius: var(--ld-radius-md);
77
+ border-radius: 0.75rem; /* Large rounded corners */
106
78
  font-size: 0.875rem;
107
79
  font-weight: 500;
108
80
  transition: all 0.2s ease;
109
81
  margin: 0.15rem 0;
110
82
  }
111
83
 
84
+ .sidebar-link-title-container {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 0.75rem;
88
+ }
89
+
112
90
  .sidebar-link-content {
113
91
  display: flex;
114
92
  align-items: center;
@@ -117,7 +95,7 @@
117
95
  gap: 0.5rem;
118
96
  }
119
97
 
120
- .sidebar-link-content > span:first-child {
98
+ .sidebar-link-content > span:last-child {
121
99
  flex: 1;
122
100
  word-wrap: break-word;
123
101
  }
@@ -162,11 +140,13 @@
162
140
 
163
141
  .sidebar-link:hover {
164
142
  color: var(--ld-color-primary-hover);
143
+ background-color: var(--ld-bg-soft);
165
144
  }
166
145
 
167
146
  .sidebar-link.active {
168
147
  color: var(--ld-color-primary);
169
148
  font-weight: 600;
149
+ background-color: var(--ld-bg-soft);
170
150
  }
171
151
 
172
152
  /* ─── Sidebar Groups ─────────────────────────────────────── */
@@ -183,29 +163,32 @@
183
163
  align-items: center;
184
164
  justify-content: space-between;
185
165
  width: 100%;
186
- padding: 0.35rem 0.75rem;
166
+ padding: 0.5rem 0.75rem;
187
167
  background: none;
188
168
  border: none;
189
169
  border-radius: var(--ld-radius-md);
190
- color: var(--ld-text-dim);
170
+ color: var(--ld-text-muted);
191
171
  font-family: var(--ld-font-sans);
192
- font-size: 0.6875rem;
193
- font-weight: 700;
194
- text-transform: uppercase;
195
- letter-spacing: 0.08em;
172
+ font-size: 0.8125rem;
173
+ font-weight: 600;
196
174
  cursor: pointer;
197
175
  transition:
198
176
  color 0.2s,
199
177
  background-color 0.2s;
200
178
  }
201
179
 
180
+ .sidebar-group-header-content {
181
+ display: flex;
182
+ align-items: center;
183
+ gap: 0.75rem;
184
+ }
185
+
202
186
  .sidebar-group-header:hover {
203
- color: var(--ld-text-muted);
204
- background-color: rgba(255, 255, 255, 0.03);
187
+ color: var(--ld-text-main);
205
188
  }
206
189
 
207
190
  .sidebar-group-header.active {
208
- color: var(--ld-color-primary);
191
+ color: var(--ld-text-main);
209
192
  }
210
193
 
211
194
  .sidebar-group-chevron {
@@ -223,14 +206,25 @@
223
206
  .sidebar-group-list {
224
207
  list-style: none;
225
208
  padding: 0;
226
- margin: 0.35rem 0 0;
227
- overflow: hidden;
209
+ margin: 0.35rem 0 0.5rem 0;
210
+ position: relative;
228
211
  }
229
212
 
230
213
  .sidebar-group-list li {
231
- margin-bottom: 1px;
214
+ margin-bottom: 2px;
232
215
  }
233
216
 
234
217
  .sidebar-link-nested {
235
- padding-left: 1.25rem;
218
+ padding-left: 0.75rem;
219
+ }
220
+
221
+ .sidebar-group-list {
222
+ list-style: none;
223
+ padding: 0;
224
+ margin: 0.35rem 0 0.5rem 0;
225
+ position: relative;
226
+ }
227
+
228
+ .sidebar-group-list li {
229
+ margin-bottom: 2px;
236
230
  }
@@ -29,6 +29,14 @@ export interface ComponentRoute {
29
29
  locale?: string;
30
30
  /** The version this route belongs to, if versioning is configured */
31
31
  version?: string;
32
+ /** Optional icon to display (Lucide icon name or raw SVG) */
33
+ icon?: string;
34
+ /** The tab this route belongs to, if tabs are configured */
35
+ tab?: string;
36
+ /** Optional badge to display next to the sidebar item */
37
+ badge?: string | { text: string; expires?: string };
38
+ /** Optional icon for the route's group */
39
+ groupIcon?: string;
32
40
  }
33
41
 
34
42
  /**
@@ -39,6 +47,8 @@ export interface CreateBoltdocsAppOptions {
39
47
  target: string;
40
48
  /** Initial routes generated by the Vite plugin (`virtual:boltdocs-routes`) */
41
49
  routes: ComponentRoute[];
50
+ /** The name of the documentation directory (e.g. 'docs') */
51
+ docsDirName: string;
42
52
  /** Site configuration (`virtual:boltdocs-config`) */
43
53
  config: any;
44
54
  /** Dynamic import mapping from `import.meta.glob` for the documentation pages */
@@ -32,7 +32,20 @@ export interface BoltdocsThemeConfig {
32
32
  /** URL path to the site logo */
33
33
  logo?: string;
34
34
  /** Items to display in the top navigation bar */
35
- navbar?: Array<{ text: string; link: string }>;
35
+ navbar?: Array<{
36
+ /** Text to display (alias for text) */
37
+ label?: string;
38
+ /** Text to display */
39
+ text?: string;
40
+ /** URL path or external link (alias for link) */
41
+ to?: string;
42
+ /** URL path or external link (alias for link) */
43
+ href?: string;
44
+ /** URL path or external link */
45
+ link?: string;
46
+ /** Alignment of the item in the navbar */
47
+ position?: "left" | "right";
48
+ }>;
36
49
  /** Items to display in the sidebar, organized optionally by group URLs */
37
50
  sidebar?: Record<string, Array<{ text: string; link: string }>>;
38
51
  /** Social links to display in the navigation bar */
@@ -66,6 +79,11 @@ export interface BoltdocsThemeConfig {
66
79
  className?: string;
67
80
  style?: any;
68
81
  };
82
+ /**
83
+ * Top-level tabs for organizing documentation groups.
84
+ * Tab discovery uses the (tab-id) directory syntax.
85
+ */
86
+ tabs?: Array<{ id: string; text: string; icon?: string }>;
69
87
  /**
70
88
  * The syntax highlighting theme for code blocks.
71
89
  * Supports any Shiki theme name (e.g., 'github-dark', 'one-dark-pro', 'aurora-x').
@@ -1,6 +1,7 @@
1
1
  import { normalizePath } from "../utils";
2
2
  import type { BoltdocsConfig } from "../config";
3
3
  import type { BoltdocsPluginOptions } from "./types";
4
+ import path from "path";
4
5
 
5
6
  /**
6
7
  * Generates the raw source code for the virtual entry file (`\0virtual:boltdocs-entry`).
@@ -36,6 +37,8 @@ const ${name} = _comp_${name}.default || _comp_${name}['${name}'] || _comp_${nam
36
37
  .join("\n");
37
38
  const componentMap = pluginComponents.map(([name]) => name).join(", ");
38
39
 
40
+ const docsDirName = path.basename(options.docsDir || "docs");
41
+
39
42
  return `
40
43
  import { createBoltdocsApp as _createApp } from 'boltdocs/client';
41
44
  import 'boltdocs/style.css';
@@ -48,8 +51,9 @@ ${componentImports}
48
51
  _createApp({
49
52
  target: '#root',
50
53
  routes: _routes,
54
+ docsDirName: '${docsDirName}',
51
55
  config: _config,
52
- modules: import.meta.glob('/docs/**/*.{md,mdx}'),
56
+ modules: import.meta.glob('/${docsDirName}/**/*.{md,mdx}'),
53
57
  hot: import.meta.hot,
54
58
  ${homeOption}
55
59
  components: { ${componentMap} },
@@ -165,7 +165,8 @@ export function boltdocsPlugin(
165
165
  ? path.resolve(viteConfig.root, viteConfig.build.outDir)
166
166
  : path.resolve(process.cwd(), "dist");
167
167
 
168
- await generateStaticPages({ docsDir, outDir, config });
168
+ const docsDirName = path.basename(docsDir || "docs");
169
+ await generateStaticPages({ docsDir, docsDirName, outDir, config });
169
170
 
170
171
  const { flushCache } = await import("../cache");
171
172
  await flushCache();
@@ -69,13 +69,17 @@ export async function generateRoutes(
69
69
  docCache.save();
70
70
 
71
71
  // Collect group metadata from directory names and index files
72
- const groupMeta = new Map<string, { title: string; position?: number }>();
72
+ const groupMeta = new Map<
73
+ string,
74
+ { title: string; position?: number; icon?: string }
75
+ >();
73
76
  for (const p of parsed) {
74
77
  if (p.relativeDir) {
75
78
  if (!groupMeta.has(p.relativeDir)) {
76
79
  groupMeta.set(p.relativeDir, {
77
80
  title: capitalize(p.relativeDir),
78
81
  position: p.inferredGroupPosition,
82
+ icon: p.route.icon,
79
83
  });
80
84
  } else {
81
85
  const entry = groupMeta.get(p.relativeDir)!;
@@ -85,6 +89,9 @@ export async function generateRoutes(
85
89
  ) {
86
90
  entry.position = p.inferredGroupPosition;
87
91
  }
92
+ if (!entry.icon && p.route.icon) {
93
+ entry.icon = p.route.icon;
94
+ }
88
95
  }
89
96
  }
90
97
 
@@ -94,6 +101,9 @@ export async function generateRoutes(
94
101
  if (p.groupMeta.position !== undefined) {
95
102
  entry.position = p.groupMeta.position;
96
103
  }
104
+ if (p.groupMeta.icon) {
105
+ entry.icon = p.groupMeta.icon;
106
+ }
97
107
  }
98
108
  }
99
109
 
@@ -107,6 +117,7 @@ export async function generateRoutes(
107
117
  group: dir,
108
118
  groupTitle: meta?.title || (dir ? capitalize(dir) : undefined),
109
119
  groupPosition: meta?.position,
120
+ groupIcon: meta?.icon,
110
121
  };
111
122
  });
112
123
 
@@ -69,6 +69,16 @@ export function parseDocFile(
69
69
  }
70
70
  }
71
71
 
72
+ // Level 3: Check for Tab hierarchy (name)
73
+ let inferredTab: string | undefined;
74
+ if (parts.length > 0) {
75
+ const tabMatch = parts[0].match(/^\((.+)\)$/);
76
+ if (tabMatch) {
77
+ inferredTab = tabMatch[1].toLowerCase();
78
+ parts = parts.slice(1);
79
+ }
80
+ }
81
+
72
82
  const cleanRelativePath = parts.join("/");
73
83
 
74
84
  let cleanRoutePath: string;
@@ -118,12 +128,12 @@ export function parseDocFile(
118
128
  .trim();
119
129
  const id = slugger.slug(text);
120
130
  // Security: Sanitize heading text for XSS
121
- headings.push({ level, text: escapeHtml(text), id });
131
+ headings.push({ level, text, id });
122
132
  }
123
133
 
124
- const sanitizedTitle = data.title ? escapeHtml(data.title) : inferredTitle;
134
+ const sanitizedTitle = data.title ? data.title : inferredTitle;
125
135
  let sanitizedDescription = data.description
126
- ? escapeHtml(data.description)
136
+ ? data.description
127
137
  : "";
128
138
 
129
139
  // If no description is provided, extract a summary from the content
@@ -135,10 +145,11 @@ export function parseDocFile(
135
145
  .replace(/\n+/g, " ") // Normalize whitespace
136
146
  .trim()
137
147
  .slice(0, 160);
138
- sanitizedDescription = escapeHtml(summary);
148
+ sanitizedDescription = summary;
139
149
  }
140
150
 
141
- const sanitizedBadge = data.badge ? escapeHtml(data.badge) : undefined;
151
+ const sanitizedBadge = data.badge ? data.badge : undefined;
152
+ const icon = data.icon ? String(data.icon) : undefined;
142
153
 
143
154
  return {
144
155
  route: {
@@ -152,20 +163,23 @@ export function parseDocFile(
152
163
  locale,
153
164
  version,
154
165
  badge: sanitizedBadge,
166
+ icon,
167
+ tab: inferredTab,
155
168
  },
156
169
  relativeDir: cleanDirName,
157
170
  isGroupIndex,
171
+ inferredTab,
158
172
  groupMeta: isGroupIndex
159
173
  ? {
160
- title: escapeHtml(
174
+ title:
161
175
  data.groupTitle ||
162
176
  data.title ||
163
177
  (cleanDirName ? capitalize(cleanDirName) : ""),
164
- ),
165
178
  position:
166
179
  data.groupPosition ??
167
180
  data.sidebarPosition ??
168
181
  (rawDirName ? extractNumberPrefix(rawDirName) : undefined),
182
+ icon,
169
183
  }
170
184
  : undefined,
171
185
  inferredGroupPosition: rawDirName
@@ -21,6 +21,8 @@ export interface RouteMeta {
21
21
  groupTitle?: string;
22
22
  /** Optional explicit position for ordering the group itself */
23
23
  groupPosition?: number;
24
+ /** Optional icon for the route's group */
25
+ groupIcon?: string;
24
26
  /** Extracted markdown headings for search indexing */
25
27
  headings?: { level: number; text: string; id: string }[];
26
28
  /** The locale this route belongs to, if i18n is configured */
@@ -29,6 +31,10 @@ export interface RouteMeta {
29
31
  version?: string;
30
32
  /** Optional badge to display next to the sidebar item (e.g., 'New', 'Experimental') */
31
33
  badge?: string | { text: string; expires?: string };
34
+ /** Optional icon to display (Lucide icon name or raw SVG) */
35
+ icon?: string;
36
+ /** The tab this route belongs to, if tabs are configured */
37
+ tab?: string;
32
38
  }
33
39
 
34
40
  /**
@@ -43,7 +49,9 @@ export interface ParsedDocFile {
43
49
  /** Whether this file is the index file for its directory group */
44
50
  isGroupIndex: boolean;
45
51
  /** If this is a group index, any specific frontmatter metadata dictating the group's title and position */
46
- groupMeta?: { title: string; position?: number };
52
+ groupMeta?: { title: string; position?: number; icon?: string };
47
53
  /** Extracted group position from the directory name if it has a numeric prefix */
48
54
  inferredGroupPosition?: number;
55
+ /** Extracted tab name from the directory name if it follows the (tab-name) syntax */
56
+ inferredTab?: string;
49
57
  }
@@ -24,7 +24,7 @@ const _require = createRequire(import.meta.url);
24
24
  * @param options - Configuration for paths and site metadata
25
25
  */
26
26
  export async function generateStaticPages(options: SSGOptions): Promise<void> {
27
- const { docsDir, outDir, config } = options;
27
+ const { docsDir, docsDirName, outDir, config } = options;
28
28
  const routes = await generateRoutes(docsDir, config);
29
29
  const siteTitle = config?.themeConfig?.title || "Boltdocs";
30
30
  const siteDescription = config?.themeConfig?.description || "";
@@ -64,6 +64,7 @@ export async function generateStaticPages(options: SSGOptions): Promise<void> {
64
64
  path: route.path,
65
65
  routes: routes,
66
66
  config: config || {},
67
+ docsDirName: docsDirName,
67
68
  modules: fakeModules,
68
69
  homePage: undefined, // No custom home page for now
69
70
  });
@@ -6,6 +6,8 @@ import { BoltdocsConfig } from "../config";
6
6
  export interface SSGOptions {
7
7
  /** The root directory containing markdown documentation files */
8
8
  docsDir: string;
9
+ /** The name of the documentation directory (e.g. 'docs') */
10
+ docsDirName: string;
9
11
  /** The output directory where Vite placed the compiled `index.html` and assets */
10
12
  outDir: string;
11
13
  /** Pre-resolved config (avoids re-resolving during the SSG phase) */