boltdocs 1.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.
Files changed (137) hide show
  1. package/dist/CodeBlock-37XMKCYY.mjs +7 -0
  2. package/dist/PackageManagerTabs-4NWXLXQO.mjs +314 -0
  3. package/dist/Playground-OE2OE6B6.mjs +7 -0
  4. package/dist/SearchDialog-FTOQZ763.mjs +187 -0
  5. package/dist/SearchDialog-ZAZXYIFX.css +2147 -0
  6. package/dist/Video-I6QY4X7J.mjs +7 -0
  7. package/dist/chunk-2YRDWM6O.mjs +56 -0
  8. package/dist/chunk-PN4GCTYG.mjs +67 -0
  9. package/dist/chunk-X2TDGMTR.mjs +64 -0
  10. package/dist/chunk-X6BYQHVC.mjs +12 -0
  11. package/dist/chunk-Z7JHYNAS.mjs +57 -0
  12. package/dist/chunk-ZFCOLEXN.mjs +1644 -0
  13. package/dist/client/index.css +2147 -0
  14. package/dist/client/index.d.mts +298 -0
  15. package/dist/client/index.d.ts +298 -0
  16. package/dist/client/index.js +2793 -0
  17. package/dist/client/index.mjs +63 -0
  18. package/dist/client/ssr.css +2147 -0
  19. package/dist/client/ssr.d.mts +25 -0
  20. package/dist/client/ssr.d.ts +25 -0
  21. package/dist/client/ssr.js +2727 -0
  22. package/dist/client/ssr.mjs +32 -0
  23. package/dist/config-D2XmHJYe.d.mts +122 -0
  24. package/dist/config-D2XmHJYe.d.ts +122 -0
  25. package/dist/index-CRQKWAeo.d.mts +82 -0
  26. package/dist/index-CRQKWAeo.d.ts +82 -0
  27. package/dist/node/cli/index.d.mts +1 -0
  28. package/dist/node/cli/index.d.ts +1 -0
  29. package/dist/node/cli/index.js +199 -0
  30. package/dist/node/cli/index.mjs +154 -0
  31. package/dist/node/index.d.mts +79 -0
  32. package/dist/node/index.d.ts +79 -0
  33. package/dist/node/index.js +797 -0
  34. package/dist/node/index.mjs +719 -0
  35. package/package.json +79 -0
  36. package/src/client/app/index.tsx +422 -0
  37. package/src/client/app/preload.tsx +56 -0
  38. package/src/client/index.ts +40 -0
  39. package/src/client/ssr.tsx +50 -0
  40. package/src/client/theme/components/CodeBlock/CodeBlock.tsx +76 -0
  41. package/src/client/theme/components/CodeBlock/index.ts +1 -0
  42. package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +154 -0
  43. package/src/client/theme/components/PackageManagerTabs/index.ts +1 -0
  44. package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +64 -0
  45. package/src/client/theme/components/Playground/Playground.tsx +86 -0
  46. package/src/client/theme/components/Playground/index.ts +1 -0
  47. package/src/client/theme/components/Playground/playground.css +168 -0
  48. package/src/client/theme/components/Video/Video.tsx +84 -0
  49. package/src/client/theme/components/Video/index.ts +1 -0
  50. package/src/client/theme/components/Video/video.css +41 -0
  51. package/src/client/theme/components/mdx/Admonition.tsx +80 -0
  52. package/src/client/theme/components/mdx/Badge.tsx +31 -0
  53. package/src/client/theme/components/mdx/Button.tsx +50 -0
  54. package/src/client/theme/components/mdx/Card.tsx +80 -0
  55. package/src/client/theme/components/mdx/List.tsx +57 -0
  56. package/src/client/theme/components/mdx/Tabs.tsx +94 -0
  57. package/src/client/theme/components/mdx/index.ts +18 -0
  58. package/src/client/theme/components/mdx/mdx-components.css +405 -0
  59. package/src/client/theme/icons/bun.tsx +62 -0
  60. package/src/client/theme/icons/deno.tsx +20 -0
  61. package/src/client/theme/icons/discord.tsx +12 -0
  62. package/src/client/theme/icons/github.tsx +15 -0
  63. package/src/client/theme/icons/npm.tsx +13 -0
  64. package/src/client/theme/icons/pnpm.tsx +72 -0
  65. package/src/client/theme/icons/twitter.tsx +12 -0
  66. package/src/client/theme/styles/home.css +60 -0
  67. package/src/client/theme/styles/markdown.css +343 -0
  68. package/src/client/theme/styles/variables.css +162 -0
  69. package/src/client/theme/styles.css +38 -0
  70. package/src/client/theme/ui/BackgroundGradient/BackgroundGradient.tsx +10 -0
  71. package/src/client/theme/ui/BackgroundGradient/index.ts +1 -0
  72. package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +68 -0
  73. package/src/client/theme/ui/Breadcrumbs/index.ts +1 -0
  74. package/src/client/theme/ui/Footer/footer.css +32 -0
  75. package/src/client/theme/ui/Head/Head.tsx +69 -0
  76. package/src/client/theme/ui/Head/index.ts +1 -0
  77. package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +125 -0
  78. package/src/client/theme/ui/LanguageSwitcher/index.ts +1 -0
  79. package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +98 -0
  80. package/src/client/theme/ui/Layout/Layout.tsx +213 -0
  81. package/src/client/theme/ui/Layout/base.css +76 -0
  82. package/src/client/theme/ui/Layout/index.ts +2 -0
  83. package/src/client/theme/ui/Layout/pagination.css +72 -0
  84. package/src/client/theme/ui/Layout/responsive.css +40 -0
  85. package/src/client/theme/ui/Link/Link.tsx +202 -0
  86. package/src/client/theme/ui/Link/index.ts +2 -0
  87. package/src/client/theme/ui/Loading/Loading.tsx +10 -0
  88. package/src/client/theme/ui/Loading/index.ts +1 -0
  89. package/src/client/theme/ui/Loading/loading.css +30 -0
  90. package/src/client/theme/ui/Navbar/GithubStars.tsx +27 -0
  91. package/src/client/theme/ui/Navbar/Navbar.tsx +145 -0
  92. package/src/client/theme/ui/Navbar/index.ts +2 -0
  93. package/src/client/theme/ui/Navbar/navbar.css +233 -0
  94. package/src/client/theme/ui/NotFound/NotFound.tsx +20 -0
  95. package/src/client/theme/ui/NotFound/index.ts +1 -0
  96. package/src/client/theme/ui/NotFound/not-found.css +64 -0
  97. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +192 -0
  98. package/src/client/theme/ui/OnThisPage/index.ts +1 -0
  99. package/src/client/theme/ui/OnThisPage/toc.css +132 -0
  100. package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +18 -0
  101. package/src/client/theme/ui/PoweredBy/index.ts +1 -0
  102. package/src/client/theme/ui/PoweredBy/powered-by.css +76 -0
  103. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +199 -0
  104. package/src/client/theme/ui/SearchDialog/index.ts +1 -0
  105. package/src/client/theme/ui/SearchDialog/search.css +152 -0
  106. package/src/client/theme/ui/Sidebar/Sidebar.tsx +200 -0
  107. package/src/client/theme/ui/Sidebar/index.ts +1 -0
  108. package/src/client/theme/ui/Sidebar/sidebar.css +269 -0
  109. package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +69 -0
  110. package/src/client/theme/ui/ThemeToggle/index.ts +1 -0
  111. package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +136 -0
  112. package/src/client/theme/ui/VersionSwitcher/index.ts +1 -0
  113. package/src/client/utils.ts +26 -0
  114. package/src/node/cache.ts +94 -0
  115. package/src/node/cli/commands/config.ts +15 -0
  116. package/src/node/cli/commands/generate-css.ts +24 -0
  117. package/src/node/cli/constants.ts +70 -0
  118. package/src/node/cli/index.ts +22 -0
  119. package/src/node/config.ts +185 -0
  120. package/src/node/index.ts +21 -0
  121. package/src/node/mdx.ts +41 -0
  122. package/src/node/plugin/entry.ts +58 -0
  123. package/src/node/plugin/html.ts +55 -0
  124. package/src/node/plugin/index.ts +190 -0
  125. package/src/node/plugin/types.ts +11 -0
  126. package/src/node/routes/cache.ts +24 -0
  127. package/src/node/routes/index.ts +152 -0
  128. package/src/node/routes/parser.ts +127 -0
  129. package/src/node/routes/sorter.ts +42 -0
  130. package/src/node/routes/types.ts +49 -0
  131. package/src/node/ssg/index.ts +110 -0
  132. package/src/node/ssg/meta.ts +34 -0
  133. package/src/node/ssg/options.ts +13 -0
  134. package/src/node/ssg/sitemap.ts +54 -0
  135. package/src/node/utils.ts +134 -0
  136. package/tsconfig.json +20 -0
  137. package/tsup.config.ts +22 -0
@@ -0,0 +1,64 @@
1
+ /* ═══════════════════════════════════════════════════════════
2
+ 404 NOT FOUND
3
+ ═══════════════════════════════════════════════════════════ */
4
+ .boltdocs-not-found {
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ min-height: 60vh;
9
+ text-align: center;
10
+ padding: 2rem;
11
+ }
12
+
13
+ .not-found-content {
14
+ max-width: 420px;
15
+ }
16
+
17
+ .not-found-code {
18
+ display: block;
19
+ font-size: 6rem;
20
+ font-weight: 900;
21
+ line-height: 1;
22
+ background: linear-gradient(
23
+ 135deg,
24
+ var(--ld-gradient-from),
25
+ var(--ld-gradient-to)
26
+ );
27
+ -webkit-background-clip: text;
28
+ -webkit-text-fill-color: transparent;
29
+ background-clip: text;
30
+ margin-bottom: 0.5rem;
31
+ }
32
+
33
+ .not-found-title {
34
+ font-size: 1.5rem;
35
+ font-weight: 700;
36
+ margin: 0 0 0.75rem;
37
+ color: var(--ld-text-main);
38
+ }
39
+
40
+ .not-found-text {
41
+ color: var(--ld-text-muted);
42
+ margin-bottom: 1.5rem;
43
+ }
44
+
45
+ a.not-found-link {
46
+ display: inline-flex;
47
+ align-items: center;
48
+ gap: 0.5rem;
49
+ padding: 0.6rem 1.5rem;
50
+ background-color: var(--ld-btn-primary-bg);
51
+ color: var(--ld-btn-primary-text) !important;
52
+ border-radius: var(--ld-radius-md);
53
+ text-decoration: none;
54
+ font-weight: 600;
55
+ transition: all 0.2s;
56
+ }
57
+
58
+ a.not-found-link:hover {
59
+ background-color: var(--ld-btn-primary-bg);
60
+ opacity: 0.9;
61
+ transform: translateY(-1px);
62
+ box-shadow: 0 4px 16px var(--ld-color-primary-glow);
63
+ color: var(--ld-btn-primary-text) !important;
64
+ }
@@ -0,0 +1,192 @@
1
+ import React, { useEffect, useState, useRef, useCallback } from "react";
2
+ import { useLocation } from "react-router-dom";
3
+ import { Pencil, CircleHelp } from "lucide-react";
4
+
5
+ interface TocHeading {
6
+ id: string;
7
+ text: string;
8
+ level: number;
9
+ }
10
+
11
+ export function OnThisPage({
12
+ headings = [],
13
+ editLink,
14
+ communityHelp,
15
+ filePath,
16
+ }: {
17
+ headings?: TocHeading[];
18
+ editLink?: string;
19
+ communityHelp?: string;
20
+ filePath?: string;
21
+ }) {
22
+ const [activeId, setActiveId] = useState<string>("");
23
+ const [indicatorStyle, setIndicatorStyle] = useState<React.CSSProperties>({});
24
+ const observerRef = useRef<IntersectionObserver | null>(null);
25
+ const location = useLocation();
26
+ const listRef = useRef<HTMLUListElement>(null);
27
+
28
+ // Reset active ID when path changes
29
+ useEffect(() => {
30
+ if (headings.length > 0) {
31
+ // Check if there's a hash in the URL
32
+ const hash = window.location.hash.substring(1);
33
+ if (hash && headings.some((h) => h.id === hash)) {
34
+ setActiveId(hash);
35
+ } else {
36
+ setActiveId(headings[0].id);
37
+ }
38
+ }
39
+ }, [location.pathname, headings]);
40
+
41
+ // Update indicator position
42
+ useEffect(() => {
43
+ if (!activeId || !listRef.current) return;
44
+
45
+ const activeElement = listRef.current.querySelector(
46
+ `a[href="#${activeId}"]`,
47
+ ) as HTMLElement;
48
+
49
+ if (activeElement) {
50
+ const { offsetTop, offsetHeight } = activeElement;
51
+ setIndicatorStyle({
52
+ transform: `translateY(${offsetTop}px)`,
53
+ height: `${offsetHeight}px`,
54
+ opacity: 1,
55
+ });
56
+ }
57
+ }, [activeId, headings]);
58
+
59
+ // IntersectionObserver for active heading tracking
60
+ useEffect(() => {
61
+ if (headings.length === 0) return;
62
+
63
+ if (observerRef.current) {
64
+ observerRef.current.disconnect();
65
+ }
66
+
67
+ const callback: IntersectionObserverCallback = (entries) => {
68
+ // Find all entries that are intersecting
69
+ const visibleEntries = entries.filter((entry) => entry.isIntersecting);
70
+
71
+ if (visibleEntries.length > 0) {
72
+ // If we have visible entries, find the one closest to the top of the viewport
73
+ // But with a priority for ones that just entered from the top
74
+ const closest = visibleEntries.reduce((prev, curr) => {
75
+ return Math.abs(curr.boundingClientRect.top - 100) <
76
+ Math.abs(prev.boundingClientRect.top - 100)
77
+ ? curr
78
+ : prev;
79
+ });
80
+ setActiveId(closest.target.id);
81
+ }
82
+ };
83
+
84
+ observerRef.current = new IntersectionObserver(callback, {
85
+ rootMargin: "-100px 0px -70% 0px",
86
+ threshold: [0, 1],
87
+ });
88
+
89
+ const observeHeadings = () => {
90
+ headings.forEach(({ id }) => {
91
+ const el = document.getElementById(id);
92
+ if (el) {
93
+ observerRef.current!.observe(el);
94
+ }
95
+ });
96
+ };
97
+
98
+ // Initial observation
99
+ observeHeadings();
100
+
101
+ // Re-observe if content changes (e.g. after some delay to ensure rendering)
102
+ const timeoutId = setTimeout(observeHeadings, 1000);
103
+
104
+ return () => {
105
+ observerRef.current?.disconnect();
106
+ clearTimeout(timeoutId);
107
+ };
108
+ }, [headings, location.pathname]);
109
+
110
+ const handleClick = useCallback(
111
+ (e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
112
+ e.preventDefault();
113
+ const el = document.getElementById(id);
114
+ if (el) {
115
+ const offset = 80;
116
+ const bodyRect = document.body.getBoundingClientRect().top;
117
+ const elementRect = el.getBoundingClientRect().top;
118
+ const elementPosition = elementRect - bodyRect;
119
+ const offsetPosition = elementPosition - offset;
120
+
121
+ window.scrollTo({
122
+ top: offsetPosition,
123
+ behavior: "smooth",
124
+ });
125
+
126
+ setActiveId(id);
127
+ window.history.pushState(null, "", `#${id}`);
128
+ }
129
+ },
130
+ [],
131
+ );
132
+
133
+ if (headings.length === 0) return null;
134
+
135
+ return (
136
+ <nav className="boltdocs-on-this-page" aria-label="Table of contents">
137
+ <p className="on-this-page-title">On this page</p>
138
+ <div className="on-this-page-container">
139
+ <div className="toc-indicator" style={indicatorStyle} />
140
+ <ul className="on-this-page-list" ref={listRef}>
141
+ {headings.map((h) => (
142
+ <li key={h.id} className={h.level === 3 ? "toc-indent" : ""}>
143
+ <a
144
+ href={`#${h.id}`}
145
+ className={`toc-link ${activeId === h.id ? "active" : ""}`}
146
+ aria-current={activeId === h.id ? "true" : undefined}
147
+ onClick={(e) => handleClick(e, h.id)}
148
+ >
149
+ {h.text}
150
+ </a>
151
+ </li>
152
+ ))}
153
+ </ul>
154
+ </div>
155
+
156
+ {/* Need help? section */}
157
+ {(editLink || communityHelp) && (
158
+ <div className="toc-help">
159
+ <p className="toc-help-title">Need help?</p>
160
+ <ul className="toc-help-links">
161
+ {editLink && filePath && (
162
+ <li>
163
+ <a
164
+ href={editLink.replace(":path", filePath)}
165
+ target="_blank"
166
+ rel="noopener noreferrer"
167
+ className="toc-help-link"
168
+ >
169
+ <Pencil size={16} />
170
+ Edit this page
171
+ </a>
172
+ </li>
173
+ )}
174
+ {communityHelp && (
175
+ <li>
176
+ <a
177
+ href={communityHelp}
178
+ target="_blank"
179
+ rel="noopener noreferrer"
180
+ className="toc-help-link"
181
+ >
182
+ <CircleHelp size={16} />
183
+ Community help
184
+ </a>
185
+ </li>
186
+ )}
187
+ </ul>
188
+ </div>
189
+ )}
190
+ </nav>
191
+ );
192
+ }
@@ -0,0 +1 @@
1
+ export { OnThisPage } from "./OnThisPage";
@@ -0,0 +1,132 @@
1
+ /* ═══════════════════════════════════════════════════════════
2
+ ON THIS PAGE (TOC)
3
+ ═══════════════════════════════════════════════════════════ */
4
+ .boltdocs-on-this-page {
5
+ width: var(--ld-toc-width);
6
+ flex-shrink: 0;
7
+ padding: 1.5rem 1rem;
8
+ position: sticky;
9
+ top: var(--ld-navbar-height);
10
+ height: calc(100vh - var(--ld-navbar-height));
11
+ overflow-y: auto;
12
+ scrollbar-width: thin;
13
+ scrollbar-color: var(--ld-bg-mute) transparent;
14
+ }
15
+
16
+ .on-this-page-title {
17
+ font-size: 0.6875rem;
18
+ font-weight: 700;
19
+ text-transform: uppercase;
20
+ letter-spacing: 0.08em;
21
+ color: var(--ld-text-dim);
22
+ margin: 0 0 0.75rem;
23
+ padding-left: 0.75rem;
24
+ }
25
+
26
+ .on-this-page-container {
27
+ position: relative;
28
+ padding-left: 2px;
29
+ }
30
+
31
+ .on-this-page-list {
32
+ list-style: none;
33
+ padding: 0;
34
+ margin: 0;
35
+ border-left: 1px solid var(--ld-border-subtle);
36
+ }
37
+
38
+ .toc-indicator {
39
+ position: absolute;
40
+ left: 0;
41
+ width: 2px;
42
+ background-color: var(--ld-color-accent);
43
+ transition:
44
+ transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
45
+ height 0.25s cubic-bezier(0.4, 0, 0.2, 1),
46
+ opacity 0.2s ease;
47
+ z-index: 1;
48
+ border-radius: 2px;
49
+ pointer-events: none;
50
+ opacity: 0;
51
+ }
52
+
53
+ .on-this-page-list li {
54
+ margin: 0;
55
+ }
56
+
57
+ .on-this-page-list li.toc-indent {
58
+ padding-left: 0.75rem;
59
+ }
60
+
61
+ .toc-link {
62
+ display: block;
63
+ padding: 0.35rem 0.75rem;
64
+ font-size: 0.8125rem;
65
+ color: var(--ld-text-dim);
66
+ text-decoration: none;
67
+ border-left: 2px solid transparent;
68
+ margin-left: -2px;
69
+ line-height: 1.4;
70
+ transition: color 0.2s;
71
+ }
72
+
73
+ .toc-link:hover {
74
+ color: var(--ld-text-main);
75
+ }
76
+
77
+ .toc-link.active {
78
+ color: var(--ld-color-primary);
79
+ font-weight: 500;
80
+ }
81
+
82
+ /* ─── Need Help Section ──────────────────────────────────── */
83
+ .toc-help {
84
+ margin-top: 2rem;
85
+ padding-top: 1rem;
86
+ border-top: 1px solid var(--ld-border-subtle);
87
+ }
88
+
89
+ .toc-help-title {
90
+ font-size: 0.6875rem;
91
+ font-weight: 700;
92
+ text-transform: uppercase;
93
+ letter-spacing: 0.08em;
94
+ color: var(--ld-text-dim);
95
+ margin: 0 0 0.5rem;
96
+ padding-left: 0.75rem;
97
+ }
98
+
99
+ .toc-help-links {
100
+ list-style: none;
101
+ padding: 0;
102
+ margin: 0;
103
+ }
104
+
105
+ .toc-help-links li {
106
+ margin-bottom: 2px;
107
+ }
108
+
109
+ .toc-help-link {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 0.4rem;
113
+ padding: 0.3rem 0.75rem;
114
+ font-size: 0.8rem;
115
+ color: var(--ld-text-dim);
116
+ text-decoration: none;
117
+ border-radius: var(--ld-radius-md);
118
+ transition:
119
+ color 0.2s,
120
+ background-color 0.2s;
121
+ }
122
+
123
+ .toc-help-link:hover {
124
+ color: var(--ld-text-muted);
125
+ background-color: rgba(255, 255, 255, 0.03);
126
+ }
127
+
128
+ .toc-help-link svg {
129
+ width: 14px;
130
+ height: 14px;
131
+ opacity: 0.6;
132
+ }
@@ -0,0 +1,18 @@
1
+ import { Zap } from "lucide-react";
2
+
3
+ export function PoweredBy() {
4
+ return (
5
+ <div className="powered-by-container">
6
+ <a
7
+ href="https://github.com/jesusalcaladev/boltdocs"
8
+ target="_blank"
9
+ rel="noopener noreferrer"
10
+ className="powered-by-link"
11
+ >
12
+ <Zap className="powered-by-icon" size={12} fill="currentColor" />
13
+ <span>Powered by</span>
14
+ <span className="powered-by-brand">LiteDocs</span>
15
+ </a>
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1 @@
1
+ export { PoweredBy } from "./PoweredBy";
@@ -0,0 +1,76 @@
1
+ .powered-by-container {
2
+ display: flex;
3
+ justify-content: center;
4
+ padding: 1rem 0.6rem;
5
+ }
6
+
7
+ .powered-by-link {
8
+ position: relative;
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: 0.45rem;
12
+ padding: 0.45rem 1rem;
13
+ border-radius: var(--ld-radius-full);
14
+ background: rgba(255, 255, 255, 0.03);
15
+ backdrop-filter: blur(8px);
16
+ -webkit-backdrop-filter: blur(8px);
17
+ border: 1px solid rgba(255, 255, 255, 0.08);
18
+ color: var(--ld-text-dim);
19
+ font-size: 0.725rem;
20
+ font-weight: 500;
21
+ text-decoration: none;
22
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
23
+ letter-spacing: 0.01em;
24
+ overflow: hidden;
25
+ }
26
+
27
+ .powered-by-link::before {
28
+ content: "";
29
+ position: absolute;
30
+ top: 0;
31
+ left: -100%;
32
+ width: 100%;
33
+ height: 100%;
34
+ background: linear-gradient(
35
+ 90deg,
36
+ transparent,
37
+ rgba(255, 255, 255, 0.05),
38
+ transparent
39
+ );
40
+ transition: 0.5s;
41
+ }
42
+
43
+ .powered-by-link:hover::before {
44
+ left: 100%;
45
+ }
46
+
47
+ .powered-by-link:hover {
48
+ background: rgba(255, 255, 255, 0.06);
49
+ border-color: rgba(255, 255, 255, 0.2);
50
+ color: var(--ld-text-main);
51
+ transform: translateY(-2px);
52
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
53
+ }
54
+
55
+ .powered-by-icon {
56
+ color: var(--ld-color-primary);
57
+ filter: drop-shadow(0 0 4px var(--ld-color-primary));
58
+ transition: transform 0.3s ease;
59
+ }
60
+
61
+ .powered-by-link:hover .powered-by-icon {
62
+ transform: scale(1.2) rotate(-10deg);
63
+ filter: drop-shadow(0 0 8px var(--ld-color-primary));
64
+ }
65
+
66
+ .powered-by-brand {
67
+ color: var(--ld-color-primary);
68
+ font-weight: 700;
69
+ text-shadow: 0 0 10px rgba(var(--ld-color-primary-rgb, 56, 189, 248), 0.3);
70
+ }
71
+
72
+ @media (max-width: 768px) {
73
+ .powered-by-container {
74
+ padding: 0.75rem 0.5rem;
75
+ }
76
+ }
@@ -0,0 +1,199 @@
1
+ import React, { useState, useEffect, useRef } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { Link } from "../Link";
4
+ import { Search } from "lucide-react";
5
+ import { ComponentRoute } from "../../../app";
6
+
7
+ interface SearchResult {
8
+ title: string;
9
+ path: string;
10
+ groupTitle?: string;
11
+ isHeading?: boolean;
12
+ }
13
+
14
+ export function SearchDialog({ routes }: { routes: ComponentRoute[] }) {
15
+ const [isOpen, setIsOpen] = useState(false);
16
+ const [query, setQuery] = useState("");
17
+ const inputRef = useRef<HTMLInputElement>(null);
18
+
19
+ useEffect(() => {
20
+ const handleKeyDown = (e: KeyboardEvent) => {
21
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
22
+ e.preventDefault();
23
+ setIsOpen((prev) => !prev);
24
+ }
25
+ if (e.key === "Escape" && isOpen) {
26
+ setIsOpen(false);
27
+ }
28
+ };
29
+ window.addEventListener("keydown", handleKeyDown);
30
+ return () => window.removeEventListener("keydown", handleKeyDown);
31
+ }, [isOpen]);
32
+
33
+ useEffect(() => {
34
+ if (isOpen) {
35
+ setTimeout(() => inputRef.current?.focus(), 50);
36
+ } else {
37
+ setQuery("");
38
+ }
39
+ }, [isOpen]);
40
+
41
+ const searchResults: SearchResult[] = React.useMemo(() => {
42
+ if (!query) {
43
+ return routes.slice(0, 10).map((r) => ({
44
+ title: r.title,
45
+ path: r.path,
46
+ groupTitle: r.groupTitle,
47
+ }));
48
+ }
49
+
50
+ const results: SearchResult[] = [];
51
+ const lowerQuery = query.toLowerCase();
52
+
53
+ for (const route of routes) {
54
+ if (route.title && route.title.toLowerCase().includes(lowerQuery)) {
55
+ results.push({
56
+ title: route.title,
57
+ path: route.path,
58
+ groupTitle: route.groupTitle,
59
+ });
60
+ }
61
+
62
+ if (route.headings) {
63
+ for (const heading of route.headings) {
64
+ if (heading.text.toLowerCase().includes(lowerQuery)) {
65
+ results.push({
66
+ title: heading.text,
67
+ path: `${route.path}#${heading.id}`,
68
+ groupTitle: route.title,
69
+ isHeading: true,
70
+ });
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ // Deduplicate results by path
77
+ const uniqueResults = [];
78
+ const seenPaths = new Set();
79
+ for (const res of results) {
80
+ if (!seenPaths.has(res.path)) {
81
+ seenPaths.add(res.path);
82
+ uniqueResults.push(res);
83
+ }
84
+ }
85
+
86
+ return uniqueResults.slice(0, 10);
87
+ }, [routes, query]);
88
+
89
+ return (
90
+ <>
91
+ <div
92
+ className="navbar-search"
93
+ role="button"
94
+ tabIndex={0}
95
+ onClick={() => setIsOpen(true)}
96
+ onKeyDown={(e) => {
97
+ if (e.key === "Enter" || e.key === " ") {
98
+ e.preventDefault();
99
+ setIsOpen(true);
100
+ }
101
+ }}
102
+ aria-label="Open search dialog"
103
+ >
104
+ <Search className="boltdocs-search-icon" size={18} />
105
+ Search docs...
106
+ <kbd>⌘K</kbd>
107
+ </div>
108
+
109
+ {isOpen &&
110
+ createPortal(
111
+ <div
112
+ className="boltdocs-search-overlay"
113
+ onPointerDown={() => setIsOpen(false)}
114
+ >
115
+ <div
116
+ className="boltdocs-search-modal"
117
+ role="dialog"
118
+ aria-modal="true"
119
+ aria-label="Search"
120
+ onPointerDown={(e) => e.stopPropagation()}
121
+ >
122
+ <div className="boltdocs-search-header">
123
+ <Search size={18} />
124
+ <input
125
+ ref={inputRef}
126
+ type="text"
127
+ aria-label="Search documentation input"
128
+ placeholder="Search documentation..."
129
+ value={query}
130
+ onChange={(e) => setQuery(e.target.value)}
131
+ />
132
+ <button
133
+ className="boltdocs-search-close"
134
+ onClick={() => setIsOpen(false)}
135
+ aria-label="Close search"
136
+ >
137
+ ESC
138
+ </button>
139
+ </div>
140
+
141
+ <div className="boltdocs-search-results">
142
+ {searchResults.length > 0 ? (
143
+ searchResults.map((result) => (
144
+ <Link
145
+ key={result.path}
146
+ to={result.path === "" ? "/" : result.path}
147
+ className={`boltdocs-search-result-item ${result.isHeading ? "is-heading" : ""}`}
148
+ onClick={(e) => {
149
+ const isSamePath =
150
+ result.path.split("#")[0] ===
151
+ window.location.pathname;
152
+ if (isSamePath && result.isHeading) {
153
+ e.preventDefault();
154
+ const id = result.path.split("#")[1];
155
+ const el = document.getElementById(id);
156
+ if (el) {
157
+ const offset = 80;
158
+ const bodyRect =
159
+ document.body.getBoundingClientRect().top;
160
+ const elementRect = el.getBoundingClientRect().top;
161
+ const elementPosition = elementRect - bodyRect;
162
+ const offsetPosition = elementPosition - offset;
163
+
164
+ window.scrollTo({
165
+ top: offsetPosition,
166
+ behavior: "smooth",
167
+ });
168
+ window.history.pushState(null, "", `#${id}`);
169
+ }
170
+ }
171
+ setIsOpen(false);
172
+ }}
173
+ >
174
+ <span className="boltdocs-search-result-title">
175
+ {result.isHeading ? (
176
+ <span className="heading-indicator">#</span>
177
+ ) : null}
178
+ {result.title}
179
+ </span>
180
+ {result.groupTitle && (
181
+ <span className="boltdocs-search-result-group">
182
+ {result.groupTitle}
183
+ </span>
184
+ )}
185
+ </Link>
186
+ ))
187
+ ) : (
188
+ <div className="boltdocs-search-empty">
189
+ No results found for "{query}"
190
+ </div>
191
+ )}
192
+ </div>
193
+ </div>
194
+ </div>,
195
+ document.body,
196
+ )}
197
+ </>
198
+ );
199
+ }
@@ -0,0 +1 @@
1
+ export { SearchDialog } from "./SearchDialog";