boltdocs 1.7.0 → 1.8.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 (42) hide show
  1. package/dist/{SearchDialog-UOAW6IR3.css → SearchDialog-4ANHNJTL.css} +213 -28
  2. package/dist/{SearchDialog-YOXMFGH6.mjs → SearchDialog-6Z7CUAYJ.mjs} +8 -1
  3. package/dist/{chunk-MULKZFVN.mjs → chunk-SFVOGJ2W.mjs} +269 -165
  4. package/dist/client/index.css +211 -26
  5. package/dist/client/index.d.mts +25 -6
  6. package/dist/client/index.d.ts +25 -6
  7. package/dist/client/index.js +614 -336
  8. package/dist/client/index.mjs +134 -5
  9. package/dist/client/ssr.css +211 -26
  10. package/dist/client/ssr.d.mts +1 -1
  11. package/dist/client/ssr.d.ts +1 -1
  12. package/dist/client/ssr.js +378 -230
  13. package/dist/client/ssr.mjs +1 -1
  14. package/dist/node/index.d.mts +2 -0
  15. package/dist/node/index.d.ts +2 -0
  16. package/dist/node/index.js +4 -1
  17. package/dist/node/index.mjs +4 -1
  18. package/dist/{types-CviV0GbX.d.ts → types-BbceAHA0.d.mts} +2 -0
  19. package/dist/{types-CviV0GbX.d.mts → types-BbceAHA0.d.ts} +2 -0
  20. package/package.json +1 -1
  21. package/src/client/app/index.tsx +8 -7
  22. package/src/client/index.ts +2 -0
  23. package/src/client/theme/components/mdx/Field.tsx +60 -0
  24. package/src/client/theme/components/mdx/Table.tsx +108 -10
  25. package/src/client/theme/components/mdx/index.ts +3 -0
  26. package/src/client/theme/components/mdx/mdx-components.css +174 -0
  27. package/src/client/theme/styles/variables.css +25 -1
  28. package/src/client/theme/ui/ErrorBoundary/ErrorBoundary.tsx +46 -0
  29. package/src/client/theme/ui/ErrorBoundary/index.ts +1 -0
  30. package/src/client/theme/ui/Layout/Layout.tsx +8 -1
  31. package/src/client/theme/ui/Link/link-preview.css +1 -20
  32. package/src/client/theme/ui/Navbar/Tabs.tsx +37 -12
  33. package/src/client/theme/ui/Navbar/navbar.css +26 -18
  34. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +1 -8
  35. package/src/client/theme/ui/ProgressBar/ProgressBar.css +17 -0
  36. package/src/client/theme/ui/ProgressBar/ProgressBar.tsx +51 -0
  37. package/src/client/theme/ui/ProgressBar/index.ts +1 -0
  38. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +11 -1
  39. package/src/client/types.ts +2 -0
  40. package/src/node/routes/index.ts +1 -0
  41. package/src/node/routes/parser.ts +11 -0
  42. package/src/node/routes/types.ts +2 -0
@@ -19,11 +19,9 @@
19
19
  .boltdocs-link-preview-content {
20
20
  padding: 0.85rem 1rem;
21
21
  background-color: var(--ld-navbar-bg);
22
- backdrop-filter: blur(20px);
23
- -webkit-backdrop-filter: blur(20px);
24
22
  border: 1px solid var(--ld-border-subtle);
25
23
  border-radius: var(--ld-radius-lg);
26
- box-shadow:
24
+ box-shadow:
27
25
  0 10px 30px -10px rgba(0, 0, 0, 0.2),
28
26
  0 4px 10px -5px rgba(0, 0, 0, 0.1);
29
27
  }
@@ -48,20 +46,3 @@
48
46
  -webkit-box-orient: vertical;
49
47
  overflow: hidden;
50
48
  }
51
-
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);
59
- }
60
-
61
- [data-theme="dark"] .boltdocs-link-preview-title {
62
- color: #fff;
63
- }
64
-
65
- [data-theme="dark"] .boltdocs-link-preview-summary {
66
- color: #94a3b8;
67
- }
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useEffect, useRef, useState } from "react";
2
2
  import { useLocation } from "react-router-dom";
3
3
  import { Link } from "../Link";
4
4
  import * as Icons from "lucide-react";
@@ -16,15 +16,40 @@ interface TabsProps {
16
16
 
17
17
  export function Tabs({ tabs, routes }: TabsProps) {
18
18
  const location = useLocation();
19
+ const containerRef = useRef<HTMLDivElement>(null);
20
+ const tabRefs = useRef<(HTMLAnchorElement | null)[]>([]);
21
+ const [indicatorStyle, setIndicatorStyle] = useState<React.CSSProperties>({
22
+ opacity: 0,
23
+ transform: "translateX(0) scaleX(0)",
24
+ width: 0,
25
+ });
26
+
19
27
  const currentRoute = routes.find((r) => r.path === location.pathname);
20
28
  const currentTabId = currentRoute?.tab?.toLowerCase();
21
29
 
30
+ // Find the active index - default to 0 if no tab detected
31
+ const activeIndex = tabs.findIndex((tab) =>
32
+ currentTabId ? currentTabId === tab.id.toLowerCase() : false
33
+ );
34
+
35
+ const finalActiveIndex = activeIndex === -1 ? 0 : activeIndex;
36
+
37
+ useEffect(() => {
38
+ const activeTab = tabRefs.current[finalActiveIndex];
39
+ if (activeTab) {
40
+ setIndicatorStyle({
41
+ opacity: 1,
42
+ width: activeTab.offsetWidth,
43
+ transform: `translateX(${activeTab.offsetLeft}px)`,
44
+ });
45
+ }
46
+ }, [finalActiveIndex, tabs, location.pathname]);
47
+
22
48
  if (!tabs || tabs.length === 0) return null;
23
49
 
24
50
  const renderTabIcon = (iconName?: string) => {
25
51
  if (!iconName) return null;
26
52
 
27
- // 1. Raw SVG
28
53
  if (iconName.trim().startsWith("<svg")) {
29
54
  return (
30
55
  <span
@@ -34,33 +59,31 @@ export function Tabs({ tabs, routes }: TabsProps) {
34
59
  );
35
60
  }
36
61
 
37
- // 2. Lucide Icon
38
62
  const LucideIcon = (Icons as any)[iconName];
39
63
  if (LucideIcon) {
40
64
  return <LucideIcon size={16} className="tab-icon lucide-icon" />;
41
65
  }
42
66
 
43
- // 3. Fallback to image URL
44
67
  return <img src={iconName} alt="" className="tab-icon img-icon" />;
45
68
  };
46
69
 
47
70
  return (
48
71
  <div className="boltdocs-tabs-container">
49
- <div className="boltdocs-tabs">
72
+ <div className="boltdocs-tabs" ref={containerRef}>
50
73
  {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());
74
+ const isActive = index === finalActiveIndex;
75
+ const firstRoute = routes.find(
76
+ (r) => r.tab && r.tab.toLowerCase() === tab.id.toLowerCase()
77
+ );
58
78
  const linkTo = firstRoute ? firstRoute.path : "#";
59
79
 
60
80
  return (
61
81
  <Link
62
82
  key={tab.id}
63
83
  to={linkTo}
84
+ ref={(el) => {
85
+ tabRefs.current[index] = el;
86
+ }}
64
87
  className={`boltdocs-tab-item ${isActive ? "active" : ""}`}
65
88
  >
66
89
  {renderTabIcon(tab.icon)}
@@ -68,6 +91,8 @@ export function Tabs({ tabs, routes }: TabsProps) {
68
91
  </Link>
69
92
  );
70
93
  })}
94
+ {/* Sliding Indicator */}
95
+ <div className="boltdocs-tab-indicator" style={indicatorStyle} />
71
96
  </div>
72
97
  </div>
73
98
  );
@@ -250,10 +250,22 @@
250
250
 
251
251
  /* Tabs Styles */
252
252
  .boltdocs-tabs-container {
253
- border-bottom: 1px solid var(--ld-border-subtle);
253
+ position: relative;
254
254
  background: var(--ld-navbar-bg);
255
255
  padding: 0;
256
- height: 46px; /* 102px total = 56px navbar + 46px tabs */
256
+ height: 46px;
257
+ }
258
+
259
+ /* Border pseudo-element to allow indicator overlap */
260
+ .boltdocs-tabs-container::after {
261
+ content: "";
262
+ position: absolute;
263
+ bottom: 0px;
264
+ left: 0;
265
+ right: 0;
266
+ height: 1px;
267
+ background: var(--ld-border-subtle);
268
+ z-index: 10;
257
269
  }
258
270
 
259
271
  .boltdocs-tabs {
@@ -264,6 +276,8 @@
264
276
  overflow-x: auto;
265
277
  scrollbar-width: none;
266
278
  padding: 0 1.5rem;
279
+ position: relative;
280
+ height: 100%;
267
281
  }
268
282
 
269
283
  .boltdocs-tabs::-webkit-scrollbar {
@@ -272,7 +286,7 @@
272
286
 
273
287
  .boltdocs-tab-item {
274
288
  padding: 0.85rem 0;
275
- padding-bottom: calc(0.85rem + 1px); /* avoid clipping the indicator */
289
+ padding-bottom: calc(0.85rem + 1px);
276
290
  font-size: 0.875rem;
277
291
  font-weight: 500;
278
292
  color: var(--ld-text-muted);
@@ -284,6 +298,7 @@
284
298
  align-items: center;
285
299
  gap: 0.6rem;
286
300
  opacity: 0.7;
301
+ z-index: 20;
287
302
  }
288
303
 
289
304
  .boltdocs-tab-item:hover {
@@ -317,23 +332,16 @@
317
332
  text-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
318
333
  }
319
334
 
320
- .boltdocs-tab-item::after {
321
- content: "";
335
+ .boltdocs-tab-indicator {
322
336
  position: absolute;
323
- bottom: 0px;
337
+ bottom: 0px; /* Aligned with the border pseudo-element */
324
338
  left: 0;
325
- right: 0;
326
339
  height: 3px;
327
- background: var(--ld-primary);
340
+ background: var(--ld-color-primary);
328
341
  border-radius: 2px 2px 0 0;
329
- box-shadow: 0 0 12px rgba(var(--ld-primary-rgb), 0.6);
330
- opacity: 0;
331
- transform: scaleX(0);
332
- transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
333
- z-index: 10;
334
- }
335
-
336
- .boltdocs-tab-item.active::after {
337
- opacity: 1;
338
- transform: scaleX(1);
342
+ box-shadow: 0 -2px 15px var(--ld-color-primary-glow);
343
+ transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), width 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
344
+ z-index: 100; /* Higher than border pseudo-element */
345
+ pointer-events: none;
346
+ transform-origin: left;
339
347
  }
@@ -169,14 +169,7 @@ export function OnThisPage({
169
169
  e.preventDefault();
170
170
  const el = document.getElementById(id);
171
171
  if (el) {
172
- const offset = 80;
173
- const bodyRect = document.body.getBoundingClientRect().top;
174
- const elementRect = el.getBoundingClientRect().top;
175
- const elementPosition = elementRect - bodyRect;
176
- const offsetPosition = elementPosition - offset;
177
-
178
- window.scrollTo({
179
- top: offsetPosition,
172
+ el.scrollIntoView({
180
173
  behavior: "smooth",
181
174
  });
182
175
 
@@ -0,0 +1,17 @@
1
+ .boltdocs-progress-container {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 2px;
7
+ z-index: 1000;
8
+ pointer-events: none;
9
+ background: transparent;
10
+ }
11
+
12
+ .boltdocs-progress-bar {
13
+ height: 100%;
14
+ background: linear-gradient(90deg, var(--ld-color-primary), var(--ld-gradient-to, var(--ld-color-primary)));
15
+ box-shadow: 0 0 8px var(--ld-color-primary-glow);
16
+ transition: width 0.1s ease-out;
17
+ }
@@ -0,0 +1,51 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import "./ProgressBar.css";
3
+
4
+ export function ProgressBar() {
5
+ const [progress, setProgress] = useState(0);
6
+
7
+ useEffect(() => {
8
+ let container: Element | null = null;
9
+ let timer: any;
10
+
11
+ const handleScroll = () => {
12
+ if (!container) return;
13
+ const { scrollTop, scrollHeight, clientHeight } = container;
14
+ if (scrollHeight <= clientHeight) {
15
+ setProgress(0);
16
+ return;
17
+ }
18
+ const scrollPercent = (scrollTop / (scrollHeight - clientHeight)) * 100;
19
+ setProgress(Math.min(100, Math.max(0, scrollPercent)));
20
+ };
21
+
22
+ const attachListener = () => {
23
+ container = document.querySelector(".boltdocs-content");
24
+ if (container) {
25
+ container.addEventListener("scroll", handleScroll);
26
+ handleScroll();
27
+ if (timer) clearInterval(timer);
28
+ return true;
29
+ }
30
+ return false;
31
+ };
32
+
33
+ if (!attachListener()) {
34
+ timer = setInterval(attachListener, 100);
35
+ }
36
+
37
+ return () => {
38
+ if (container) container.removeEventListener("scroll", handleScroll);
39
+ if (timer) clearInterval(timer);
40
+ };
41
+ }, []);
42
+
43
+ return (
44
+ <div className="boltdocs-progress-container">
45
+ <div
46
+ className="boltdocs-progress-bar"
47
+ style={{ width: `${progress}%` }}
48
+ />
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1 @@
1
+ export * from "./ProgressBar";
@@ -49,7 +49,7 @@ export function SearchDialog({ routes }: { routes: ComponentRoute[] }) {
49
49
 
50
50
  const results: SearchResult[] = [];
51
51
  const lowerQuery = query.toLowerCase();
52
-
52
+
53
53
  for (const route of routes) {
54
54
  if (route.title && route.title.toLowerCase().includes(lowerQuery)) {
55
55
  results.push({
@@ -71,6 +71,16 @@ export function SearchDialog({ routes }: { routes: ComponentRoute[] }) {
71
71
  }
72
72
  }
73
73
  }
74
+
75
+ if (route._content && route._content.toLowerCase().includes(lowerQuery)) {
76
+ // If it's a content match but not a title/heading match, add it
77
+ // We only add the page itself for now
78
+ results.push({
79
+ title: route.title,
80
+ path: route.path,
81
+ groupTitle: route.groupTitle,
82
+ });
83
+ }
74
84
  }
75
85
 
76
86
  // Deduplicate results by path
@@ -37,6 +37,8 @@ export interface ComponentRoute {
37
37
  badge?: string | { text: string; expires?: string };
38
38
  /** Optional icon for the route's group */
39
39
  groupIcon?: string;
40
+ /** The extracted plain-text content of the page for search indexing */
41
+ _content?: string;
40
42
  }
41
43
 
42
44
  /**
@@ -29,6 +29,7 @@ export async function generateRoutes(
29
29
  ): Promise<RouteMeta[]> {
30
30
  // Load persistent cache on first call
31
31
  docCache.load();
32
+ docCache.invalidateAll(); // FORCE RE-PARSE to pick up new _content field
32
33
 
33
34
  const files = await fastGlob(["**/*.md", "**/*.mdx"], {
34
35
  cwd: docsDir,
@@ -151,6 +151,16 @@ export function parseDocFile(
151
151
  const sanitizedBadge = data.badge ? data.badge : undefined;
152
152
  const icon = data.icon ? String(data.icon) : undefined;
153
153
 
154
+ // Extract full content as plain text for search indexing
155
+ const plainText = content
156
+ .replace(/^#+.*$/gm, "") // Remove headers
157
+ .replace(/\[([^\]]+)\]\([^\)]+\)/g, "$1") // Simplify links
158
+ .replace(/<[^>]+>/g, "") // Remove HTML/JSX tags
159
+ .replace(/\{[^\}]+\}/g, "") // Remove JS expressions/curly braces
160
+ .replace(/[_*`]/g, "") // Remove formatting
161
+ .replace(/\n+/g, " ") // Normalize whitespace
162
+ .trim();
163
+
154
164
  return {
155
165
  route: {
156
166
  path: finalPath,
@@ -165,6 +175,7 @@ export function parseDocFile(
165
175
  badge: sanitizedBadge,
166
176
  icon,
167
177
  tab: inferredTab,
178
+ _content: plainText,
168
179
  },
169
180
  relativeDir: cleanDirName,
170
181
  isGroupIndex,
@@ -35,6 +35,8 @@ export interface RouteMeta {
35
35
  icon?: string;
36
36
  /** The tab this route belongs to, if tabs are configured */
37
37
  tab?: string;
38
+ /** The extracted plain-text content of the page for search indexing */
39
+ _content?: string;
38
40
  }
39
41
 
40
42
  /**