@seekora-ai/ui-sdk-react 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.
- package/dist/components/Breadcrumb.d.ts +43 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -0
- package/dist/components/Breadcrumb.js +119 -0
- package/dist/components/ClearRefinements.d.ts +42 -0
- package/dist/components/ClearRefinements.d.ts.map +1 -0
- package/dist/components/ClearRefinements.js +80 -0
- package/dist/components/CurrentRefinements.d.ts +41 -0
- package/dist/components/CurrentRefinements.d.ts.map +1 -0
- package/dist/components/CurrentRefinements.js +83 -0
- package/dist/components/Facets.d.ts +53 -0
- package/dist/components/Facets.d.ts.map +1 -0
- package/dist/components/Facets.js +195 -0
- package/dist/components/FederatedDropdown.d.ts +92 -0
- package/dist/components/FederatedDropdown.d.ts.map +1 -0
- package/dist/components/FederatedDropdown.js +510 -0
- package/dist/components/HierarchicalMenu.d.ts +55 -0
- package/dist/components/HierarchicalMenu.d.ts.map +1 -0
- package/dist/components/HierarchicalMenu.js +168 -0
- package/dist/components/Highlight.d.ts +51 -0
- package/dist/components/Highlight.d.ts.map +1 -0
- package/dist/components/Highlight.js +155 -0
- package/dist/components/HitsPerPage.d.ts +41 -0
- package/dist/components/HitsPerPage.d.ts.map +1 -0
- package/dist/components/HitsPerPage.js +72 -0
- package/dist/components/InfiniteHits.d.ts +56 -0
- package/dist/components/InfiniteHits.d.ts.map +1 -0
- package/dist/components/InfiniteHits.js +181 -0
- package/dist/components/MobileFilters.d.ts +71 -0
- package/dist/components/MobileFilters.d.ts.map +1 -0
- package/dist/components/MobileFilters.js +242 -0
- package/dist/components/Pagination.d.ts +44 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +142 -0
- package/dist/components/QuerySuggestions.d.ts +38 -0
- package/dist/components/QuerySuggestions.d.ts.map +1 -0
- package/dist/components/QuerySuggestions.js +86 -0
- package/dist/components/QuerySuggestionsDropdown.d.ts +86 -0
- package/dist/components/QuerySuggestionsDropdown.d.ts.map +1 -0
- package/dist/components/QuerySuggestionsDropdown.js +395 -0
- package/dist/components/RangeInput.d.ts +58 -0
- package/dist/components/RangeInput.d.ts.map +1 -0
- package/dist/components/RangeInput.js +203 -0
- package/dist/components/RangeSlider.d.ts +51 -0
- package/dist/components/RangeSlider.d.ts.map +1 -0
- package/dist/components/RangeSlider.js +193 -0
- package/dist/components/Recommendations.d.ts +90 -0
- package/dist/components/Recommendations.d.ts.map +1 -0
- package/dist/components/Recommendations.js +270 -0
- package/dist/components/RichQuerySuggestions.d.ts +77 -0
- package/dist/components/RichQuerySuggestions.d.ts.map +1 -0
- package/dist/components/RichQuerySuggestions.js +492 -0
- package/dist/components/SearchBar.d.ts +40 -0
- package/dist/components/SearchBar.d.ts.map +1 -0
- package/dist/components/SearchBar.js +217 -0
- package/dist/components/SearchBarWithSuggestions.d.ts +99 -0
- package/dist/components/SearchBarWithSuggestions.d.ts.map +1 -0
- package/dist/components/SearchBarWithSuggestions.js +275 -0
- package/dist/components/SearchLayout.d.ts +35 -0
- package/dist/components/SearchLayout.d.ts.map +1 -0
- package/dist/components/SearchLayout.js +56 -0
- package/dist/components/SearchProvider.d.ts +28 -0
- package/dist/components/SearchProvider.d.ts.map +1 -0
- package/dist/components/SearchProvider.js +43 -0
- package/dist/components/SearchResults.d.ts +51 -0
- package/dist/components/SearchResults.d.ts.map +1 -0
- package/dist/components/SearchResults.js +485 -0
- package/dist/components/SortBy.d.ts +44 -0
- package/dist/components/SortBy.d.ts.map +1 -0
- package/dist/components/SortBy.js +61 -0
- package/dist/components/Stats.d.ts +37 -0
- package/dist/components/Stats.d.ts.map +1 -0
- package/dist/components/Stats.js +52 -0
- package/dist/components/suggestions/AmazonDropdown.d.ts +30 -0
- package/dist/components/suggestions/AmazonDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/AmazonDropdown.js +529 -0
- package/dist/components/suggestions/GoogleDropdown.d.ts +31 -0
- package/dist/components/suggestions/GoogleDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/GoogleDropdown.js +370 -0
- package/dist/components/suggestions/MinimalDropdown.d.ts +24 -0
- package/dist/components/suggestions/MinimalDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/MinimalDropdown.js +314 -0
- package/dist/components/suggestions/MobileSheetDropdown.d.ts +31 -0
- package/dist/components/suggestions/MobileSheetDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/MobileSheetDropdown.js +485 -0
- package/dist/components/suggestions/PinterestDropdown.d.ts +29 -0
- package/dist/components/suggestions/PinterestDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/PinterestDropdown.js +450 -0
- package/dist/components/suggestions/ShopifyDropdown.d.ts +27 -0
- package/dist/components/suggestions/ShopifyDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/ShopifyDropdown.js +451 -0
- package/dist/components/suggestions/SpotlightDropdown.d.ts +33 -0
- package/dist/components/suggestions/SpotlightDropdown.d.ts.map +1 -0
- package/dist/components/suggestions/SpotlightDropdown.js +547 -0
- package/dist/components/suggestions/SuggestionSearchBar.d.ts +123 -0
- package/dist/components/suggestions/SuggestionSearchBar.d.ts.map +1 -0
- package/dist/components/suggestions/SuggestionSearchBar.js +652 -0
- package/dist/components/suggestions/index.d.ts +37 -0
- package/dist/components/suggestions/index.d.ts.map +1 -0
- package/dist/components/suggestions/index.js +59 -0
- package/dist/components/suggestions/styles/index.d.ts +11 -0
- package/dist/components/suggestions/styles/index.d.ts.map +1 -0
- package/dist/components/suggestions/styles/index.js +289 -0
- package/dist/components/suggestions/styles/responsive.d.ts +107 -0
- package/dist/components/suggestions/styles/responsive.d.ts.map +1 -0
- package/dist/components/suggestions/styles/responsive.js +237 -0
- package/dist/components/suggestions/types.d.ts +489 -0
- package/dist/components/suggestions/types.d.ts.map +1 -0
- package/dist/components/suggestions/types.js +6 -0
- package/dist/components/suggestions/utils.d.ts +213 -0
- package/dist/components/suggestions/utils.d.ts.map +1 -0
- package/dist/components/suggestions/utils.js +514 -0
- package/dist/hooks/useAnalytics.d.ts +20 -0
- package/dist/hooks/useAnalytics.d.ts.map +1 -0
- package/dist/hooks/useAnalytics.js +62 -0
- package/dist/hooks/useNaturalLanguageFilters.d.ts +48 -0
- package/dist/hooks/useNaturalLanguageFilters.d.ts.map +1 -0
- package/dist/hooks/useNaturalLanguageFilters.js +221 -0
- package/dist/hooks/useQuerySuggestions.d.ts +21 -0
- package/dist/hooks/useQuerySuggestions.d.ts.map +1 -0
- package/dist/hooks/useQuerySuggestions.js +68 -0
- package/dist/hooks/useQuerySuggestionsEnhanced.d.ts +114 -0
- package/dist/hooks/useQuerySuggestionsEnhanced.d.ts.map +1 -0
- package/dist/hooks/useQuerySuggestionsEnhanced.js +376 -0
- package/dist/hooks/useSearchState.d.ts +35 -0
- package/dist/hooks/useSearchState.d.ts.map +1 -0
- package/dist/hooks/useSearchState.js +68 -0
- package/dist/hooks/useSeekoraSearch.d.ts +20 -0
- package/dist/hooks/useSeekoraSearch.d.ts.map +1 -0
- package/dist/hooks/useSeekoraSearch.js +63 -0
- package/dist/hooks/useSmartSuggestions.d.ts +55 -0
- package/dist/hooks/useSmartSuggestions.d.ts.map +1 -0
- package/dist/hooks/useSmartSuggestions.js +236 -0
- package/dist/hooks/useSuggestionsAnalytics.d.ts +91 -0
- package/dist/hooks/useSuggestionsAnalytics.d.ts.map +1 -0
- package/dist/hooks/useSuggestionsAnalytics.js +226 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.umd.js +1 -0
- package/dist/src/index.d.ts +2849 -0
- package/dist/src/index.esm.js +11679 -0
- package/dist/src/index.esm.js.map +1 -0
- package/dist/src/index.js +11761 -0
- package/dist/src/index.js.map +1 -0
- package/dist/themes/createTheme.d.ts +8 -0
- package/dist/themes/createTheme.d.ts.map +1 -0
- package/dist/themes/createTheme.js +10 -0
- package/dist/themes/dark.d.ts +6 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/dark.js +34 -0
- package/dist/themes/default.d.ts +6 -0
- package/dist/themes/default.d.ts.map +1 -0
- package/dist/themes/default.js +71 -0
- package/dist/themes/mergeThemes.d.ts +7 -0
- package/dist/themes/mergeThemes.d.ts.map +1 -0
- package/dist/themes/mergeThemes.js +6 -0
- package/dist/themes/minimal.d.ts +6 -0
- package/dist/themes/minimal.d.ts.map +1 -0
- package/dist/themes/minimal.js +34 -0
- package/dist/themes/suggestions.d.ts +216 -0
- package/dist/themes/suggestions.d.ts.map +1 -0
- package/dist/themes/suggestions.js +546 -0
- package/dist/themes/types.d.ts +7 -0
- package/dist/themes/types.d.ts.map +1 -0
- package/dist/themes/types.js +6 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/package.json +65 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SuggestionSearchBar - Unified Search Component with Premium Dropdowns
|
|
3
|
+
*
|
|
4
|
+
* A self-contained search component that:
|
|
5
|
+
* - Fetches suggestions from the API automatically
|
|
6
|
+
* - Parses responses internally (no user-side parsing needed)
|
|
7
|
+
* - Renders any of the 7 premium dropdown variants
|
|
8
|
+
* - Handles analytics tracking
|
|
9
|
+
* - Supports recent searches via localStorage
|
|
10
|
+
* - Fully responsive and mobile-ready
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <SuggestionSearchBar
|
|
15
|
+
* client={seekoraClient}
|
|
16
|
+
* variant="amazon"
|
|
17
|
+
* onSearch={(query) => router.push(`/search?q=${query}`)}
|
|
18
|
+
* />
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import React, { useState, useRef, useCallback, useEffect, forwardRef, useImperativeHandle, useMemo, } from 'react';
|
|
22
|
+
import { getRecentSearches, addRecentSearch, removeRecentSearch, cx, mergeStyles, getSuggestionsCache, } from './utils';
|
|
23
|
+
import { useResponsive } from './styles/responsive';
|
|
24
|
+
import { useInjectResponsiveStyles } from './styles';
|
|
25
|
+
import { useSearchContext } from '../SearchProvider';
|
|
26
|
+
import { useSuggestionsAnalytics } from '../../hooks/useSuggestionsAnalytics';
|
|
27
|
+
// Import all variants
|
|
28
|
+
import { AmazonDropdown } from './AmazonDropdown';
|
|
29
|
+
import { GoogleDropdown } from './GoogleDropdown';
|
|
30
|
+
import { PinterestDropdown } from './PinterestDropdown';
|
|
31
|
+
import { SpotlightDropdown } from './SpotlightDropdown';
|
|
32
|
+
import { ShopifyDropdown } from './ShopifyDropdown';
|
|
33
|
+
import { MobileSheetDropdown } from './MobileSheetDropdown';
|
|
34
|
+
import { MinimalDropdown } from './MinimalDropdown';
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Theme helpers (support both Theme { colors } and DropdownThemeConfig)
|
|
37
|
+
// ============================================================================
|
|
38
|
+
function getThemeVars(theme) {
|
|
39
|
+
if (!theme) {
|
|
40
|
+
return {
|
|
41
|
+
'--seekora-bg-surface': '#ffffff',
|
|
42
|
+
'--seekora-bg-secondary': '#f7f7f7',
|
|
43
|
+
'--seekora-bg-tertiary': '#f0f0f0',
|
|
44
|
+
'--seekora-bg-hover': '#f5f5f5',
|
|
45
|
+
'--seekora-text-primary': '#0f1111',
|
|
46
|
+
'--seekora-text-secondary': '#565959',
|
|
47
|
+
'--seekora-text-tertiary': '#767676',
|
|
48
|
+
'--seekora-border-color': '#e7e7e7',
|
|
49
|
+
'--seekora-primary': '#f90',
|
|
50
|
+
'--seekora-text-on-primary': '#0f1111',
|
|
51
|
+
'--seekora-text-inverse': '#ffffff',
|
|
52
|
+
'--seekora-link': '#007185',
|
|
53
|
+
'--seekora-font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const t = theme;
|
|
57
|
+
const colors = t.colors ?? {};
|
|
58
|
+
return {
|
|
59
|
+
'--seekora-bg-surface': t.backgroundColor ?? colors.background ?? '#ffffff',
|
|
60
|
+
'--seekora-bg-secondary': t.surfaceColor ?? colors.hover ?? '#f7f7f7',
|
|
61
|
+
'--seekora-bg-tertiary': t.hoverColor ?? colors.hover ?? '#f0f0f0',
|
|
62
|
+
'--seekora-bg-hover': t.hoverColor ?? colors.hover ?? '#f5f5f5',
|
|
63
|
+
'--seekora-text-primary': t.textColor ?? colors.text ?? '#0f1111',
|
|
64
|
+
'--seekora-text-secondary': t.textSecondaryColor ?? colors.text ?? '#565959',
|
|
65
|
+
'--seekora-text-tertiary': t.textSecondaryColor ?? '#767676',
|
|
66
|
+
'--seekora-border-color': t.borderColor ?? colors.border ?? '#e7e7e7',
|
|
67
|
+
'--seekora-primary': t.primaryColor ?? colors.primary ?? '#f90',
|
|
68
|
+
'--seekora-text-on-primary': t.textOnPrimaryColor ?? '#0f1111',
|
|
69
|
+
'--seekora-text-inverse': t.textInverseColor ?? '#ffffff',
|
|
70
|
+
'--seekora-link': t.primaryColor ?? colors.primary ?? '#007185',
|
|
71
|
+
'--seekora-font-family': t.fontFamily ?? '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function themeToDropdownConfig(theme) {
|
|
75
|
+
if (!theme)
|
|
76
|
+
return undefined;
|
|
77
|
+
const t = theme;
|
|
78
|
+
const colors = t.colors ?? {};
|
|
79
|
+
return {
|
|
80
|
+
backgroundColor: t.backgroundColor ?? colors.background,
|
|
81
|
+
textColor: t.textColor ?? colors.text,
|
|
82
|
+
surfaceColor: t.surfaceColor ?? colors.hover,
|
|
83
|
+
borderColor: t.borderColor ?? colors.border,
|
|
84
|
+
hoverColor: t.hoverColor ?? colors.hover,
|
|
85
|
+
primaryColor: t.primaryColor ?? colors.primary,
|
|
86
|
+
textSecondaryColor: t.textSecondaryColor ?? colors.text,
|
|
87
|
+
fontFamily: t.fontFamily,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Variant Map
|
|
92
|
+
// ============================================================================
|
|
93
|
+
const VariantComponents = {
|
|
94
|
+
amazon: AmazonDropdown,
|
|
95
|
+
google: GoogleDropdown,
|
|
96
|
+
pinterest: PinterestDropdown,
|
|
97
|
+
spotlight: SpotlightDropdown,
|
|
98
|
+
shopify: ShopifyDropdown,
|
|
99
|
+
'mobile-sheet': MobileSheetDropdown,
|
|
100
|
+
minimal: MinimalDropdown,
|
|
101
|
+
};
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Styles
|
|
104
|
+
// ============================================================================
|
|
105
|
+
const createStyles = (isMobile) => ({
|
|
106
|
+
container: {
|
|
107
|
+
position: 'relative',
|
|
108
|
+
width: '100%',
|
|
109
|
+
fontFamily: 'var(--seekora-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif)',
|
|
110
|
+
},
|
|
111
|
+
inputWrapper: {
|
|
112
|
+
display: 'flex',
|
|
113
|
+
alignItems: 'center',
|
|
114
|
+
backgroundColor: 'var(--seekora-bg-surface, #ffffff)',
|
|
115
|
+
color: 'var(--seekora-text-primary, #111827)',
|
|
116
|
+
borderWidth: '2px',
|
|
117
|
+
borderStyle: 'solid',
|
|
118
|
+
borderColor: 'var(--seekora-border-color, #d1d5db)',
|
|
119
|
+
borderRadius: isMobile ? '8px' : '24px',
|
|
120
|
+
overflow: 'hidden',
|
|
121
|
+
transition: 'border-color 150ms, box-shadow 150ms',
|
|
122
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.05)',
|
|
123
|
+
},
|
|
124
|
+
inputWrapperFocused: {
|
|
125
|
+
borderColor: 'var(--seekora-primary, #f90)',
|
|
126
|
+
boxShadow: '0 0 0 3px rgba(255, 153, 0, 0.15), 0 2px 8px rgba(0,0,0,0.1)',
|
|
127
|
+
},
|
|
128
|
+
searchIcon: {
|
|
129
|
+
padding: isMobile ? '0 10px 0 14px' : '0 12px 0 18px',
|
|
130
|
+
color: 'var(--seekora-text-secondary, #6b7280)',
|
|
131
|
+
display: 'flex',
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
flexShrink: 0,
|
|
134
|
+
},
|
|
135
|
+
input: {
|
|
136
|
+
flex: 1,
|
|
137
|
+
padding: isMobile ? '12px 8px 12px 0' : '14px 12px 14px 0',
|
|
138
|
+
fontSize: isMobile ? '16px' : '15px',
|
|
139
|
+
borderWidth: 0,
|
|
140
|
+
borderStyle: 'none',
|
|
141
|
+
outline: 'none',
|
|
142
|
+
backgroundColor: 'transparent',
|
|
143
|
+
color: 'var(--seekora-text-primary, #0f1111)',
|
|
144
|
+
fontFamily: 'inherit',
|
|
145
|
+
minWidth: 0,
|
|
146
|
+
WebkitAppearance: 'none',
|
|
147
|
+
},
|
|
148
|
+
clearButton: {
|
|
149
|
+
padding: '8px 14px',
|
|
150
|
+
background: 'none',
|
|
151
|
+
borderWidth: 0,
|
|
152
|
+
borderStyle: 'none',
|
|
153
|
+
cursor: 'pointer',
|
|
154
|
+
color: 'var(--seekora-text-tertiary, #9ca3af)',
|
|
155
|
+
fontSize: '18px',
|
|
156
|
+
lineHeight: 1,
|
|
157
|
+
display: 'flex',
|
|
158
|
+
alignItems: 'center',
|
|
159
|
+
justifyContent: 'center',
|
|
160
|
+
flexShrink: 0,
|
|
161
|
+
transition: 'color 150ms',
|
|
162
|
+
},
|
|
163
|
+
loadingSpinner: {
|
|
164
|
+
width: '18px',
|
|
165
|
+
height: '18px',
|
|
166
|
+
borderWidth: '2px',
|
|
167
|
+
borderStyle: 'solid',
|
|
168
|
+
borderColor: '#e5e7eb',
|
|
169
|
+
borderTopColor: '#f90',
|
|
170
|
+
borderRadius: '50%',
|
|
171
|
+
animation: 'seekora-spin 0.8s linear infinite',
|
|
172
|
+
marginRight: '12px',
|
|
173
|
+
flexShrink: 0,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// Component
|
|
178
|
+
// ============================================================================
|
|
179
|
+
export const SuggestionSearchBar = forwardRef(function SuggestionSearchBar(props, ref) {
|
|
180
|
+
const { client, variant = 'amazon', autoMobileVariant = true, placeholder = 'Search...', defaultQuery = '', value, minQueryLength = 1, maxSuggestions = 8, debounceMs = 200, includeDropdownRecommendations = true, includeCategories = true, filteredTabs, analyticsTags, enableRecentSearches = true, maxRecentSearches = 10, showProducts = true, showTrendingOnEmpty = true, enableAnalytics = true, analyticsConfig, suggestionFields, productFields, theme, onSearch, onQueryChange, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, className, style, inputClassName, dropdownWidth, dropdownMaxHeight = '500px', zIndex = 1000, enableCache = true, cacheTtlMs = 30000, cacheMaxSize = 100, } = props;
|
|
181
|
+
// Theme: prop overrides context (SearchProvider theme)
|
|
182
|
+
const searchContext = useSearchContext();
|
|
183
|
+
const effectiveTheme = theme ?? searchContext.theme;
|
|
184
|
+
// Initialize analytics hook for proper event tracking
|
|
185
|
+
const analytics = useSuggestionsAnalytics({
|
|
186
|
+
client,
|
|
187
|
+
enabled: enableAnalytics,
|
|
188
|
+
analyticsTags: analyticsConfig?.tags ?? analyticsTags,
|
|
189
|
+
trackImpressions: analyticsConfig?.trackImpressions ?? true,
|
|
190
|
+
trackClicks: analyticsConfig?.trackSuggestionClicks ?? true,
|
|
191
|
+
});
|
|
192
|
+
// Inject responsive styles
|
|
193
|
+
useInjectResponsiveStyles();
|
|
194
|
+
// Inject theme CSS variables so dropdown and page use one consistent theme
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
const root = document.documentElement;
|
|
197
|
+
const themeVars = getThemeVars(effectiveTheme);
|
|
198
|
+
Object.entries(themeVars).forEach(([key, value]) => {
|
|
199
|
+
root.style.setProperty(key, value);
|
|
200
|
+
});
|
|
201
|
+
}, [effectiveTheme]);
|
|
202
|
+
// Responsive state
|
|
203
|
+
const responsive = useResponsive();
|
|
204
|
+
const { isMobile } = responsive;
|
|
205
|
+
// Determine actual variant (auto-switch to mobile on small screens)
|
|
206
|
+
const actualVariant = useMemo(() => {
|
|
207
|
+
if (autoMobileVariant && isMobile && variant !== 'mobile-sheet' && variant !== 'spotlight') {
|
|
208
|
+
return 'mobile-sheet';
|
|
209
|
+
}
|
|
210
|
+
return variant;
|
|
211
|
+
}, [variant, isMobile, autoMobileVariant]);
|
|
212
|
+
// State
|
|
213
|
+
const [query, setQuery] = useState(defaultQuery);
|
|
214
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
215
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
216
|
+
const [loading, setLoading] = useState(false);
|
|
217
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
218
|
+
const [products, setProducts] = useState([]);
|
|
219
|
+
const [categories, setCategories] = useState([]);
|
|
220
|
+
const [tabs, setTabs] = useState([]);
|
|
221
|
+
const [trendingSearches, setTrendingSearches] = useState([]);
|
|
222
|
+
const [recentSearches, setRecentSearches] = useState([]);
|
|
223
|
+
const [activeTab, setActiveTab] = useState('all');
|
|
224
|
+
// Refs
|
|
225
|
+
const inputRef = useRef(null);
|
|
226
|
+
const dropdownRef = useRef(null);
|
|
227
|
+
const debounceRef = useRef(null);
|
|
228
|
+
const containerRef = useRef(null);
|
|
229
|
+
// Initialize cache
|
|
230
|
+
const cache = useMemo(() => {
|
|
231
|
+
if (!enableCache)
|
|
232
|
+
return null;
|
|
233
|
+
return getSuggestionsCache({ maxSize: cacheMaxSize, defaultTtlMs: cacheTtlMs });
|
|
234
|
+
}, [enableCache, cacheMaxSize, cacheTtlMs]);
|
|
235
|
+
// Controlled vs uncontrolled query
|
|
236
|
+
const currentQuery = value !== undefined ? value : query;
|
|
237
|
+
// Styles
|
|
238
|
+
const styles = useMemo(() => createStyles(isMobile), [isMobile]);
|
|
239
|
+
// Load recent searches on mount
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
if (enableRecentSearches) {
|
|
242
|
+
const allRecent = getRecentSearches();
|
|
243
|
+
setRecentSearches(allRecent.slice(0, maxRecentSearches));
|
|
244
|
+
}
|
|
245
|
+
}, [enableRecentSearches, maxRecentSearches]);
|
|
246
|
+
// ========================================================================
|
|
247
|
+
// API Data Fetching
|
|
248
|
+
// ========================================================================
|
|
249
|
+
const fetchSuggestions = useCallback(async (searchQuery) => {
|
|
250
|
+
if (!client)
|
|
251
|
+
return;
|
|
252
|
+
// Allow empty string "" for focus-triggered search (API returns trending/default); otherwise enforce min length
|
|
253
|
+
if (searchQuery.length > 0 && searchQuery.length < minQueryLength) {
|
|
254
|
+
setSuggestions([]);
|
|
255
|
+
setProducts([]);
|
|
256
|
+
setTabs([]);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// Build cache key from query and options
|
|
260
|
+
const cacheOptions = {
|
|
261
|
+
maxSuggestions,
|
|
262
|
+
includeDropdownRecommendations,
|
|
263
|
+
includeCategories,
|
|
264
|
+
filteredTabs: filteredTabs?.map(t => t.filter).join(','),
|
|
265
|
+
};
|
|
266
|
+
const cacheKey = cache?.generateKey(searchQuery, cacheOptions);
|
|
267
|
+
// Check cache first
|
|
268
|
+
if (cache && cacheKey) {
|
|
269
|
+
const cachedResponse = cache.get(cacheKey);
|
|
270
|
+
if (cachedResponse) {
|
|
271
|
+
console.log('Seekora: Cache hit for query:', searchQuery);
|
|
272
|
+
parseAndSetData(cachedResponse);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
setLoading(true);
|
|
277
|
+
try {
|
|
278
|
+
// Use the SDK client to fetch suggestions ("" is valid - API returns something for empty query)
|
|
279
|
+
// IMPORTANT: returnFullResponse: true is required to get filtered_tabs and extensions
|
|
280
|
+
const response = await client.getSuggestions?.(searchQuery, {
|
|
281
|
+
hitsPerPage: maxSuggestions,
|
|
282
|
+
include_dropdown_recommendations: includeDropdownRecommendations,
|
|
283
|
+
include_categories: includeCategories,
|
|
284
|
+
filtered_tabs: filteredTabs,
|
|
285
|
+
analytics_tags: analyticsTags,
|
|
286
|
+
returnFullResponse: true, // Required to get tabs and products
|
|
287
|
+
});
|
|
288
|
+
// Cache the response
|
|
289
|
+
if (cache && cacheKey && response) {
|
|
290
|
+
cache.set(cacheKey, response);
|
|
291
|
+
console.log('Seekora: Cached response for query:', searchQuery);
|
|
292
|
+
}
|
|
293
|
+
// Parse the response - SDK returns normalized structure
|
|
294
|
+
parseAndSetData(response);
|
|
295
|
+
// Track impressions via analytics hook (sends to API)
|
|
296
|
+
if (enableAnalytics && response && !Array.isArray(response)) {
|
|
297
|
+
const resp = response;
|
|
298
|
+
analytics.trackImpression({
|
|
299
|
+
suggestions: (resp.suggestions || []).map((s) => ({
|
|
300
|
+
query: s.query || s.text || '',
|
|
301
|
+
objectID: s.objectID || s.id,
|
|
302
|
+
popularity: s.popularity,
|
|
303
|
+
})),
|
|
304
|
+
products: resp.extensions?.trending_products || resp.extensions?.item_recommendations || [],
|
|
305
|
+
categories: resp.extensions?.popular_categories || [],
|
|
306
|
+
brands: resp.extensions?.popular_brands || [],
|
|
307
|
+
query: searchQuery,
|
|
308
|
+
timestamp: Date.now(),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
console.error('Seekora: Failed to fetch suggestions', error);
|
|
314
|
+
setSuggestions([]);
|
|
315
|
+
setProducts([]);
|
|
316
|
+
setTabs([]);
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
setLoading(false);
|
|
320
|
+
}
|
|
321
|
+
}, [client, minQueryLength, maxSuggestions, includeDropdownRecommendations, includeCategories, filteredTabs, analyticsTags, enableAnalytics, analytics, cache]);
|
|
322
|
+
// Parse API response - handles multiple response formats
|
|
323
|
+
const parseAndSetData = useCallback((response) => {
|
|
324
|
+
// Handle different response structures from the API/SDK
|
|
325
|
+
let hits = [];
|
|
326
|
+
let extensions = {};
|
|
327
|
+
console.log('Seekora: Raw response structure', {
|
|
328
|
+
hasSuggestions: !!response?.suggestions,
|
|
329
|
+
hasResults: !!response?.results,
|
|
330
|
+
hasExtensions: !!response?.extensions,
|
|
331
|
+
hasData: !!response?.data,
|
|
332
|
+
});
|
|
333
|
+
// SDK returnFullResponse format: { suggestions, results, extensions }
|
|
334
|
+
if (response?.suggestions && response?.extensions) {
|
|
335
|
+
hits = response.suggestions || [];
|
|
336
|
+
extensions = response.extensions || {};
|
|
337
|
+
}
|
|
338
|
+
// Multi-index response: { results: [{ hits, extensions }, ...] }
|
|
339
|
+
else if (response?.results && Array.isArray(response.results)) {
|
|
340
|
+
const result0 = response.results[0] || {};
|
|
341
|
+
const result1 = response.results[1] || {};
|
|
342
|
+
hits = result0.hits || result0.suggestions || [];
|
|
343
|
+
// Extensions can be in result0 or result1
|
|
344
|
+
extensions = result0.extensions || result1.extensions || {};
|
|
345
|
+
}
|
|
346
|
+
// Wrapped response: { data: { hits, extensions, ... } }
|
|
347
|
+
else if (response?.data) {
|
|
348
|
+
hits = response.data.hits || [];
|
|
349
|
+
extensions = response.data.extensions || response.data.dropdown_recommendations || {};
|
|
350
|
+
}
|
|
351
|
+
// Direct response: { hits, extensions, ... }
|
|
352
|
+
else if (response?.hits) {
|
|
353
|
+
hits = response.hits || [];
|
|
354
|
+
extensions = response.extensions || response.dropdown_recommendations || {};
|
|
355
|
+
}
|
|
356
|
+
// Array response
|
|
357
|
+
else if (Array.isArray(response)) {
|
|
358
|
+
hits = response;
|
|
359
|
+
}
|
|
360
|
+
// Set suggestions
|
|
361
|
+
setSuggestions(hits);
|
|
362
|
+
// Set filtered tabs from extensions
|
|
363
|
+
const filteredTabsData = extensions.filtered_tabs || [];
|
|
364
|
+
setTabs(filteredTabsData);
|
|
365
|
+
// Set products from first tab or trending
|
|
366
|
+
if (filteredTabsData.length > 0 && filteredTabsData[0].products) {
|
|
367
|
+
setProducts(filteredTabsData[0].products);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
setProducts(extensions.trending_products || extensions.item_recommendations || []);
|
|
371
|
+
}
|
|
372
|
+
// Set categories
|
|
373
|
+
setCategories(extensions.popular_categories || []);
|
|
374
|
+
// Set trending searches
|
|
375
|
+
setTrendingSearches(extensions.trending_searches || extensions.top_searches || []);
|
|
376
|
+
console.log('Seekora: Parsed response', {
|
|
377
|
+
suggestions: hits.length,
|
|
378
|
+
tabs: filteredTabsData.length,
|
|
379
|
+
products: filteredTabsData[0]?.products?.length || 0,
|
|
380
|
+
extensionKeys: Object.keys(extensions),
|
|
381
|
+
});
|
|
382
|
+
}, []);
|
|
383
|
+
// ========================================================================
|
|
384
|
+
// Event Handlers
|
|
385
|
+
// ========================================================================
|
|
386
|
+
const handleInputChange = useCallback((e) => {
|
|
387
|
+
const newQuery = e.target.value;
|
|
388
|
+
if (value === undefined) {
|
|
389
|
+
setQuery(newQuery);
|
|
390
|
+
}
|
|
391
|
+
onQueryChange?.(newQuery);
|
|
392
|
+
setActiveTab('all');
|
|
393
|
+
// Debounced fetch
|
|
394
|
+
if (debounceRef.current) {
|
|
395
|
+
clearTimeout(debounceRef.current);
|
|
396
|
+
}
|
|
397
|
+
if (newQuery.length >= minQueryLength) {
|
|
398
|
+
setIsOpen(true);
|
|
399
|
+
debounceRef.current = setTimeout(() => {
|
|
400
|
+
fetchSuggestions(newQuery);
|
|
401
|
+
}, debounceMs);
|
|
402
|
+
}
|
|
403
|
+
else if (newQuery.length === 0 && (enableRecentSearches || showTrendingOnEmpty)) {
|
|
404
|
+
setIsOpen(true);
|
|
405
|
+
setSuggestions([]);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
setIsOpen(false);
|
|
409
|
+
}
|
|
410
|
+
}, [value, onQueryChange, minQueryLength, debounceMs, fetchSuggestions, enableRecentSearches, showTrendingOnEmpty]);
|
|
411
|
+
const handleInputFocus = useCallback(() => {
|
|
412
|
+
setIsFocused(true);
|
|
413
|
+
setIsOpen(true);
|
|
414
|
+
// Track dropdown open
|
|
415
|
+
if (enableAnalytics) {
|
|
416
|
+
analytics.trackDropdownOpen(currentQuery);
|
|
417
|
+
}
|
|
418
|
+
// Trigger empty search "" when user clicks/focuses the box so API returns trending/default content
|
|
419
|
+
if (currentQuery.length >= minQueryLength) {
|
|
420
|
+
if (suggestions.length === 0)
|
|
421
|
+
fetchSuggestions(currentQuery);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
fetchSuggestions('');
|
|
425
|
+
}
|
|
426
|
+
}, [currentQuery, minQueryLength, suggestions.length, fetchSuggestions, enableAnalytics, analytics]);
|
|
427
|
+
const handleInputBlur = useCallback(() => {
|
|
428
|
+
setIsFocused(false);
|
|
429
|
+
// Delay close to allow click events on dropdown
|
|
430
|
+
setTimeout(() => {
|
|
431
|
+
if (!containerRef.current?.contains(document.activeElement)) {
|
|
432
|
+
setIsOpen(false);
|
|
433
|
+
}
|
|
434
|
+
}, 150);
|
|
435
|
+
}, []);
|
|
436
|
+
const handleKeyDown = useCallback((e) => {
|
|
437
|
+
if (e.key === 'Escape') {
|
|
438
|
+
setIsOpen(false);
|
|
439
|
+
inputRef.current?.blur();
|
|
440
|
+
}
|
|
441
|
+
else if (e.key === 'Enter') {
|
|
442
|
+
e.preventDefault();
|
|
443
|
+
// Check if there's an active selection in the dropdown
|
|
444
|
+
const activeIndex = dropdownRef.current?.getActiveIndex?.() ?? -1;
|
|
445
|
+
if (activeIndex >= 0 && isOpen) {
|
|
446
|
+
// Let the dropdown handle the selection
|
|
447
|
+
dropdownRef.current?.selectActive?.();
|
|
448
|
+
}
|
|
449
|
+
else if (currentQuery.trim()) {
|
|
450
|
+
// No active selection, submit the current query
|
|
451
|
+
onSearch?.(currentQuery.trim());
|
|
452
|
+
if (enableRecentSearches) {
|
|
453
|
+
addRecentSearch(currentQuery.trim());
|
|
454
|
+
setRecentSearches(getRecentSearches().slice(0, maxRecentSearches));
|
|
455
|
+
}
|
|
456
|
+
// Track search submit analytics
|
|
457
|
+
if (enableAnalytics) {
|
|
458
|
+
analytics.trackSearchSubmit(currentQuery.trim(), false);
|
|
459
|
+
}
|
|
460
|
+
setIsOpen(false);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
464
|
+
e.preventDefault();
|
|
465
|
+
// Open dropdown if not already open
|
|
466
|
+
if (!isOpen && (suggestions.length > 0 || recentSearches.length > 0)) {
|
|
467
|
+
setIsOpen(true);
|
|
468
|
+
}
|
|
469
|
+
if (e.key === 'ArrowDown') {
|
|
470
|
+
dropdownRef.current?.navigateNext?.();
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
dropdownRef.current?.navigatePrevious?.();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}, [currentQuery, onSearch, enableRecentSearches, maxRecentSearches, isOpen, suggestions.length, recentSearches.length, enableAnalytics, analytics]);
|
|
477
|
+
const handleSuggestionSelect = useCallback((suggestion, index) => {
|
|
478
|
+
const text = suggestion.query || suggestion.text || suggestion.objectID || '';
|
|
479
|
+
if (value === undefined) {
|
|
480
|
+
setQuery(text);
|
|
481
|
+
}
|
|
482
|
+
onQueryChange?.(text);
|
|
483
|
+
onSuggestionSelect?.(suggestion, index);
|
|
484
|
+
if (enableRecentSearches && text) {
|
|
485
|
+
addRecentSearch(text);
|
|
486
|
+
setRecentSearches(getRecentSearches().slice(0, maxRecentSearches));
|
|
487
|
+
}
|
|
488
|
+
// Track analytics via the proper hook (sends to API)
|
|
489
|
+
if (enableAnalytics) {
|
|
490
|
+
analytics.trackSuggestionClick({
|
|
491
|
+
suggestion: {
|
|
492
|
+
query: text,
|
|
493
|
+
objectID: suggestion.objectID || suggestion.id,
|
|
494
|
+
popularity: suggestion.popularity,
|
|
495
|
+
},
|
|
496
|
+
position: index,
|
|
497
|
+
section: 'suggestions',
|
|
498
|
+
query: currentQuery,
|
|
499
|
+
totalSuggestions: suggestions.length,
|
|
500
|
+
});
|
|
501
|
+
analytics.trackSearchSubmit(text, true, suggestion);
|
|
502
|
+
}
|
|
503
|
+
onSearch?.(text);
|
|
504
|
+
setIsOpen(false);
|
|
505
|
+
}, [value, onQueryChange, onSuggestionSelect, enableRecentSearches, maxRecentSearches, enableAnalytics, onSearch, analytics, currentQuery, suggestions.length]);
|
|
506
|
+
const handleProductClick = useCallback((product, index) => {
|
|
507
|
+
onProductClick?.(product, index);
|
|
508
|
+
// Track analytics via the proper hook (sends to API)
|
|
509
|
+
if (enableAnalytics) {
|
|
510
|
+
analytics.trackProductClick({
|
|
511
|
+
product: {
|
|
512
|
+
id: product.id || product.objectID,
|
|
513
|
+
objectID: product.objectID || product.id,
|
|
514
|
+
title: product.title || product.name,
|
|
515
|
+
image: product.image,
|
|
516
|
+
price: product.price,
|
|
517
|
+
},
|
|
518
|
+
position: index,
|
|
519
|
+
section: activeTab !== 'all' ? 'filtered_tab' : 'products',
|
|
520
|
+
tabId: activeTab !== 'all' ? activeTab : undefined,
|
|
521
|
+
query: currentQuery,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}, [onProductClick, enableAnalytics, analytics, activeTab, currentQuery]);
|
|
525
|
+
const handleCategoryClick = useCallback((category, index) => {
|
|
526
|
+
onCategoryClick?.(category, index);
|
|
527
|
+
// Track analytics via the proper hook (sends to API)
|
|
528
|
+
if (enableAnalytics) {
|
|
529
|
+
analytics.trackCategoryClick({
|
|
530
|
+
value: category.label || category.value || category.id,
|
|
531
|
+
count: category.count,
|
|
532
|
+
path: category.path,
|
|
533
|
+
}, currentQuery);
|
|
534
|
+
}
|
|
535
|
+
}, [onCategoryClick, enableAnalytics, analytics, currentQuery]);
|
|
536
|
+
const handleTabChange = useCallback((tabId) => {
|
|
537
|
+
console.log('Seekora: Tab changed to', tabId);
|
|
538
|
+
setActiveTab(tabId);
|
|
539
|
+
onTabChange?.(tabId);
|
|
540
|
+
// Track analytics via the proper hook (sends to API)
|
|
541
|
+
if (enableAnalytics) {
|
|
542
|
+
const tab = tabs.find(t => t.id === tabId);
|
|
543
|
+
if (tab) {
|
|
544
|
+
analytics.trackTabSelect({
|
|
545
|
+
id: tab.id,
|
|
546
|
+
label: tab.label,
|
|
547
|
+
filter: tab.filter,
|
|
548
|
+
products: tab.products,
|
|
549
|
+
nb_hits: tab.nb_hits,
|
|
550
|
+
}, currentQuery);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}, [onTabChange, enableAnalytics, analytics, tabs, currentQuery]);
|
|
554
|
+
const handleRecentClick = useCallback((text) => {
|
|
555
|
+
if (value === undefined) {
|
|
556
|
+
setQuery(text);
|
|
557
|
+
}
|
|
558
|
+
onQueryChange?.(text);
|
|
559
|
+
fetchSuggestions(text);
|
|
560
|
+
// Track analytics via the proper hook (sends to API)
|
|
561
|
+
if (enableAnalytics) {
|
|
562
|
+
analytics.trackRecentSearchClick({
|
|
563
|
+
query: text,
|
|
564
|
+
timestamp: Date.now(),
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}, [value, onQueryChange, fetchSuggestions, enableAnalytics, analytics]);
|
|
568
|
+
const handleRecentRemove = useCallback((text) => {
|
|
569
|
+
removeRecentSearch(text);
|
|
570
|
+
setRecentSearches(getRecentSearches().slice(0, maxRecentSearches));
|
|
571
|
+
}, [maxRecentSearches]);
|
|
572
|
+
const handleClear = useCallback(() => {
|
|
573
|
+
if (value === undefined) {
|
|
574
|
+
setQuery('');
|
|
575
|
+
}
|
|
576
|
+
onQueryChange?.('');
|
|
577
|
+
setSuggestions([]);
|
|
578
|
+
setProducts([]);
|
|
579
|
+
setTabs([]);
|
|
580
|
+
inputRef.current?.focus();
|
|
581
|
+
}, [value, onQueryChange]);
|
|
582
|
+
const handleClose = useCallback(() => {
|
|
583
|
+
setIsOpen(false);
|
|
584
|
+
// Track dropdown close
|
|
585
|
+
if (enableAnalytics) {
|
|
586
|
+
analytics.trackDropdownClose(currentQuery);
|
|
587
|
+
}
|
|
588
|
+
}, [enableAnalytics, analytics, currentQuery]);
|
|
589
|
+
// ========================================================================
|
|
590
|
+
// Imperative Handle
|
|
591
|
+
// ========================================================================
|
|
592
|
+
useImperativeHandle(ref, () => ({
|
|
593
|
+
focus: () => inputRef.current?.focus(),
|
|
594
|
+
blur: () => inputRef.current?.blur(),
|
|
595
|
+
clear: handleClear,
|
|
596
|
+
getQuery: () => currentQuery,
|
|
597
|
+
setQuery: (q) => {
|
|
598
|
+
if (value === undefined) {
|
|
599
|
+
setQuery(q);
|
|
600
|
+
}
|
|
601
|
+
onQueryChange?.(q);
|
|
602
|
+
},
|
|
603
|
+
openDropdown: () => setIsOpen(true),
|
|
604
|
+
closeDropdown: () => setIsOpen(false),
|
|
605
|
+
refresh: () => fetchSuggestions(currentQuery),
|
|
606
|
+
clearCache: () => cache?.clear(),
|
|
607
|
+
getCacheStats: () => cache?.getStats() ?? null,
|
|
608
|
+
}), [currentQuery, value, onQueryChange, handleClear, fetchSuggestions, cache]);
|
|
609
|
+
// ========================================================================
|
|
610
|
+
// Render
|
|
611
|
+
// ========================================================================
|
|
612
|
+
const DropdownComponent = VariantComponents[actualVariant] || AmazonDropdown;
|
|
613
|
+
// Get products for current tab - always derive from tabs to ensure consistency
|
|
614
|
+
const currentTabProducts = useMemo(() => {
|
|
615
|
+
console.log('Seekora: Computing products for tab', activeTab, 'from', tabs.length, 'tabs');
|
|
616
|
+
if (tabs.length === 0) {
|
|
617
|
+
return products; // Fallback to products state if no tabs
|
|
618
|
+
}
|
|
619
|
+
if (activeTab === 'all') {
|
|
620
|
+
// For 'all' tab, use first tab's products or combined
|
|
621
|
+
const firstTabProducts = tabs[0]?.products || [];
|
|
622
|
+
return firstTabProducts.length > 0 ? firstTabProducts : products;
|
|
623
|
+
}
|
|
624
|
+
// Find the specific tab
|
|
625
|
+
const tab = tabs.find(t => t.id === activeTab);
|
|
626
|
+
if (tab?.products && tab.products.length > 0) {
|
|
627
|
+
console.log('Seekora: Found', tab.products.length, 'products for tab', activeTab);
|
|
628
|
+
return tab.products;
|
|
629
|
+
}
|
|
630
|
+
// Fallback
|
|
631
|
+
return products;
|
|
632
|
+
}, [activeTab, tabs, products]);
|
|
633
|
+
return (React.createElement("div", { ref: containerRef, className: cx('seekora-suggestion-search', className), style: mergeStyles(styles.container, style) },
|
|
634
|
+
React.createElement("div", { style: mergeStyles(styles.inputWrapper, isFocused ? styles.inputWrapperFocused : undefined) },
|
|
635
|
+
React.createElement("div", { style: styles.searchIcon },
|
|
636
|
+
React.createElement("svg", { viewBox: "0 0 20 20", fill: "currentColor", width: "20", height: "20" },
|
|
637
|
+
React.createElement("path", { fillRule: "evenodd", 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", clipRule: "evenodd" }))),
|
|
638
|
+
React.createElement("input", { ref: inputRef, type: "text", value: currentQuery, onChange: handleInputChange, onFocus: handleInputFocus, onBlur: handleInputBlur, onKeyDown: handleKeyDown, placeholder: placeholder, className: inputClassName, style: styles.input, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: false, "aria-label": "Search", "aria-expanded": isOpen, "aria-haspopup": "listbox" }),
|
|
639
|
+
loading && React.createElement("div", { style: styles.loadingSpinner }),
|
|
640
|
+
currentQuery && !loading && (React.createElement("button", { type: "button", onClick: handleClear, style: styles.clearButton, "aria-label": "Clear search" }, "\u00D7"))),
|
|
641
|
+
isOpen && (React.createElement(DropdownComponent, { query: currentQuery, isOpen: isOpen, loading: loading, suggestions: suggestions, products: currentTabProducts, categories: tabs.map(t => ({ id: t.id, label: t.label, count: t.nb_hits })), recentSearches: recentSearches.map(r => typeof r === 'string' ? r : r.query), trendingSearches: trendingSearches, filteredTabs: tabs, activeTab: activeTab, suggestionFields: suggestionFields, productFields: productFields, theme: themeToDropdownConfig(effectiveTheme) ?? effectiveTheme, width: dropdownWidth || '100%', maxHeight: dropdownMaxHeight, zIndex: zIndex, analytics: {
|
|
642
|
+
enabled: enableAnalytics,
|
|
643
|
+
trackSuggestionClicks: analyticsConfig?.trackSuggestionClicks ?? true,
|
|
644
|
+
trackProductClicks: analyticsConfig?.trackProductClicks ?? true,
|
|
645
|
+
trackImpressions: analyticsConfig?.trackImpressions ?? true,
|
|
646
|
+
trackCategoryClicks: analyticsConfig?.trackCategoryClicks ?? true,
|
|
647
|
+
trackBrandClicks: analyticsConfig?.trackBrandClicks ?? true,
|
|
648
|
+
trackSearchSubmit: analyticsConfig?.trackSearchSubmit ?? true,
|
|
649
|
+
tags: analyticsConfig?.tags ?? analyticsTags,
|
|
650
|
+
}, onSuggestionSelect: handleSuggestionSelect, onProductClick: handleProductClick, onCategoryClick: handleCategoryClick, onTabChange: handleTabChange, onRecentClick: handleRecentClick, onRecentRemove: handleRecentRemove, onClose: handleClose }))));
|
|
651
|
+
});
|
|
652
|
+
export default SuggestionSearchBar;
|