boltdocs 1.3.0 → 1.3.2

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 (103) hide show
  1. package/dist/{cache-EHR7SXRU.mjs → cache-GQHF6BXI.mjs} +1 -1
  2. package/dist/{chunk-GSYECEZY.mjs → chunk-CYBWLFOG.mjs} +5 -1
  3. package/dist/node/index.js +36 -20
  4. package/dist/node/index.mjs +34 -22
  5. package/package.json +1 -1
  6. package/src/client/app/index.tsx +344 -344
  7. package/src/client/app/preload.tsx +56 -56
  8. package/src/client/index.ts +40 -40
  9. package/src/client/ssr.tsx +51 -51
  10. package/src/client/theme/components/CodeBlock/CodeBlock.tsx +76 -76
  11. package/src/client/theme/components/CodeBlock/index.ts +1 -1
  12. package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +154 -154
  13. package/src/client/theme/components/PackageManagerTabs/index.ts +1 -1
  14. package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +64 -64
  15. package/src/client/theme/components/Playground/Playground.tsx +124 -124
  16. package/src/client/theme/components/Playground/index.ts +1 -1
  17. package/src/client/theme/components/Playground/playground.css +168 -168
  18. package/src/client/theme/components/Video/Video.tsx +84 -84
  19. package/src/client/theme/components/Video/index.ts +1 -1
  20. package/src/client/theme/components/Video/video.css +41 -41
  21. package/src/client/theme/components/mdx/Admonition.tsx +80 -80
  22. package/src/client/theme/components/mdx/Badge.tsx +31 -31
  23. package/src/client/theme/components/mdx/Button.tsx +50 -50
  24. package/src/client/theme/components/mdx/Card.tsx +80 -80
  25. package/src/client/theme/components/mdx/List.tsx +57 -57
  26. package/src/client/theme/components/mdx/Tabs.tsx +94 -94
  27. package/src/client/theme/components/mdx/index.ts +18 -18
  28. package/src/client/theme/components/mdx/mdx-components.css +424 -424
  29. package/src/client/theme/icons/bun.tsx +62 -62
  30. package/src/client/theme/icons/deno.tsx +20 -20
  31. package/src/client/theme/icons/discord.tsx +12 -12
  32. package/src/client/theme/icons/github.tsx +15 -15
  33. package/src/client/theme/icons/npm.tsx +13 -13
  34. package/src/client/theme/icons/pnpm.tsx +72 -72
  35. package/src/client/theme/icons/twitter.tsx +12 -12
  36. package/src/client/theme/styles/markdown.css +343 -343
  37. package/src/client/theme/styles/variables.css +162 -162
  38. package/src/client/theme/styles.css +37 -37
  39. package/src/client/theme/ui/BackgroundGradient/BackgroundGradient.tsx +10 -10
  40. package/src/client/theme/ui/BackgroundGradient/index.ts +1 -1
  41. package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +68 -68
  42. package/src/client/theme/ui/Breadcrumbs/index.ts +1 -1
  43. package/src/client/theme/ui/Footer/footer.css +32 -32
  44. package/src/client/theme/ui/Head/Head.tsx +69 -69
  45. package/src/client/theme/ui/Head/index.ts +1 -1
  46. package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +125 -125
  47. package/src/client/theme/ui/LanguageSwitcher/index.ts +1 -1
  48. package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +98 -98
  49. package/src/client/theme/ui/Layout/Layout.tsx +202 -202
  50. package/src/client/theme/ui/Layout/base.css +76 -76
  51. package/src/client/theme/ui/Layout/index.ts +2 -2
  52. package/src/client/theme/ui/Layout/pagination.css +72 -72
  53. package/src/client/theme/ui/Layout/responsive.css +36 -36
  54. package/src/client/theme/ui/Link/Link.tsx +254 -254
  55. package/src/client/theme/ui/Link/index.ts +2 -2
  56. package/src/client/theme/ui/Loading/Loading.tsx +10 -10
  57. package/src/client/theme/ui/Loading/index.ts +1 -1
  58. package/src/client/theme/ui/Loading/loading.css +30 -30
  59. package/src/client/theme/ui/Navbar/GithubStars.tsx +27 -27
  60. package/src/client/theme/ui/Navbar/Navbar.tsx +145 -145
  61. package/src/client/theme/ui/Navbar/index.ts +2 -2
  62. package/src/client/theme/ui/Navbar/navbar.css +233 -233
  63. package/src/client/theme/ui/NotFound/NotFound.tsx +19 -19
  64. package/src/client/theme/ui/NotFound/index.ts +1 -1
  65. package/src/client/theme/ui/NotFound/not-found.css +64 -64
  66. package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +235 -235
  67. package/src/client/theme/ui/OnThisPage/index.ts +1 -1
  68. package/src/client/theme/ui/OnThisPage/toc.css +132 -132
  69. package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +18 -18
  70. package/src/client/theme/ui/PoweredBy/index.ts +1 -1
  71. package/src/client/theme/ui/PoweredBy/powered-by.css +76 -76
  72. package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +199 -199
  73. package/src/client/theme/ui/SearchDialog/index.ts +1 -1
  74. package/src/client/theme/ui/SearchDialog/search.css +152 -152
  75. package/src/client/theme/ui/Sidebar/Sidebar.tsx +204 -204
  76. package/src/client/theme/ui/Sidebar/index.ts +1 -1
  77. package/src/client/theme/ui/Sidebar/sidebar.css +236 -236
  78. package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +69 -69
  79. package/src/client/theme/ui/ThemeToggle/index.ts +1 -1
  80. package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +136 -136
  81. package/src/client/theme/ui/VersionSwitcher/index.ts +1 -1
  82. package/src/client/types.ts +50 -50
  83. package/src/client/utils.ts +26 -26
  84. package/src/node/cache.ts +408 -408
  85. package/src/node/config.ts +192 -192
  86. package/src/node/index.ts +21 -21
  87. package/src/node/mdx.ts +120 -120
  88. package/src/node/plugin/entry.ts +58 -58
  89. package/src/node/plugin/html.ts +55 -55
  90. package/src/node/plugin/index.ts +193 -193
  91. package/src/node/plugin/types.ts +11 -11
  92. package/src/node/routes/cache.ts +28 -28
  93. package/src/node/routes/index.ts +167 -167
  94. package/src/node/routes/parser.ts +153 -127
  95. package/src/node/routes/sorter.ts +42 -42
  96. package/src/node/routes/types.ts +49 -49
  97. package/src/node/ssg/index.ts +114 -114
  98. package/src/node/ssg/meta.ts +33 -34
  99. package/src/node/ssg/options.ts +13 -13
  100. package/src/node/ssg/sitemap.ts +55 -54
  101. package/src/node/utils.ts +145 -134
  102. package/tsconfig.json +20 -20
  103. package/tsup.config.ts +22 -22
@@ -1,236 +1,236 @@
1
- /* ═══════════════════════════════════════════════════════════
2
- SIDEBAR
3
- ═══════════════════════════════════════════════════════════ */
4
- .boltdocs-sidebar {
5
- width: var(--ld-sidebar-width);
6
- flex-shrink: 0;
7
- background-color: var(--ld-sidebar-bg);
8
- backdrop-filter: blur(var(--ld-sidebar-blur));
9
- -webkit-backdrop-filter: blur(var(--ld-sidebar-blur));
10
- border-right: 1px solid var(--ld-border-subtle);
11
- padding: 1rem 0.6rem;
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;
18
- display: flex;
19
- 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;
24
- }
25
-
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%;
42
- display: flex;
43
- 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
- justify-content: center;
51
- padding: 0 0 1rem;
52
- }
53
-
54
- .sidebar-collapse-btn {
55
- 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);
67
- }
68
-
69
- .sidebar-collapse-btn:hover {
70
- background-color: var(--ld-bg-mute);
71
- color: var(--ld-text-main);
72
- transform: scale(1.05);
73
- }
74
-
75
- .boltdocs-sidebar::-webkit-scrollbar {
76
- width: 4px;
77
- }
78
-
79
- .boltdocs-sidebar::-webkit-scrollbar-track {
80
- background: transparent;
81
- }
82
-
83
- .boltdocs-sidebar::-webkit-scrollbar-thumb {
84
- background: var(--ld-bg-mute);
85
- border-radius: 4px;
86
- }
87
-
88
- .sidebar-list {
89
- list-style: none;
90
- padding: 0;
91
- margin: 0 0 0.5rem;
92
- }
93
-
94
- .sidebar-list li {
95
- margin-bottom: 1px;
96
- }
97
-
98
- .sidebar-link {
99
- display: flex;
100
- align-items: center;
101
- justify-content: space-between;
102
- padding: 0.45rem 0.75rem;
103
- color: var(--ld-text-muted);
104
- text-decoration: none;
105
- border-radius: var(--ld-radius-md);
106
- font-size: 0.875rem;
107
- font-weight: 500;
108
- transition: all 0.2s ease;
109
- margin: 0.15rem 0;
110
- }
111
-
112
- .sidebar-link-content {
113
- display: flex;
114
- align-items: center;
115
- justify-content: space-between;
116
- width: 100%;
117
- gap: 0.5rem;
118
- }
119
-
120
- .sidebar-link-content > span:first-child {
121
- flex: 1;
122
- word-wrap: break-word;
123
- }
124
-
125
- .sidebar-badge {
126
- font-size: 0.45rem;
127
- font-weight: 500;
128
- padding: 0.1rem 0.3rem;
129
- border-radius: 9999px;
130
- text-transform: uppercase;
131
- letter-spacing: 0.05em;
132
- white-space: nowrap;
133
- line-height: 1;
134
- display: inline-flex;
135
- align-items: center;
136
- flex-shrink: 0;
137
- }
138
-
139
- .sidebar-badge.badge-new {
140
- color: #38bdf8;
141
- background-color: rgba(56, 189, 248, 0.15);
142
- border: 1px solid rgba(56, 189, 248, 0.25);
143
- }
144
-
145
- .sidebar-badge.badge-experimental {
146
- color: #eab308; /* Yellow */
147
- background-color: rgba(234, 179, 8, 0.15);
148
- border: 1px solid rgba(234, 179, 8, 0.25);
149
- }
150
-
151
- .sidebar-badge.badge-updated {
152
- color: #a1a1aa; /* Grey / Zinc 400 */
153
- background-color: rgba(161, 161, 170, 0.15);
154
- border: 1px solid rgba(161, 161, 170, 0.25);
155
- }
156
-
157
- .sidebar-badge.badge-default {
158
- color: var(--ld-text-muted);
159
- background-color: var(--ld-bg-mute);
160
- border: 1px solid var(--ld-border-subtle);
161
- }
162
-
163
- .sidebar-link:hover {
164
- color: var(--ld-color-primary-hover);
165
- }
166
-
167
- .sidebar-link.active {
168
- color: var(--ld-color-primary);
169
- font-weight: 600;
170
- }
171
-
172
- /* ─── Sidebar Groups ─────────────────────────────────────── */
173
- .sidebar-group {
174
- margin-top: 1.25rem;
175
- }
176
-
177
- .sidebar-group:first-child {
178
- margin-top: 0;
179
- }
180
-
181
- .sidebar-group-header {
182
- display: flex;
183
- align-items: center;
184
- justify-content: space-between;
185
- width: 100%;
186
- padding: 0.35rem 0.75rem;
187
- background: none;
188
- border: none;
189
- border-radius: var(--ld-radius-md);
190
- color: var(--ld-text-dim);
191
- font-family: var(--ld-font-sans);
192
- font-size: 0.6875rem;
193
- font-weight: 700;
194
- text-transform: uppercase;
195
- letter-spacing: 0.08em;
196
- cursor: pointer;
197
- transition:
198
- color 0.2s,
199
- background-color 0.2s;
200
- }
201
-
202
- .sidebar-group-header:hover {
203
- color: var(--ld-text-muted);
204
- background-color: rgba(255, 255, 255, 0.03);
205
- }
206
-
207
- .sidebar-group-header.active {
208
- color: var(--ld-color-primary);
209
- }
210
-
211
- .sidebar-group-chevron {
212
- display: inline-flex;
213
- align-items: center;
214
- transition: transform 0.25s ease;
215
- font-size: 0.75rem;
216
- line-height: 1;
217
- }
218
-
219
- .sidebar-group-chevron.open {
220
- transform: rotate(90deg);
221
- }
222
-
223
- .sidebar-group-list {
224
- list-style: none;
225
- padding: 0;
226
- margin: 0.35rem 0 0;
227
- overflow: hidden;
228
- }
229
-
230
- .sidebar-group-list li {
231
- margin-bottom: 1px;
232
- }
233
-
234
- .sidebar-link-nested {
235
- padding-left: 1.25rem;
236
- }
1
+ /* ═══════════════════════════════════════════════════════════
2
+ SIDEBAR
3
+ ═══════════════════════════════════════════════════════════ */
4
+ .boltdocs-sidebar {
5
+ width: var(--ld-sidebar-width);
6
+ flex-shrink: 0;
7
+ background-color: var(--ld-sidebar-bg);
8
+ backdrop-filter: blur(var(--ld-sidebar-blur));
9
+ -webkit-backdrop-filter: blur(var(--ld-sidebar-blur));
10
+ border-right: 1px solid var(--ld-border-subtle);
11
+ padding: 1rem 0.6rem;
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;
18
+ display: flex;
19
+ 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;
24
+ }
25
+
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%;
42
+ display: flex;
43
+ 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
+ justify-content: center;
51
+ padding: 0 0 1rem;
52
+ }
53
+
54
+ .sidebar-collapse-btn {
55
+ 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);
67
+ }
68
+
69
+ .sidebar-collapse-btn:hover {
70
+ background-color: var(--ld-bg-mute);
71
+ color: var(--ld-text-main);
72
+ transform: scale(1.05);
73
+ }
74
+
75
+ .boltdocs-sidebar::-webkit-scrollbar {
76
+ width: 4px;
77
+ }
78
+
79
+ .boltdocs-sidebar::-webkit-scrollbar-track {
80
+ background: transparent;
81
+ }
82
+
83
+ .boltdocs-sidebar::-webkit-scrollbar-thumb {
84
+ background: var(--ld-bg-mute);
85
+ border-radius: 4px;
86
+ }
87
+
88
+ .sidebar-list {
89
+ list-style: none;
90
+ padding: 0;
91
+ margin: 0 0 0.5rem;
92
+ }
93
+
94
+ .sidebar-list li {
95
+ margin-bottom: 1px;
96
+ }
97
+
98
+ .sidebar-link {
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ padding: 0.45rem 0.75rem;
103
+ color: var(--ld-text-muted);
104
+ text-decoration: none;
105
+ border-radius: var(--ld-radius-md);
106
+ font-size: 0.875rem;
107
+ font-weight: 500;
108
+ transition: all 0.2s ease;
109
+ margin: 0.15rem 0;
110
+ }
111
+
112
+ .sidebar-link-content {
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: space-between;
116
+ width: 100%;
117
+ gap: 0.5rem;
118
+ }
119
+
120
+ .sidebar-link-content > span:first-child {
121
+ flex: 1;
122
+ word-wrap: break-word;
123
+ }
124
+
125
+ .sidebar-badge {
126
+ font-size: 0.45rem;
127
+ font-weight: 500;
128
+ padding: 0.1rem 0.3rem;
129
+ border-radius: 9999px;
130
+ text-transform: uppercase;
131
+ letter-spacing: 0.05em;
132
+ white-space: nowrap;
133
+ line-height: 1;
134
+ display: inline-flex;
135
+ align-items: center;
136
+ flex-shrink: 0;
137
+ }
138
+
139
+ .sidebar-badge.badge-new {
140
+ color: #38bdf8;
141
+ background-color: rgba(56, 189, 248, 0.15);
142
+ border: 1px solid rgba(56, 189, 248, 0.25);
143
+ }
144
+
145
+ .sidebar-badge.badge-experimental {
146
+ color: #eab308; /* Yellow */
147
+ background-color: rgba(234, 179, 8, 0.15);
148
+ border: 1px solid rgba(234, 179, 8, 0.25);
149
+ }
150
+
151
+ .sidebar-badge.badge-updated {
152
+ color: #a1a1aa; /* Grey / Zinc 400 */
153
+ background-color: rgba(161, 161, 170, 0.15);
154
+ border: 1px solid rgba(161, 161, 170, 0.25);
155
+ }
156
+
157
+ .sidebar-badge.badge-default {
158
+ color: var(--ld-text-muted);
159
+ background-color: var(--ld-bg-mute);
160
+ border: 1px solid var(--ld-border-subtle);
161
+ }
162
+
163
+ .sidebar-link:hover {
164
+ color: var(--ld-color-primary-hover);
165
+ }
166
+
167
+ .sidebar-link.active {
168
+ color: var(--ld-color-primary);
169
+ font-weight: 600;
170
+ }
171
+
172
+ /* ─── Sidebar Groups ─────────────────────────────────────── */
173
+ .sidebar-group {
174
+ margin-top: 1.25rem;
175
+ }
176
+
177
+ .sidebar-group:first-child {
178
+ margin-top: 0;
179
+ }
180
+
181
+ .sidebar-group-header {
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: space-between;
185
+ width: 100%;
186
+ padding: 0.35rem 0.75rem;
187
+ background: none;
188
+ border: none;
189
+ border-radius: var(--ld-radius-md);
190
+ color: var(--ld-text-dim);
191
+ font-family: var(--ld-font-sans);
192
+ font-size: 0.6875rem;
193
+ font-weight: 700;
194
+ text-transform: uppercase;
195
+ letter-spacing: 0.08em;
196
+ cursor: pointer;
197
+ transition:
198
+ color 0.2s,
199
+ background-color 0.2s;
200
+ }
201
+
202
+ .sidebar-group-header:hover {
203
+ color: var(--ld-text-muted);
204
+ background-color: rgba(255, 255, 255, 0.03);
205
+ }
206
+
207
+ .sidebar-group-header.active {
208
+ color: var(--ld-color-primary);
209
+ }
210
+
211
+ .sidebar-group-chevron {
212
+ display: inline-flex;
213
+ align-items: center;
214
+ transition: transform 0.25s ease;
215
+ font-size: 0.75rem;
216
+ line-height: 1;
217
+ }
218
+
219
+ .sidebar-group-chevron.open {
220
+ transform: rotate(90deg);
221
+ }
222
+
223
+ .sidebar-group-list {
224
+ list-style: none;
225
+ padding: 0;
226
+ margin: 0.35rem 0 0;
227
+ overflow: hidden;
228
+ }
229
+
230
+ .sidebar-group-list li {
231
+ margin-bottom: 1px;
232
+ }
233
+
234
+ .sidebar-link-nested {
235
+ padding-left: 1.25rem;
236
+ }
@@ -1,69 +1,69 @@
1
- import React, { useEffect, useState } from "react";
2
- import { Sun, Moon } from "lucide-react";
3
-
4
- export function ThemeToggle() {
5
- const [theme, setTheme] = useState("dark");
6
- const [mounted, setMounted] = useState(false);
7
-
8
- useEffect(() => {
9
- setMounted(true);
10
- // On mount, read from localStorage or matchMedia
11
- const stored = localStorage.getItem("boltdocs-theme");
12
- if (stored === "light" || stored === "dark") {
13
- setTheme(stored);
14
- } else {
15
- const prefersDark = window.matchMedia(
16
- "(prefers-color-scheme: dark)",
17
- ).matches;
18
- setTheme(prefersDark ? "dark" : "light");
19
- }
20
-
21
- // Listen to system changes if no localStorage is set
22
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
23
- const handleChange = (e: MediaQueryListEvent) => {
24
- if (!localStorage.getItem("boltdocs-theme")) {
25
- setTheme(e.matches ? "dark" : "light");
26
- }
27
- };
28
- mediaQuery.addEventListener("change", handleChange);
29
- return () => mediaQuery.removeEventListener("change", handleChange);
30
- }, []);
31
-
32
- useEffect(() => {
33
- if (!mounted) return;
34
- const root = document.documentElement;
35
- if (theme === "light") {
36
- root.classList.add("theme-light");
37
- root.dataset.theme = "light";
38
- } else {
39
- root.classList.remove("theme-light");
40
- root.dataset.theme = "dark";
41
- }
42
- }, [theme, mounted]);
43
-
44
- const toggleTheme = () => {
45
- const newTheme = theme === "dark" ? "light" : "dark";
46
- setTheme(newTheme);
47
- localStorage.setItem("boltdocs-theme", newTheme);
48
- };
49
-
50
- // Prevent hydration mismatch by rendering a placeholder with same layout
51
- if (!mounted) {
52
- return (
53
- <button className="navbar-icon-btn" aria-label="Toggle theme" disabled>
54
- <span style={{ width: 20, height: 20, display: "inline-block" }}></span>
55
- </button>
56
- );
57
- }
58
-
59
- return (
60
- <button
61
- className="navbar-icon-btn"
62
- onClick={toggleTheme}
63
- aria-label="Toggle theme"
64
- title={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}
65
- >
66
- {theme === "dark" ? <Sun size={20} /> : <Moon size={20} />}
67
- </button>
68
- );
69
- }
1
+ import React, { useEffect, useState } from "react";
2
+ import { Sun, Moon } from "lucide-react";
3
+
4
+ export function ThemeToggle() {
5
+ const [theme, setTheme] = useState("dark");
6
+ const [mounted, setMounted] = useState(false);
7
+
8
+ useEffect(() => {
9
+ setMounted(true);
10
+ // On mount, read from localStorage or matchMedia
11
+ const stored = localStorage.getItem("boltdocs-theme");
12
+ if (stored === "light" || stored === "dark") {
13
+ setTheme(stored);
14
+ } else {
15
+ const prefersDark = window.matchMedia(
16
+ "(prefers-color-scheme: dark)",
17
+ ).matches;
18
+ setTheme(prefersDark ? "dark" : "light");
19
+ }
20
+
21
+ // Listen to system changes if no localStorage is set
22
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
23
+ const handleChange = (e: MediaQueryListEvent) => {
24
+ if (!localStorage.getItem("boltdocs-theme")) {
25
+ setTheme(e.matches ? "dark" : "light");
26
+ }
27
+ };
28
+ mediaQuery.addEventListener("change", handleChange);
29
+ return () => mediaQuery.removeEventListener("change", handleChange);
30
+ }, []);
31
+
32
+ useEffect(() => {
33
+ if (!mounted) return;
34
+ const root = document.documentElement;
35
+ if (theme === "light") {
36
+ root.classList.add("theme-light");
37
+ root.dataset.theme = "light";
38
+ } else {
39
+ root.classList.remove("theme-light");
40
+ root.dataset.theme = "dark";
41
+ }
42
+ }, [theme, mounted]);
43
+
44
+ const toggleTheme = () => {
45
+ const newTheme = theme === "dark" ? "light" : "dark";
46
+ setTheme(newTheme);
47
+ localStorage.setItem("boltdocs-theme", newTheme);
48
+ };
49
+
50
+ // Prevent hydration mismatch by rendering a placeholder with same layout
51
+ if (!mounted) {
52
+ return (
53
+ <button className="navbar-icon-btn" aria-label="Toggle theme" disabled>
54
+ <span style={{ width: 20, height: 20, display: "inline-block" }}></span>
55
+ </button>
56
+ );
57
+ }
58
+
59
+ return (
60
+ <button
61
+ className="navbar-icon-btn"
62
+ onClick={toggleTheme}
63
+ aria-label="Toggle theme"
64
+ title={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}
65
+ >
66
+ {theme === "dark" ? <Sun size={20} /> : <Moon size={20} />}
67
+ </button>
68
+ );
69
+ }
@@ -1 +1 @@
1
- export { ThemeToggle } from "./ThemeToggle";
1
+ export { ThemeToggle } from "./ThemeToggle";