@imjp/writenex-astro 0.1.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 (141) hide show
  1. package/README.md +539 -0
  2. package/dist/chunk-5PM6EQE5.js +151 -0
  3. package/dist/chunk-5PM6EQE5.js.map +1 -0
  4. package/dist/chunk-7XU5X6CW.js +1331 -0
  5. package/dist/chunk-7XU5X6CW.js.map +1 -0
  6. package/dist/chunk-AAOQHQPU.js +574 -0
  7. package/dist/chunk-AAOQHQPU.js.map +1 -0
  8. package/dist/chunk-CF2XXJFF.js +1410 -0
  9. package/dist/chunk-CF2XXJFF.js.map +1 -0
  10. package/dist/chunk-CRPZUUDU.js +52 -0
  11. package/dist/chunk-CRPZUUDU.js.map +1 -0
  12. package/dist/chunk-CYLDJ3HZ.js +310 -0
  13. package/dist/chunk-CYLDJ3HZ.js.map +1 -0
  14. package/dist/chunk-KIKIPIFA.js +1 -0
  15. package/dist/chunk-KIKIPIFA.js.map +1 -0
  16. package/dist/chunk-XNTQTTJU.js +145 -0
  17. package/dist/chunk-XNTQTTJU.js.map +1 -0
  18. package/dist/client/index.css +2 -0
  19. package/dist/client/index.css.map +1 -0
  20. package/dist/client/index.js +375 -0
  21. package/dist/client/index.js.map +1 -0
  22. package/dist/client/styles.css +584 -0
  23. package/dist/client/variables.css +304 -0
  24. package/dist/config/index.d.ts +54 -0
  25. package/dist/config/index.js +38 -0
  26. package/dist/config/index.js.map +1 -0
  27. package/dist/config-BmEdBDo_.d.ts +220 -0
  28. package/dist/content-BWR52vD-.d.ts +64 -0
  29. package/dist/discovery/index.d.ts +310 -0
  30. package/dist/discovery/index.js +38 -0
  31. package/dist/discovery/index.js.map +1 -0
  32. package/dist/errors-C0iYiDTv.d.ts +107 -0
  33. package/dist/filesystem/index.d.ts +1292 -0
  34. package/dist/filesystem/index.js +203 -0
  35. package/dist/filesystem/index.js.map +1 -0
  36. package/dist/image-FP7w5ZIs.d.ts +47 -0
  37. package/dist/index.d.ts +64 -0
  38. package/dist/index.js +151 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/loader-55LWCXHA.js +12 -0
  41. package/dist/loader-55LWCXHA.js.map +1 -0
  42. package/dist/loader-CrdnaAWR.d.ts +327 -0
  43. package/dist/server/index.d.ts +357 -0
  44. package/dist/server/index.js +37 -0
  45. package/dist/server/index.js.map +1 -0
  46. package/package.json +94 -0
  47. package/src/client/App.tsx +900 -0
  48. package/src/client/components/ConfigPanel/ConfigPanel.css +553 -0
  49. package/src/client/components/ConfigPanel/ConfigPanel.tsx +396 -0
  50. package/src/client/components/ConfigPanel/index.ts +6 -0
  51. package/src/client/components/CreateContentModal/CreateContentModal.css +327 -0
  52. package/src/client/components/CreateContentModal/CreateContentModal.tsx +216 -0
  53. package/src/client/components/CreateContentModal/index.ts +7 -0
  54. package/src/client/components/Editor/Editor.css +885 -0
  55. package/src/client/components/Editor/Editor.tsx +484 -0
  56. package/src/client/components/Editor/ImageDialog.css +344 -0
  57. package/src/client/components/Editor/ImageDialog.tsx +367 -0
  58. package/src/client/components/Editor/LinkDialog.css +326 -0
  59. package/src/client/components/Editor/LinkDialog.tsx +332 -0
  60. package/src/client/components/Editor/index.ts +6 -0
  61. package/src/client/components/FrontmatterForm/FrontmatterForm.css +468 -0
  62. package/src/client/components/FrontmatterForm/FrontmatterForm.tsx +914 -0
  63. package/src/client/components/FrontmatterForm/index.ts +7 -0
  64. package/src/client/components/Header/Header.css +300 -0
  65. package/src/client/components/Header/Header.tsx +300 -0
  66. package/src/client/components/Header/index.ts +7 -0
  67. package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.css +239 -0
  68. package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.tsx +151 -0
  69. package/src/client/components/KeyboardShortcuts/index.ts +6 -0
  70. package/src/client/components/LazyEditor.tsx +75 -0
  71. package/src/client/components/LiveRegion/LiveRegion.css +19 -0
  72. package/src/client/components/LiveRegion/LiveRegion.tsx +60 -0
  73. package/src/client/components/LiveRegion/index.ts +7 -0
  74. package/src/client/components/SearchReplace/SearchReplacePanel.css +300 -0
  75. package/src/client/components/SearchReplace/SearchReplacePanel.tsx +332 -0
  76. package/src/client/components/SearchReplace/index.ts +7 -0
  77. package/src/client/components/SelectCollectionModal/SelectCollectionModal.css +308 -0
  78. package/src/client/components/SelectCollectionModal/SelectCollectionModal.tsx +223 -0
  79. package/src/client/components/SelectCollectionModal/index.ts +7 -0
  80. package/src/client/components/Sidebar/Sidebar.css +570 -0
  81. package/src/client/components/Sidebar/Sidebar.tsx +617 -0
  82. package/src/client/components/Sidebar/index.ts +7 -0
  83. package/src/client/components/SkipLink/SkipLink.css +51 -0
  84. package/src/client/components/SkipLink/SkipLink.tsx +67 -0
  85. package/src/client/components/SkipLink/index.ts +7 -0
  86. package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.css +233 -0
  87. package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.tsx +160 -0
  88. package/src/client/components/UnsavedChangesModal/index.ts +1 -0
  89. package/src/client/components/VersionHistory/DiffViewer.css +430 -0
  90. package/src/client/components/VersionHistory/DiffViewer.tsx +383 -0
  91. package/src/client/components/VersionHistory/VersionActions.css +318 -0
  92. package/src/client/components/VersionHistory/VersionActions.tsx +277 -0
  93. package/src/client/components/VersionHistory/VersionHistoryPanel.css +369 -0
  94. package/src/client/components/VersionHistory/VersionHistoryPanel.tsx +469 -0
  95. package/src/client/components/VersionHistory/index.ts +9 -0
  96. package/src/client/context/ApiContext.tsx +154 -0
  97. package/src/client/context/ThemeContext.tsx +172 -0
  98. package/src/client/hooks/useAnnounce.ts +201 -0
  99. package/src/client/hooks/useApi.ts +374 -0
  100. package/src/client/hooks/useArrowNavigation.ts +286 -0
  101. package/src/client/hooks/useAutosave.ts +241 -0
  102. package/src/client/hooks/useFocusTrap.ts +178 -0
  103. package/src/client/hooks/useKeyboardShortcuts.ts +203 -0
  104. package/src/client/hooks/useSearch.ts +206 -0
  105. package/src/client/hooks/useVersionHistory.ts +451 -0
  106. package/src/client/index.tsx +70 -0
  107. package/src/client/styles.css +584 -0
  108. package/src/client/utils/focus.ts +57 -0
  109. package/src/client/utils/openInEditor.ts +130 -0
  110. package/src/client/variables.css +304 -0
  111. package/src/config/defaults.ts +109 -0
  112. package/src/config/index.ts +32 -0
  113. package/src/config/loader.ts +174 -0
  114. package/src/config/schema.ts +161 -0
  115. package/src/core/constants.ts +39 -0
  116. package/src/core/errors.ts +739 -0
  117. package/src/core/index.ts +11 -0
  118. package/src/discovery/collections.ts +216 -0
  119. package/src/discovery/index.ts +33 -0
  120. package/src/discovery/patterns.ts +702 -0
  121. package/src/discovery/schema.ts +453 -0
  122. package/src/filesystem/images.ts +798 -0
  123. package/src/filesystem/index.ts +107 -0
  124. package/src/filesystem/reader.ts +452 -0
  125. package/src/filesystem/version-config.ts +390 -0
  126. package/src/filesystem/versions.ts +1339 -0
  127. package/src/filesystem/watcher.ts +226 -0
  128. package/src/filesystem/writer.ts +540 -0
  129. package/src/index.ts +61 -0
  130. package/src/integration.ts +228 -0
  131. package/src/server/assets.ts +254 -0
  132. package/src/server/cache.ts +355 -0
  133. package/src/server/index.ts +33 -0
  134. package/src/server/middleware.ts +209 -0
  135. package/src/server/routes.ts +1428 -0
  136. package/src/types/api.ts +61 -0
  137. package/src/types/config.ts +134 -0
  138. package/src/types/content.ts +64 -0
  139. package/src/types/image.ts +48 -0
  140. package/src/types/index.ts +58 -0
  141. package/src/types/version.ts +117 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview FrontmatterForm component exports
3
+ *
4
+ * @module @writenex/astro/client/components/FrontmatterForm
5
+ */
6
+
7
+ export { FrontmatterForm } from "./FrontmatterForm";
@@ -0,0 +1,300 @@
1
+ .wn-header {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ padding: var(--wn-space-4) var(--wn-space-5);
6
+ border-bottom: 1px solid var(--wn-overlay-10);
7
+ background-color: var(--wn-zinc-900);
8
+ min-height: 72px;
9
+ }
10
+
11
+ .wn-header-left {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: var(--wn-space-4);
15
+ }
16
+
17
+ .wn-header-brand {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: var(--wn-space-3);
21
+ }
22
+
23
+ .wn-header-title {
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: var(--wn-space-1);
27
+ }
28
+
29
+ .wn-header-title-row {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: var(--wn-space-3);
33
+ }
34
+
35
+ .wn-header-tagline {
36
+ font-size: var(--wn-font-base);
37
+ color: rgba(255, 255, 255, 0.5);
38
+ }
39
+
40
+ .wn-header-logo-text {
41
+ font-size: var(--wn-font-lg);
42
+ font-weight: 700;
43
+ color: var(--wn-zinc-100);
44
+ }
45
+
46
+ .wn-header-badge {
47
+ font-size: var(--wn-font-xs);
48
+ font-weight: 500;
49
+ text-transform: uppercase;
50
+ letter-spacing: 0.05em;
51
+ padding: var(--wn-space-1) var(--wn-space-3);
52
+ border-radius: var(--wn-radius-sm);
53
+ background-color: var(--wn-overlay-10);
54
+ color: rgba(255, 255, 255, 0.5);
55
+ }
56
+
57
+ .wn-toolbar {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: var(--wn-space-3);
61
+ padding: var(--wn-space-1) var(--wn-space-3);
62
+ border-radius: var(--wn-radius-lg);
63
+ border: 1px solid var(--wn-overlay-10);
64
+ background-color: var(--wn-overlay-5);
65
+ }
66
+
67
+ .wn-toolbar-separator {
68
+ width: 1px;
69
+ height: 24px;
70
+ background-color: var(--wn-overlay-10);
71
+ margin: 0 var(--wn-space-2);
72
+ }
73
+
74
+ .wn-toolbar-btn {
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ width: var(--wn-icon-btn-lg);
79
+ height: var(--wn-icon-btn-lg);
80
+ border-radius: var(--wn-radius-md);
81
+ border: none;
82
+ background: transparent;
83
+ color: rgba(255, 255, 255, 0.6);
84
+ cursor: pointer;
85
+ transition: all 0.15s ease;
86
+ }
87
+
88
+ .wn-toolbar-btn:hover {
89
+ background: var(--wn-overlay-10);
90
+ color: #fff;
91
+ }
92
+
93
+ .wn-toolbar-btn--active {
94
+ background: var(--wn-overlay-10);
95
+ color: #fff;
96
+ }
97
+
98
+ .wn-toolbar-btn--disabled {
99
+ opacity: 0.4;
100
+ cursor: not-allowed;
101
+ }
102
+
103
+ .wn-toolbar-btn--disabled:hover {
104
+ background: transparent;
105
+ color: rgba(255, 255, 255, 0.6);
106
+ }
107
+
108
+ /* ============================================================================
109
+ CSS TOOLTIPS FOR TOOLBAR
110
+ ============================================================================ */
111
+
112
+ .wn-toolbar-btn {
113
+ position: relative;
114
+ }
115
+
116
+ .wn-toolbar-btn::after {
117
+ content: attr(aria-label);
118
+ position: absolute;
119
+ top: calc(100% + var(--wn-space-3));
120
+ left: 50%;
121
+ transform: translateX(-50%);
122
+ padding: var(--wn-space-2) var(--wn-space-3);
123
+ background-color: var(--wn-zinc-800);
124
+ border: 1px solid var(--wn-overlay-10);
125
+ border-radius: var(--wn-radius-md);
126
+ font-size: var(--wn-font-xs);
127
+ font-weight: 500;
128
+ white-space: nowrap;
129
+ color: var(--wn-zinc-50);
130
+ opacity: 0;
131
+ visibility: hidden;
132
+ pointer-events: none;
133
+ transition:
134
+ opacity var(--wn-transition-fast) ease,
135
+ visibility var(--wn-transition-fast) ease;
136
+ z-index: var(--wn-z-dialog);
137
+ box-shadow: var(--wn-shadow-md);
138
+ }
139
+
140
+ /* Tooltip arrow */
141
+ .wn-toolbar-btn::before {
142
+ content: "";
143
+ position: absolute;
144
+ top: calc(100% + 2px);
145
+ left: 50%;
146
+ transform: translateX(-50%);
147
+ border: 6px solid transparent;
148
+ border-bottom-color: var(--wn-zinc-800);
149
+ opacity: 0;
150
+ visibility: hidden;
151
+ pointer-events: none;
152
+ transition:
153
+ opacity var(--wn-transition-fast) ease,
154
+ visibility var(--wn-transition-fast) ease;
155
+ z-index: var(--wn-z-tooltip);
156
+ }
157
+
158
+ .wn-toolbar-btn:hover::after,
159
+ .wn-toolbar-btn:hover::before {
160
+ opacity: 1;
161
+ visibility: visible;
162
+ }
163
+
164
+ /* Hide tooltip for disabled buttons */
165
+ .wn-toolbar-btn--disabled::after,
166
+ .wn-toolbar-btn--disabled::before {
167
+ display: none;
168
+ }
169
+
170
+ .wn-placeholder-icon {
171
+ font-size: var(--wn-font-base);
172
+ font-weight: 600;
173
+ font-family: monospace;
174
+ }
175
+
176
+ /* ============================================================================
177
+ THEME SWITCHER
178
+ ============================================================================ */
179
+
180
+ .wn-theme-switcher {
181
+ position: relative;
182
+ }
183
+
184
+ .wn-theme-dropdown {
185
+ position: absolute;
186
+ top: calc(100% + var(--wn-space-3));
187
+ right: 0;
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: var(--wn-space-1);
191
+ min-width: 120px;
192
+ padding: var(--wn-space-2);
193
+ border-radius: var(--wn-radius-lg);
194
+ border: 1px solid var(--wn-overlay-10);
195
+ background-color: var(--wn-zinc-800);
196
+ box-shadow: var(--wn-shadow-md);
197
+ z-index: var(--wn-z-dropdown);
198
+ }
199
+
200
+ .wn-theme-option {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: var(--wn-space-3);
204
+ width: 100%;
205
+ padding: var(--wn-space-3) var(--wn-space-4);
206
+ border: none;
207
+ border-radius: var(--wn-radius-md);
208
+ background: transparent;
209
+ color: rgba(255, 255, 255, 0.7);
210
+ font-size: var(--wn-font-sm);
211
+ cursor: pointer;
212
+ transition: all 0.15s ease;
213
+ }
214
+
215
+ .wn-theme-option:hover {
216
+ background: var(--wn-overlay-10);
217
+ color: #fff;
218
+ }
219
+
220
+ .wn-theme-option--selected {
221
+ background: var(--wn-overlay-10);
222
+ color: #fff;
223
+ }
224
+
225
+ /* ============================================================================
226
+ LIGHT MODE OVERRIDES
227
+ ============================================================================ */
228
+
229
+ .wn-light .wn-header {
230
+ border-bottom-color: var(--wn-overlay-light-10);
231
+ background-color: #fff;
232
+ }
233
+
234
+ .wn-light .wn-header-tagline {
235
+ color: rgba(0, 0, 0, 0.5);
236
+ }
237
+
238
+ .wn-light .wn-header-logo-text {
239
+ color: var(--wn-zinc-900);
240
+ }
241
+
242
+ .wn-light .wn-header-badge {
243
+ background-color: var(--wn-overlay-light-5);
244
+ color: rgba(0, 0, 0, 0.5);
245
+ }
246
+
247
+ .wn-light .wn-toolbar {
248
+ border-color: var(--wn-overlay-light-10);
249
+ background-color: var(--wn-overlay-light-3);
250
+ }
251
+
252
+ .wn-light .wn-toolbar-separator {
253
+ background-color: var(--wn-overlay-light-10);
254
+ }
255
+
256
+ .wn-light .wn-toolbar-btn {
257
+ color: rgba(0, 0, 0, 0.6);
258
+ }
259
+
260
+ .wn-light .wn-toolbar-btn:hover {
261
+ background: var(--wn-overlay-light-10);
262
+ color: var(--wn-zinc-900);
263
+ }
264
+
265
+ .wn-light .wn-toolbar-btn--active {
266
+ background: var(--wn-overlay-light-10);
267
+ color: var(--wn-zinc-900);
268
+ }
269
+
270
+ /* Light mode tooltip */
271
+ .wn-light .wn-toolbar-btn::after {
272
+ background-color: #fff;
273
+ border-color: var(--wn-overlay-light-10);
274
+ color: var(--wn-zinc-900);
275
+ box-shadow: var(--wn-shadow-md);
276
+ }
277
+
278
+ .wn-light .wn-toolbar-btn::before {
279
+ border-bottom-color: #fff;
280
+ }
281
+
282
+ .wn-light .wn-theme-dropdown {
283
+ border-color: var(--wn-overlay-light-10);
284
+ background-color: #fff;
285
+ box-shadow: var(--wn-shadow-md);
286
+ }
287
+
288
+ .wn-light .wn-theme-option {
289
+ color: rgba(0, 0, 0, 0.7);
290
+ }
291
+
292
+ .wn-light .wn-theme-option:hover {
293
+ background: var(--wn-overlay-light-5);
294
+ color: var(--wn-zinc-900);
295
+ }
296
+
297
+ .wn-light .wn-theme-option--selected {
298
+ background: var(--wn-overlay-light-5);
299
+ color: var(--wn-zinc-900);
300
+ }
@@ -0,0 +1,300 @@
1
+ /**
2
+ * @fileoverview Application Header Component for Writenex Astro
3
+ *
4
+ * This component provides the main header bar with logo, branding, and
5
+ * a unified toolbar for editor operations.
6
+ *
7
+ * @module @writenex/astro/client/components/Header
8
+ */
9
+
10
+ import { useState, useRef, useEffect } from "react";
11
+ import {
12
+ Keyboard,
13
+ Settings,
14
+ Folder,
15
+ Info,
16
+ Search,
17
+ Sun,
18
+ Moon,
19
+ Monitor,
20
+ History,
21
+ Plus,
22
+ } from "lucide-react";
23
+ import { useTheme, type Theme } from "../../context/ThemeContext";
24
+ import "./Header.css";
25
+
26
+ /**
27
+ * Writenex Logo SVG component
28
+ */
29
+ function LogoIcon(): React.ReactElement {
30
+ return (
31
+ <svg
32
+ width="56"
33
+ height="56"
34
+ viewBox="0 0 24 24"
35
+ fill="#335DFF"
36
+ xmlns="http://www.w3.org/2000/svg"
37
+ aria-hidden="true"
38
+ >
39
+ <path d="M20.18 4.22l3.7 0c0.05,0 0.08,0.02 0.1,0.06 0.03,0.03 0.03,0.08 0,0.12l-5.78 10.31c-0.02,0.04 -0.06,0.06 -0.1,0.06 -0.04,0 -0.08,-0.02 -0.1,-0.06l-1.89 -3.28c-0.03,-0.04 -0.03,-0.08 -0.01,-0.12l3.98 -7.03c0.02,-0.04 0.06,-0.06 0.1,-0.06zm-6.13 6.34l3.24 5.65c0.03,0.04 0.03,0.09 0,0.12l-1.9 3.39c-0.02,0.04 -0.05,0.06 -0.1,0.06 -0.04,0 -0.08,-0.02 -0.1,-0.06l-3.17 -5.68 -3.12 5.68c-0.02,0.04 -0.06,0.06 -0.1,0.06 -0.04,0 -0.08,-0.02 -0.1,-0.06l-1.92 -3.38c-0.03,-0.04 -0.03,-0.09 0,-0.13l3.26 -5.66 -3.48 -6.15c-0.02,-0.04 -0.02,-0.09 0,-0.12 0.02,-0.04 0.06,-0.06 0.1,-0.06l3.74 0c0.05,0 0.08,0.02 0.11,0.06l1.51 2.7 1.53 -2.7c0.02,-0.04 0.06,-0.06 0.1,-0.06l3.84 0c0.04,0 0.08,0.02 0.1,0.06 0.02,0.03 0.02,0.08 0,0.12l-3.54 6.16zm-10.06 -6.28l3.99 7.01c0.02,0.04 0.02,0.08 0,0.12l-1.91 3.31c-0.03,0.04 -0.06,0.06 -0.11,0.06 -0.04,0 -0.08,-0.02 -0.1,-0.06l-5.84 -10.32c-0.03,-0.04 -0.03,-0.09 0,-0.12 0.02,-0.04 0.05,-0.06 0.1,-0.06l3.76 0c0.05,0 0.09,0.02 0.11,0.06z" />
40
+ </svg>
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Props for the Header component
46
+ */
47
+ interface HeaderProps {
48
+ /** Whether the sidebar is open */
49
+ isSidebarOpen?: boolean;
50
+ /** Callback to toggle sidebar */
51
+ onToggleSidebar?: () => void;
52
+ /** Whether the frontmatter panel is open */
53
+ isFrontmatterOpen?: boolean;
54
+ /** Callback to toggle frontmatter panel */
55
+ onToggleFrontmatter?: () => void;
56
+ /** Whether the search panel is open */
57
+ isSearchOpen?: boolean;
58
+ /** Callback to toggle search panel */
59
+ onToggleSearch?: () => void;
60
+ /** Whether the version history panel is open */
61
+ isVersionHistoryOpen?: boolean;
62
+ /** Callback to toggle version history panel */
63
+ onToggleVersionHistory?: () => void;
64
+ /** Whether version history is available (content selected) */
65
+ versionHistoryEnabled?: boolean;
66
+ /** Callback when keyboard shortcuts button is clicked */
67
+ onKeyboardShortcuts?: () => void;
68
+ /** Callback when settings button is clicked */
69
+ onSettings?: () => void;
70
+ /** Callback when new content button is clicked */
71
+ onNewContent?: () => void;
72
+ }
73
+
74
+ /**
75
+ * Visual separator for grouping toolbar buttons.
76
+ */
77
+ function ToolbarSeparator(): React.ReactElement {
78
+ return <div className="wn-toolbar-separator" />;
79
+ }
80
+
81
+ /**
82
+ * Toolbar button component with icon
83
+ */
84
+ function ToolbarButton({
85
+ icon,
86
+ label,
87
+ onClick,
88
+ active = false,
89
+ disabled = false,
90
+ }: {
91
+ icon: React.ReactNode;
92
+ label: string;
93
+ onClick?: () => void;
94
+ active?: boolean;
95
+ disabled?: boolean;
96
+ }): React.ReactElement {
97
+ const className = [
98
+ "wn-toolbar-btn",
99
+ active ? "wn-toolbar-btn--active" : "",
100
+ disabled ? "wn-toolbar-btn--disabled" : "",
101
+ ]
102
+ .filter(Boolean)
103
+ .join(" ");
104
+
105
+ return (
106
+ <button
107
+ type="button"
108
+ onClick={onClick}
109
+ disabled={disabled}
110
+ aria-label={label}
111
+ className={className}
112
+ >
113
+ {icon}
114
+ </button>
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Theme option configuration
120
+ */
121
+ const THEME_OPTIONS: { value: Theme; label: string; icon: typeof Sun }[] = [
122
+ { value: "light", label: "Light", icon: Sun },
123
+ { value: "dark", label: "Dark", icon: Moon },
124
+ { value: "system", label: "System", icon: Monitor },
125
+ ];
126
+
127
+ /**
128
+ * Theme switcher dropdown component
129
+ */
130
+ function ThemeSwitcher(): React.ReactElement {
131
+ const { theme, resolvedTheme, setTheme } = useTheme();
132
+ const [isOpen, setIsOpen] = useState(false);
133
+ const dropdownRef = useRef<HTMLDivElement>(null);
134
+
135
+ // Close dropdown when clicking outside
136
+ useEffect(() => {
137
+ const handleClickOutside = (event: MouseEvent) => {
138
+ if (
139
+ dropdownRef.current &&
140
+ !dropdownRef.current.contains(event.target as Node)
141
+ ) {
142
+ setIsOpen(false);
143
+ }
144
+ };
145
+
146
+ if (isOpen) {
147
+ document.addEventListener("mousedown", handleClickOutside);
148
+ return () =>
149
+ document.removeEventListener("mousedown", handleClickOutside);
150
+ }
151
+ }, [isOpen]);
152
+
153
+ // Close on escape key
154
+ useEffect(() => {
155
+ const handleEscape = (event: KeyboardEvent) => {
156
+ if (event.key === "Escape") setIsOpen(false);
157
+ };
158
+
159
+ if (isOpen) {
160
+ document.addEventListener("keydown", handleEscape);
161
+ return () => document.removeEventListener("keydown", handleEscape);
162
+ }
163
+ }, [isOpen]);
164
+
165
+ const ThemeIcon = resolvedTheme === "dark" ? Moon : Sun;
166
+
167
+ return (
168
+ <div className="wn-theme-switcher" ref={dropdownRef}>
169
+ <button
170
+ type="button"
171
+ className={`wn-toolbar-btn ${isOpen ? "wn-toolbar-btn--active" : ""}`}
172
+ onClick={() => setIsOpen(!isOpen)}
173
+ aria-label="Switch theme"
174
+ aria-expanded={isOpen}
175
+ aria-haspopup="listbox"
176
+ >
177
+ <ThemeIcon size={16} />
178
+ </button>
179
+
180
+ {isOpen && (
181
+ <div className="wn-theme-dropdown" role="listbox" aria-label="Theme">
182
+ {THEME_OPTIONS.map((option) => {
183
+ const Icon = option.icon;
184
+ const isSelected = theme === option.value;
185
+ return (
186
+ <button
187
+ key={option.value}
188
+ type="button"
189
+ role="option"
190
+ aria-selected={isSelected}
191
+ className={`wn-theme-option ${isSelected ? "wn-theme-option--selected" : ""}`}
192
+ onClick={() => {
193
+ setTheme(option.value);
194
+ setIsOpen(false);
195
+ }}
196
+ >
197
+ <Icon size={16} />
198
+ <span>{option.label}</span>
199
+ </button>
200
+ );
201
+ })}
202
+ </div>
203
+ )}
204
+ </div>
205
+ );
206
+ }
207
+
208
+ /**
209
+ * Main application header with logo and unified toolbar.
210
+ *
211
+ * @component
212
+ */
213
+ export function Header({
214
+ isSidebarOpen = true,
215
+ onToggleSidebar,
216
+ isFrontmatterOpen = true,
217
+ onToggleFrontmatter,
218
+ isSearchOpen = false,
219
+ onToggleSearch,
220
+ isVersionHistoryOpen = false,
221
+ onToggleVersionHistory,
222
+ versionHistoryEnabled = false,
223
+ onKeyboardShortcuts,
224
+ onSettings,
225
+ onNewContent,
226
+ }: HeaderProps): React.ReactElement {
227
+ return (
228
+ <header className="wn-header">
229
+ {/* Left side: Logo and branding */}
230
+ <div className="wn-header-left">
231
+ <div className="wn-header-brand">
232
+ <LogoIcon />
233
+ <div className="wn-header-title">
234
+ <div className="wn-header-title-row">
235
+ <span className="wn-header-logo-text">Writenex</span>
236
+ <span className="wn-header-badge">Astro</span>
237
+ </div>
238
+ <span className="wn-header-tagline">
239
+ Edit your Astro content visually.
240
+ </span>
241
+ </div>
242
+ </div>
243
+ </div>
244
+
245
+ {/* Right side: Toolbar */}
246
+ <div className="wn-toolbar">
247
+ {/* New Content Button */}
248
+ <ToolbarButton
249
+ icon={<Plus size={16} />}
250
+ label="New Content (Alt+N)"
251
+ onClick={onNewContent}
252
+ />
253
+
254
+ <ToolbarSeparator />
255
+
256
+ {/* Group 1: Panels & Actions */}
257
+ <ToolbarButton
258
+ icon={<Folder size={16} />}
259
+ label="Toggle Explorer"
260
+ onClick={onToggleSidebar}
261
+ active={isSidebarOpen}
262
+ />
263
+ <ToolbarButton
264
+ icon={<Info size={16} />}
265
+ label="Toggle Frontmatter"
266
+ onClick={onToggleFrontmatter}
267
+ active={isFrontmatterOpen}
268
+ />
269
+ <ToolbarButton
270
+ icon={<Search size={16} />}
271
+ label="Search & Replace (Ctrl+F)"
272
+ onClick={onToggleSearch}
273
+ active={isSearchOpen}
274
+ />
275
+ <ToolbarButton
276
+ icon={<History size={16} />}
277
+ label="Version History"
278
+ onClick={onToggleVersionHistory}
279
+ active={isVersionHistoryOpen}
280
+ disabled={!versionHistoryEnabled}
281
+ />
282
+
283
+ <ToolbarSeparator />
284
+
285
+ {/* Group 2: Preferences */}
286
+ <ThemeSwitcher />
287
+ <ToolbarButton
288
+ icon={<Keyboard size={16} />}
289
+ label="Keyboard Shortcuts (Ctrl+/)"
290
+ onClick={onKeyboardShortcuts}
291
+ />
292
+ <ToolbarButton
293
+ icon={<Settings size={16} />}
294
+ label="Settings"
295
+ onClick={onSettings}
296
+ />
297
+ </div>
298
+ </header>
299
+ );
300
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview Header component exports
3
+ *
4
+ * @module @writenex/astro/client/components/Header
5
+ */
6
+
7
+ export { Header } from "./Header";