astro-tractstack 2.0.0-rc.9 → 2.0.1

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 (142) hide show
  1. package/LICENSE +8 -97
  2. package/README.md +7 -5
  3. package/bin/create-tractstack.js +31 -8
  4. package/dist/index.js +106 -29
  5. package/package.json +10 -5
  6. package/templates/css/frontend.css +1 -1
  7. package/templates/custom/minimal/CodeHook.astro +13 -12
  8. package/templates/custom/minimal/CustomRoutes.astro +25 -31
  9. package/templates/custom/with-examples/CodeHook.astro +22 -11
  10. package/templates/custom/with-examples/CustomRoutes.astro +4 -8
  11. package/templates/custom/with-examples/ProductCard.astro +29 -0
  12. package/templates/custom/with-examples/ProductCardWrapper.astro +43 -0
  13. package/templates/custom/with-examples/ProductGrid.astro +64 -0
  14. package/templates/custom/with-examples/pages/Collections.astro +58 -98
  15. package/templates/gitignore +42 -0
  16. package/templates/prettierignore +5 -0
  17. package/templates/prettierrc +19 -0
  18. package/templates/src/client/app.js +127 -0
  19. package/templates/src/client/htmx.min.js +3519 -0
  20. package/templates/src/client/view.js +429 -0
  21. package/templates/src/components/Footer.astro +4 -9
  22. package/templates/src/components/Header.astro +67 -60
  23. package/templates/src/components/Menu.tsx +188 -52
  24. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
  25. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +9 -13
  26. package/templates/src/components/codehooks/EpinetTableView.tsx +11 -7
  27. package/templates/src/components/codehooks/EpinetWrapper.tsx +10 -9
  28. package/templates/src/components/codehooks/FeaturedArticle.astro +105 -0
  29. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +318 -0
  30. package/templates/src/components/codehooks/ListContent.astro +32 -162
  31. package/templates/src/components/codehooks/ListContentSetup.tsx +43 -138
  32. package/templates/src/components/codehooks/ProductCardSetup.tsx +152 -0
  33. package/templates/src/components/codehooks/ProductGridSetup.tsx +274 -0
  34. package/templates/src/components/codehooks/SearchWidget.tsx +453 -0
  35. package/templates/src/components/compositor/Node.tsx +3 -6
  36. package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +21 -11
  37. package/templates/src/components/compositor/elements/BunnyVideo.tsx +21 -20
  38. package/templates/src/components/compositor/nodes/Pane.tsx +51 -21
  39. package/templates/src/components/compositor/nodes/RenderChildren.tsx +6 -1
  40. package/templates/src/components/compositor/nodes/Widget.tsx +16 -2
  41. package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +155 -0
  42. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +20 -1
  43. package/templates/src/components/edit/Header.tsx +10 -4
  44. package/templates/src/components/edit/PanelSwitch.tsx +11 -7
  45. package/templates/src/components/edit/SettingsPanel.tsx +29 -18
  46. package/templates/src/components/edit/ToolBar.tsx +1 -28
  47. package/templates/src/components/edit/ToolMode.tsx +45 -32
  48. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +12 -2
  49. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +8 -2
  50. package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +1 -1
  51. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +17 -27
  52. package/templates/src/components/edit/pane/PageGenSelector.tsx +16 -16
  53. package/templates/src/components/edit/pane/PageGenSpecial.tsx +26 -49
  54. package/templates/src/components/edit/pane/PageGen_preview.tsx +17 -2
  55. package/templates/src/components/edit/pane/PanePanel_path.tsx +2 -4
  56. package/templates/src/components/edit/pane/PanePanel_title.tsx +243 -76
  57. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +17 -19
  58. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +48 -37
  59. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +60 -55
  60. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +56 -50
  61. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +54 -47
  62. package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +54 -44
  63. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +113 -138
  64. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +54 -40
  65. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +3 -3
  66. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +56 -49
  67. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +14 -5
  68. package/templates/src/components/edit/state/SaveModal.tsx +316 -169
  69. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +1 -1
  70. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +56 -55
  71. package/templates/src/components/edit/widgets/BunnyWidget.tsx +538 -59
  72. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +656 -0
  73. package/templates/src/components/edit/widgets/ToggleWidget.tsx +9 -16
  74. package/templates/src/components/fields/ArtpackImage.tsx +4 -1
  75. package/templates/src/components/fields/BackgroundImage.tsx +1 -1
  76. package/templates/src/components/fields/BackgroundImageWrapper.tsx +127 -35
  77. package/templates/src/components/fields/ColorPickerCombo.tsx +66 -62
  78. package/templates/src/components/fields/ImageUpload.tsx +1 -1
  79. package/templates/src/components/fields/ViewportComboBox.tsx +59 -42
  80. package/templates/src/components/form/ActionBuilderBeliefSelector.tsx +117 -0
  81. package/templates/src/components/form/ActionBuilderField.tsx +306 -87
  82. package/templates/src/components/search/SearchModal.tsx +420 -0
  83. package/templates/src/components/search/SearchResults.tsx +367 -0
  84. package/templates/src/components/search/SearchWrapper.tsx +46 -0
  85. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -1
  86. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +34 -8
  87. package/templates/src/components/storykeep/Dashboard_Branding.tsx +1 -1
  88. package/templates/src/components/storykeep/Dashboard_Content.tsx +6 -0
  89. package/templates/src/components/storykeep/StoryKeepBackdrop.astro +87 -0
  90. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +38 -34
  91. package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +1 -1
  92. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +56 -8
  93. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +18 -3
  94. package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +5 -8
  95. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +274 -228
  96. package/templates/src/components/storykeep/widgets/Wizard.tsx +14 -7
  97. package/templates/src/components/widgets/ImpressionWrapper.tsx +0 -1
  98. package/templates/src/constants/shapes.ts +9 -0
  99. package/templates/src/constants.ts +2121 -16
  100. package/templates/src/hooks/useSearch.ts +228 -0
  101. package/templates/src/layouts/Layout.astro +213 -104
  102. package/templates/src/lib/storyData.ts +4 -1
  103. package/templates/src/pages/[...slug]/edit.astro +14 -14
  104. package/templates/src/pages/[...slug].astro +82 -21
  105. package/templates/src/pages/api/orphan-analysis.ts +0 -1
  106. package/templates/src/pages/api/tailwind.ts +23 -21
  107. package/templates/src/pages/context/[...contextSlug]/edit.astro +14 -14
  108. package/templates/src/pages/context/[...contextSlug].astro +7 -2
  109. package/templates/src/pages/storykeep/advanced.astro +5 -4
  110. package/templates/src/pages/storykeep/branding.astro +5 -4
  111. package/templates/src/pages/storykeep/content.astro +5 -4
  112. package/templates/src/pages/storykeep/init.astro +40 -1
  113. package/templates/src/pages/storykeep/login.astro +1 -1
  114. package/templates/src/pages/storykeep.astro +5 -4
  115. package/templates/src/stores/nodes.ts +59 -88
  116. package/templates/src/stores/orphanAnalysis.ts +19 -21
  117. package/templates/src/stores/storykeep.ts +7 -0
  118. package/templates/src/types/compositorTypes.ts +6 -0
  119. package/templates/src/types/tractstack.ts +17 -0
  120. package/templates/src/utils/actions/lispLexer.ts +2 -2
  121. package/templates/src/utils/actions/preParse_Action.ts +3 -0
  122. package/templates/src/utils/api/beliefHelpers.ts +12 -36
  123. package/templates/src/utils/api/menuHelpers.ts +2 -2
  124. package/templates/src/utils/api.ts +26 -0
  125. package/templates/src/utils/compositor/TemplateNodes.ts +7 -0
  126. package/templates/src/utils/compositor/allowInsert.ts +5 -3
  127. package/templates/src/utils/compositor/nodesHelper.ts +4 -0
  128. package/templates/src/utils/compositor/processMarkdown.ts +16 -2
  129. package/templates/src/utils/compositor/reduceNodesClassNames.ts +4 -0
  130. package/templates/src/utils/compositor/templateMarkdownStyles.ts +13 -13
  131. package/templates/src/utils/compositor/typeGuards.ts +1 -0
  132. package/templates/src/utils/customHelpers.ts +38 -0
  133. package/templates/src/utils/helpers.ts +2 -2
  134. package/templates/src/utils/layout.ts +65 -144
  135. package/utils/inject-files.ts +95 -18
  136. package/templates/src/client/analytics-events.js +0 -207
  137. package/templates/src/client/belief-events.js +0 -191
  138. package/templates/src/client/sse.js +0 -613
  139. package/templates/src/components/codehooks/FeaturedContent.astro +0 -273
  140. package/templates/src/components/codehooks/FeaturedContentSetup.tsx +0 -738
  141. package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +0 -128
  142. package/templates/src/components/edit/pane/PanePanel_slug.tsx +0 -219
@@ -0,0 +1,420 @@
1
+ import {
2
+ useState,
3
+ useEffect,
4
+ useRef,
5
+ useMemo,
6
+ type ChangeEvent,
7
+ type KeyboardEvent,
8
+ } from 'react';
9
+ import { Dialog } from '@ark-ui/react/dialog';
10
+ import { Portal } from '@ark-ui/react/portal';
11
+ import { MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/24/outline';
12
+ import { useSearch } from '@/hooks/useSearch';
13
+ import SearchResults from './SearchResults';
14
+ import type {
15
+ FullContentMapItem,
16
+ DiscoverySuggestion,
17
+ } from '@/types/tractstack';
18
+
19
+ interface SearchModalProps {
20
+ isOpen: boolean;
21
+ onClose: () => void;
22
+ contentMap: FullContentMapItem[];
23
+ }
24
+
25
+ // 1. Define a new type for the selected suggestions to include their type
26
+ interface SelectedSuggestion {
27
+ term: string;
28
+ type: string;
29
+ }
30
+
31
+ export default function SearchModal({
32
+ isOpen,
33
+ onClose,
34
+ contentMap,
35
+ }: SearchModalProps) {
36
+ const [query, setQuery] = useState('');
37
+ // 2. Update state to use the new type instead of just string[]
38
+ const [selectedSuggestions, setSelectedSuggestions] = useState<
39
+ SelectedSuggestion[]
40
+ >([]);
41
+ const inputRef = useRef<HTMLInputElement>(null);
42
+ const {
43
+ suggestions,
44
+ isDiscovering,
45
+ discoverError,
46
+ searchResults,
47
+ isRetrieving,
48
+ retrieveError,
49
+ discoverTerms,
50
+ selectSuggestion,
51
+ selectExactMatch,
52
+ clearAll,
53
+ } = useSearch();
54
+
55
+ useEffect(() => {
56
+ if (isOpen && inputRef.current) {
57
+ inputRef.current.focus();
58
+ }
59
+ }, [isOpen]);
60
+
61
+ useEffect(() => {
62
+ if (!isOpen) {
63
+ setQuery('');
64
+ // 3. Update cleanup logic to use the new state
65
+ setSelectedSuggestions([]);
66
+ clearAll();
67
+ }
68
+ }, [isOpen, clearAll]);
69
+
70
+ useEffect(() => {
71
+ if (query.trim().length >= 3) {
72
+ discoverTerms(query);
73
+ }
74
+ }, [query, discoverTerms]);
75
+
76
+ const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
77
+ setQuery(e.target.value);
78
+ };
79
+
80
+ const handleClose = () => {
81
+ setQuery('');
82
+ // 4. Update cleanup logic to use the new state
83
+ setSelectedSuggestions([]);
84
+ clearAll();
85
+ onClose();
86
+ };
87
+
88
+ const handleKeyDown = (e: KeyboardEvent) => {
89
+ if (e.key === 'Escape') {
90
+ handleClose();
91
+ } else if (e.key === 'Enter' && query.trim()) {
92
+ if (query.trim().length < 3) return;
93
+
94
+ if (suggestions.length === 1) {
95
+ handleSuggestionSelect(suggestions[0]);
96
+ } else if (suggestions.length > 0) {
97
+ const exactMatch = suggestions.find(
98
+ (s) => s.term.toLowerCase() === query.trim().toLowerCase()
99
+ );
100
+ if (exactMatch) {
101
+ handleSuggestionSelect(exactMatch);
102
+ } else {
103
+ handleSuggestionSelect(suggestions[0]);
104
+ }
105
+ } else {
106
+ handleExactMatch(query.trim());
107
+ }
108
+ }
109
+ };
110
+
111
+ const handleSuggestionClick = (suggestion: DiscoverySuggestion) => {
112
+ handleSuggestionSelect(suggestion);
113
+ };
114
+
115
+ const handleSuggestionSelect = (suggestion: DiscoverySuggestion) => {
116
+ // 5. Update how suggestions are added to the state
117
+ // Check for duplicates before adding
118
+ if (!selectedSuggestions.some((s) => s.term === suggestion.term)) {
119
+ setSelectedSuggestions((prev) => [
120
+ ...prev,
121
+ { term: suggestion.term, type: suggestion.type },
122
+ ]);
123
+ }
124
+
125
+ setQuery('');
126
+ selectSuggestion(suggestion);
127
+ };
128
+
129
+ const handleExactMatch = (term: string) => {
130
+ // 6. Update exact match handling to add a default type
131
+ // From the legend, "Exact Match" uses the 'EXACT' style
132
+ if (!selectedSuggestions.some((s) => s.term === term)) {
133
+ setSelectedSuggestions((prev) => [...prev, { term, type: 'EXACT' }]);
134
+ }
135
+
136
+ setQuery('');
137
+ selectExactMatch(term);
138
+ };
139
+
140
+ const removeTerm = (indexToRemove: number) => {
141
+ // 7. Update remove logic to use the new state
142
+ setSelectedSuggestions((prev) =>
143
+ prev.filter((_, index) => index !== indexToRemove)
144
+ );
145
+ clearAll();
146
+ if (inputRef.current) {
147
+ inputRef.current.focus();
148
+ }
149
+ };
150
+
151
+ const getTypeColor = (type: string) => {
152
+ switch (type) {
153
+ case 'TOPIC':
154
+ return 'bg-purple-100 text-purple-800 border-purple-200';
155
+ case 'EXACT':
156
+ return 'bg-orange-100 text-orange-800 border-orange-200';
157
+ case 'TEXT':
158
+ return 'bg-green-100 text-green-800 border-green-200';
159
+ default:
160
+ return 'bg-gray-100 text-gray-800 border-gray-200';
161
+ }
162
+ };
163
+
164
+ // 8. (Optional but recommended) Create a helper for the 'X' button color
165
+ const getCloseButtonColor = (type: string) => {
166
+ switch (type) {
167
+ case 'TOPIC':
168
+ return 'text-purple-600 hover:text-purple-800';
169
+ case 'EXACT':
170
+ return 'text-orange-600 hover:text-orange-800';
171
+ case 'TEXT':
172
+ return 'text-green-600 hover:text-green-800';
173
+ default:
174
+ return 'text-gray-600 hover:text-gray-800';
175
+ }
176
+ };
177
+
178
+ // Determine the correct suggestion for autocompletion, prioritizing an exact match
179
+ // to align with the behavior of the 'Enter' key press.
180
+ const suggestionForDisplay = useMemo(() => {
181
+ if (query.length < 3 || suggestions.length === 0) {
182
+ return null;
183
+ }
184
+ const exactMatch = suggestions.find(
185
+ (s) => s.term.toLowerCase() === query.trim().toLowerCase()
186
+ );
187
+ return exactMatch || suggestions[0];
188
+ }, [suggestions, query]);
189
+
190
+ const bestCompletion = suggestionForDisplay ? suggestionForDisplay.term : '';
191
+
192
+ const showCompletion =
193
+ bestCompletion.toLowerCase().startsWith(query.toLowerCase()) &&
194
+ query.length >= 3 &&
195
+ bestCompletion.length > query.length;
196
+
197
+ let preservedCompletion = '';
198
+ if (showCompletion) {
199
+ const completionText = bestCompletion.slice(query.length);
200
+ preservedCompletion = completionText.startsWith(' ')
201
+ ? '\u00A0' + completionText.slice(1)
202
+ : completionText;
203
+ }
204
+
205
+ const showSuggestions =
206
+ suggestions.length > 0 && !searchResults && query.length >= 3;
207
+ const showResults = searchResults !== null;
208
+ const totalResults = searchResults
209
+ ? searchResults.storyFragmentResults.length +
210
+ searchResults.contextPaneResults.length +
211
+ searchResults.resourceResults.length
212
+ : 0;
213
+
214
+ return (
215
+ <Dialog.Root
216
+ open={isOpen}
217
+ onOpenChange={(details) => !details.open && handleClose()}
218
+ >
219
+ <Portal>
220
+ <Dialog.Backdrop className="fixed inset-0 z-50 bg-black bg-opacity-50 backdrop-blur-sm" />
221
+ <Dialog.Positioner className="fixed inset-0 z-50 mx-auto max-w-3xl p-2 pt-16 md:p-4">
222
+ <Dialog.Content
223
+ className="bg-mywhite mx-auto w-full overflow-hidden rounded-lg shadow-2xl"
224
+ style={{ height: '80vh' }}
225
+ >
226
+ <div className="relative w-full border-b border-gray-200 p-4">
227
+ {/* 9. Update the rendering of selected term pills */}
228
+ {selectedSuggestions.length > 0 && (
229
+ <div className="mb-3 flex flex-wrap gap-2">
230
+ {selectedSuggestions.map((suggestion, index) => (
231
+ <div
232
+ key={index}
233
+ // Use getTypeColor to dynamically set the class
234
+ className={`inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-bold ${getTypeColor(
235
+ suggestion.type
236
+ )}`}
237
+ >
238
+ <span>{suggestion.term}</span>
239
+ <button
240
+ onClick={() => removeTerm(index)}
241
+ // Use the new helper for the button color
242
+ className={`flex items-center justify-center rounded-full ${getCloseButtonColor(
243
+ suggestion.type
244
+ )}`}
245
+ aria-label={`Remove ${suggestion.term}`}
246
+ >
247
+ <XMarkIcon className="h-4 w-4" />
248
+ </button>
249
+ </div>
250
+ ))}
251
+ </div>
252
+ )}
253
+
254
+ {!showResults && (
255
+ <div className="relative w-full px-6 py-2">
256
+ {showCompletion && (
257
+ <div className="pointer-events-none absolute left-0 top-0 flex h-full w-full items-center px-6 py-2 text-xl text-gray-400">
258
+ <span style={{ visibility: 'hidden', whiteSpace: 'pre' }}>
259
+ {query}
260
+ </span>
261
+ {preservedCompletion}
262
+ </div>
263
+ )}
264
+ <input
265
+ ref={inputRef}
266
+ type="text"
267
+ value={query}
268
+ onChange={handleInputChange}
269
+ onKeyDown={handleKeyDown}
270
+ placeholder="Search content..."
271
+ className="text-mydarkgrey relative z-10 w-full border-none bg-transparent text-xl placeholder-gray-500 outline-none"
272
+ style={{ background: 'transparent', padding: '0' }}
273
+ />
274
+ </div>
275
+ )}
276
+
277
+ <button
278
+ onClick={handleClose}
279
+ className="text-mydarkgrey hover:text-myblue absolute right-4 top-6 rounded-lg p-2 transition-colors hover:bg-gray-100"
280
+ aria-label="Close search"
281
+ >
282
+ <XMarkIcon className="h-6 w-6" />
283
+ </button>
284
+ </div>
285
+
286
+ <div
287
+ className="w-full overflow-y-auto"
288
+ style={{ height: 'calc(80vh - 80px)' }}
289
+ >
290
+ {/* 10. Final cleanup logic update */}
291
+ {!query.trim() && selectedSuggestions.length === 0 && (
292
+ <div className="w-full p-8 text-center text-gray-500">
293
+ <MagnifyingGlassIcon className="mx-auto mb-4 h-16 w-16 text-gray-300" />
294
+ <p className="text-lg">Search across all content</p>
295
+ <p className="mt-2 text-sm">
296
+ Start typing to discover content suggestions
297
+ </p>
298
+ </div>
299
+ )}
300
+
301
+ {query.trim() && query.trim().length < 3 && (
302
+ <div className="w-full p-8 text-center text-gray-500">
303
+ <p className="text-lg">Keep typing...</p>
304
+ <p className="mt-2 text-sm">
305
+ Need at least 3 characters to search
306
+ </p>
307
+ </div>
308
+ )}
309
+
310
+ {query.trim().length >= 3 && isDiscovering && (
311
+ <div className="w-full p-8 text-center">
312
+ <div className="border-myblue inline-block h-8 w-8 animate-spin rounded-full border-b-2"></div>
313
+ <p className="text-mydarkgrey mt-4">Discovering...</p>
314
+ </div>
315
+ )}
316
+
317
+ {query.trim().length >= 3 && discoverError && (
318
+ <div className="w-full p-8 text-center text-red-600">
319
+ <p>Discovery failed: {discoverError}</p>
320
+ <button
321
+ onClick={() => discoverTerms(query.trim())}
322
+ className="text-myblue mt-2 hover:underline"
323
+ >
324
+ Try again
325
+ </button>
326
+ </div>
327
+ )}
328
+
329
+ {showSuggestions && (
330
+ <div className="w-full p-6">
331
+ <p className="text-mydarkgrey mb-4 text-sm font-bold">
332
+ Suggestions ({suggestions.length})
333
+ </p>
334
+ <div className="flex flex-wrap gap-2">
335
+ {suggestions.map((suggestion, index) => (
336
+ <button
337
+ key={index}
338
+ onClick={() => handleSuggestionClick(suggestion)}
339
+ className={`inline-flex items-center rounded-lg border px-3 py-1.5 text-sm font-bold transition-all hover:shadow-md ${getTypeColor(
340
+ suggestion.type
341
+ )}`}
342
+ >
343
+ <span>{suggestion.term}</span>
344
+ </button>
345
+ ))}
346
+ </div>
347
+ <p className="text-mydarkgrey mt-4 text-xs">
348
+ Click a suggestion or press Enter to search
349
+ </p>
350
+ <div className="mt-6 border-t border-gray-200 pt-4">
351
+ <div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-gray-600">
352
+ <span className="font-bold">Legend:</span>
353
+ <span
354
+ className={`inline-flex items-center rounded-lg border px-3 py-1.5 text-sm font-bold ${getTypeColor(
355
+ 'EXACT'
356
+ )}`}
357
+ >
358
+ Exact Match
359
+ </span>
360
+ <span
361
+ className={`inline-flex items-center rounded-lg border px-3 py-1.5 text-sm font-bold ${getTypeColor(
362
+ 'TOPIC'
363
+ )}`}
364
+ >
365
+ Topic
366
+ </span>
367
+ <span
368
+ className={`inline-flex items-center rounded-lg border px-3 py-1.5 text-sm font-bold ${getTypeColor(
369
+ 'TEXT'
370
+ )}`}
371
+ >
372
+ Text Match
373
+ </span>
374
+ </div>
375
+ </div>
376
+ </div>
377
+ )}
378
+
379
+ {isRetrieving && (
380
+ <div className="w-full p-8 text-center">
381
+ <div className="border-myblue inline-block h-8 w-8 animate-spin rounded-full border-b-2"></div>
382
+ <p className="text-mydarkgrey mt-4">Searching...</p>
383
+ </div>
384
+ )}
385
+
386
+ {retrieveError && (
387
+ <div className="w-full p-8 text-center text-red-600">
388
+ <p>Search failed: {retrieveError}</p>
389
+ </div>
390
+ )}
391
+
392
+ {!isRetrieving &&
393
+ !retrieveError &&
394
+ showResults &&
395
+ totalResults === 0 && (
396
+ <div className="w-full p-8 text-center text-gray-500">
397
+ <p className="text-lg">No results found</p>
398
+ <p className="mt-2 text-sm">
399
+ Try different keywords or check your spelling
400
+ </p>
401
+ </div>
402
+ )}
403
+
404
+ {!isRetrieving &&
405
+ !retrieveError &&
406
+ showResults &&
407
+ totalResults > 0 && (
408
+ <SearchResults
409
+ results={searchResults}
410
+ contentMap={contentMap}
411
+ getTypeColor={getTypeColor}
412
+ />
413
+ )}
414
+ </div>
415
+ </Dialog.Content>
416
+ </Dialog.Positioner>
417
+ </Portal>
418
+ </Dialog.Root>
419
+ );
420
+ }