astro-tractstack 2.0.0-rc.8 → 2.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.
- package/LICENSE +8 -97
- package/README.md +7 -5
- package/bin/create-tractstack.js +35 -11
- package/dist/index.js +106 -29
- package/package.json +10 -5
- package/templates/css/frontend.css +1 -1
- package/templates/custom/minimal/CodeHook.astro +13 -12
- package/templates/custom/minimal/CustomRoutes.astro +25 -31
- package/templates/custom/with-examples/CodeHook.astro +22 -11
- package/templates/custom/with-examples/CustomRoutes.astro +4 -8
- package/templates/custom/with-examples/ProductCard.astro +29 -0
- package/templates/custom/with-examples/ProductCardWrapper.astro +43 -0
- package/templates/custom/with-examples/ProductGrid.astro +64 -0
- package/templates/custom/with-examples/pages/Collections.astro +58 -98
- package/templates/gitignore +42 -0
- package/templates/prettierignore +5 -0
- package/templates/prettierrc +19 -0
- package/templates/src/client/app.js +127 -0
- package/templates/src/client/htmx.min.js +3519 -0
- package/templates/src/client/view.js +429 -0
- package/templates/src/components/Footer.astro +4 -9
- package/templates/src/components/Header.astro +67 -60
- package/templates/src/components/Menu.tsx +188 -52
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +9 -13
- package/templates/src/components/codehooks/EpinetTableView.tsx +11 -7
- package/templates/src/components/codehooks/EpinetWrapper.tsx +1 -0
- package/templates/src/components/codehooks/FeaturedArticle.astro +105 -0
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +318 -0
- package/templates/src/components/codehooks/ListContent.astro +32 -162
- package/templates/src/components/codehooks/ListContentSetup.tsx +43 -138
- package/templates/src/components/codehooks/ProductCardSetup.tsx +152 -0
- package/templates/src/components/codehooks/ProductGridSetup.tsx +274 -0
- package/templates/src/components/codehooks/SearchWidget.tsx +453 -0
- package/templates/src/components/compositor/Node.tsx +3 -6
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +21 -11
- package/templates/src/components/compositor/elements/BunnyVideo.tsx +21 -20
- package/templates/src/components/compositor/nodes/Pane.tsx +51 -21
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +6 -1
- package/templates/src/components/compositor/nodes/Widget.tsx +16 -2
- package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +155 -0
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +20 -1
- package/templates/src/components/edit/Header.tsx +10 -4
- package/templates/src/components/edit/PanelSwitch.tsx +11 -7
- package/templates/src/components/edit/SettingsPanel.tsx +29 -18
- package/templates/src/components/edit/ToolBar.tsx +1 -28
- package/templates/src/components/edit/ToolMode.tsx +45 -32
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +12 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +8 -2
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +1 -1
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +17 -27
- package/templates/src/components/edit/pane/PageGenSelector.tsx +16 -16
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +26 -49
- package/templates/src/components/edit/pane/PageGen_preview.tsx +17 -2
- package/templates/src/components/edit/pane/PanePanel_path.tsx +2 -4
- package/templates/src/components/edit/pane/PanePanel_title.tsx +243 -76
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +17 -19
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +48 -37
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +60 -55
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +56 -50
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +54 -47
- package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +54 -44
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +113 -138
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +54 -40
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +3 -3
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +56 -49
- package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +14 -5
- package/templates/src/components/edit/state/SaveModal.tsx +316 -169
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +1 -1
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +56 -55
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +538 -59
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +656 -0
- package/templates/src/components/edit/widgets/ToggleWidget.tsx +9 -16
- package/templates/src/components/fields/ArtpackImage.tsx +4 -1
- package/templates/src/components/fields/BackgroundImage.tsx +1 -1
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +127 -35
- package/templates/src/components/fields/ColorPickerCombo.tsx +66 -62
- package/templates/src/components/fields/ImageUpload.tsx +1 -1
- package/templates/src/components/fields/ViewportComboBox.tsx +59 -42
- package/templates/src/components/form/ActionBuilderBeliefSelector.tsx +117 -0
- package/templates/src/components/form/ActionBuilderField.tsx +306 -87
- package/templates/src/components/search/SearchModal.tsx +420 -0
- package/templates/src/components/search/SearchResults.tsx +367 -0
- package/templates/src/components/search/SearchWrapper.tsx +46 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +34 -8
- package/templates/src/components/storykeep/Dashboard_Content.tsx +6 -0
- package/templates/src/components/storykeep/StoryKeepBackdrop.astro +87 -0
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +37 -33
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +55 -7
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +17 -2
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +5 -8
- package/templates/src/components/storykeep/state/FetchAnalytics.tsx +274 -228
- package/templates/src/components/storykeep/widgets/Wizard.tsx +14 -7
- package/templates/src/components/tenant/RegistrationForm.tsx +1 -1
- package/templates/src/components/widgets/ImpressionWrapper.tsx +0 -1
- package/templates/src/constants/shapes.ts +9 -0
- package/templates/src/constants.ts +2121 -16
- package/templates/src/hooks/useSearch.ts +228 -0
- package/templates/src/layouts/Layout.astro +213 -104
- package/templates/src/lib/storyData.ts +4 -1
- package/templates/src/pages/[...slug]/edit.astro +14 -14
- package/templates/src/pages/[...slug].astro +82 -21
- package/templates/src/pages/api/orphan-analysis.ts +0 -1
- package/templates/src/pages/api/tailwind.ts +23 -21
- package/templates/src/pages/context/[...contextSlug]/edit.astro +14 -14
- package/templates/src/pages/context/[...contextSlug].astro +7 -2
- package/templates/src/pages/storykeep/advanced.astro +5 -4
- package/templates/src/pages/storykeep/branding.astro +5 -4
- package/templates/src/pages/storykeep/content.astro +5 -4
- package/templates/src/pages/storykeep/init.astro +40 -1
- package/templates/src/pages/storykeep/login.astro +1 -1
- package/templates/src/pages/storykeep.astro +5 -4
- package/templates/src/stores/nodes.ts +59 -88
- package/templates/src/stores/orphanAnalysis.ts +19 -21
- package/templates/src/stores/storykeep.ts +7 -0
- package/templates/src/types/compositorTypes.ts +6 -0
- package/templates/src/types/tractstack.ts +17 -0
- package/templates/src/utils/actions/lispLexer.ts +2 -2
- package/templates/src/utils/actions/preParse_Action.ts +3 -0
- package/templates/src/utils/api/beliefHelpers.ts +12 -36
- package/templates/src/utils/api/menuHelpers.ts +2 -2
- package/templates/src/utils/api.ts +26 -0
- package/templates/src/utils/compositor/TemplateNodes.ts +7 -0
- package/templates/src/utils/compositor/allowInsert.ts +5 -3
- package/templates/src/utils/compositor/nodesHelper.ts +4 -0
- package/templates/src/utils/compositor/processMarkdown.ts +16 -2
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +4 -0
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +13 -13
- package/templates/src/utils/compositor/typeGuards.ts +1 -0
- package/templates/src/utils/customHelpers.ts +38 -0
- package/templates/src/utils/helpers.ts +2 -2
- package/templates/src/utils/layout.ts +65 -144
- package/utils/inject-files.ts +95 -18
- package/templates/src/client/analytics-events.js +0 -207
- package/templates/src/client/belief-events.js +0 -191
- package/templates/src/client/sse.js +0 -613
- package/templates/src/components/codehooks/FeaturedContent.astro +0 -273
- package/templates/src/components/codehooks/FeaturedContentSetup.tsx +0 -738
- package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +0 -128
- 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
|
+
}
|