@rubixstudios/payload-typesense 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +182 -0
  3. package/dist/components/HeadlessSearchInput.d.ts +86 -0
  4. package/dist/components/HeadlessSearchInput.d.ts.map +1 -0
  5. package/dist/components/HeadlessSearchInput.js +602 -0
  6. package/dist/components/ThemeProvider.d.ts +10 -0
  7. package/dist/components/ThemeProvider.d.ts.map +1 -0
  8. package/dist/components/ThemeProvider.js +17 -0
  9. package/dist/components/index.d.ts +6 -0
  10. package/dist/components/index.d.ts.map +1 -0
  11. package/dist/components/index.js +3 -0
  12. package/dist/components/themes/hooks.d.ts +52 -0
  13. package/dist/components/themes/hooks.d.ts.map +1 -0
  14. package/dist/components/themes/hooks.js +177 -0
  15. package/dist/components/themes/index.d.ts +5 -0
  16. package/dist/components/themes/index.d.ts.map +1 -0
  17. package/dist/components/themes/index.js +4 -0
  18. package/dist/components/themes/themes.d.ts +6 -0
  19. package/dist/components/themes/themes.d.ts.map +1 -0
  20. package/dist/components/themes/themes.js +156 -0
  21. package/dist/components/themes/types.d.ts +147 -0
  22. package/dist/components/themes/types.d.ts.map +1 -0
  23. package/dist/components/themes/types.js +1 -0
  24. package/dist/components/themes/utils.d.ts +30 -0
  25. package/dist/components/themes/utils.d.ts.map +1 -0
  26. package/dist/components/themes/utils.js +397 -0
  27. package/dist/endpoints/customEndpointHandler.d.ts +3 -0
  28. package/dist/endpoints/customEndpointHandler.d.ts.map +1 -0
  29. package/dist/endpoints/customEndpointHandler.js +5 -0
  30. package/dist/endpoints/health.d.ts +12 -0
  31. package/dist/endpoints/health.d.ts.map +1 -0
  32. package/dist/endpoints/health.js +174 -0
  33. package/dist/endpoints/search.d.ts +13 -0
  34. package/dist/endpoints/search.d.ts.map +1 -0
  35. package/dist/endpoints/search.js +375 -0
  36. package/dist/index.d.ts +39 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +148 -0
  39. package/dist/lib/cache.d.ts +41 -0
  40. package/dist/lib/cache.d.ts.map +1 -0
  41. package/dist/lib/cache.js +96 -0
  42. package/dist/lib/config-validation.d.ts +75 -0
  43. package/dist/lib/config-validation.d.ts.map +1 -0
  44. package/dist/lib/config-validation.js +174 -0
  45. package/dist/lib/hooks.d.ts +4 -0
  46. package/dist/lib/hooks.d.ts.map +1 -0
  47. package/dist/lib/hooks.js +54 -0
  48. package/dist/lib/initialization.d.ts +5 -0
  49. package/dist/lib/initialization.d.ts.map +1 -0
  50. package/dist/lib/initialization.js +102 -0
  51. package/dist/lib/schema-mapper.d.ts +14 -0
  52. package/dist/lib/schema-mapper.d.ts.map +1 -0
  53. package/dist/lib/schema-mapper.js +137 -0
  54. package/dist/lib/types.d.ts +183 -0
  55. package/dist/lib/types.d.ts.map +1 -0
  56. package/dist/lib/types.js +2 -0
  57. package/dist/lib/typesense-client.d.ts +5 -0
  58. package/dist/lib/typesense-client.d.ts.map +1 -0
  59. package/dist/lib/typesense-client.js +20 -0
  60. package/package.json +92 -0
@@ -0,0 +1,602 @@
1
+ 'use client';
2
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { useThemeConfig } from './themes/hooks.js';
4
+ const HeadlessSearchInput = ({ baseUrl, className = '', collection, collections, debounceMs = 300, enableSuggestions: _enableSuggestions = true, errorClassName = '', inputClassName = '', inputWrapperClassName = '', minQueryLength = 2, noResultsClassName = '', onResultClick, onResults, onSearch, perPage = 10, placeholder = 'Search...', renderError, renderInput, renderNoResults, renderResult, renderResultsHeader, resultItemClassName = '', resultsClassName = '', resultsContainerClassName = '', resultsHeaderClassName = '', resultsListClassName = '', showLoading = true, showResultCount = true, showSearchTime = true, theme = 'modern' })=>{
5
+ const [query, setQuery] = useState('');
6
+ const [results, setResults] = useState(null);
7
+ const [isLoading, setIsLoading] = useState(false);
8
+ const [isOpen, setIsOpen] = useState(false);
9
+ const [error, setError] = useState(null);
10
+ const inputRef = useRef(null);
11
+ const resultsRef = useRef(null);
12
+ const debounceRef = useRef(undefined);
13
+ const collectionsRef = useRef(collections);
14
+ // Theme configuration
15
+ const themeConfig = useThemeConfig({
16
+ theme: typeof theme === 'string' ? theme : theme.theme || 'modern',
17
+ ...typeof theme === 'object' ? theme : {}
18
+ });
19
+ // Note: If neither collection nor collections is provided, the component will search all collections
20
+ // Update collections ref when prop changes
21
+ useEffect(()=>{
22
+ collectionsRef.current = collections;
23
+ }, [
24
+ collections
25
+ ]);
26
+ // Debounced search function
27
+ // Use refs to avoid recreating functions on every render
28
+ const onResultsRef = useRef(onResults);
29
+ const onSearchRef = useRef(onSearch);
30
+ onResultsRef.current = onResults;
31
+ onSearchRef.current = onSearch;
32
+ const performSearch = useCallback(async (searchQuery)=>{
33
+ if (searchQuery.length < minQueryLength) {
34
+ setResults(null);
35
+ setIsLoading(false);
36
+ return;
37
+ }
38
+ setIsLoading(true);
39
+ setError(null);
40
+ try {
41
+ let searchUrl;
42
+ let searchResults;
43
+ if (collection) {
44
+ // Single collection search
45
+ searchUrl = `${baseUrl}/api/search/${collection}?q=${encodeURIComponent(searchQuery)}&per_page=${perPage}`;
46
+ } else if (collectionsRef.current && collectionsRef.current.length > 0) {
47
+ // Multiple collections specified - use universal search and filter client-side
48
+ searchUrl = `${baseUrl}/api/search?q=${encodeURIComponent(searchQuery)}&per_page=${perPage * 2}`;
49
+ } else {
50
+ // No collections specified - use universal search
51
+ searchUrl = `${baseUrl}/api/search?q=${encodeURIComponent(searchQuery)}&per_page=${perPage}`;
52
+ }
53
+ const response = await fetch(searchUrl);
54
+ if (!response.ok) {
55
+ throw new Error(`Search failed: ${response.status} ${response.statusText}`);
56
+ }
57
+ searchResults = await response.json();
58
+ // Filter results if specific collections were requested
59
+ if (collectionsRef.current && collectionsRef.current.length > 0) {
60
+ const filteredHits = searchResults.hits?.filter((hit)=>hit.collection && collectionsRef.current.includes(hit.collection)) || [];
61
+ const filteredCollections = searchResults.collections?.filter((col)=>col.collection && collectionsRef.current.includes(col.collection)) || [];
62
+ searchResults = {
63
+ ...searchResults,
64
+ collections: filteredCollections,
65
+ found: filteredHits.length,
66
+ hits: filteredHits
67
+ };
68
+ }
69
+ setResults(searchResults);
70
+ onResultsRef.current?.(searchResults);
71
+ onSearchRef.current?.(searchQuery, searchResults);
72
+ } catch (err) {
73
+ setError(err instanceof Error ? err.message : 'Search failed');
74
+ setResults(null);
75
+ } finally{
76
+ setIsLoading(false);
77
+ }
78
+ }, [
79
+ baseUrl,
80
+ collection,
81
+ perPage,
82
+ minQueryLength
83
+ ]);
84
+ // Debounced search effect
85
+ useEffect(()=>{
86
+ if (debounceRef.current) {
87
+ clearTimeout(debounceRef.current);
88
+ }
89
+ if (query.length >= minQueryLength) {
90
+ debounceRef.current = setTimeout(()=>{
91
+ void performSearch(query);
92
+ void onSearchRef.current?.(query, results || {
93
+ found: 0,
94
+ hits: [],
95
+ page: 1,
96
+ request_params: {
97
+ per_page: 10,
98
+ q: query
99
+ },
100
+ search_cutoff: false,
101
+ search_time_ms: 0
102
+ });
103
+ }, debounceMs);
104
+ } else {
105
+ setResults(null);
106
+ setIsLoading(false);
107
+ }
108
+ return ()=>{
109
+ if (debounceRef.current) {
110
+ clearTimeout(debounceRef.current);
111
+ }
112
+ };
113
+ // eslint-disable-next-line react-hooks/exhaustive-deps
114
+ }, [
115
+ query,
116
+ debounceMs,
117
+ minQueryLength,
118
+ performSearch
119
+ ]);
120
+ // Handle input change
121
+ const handleInputChange = (e)=>{
122
+ const value = e.target.value;
123
+ setQuery(value);
124
+ setIsOpen(value.length >= minQueryLength);
125
+ };
126
+ // Handle input focus
127
+ const handleInputFocus = ()=>{
128
+ if (query.length >= minQueryLength) {
129
+ setIsOpen(true);
130
+ }
131
+ };
132
+ // Handle input blur
133
+ const handleInputBlur = (_e)=>{
134
+ // Delay hiding results to allow clicking on them
135
+ setTimeout(()=>{
136
+ if (!resultsRef.current?.contains(document.activeElement)) {
137
+ setIsOpen(false);
138
+ }
139
+ }, 150);
140
+ };
141
+ // Handle result click
142
+ const handleResultClick = (result)=>{
143
+ onResultClick?.(result);
144
+ setIsOpen(false);
145
+ setQuery('');
146
+ };
147
+ // Handle keyboard navigation
148
+ const handleKeyDown = (e)=>{
149
+ if (!isOpen || !results) {
150
+ return;
151
+ }
152
+ const resultItems = resultsRef.current?.querySelectorAll('[data-result-item]');
153
+ if (!resultItems) {
154
+ return;
155
+ }
156
+ const currentIndex = Array.from(resultItems).findIndex((item)=>item === document.activeElement);
157
+ switch(e.key){
158
+ case 'ArrowDown':
159
+ {
160
+ e.preventDefault();
161
+ const nextIndex = currentIndex < resultItems.length - 1 ? currentIndex + 1 : 0;
162
+ resultItems[nextIndex]?.focus();
163
+ break;
164
+ }
165
+ case 'ArrowUp':
166
+ {
167
+ e.preventDefault();
168
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : resultItems.length - 1;
169
+ resultItems[prevIndex]?.focus();
170
+ break;
171
+ }
172
+ case 'Enter':
173
+ e.preventDefault();
174
+ if (currentIndex >= 0 && resultItems[currentIndex]) {
175
+ ;
176
+ resultItems[currentIndex]?.click();
177
+ }
178
+ break;
179
+ case 'Escape':
180
+ setIsOpen(false);
181
+ inputRef.current?.blur();
182
+ break;
183
+ }
184
+ };
185
+ // Default render functions
186
+ const defaultRenderResult = (result, _index)=>{
187
+ // Calculate relative percentage based on the highest score in current results
188
+ const maxScore = results?.hits?.reduce((max, hit)=>Math.max(max, hit.text_match || 0), 0) || 1;
189
+ const relativePercentage = Math.round((result.text_match || 0) / maxScore * 100);
190
+ return /*#__PURE__*/ React.createElement("div", {
191
+ className: `${resultsContainerClassName}`,
192
+ style: {
193
+ backgroundColor: themeConfig.theme.colors.resultBackground,
194
+ borderBottom: `1px solid ${themeConfig.theme.colors.resultBorder}`,
195
+ cursor: 'pointer',
196
+ padding: themeConfig.theme.spacing.itemPadding,
197
+ transition: themeConfig.config.enableAnimations !== false ? `all ${themeConfig.theme.animations.transitionFast} ${themeConfig.theme.animations.easeInOut}` : 'none'
198
+ }
199
+ }, /*#__PURE__*/ React.createElement("div", {
200
+ className: `${themeConfig.classes.resultItem} ${resultItemClassName}`,
201
+ "data-result-item": true,
202
+ key: result.document?.id || result.id || _index,
203
+ onBlur: (e)=>{
204
+ e.currentTarget.style.backgroundColor = themeConfig.theme.colors.resultBackground;
205
+ },
206
+ onClick: ()=>handleResultClick(result),
207
+ onFocus: (e)=>{
208
+ e.currentTarget.style.backgroundColor = themeConfig.theme.colors.resultBackgroundFocus;
209
+ },
210
+ onKeyDown: (e)=>{
211
+ if (e.key === 'Enter' || e.key === ' ') {
212
+ e.preventDefault();
213
+ handleResultClick(result);
214
+ }
215
+ },
216
+ onMouseEnter: (e)=>{
217
+ e.currentTarget.style.backgroundColor = themeConfig.theme.colors.resultBackgroundHover;
218
+ },
219
+ onMouseLeave: (e)=>{
220
+ e.currentTarget.style.backgroundColor = themeConfig.theme.colors.resultBackground;
221
+ },
222
+ role: "button",
223
+ tabIndex: 0
224
+ }, /*#__PURE__*/ React.createElement("div", {
225
+ style: {
226
+ alignItems: 'flex-start',
227
+ display: 'flex',
228
+ gap: '12px'
229
+ }
230
+ }, /*#__PURE__*/ React.createElement("div", {
231
+ style: {
232
+ flexShrink: 0,
233
+ marginTop: '4px'
234
+ }
235
+ }, /*#__PURE__*/ React.createElement("div", {
236
+ style: {
237
+ alignItems: 'center',
238
+ backgroundColor: themeConfig.theme.colors.collectionBadge,
239
+ borderRadius: themeConfig.theme.spacing.inputBorderRadius,
240
+ color: themeConfig.theme.colors.collectionBadgeText,
241
+ display: 'flex',
242
+ fontSize: '14px',
243
+ fontWeight: themeConfig.theme.typography.fontWeightMedium,
244
+ height: '32px',
245
+ justifyContent: 'center',
246
+ width: '32px'
247
+ }
248
+ }, result.collection?.charAt(0).toUpperCase() || '📄')), /*#__PURE__*/ React.createElement("div", {
249
+ style: {
250
+ flex: 1,
251
+ minWidth: 0
252
+ }
253
+ }, /*#__PURE__*/ React.createElement("div", {
254
+ style: {
255
+ alignItems: 'center',
256
+ display: 'flex',
257
+ justifyContent: 'space-between',
258
+ marginBottom: '8px'
259
+ }
260
+ }, /*#__PURE__*/ React.createElement("h3", {
261
+ style: {
262
+ color: themeConfig.theme.colors.titleText,
263
+ fontFamily: themeConfig.theme.typography.fontFamily,
264
+ fontSize: themeConfig.theme.typography.fontSizeLg,
265
+ fontWeight: themeConfig.theme.typography.fontWeightSemibold,
266
+ lineHeight: themeConfig.theme.typography.lineHeightTight,
267
+ margin: 0,
268
+ overflow: 'hidden',
269
+ textOverflow: 'ellipsis',
270
+ whiteSpace: 'nowrap'
271
+ }
272
+ }, result.document?.title || result.document?.name || result.title || 'Untitled'), typeof result.text_match === 'number' && !isNaN(result.text_match) && /*#__PURE__*/ React.createElement("span", {
273
+ style: {
274
+ alignItems: 'center',
275
+ backgroundColor: themeConfig.theme.colors.scoreBadge,
276
+ borderRadius: '4px',
277
+ color: themeConfig.theme.colors.scoreBadgeText,
278
+ display: 'inline-flex',
279
+ fontSize: themeConfig.theme.typography.fontSizeXs,
280
+ fontWeight: themeConfig.theme.typography.fontWeightMedium,
281
+ marginLeft: '8px',
282
+ padding: '2px 6px'
283
+ }
284
+ }, relativePercentage, "%")), (result.highlight?.title?.snippet || result.highlight?.content?.snippet) && /*#__PURE__*/ React.createElement("div", {
285
+ dangerouslySetInnerHTML: {
286
+ __html: result.highlight?.title?.snippet || result.highlight?.content?.snippet || ''
287
+ },
288
+ style: {
289
+ color: themeConfig.theme.colors.descriptionText,
290
+ display: '-webkit-box',
291
+ fontSize: themeConfig.theme.typography.fontSizeSm,
292
+ lineHeight: themeConfig.theme.typography.lineHeightNormal,
293
+ marginTop: '4px',
294
+ overflow: 'hidden',
295
+ WebkitBoxOrient: 'vertical',
296
+ WebkitLineClamp: 2
297
+ }
298
+ }), /*#__PURE__*/ React.createElement("div", {
299
+ style: {
300
+ alignItems: 'center',
301
+ color: themeConfig.theme.colors.metaText,
302
+ display: 'flex',
303
+ fontSize: themeConfig.theme.typography.fontSizeXs,
304
+ gap: '12px',
305
+ marginTop: '8px'
306
+ }
307
+ }, /*#__PURE__*/ React.createElement("span", {
308
+ style: {
309
+ alignItems: 'center',
310
+ display: 'inline-flex'
311
+ }
312
+ }, /*#__PURE__*/ React.createElement("svg", {
313
+ fill: "currentColor",
314
+ style: {
315
+ height: '12px',
316
+ marginRight: '4px',
317
+ width: '12px'
318
+ },
319
+ viewBox: "0 0 20 20"
320
+ }, /*#__PURE__*/ React.createElement("path", {
321
+ clipRule: "evenodd",
322
+ d: "M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z",
323
+ fillRule: "evenodd"
324
+ })), result.collection), (result.document?.updatedAt || result.updatedAt) && /*#__PURE__*/ React.createElement("span", {
325
+ style: {
326
+ alignItems: 'center',
327
+ display: 'inline-flex'
328
+ }
329
+ }, /*#__PURE__*/ React.createElement("svg", {
330
+ fill: "currentColor",
331
+ style: {
332
+ height: '12px',
333
+ marginRight: '4px',
334
+ width: '12px'
335
+ },
336
+ viewBox: "0 0 20 20"
337
+ }, /*#__PURE__*/ React.createElement("path", {
338
+ clipRule: "evenodd",
339
+ d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z",
340
+ fillRule: "evenodd"
341
+ })), new Date(result.document?.updatedAt || result.updatedAt).toLocaleDateString()))), /*#__PURE__*/ React.createElement("div", {
342
+ style: {
343
+ flexShrink: 0,
344
+ opacity: 0,
345
+ transition: `opacity ${themeConfig.theme.animations.transitionNormal} ${themeConfig.theme.animations.easeInOut}`
346
+ }
347
+ }, /*#__PURE__*/ React.createElement("svg", {
348
+ fill: "none",
349
+ stroke: "currentColor",
350
+ style: {
351
+ color: themeConfig.theme.colors.metaText,
352
+ height: '16px',
353
+ width: '16px'
354
+ },
355
+ viewBox: "0 0 24 24"
356
+ }, /*#__PURE__*/ React.createElement("path", {
357
+ d: "M9 5l7 7-7 7",
358
+ strokeLinecap: "round",
359
+ strokeLinejoin: "round",
360
+ strokeWidth: 2
361
+ }))))));
362
+ };
363
+ const defaultRenderNoResults = (_query)=>/*#__PURE__*/ React.createElement("div", {
364
+ className: `${noResultsClassName}`,
365
+ style: {
366
+ color: themeConfig.theme.colors.noResultsText,
367
+ fontFamily: themeConfig.theme.typography.fontFamily,
368
+ fontSize: themeConfig.theme.typography.fontSizeSm,
369
+ padding: '40px 20px',
370
+ textAlign: 'center'
371
+ }
372
+ }, /*#__PURE__*/ React.createElement("div", {
373
+ style: {
374
+ alignItems: 'center',
375
+ display: 'flex',
376
+ flexDirection: 'column'
377
+ }
378
+ }, /*#__PURE__*/ React.createElement("svg", {
379
+ fill: "none",
380
+ stroke: "currentColor",
381
+ style: {
382
+ color: themeConfig.theme.colors.metaText,
383
+ height: '48px',
384
+ marginBottom: '12px',
385
+ width: '48px'
386
+ },
387
+ viewBox: "0 0 24 24"
388
+ }, /*#__PURE__*/ React.createElement("path", {
389
+ d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z",
390
+ strokeLinecap: "round",
391
+ strokeLinejoin: "round",
392
+ strokeWidth: 1
393
+ })), /*#__PURE__*/ React.createElement("h3", {
394
+ style: {
395
+ color: themeConfig.theme.colors.titleText,
396
+ fontSize: themeConfig.theme.typography.fontSizeSm,
397
+ fontWeight: themeConfig.theme.typography.fontWeightMedium,
398
+ margin: 0,
399
+ marginBottom: '4px'
400
+ }
401
+ }, "No results found"), /*#__PURE__*/ React.createElement("p", {
402
+ style: {
403
+ color: themeConfig.theme.colors.descriptionText,
404
+ fontSize: themeConfig.theme.typography.fontSizeSm,
405
+ margin: 0
406
+ }
407
+ }, "Try searching for something else")));
408
+ const defaultRenderError = (error)=>/*#__PURE__*/ React.createElement("div", {
409
+ className: `${errorClassName}`,
410
+ style: {
411
+ alignItems: 'center',
412
+ backgroundColor: themeConfig.theme.colors.errorBackground,
413
+ borderBottom: `1px solid ${themeConfig.theme.colors.resultBorder}`,
414
+ color: themeConfig.theme.colors.errorText,
415
+ display: 'flex',
416
+ fontFamily: themeConfig.theme.typography.fontFamily,
417
+ fontSize: themeConfig.theme.typography.fontSizeSm,
418
+ gap: '8px',
419
+ padding: '16px'
420
+ }
421
+ }, /*#__PURE__*/ React.createElement("svg", {
422
+ fill: "currentColor",
423
+ style: {
424
+ flexShrink: 0,
425
+ height: '20px',
426
+ width: '20px'
427
+ },
428
+ viewBox: "0 0 20 20"
429
+ }, /*#__PURE__*/ React.createElement("path", {
430
+ clipRule: "evenodd",
431
+ d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z",
432
+ fillRule: "evenodd"
433
+ })), /*#__PURE__*/ React.createElement("div", null, /*#__PURE__*/ React.createElement("h3", {
434
+ style: {
435
+ fontSize: themeConfig.theme.typography.fontSizeSm,
436
+ fontWeight: themeConfig.theme.typography.fontWeightMedium,
437
+ margin: 0
438
+ }
439
+ }, "Search Error"), /*#__PURE__*/ React.createElement("p", {
440
+ style: {
441
+ color: themeConfig.theme.colors.errorText,
442
+ fontSize: themeConfig.theme.typography.fontSizeSm,
443
+ margin: 0
444
+ }
445
+ }, error)));
446
+ const defaultRenderResultsHeader = (found, searchTime)=>/*#__PURE__*/ React.createElement("div", {
447
+ className: `${resultsHeaderClassName}`,
448
+ style: {
449
+ alignItems: 'center',
450
+ backgroundColor: themeConfig.theme.colors.headerBackground,
451
+ borderBottom: `1px solid ${themeConfig.theme.colors.headerBorder}`,
452
+ color: themeConfig.theme.colors.headerText,
453
+ display: 'flex',
454
+ fontFamily: themeConfig.theme.typography.fontFamily,
455
+ fontSize: themeConfig.theme.spacing.headerFontSize,
456
+ fontWeight: themeConfig.theme.typography.fontWeightMedium,
457
+ justifyContent: 'space-between',
458
+ padding: themeConfig.theme.spacing.headerPadding
459
+ }
460
+ }, /*#__PURE__*/ React.createElement("div", {
461
+ style: {
462
+ alignItems: 'center',
463
+ display: 'flex',
464
+ gap: '8px'
465
+ }
466
+ }, /*#__PURE__*/ React.createElement("svg", {
467
+ fill: "currentColor",
468
+ style: {
469
+ color: themeConfig.theme.colors.metaText,
470
+ height: '16px',
471
+ width: '16px'
472
+ },
473
+ viewBox: "0 0 20 20"
474
+ }, /*#__PURE__*/ React.createElement("path", {
475
+ clipRule: "evenodd",
476
+ d: "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
477
+ fillRule: "evenodd"
478
+ })), /*#__PURE__*/ React.createElement("span", {
479
+ style: {
480
+ color: themeConfig.theme.colors.headerText,
481
+ fontSize: themeConfig.theme.typography.fontSizeSm,
482
+ fontWeight: themeConfig.theme.typography.fontWeightMedium
483
+ }
484
+ }, found, " result", found !== 1 ? 's' : '', " found")), showSearchTime && /*#__PURE__*/ React.createElement("span", {
485
+ style: {
486
+ backgroundColor: themeConfig.theme.colors.inputBorder,
487
+ borderRadius: '12px',
488
+ color: themeConfig.theme.colors.metaText,
489
+ fontSize: themeConfig.theme.typography.fontSizeXs,
490
+ padding: '4px 8px'
491
+ }
492
+ }, searchTime, "ms"));
493
+ return /*#__PURE__*/ React.createElement("div", {
494
+ className: `${className}`,
495
+ style: {
496
+ margin: '0 auto',
497
+ maxWidth: '600px',
498
+ position: 'relative',
499
+ width: '100%'
500
+ }
501
+ }, /*#__PURE__*/ React.createElement("div", {
502
+ className: `${inputWrapperClassName}`,
503
+ style: {
504
+ position: 'relative',
505
+ width: '100%'
506
+ }
507
+ }, renderInput ? renderInput({
508
+ className: `${inputClassName}`,
509
+ onBlur: handleInputBlur,
510
+ onChange: handleInputChange,
511
+ onFocus: handleInputFocus,
512
+ onKeyDown: handleKeyDown,
513
+ placeholder,
514
+ ref: inputRef,
515
+ value: query
516
+ }) : /*#__PURE__*/ React.createElement("input", {
517
+ "aria-label": "Search input",
518
+ autoComplete: "off",
519
+ className: `${inputClassName}`,
520
+ onBlur: (e)=>{
521
+ e.target.style.borderColor = themeConfig.theme.colors.inputBorder;
522
+ e.target.style.boxShadow = 'none';
523
+ handleInputBlur(e);
524
+ },
525
+ onChange: handleInputChange,
526
+ onFocus: (e)=>{
527
+ e.target.style.borderColor = themeConfig.theme.colors.inputBorderFocus;
528
+ e.target.style.boxShadow = themeConfig.config.enableShadows !== false ? `0 0 0 3px ${themeConfig.theme.colors.inputBorderFocus}20` : 'none';
529
+ handleInputFocus();
530
+ },
531
+ onKeyDown: handleKeyDown,
532
+ placeholder: placeholder,
533
+ ref: inputRef,
534
+ style: {
535
+ backgroundColor: themeConfig.theme.colors.inputBackground,
536
+ border: `2px solid ${themeConfig.theme.colors.inputBorder}`,
537
+ borderRadius: themeConfig.config.enableRoundedCorners !== false ? themeConfig.theme.spacing.inputBorderRadius : '0',
538
+ boxShadow: 'none',
539
+ color: themeConfig.theme.colors.inputText,
540
+ fontFamily: themeConfig.theme.typography.fontFamily,
541
+ fontSize: themeConfig.theme.spacing.inputFontSize,
542
+ fontWeight: themeConfig.theme.typography.fontWeightNormal,
543
+ lineHeight: themeConfig.theme.typography.lineHeightNormal,
544
+ outline: 'none',
545
+ padding: themeConfig.theme.spacing.inputPadding,
546
+ transition: themeConfig.config.enableAnimations !== false ? `all ${themeConfig.theme.animations.transitionNormal} ${themeConfig.theme.animations.easeInOut}` : 'none',
547
+ width: '100%'
548
+ },
549
+ title: "Search input",
550
+ type: "text",
551
+ value: query
552
+ }), isLoading && showLoading && /*#__PURE__*/ React.createElement("div", {
553
+ style: {
554
+ position: 'absolute',
555
+ right: '12px',
556
+ top: '50%',
557
+ transform: 'translateY(-50%)'
558
+ }
559
+ }, /*#__PURE__*/ React.createElement("div", {
560
+ "data-testid": "loading-spinner",
561
+ style: {
562
+ animation: `spin 1s linear infinite`,
563
+ border: `2px solid ${themeConfig.theme.colors.inputBorder}`,
564
+ borderRadius: '50%',
565
+ borderTop: `2px solid ${themeConfig.theme.colors.inputBorderFocus}`,
566
+ height: '16px',
567
+ width: '16px'
568
+ }
569
+ }))), isOpen && /*#__PURE__*/ React.createElement("div", {
570
+ className: `${resultsContainerClassName}`,
571
+ ref: resultsRef,
572
+ style: {
573
+ backgroundColor: themeConfig.theme.colors.resultsBackground,
574
+ border: `1px solid ${themeConfig.theme.colors.resultsBorder}`,
575
+ borderRadius: themeConfig.config.enableRoundedCorners !== false ? themeConfig.theme.spacing.resultsBorderRadius : '0',
576
+ boxShadow: themeConfig.config.enableShadows !== false ? themeConfig.theme.shadows.shadowLg : 'none',
577
+ boxSizing: 'border-box',
578
+ display: 'flex',
579
+ left: '0',
580
+ marginTop: '10px',
581
+ maxHeight: themeConfig.theme.spacing.resultsMaxHeight,
582
+ overflow: 'hidden',
583
+ position: 'absolute',
584
+ right: '0',
585
+ top: '100%',
586
+ zIndex: 1000,
587
+ ...themeConfig.config.enableAnimations !== false && {
588
+ animation: `slideDown ${themeConfig.theme.animations.animationNormal} ${themeConfig.theme.animations.easeOut}`
589
+ }
590
+ }
591
+ }, /*#__PURE__*/ React.createElement("div", {
592
+ className: `relative ${resultsClassName}`,
593
+ style: {
594
+ minHeight: 0,
595
+ overflowY: 'auto'
596
+ }
597
+ }, error && (renderError ? renderError(error) : defaultRenderError(error)), !error && results && /*#__PURE__*/ React.createElement(React.Fragment, null, showResultCount && (renderResultsHeader ? renderResultsHeader(results.found, results.search_time_ms) : defaultRenderResultsHeader(results.found, results.search_time_ms)), results.hits.length > 0 ? /*#__PURE__*/ React.createElement("div", {
598
+ className: `${resultsListClassName}`
599
+ }, results.hits.map((result, index)=>renderResult ? renderResult(result, index) : defaultRenderResult(result, index))) : renderNoResults ? renderNoResults(query) : defaultRenderNoResults(query)))));
600
+ };
601
+ export default HeadlessSearchInput;
602
+ export { HeadlessSearchInput };
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import type { ThemeConfig, ThemeContextValue } from './themes/types.js';
3
+ interface ThemeProviderProps {
4
+ children: React.ReactNode;
5
+ config: ThemeConfig;
6
+ }
7
+ export declare function ThemeProvider({ children, config }: ThemeProviderProps): React.JSX.Element;
8
+ export declare function useTheme(): ThemeContextValue;
9
+ export {};
10
+ //# sourceMappingURL=ThemeProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../src/components/ThemeProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA6B,MAAM,OAAO,CAAA;AAEjD,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAIvE,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,MAAM,EAAE,WAAW,CAAA;CACpB;AAID,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,kBAAkB,qBAIrE;AAED,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C"}
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+ import React, { createContext, use } from 'react';
3
+ import { useThemeConfig } from './themes/hooks.js';
4
+ const ThemeContext = /*#__PURE__*/ createContext(null);
5
+ export function ThemeProvider({ children, config }) {
6
+ const themeContext = useThemeConfig(config);
7
+ return /*#__PURE__*/ React.createElement(ThemeContext, {
8
+ value: themeContext
9
+ }, children);
10
+ }
11
+ export function useTheme() {
12
+ const context = use(ThemeContext);
13
+ if (!context) {
14
+ throw new Error('useTheme must be used within a ThemeProvider');
15
+ }
16
+ return context;
17
+ }
@@ -0,0 +1,6 @@
1
+ export type { BaseSearchInputProps, HealthCheckResponse, SearchResponse, SearchResult, TypesenseSearchConfig, } from '../lib/types.js';
2
+ export { default as HeadlessSearchInput } from './HeadlessSearchInput.js';
3
+ export type { HeadlessSearchInputProps } from './HeadlessSearchInput.js';
4
+ export { ThemeProvider } from './ThemeProvider.js';
5
+ export * from './themes/index.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,qBAAqB,GACtB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AACzE,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,cAAc,mBAAmB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { default as HeadlessSearchInput } from './HeadlessSearchInput.js';
2
+ export { ThemeProvider } from './ThemeProvider.js';
3
+ export * from './themes/index.js';