@seekora-ai/ui-sdk-react 0.2.15 → 0.2.16
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/FacetDropdown.d.ts +2 -0
- package/dist/components/FacetDropdown.d.ts.map +1 -1
- package/dist/components/FacetDropdown.js +47 -25
- package/dist/components/Facets.d.ts +4 -0
- package/dist/components/Facets.d.ts.map +1 -1
- package/dist/components/Facets.js +21 -2
- package/dist/components/InfiniteHits.d.ts +0 -7
- package/dist/components/InfiniteHits.d.ts.map +1 -1
- package/dist/components/InfiniteHits.js +2 -13
- package/dist/components/Pagination.d.ts.map +1 -1
- package/dist/components/Pagination.js +27 -9
- package/dist/components/QuerySuggestions.d.ts +0 -4
- package/dist/components/QuerySuggestions.d.ts.map +1 -1
- package/dist/components/QuerySuggestions.js +1 -17
- package/dist/components/QuerySuggestionsDropdown.d.ts +0 -4
- package/dist/components/QuerySuggestionsDropdown.d.ts.map +1 -1
- package/dist/components/QuerySuggestionsDropdown.js +1 -16
- package/dist/components/Recommendations.d.ts +0 -7
- package/dist/components/Recommendations.d.ts.map +1 -1
- package/dist/components/Recommendations.js +3 -23
- package/dist/components/RichQuerySuggestions.d.ts +0 -4
- package/dist/components/RichQuerySuggestions.d.ts.map +1 -1
- package/dist/components/RichQuerySuggestions.js +1 -10
- package/dist/components/SearchBar.d.ts +0 -4
- package/dist/components/SearchBar.d.ts.map +1 -1
- package/dist/components/SearchBar.js +2 -4
- package/dist/components/SearchBarWithSuggestions.js +1 -1
- package/dist/components/SearchLayout.d.ts.map +1 -1
- package/dist/components/SearchLayout.js +13 -17
- package/dist/components/SearchProvider.d.ts.map +1 -1
- package/dist/components/SearchProvider.js +1 -3
- package/dist/components/SearchResults.d.ts +0 -6
- package/dist/components/SearchResults.d.ts.map +1 -1
- package/dist/components/SearchResults.js +1 -14
- package/dist/components/primitives/ImageDisplay.d.ts.map +1 -1
- package/dist/components/primitives/ImageDisplay.js +24 -14
- package/dist/components/primitives/ImageZoom.d.ts.map +1 -1
- package/dist/components/primitives/ImageZoom.js +59 -4
- package/dist/components/primitives/VariantSwatches.d.ts.map +1 -1
- package/dist/components/primitives/VariantSwatches.js +25 -10
- package/dist/components/section-primitives/SectionItemGrid.d.ts +1 -3
- package/dist/components/section-primitives/SectionItemGrid.d.ts.map +1 -1
- package/dist/components/section-primitives/SectionItemGrid.js +1 -4
- package/dist/components/section-primitives/index.d.ts +0 -1
- package/dist/components/section-primitives/index.d.ts.map +1 -1
- package/dist/components/section-primitives/index.js +0 -1
- package/dist/components/suggestions/AmazonDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/AmazonDropdown.js +3 -21
- package/dist/components/suggestions/GoogleDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/GoogleDropdown.js +3 -20
- package/dist/components/suggestions/MinimalDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/MinimalDropdown.js +2 -2
- package/dist/components/suggestions/MobileSheetDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/MobileSheetDropdown.js +60 -60
- package/dist/components/suggestions/PinterestDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/PinterestDropdown.js +41 -41
- package/dist/components/suggestions/ShopifyDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/ShopifyDropdown.js +3 -4
- package/dist/components/suggestions/SpotlightDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/SpotlightDropdown.js +2 -3
- package/dist/components/suggestions/SuggestionSearchBar.d.ts.map +1 -1
- package/dist/components/suggestions/SuggestionSearchBar.js +2 -15
- package/dist/components/suggestions/types.d.ts +0 -6
- package/dist/components/suggestions/types.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ItemCard.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ItemCard.js +32 -8
- package/dist/components/suggestions-primitives/ItemGrid.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ItemGrid.js +9 -2
- package/dist/components/suggestions-primitives/ProductCard.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ProductCard.js +14 -5
- package/dist/components/suggestions-primitives/ProductCardLayouts.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ProductCardLayouts.js +32 -12
- package/dist/components/suggestions-primitives/ProductGrid.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ProductGrid.js +8 -3
- package/dist/components/suggestions-primitives/RecentSearchesList.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/RecentSearchesList.js +12 -5
- package/dist/components/suggestions-primitives/SearchInput.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/SearchInput.js +1 -1
- package/dist/components/suggestions-primitives/SuggestionItem.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/SuggestionItem.js +5 -3
- package/dist/components/suggestions-primitives/SuggestionList.d.ts +1 -8
- package/dist/components/suggestions-primitives/SuggestionList.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/SuggestionList.js +1 -7
- package/dist/components/suggestions-primitives/TrendingList.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/TrendingList.js +14 -7
- package/dist/components/suggestions-primitives/index.d.ts +1 -3
- package/dist/components/suggestions-primitives/index.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/index.js +1 -2
- package/dist/docsearch/components/DocSearch.d.ts.map +1 -1
- package/dist/docsearch/components/DocSearch.js +1 -1
- package/dist/docsearch/components/Results.d.ts +1 -3
- package/dist/docsearch/components/Results.d.ts.map +1 -1
- package/dist/docsearch/components/Results.js +1 -9
- package/dist/docsearch/components/SearchBox.d.ts +1 -2
- package/dist/docsearch/components/SearchBox.d.ts.map +1 -1
- package/dist/docsearch/components/SearchBox.js +4 -6
- package/dist/docsearch/hooks/useSeekoraSearch.d.ts.map +1 -1
- package/dist/docsearch/hooks/useSeekoraSearch.js +6 -0
- package/dist/docsearch/types.d.ts +0 -1
- package/dist/docsearch/types.d.ts.map +1 -1
- package/dist/docsearch.css +2 -5
- package/dist/hooks/useClickTracking.d.ts.map +1 -1
- package/dist/hooks/useClickTracking.js +4 -11
- package/dist/hooks/useExperiment.d.ts.map +1 -1
- package/dist/hooks/useExperiment.js +10 -33
- package/dist/hooks/useFilters.d.ts +27 -0
- package/dist/hooks/useFilters.d.ts.map +1 -0
- package/dist/hooks/useFilters.js +66 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.umd.js +1 -1
- package/dist/src/index.d.ts +39 -79
- package/dist/src/index.esm.js +800 -452
- package/dist/src/index.esm.js.map +1 -1
- package/dist/src/index.js +800 -453
- package/dist/src/index.js.map +1 -1
- package/dist/utils/responsive.d.ts +130 -0
- package/dist/utils/responsive.d.ts.map +1 -0
- package/dist/utils/responsive.js +231 -0
- package/package.json +5 -5
- package/src/docsearch/docsearch.css +2 -5
package/dist/src/index.js
CHANGED
|
@@ -582,6 +582,87 @@ class SearchStateManager {
|
|
|
582
582
|
this.abVariant = abVariant;
|
|
583
583
|
log.verbose('SearchStateManager: A/B test updated', { abTestId, abVariant });
|
|
584
584
|
}
|
|
585
|
+
// =========================================================================
|
|
586
|
+
// Filters API
|
|
587
|
+
// =========================================================================
|
|
588
|
+
/** Build filter_by string from current refinements */
|
|
589
|
+
buildFilterString() {
|
|
590
|
+
if (this.state.refinements.length === 0)
|
|
591
|
+
return '';
|
|
592
|
+
const filtersByField = {};
|
|
593
|
+
this.state.refinements.forEach((refinement) => {
|
|
594
|
+
if (!filtersByField[refinement.field]) {
|
|
595
|
+
filtersByField[refinement.field] = [];
|
|
596
|
+
}
|
|
597
|
+
filtersByField[refinement.field].push(refinement.value);
|
|
598
|
+
});
|
|
599
|
+
const filterParts = [];
|
|
600
|
+
Object.entries(filtersByField).forEach(([field, values]) => {
|
|
601
|
+
values.forEach((value) => {
|
|
602
|
+
filterParts.push(`${field}:${value}`);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
return filterParts.join(' && ');
|
|
606
|
+
}
|
|
607
|
+
/** Fetch filter values independently from search */
|
|
608
|
+
async fetchFilters(options) {
|
|
609
|
+
log.verbose('SearchStateManager: Fetching filters', { options });
|
|
610
|
+
try {
|
|
611
|
+
const filterString = this.buildFilterString();
|
|
612
|
+
const response = await this.client.getFilters({
|
|
613
|
+
q: this.state.query || undefined,
|
|
614
|
+
filter: filterString || undefined,
|
|
615
|
+
...options,
|
|
616
|
+
});
|
|
617
|
+
log.info('SearchStateManager: Filters fetched', {
|
|
618
|
+
filterCount: response?.filters?.length,
|
|
619
|
+
});
|
|
620
|
+
return response;
|
|
621
|
+
}
|
|
622
|
+
catch (err) {
|
|
623
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
624
|
+
log.error('SearchStateManager: Failed to fetch filters', { error: error.message });
|
|
625
|
+
throw error;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/** Search within a facet's values (for autocomplete in facet search boxes) */
|
|
629
|
+
async searchFacetValues(facetName, query) {
|
|
630
|
+
log.verbose('SearchStateManager: Searching facet values', { facetName, query });
|
|
631
|
+
try {
|
|
632
|
+
const filterString = this.buildFilterString();
|
|
633
|
+
const response = await this.client.searchFacetValues(facetName, {
|
|
634
|
+
q: this.state.query || undefined,
|
|
635
|
+
filter: filterString || undefined,
|
|
636
|
+
facetQuery: query,
|
|
637
|
+
});
|
|
638
|
+
log.info('SearchStateManager: Facet values fetched', {
|
|
639
|
+
facetName,
|
|
640
|
+
valueCount: response?.values?.length,
|
|
641
|
+
});
|
|
642
|
+
return response;
|
|
643
|
+
}
|
|
644
|
+
catch (err) {
|
|
645
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
646
|
+
log.error('SearchStateManager: Failed to search facet values', { facetName, error: error.message });
|
|
647
|
+
throw error;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/** Get filter schema (for UI initialization) */
|
|
651
|
+
async getFiltersSchema() {
|
|
652
|
+
log.verbose('SearchStateManager: Getting filters schema');
|
|
653
|
+
try {
|
|
654
|
+
const response = await this.client.getFiltersSchema();
|
|
655
|
+
log.info('SearchStateManager: Filters schema fetched', {
|
|
656
|
+
fieldCount: response?.fields?.length,
|
|
657
|
+
});
|
|
658
|
+
return response;
|
|
659
|
+
}
|
|
660
|
+
catch (err) {
|
|
661
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
662
|
+
log.error('SearchStateManager: Failed to get filters schema', { error: error.message });
|
|
663
|
+
throw error;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
585
666
|
// Clear all state
|
|
586
667
|
clear() {
|
|
587
668
|
this.state = {
|
|
@@ -1389,9 +1470,7 @@ const SearchProvider = ({ client, theme: themeConfig, enableAnalytics = true, au
|
|
|
1389
1470
|
React.useEffect(() => {
|
|
1390
1471
|
if (abTestId !== undefined || abVariant !== undefined) {
|
|
1391
1472
|
stateManager.setAbTest(abTestId, abVariant);
|
|
1392
|
-
|
|
1393
|
-
client.setAbTest(abTestId, abVariant);
|
|
1394
|
-
}
|
|
1473
|
+
client.setAbTest(abTestId, abVariant);
|
|
1395
1474
|
}
|
|
1396
1475
|
}, [stateManager, client, abTestId, abVariant]);
|
|
1397
1476
|
const value = React.useMemo(() => ({
|
|
@@ -1608,7 +1687,7 @@ const DefaultSearchIcon = ({ size = 18 }) => (React.createElement("svg", { width
|
|
|
1608
1687
|
const DefaultClearIcon = ({ size = 14 }) => (React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" },
|
|
1609
1688
|
React.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1610
1689
|
React.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })));
|
|
1611
|
-
const SearchBar = ({ placeholder = '
|
|
1690
|
+
const SearchBar = ({ placeholder = 'Powered by Seekora', showSuggestions = true, debounceMs = 300, minQueryLength = 2, maxSuggestions = 10, onSearch, onQueryChange, onSuggestionSelect, onSearchStateChange, searchOptions, className, style, theme: customTheme, renderSuggestion, renderSearchIcon, showClearButton = true, renderClearIcon, showSubmitButton = false, renderSubmitButton, size = 'medium', }) => {
|
|
1612
1691
|
const { client, theme, enableAnalytics, autoTrackSearch } = useSearchContext();
|
|
1613
1692
|
const { query, setQuery, search: triggerSearch, results, loading: searchLoading, error: searchError } = useSearchState();
|
|
1614
1693
|
const [isFocused, setIsFocused] = React.useState(false);
|
|
@@ -1738,12 +1817,11 @@ const SearchBar = ({ placeholder = 'Search...', showSuggestions = true, debounce
|
|
|
1738
1817
|
}, 200);
|
|
1739
1818
|
}, []);
|
|
1740
1819
|
const defaultRenderSuggestion = (suggestion, index) => (React.createElement("div", { key: index }, suggestion));
|
|
1741
|
-
const defaultRenderLoading = () => (React.createElement("div", { style: { padding: theme.spacing.medium, textAlign: 'center' } }, "Loading suggestions..."));
|
|
1742
1820
|
const searchBarTheme = customTheme || {};
|
|
1743
1821
|
const isLoading = suggestionsLoading || searchLoading;
|
|
1744
1822
|
// Show list when we have suggestions (including previous while loading) or when loading and showLoadingState
|
|
1745
1823
|
const hasSuggestions = displayedSuggestions.length > 0;
|
|
1746
|
-
const showSuggestionsList = isFocused && showSuggestions && query.length >= minQueryLength &&
|
|
1824
|
+
const showSuggestionsList = isFocused && showSuggestions && query.length >= minQueryLength && hasSuggestions;
|
|
1747
1825
|
// Get processing time from results
|
|
1748
1826
|
const res = results;
|
|
1749
1827
|
const processingTime = res?.processingTimeMS
|
|
@@ -1871,7 +1949,6 @@ const SearchBar = ({ placeholder = 'Search...', showSuggestions = true, debounce
|
|
|
1871
1949
|
zIndex: Z_INDEX$2.dropdown,
|
|
1872
1950
|
boxSizing: 'border-box',
|
|
1873
1951
|
} },
|
|
1874
|
-
isLoading && displayedSuggestions.length === 0 && showLoadingState && (renderLoading ? renderLoading() : defaultRenderLoading()),
|
|
1875
1952
|
displayedSuggestions.length > 0 && (React.createElement(React.Fragment, null, displayedSuggestions.map((suggestion, index) => {
|
|
1876
1953
|
const isSelected = index === selectedIndex;
|
|
1877
1954
|
const renderFn = renderSuggestion || defaultRenderSuggestion;
|
|
@@ -1914,7 +1991,7 @@ const formatPrice$1 = (value, currency = '₹') => {
|
|
|
1914
1991
|
}
|
|
1915
1992
|
return String(value);
|
|
1916
1993
|
};
|
|
1917
|
-
const SearchResults = ({ results: resultsProp, loading: loadingProp, error: errorProp, onResultClick, renderResult, renderEmpty,
|
|
1994
|
+
const SearchResults = ({ results: resultsProp, loading: loadingProp, error: errorProp, onResultClick, renderResult, renderEmpty, renderError, className, style, theme: customTheme, itemsPerPage = 20, showPagination = false, viewMode = 'list', fieldMapping, extractResults, enableKeyboardNavigation = true, autoFocus = false, minHeight = '400px', minWidth = '100%', }) => {
|
|
1918
1995
|
const { theme, client, enableAnalytics } = useSearchContext();
|
|
1919
1996
|
const { results: stateResults, loading: stateLoading, error: stateError, currentPage, itemsPerPage: stateItemsPerPage } = useSearchState();
|
|
1920
1997
|
const searchResultsTheme = customTheme || {};
|
|
@@ -2184,11 +2261,6 @@ const SearchResults = ({ results: resultsProp, loading: loadingProp, error: erro
|
|
|
2184
2261
|
textAlign: 'center',
|
|
2185
2262
|
color: theme.colors.text,
|
|
2186
2263
|
} }, "No results found"));
|
|
2187
|
-
const defaultRenderLoading = () => (React.createElement("div", { className: searchResultsTheme.loadingState, style: {
|
|
2188
|
-
padding: theme.spacing.large,
|
|
2189
|
-
textAlign: 'center',
|
|
2190
|
-
color: theme.colors.text,
|
|
2191
|
-
} }, "Loading results..."));
|
|
2192
2264
|
const defaultRenderError = (err) => (React.createElement("div", { className: searchResultsTheme.errorState, style: {
|
|
2193
2265
|
padding: theme.spacing.large,
|
|
2194
2266
|
textAlign: 'center',
|
|
@@ -2297,8 +2369,6 @@ const SearchResults = ({ results: resultsProp, loading: loadingProp, error: erro
|
|
|
2297
2369
|
const containerStyle = {
|
|
2298
2370
|
minHeight: `var(--seekora-results-min-height, ${minHeight})`,
|
|
2299
2371
|
minWidth: `var(--seekora-results-min-width, ${minWidth})`,
|
|
2300
|
-
transition: 'opacity 200ms ease-in-out',
|
|
2301
|
-
opacity: loading && resultItems.length > 0 ? loadingOpacity : 1,
|
|
2302
2372
|
...style,
|
|
2303
2373
|
};
|
|
2304
2374
|
// Determine results list style based on view mode
|
|
@@ -2322,12 +2392,6 @@ const SearchResults = ({ results: resultsProp, loading: loadingProp, error: erro
|
|
|
2322
2392
|
hasError: !!error,
|
|
2323
2393
|
isLoading: loading,
|
|
2324
2394
|
});
|
|
2325
|
-
// When loading with no previous results, show loading only if showLoadingState (default: show previous results, no loading screen)
|
|
2326
|
-
if (loading && resultItems.length === 0 && showLoadingState) {
|
|
2327
|
-
log.verbose('SearchResults: Rendering loading state');
|
|
2328
|
-
return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style: containerStyle }, renderLoading ? renderLoading() : defaultRenderLoading()));
|
|
2329
|
-
}
|
|
2330
|
-
// When loading with previous results, fall through and render them (with opacity transition)
|
|
2331
2395
|
if (error) {
|
|
2332
2396
|
log.error('SearchResults: Rendering error state', {
|
|
2333
2397
|
error: error.message,
|
|
@@ -2551,6 +2615,183 @@ const Stats = ({ results: resultsProp, renderStats, className, style, theme: cus
|
|
|
2551
2615
|
return null;
|
|
2552
2616
|
};
|
|
2553
2617
|
|
|
2618
|
+
/**
|
|
2619
|
+
* Responsive utilities for adaptive layouts across all screen sizes
|
|
2620
|
+
*
|
|
2621
|
+
* Provides hooks and utilities for:
|
|
2622
|
+
* - Responsive breakpoints
|
|
2623
|
+
* - Responsive grid columns
|
|
2624
|
+
* - Responsive spacing
|
|
2625
|
+
* - Responsive font sizes
|
|
2626
|
+
* - Touch target sizing
|
|
2627
|
+
*/
|
|
2628
|
+
/**
|
|
2629
|
+
* Standard breakpoints (in pixels)
|
|
2630
|
+
*/
|
|
2631
|
+
const BREAKPOINTS = {
|
|
2632
|
+
sm: 576, // Small devices (landscape phones, ≥ 576px)
|
|
2633
|
+
md: 768, // Medium devices (tablets, ≥ 768px)
|
|
2634
|
+
lg: 992, // Large devices (desktops, ≥ 992px)
|
|
2635
|
+
xl: 1200, // Extra large devices (large desktops, ≥ 1200px)
|
|
2636
|
+
xxl: 1400, // Extra extra large devices (larger desktops, ≥ 1400px)
|
|
2637
|
+
};
|
|
2638
|
+
/**
|
|
2639
|
+
* Get current breakpoint based on window width
|
|
2640
|
+
*/
|
|
2641
|
+
function getCurrentBreakpoint(width) {
|
|
2642
|
+
if (width >= BREAKPOINTS.xxl)
|
|
2643
|
+
return 'xxl';
|
|
2644
|
+
if (width >= BREAKPOINTS.xl)
|
|
2645
|
+
return 'xl';
|
|
2646
|
+
if (width >= BREAKPOINTS.lg)
|
|
2647
|
+
return 'lg';
|
|
2648
|
+
if (width >= BREAKPOINTS.md)
|
|
2649
|
+
return 'md';
|
|
2650
|
+
if (width >= BREAKPOINTS.sm)
|
|
2651
|
+
return 'sm';
|
|
2652
|
+
return 'xs';
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Hook to get current breakpoint
|
|
2656
|
+
*/
|
|
2657
|
+
function useBreakpoint() {
|
|
2658
|
+
const [breakpoint, setBreakpoint] = React.useState(() => {
|
|
2659
|
+
if (typeof window === 'undefined')
|
|
2660
|
+
return 'lg';
|
|
2661
|
+
return getCurrentBreakpoint(window.innerWidth);
|
|
2662
|
+
});
|
|
2663
|
+
React.useEffect(() => {
|
|
2664
|
+
const handleResize = () => {
|
|
2665
|
+
setBreakpoint(getCurrentBreakpoint(window.innerWidth));
|
|
2666
|
+
};
|
|
2667
|
+
handleResize();
|
|
2668
|
+
window.addEventListener('resize', handleResize);
|
|
2669
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
2670
|
+
}, []);
|
|
2671
|
+
return breakpoint;
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Hook to check if current viewport matches breakpoint
|
|
2675
|
+
*/
|
|
2676
|
+
function useMediaQuery(query) {
|
|
2677
|
+
const [matches, setMatches] = React.useState(() => {
|
|
2678
|
+
if (typeof window === 'undefined')
|
|
2679
|
+
return false;
|
|
2680
|
+
return window.matchMedia(query).matches;
|
|
2681
|
+
});
|
|
2682
|
+
React.useEffect(() => {
|
|
2683
|
+
const mediaQuery = window.matchMedia(query);
|
|
2684
|
+
const handleChange = () => setMatches(mediaQuery.matches);
|
|
2685
|
+
handleChange();
|
|
2686
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
2687
|
+
return () => mediaQuery.removeEventListener('change', handleChange);
|
|
2688
|
+
}, [query]);
|
|
2689
|
+
return matches;
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* Check if mobile viewport (< 768px)
|
|
2693
|
+
*/
|
|
2694
|
+
function useIsMobile() {
|
|
2695
|
+
return useMediaQuery(`(max-width: ${BREAKPOINTS.md - 1}px)`);
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Get responsive grid columns based on breakpoint
|
|
2699
|
+
*/
|
|
2700
|
+
function getResponsiveColumns(breakpoint, defaultColumns = 4) {
|
|
2701
|
+
switch (breakpoint) {
|
|
2702
|
+
case 'xs':
|
|
2703
|
+
return Math.min(defaultColumns, 1); // 1 column on extra small
|
|
2704
|
+
case 'sm':
|
|
2705
|
+
return Math.min(defaultColumns, 2); // 2 columns on small
|
|
2706
|
+
case 'md':
|
|
2707
|
+
return Math.min(defaultColumns, 3); // 3 columns on medium
|
|
2708
|
+
case 'lg':
|
|
2709
|
+
return Math.min(defaultColumns, 4); // 4 columns on large
|
|
2710
|
+
case 'xl':
|
|
2711
|
+
return Math.min(defaultColumns, 5); // 5 columns on extra large
|
|
2712
|
+
case 'xxl':
|
|
2713
|
+
return defaultColumns; // Full columns on extra extra large
|
|
2714
|
+
default:
|
|
2715
|
+
return defaultColumns;
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
/**
|
|
2719
|
+
* Hook to get responsive grid columns
|
|
2720
|
+
*/
|
|
2721
|
+
function useResponsiveColumns(defaultColumns = 4) {
|
|
2722
|
+
const breakpoint = useBreakpoint();
|
|
2723
|
+
return getResponsiveColumns(breakpoint, defaultColumns);
|
|
2724
|
+
}
|
|
2725
|
+
/**
|
|
2726
|
+
* Minimum touch target size (WCAG AAA)
|
|
2727
|
+
*/
|
|
2728
|
+
const MIN_TOUCH_TARGET = 44; // 44x44px minimum for touch
|
|
2729
|
+
/**
|
|
2730
|
+
* Get touch target size based on device type
|
|
2731
|
+
*/
|
|
2732
|
+
function getTouchTargetSize(isMobile, baseSize = 32) {
|
|
2733
|
+
return isMobile ? Math.max(MIN_TOUCH_TARGET, baseSize) : baseSize;
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Hook to get responsive touch target size
|
|
2737
|
+
*/
|
|
2738
|
+
function useTouchTargetSize(baseSize = 32) {
|
|
2739
|
+
const isMobile = useIsMobile();
|
|
2740
|
+
return getTouchTargetSize(isMobile, baseSize);
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Get responsive gap/padding for grids
|
|
2744
|
+
*/
|
|
2745
|
+
function getResponsiveGap(breakpoint, baseGap = 12) {
|
|
2746
|
+
switch (breakpoint) {
|
|
2747
|
+
case 'xs':
|
|
2748
|
+
return Math.max(4, baseGap * 0.5); // 50% on extra small
|
|
2749
|
+
case 'sm':
|
|
2750
|
+
return Math.max(6, baseGap * 0.75); // 75% on small
|
|
2751
|
+
case 'md':
|
|
2752
|
+
case 'lg':
|
|
2753
|
+
return baseGap; // 100% on medium/large
|
|
2754
|
+
case 'xl':
|
|
2755
|
+
case 'xxl':
|
|
2756
|
+
return baseGap * 1.25; // 125% on extra large
|
|
2757
|
+
default:
|
|
2758
|
+
return baseGap;
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Hook to get responsive gap
|
|
2763
|
+
*/
|
|
2764
|
+
function useResponsiveGap(baseGap = 12) {
|
|
2765
|
+
const breakpoint = useBreakpoint();
|
|
2766
|
+
return getResponsiveGap(breakpoint, baseGap);
|
|
2767
|
+
}
|
|
2768
|
+
/**
|
|
2769
|
+
* Get responsive padding for containers
|
|
2770
|
+
*/
|
|
2771
|
+
function getResponsivePadding(breakpoint, basePadding = 12) {
|
|
2772
|
+
switch (breakpoint) {
|
|
2773
|
+
case 'xs':
|
|
2774
|
+
return Math.max(8, basePadding * 0.67); // ~67% on extra small
|
|
2775
|
+
case 'sm':
|
|
2776
|
+
return Math.max(10, basePadding * 0.83); // ~83% on small
|
|
2777
|
+
case 'md':
|
|
2778
|
+
case 'lg':
|
|
2779
|
+
return basePadding; // 100% on medium/large
|
|
2780
|
+
case 'xl':
|
|
2781
|
+
case 'xxl':
|
|
2782
|
+
return basePadding * 1.33; // ~133% on extra large
|
|
2783
|
+
default:
|
|
2784
|
+
return basePadding;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* Hook to get responsive padding
|
|
2789
|
+
*/
|
|
2790
|
+
function useResponsivePadding(basePadding = 12) {
|
|
2791
|
+
const breakpoint = useBreakpoint();
|
|
2792
|
+
return getResponsivePadding(breakpoint, basePadding);
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2554
2795
|
/**
|
|
2555
2796
|
* Pagination Component
|
|
2556
2797
|
*
|
|
@@ -2565,16 +2806,29 @@ const Stats = ({ results: resultsProp, renderStats, className, style, theme: cus
|
|
|
2565
2806
|
* --seekora-pagination-border
|
|
2566
2807
|
* --seekora-pagination-radius
|
|
2567
2808
|
*/
|
|
2568
|
-
/** Size-specific style tokens */
|
|
2569
|
-
const
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2809
|
+
/** Size-specific style tokens (responsive) */
|
|
2810
|
+
const getSizeTokens = (size, isMobile) => {
|
|
2811
|
+
if (isMobile) {
|
|
2812
|
+
// Larger touch targets on mobile
|
|
2813
|
+
return {
|
|
2814
|
+
small: { paddingKey: 'small', fontSizeKey: 'small', minWidth: '40px', minHeight: '40px' },
|
|
2815
|
+
medium: { paddingKey: 'small', fontSizeKey: 'medium', minWidth: '44px', minHeight: '44px' },
|
|
2816
|
+
large: { paddingKey: 'medium', fontSizeKey: 'large', minWidth: '48px', minHeight: '48px' },
|
|
2817
|
+
}[size];
|
|
2818
|
+
}
|
|
2819
|
+
return {
|
|
2820
|
+
small: { paddingKey: 'small', fontSizeKey: 'small', minWidth: '32px', minHeight: '32px' },
|
|
2821
|
+
medium: { paddingKey: 'small', fontSizeKey: 'medium', minWidth: '40px', minHeight: '40px' },
|
|
2822
|
+
large: { paddingKey: 'medium', fontSizeKey: 'large', minWidth: '48px', minHeight: '48px' },
|
|
2823
|
+
}[size];
|
|
2573
2824
|
};
|
|
2574
|
-
const Pagination = ({ results: resultsProp, currentPage: currentPageProp, itemsPerPage: itemsPerPageProp, totalPages: totalPagesProp, onPageChange, maxPages = 7, showFirstLast = true, showPrevNext = true, renderPageButton, className, style, theme: customTheme, variant = 'numbered', loadMoreText = 'Load More', size = 'medium', showPageInfo, previousLabel = 'Previous', nextLabel = 'Next', }) => {
|
|
2825
|
+
const Pagination = ({ results: resultsProp, currentPage: currentPageProp, itemsPerPage: itemsPerPageProp, totalPages: totalPagesProp, onPageChange, maxPages: maxPagesProp = 7, showFirstLast = true, showPrevNext = true, renderPageButton, className, style, theme: customTheme, variant = 'numbered', loadMoreText = 'Load More', size = 'medium', showPageInfo, previousLabel = 'Previous', nextLabel = 'Next', }) => {
|
|
2575
2826
|
const { theme } = useSearchContext();
|
|
2576
2827
|
const { results: stateResults, currentPage: stateCurrentPage, setPage } = useSearchState();
|
|
2577
2828
|
const paginationTheme = customTheme || {};
|
|
2829
|
+
const isMobile = useIsMobile();
|
|
2830
|
+
// Responsive maxPages - show fewer pages on mobile
|
|
2831
|
+
const maxPages = isMobile ? Math.min(maxPagesProp, 5) : maxPagesProp;
|
|
2578
2832
|
// Use results from prop if provided, otherwise from state manager
|
|
2579
2833
|
const results = resultsProp || stateResults;
|
|
2580
2834
|
// Use currentPage from prop if provided, otherwise from state manager
|
|
@@ -2598,8 +2852,8 @@ const Pagination = ({ results: resultsProp, currentPage: currentPageProp, itemsP
|
|
|
2598
2852
|
const resolvedShowPageInfo = showPageInfo !== undefined
|
|
2599
2853
|
? showPageInfo
|
|
2600
2854
|
: variant === 'simple';
|
|
2601
|
-
// Size tokens
|
|
2602
|
-
const sizeTokens =
|
|
2855
|
+
// Size tokens (responsive)
|
|
2856
|
+
const sizeTokens = getSizeTokens(size, isMobile);
|
|
2603
2857
|
// CSS variable aware helpers — allow overrides via custom properties
|
|
2604
2858
|
const cssVarBg = 'var(--seekora-pagination-bg, ' + theme.colors.background + ')';
|
|
2605
2859
|
const cssVarColor = 'var(--seekora-pagination-color, ' + theme.colors.text + ')';
|
|
@@ -2619,7 +2873,7 @@ const Pagination = ({ results: resultsProp, currentPage: currentPageProp, itemsP
|
|
|
2619
2873
|
};
|
|
2620
2874
|
const defaultRenderPageButton = (page, isActive, isDisabled) => (React.createElement("button", { type: "button", disabled: isDisabled, onClick: () => handlePageChange(page), "aria-current": isActive ? 'page' : undefined, "aria-label": `Page ${page}`, className: clsx(paginationTheme.item, isActive && paginationTheme.itemActive, isDisabled), style: {
|
|
2621
2875
|
padding: theme.spacing[sizeTokens.paddingKey],
|
|
2622
|
-
margin: `0 ${theme.spacing.small}`,
|
|
2876
|
+
margin: isMobile ? `0 4px` : `0 ${theme.spacing.small}`,
|
|
2623
2877
|
border: `1px solid ${cssVarBorder}`,
|
|
2624
2878
|
borderRadius: cssVarRadius,
|
|
2625
2879
|
backgroundColor: isActive ? cssVarActiveBg : cssVarBg,
|
|
@@ -2628,6 +2882,10 @@ const Pagination = ({ results: resultsProp, currentPage: currentPageProp, itemsP
|
|
|
2628
2882
|
opacity: 1,
|
|
2629
2883
|
fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
|
|
2630
2884
|
minWidth: sizeTokens.minWidth,
|
|
2885
|
+
minHeight: sizeTokens.minHeight,
|
|
2886
|
+
display: 'inline-flex',
|
|
2887
|
+
alignItems: 'center',
|
|
2888
|
+
justifyContent: 'center',
|
|
2631
2889
|
...(isActive && {
|
|
2632
2890
|
fontWeight: 'bold',
|
|
2633
2891
|
}),
|
|
@@ -3011,6 +3269,71 @@ const SortBy = ({ options, value: valueProp, defaultValue, onSortChange, renderS
|
|
|
3011
3269
|
return null;
|
|
3012
3270
|
};
|
|
3013
3271
|
|
|
3272
|
+
/**
|
|
3273
|
+
* useFilters Hook
|
|
3274
|
+
*
|
|
3275
|
+
* React hook for fetching filter values, searching facet values,
|
|
3276
|
+
* and retrieving filter schema via the dedicated Filters API.
|
|
3277
|
+
*/
|
|
3278
|
+
const useFilters = (options) => {
|
|
3279
|
+
const { stateManager } = useSearchContext();
|
|
3280
|
+
const [filters, setFilters] = React.useState([]);
|
|
3281
|
+
const [schema, setSchema] = React.useState(null);
|
|
3282
|
+
const [loading, setLoading] = React.useState(false);
|
|
3283
|
+
const [error, setError] = React.useState(null);
|
|
3284
|
+
const mountedRef = React.useRef(true);
|
|
3285
|
+
const autoFetch = options?.autoFetch !== false;
|
|
3286
|
+
// Extract non-autoFetch options to pass to fetchFilters
|
|
3287
|
+
const fetchFilters = React.useCallback(async () => {
|
|
3288
|
+
setLoading(true);
|
|
3289
|
+
setError(null);
|
|
3290
|
+
try {
|
|
3291
|
+
const { autoFetch: _, ...filterOptions } = options || {};
|
|
3292
|
+
const response = await stateManager.fetchFilters(filterOptions);
|
|
3293
|
+
if (mountedRef.current) {
|
|
3294
|
+
setFilters(response?.filters || []);
|
|
3295
|
+
setLoading(false);
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
catch (err) {
|
|
3299
|
+
if (mountedRef.current) {
|
|
3300
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3301
|
+
setLoading(false);
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
}, [stateManager, options?.facetBy, options?.maxFacetValues, options?.disjunctiveFacets?.join(',')]);
|
|
3305
|
+
// Refetch when query or refinements change
|
|
3306
|
+
React.useEffect(() => {
|
|
3307
|
+
if (!autoFetch)
|
|
3308
|
+
return;
|
|
3309
|
+
const unsubscribe = stateManager.subscribe((_state) => {
|
|
3310
|
+
fetchFilters();
|
|
3311
|
+
});
|
|
3312
|
+
return unsubscribe;
|
|
3313
|
+
}, [stateManager, autoFetch, fetchFilters]);
|
|
3314
|
+
// Fetch schema once on mount
|
|
3315
|
+
React.useEffect(() => {
|
|
3316
|
+
stateManager.getFiltersSchema().then((response) => {
|
|
3317
|
+
if (mountedRef.current) {
|
|
3318
|
+
setSchema(response);
|
|
3319
|
+
}
|
|
3320
|
+
}).catch(() => {
|
|
3321
|
+
// Schema fetch failure is non-critical
|
|
3322
|
+
});
|
|
3323
|
+
}, [stateManager]);
|
|
3324
|
+
// Cleanup
|
|
3325
|
+
React.useEffect(() => {
|
|
3326
|
+
return () => {
|
|
3327
|
+
mountedRef.current = false;
|
|
3328
|
+
};
|
|
3329
|
+
}, []);
|
|
3330
|
+
const searchFacetValues = React.useCallback((facetName, query) => stateManager.searchFacetValues(facetName, query), [stateManager]);
|
|
3331
|
+
const refetch = React.useCallback(async () => {
|
|
3332
|
+
await fetchFilters();
|
|
3333
|
+
}, [fetchFilters]);
|
|
3334
|
+
return { filters, schema, loading, error, searchFacetValues, refetch };
|
|
3335
|
+
};
|
|
3336
|
+
|
|
3014
3337
|
/**
|
|
3015
3338
|
* RangeSlider Component
|
|
3016
3339
|
*
|
|
@@ -3327,10 +3650,12 @@ const CSS_VAR_DEFAULTS = {
|
|
|
3327
3650
|
// ---------------------------------------------------------------------------
|
|
3328
3651
|
// Component
|
|
3329
3652
|
// ---------------------------------------------------------------------------
|
|
3330
|
-
const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, renderFacet, renderFacetItem, maxItems = 10, showMore = true, className, style, theme: customTheme, variant = 'checkbox', searchable = false, showCounts = true, colorMap, defaultCollapsed = false, size = 'medium', facetRanges, }) => {
|
|
3653
|
+
const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, renderFacet, renderFacetItem, maxItems = 10, showMore = true, className, style, theme: customTheme, variant = 'checkbox', searchable = false, showCounts = true, colorMap, defaultCollapsed = false, size = 'medium', facetRanges, useFiltersApi = false, disjunctiveFacets, }) => {
|
|
3331
3654
|
const { theme } = useSearchContext();
|
|
3332
3655
|
const { results: stateResults, refinements, addRefinement, removeRefinement } = useSearchState();
|
|
3333
3656
|
const facetsTheme = customTheme || {};
|
|
3657
|
+
// Use dedicated Filters API when useFiltersApi is enabled
|
|
3658
|
+
const { filters: filtersApiData, searchFacetValues } = useFilters(useFiltersApi ? { disjunctiveFacets } : { autoFetch: false });
|
|
3334
3659
|
// expandedFacets is used for "Show more/less" in checkbox/color-swatch variants
|
|
3335
3660
|
// AND for collapse/expand in collapsible variant.
|
|
3336
3661
|
const [expandedFacets, setExpandedFacets] = React.useState({});
|
|
@@ -3338,11 +3663,27 @@ const Facets = ({ results: resultsProp, facets: facetsProp, onFacetChange, rende
|
|
|
3338
3663
|
// Use results from prop if provided, otherwise from state manager
|
|
3339
3664
|
const results = resultsProp || stateResults;
|
|
3340
3665
|
// -------------------------------------------------------------------
|
|
3341
|
-
// Extract facets from results
|
|
3666
|
+
// Extract facets from results (or Filters API)
|
|
3342
3667
|
// -------------------------------------------------------------------
|
|
3343
3668
|
const extractFacets = () => {
|
|
3344
3669
|
if (facetsProp)
|
|
3345
3670
|
return facetsProp;
|
|
3671
|
+
// When using Filters API, convert FilterField[] to Facet[]
|
|
3672
|
+
if (useFiltersApi && filtersApiData.length > 0) {
|
|
3673
|
+
log.verbose('Facets: Using Filters API data', { filterCount: filtersApiData.length });
|
|
3674
|
+
return filtersApiData.map((filter) => ({
|
|
3675
|
+
field: filter.field || '',
|
|
3676
|
+
label: filter.field || '',
|
|
3677
|
+
items: (filter.values || []).map((v) => ({
|
|
3678
|
+
value: v.value || '',
|
|
3679
|
+
count: v.count || 0,
|
|
3680
|
+
selected: refinements.some(r => r.field === filter.field && r.value === v.value),
|
|
3681
|
+
})),
|
|
3682
|
+
stats: filter.stats?.min != null && filter.stats?.max != null
|
|
3683
|
+
? { min: filter.stats.min, max: filter.stats.max, avg: filter.stats.avg, sum: filter.stats.sum }
|
|
3684
|
+
: undefined,
|
|
3685
|
+
}));
|
|
3686
|
+
}
|
|
3346
3687
|
// Try to get facets from various locations in the response
|
|
3347
3688
|
// Primary location: data.facets (widget_mode response structure)
|
|
3348
3689
|
const rawFacets = results?.data?.facets
|
|
@@ -4066,8 +4407,8 @@ const Z_INDEX$1 = {
|
|
|
4066
4407
|
// ---------------------------------------------------------------------------
|
|
4067
4408
|
// Component
|
|
4068
4409
|
// ---------------------------------------------------------------------------
|
|
4069
|
-
const FacetDropdown = ({ field, placeholder = 'All Categories', className, theme: customTheme = {}, onChange, value: controlledValue, maxOptions = 10, showCounts = true, options: providedOptions, client: providedClient, applyFilter = true, navigateOnSelect = false, searchPageUrl = '/search', }) => {
|
|
4070
|
-
const { client: contextClient, theme } = useSearchContext();
|
|
4410
|
+
const FacetDropdown = ({ field, placeholder = 'All Categories', className, theme: customTheme = {}, onChange, value: controlledValue, maxOptions = 10, showCounts = true, options: providedOptions, client: providedClient, applyFilter = true, navigateOnSelect = false, searchPageUrl = '/search', useFiltersApi = false, }) => {
|
|
4411
|
+
const { client: contextClient, theme, stateManager } = useSearchContext();
|
|
4071
4412
|
const { addRefinement, removeRefinement, refinements, search } = useSearchState();
|
|
4072
4413
|
const client = providedClient || contextClient;
|
|
4073
4414
|
const [open, setOpen] = React.useState(false);
|
|
@@ -4085,30 +4426,52 @@ const FacetDropdown = ({ field, placeholder = 'All Categories', className, theme
|
|
|
4085
4426
|
if (providedOptions || !client)
|
|
4086
4427
|
return;
|
|
4087
4428
|
try {
|
|
4088
|
-
log.verbose('FacetDropdown: Fetching facet values', { field });
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
count:
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4429
|
+
log.verbose('FacetDropdown: Fetching facet values', { field, useFiltersApi });
|
|
4430
|
+
if (useFiltersApi) {
|
|
4431
|
+
// Use dedicated Filters API via state manager
|
|
4432
|
+
const response = await stateManager.fetchFilters({
|
|
4433
|
+
facetBy: field,
|
|
4434
|
+
maxFacetValues: 100,
|
|
4435
|
+
});
|
|
4436
|
+
const targetFilter = (response?.filters || []).find((f) => f.field === field);
|
|
4437
|
+
if (targetFilter && targetFilter.values) {
|
|
4438
|
+
const options = targetFilter.values.map((v) => ({
|
|
4439
|
+
value: v.value || '',
|
|
4440
|
+
count: v.count || 0,
|
|
4441
|
+
label: v.value || '',
|
|
4442
|
+
}));
|
|
4443
|
+
setFacetOptions(options);
|
|
4444
|
+
log.verbose('FacetDropdown: Facet options loaded via Filters API', { field, count: options.length });
|
|
4445
|
+
}
|
|
4446
|
+
else {
|
|
4447
|
+
log.verbose('FacetDropdown: No filter data found', { field });
|
|
4448
|
+
setFacetOptions([]);
|
|
4449
|
+
}
|
|
4108
4450
|
}
|
|
4109
4451
|
else {
|
|
4110
|
-
|
|
4111
|
-
|
|
4452
|
+
// Fallback: use search API with per_page=0
|
|
4453
|
+
const response = await client.search('*', {
|
|
4454
|
+
facet_by: field,
|
|
4455
|
+
max_facet_values: 100,
|
|
4456
|
+
per_page: 0,
|
|
4457
|
+
});
|
|
4458
|
+
log.verbose('FacetDropdown: Search response', { response });
|
|
4459
|
+
const facetsData = response?.data?.facets || response?.facets;
|
|
4460
|
+
const facets = Array.isArray(facetsData) ? facetsData : [];
|
|
4461
|
+
const targetFacet = facets.find((f) => f.field_name === field || f.field === field);
|
|
4462
|
+
if (targetFacet && targetFacet.counts) {
|
|
4463
|
+
const options = targetFacet.counts.map((count) => ({
|
|
4464
|
+
value: count.value,
|
|
4465
|
+
count: count.count,
|
|
4466
|
+
label: count.value,
|
|
4467
|
+
}));
|
|
4468
|
+
setFacetOptions(options);
|
|
4469
|
+
log.verbose('FacetDropdown: Facet options loaded', { field, count: options.length });
|
|
4470
|
+
}
|
|
4471
|
+
else {
|
|
4472
|
+
log.verbose('FacetDropdown: No facet data found', { field, facets });
|
|
4473
|
+
setFacetOptions([]);
|
|
4474
|
+
}
|
|
4112
4475
|
}
|
|
4113
4476
|
}
|
|
4114
4477
|
catch (error) {
|
|
@@ -4119,7 +4482,7 @@ const FacetDropdown = ({ field, placeholder = 'All Categories', className, theme
|
|
|
4119
4482
|
});
|
|
4120
4483
|
setFacetOptions([]);
|
|
4121
4484
|
}
|
|
4122
|
-
}, [field, client, providedOptions]);
|
|
4485
|
+
}, [field, client, providedOptions, useFiltersApi]);
|
|
4123
4486
|
React.useEffect(() => {
|
|
4124
4487
|
if (!providedOptions && client) {
|
|
4125
4488
|
fetchFacetValues();
|
|
@@ -4754,15 +5117,9 @@ const ClearRefinements = ({ clearsQuery = false, resetLabel = 'Clear all filters
|
|
|
4754
5117
|
const SearchLayout = ({ sidebar, children, header, footer, sidebarWidth = '300px', className, style, theme: customTheme, showSidebarOnMobile = false, }) => {
|
|
4755
5118
|
const { theme } = useSearchContext();
|
|
4756
5119
|
const layoutTheme = customTheme || {};
|
|
4757
|
-
const
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
setIsMobile(window.innerWidth <= 768);
|
|
4761
|
-
};
|
|
4762
|
-
checkMobile();
|
|
4763
|
-
window.addEventListener('resize', checkMobile);
|
|
4764
|
-
return () => window.removeEventListener('resize', checkMobile);
|
|
4765
|
-
}, []);
|
|
5120
|
+
const isMobile = useIsMobile();
|
|
5121
|
+
const responsivePadding = useResponsivePadding(parseInt(theme.spacing.medium) || 16);
|
|
5122
|
+
const responsiveGap = useResponsiveGap(parseInt(theme.spacing.large) || 24);
|
|
4766
5123
|
return (React.createElement("div", { className: clsx(layoutTheme.container, className), style: {
|
|
4767
5124
|
display: 'flex',
|
|
4768
5125
|
flexDirection: 'column',
|
|
@@ -4773,7 +5130,7 @@ const SearchLayout = ({ sidebar, children, header, footer, sidebarWidth = '300px
|
|
|
4773
5130
|
...style,
|
|
4774
5131
|
} },
|
|
4775
5132
|
header && (React.createElement("header", { className: layoutTheme.header, style: {
|
|
4776
|
-
padding:
|
|
5133
|
+
padding: responsivePadding,
|
|
4777
5134
|
borderBottom: `1px solid ${theme.colors.border}`,
|
|
4778
5135
|
backgroundColor: theme.colors.background,
|
|
4779
5136
|
} }, header)),
|
|
@@ -4782,15 +5139,16 @@ const SearchLayout = ({ sidebar, children, header, footer, sidebarWidth = '300px
|
|
|
4782
5139
|
flex: 1,
|
|
4783
5140
|
width: '100%',
|
|
4784
5141
|
maxWidth: '100%',
|
|
4785
|
-
|
|
4786
|
-
|
|
5142
|
+
flexDirection: isMobile ? 'column' : 'row',
|
|
5143
|
+
gap: responsiveGap,
|
|
5144
|
+
padding: responsivePadding,
|
|
4787
5145
|
backgroundColor: theme.colors.background,
|
|
4788
5146
|
color: theme.colors.text,
|
|
4789
|
-
overflow: 'hidden',
|
|
5147
|
+
overflow: isMobile ? 'visible' : 'hidden',
|
|
4790
5148
|
} },
|
|
4791
5149
|
sidebar && (!isMobile || showSidebarOnMobile) && (React.createElement("aside", { className: layoutTheme.sidebar, style: {
|
|
4792
|
-
width: sidebarWidth,
|
|
4793
|
-
minWidth: sidebarWidth,
|
|
5150
|
+
width: isMobile ? '100%' : sidebarWidth,
|
|
5151
|
+
minWidth: isMobile ? '100%' : sidebarWidth,
|
|
4794
5152
|
flexShrink: 0,
|
|
4795
5153
|
} }, sidebar)),
|
|
4796
5154
|
React.createElement("main", { className: layoutTheme.main, style: {
|
|
@@ -4803,7 +5161,7 @@ const SearchLayout = ({ sidebar, children, header, footer, sidebarWidth = '300px
|
|
|
4803
5161
|
overflow: 'auto',
|
|
4804
5162
|
} }, children)),
|
|
4805
5163
|
footer && (React.createElement("footer", { className: layoutTheme.footer, style: {
|
|
4806
|
-
padding:
|
|
5164
|
+
padding: responsivePadding,
|
|
4807
5165
|
borderTop: `1px solid ${theme.colors.border}`,
|
|
4808
5166
|
backgroundColor: theme.colors.background,
|
|
4809
5167
|
} }, footer))));
|
|
@@ -5084,7 +5442,7 @@ const HitsPerPage = ({ items, onHitsPerPageChange, renderSelect, className, styl
|
|
|
5084
5442
|
* Displays search results with infinite scroll or "Show More" button
|
|
5085
5443
|
* Accumulates results as user loads more pages
|
|
5086
5444
|
*/
|
|
5087
|
-
const InfiniteHits = ({ renderHit, renderEmpty,
|
|
5445
|
+
const InfiniteHits = ({ renderHit, renderEmpty, renderShowMore, showMoreButton = true, useInfiniteScroll = false, scrollThreshold = 0.1, fieldMapping, showMoreLabel = 'Show more', onHitClick, className, style, theme: customTheme, syncWithState = true, }) => {
|
|
5088
5446
|
const { theme, stateManager } = useSearchContext();
|
|
5089
5447
|
const { results, loading, currentPage, setPage } = useSearchState();
|
|
5090
5448
|
const infiniteHitsTheme = customTheme || {};
|
|
@@ -5216,12 +5574,6 @@ const InfiniteHits = ({ renderHit, renderEmpty, showInitialLoading = false, rend
|
|
|
5216
5574
|
textAlign: 'center',
|
|
5217
5575
|
color: theme.colors.textSecondary,
|
|
5218
5576
|
} }, "No results found"));
|
|
5219
|
-
// Default loading state
|
|
5220
|
-
const defaultRenderLoading = () => (React.createElement("div", { className: infiniteHitsTheme.loading, style: {
|
|
5221
|
-
padding: theme.spacing.medium,
|
|
5222
|
-
textAlign: 'center',
|
|
5223
|
-
color: theme.colors.textSecondary,
|
|
5224
|
-
} }, loadingLabel));
|
|
5225
5577
|
// Default "Show More" button
|
|
5226
5578
|
const defaultRenderShowMore = () => (React.createElement("button", { type: "button", onClick: handleShowMore, disabled: isLastPage || isLoadingMore, className: clsx(infiniteHitsTheme.loadMore, (isLastPage || isLoadingMore) && infiniteHitsTheme.loadMoreDisabled), style: {
|
|
5227
5579
|
display: 'block',
|
|
@@ -5238,11 +5590,7 @@ const InfiniteHits = ({ renderHit, renderEmpty, showInitialLoading = false, rend
|
|
|
5238
5590
|
: theme.borderRadius.medium,
|
|
5239
5591
|
cursor: isLastPage || isLoadingMore ? 'not-allowed' : 'pointer',
|
|
5240
5592
|
transition: theme.transitions?.fast || '150ms ease-in-out',
|
|
5241
|
-
} },
|
|
5242
|
-
// Initial loading state (only when showInitialLoading: default no loading screen)
|
|
5243
|
-
if (loading && accumulatedHits.length === 0 && showInitialLoading) {
|
|
5244
|
-
return (React.createElement("div", { className: clsx(infiniteHitsTheme.root, className), style: style }, renderLoading ? renderLoading() : defaultRenderLoading()));
|
|
5245
|
-
}
|
|
5593
|
+
} }, isLastPage ? 'No more results' : showMoreLabel));
|
|
5246
5594
|
if (loading && accumulatedHits.length === 0) {
|
|
5247
5595
|
return React.createElement("div", { className: clsx(infiniteHitsTheme.root, className), style: style });
|
|
5248
5596
|
}
|
|
@@ -5255,7 +5603,6 @@ const InfiniteHits = ({ renderHit, renderEmpty, showInitialLoading = false, rend
|
|
|
5255
5603
|
showMoreButton && !useInfiniteScroll && !isLastPage && (renderShowMore
|
|
5256
5604
|
? renderShowMore({ isLoading: isLoadingMore, isLastPage, onClick: handleShowMore })
|
|
5257
5605
|
: defaultRenderShowMore()),
|
|
5258
|
-
isLoadingMore && (renderLoading ? renderLoading() : defaultRenderLoading()),
|
|
5259
5606
|
useInfiniteScroll && !isLastPage && (React.createElement("div", { ref: sentinelRef, className: infiniteHitsTheme.sentinel, style: { height: '1px', visibility: 'hidden' }, "aria-hidden": "true" }))));
|
|
5260
5607
|
};
|
|
5261
5608
|
|
|
@@ -6047,16 +6394,11 @@ const MobileFiltersButton = ({ onClick, text = 'Filters', showCount = true, clas
|
|
|
6047
6394
|
* - FrequentlyBoughtTogether: Bundle recommendations
|
|
6048
6395
|
* - RecentlyViewed: User's recently viewed items
|
|
6049
6396
|
*/
|
|
6050
|
-
const RelatedProducts = ({ productId, items: itemsProp, loading: loadingProp = false,
|
|
6397
|
+
const RelatedProducts = ({ productId, items: itemsProp, loading: loadingProp = false, title = 'Related Products', maxItems = 6, renderItem, onItemClick, className, style, theme: customTheme, layout = 'horizontal', currencySymbol = '$', }) => {
|
|
6051
6398
|
const { theme } = useSearchContext();
|
|
6052
|
-
const recommendationTheme = customTheme || {};
|
|
6053
6399
|
// If items are provided, use them directly
|
|
6054
6400
|
const items = itemsProp?.slice(0, maxItems) || [];
|
|
6055
6401
|
const loading = loadingProp;
|
|
6056
|
-
if (loading && items.length === 0 && showLoadingState) {
|
|
6057
|
-
return (React.createElement("div", { className: clsx(recommendationTheme.root, className), style: style },
|
|
6058
|
-
React.createElement("div", { className: recommendationTheme.loading, style: getLoadingStyle(theme) }, "Loading related products...")));
|
|
6059
|
-
}
|
|
6060
6402
|
if (loading && items.length === 0)
|
|
6061
6403
|
return null;
|
|
6062
6404
|
if (items.length === 0) {
|
|
@@ -6064,15 +6406,10 @@ const RelatedProducts = ({ productId, items: itemsProp, loading: loadingProp = f
|
|
|
6064
6406
|
}
|
|
6065
6407
|
return (React.createElement(RecommendationSection, { title: title, items: items, renderItem: renderItem, onItemClick: onItemClick, className: className, style: style, theme: customTheme, layout: layout, currencySymbol: currencySymbol }));
|
|
6066
6408
|
};
|
|
6067
|
-
const TrendingItems = ({ items: itemsProp, loading: loadingProp = false,
|
|
6409
|
+
const TrendingItems = ({ items: itemsProp, loading: loadingProp = false, title = 'Trending Now', maxItems = 8, renderItem, onItemClick, className, style, theme: customTheme, layout = 'horizontal', currencySymbol = '$', }) => {
|
|
6068
6410
|
const { theme } = useSearchContext();
|
|
6069
|
-
const recommendationTheme = customTheme || {};
|
|
6070
6411
|
const items = itemsProp?.slice(0, maxItems) || [];
|
|
6071
6412
|
const loading = loadingProp;
|
|
6072
|
-
if (loading && items.length === 0 && showLoadingState) {
|
|
6073
|
-
return (React.createElement("div", { className: clsx(recommendationTheme.root, className), style: style },
|
|
6074
|
-
React.createElement("div", { className: recommendationTheme.loading, style: getLoadingStyle(theme) }, "Loading trending items...")));
|
|
6075
|
-
}
|
|
6076
6413
|
if (loading && items.length === 0)
|
|
6077
6414
|
return null;
|
|
6078
6415
|
if (items.length === 0) {
|
|
@@ -6080,7 +6417,7 @@ const TrendingItems = ({ items: itemsProp, loading: loadingProp = false, showLoa
|
|
|
6080
6417
|
}
|
|
6081
6418
|
return (React.createElement(RecommendationSection, { title: title, items: items, renderItem: renderItem, onItemClick: onItemClick, className: className, style: style, theme: customTheme, layout: layout, currencySymbol: currencySymbol }));
|
|
6082
6419
|
};
|
|
6083
|
-
const FrequentlyBoughtTogether = ({ productId, items: itemsProp, loading: loadingProp = false,
|
|
6420
|
+
const FrequentlyBoughtTogether = ({ productId, items: itemsProp, loading: loadingProp = false, title = 'Frequently Bought Together', maxItems = 4, renderItem, onItemClick, className, style, theme: customTheme, layout = 'horizontal', currencySymbol = '$', showAddAllButton = true, onAddAll, }) => {
|
|
6084
6421
|
const { theme } = useSearchContext();
|
|
6085
6422
|
const recommendationTheme = customTheme || {};
|
|
6086
6423
|
const items = itemsProp?.slice(0, maxItems) || [];
|
|
@@ -6093,10 +6430,6 @@ const FrequentlyBoughtTogether = ({ productId, items: itemsProp, loading: loadin
|
|
|
6093
6430
|
return sum + price;
|
|
6094
6431
|
}, 0);
|
|
6095
6432
|
}, [items]);
|
|
6096
|
-
if (loading && items.length === 0 && showLoadingState) {
|
|
6097
|
-
return (React.createElement("div", { className: clsx(recommendationTheme.root, className), style: style },
|
|
6098
|
-
React.createElement("div", { className: recommendationTheme.loading, style: getLoadingStyle(theme) }, "Loading recommendations...")));
|
|
6099
|
-
}
|
|
6100
6433
|
if (loading && items.length === 0)
|
|
6101
6434
|
return null;
|
|
6102
6435
|
if (items.length === 0) {
|
|
@@ -6303,21 +6636,13 @@ const DefaultRecommendationItem = ({ item, theme: recommendationTheme, currencyS
|
|
|
6303
6636
|
currencySymbol,
|
|
6304
6637
|
typeof price === 'number' ? price.toFixed(2) : price)))));
|
|
6305
6638
|
};
|
|
6306
|
-
// Helper function
|
|
6307
|
-
function getLoadingStyle(theme) {
|
|
6308
|
-
return {
|
|
6309
|
-
padding: theme.spacing.large,
|
|
6310
|
-
textAlign: 'center',
|
|
6311
|
-
color: theme.colors.textSecondary,
|
|
6312
|
-
};
|
|
6313
|
-
}
|
|
6314
6639
|
|
|
6315
6640
|
/**
|
|
6316
6641
|
* QuerySuggestions Component
|
|
6317
6642
|
*
|
|
6318
6643
|
* Standalone component for displaying query suggestions
|
|
6319
6644
|
*/
|
|
6320
|
-
const QuerySuggestions = ({ query = '', maxSuggestions = 10, debounceMs = 300, minQueryLength = 2, onSuggestionClick, renderSuggestion,
|
|
6645
|
+
const QuerySuggestions = ({ query = '', maxSuggestions = 10, debounceMs = 300, minQueryLength = 2, onSuggestionClick, renderSuggestion, renderEmpty, showTitle = false, title = 'Suggestions', className, style, theme: customTheme, }) => {
|
|
6321
6646
|
const { client, theme } = useSearchContext();
|
|
6322
6647
|
const [selectedIndex, setSelectedIndex] = React.useState(-1);
|
|
6323
6648
|
const { suggestions, loading, error } = useQuerySuggestions({
|
|
@@ -6335,11 +6660,6 @@ const QuerySuggestions = ({ query = '', maxSuggestions = 10, debounceMs = 300, m
|
|
|
6335
6660
|
"(",
|
|
6336
6661
|
suggestion.count,
|
|
6337
6662
|
")"))));
|
|
6338
|
-
const defaultRenderLoading = () => (React.createElement("div", { style: {
|
|
6339
|
-
padding: theme.spacing.medium,
|
|
6340
|
-
textAlign: 'center',
|
|
6341
|
-
color: theme.colors.text,
|
|
6342
|
-
} }, "Loading suggestions..."));
|
|
6343
6663
|
const defaultRenderEmpty = () => (React.createElement("div", { style: {
|
|
6344
6664
|
padding: theme.spacing.medium,
|
|
6345
6665
|
textAlign: 'center',
|
|
@@ -6354,17 +6674,6 @@ const QuerySuggestions = ({ query = '', maxSuggestions = 10, debounceMs = 300, m
|
|
|
6354
6674
|
if (query.length < minQueryLength) {
|
|
6355
6675
|
return null;
|
|
6356
6676
|
}
|
|
6357
|
-
// When loading with no previous results, show loading only if showLoadingState (default: show previous results, no loading screen)
|
|
6358
|
-
if (loading && displayedSuggestions.length === 0 && showLoadingState) {
|
|
6359
|
-
return (React.createElement("div", { className: clsx(suggestionsTheme.container, className), style: style },
|
|
6360
|
-
showTitle && (React.createElement("div", { className: suggestionsTheme.title, style: {
|
|
6361
|
-
fontSize: theme.typography.fontSize.large,
|
|
6362
|
-
fontWeight: 'bold',
|
|
6363
|
-
marginBottom: theme.spacing.medium,
|
|
6364
|
-
color: theme.colors.text,
|
|
6365
|
-
} }, title)),
|
|
6366
|
-
renderLoading ? renderLoading() : defaultRenderLoading()));
|
|
6367
|
-
}
|
|
6368
6677
|
if (error || (!loading && displayedSuggestions.length === 0)) {
|
|
6369
6678
|
return (React.createElement("div", { className: clsx(suggestionsTheme.container, className), style: style },
|
|
6370
6679
|
showTitle && (React.createElement("div", { className: suggestionsTheme.title, style: {
|
|
@@ -6906,15 +7215,6 @@ const defaultStyles$1 = {
|
|
|
6906
7215
|
removeButtonVisible: {
|
|
6907
7216
|
opacity: 1,
|
|
6908
7217
|
},
|
|
6909
|
-
loadingState: {
|
|
6910
|
-
display: 'flex',
|
|
6911
|
-
alignItems: 'center',
|
|
6912
|
-
justifyContent: 'center',
|
|
6913
|
-
padding: '24px 16px',
|
|
6914
|
-
color: 'var(--seekora-text-secondary, inherit)',
|
|
6915
|
-
fontSize: '14px',
|
|
6916
|
-
gap: '8px',
|
|
6917
|
-
},
|
|
6918
7218
|
emptyState: {
|
|
6919
7219
|
display: 'flex',
|
|
6920
7220
|
flexDirection: 'column',
|
|
@@ -6968,14 +7268,11 @@ const ClockIcon$6 = ({ className, style }) => (React.createElement("svg", { clas
|
|
|
6968
7268
|
React.createElement("path", { fillRule: "evenodd", 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", clipRule: "evenodd" })));
|
|
6969
7269
|
const CloseIcon$1 = ({ className, style }) => (React.createElement("svg", { className: className, style: style, viewBox: "0 0 20 20", fill: "currentColor", width: "16", height: "16" },
|
|
6970
7270
|
React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" })));
|
|
6971
|
-
const LoadingSpinner = ({ style }) => (React.createElement("svg", { style: { animation: 'spin 1s linear infinite', ...style }, viewBox: "0 0 24 24", width: "20", height: "20" },
|
|
6972
|
-
React.createElement("circle", { cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "3", fill: "none", opacity: "0.25" }),
|
|
6973
|
-
React.createElement("path", { fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })));
|
|
6974
7271
|
// ============================================================================
|
|
6975
7272
|
// Component
|
|
6976
7273
|
// ============================================================================
|
|
6977
7274
|
const QuerySuggestionsDropdown = React.forwardRef(function QuerySuggestionsDropdown(props, ref) {
|
|
6978
|
-
const { query, isOpen = true, maxSuggestions = 8, minQueryLength = 1, debounceMs = 200, showRecentSearches = true, maxRecentSearches = 5, showCounts = true,
|
|
7275
|
+
const { query, isOpen = true, maxSuggestions = 8, minQueryLength = 1, debounceMs = 200, showRecentSearches = true, maxRecentSearches = 5, showCounts = true, showEmptyState = true, highlight = { enabled: true, preTag: '<mark>', postTag: '</mark>' }, keyboardNav = { enabled: true }, animation = { enabled: true, duration: 150, entrance: 'fade' }, classNames = {}, style, renderSuggestion, renderRecentSearch, renderEmpty, footer, position = 'absolute', width = '100%', zIndex = 1000, closeOnClickOutside = true, closeOnEscape = true, ariaLabel = 'Search suggestions', onSuggestionSelect, onRecentSearchClick, onRecentSearchRemove, onOpen, onClose, onNavigate, } = props;
|
|
6979
7276
|
const { client, theme } = useSearchContext();
|
|
6980
7277
|
const containerRef = React.useRef(null);
|
|
6981
7278
|
const [activeIndex, setActiveIndex] = React.useState(-1);
|
|
@@ -7157,9 +7454,6 @@ const QuerySuggestionsDropdown = React.forwardRef(function QuerySuggestionsDropd
|
|
|
7157
7454
|
...animationStyle,
|
|
7158
7455
|
...style,
|
|
7159
7456
|
} },
|
|
7160
|
-
loading && showLoading && (React.createElement("div", { className: classNames.loadingState, style: defaultStyles$1.loadingState }, renderLoading ? renderLoading() : (React.createElement(React.Fragment, null,
|
|
7161
|
-
React.createElement(LoadingSpinner, null),
|
|
7162
|
-
React.createElement("span", null, "Searching..."))))),
|
|
7163
7457
|
showRecent && (React.createElement("div", { className: clsx('seekora-suggestions-section', classNames.section, classNames.recentSearches) },
|
|
7164
7458
|
React.createElement("div", { className: classNames.sectionTitle, style: defaultStyles$1.sectionTitle }, "Recent Searches"),
|
|
7165
7459
|
recentSearches.slice(0, maxRecentSearches).map((search, index) => {
|
|
@@ -7406,14 +7700,6 @@ const styles$2 = {
|
|
|
7406
7700
|
fontSize: '12px',
|
|
7407
7701
|
color: 'var(--seekora-text-secondary, inherit)',
|
|
7408
7702
|
},
|
|
7409
|
-
loadingOverlay: {
|
|
7410
|
-
position: 'absolute',
|
|
7411
|
-
inset: 0,
|
|
7412
|
-
display: 'flex',
|
|
7413
|
-
alignItems: 'center',
|
|
7414
|
-
justifyContent: 'center',
|
|
7415
|
-
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
7416
|
-
},
|
|
7417
7703
|
emptyState: {
|
|
7418
7704
|
padding: '32px 16px',
|
|
7419
7705
|
textAlign: 'center',
|
|
@@ -7441,7 +7727,7 @@ const CloseIcon = () => (React.createElement("svg", { viewBox: "0 0 20 20", fill
|
|
|
7441
7727
|
// Component
|
|
7442
7728
|
// ============================================================================
|
|
7443
7729
|
const RichQuerySuggestions = React.forwardRef(function RichQuerySuggestions(props, ref) {
|
|
7444
|
-
const { query, isOpen = true, sections = DEFAULT_SECTIONS, maxSuggestionsPerSection = 8, minQueryLength = 0, debounceMs = 200, includeDropdownRecommendations = true, includeDropdownProductList = true, includeFilteredTabs = true, includeCategories = true, maxCategories = 3, showCounts = true, showCategoryCounts = true, showSectionHeaders = true, classNames = {}, style, renderSuggestion, renderCategory, renderTrendingItem, renderRecentItem, header, footer, width = '100%', maxHeight = '480px', zIndex = 1000, ariaLabel = 'Search suggestions', analyticsTags, onSuggestionSelect, onCategoryClick, onRecentSearchClick, onRecentSearchRemove, onViewAllClick, onOpen, onClose,
|
|
7730
|
+
const { query, isOpen = true, sections = DEFAULT_SECTIONS, maxSuggestionsPerSection = 8, minQueryLength = 0, debounceMs = 200, includeDropdownRecommendations = true, includeDropdownProductList = true, includeFilteredTabs = true, includeCategories = true, maxCategories = 3, showCounts = true, showCategoryCounts = true, showSectionHeaders = true, classNames = {}, style, renderSuggestion, renderCategory, renderTrendingItem, renderRecentItem, header, footer, width = '100%', maxHeight = '480px', zIndex = 1000, ariaLabel = 'Search suggestions', analyticsTags, onSuggestionSelect, onCategoryClick, onRecentSearchClick, onRecentSearchRemove, onViewAllClick, onOpen, onClose, } = props;
|
|
7445
7731
|
const { client } = useSearchContext();
|
|
7446
7732
|
const containerRef = React.useRef(null);
|
|
7447
7733
|
const [activeIndex, setActiveIndex] = React.useState(-1);
|
|
@@ -7660,7 +7946,6 @@ const RichQuerySuggestions = React.forwardRef(function RichQuerySuggestions(prop
|
|
|
7660
7946
|
} },
|
|
7661
7947
|
header && React.createElement("div", { style: styles$2.header }, header),
|
|
7662
7948
|
React.createElement("div", { style: { ...styles$2.content, maxHeight } },
|
|
7663
|
-
loading && showLoadingOverlay && (React.createElement("div", { style: styles$2.loadingOverlay }, renderLoading ? renderLoading() : React.createElement("span", null, "Loading..."))),
|
|
7664
7949
|
enabledSections.map((section, index) => {
|
|
7665
7950
|
let content = null;
|
|
7666
7951
|
switch (section.id) {
|
|
@@ -8534,7 +8819,7 @@ const ClearIcon$1 = () => (React.createElement("svg", { viewBox: "0 0 20 20", fi
|
|
|
8534
8819
|
// Component
|
|
8535
8820
|
// ============================================================================
|
|
8536
8821
|
const SearchBarWithSuggestions = React.forwardRef(function SearchBarWithSuggestions(props, ref) {
|
|
8537
|
-
const { variant = 'classic', placeholder = '
|
|
8822
|
+
const { variant = 'classic', placeholder = 'Powered by Seekora', initialQuery = '', value, onQueryChange, onSearch, onSuggestionSelect, onProductClick, showSearchButton = false, searchButtonText = 'Search', showClearButton = true, autoFocus = false, minQueryLength = 1, maxSuggestions = 8, maxProducts = 8, debounceMs = 200, showRecentSearches = true, showTrendingOnEmpty = true, includeDropdownRecommendations = false, filteredTabs, enableAnalytics = true, analyticsTags, includeFacets, includeCategories, dropdownWidth, dropdownMaxHeight, classNames = {}, style, inputStyle, ariaLabel = 'Search', } = props;
|
|
8538
8823
|
const { client } = useSearchContext();
|
|
8539
8824
|
const inputRef = React.useRef(null);
|
|
8540
8825
|
const dropdownRef = React.useRef(null);
|
|
@@ -8989,7 +9274,7 @@ const inputStyles = {
|
|
|
8989
9274
|
color: 'var(--seekora-text-primary, inherit)',
|
|
8990
9275
|
fontFamily: 'inherit',
|
|
8991
9276
|
};
|
|
8992
|
-
function SearchInput({ placeholder = '
|
|
9277
|
+
function SearchInput({ placeholder = 'Powered by Seekora', autoFocus = false, showClearButton = true, closeOnBlur = true, leftIcon, className, style, inputClassName, inputStyle, ariaLabel = 'Search', }) {
|
|
8993
9278
|
const { query, setQuery, isOpen, setIsOpen, navigateNext, navigatePrev, selectActive, close, } = useSuggestionsContext();
|
|
8994
9279
|
const inputRef = React.useRef(null);
|
|
8995
9280
|
const handleFocus = React.useCallback(() => {
|
|
@@ -9197,17 +9482,19 @@ const defaultItemStyle = {
|
|
|
9197
9482
|
whiteSpace: 'nowrap',
|
|
9198
9483
|
};
|
|
9199
9484
|
function SuggestionItem({ suggestion, index, isActive, onSelect, className, style, enableHighlightMarkup = true, highlightMarkupOptions, renderHighlight, }) {
|
|
9485
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
9200
9486
|
const displayText = suggestion.highlightedQuery ?? suggestion.query;
|
|
9201
9487
|
const content = renderHighlight != null
|
|
9202
9488
|
? renderHighlight(displayText)
|
|
9203
9489
|
: enableHighlightMarkup
|
|
9204
9490
|
? parseHighlightMarkup(displayText ?? '', highlightMarkupOptions)
|
|
9205
9491
|
: (suggestion.query ?? displayText ?? '');
|
|
9206
|
-
|
|
9492
|
+
const showHighlight = isActive || isHovered;
|
|
9493
|
+
return (React.createElement("li", { role: "option", "aria-selected": isActive, id: `seekora-suggestion-${index}`, className: clsx('seekora-suggestions-item', isActive && 'seekora-suggestions-item--active', isHovered && 'seekora-suggestions-item--hover', className), style: {
|
|
9207
9494
|
...defaultItemStyle,
|
|
9208
|
-
...(
|
|
9495
|
+
...(showHighlight ? { backgroundColor: 'var(--seekora-bg-hover, #f3f4f6)' } : {}),
|
|
9209
9496
|
...style,
|
|
9210
|
-
}, onMouseDown: (e) => {
|
|
9497
|
+
}, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onMouseDown: (e) => {
|
|
9211
9498
|
e.preventDefault();
|
|
9212
9499
|
onSelect();
|
|
9213
9500
|
} },
|
|
@@ -9226,18 +9513,12 @@ const listStyle = {
|
|
|
9226
9513
|
margin: 0,
|
|
9227
9514
|
padding: '4px 0',
|
|
9228
9515
|
};
|
|
9229
|
-
function SuggestionList({ maxItems = 10, className, style, listClassName,
|
|
9516
|
+
function SuggestionList({ maxItems = 10, className, style, listClassName, enableHighlightMarkup = true, highlightMarkupOptions, renderItem, }) {
|
|
9230
9517
|
const { suggestions, activeIndex, loading, selectSuggestion, getAllNavigableItems, } = useSuggestionsContext();
|
|
9231
9518
|
const items = suggestions.slice(0, maxItems);
|
|
9232
9519
|
const navigableItems = getAllNavigableItems();
|
|
9233
9520
|
const suggestionStartIndex = navigableItems.findIndex((n) => n.type === 'suggestion');
|
|
9234
9521
|
const activeIsInSuggestions = suggestionStartIndex >= 0 && activeIndex >= suggestionStartIndex && activeIndex < suggestionStartIndex + items.length;
|
|
9235
|
-
// When loading with no previous results, show loading only if showLoadingState (default: don't show loading screen)
|
|
9236
|
-
if (loading && items.length === 0 && showLoadingState) {
|
|
9237
|
-
if (renderLoading)
|
|
9238
|
-
return React.createElement(React.Fragment, null, renderLoading());
|
|
9239
|
-
return (React.createElement("div", { className: clsx('seekora-suggestions-loading', className), style: { padding: 16, color: 'var(--seekora-text-secondary, #6b7280)', fontSize: '0.875rem', ...style } }, "Loading..."));
|
|
9240
|
-
}
|
|
9241
9522
|
// When loading with previous results, show previous results (no loading UI)
|
|
9242
9523
|
if (items.length === 0)
|
|
9243
9524
|
return null;
|
|
@@ -9367,10 +9648,66 @@ function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, s
|
|
|
9367
9648
|
const rect = containerRef.current.getBoundingClientRect();
|
|
9368
9649
|
const bgPosX = (cursorPos.x / rect.width) * 100;
|
|
9369
9650
|
const bgPosY = (cursorPos.y / rect.height) * 100;
|
|
9651
|
+
// Calculate available space in all directions
|
|
9652
|
+
const viewportWidth = window.innerWidth;
|
|
9653
|
+
const viewportHeight = window.innerHeight;
|
|
9654
|
+
const gap = 16;
|
|
9655
|
+
const mouseBuffer = 50; // Minimum distance from mouse cursor
|
|
9656
|
+
// Calculate mouse position in viewport
|
|
9657
|
+
const mouseX = rect.left + cursorPos.x;
|
|
9658
|
+
const mouseY = rect.top + cursorPos.y;
|
|
9659
|
+
// Calculate space available in each direction
|
|
9660
|
+
const spaceRight = viewportWidth - rect.right - gap;
|
|
9661
|
+
const spaceLeft = rect.left - gap;
|
|
9662
|
+
// Determine optimal horizontal position
|
|
9663
|
+
let left;
|
|
9664
|
+
let top = rect.top;
|
|
9665
|
+
// Try right side first (default Amazon-style)
|
|
9666
|
+
if (spaceRight >= zoomPanelSize.width) {
|
|
9667
|
+
left = rect.right + gap;
|
|
9668
|
+
}
|
|
9669
|
+
// Try left side
|
|
9670
|
+
else if (spaceLeft >= zoomPanelSize.width) {
|
|
9671
|
+
left = rect.left - zoomPanelSize.width - gap;
|
|
9672
|
+
}
|
|
9673
|
+
// Not enough space on either side - center on the side with more space
|
|
9674
|
+
else if (spaceRight > spaceLeft) {
|
|
9675
|
+
left = rect.right + gap;
|
|
9676
|
+
}
|
|
9677
|
+
else {
|
|
9678
|
+
left = Math.max(gap, rect.left - zoomPanelSize.width - gap);
|
|
9679
|
+
}
|
|
9680
|
+
// Adjust vertical position to keep within viewport
|
|
9681
|
+
// Try to align with image top by default
|
|
9682
|
+
if (rect.top + zoomPanelSize.height > viewportHeight - gap) {
|
|
9683
|
+
// Panel would overflow bottom - adjust upward
|
|
9684
|
+
top = Math.max(gap, viewportHeight - zoomPanelSize.height - gap);
|
|
9685
|
+
}
|
|
9686
|
+
// Ensure panel doesn't overlap with mouse cursor
|
|
9687
|
+
const panelRight = left + zoomPanelSize.width;
|
|
9688
|
+
const panelBottom = top + zoomPanelSize.height;
|
|
9689
|
+
// Check if mouse is inside the panel area
|
|
9690
|
+
if (mouseX >= left - mouseBuffer &&
|
|
9691
|
+
mouseX <= panelRight + mouseBuffer &&
|
|
9692
|
+
mouseY >= top - mouseBuffer &&
|
|
9693
|
+
mouseY <= panelBottom + mouseBuffer) {
|
|
9694
|
+
// Mouse is too close - try to reposition
|
|
9695
|
+
// If on right side and there's space on left, flip to left
|
|
9696
|
+
if (left > rect.right && spaceLeft >= zoomPanelSize.width) {
|
|
9697
|
+
left = rect.left - zoomPanelSize.width - gap;
|
|
9698
|
+
}
|
|
9699
|
+
// If on left side and there's space on right, flip to right
|
|
9700
|
+
else if (left < rect.left && spaceRight >= zoomPanelSize.width) {
|
|
9701
|
+
left = rect.right + gap;
|
|
9702
|
+
}
|
|
9703
|
+
}
|
|
9704
|
+
// Clamp to viewport boundaries
|
|
9705
|
+
left = Math.max(gap, Math.min(left, viewportWidth - zoomPanelSize.width - gap));
|
|
9706
|
+
top = Math.max(gap, Math.min(top, viewportHeight - zoomPanelSize.height - gap));
|
|
9370
9707
|
return {
|
|
9371
|
-
position: 'fixed',
|
|
9372
|
-
top
|
|
9373
|
-
left
|
|
9708
|
+
position: 'fixed',
|
|
9709
|
+
top,
|
|
9710
|
+
left,
|
|
9374
9711
|
width: zoomPanelSize.width,
|
|
9375
9712
|
height: zoomPanelSize.height,
|
|
9376
9713
|
backgroundImage: `url(${src})`,
|
|
@@ -9407,7 +9744,6 @@ function ImageZoom({ src, alt = '', mode = 'both', zoomLevel = 2.5, className, s
|
|
|
9407
9744
|
if (!containerRef.current || !imageRef.current)
|
|
9408
9745
|
return {};
|
|
9409
9746
|
const rect = containerRef.current.getBoundingClientRect();
|
|
9410
|
-
imageRef.current.getBoundingClientRect();
|
|
9411
9747
|
return {
|
|
9412
9748
|
position: 'absolute',
|
|
9413
9749
|
width: rect.width * zoomLevel,
|
|
@@ -9630,6 +9966,11 @@ function ImageDisplay({ images, variant = 'single', alt = '', className, style,
|
|
|
9630
9966
|
const [hovering, setHovering] = React.useState(false);
|
|
9631
9967
|
const safeImages = Array.isArray(images) ? images.filter(Boolean) : [];
|
|
9632
9968
|
const current = safeImages[index] ?? safeImages[0];
|
|
9969
|
+
// Responsive values
|
|
9970
|
+
const isMobile = useIsMobile();
|
|
9971
|
+
const arrowSize = useTouchTargetSize(32); // 44px on mobile, 32px on desktop
|
|
9972
|
+
const thumbSize = isMobile ? 56 : 48; // Larger thumbnails on mobile
|
|
9973
|
+
const responsiveGap = useResponsiveGap(8);
|
|
9633
9974
|
if (safeImages.length === 0) {
|
|
9634
9975
|
return React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-placeholder', className), style: { ...imgBaseStyle, ...style }, "aria-hidden": true });
|
|
9635
9976
|
}
|
|
@@ -9666,22 +10007,24 @@ function ImageDisplay({ images, variant = 'single', alt = '', className, style,
|
|
|
9666
10007
|
return (React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-carousel', className), style: { position: 'relative', ...style } },
|
|
9667
10008
|
mainImage,
|
|
9668
10009
|
safeImages.length > 1 && (React.createElement(React.Fragment, null,
|
|
9669
|
-
React.createElement("button", { type: "button", "aria-label": "Previous", className: "seekora-img-carousel-prev", style: arrowStyle(true), onMouseDown: (e) => { e.stopPropagation(); e.preventDefault(); go(-1); }, onClick: (e) => { e.stopPropagation(); e.preventDefault(); }, onMouseEnter: (e) => { e.currentTarget.style.backgroundColor = 'var(--seekora-carousel-btn-bg-hover, rgba(255,255,255,1))'; }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = 'var(--seekora-carousel-btn-bg, rgba(255,255,255,0.9))'; } }, "\u2039"),
|
|
9670
|
-
React.createElement("button", { type: "button", "aria-label": "Next", className: "seekora-img-carousel-next", style: arrowStyle(false), onMouseDown: (e) => { e.stopPropagation(); e.preventDefault(); go(1); }, onClick: (e) => { e.stopPropagation(); e.preventDefault(); }, onMouseEnter: (e) => { e.currentTarget.style.backgroundColor = 'var(--seekora-carousel-btn-bg-hover, rgba(255,255,255,1))'; }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = 'var(--seekora-carousel-btn-bg, rgba(255,255,255,0.9))'; } }, "\u203A"),
|
|
10010
|
+
React.createElement("button", { type: "button", "aria-label": "Previous", className: "seekora-img-carousel-prev", style: arrowStyle(true, arrowSize), onMouseDown: (e) => { e.stopPropagation(); e.preventDefault(); go(-1); }, onClick: (e) => { e.stopPropagation(); e.preventDefault(); }, onMouseEnter: (e) => { e.currentTarget.style.backgroundColor = 'var(--seekora-carousel-btn-bg-hover, rgba(255,255,255,1))'; }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = 'var(--seekora-carousel-btn-bg, rgba(255,255,255,0.9))'; } }, "\u2039"),
|
|
10011
|
+
React.createElement("button", { type: "button", "aria-label": "Next", className: "seekora-img-carousel-next", style: arrowStyle(false, arrowSize), onMouseDown: (e) => { e.stopPropagation(); e.preventDefault(); go(1); }, onClick: (e) => { e.stopPropagation(); e.preventDefault(); }, onMouseEnter: (e) => { e.currentTarget.style.backgroundColor = 'var(--seekora-carousel-btn-bg-hover, rgba(255,255,255,1))'; }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = 'var(--seekora-carousel-btn-bg, rgba(255,255,255,0.9))'; } }, "\u203A"),
|
|
9671
10012
|
showDots && (React.createElement("div", { className: "seekora-img-carousel-dots", style: {
|
|
9672
10013
|
position: 'absolute',
|
|
9673
|
-
bottom: 8,
|
|
10014
|
+
bottom: isMobile ? 12 : 8,
|
|
9674
10015
|
left: '50%',
|
|
9675
10016
|
transform: 'translateX(-50%)',
|
|
9676
10017
|
display: 'flex',
|
|
9677
|
-
gap: 6,
|
|
9678
|
-
padding: '6px 12px',
|
|
10018
|
+
gap: isMobile ? 8 : 6,
|
|
10019
|
+
padding: isMobile ? '8px 16px' : '6px 12px',
|
|
9679
10020
|
backgroundColor: 'var(--seekora-carousel-dots-bg, rgba(0,0,0,0.5))',
|
|
9680
10021
|
borderRadius: 12,
|
|
9681
10022
|
zIndex: 10,
|
|
9682
10023
|
} }, safeImages.map((_, i) => (React.createElement("button", { key: i, type: "button", "aria-label": `Go to image ${i + 1}`, onMouseDown: (e) => { e.stopPropagation(); e.preventDefault(); }, onClick: (e) => { e.stopPropagation(); e.preventDefault(); setIndex(i); }, style: {
|
|
9683
|
-
width: 8,
|
|
9684
|
-
height: 8,
|
|
10024
|
+
width: isMobile ? 10 : 8,
|
|
10025
|
+
height: isMobile ? 10 : 8,
|
|
10026
|
+
minWidth: isMobile ? 44 : 32, // Ensure touch target
|
|
10027
|
+
minHeight: isMobile ? 44 : 32,
|
|
9685
10028
|
borderRadius: '50%',
|
|
9686
10029
|
border: 'none',
|
|
9687
10030
|
padding: 0,
|
|
@@ -9693,26 +10036,28 @@ function ImageDisplay({ images, variant = 'single', alt = '', className, style,
|
|
|
9693
10036
|
if (variant === 'thumbStrip' || variant === 'thumbList') {
|
|
9694
10037
|
const thumbMainStyle = style?.aspectRatio ? { ...imgBaseStyle, aspectRatio: style.aspectRatio } : imgBaseStyle;
|
|
9695
10038
|
const mainImage = enableZoom ? (React.createElement(ImageZoom, { src: current, alt: alt, mode: zoomMode, zoomLevel: zoomLevel, images: safeImages, currentIndex: index, className: "seekora-img-thumb-main", style: thumbMainStyle })) : (React.createElement("img", { src: current, alt: alt, className: "seekora-img-thumb-main", style: thumbMainStyle, loading: "lazy" }));
|
|
9696
|
-
return (React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-thumbstrip', className), style: { display: 'flex', flexDirection: 'column', gap:
|
|
10039
|
+
return (React.createElement("div", { className: clsx('seekora-img-display', 'seekora-img-thumbstrip', className), style: { display: 'flex', flexDirection: 'column', gap: responsiveGap, ...style } },
|
|
9697
10040
|
mainImage,
|
|
9698
|
-
React.createElement("div", { className: "seekora-img-thumbs", style: { display: 'flex', gap: 4, overflowX: 'auto', paddingBottom: 4 } }, safeImages.map((src, i) => (React.createElement("button", { type: "button", key: i, className: clsx('seekora-img-thumb', i === index && 'seekora-img-thumb--active'), style: { flexShrink: 0, width:
|
|
10041
|
+
React.createElement("div", { className: "seekora-img-thumbs", style: { display: 'flex', gap: isMobile ? 6 : 4, overflowX: 'auto', paddingBottom: isMobile ? 6 : 4 } }, safeImages.map((src, i) => (React.createElement("button", { type: "button", key: i, className: clsx('seekora-img-thumb', i === index && 'seekora-img-thumb--active'), style: { flexShrink: 0, width: thumbSize, height: thumbSize, minWidth: thumbSize, minHeight: thumbSize, padding: 0, border: i === index ? '2px solid var(--seekora-primary)' : '1px solid transparent', borderRadius: 4, overflow: 'hidden', cursor: 'pointer', background: 'none' }, onMouseDown: (e) => { e.stopPropagation(); e.preventDefault(); setIndex(i); }, onClick: (e) => { e.stopPropagation(); e.preventDefault(); } },
|
|
9699
10042
|
React.createElement("img", { src: src, alt: "", style: { width: '100%', height: '100%', objectFit: 'cover' } })))))));
|
|
9700
10043
|
}
|
|
9701
10044
|
return React.createElement("img", { src: current, alt: alt, className: clsx('seekora-img-display', className), style: { ...imgBaseStyle, ...style }, loading: "lazy" });
|
|
9702
10045
|
}
|
|
9703
|
-
function arrowStyle(left) {
|
|
10046
|
+
function arrowStyle(left, size = 32) {
|
|
9704
10047
|
return {
|
|
9705
10048
|
position: 'absolute',
|
|
9706
10049
|
top: '50%',
|
|
9707
|
-
[left ? 'left' : 'right']: 8,
|
|
10050
|
+
[left ? 'left' : 'right']: size >= 44 ? 4 : 8, // Less offset on mobile for larger buttons
|
|
9708
10051
|
transform: 'translateY(-50%)',
|
|
9709
|
-
width:
|
|
9710
|
-
height:
|
|
10052
|
+
width: size,
|
|
10053
|
+
height: size,
|
|
10054
|
+
minWidth: size,
|
|
10055
|
+
minHeight: size,
|
|
9711
10056
|
borderRadius: '50%',
|
|
9712
10057
|
border: 'none',
|
|
9713
10058
|
backgroundColor: 'var(--seekora-carousel-btn-bg, rgba(255, 255, 255, 0.9))',
|
|
9714
10059
|
color: 'var(--seekora-carousel-btn-text, #111)',
|
|
9715
|
-
fontSize: '1.25rem',
|
|
10060
|
+
fontSize: size >= 44 ? '1.5rem' : '1.25rem', // Larger font on mobile
|
|
9716
10061
|
fontWeight: 'bold',
|
|
9717
10062
|
cursor: 'pointer',
|
|
9718
10063
|
display: 'flex',
|
|
@@ -9833,7 +10178,7 @@ const cardStyle$1 = {
|
|
|
9833
10178
|
textAlign: 'left',
|
|
9834
10179
|
fontSize: 'inherit',
|
|
9835
10180
|
fontFamily: 'inherit',
|
|
9836
|
-
transition: `background-color ${TRANSITIONS$1.fast}`,
|
|
10181
|
+
transition: `background-color ${TRANSITIONS$1.fast}, box-shadow ${TRANSITIONS$1.fast}`,
|
|
9837
10182
|
};
|
|
9838
10183
|
const imgStyle$1 = {
|
|
9839
10184
|
width: '100%',
|
|
@@ -9849,28 +10194,51 @@ function ItemCard({ item, position, onSelect, className, style, asLink = true, i
|
|
|
9849
10194
|
const title = item.title ?? item.primaryText ?? '';
|
|
9850
10195
|
const description = item.description ?? item.secondaryText;
|
|
9851
10196
|
const href = item.url;
|
|
10197
|
+
const isMobile = useIsMobile();
|
|
10198
|
+
const horizontalGap = useResponsiveGap(12);
|
|
9852
10199
|
const isHorizontal = layout === 'horizontal';
|
|
9853
|
-
const
|
|
10200
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
10201
|
+
// Responsive dimensions for horizontal layout
|
|
10202
|
+
const imageMinWidth = isMobile ? 60 : 80;
|
|
10203
|
+
const imageMaxWidth = isMobile ? 100 : 120;
|
|
10204
|
+
const imageFlex = isMobile ? '20%' : '20%';
|
|
10205
|
+
const imageHeight = isMobile ? 60 : 80;
|
|
10206
|
+
// Responsive font sizes
|
|
10207
|
+
const titleFontSize = isMobile ? '0.9375rem' : '0.875rem';
|
|
10208
|
+
const descriptionFontSize = isMobile ? '0.875rem' : '0.8125rem';
|
|
10209
|
+
const textGap = isMobile ? 6 : 4;
|
|
10210
|
+
const lineClamp = isMobile ? 3 : 2; // More lines on mobile
|
|
10211
|
+
// Hover style
|
|
10212
|
+
const cardHoverStyle = isHovered
|
|
10213
|
+
? {
|
|
10214
|
+
backgroundColor: 'var(--seekora-card-hover-bg, var(--seekora-bg-hover, #f9fafb))',
|
|
10215
|
+
boxShadow: 'var(--seekora-card-hover-shadow, 0 2px 8px rgba(0, 0, 0, 0.08))',
|
|
10216
|
+
}
|
|
10217
|
+
: {};
|
|
10218
|
+
const imageBlock = images.length > 0 ? (React.createElement("div", { style: { position: 'relative', overflow: 'hidden', borderRadius: 4, ...(isHorizontal ? { minWidth: imageMinWidth, flexBasis: imageFlex, maxWidth: imageMaxWidth, flexShrink: 0 } : {}) } },
|
|
9854
10219
|
React.createElement(ImageDisplay, { images: images, variant: imageVariant, alt: String(title), className: "seekora-item-card-image" }),
|
|
9855
|
-
actionButtons && actionButtons.length > 0 && actionButtonsPosition?.startsWith('overlay') && (React.createElement(ActionButtons, { buttons: actionButtons, position: actionButtonsPosition === 'overlay-top-right' ? 'top-right' : 'bottom-center', showLabels: showActionLabels, size: "small" })))) : (React.createElement("div", { className: "seekora-item-card-placeholder", style: { ...imgStyle$1, ...(isHorizontal ? { minWidth:
|
|
9856
|
-
const textBlock = (React.createElement("div", { style: isHorizontal ? { display: 'flex', flexDirection: 'column', gap:
|
|
9857
|
-
React.createElement("span", { className: "seekora-item-card-title", style: { fontSize:
|
|
9858
|
-
description ? (React.createElement("span", { className: "seekora-item-card-description", style: { fontSize:
|
|
10220
|
+
actionButtons && actionButtons.length > 0 && actionButtonsPosition?.startsWith('overlay') && (React.createElement(ActionButtons, { buttons: actionButtons, position: actionButtonsPosition === 'overlay-top-right' ? 'top-right' : 'bottom-center', showLabels: showActionLabels, size: "small" })))) : (React.createElement("div", { className: "seekora-item-card-placeholder", style: { ...imgStyle$1, ...(isHorizontal ? { minWidth: imageMinWidth, flexBasis: imageFlex, maxWidth: imageMaxWidth, height: imageHeight, flexShrink: 0 } : {}) }, "aria-hidden": true }));
|
|
10221
|
+
const textBlock = (React.createElement("div", { style: isHorizontal ? { display: 'flex', flexDirection: 'column', gap: textGap, flex: 1, minWidth: 0 } : undefined },
|
|
10222
|
+
React.createElement("span", { className: "seekora-item-card-title", style: { fontSize: titleFontSize, fontWeight: 500, lineHeight: 1.4, overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: isHorizontal ? 2 : 3, WebkitBoxOrient: 'vertical' } }, String(title)),
|
|
10223
|
+
description ? (React.createElement("span", { className: "seekora-item-card-description", style: { fontSize: descriptionFontSize, color: 'var(--seekora-text-secondary, #6b7280)', lineHeight: 1.3, display: '-webkit-box', WebkitLineClamp: lineClamp, WebkitBoxOrient: 'vertical', overflow: 'hidden' } }, String(description))) : null,
|
|
9859
10224
|
actionButtons && actionButtons.length > 0 && actionButtonsPosition === 'inline' && (React.createElement(ActionButtons, { buttons: actionButtons, position: "inline", showLabels: showActionLabels, size: "small", layout: "horizontal" }))));
|
|
9860
|
-
const content = isHorizontal ? (React.createElement("div", { style: { display: 'flex', gap:
|
|
10225
|
+
const content = isHorizontal ? (React.createElement("div", { style: { display: 'flex', gap: horizontalGap, alignItems: 'flex-start' } },
|
|
9861
10226
|
imageBlock,
|
|
9862
10227
|
textBlock)) : (React.createElement(React.Fragment, null,
|
|
9863
10228
|
imageBlock,
|
|
9864
10229
|
textBlock));
|
|
9865
10230
|
const commonProps = {
|
|
9866
|
-
className: clsx('seekora-item-card', isHorizontal && 'seekora-item-card--horizontal', className),
|
|
10231
|
+
className: clsx('seekora-item-card', isHorizontal && 'seekora-item-card--horizontal', isHovered && 'seekora-item-card--hover', className),
|
|
9867
10232
|
style: {
|
|
9868
10233
|
...cardStyle$1,
|
|
9869
10234
|
...(isHorizontal ? { flexDirection: 'row' } : {}),
|
|
10235
|
+
...cardHoverStyle,
|
|
9870
10236
|
...style,
|
|
9871
10237
|
},
|
|
9872
10238
|
'data-position': position,
|
|
9873
10239
|
onClick: onSelect,
|
|
10240
|
+
onMouseEnter: () => setIsHovered(true),
|
|
10241
|
+
onMouseLeave: () => setIsHovered(false),
|
|
9874
10242
|
onMouseDown: onSelect ? (e) => { e.preventDefault(); onSelect(); } : undefined,
|
|
9875
10243
|
};
|
|
9876
10244
|
if (asLink && href) {
|
|
@@ -9888,11 +10256,6 @@ function ItemCard({ item, position, onSelect, className, style, asLink = true, i
|
|
|
9888
10256
|
*/
|
|
9889
10257
|
const SPACING$1 = {
|
|
9890
10258
|
md: 12};
|
|
9891
|
-
const defaultGridStyle = {
|
|
9892
|
-
display: 'grid',
|
|
9893
|
-
gap: SPACING$1.md,
|
|
9894
|
-
padding: SPACING$1.md,
|
|
9895
|
-
};
|
|
9896
10259
|
function toGenericItem(item, getItemId, getItemTitle, getItemImage, getItemDescription, getItemUrl) {
|
|
9897
10260
|
return {
|
|
9898
10261
|
id: getItemId(item),
|
|
@@ -9904,12 +10267,18 @@ function toGenericItem(item, getItemId, getItemTitle, getItemImage, getItemDescr
|
|
|
9904
10267
|
};
|
|
9905
10268
|
}
|
|
9906
10269
|
function ItemGrid({ items, onItemClick, getItemId = (i) => i.id, getItemTitle = (i) => i.title, getItemImage = (i) => i.image, getItemDescription = (i) => i.description, getItemUrl = (i) => i.url, renderItem, maxItems = 24, columns = 4, className, style, gridClassName, }) {
|
|
10270
|
+
// Responsive columns and spacing
|
|
10271
|
+
const responsiveColumns = useResponsiveColumns(columns);
|
|
10272
|
+
const responsiveGap = useResponsiveGap(SPACING$1.md);
|
|
10273
|
+
const responsivePadding = useResponsivePadding(SPACING$1.md);
|
|
9907
10274
|
const slice = items.slice(0, maxItems);
|
|
9908
10275
|
if (slice.length === 0)
|
|
9909
10276
|
return null;
|
|
9910
10277
|
const gridStyle = {
|
|
9911
|
-
|
|
9912
|
-
|
|
10278
|
+
display: 'grid',
|
|
10279
|
+
gap: responsiveGap,
|
|
10280
|
+
padding: responsivePadding,
|
|
10281
|
+
gridTemplateColumns: `repeat(${responsiveColumns}, minmax(0, 1fr))`,
|
|
9913
10282
|
};
|
|
9914
10283
|
return (React.createElement("div", { className: clsx('seekora-item-grid', className), style: style },
|
|
9915
10284
|
React.createElement("div", { className: clsx('seekora-item-grid-inner', gridClassName), style: gridStyle }, slice.map((item, i) => {
|
|
@@ -10708,6 +11077,12 @@ const getAvailability$1 = (optionName, value, options, variants, selectedValues)
|
|
|
10708
11077
|
};
|
|
10709
11078
|
function VariantSwatches({ options, visibleOptions, maxValues = 5, colorMap, selectedValues, variants, showAvailability = true, onSwatchHover, onSwatchClick, className, style, }) {
|
|
10710
11079
|
const [expandedOptions, setExpandedOptions] = React.useState(new Set());
|
|
11080
|
+
const isMobile = useIsMobile();
|
|
11081
|
+
// Responsive sizing
|
|
11082
|
+
const colorSwatchSize = isMobile ? 20 : 14; // Larger on mobile for touch
|
|
11083
|
+
const textSwatchPadding = isMobile ? '4px 10px' : '1px 6px';
|
|
11084
|
+
const textSwatchFontSize = isMobile ? '0.75rem' : '0.6875rem';
|
|
11085
|
+
const gap = isMobile ? 6 : 4;
|
|
10711
11086
|
if (!options || options.length === 0)
|
|
10712
11087
|
return null;
|
|
10713
11088
|
const filtered = visibleOptions
|
|
@@ -10728,14 +11103,14 @@ function VariantSwatches({ options, visibleOptions, maxValues = 5, colorMap, sel
|
|
|
10728
11103
|
return next;
|
|
10729
11104
|
});
|
|
10730
11105
|
};
|
|
10731
|
-
return (React.createElement("div", { className: clsx('seekora-variant-swatches', className), style: { display: 'flex', flexDirection: 'column', gap
|
|
11106
|
+
return (React.createElement("div", { className: clsx('seekora-variant-swatches', className), style: { display: 'flex', flexDirection: 'column', gap, ...style } }, filtered.map((option) => {
|
|
10732
11107
|
const isColor = isColorOption$1(option.name);
|
|
10733
11108
|
const isExpanded = expandedOptions.has(option.name);
|
|
10734
11109
|
const visible = isExpanded ? option.values : option.values.slice(0, maxValues);
|
|
10735
11110
|
const overflow = option.values.length - maxValues;
|
|
10736
11111
|
const hasOverflow = overflow > 0;
|
|
10737
11112
|
const selectedValue = selectedValues?.[option.name];
|
|
10738
|
-
return (React.createElement("div", { key: option.name, className: "seekora-variant-swatch-group", style: { display: 'flex', alignItems: 'center', gap
|
|
11113
|
+
return (React.createElement("div", { key: option.name, className: "seekora-variant-swatch-group", style: { display: 'flex', alignItems: 'center', gap, flexWrap: 'wrap' } },
|
|
10739
11114
|
visible.map((value) => {
|
|
10740
11115
|
const color = isColor ? resolveColor$1(value, colorMap) : null;
|
|
10741
11116
|
const isSelected = selectedValue === value;
|
|
@@ -10744,9 +11119,13 @@ function VariantSwatches({ options, visibleOptions, maxValues = 5, colorMap, sel
|
|
|
10744
11119
|
: true;
|
|
10745
11120
|
if (color) {
|
|
10746
11121
|
return (React.createElement("span", { key: value, className: clsx('seekora-variant-swatch', 'seekora-variant-swatch--color', isSelected && 'seekora-variant-swatch--selected', !isAvailable && 'seekora-variant-swatch--unavailable'), title: `${value}${!isAvailable ? ' (Unavailable)' : ''}`, style: {
|
|
10747
|
-
display: 'inline-
|
|
10748
|
-
|
|
10749
|
-
|
|
11122
|
+
display: 'inline-flex',
|
|
11123
|
+
alignItems: 'center',
|
|
11124
|
+
justifyContent: 'center',
|
|
11125
|
+
width: colorSwatchSize,
|
|
11126
|
+
height: colorSwatchSize,
|
|
11127
|
+
minWidth: colorSwatchSize,
|
|
11128
|
+
minHeight: colorSwatchSize,
|
|
10750
11129
|
borderRadius: '50%',
|
|
10751
11130
|
backgroundColor: color,
|
|
10752
11131
|
border: isSelected
|
|
@@ -10778,9 +11157,12 @@ function VariantSwatches({ options, visibleOptions, maxValues = 5, colorMap, sel
|
|
|
10778
11157
|
} }))));
|
|
10779
11158
|
}
|
|
10780
11159
|
return (React.createElement("span", { key: value, className: clsx('seekora-variant-swatch', 'seekora-variant-swatch--text', isSelected && 'seekora-variant-swatch--selected', !isAvailable && 'seekora-variant-swatch--unavailable'), style: {
|
|
10781
|
-
display: 'inline-
|
|
10782
|
-
|
|
10783
|
-
|
|
11160
|
+
display: 'inline-flex',
|
|
11161
|
+
alignItems: 'center',
|
|
11162
|
+
justifyContent: 'center',
|
|
11163
|
+
padding: textSwatchPadding,
|
|
11164
|
+
minHeight: isMobile ? 32 : 24, // Minimum touch target on mobile
|
|
11165
|
+
fontSize: textSwatchFontSize,
|
|
10784
11166
|
borderRadius: 3,
|
|
10785
11167
|
border: isSelected
|
|
10786
11168
|
? '1px solid var(--seekora-primary, #111827)'
|
|
@@ -10807,11 +11189,12 @@ function VariantSwatches({ options, visibleOptions, maxValues = 5, colorMap, sel
|
|
|
10807
11189
|
} }, value));
|
|
10808
11190
|
}),
|
|
10809
11191
|
hasOverflow && (React.createElement("button", { type: "button", className: "seekora-variant-swatch--overflow", onClick: (e) => toggleExpanded(option.name, e), style: {
|
|
10810
|
-
fontSize:
|
|
11192
|
+
fontSize: textSwatchFontSize,
|
|
10811
11193
|
color: 'var(--seekora-primary, #2563eb)',
|
|
10812
11194
|
background: 'none',
|
|
10813
11195
|
border: 'none',
|
|
10814
|
-
padding: 0,
|
|
11196
|
+
padding: isMobile ? '4px 8px' : 0,
|
|
11197
|
+
minHeight: isMobile ? 32 : 'auto', // Touch target on mobile
|
|
10815
11198
|
cursor: 'pointer',
|
|
10816
11199
|
textDecoration: 'underline',
|
|
10817
11200
|
fontWeight: 500,
|
|
@@ -10846,16 +11229,24 @@ function ImageBlock({ images, title, imageVariant, aspectRatio, enableZoom, zoom
|
|
|
10846
11229
|
}
|
|
10847
11230
|
/** minimal: image, title, price (current default behavior) */
|
|
10848
11231
|
function MinimalLayout({ images, title, price, product, imageVariant, displayConfig, enableImageZoom, imageZoomMode, imageZoomLevel }) {
|
|
11232
|
+
const isMobile = useIsMobile();
|
|
11233
|
+
const titleFontSize = isMobile ? '0.9375rem' : '0.875rem'; // Slightly larger on mobile
|
|
11234
|
+
const priceFontSize = isMobile ? '0.9375rem' : '0.875rem';
|
|
10849
11235
|
return (React.createElement(React.Fragment, null,
|
|
10850
11236
|
React.createElement(ImageBlock, { images: images, title: title, imageVariant: imageVariant, aspectRatio: displayConfig.imageAspectRatio, enableZoom: enableImageZoom, zoomMode: imageZoomMode, zoomLevel: imageZoomLevel }),
|
|
10851
|
-
React.createElement("span", { className: "seekora-product-card__title", style: { fontSize:
|
|
10852
|
-
price != null && !Number.isNaN(price) && (React.createElement("span", { className: "seekora-product-card__price seekora-suggestions-product-card-price", style: { fontSize:
|
|
11237
|
+
React.createElement("span", { className: "seekora-product-card__title", style: { fontSize: titleFontSize, fontWeight: 500, lineHeight: 1.4, overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' } }, title),
|
|
11238
|
+
price != null && !Number.isNaN(price) && (React.createElement("span", { className: "seekora-product-card__price seekora-suggestions-product-card-price", style: { fontSize: priceFontSize, color: 'var(--seekora-text-secondary, #6b7280)' } },
|
|
10853
11239
|
product.currency ?? '$',
|
|
10854
11240
|
price.toFixed(2)))));
|
|
10855
11241
|
}
|
|
10856
11242
|
/** standard: image, badges, brand, title, price + compare price, color swatches */
|
|
10857
11243
|
function StandardLayout({ images, title, price, comparePrice, brand, badges, options, variants, product, imageVariant, displayConfig, onVariantHover, onVariantClick, selectedVariants, actionButtons, actionButtonsPosition, showActionLabels, enableImageZoom, imageZoomMode, imageZoomLevel, }) {
|
|
10858
11244
|
const cfg = displayConfig;
|
|
11245
|
+
const isMobile = useIsMobile();
|
|
11246
|
+
const bodyGap = isMobile ? 6 : 4; // More gap on mobile
|
|
11247
|
+
const titleFontSize = isMobile ? '0.9375rem' : '0.875rem';
|
|
11248
|
+
const brandFontSize = isMobile ? '0.8125rem' : '0.75rem';
|
|
11249
|
+
const priceFontSize = isMobile ? '0.9375rem' : '0.875rem';
|
|
10859
11250
|
// Normalize position: 'overlay-*' positions render over image, everything else renders inline
|
|
10860
11251
|
const isOverlayPosition = actionButtonsPosition?.startsWith('overlay');
|
|
10861
11252
|
return (React.createElement(React.Fragment, null,
|
|
@@ -10863,11 +11254,11 @@ function StandardLayout({ images, title, price, comparePrice, brand, badges, opt
|
|
|
10863
11254
|
React.createElement(ImageBlock, { images: images, title: title, imageVariant: imageVariant, aspectRatio: cfg.imageAspectRatio, enableZoom: enableImageZoom, zoomMode: imageZoomMode, zoomLevel: imageZoomLevel }),
|
|
10864
11255
|
cfg.showBadges !== false && badges.length > 0 && (React.createElement(BadgeList, { badges: badges, position: "top-left", maxBadges: 2 })),
|
|
10865
11256
|
actionButtons && actionButtons.length > 0 && isOverlayPosition && (React.createElement(ActionButtons, { buttons: actionButtons, position: actionButtonsPosition === 'overlay-top-right' ? 'top-right' : 'bottom-center', showLabels: showActionLabels, size: "small" }))),
|
|
10866
|
-
React.createElement("div", { className: "seekora-product-card__body", style: { display: 'flex', flexDirection: 'column', gap:
|
|
10867
|
-
cfg.showBrand !== false && brand && (React.createElement("span", { className: "seekora-product-card__brand", style: { fontSize:
|
|
10868
|
-
React.createElement("span", { className: "seekora-product-card__title", style: { fontSize:
|
|
11257
|
+
React.createElement("div", { className: "seekora-product-card__body", style: { display: 'flex', flexDirection: 'column', gap: bodyGap } },
|
|
11258
|
+
cfg.showBrand !== false && brand && (React.createElement("span", { className: "seekora-product-card__brand", style: { fontSize: brandFontSize, color: 'var(--seekora-text-secondary, #6b7280)', textTransform: 'uppercase', letterSpacing: '0.02em' } }, brand)),
|
|
11259
|
+
React.createElement("span", { className: "seekora-product-card__title", style: { fontSize: titleFontSize, fontWeight: 500, lineHeight: 1.4, overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' } }, title),
|
|
10869
11260
|
React.createElement("div", { className: "seekora-product-card__price" },
|
|
10870
|
-
React.createElement(PriceDisplay, { price: price ?? undefined, comparePrice: comparePrice ?? undefined, currency: cfg.currency ?? product.currency, currencyPosition: cfg.currencyPosition, priceDecimals: cfg.priceDecimals, showDiscount: cfg.showDiscount, style: { fontSize:
|
|
11261
|
+
React.createElement(PriceDisplay, { price: price ?? undefined, comparePrice: comparePrice ?? undefined, currency: cfg.currency ?? product.currency, currencyPosition: cfg.currencyPosition, priceDecimals: cfg.priceDecimals, showDiscount: cfg.showDiscount, style: { fontSize: priceFontSize } })),
|
|
10871
11262
|
cfg.showVariants !== false && options.length > 0 && (React.createElement(VariantSwatches, { options: options, visibleOptions: cfg.variantOptionsToShow, maxValues: cfg.maxVariantValues, selectedValues: selectedVariants, variants: variants, onSwatchHover: onVariantHover, onSwatchClick: onVariantClick })),
|
|
10872
11263
|
actionButtons && actionButtons.length > 0 && !isOverlayPosition && (React.createElement(ActionButtons, { buttons: actionButtons, position: "inline", showLabels: showActionLabels, size: "small", layout: "horizontal" })))));
|
|
10873
11264
|
}
|
|
@@ -10912,16 +11303,27 @@ function CompactLayout({ images, title, price, product, imageVariant, displayCon
|
|
|
10912
11303
|
/** horizontal: image left + content right (title, brand, price, swatches) */
|
|
10913
11304
|
function HorizontalLayout({ images, title, price, comparePrice, brand, badges, options, variants, product, imageVariant, displayConfig, onVariantHover, onVariantClick, selectedVariants, actionButtons, actionButtonsPosition, showActionLabels, enableImageZoom, imageZoomMode, imageZoomLevel, }) {
|
|
10914
11305
|
const cfg = displayConfig;
|
|
10915
|
-
|
|
10916
|
-
|
|
11306
|
+
const isMobile = useIsMobile();
|
|
11307
|
+
const containerGap = useResponsiveGap(12);
|
|
11308
|
+
const bodyGap = isMobile ? 6 : 4;
|
|
11309
|
+
// Responsive image dimensions
|
|
11310
|
+
const imageMinWidth = isMobile ? 60 : 80;
|
|
11311
|
+
const imageMaxWidth = isMobile ? 100 : 120;
|
|
11312
|
+
const imageFlex = isMobile ? '20%' : '25%';
|
|
11313
|
+
// Responsive font sizes
|
|
11314
|
+
const brandFontSize = isMobile ? '0.6875rem' : '0.75rem';
|
|
11315
|
+
const titleFontSize = isMobile ? '0.875rem' : '0.875rem';
|
|
11316
|
+
const priceFontSize = isMobile ? '0.875rem' : '0.875rem';
|
|
11317
|
+
return (React.createElement("div", { style: { display: 'flex', gap: containerGap, alignItems: 'flex-start' } },
|
|
11318
|
+
React.createElement("div", { style: { position: 'relative', minWidth: imageMinWidth, flexBasis: imageFlex, maxWidth: imageMaxWidth, flexShrink: 0 } },
|
|
10917
11319
|
React.createElement(ImageBlock, { images: images, title: title, imageVariant: imageVariant, aspectRatio: "1:1", enableZoom: enableImageZoom, zoomMode: imageZoomMode, zoomLevel: imageZoomLevel }),
|
|
10918
11320
|
cfg.showBadges !== false && badges.length > 0 && (React.createElement(BadgeList, { badges: badges, position: "top-left", maxBadges: 1 })),
|
|
10919
11321
|
actionButtons && actionButtons.length > 0 && actionButtonsPosition?.startsWith('overlay') && (React.createElement(ActionButtons, { buttons: actionButtons, position: actionButtonsPosition === 'overlay-top-right' ? 'top-right' : 'bottom-center', showLabels: showActionLabels, size: "small" }))),
|
|
10920
|
-
React.createElement("div", { className: "seekora-product-card__body", style: { display: 'flex', flexDirection: 'column', gap:
|
|
10921
|
-
cfg.showBrand !== false && brand && (React.createElement("span", { className: "seekora-product-card__brand", style: { fontSize:
|
|
10922
|
-
React.createElement("span", { className: "seekora-product-card__title", style: { fontSize:
|
|
11322
|
+
React.createElement("div", { className: "seekora-product-card__body", style: { display: 'flex', flexDirection: 'column', gap: bodyGap, flex: 1, minWidth: 0 } },
|
|
11323
|
+
cfg.showBrand !== false && brand && (React.createElement("span", { className: "seekora-product-card__brand", style: { fontSize: brandFontSize, color: 'var(--seekora-text-secondary, #6b7280)', textTransform: 'uppercase' } }, brand)),
|
|
11324
|
+
React.createElement("span", { className: "seekora-product-card__title", style: { fontSize: titleFontSize, fontWeight: 500, lineHeight: 1.4, overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: isMobile ? 3 : 2, WebkitBoxOrient: 'vertical' } }, title),
|
|
10923
11325
|
React.createElement("div", { className: "seekora-product-card__price" },
|
|
10924
|
-
React.createElement(PriceDisplay, { price: price ?? undefined, comparePrice: comparePrice ?? undefined, currency: cfg.currency ?? product.currency, currencyPosition: cfg.currencyPosition, priceDecimals: cfg.priceDecimals, showDiscount: cfg.showDiscount, style: { fontSize:
|
|
11326
|
+
React.createElement(PriceDisplay, { price: price ?? undefined, comparePrice: comparePrice ?? undefined, currency: cfg.currency ?? product.currency, currencyPosition: cfg.currencyPosition, priceDecimals: cfg.priceDecimals, showDiscount: cfg.showDiscount, style: { fontSize: priceFontSize } })),
|
|
10925
11327
|
cfg.showVariants !== false && options.length > 0 && (React.createElement(VariantSwatches, { options: options, visibleOptions: cfg.variantOptionsToShow, maxValues: cfg.maxVariantValues ?? 3, selectedValues: selectedVariants, variants: variants, onSwatchHover: onVariantHover, onSwatchClick: onVariantClick })),
|
|
10926
11328
|
actionButtons && actionButtons.length > 0 && actionButtonsPosition === 'inline' && (React.createElement(ActionButtons, { buttons: actionButtons, position: "inline", showLabels: showActionLabels, size: "small", layout: "horizontal" })))));
|
|
10927
11329
|
}
|
|
@@ -10947,7 +11349,7 @@ const cardStyle = {
|
|
|
10947
11349
|
borderRadius: `var(--seekora-border-radius, ${BORDER_RADIUS.md}px)`,
|
|
10948
11350
|
backgroundColor: 'transparent',
|
|
10949
11351
|
textAlign: 'left',
|
|
10950
|
-
transition: `background-color ${TRANSITIONS.fast}`,
|
|
11352
|
+
transition: `background-color ${TRANSITIONS.fast}, box-shadow ${TRANSITIONS.fast}`,
|
|
10951
11353
|
};
|
|
10952
11354
|
const imgStyle = {
|
|
10953
11355
|
width: '100%',
|
|
@@ -10999,9 +11401,17 @@ function ProductCard({ product, position, section, tabId, onSelect, className, s
|
|
|
10999
11401
|
const images = effectiveImages;
|
|
11000
11402
|
const title = effectiveTitle;
|
|
11001
11403
|
const price = effectivePrice;
|
|
11404
|
+
// Hover state for card
|
|
11405
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
11406
|
+
const cardHoverStyle = isHovered
|
|
11407
|
+
? {
|
|
11408
|
+
backgroundColor: 'var(--seekora-card-hover-bg, var(--seekora-bg-hover, #f9fafb))',
|
|
11409
|
+
boxShadow: 'var(--seekora-card-hover-shadow, 0 2px 8px rgba(0, 0, 0, 0.08))',
|
|
11410
|
+
}
|
|
11411
|
+
: {};
|
|
11002
11412
|
// If no displayConfig, render original minimal layout (backwards compat)
|
|
11003
11413
|
if (!displayConfig) {
|
|
11004
|
-
return (React.createElement("div", { role: "button", tabIndex: 0, className: clsx('seekora-suggestions-product-card', className), style: { ...cardStyle, ...style }, onClick: () => onSelect(), onKeyDown: (e) => {
|
|
11414
|
+
return (React.createElement("div", { role: "button", tabIndex: 0, className: clsx('seekora-suggestions-product-card', isHovered && 'seekora-suggestions-product-card--hover', className), style: { ...cardStyle, ...cardHoverStyle, ...style }, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onClick: () => onSelect(), onKeyDown: (e) => {
|
|
11005
11415
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
11006
11416
|
e.preventDefault();
|
|
11007
11417
|
onSelect();
|
|
@@ -11062,20 +11472,21 @@ function ProductCard({ product, position, section, tabId, onSelect, className, s
|
|
|
11062
11472
|
horizontal: HorizontalLayout,
|
|
11063
11473
|
};
|
|
11064
11474
|
const LayoutComponent = layoutMap[layoutStyle] ?? MinimalLayout;
|
|
11065
|
-
const rootClassName = clsx('seekora-product-card', `seekora-product-card--${layoutStyle}`, 'seekora-suggestions-product-card', className);
|
|
11475
|
+
const rootClassName = clsx('seekora-product-card', `seekora-product-card--${layoutStyle}`, 'seekora-suggestions-product-card', isHovered && 'seekora-product-card--hover', className);
|
|
11066
11476
|
const rootStyle = {
|
|
11067
11477
|
...cardStyle,
|
|
11068
11478
|
...(layoutStyle === 'horizontal' ? { flexDirection: 'row' } : {}),
|
|
11479
|
+
...cardHoverStyle,
|
|
11069
11480
|
...style,
|
|
11070
11481
|
};
|
|
11071
11482
|
if (asLink && product.url) {
|
|
11072
|
-
return (React.createElement("a", { href: product.url, className: rootClassName, style: { ...rootStyle, textDecoration: 'none', color: 'inherit' }, onClick: (e) => {
|
|
11483
|
+
return (React.createElement("a", { href: product.url, className: rootClassName, style: { ...rootStyle, textDecoration: 'none', color: 'inherit' }, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onClick: (e) => {
|
|
11073
11484
|
e.preventDefault();
|
|
11074
11485
|
onSelect();
|
|
11075
11486
|
}, "data-position": position, "data-section": section, "data-tab-id": tabId },
|
|
11076
11487
|
React.createElement(LayoutComponent, { ...layoutProps })));
|
|
11077
11488
|
}
|
|
11078
|
-
return (React.createElement("div", { role: "button", tabIndex: 0, className: rootClassName, style: rootStyle, onClick: () => onSelect(), onKeyDown: (e) => {
|
|
11489
|
+
return (React.createElement("div", { role: "button", tabIndex: 0, className: rootClassName, style: rootStyle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onClick: () => onSelect(), onKeyDown: (e) => {
|
|
11079
11490
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
11080
11491
|
e.preventDefault();
|
|
11081
11492
|
onSelect();
|
|
@@ -11091,6 +11502,10 @@ function ProductCard({ product, position, section, tabId, onSelect, className, s
|
|
|
11091
11502
|
*/
|
|
11092
11503
|
function ProductGrid({ maxItems = 8, source = 'trending', columns = 4, className, style, gridClassName, displayConfig, onVariantHover, }) {
|
|
11093
11504
|
const { trendingProducts, filteredTabs, activeTabId, selectProduct, getAllNavigableItems, } = useSuggestionsContext();
|
|
11505
|
+
// Responsive columns and spacing
|
|
11506
|
+
const responsiveColumns = useResponsiveColumns(columns);
|
|
11507
|
+
const responsiveGap = useResponsiveGap(12);
|
|
11508
|
+
const responsivePadding = useResponsivePadding(12);
|
|
11094
11509
|
const products = React.useMemo(() => {
|
|
11095
11510
|
if (source === 'trending')
|
|
11096
11511
|
return trendingProducts;
|
|
@@ -11104,9 +11519,9 @@ function ProductGrid({ maxItems = 8, source = 'trending', columns = 4, className
|
|
|
11104
11519
|
return null;
|
|
11105
11520
|
const gridStyle = {
|
|
11106
11521
|
display: 'grid',
|
|
11107
|
-
gridTemplateColumns: `repeat(${
|
|
11108
|
-
gap:
|
|
11109
|
-
padding:
|
|
11522
|
+
gridTemplateColumns: `repeat(${responsiveColumns}, minmax(0, 1fr))`,
|
|
11523
|
+
gap: responsiveGap,
|
|
11524
|
+
padding: responsivePadding,
|
|
11110
11525
|
};
|
|
11111
11526
|
return (React.createElement("div", { className: clsx('seekora-suggestions-product-grid', className), style: style },
|
|
11112
11527
|
React.createElement("div", { className: clsx('seekora-suggestions-product-grid-inner', gridClassName), style: gridStyle }, items.map((product, i) => {
|
|
@@ -11167,6 +11582,17 @@ const itemStyle$1 = {
|
|
|
11167
11582
|
color: 'var(--seekora-text-primary, #111827)',
|
|
11168
11583
|
transition: 'background-color 120ms ease',
|
|
11169
11584
|
};
|
|
11585
|
+
function RecentSearchItem({ search, onSelect }) {
|
|
11586
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
11587
|
+
return (React.createElement("li", null,
|
|
11588
|
+
React.createElement("button", { type: "button", className: clsx('seekora-suggestions-recent-item', isHovered && 'seekora-suggestions-recent-item--hover'), style: {
|
|
11589
|
+
...itemStyle$1,
|
|
11590
|
+
...(isHovered ? { backgroundColor: 'var(--seekora-bg-hover, #f3f4f6)' } : {}),
|
|
11591
|
+
}, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onMouseDown: (e) => {
|
|
11592
|
+
e.preventDefault();
|
|
11593
|
+
onSelect();
|
|
11594
|
+
} }, search.query)));
|
|
11595
|
+
}
|
|
11170
11596
|
function RecentSearchesList({ title = 'Recent', maxItems = 8, className, style, listClassName, renderItem, }) {
|
|
11171
11597
|
const { recentSearches, query, selectRecentSearch } = useSuggestionsContext();
|
|
11172
11598
|
const items = recentSearches.slice(0, maxItems);
|
|
@@ -11179,11 +11605,7 @@ function RecentSearchesList({ title = 'Recent', maxItems = 8, className, style,
|
|
|
11179
11605
|
if (renderItem) {
|
|
11180
11606
|
return React.createElement("li", { key: `${search.query}-${search.timestamp}` }, renderItem(search, i, onSelect));
|
|
11181
11607
|
}
|
|
11182
|
-
return (React.createElement(
|
|
11183
|
-
React.createElement("button", { type: "button", className: "seekora-suggestions-recent-item", style: itemStyle$1, onMouseDown: (e) => {
|
|
11184
|
-
e.preventDefault();
|
|
11185
|
-
onSelect();
|
|
11186
|
-
} }, search.query)));
|
|
11608
|
+
return (React.createElement(RecentSearchItem, { key: `${search.query}-${search.timestamp}`, search: search, onSelect: onSelect }));
|
|
11187
11609
|
}))));
|
|
11188
11610
|
}
|
|
11189
11611
|
|
|
@@ -11204,6 +11626,19 @@ const itemStyle = {
|
|
|
11204
11626
|
color: 'var(--seekora-text-primary, #111827)',
|
|
11205
11627
|
transition: 'background-color 120ms ease',
|
|
11206
11628
|
};
|
|
11629
|
+
function TrendingItem({ trending, onSelect }) {
|
|
11630
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
11631
|
+
return (React.createElement("li", null,
|
|
11632
|
+
React.createElement("button", { type: "button", className: clsx('seekora-suggestions-trending-item', isHovered && 'seekora-suggestions-trending-item--hover'), style: {
|
|
11633
|
+
...itemStyle,
|
|
11634
|
+
...(isHovered ? { backgroundColor: 'var(--seekora-bg-hover, #f3f4f6)' } : {}),
|
|
11635
|
+
}, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onMouseDown: (e) => {
|
|
11636
|
+
e.preventDefault();
|
|
11637
|
+
onSelect();
|
|
11638
|
+
} },
|
|
11639
|
+
trending.query,
|
|
11640
|
+
trending.count != null ? (React.createElement("span", { style: { marginLeft: 8, color: 'var(--seekora-text-secondary, #6b7280)', fontSize: '0.875em' } }, trending.count)) : null)));
|
|
11641
|
+
}
|
|
11207
11642
|
function TrendingList({ title = 'Trending', maxItems = 8, className, style, listClassName, renderItem, }) {
|
|
11208
11643
|
const { trendingSearches, selectTrendingSearch } = useSuggestionsContext();
|
|
11209
11644
|
const items = trendingSearches.slice(0, maxItems);
|
|
@@ -11216,31 +11651,10 @@ function TrendingList({ title = 'Trending', maxItems = 8, className, style, list
|
|
|
11216
11651
|
if (renderItem) {
|
|
11217
11652
|
return React.createElement("li", { key: `${trending.query}-${i}` }, renderItem(trending, i, onSelect));
|
|
11218
11653
|
}
|
|
11219
|
-
return (React.createElement(
|
|
11220
|
-
React.createElement("button", { type: "button", className: "seekora-suggestions-trending-item", style: itemStyle, onMouseDown: (e) => {
|
|
11221
|
-
e.preventDefault();
|
|
11222
|
-
onSelect();
|
|
11223
|
-
} },
|
|
11224
|
-
trending.query,
|
|
11225
|
-
trending.count != null ? (React.createElement("span", { style: { marginLeft: 8, color: 'var(--seekora-text-secondary, #6b7280)', fontSize: '0.875em' } }, trending.count)) : null)));
|
|
11654
|
+
return (React.createElement(TrendingItem, { key: `${trending.query}-${i}`, trending: trending, onSelect: onSelect }));
|
|
11226
11655
|
}))));
|
|
11227
11656
|
}
|
|
11228
11657
|
|
|
11229
|
-
/**
|
|
11230
|
-
* SuggestionsLoading – loading indicator (primitive)
|
|
11231
|
-
*/
|
|
11232
|
-
function SuggestionsLoading({ className, style, text = 'Loading...' }) {
|
|
11233
|
-
const { loading } = useSuggestionsContext();
|
|
11234
|
-
if (!loading)
|
|
11235
|
-
return null;
|
|
11236
|
-
return (React.createElement("div", { className: clsx('seekora-suggestions-loading', className), style: {
|
|
11237
|
-
padding: 16,
|
|
11238
|
-
color: 'var(--seekora-text-secondary, #6b7280)',
|
|
11239
|
-
fontSize: '0.875rem',
|
|
11240
|
-
...style,
|
|
11241
|
-
} }, text));
|
|
11242
|
-
}
|
|
11243
|
-
|
|
11244
11658
|
/**
|
|
11245
11659
|
* SuggestionsError – error message (primitive)
|
|
11246
11660
|
*/
|
|
@@ -11718,16 +12132,6 @@ function SectionSearchProvider({ children, query, refinements = [], maxItems = 1
|
|
|
11718
12132
|
return React.createElement(SectionSearchContext.Provider, { value: value }, children);
|
|
11719
12133
|
}
|
|
11720
12134
|
|
|
11721
|
-
/**
|
|
11722
|
-
* SectionLoading – loading state for section (primitive)
|
|
11723
|
-
*/
|
|
11724
|
-
function SectionLoading({ className, style, text = 'Loading...' }) {
|
|
11725
|
-
const { loading } = useSectionSearchContext();
|
|
11726
|
-
if (!loading)
|
|
11727
|
-
return null;
|
|
11728
|
-
return (React.createElement("div", { className: className, style: { padding: 16, color: 'var(--seekora-text-secondary)', fontSize: '0.875rem', ...style } }, text));
|
|
11729
|
-
}
|
|
11730
|
-
|
|
11731
12135
|
/**
|
|
11732
12136
|
* SectionError – error state for section (primitive)
|
|
11733
12137
|
*/
|
|
@@ -11743,10 +12147,8 @@ function SectionError({ className, style, render }) {
|
|
|
11743
12147
|
/**
|
|
11744
12148
|
* SectionItemGrid – generic grid of items from SectionSearchProvider (primitive)
|
|
11745
12149
|
*/
|
|
11746
|
-
function SectionItemGrid({ columns = 4, maxItems = 12, className, style,
|
|
12150
|
+
function SectionItemGrid({ columns = 4, maxItems = 12, className, style, getItemId = (i) => i.id ?? String(i?.objectID ?? ''), getItemTitle = (i) => i.title ?? i?.title ?? '', getItemImage = (i) => i.image ?? i?.image, getItemDescription = (i) => i.description ?? i?.description, getItemUrl = (i) => i.url ?? i?.url, renderItem, }) {
|
|
11747
12151
|
const { items, loading, error, trackClick } = useSectionSearchContext();
|
|
11748
|
-
if (loading && items.length === 0 && showLoadingState)
|
|
11749
|
-
return React.createElement(SectionLoading, { className: className, style: style });
|
|
11750
12152
|
if (error)
|
|
11751
12153
|
return React.createElement(SectionError, { className: className, style: style });
|
|
11752
12154
|
// When loading with previous items, show them (no loading screen)
|
|
@@ -12407,22 +12809,6 @@ const createStyles$7 = (theme = {}, isMobile = false) => ({
|
|
|
12407
12809
|
fontWeight: 700,
|
|
12408
12810
|
color: 'var(--seekora-text-primary, #0f1111)',
|
|
12409
12811
|
},
|
|
12410
|
-
loading: {
|
|
12411
|
-
display: 'flex',
|
|
12412
|
-
alignItems: 'center',
|
|
12413
|
-
justifyContent: 'center',
|
|
12414
|
-
padding: '40px',
|
|
12415
|
-
color: 'var(--seekora-text-secondary, #565959)',
|
|
12416
|
-
gap: '10px',
|
|
12417
|
-
},
|
|
12418
|
-
spinner: {
|
|
12419
|
-
width: '20px',
|
|
12420
|
-
height: '20px',
|
|
12421
|
-
border: '2px solid var(--seekora-border-color, #ddd)',
|
|
12422
|
-
borderTopColor: 'var(--seekora-primary, #f90)',
|
|
12423
|
-
borderRadius: '50%',
|
|
12424
|
-
animation: 'seekora-spin 0.8s linear infinite',
|
|
12425
|
-
},
|
|
12426
12812
|
empty: {
|
|
12427
12813
|
padding: '40px 20px',
|
|
12428
12814
|
textAlign: 'center',
|
|
@@ -12486,7 +12872,7 @@ const RatingStars = ({ rating, count }) => {
|
|
|
12486
12872
|
")"))));
|
|
12487
12873
|
};
|
|
12488
12874
|
const AmazonDropdown = React.forwardRef(function AmazonDropdown(props, ref) {
|
|
12489
|
-
const { query, isOpen = true, loading = false, suggestions = [], products = [], categories = [], recentSearches = [], trendingSearches = [], filteredTabs = [], activeTab = 'all', suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, categoryFields = { id: 'id', label: 'label' }, productDisplay = {}, theme = {}, showDepartments = true, currentDepartment, departments = [], showPrime = false, width = '700px', maxHeight = '500px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, onRecentClick, onRecentRemove, onRecentClearAll, onViewAll, onClose, header, footer,
|
|
12875
|
+
const { query, isOpen = true, loading = false, suggestions = [], products = [], categories = [], recentSearches = [], trendingSearches = [], filteredTabs = [], activeTab = 'all', suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, categoryFields = { id: 'id', label: 'label' }, productDisplay = {}, theme = {}, showDepartments = true, currentDepartment, departments = [], showPrime = false, width = '700px', maxHeight = '500px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, onRecentClick, onRecentRemove, onRecentClearAll, onViewAll, onClose, header, footer, renderEmpty, } = props;
|
|
12490
12876
|
// Inject global responsive styles
|
|
12491
12877
|
useInjectResponsiveStyles();
|
|
12492
12878
|
// Responsive state
|
|
@@ -12583,9 +12969,7 @@ const AmazonDropdown = React.forwardRef(function AmazonDropdown(props, ref) {
|
|
|
12583
12969
|
}
|
|
12584
12970
|
`),
|
|
12585
12971
|
header,
|
|
12586
|
-
|
|
12587
|
-
React.createElement("div", { style: styles.spinner }),
|
|
12588
|
-
React.createElement("span", null, "Searching...")))) : (React.createElement(React.Fragment, null,
|
|
12972
|
+
React.createElement(React.Fragment, null,
|
|
12589
12973
|
filteredTabs.length > 0 && (React.createElement("div", { style: {
|
|
12590
12974
|
display: 'flex',
|
|
12591
12975
|
gap: '8px',
|
|
@@ -12690,7 +13074,7 @@ const AmazonDropdown = React.forwardRef(function AmazonDropdown(props, ref) {
|
|
|
12690
13074
|
currency: '',
|
|
12691
13075
|
priceDecimals: productDisplay.priceDecimals
|
|
12692
13076
|
}))),
|
|
12693
|
-
product.rating && (React.createElement(RatingStars, { rating: parseFloat(String(product.rating)), count: product.reviewCount ? parseInt(String(product.reviewCount)) : undefined }))))))))))
|
|
13077
|
+
product.rating && (React.createElement(RatingStars, { rating: parseFloat(String(product.rating)), count: product.reviewCount ? parseInt(String(product.reviewCount)) : undefined })))))))))),
|
|
12694
13078
|
footer !== undefined ? footer : (React.createElement("div", { style: styles.footer },
|
|
12695
13079
|
React.createElement("div", { style: styles.keyboard },
|
|
12696
13080
|
React.createElement("span", { style: styles.key }, "\u2191"),
|
|
@@ -12880,22 +13264,6 @@ const createStyles$6 = (isMobile = false) => ({
|
|
|
12880
13264
|
backgroundColor: 'var(--seekora-bg-hover, #f1f3f4)',
|
|
12881
13265
|
color: 'var(--seekora-text-primary, #202124)',
|
|
12882
13266
|
},
|
|
12883
|
-
loading: {
|
|
12884
|
-
display: 'flex',
|
|
12885
|
-
alignItems: 'center',
|
|
12886
|
-
justifyContent: 'center',
|
|
12887
|
-
padding: '24px',
|
|
12888
|
-
color: 'var(--seekora-text-secondary, #5f6368)',
|
|
12889
|
-
gap: '12px',
|
|
12890
|
-
},
|
|
12891
|
-
spinner: {
|
|
12892
|
-
width: '24px',
|
|
12893
|
-
height: '24px',
|
|
12894
|
-
border: '3px solid var(--seekora-border-color, #e8eaed)',
|
|
12895
|
-
borderTopColor: 'var(--seekora-primary, #4285f4)',
|
|
12896
|
-
borderRadius: '50%',
|
|
12897
|
-
animation: 'seekora-spin 0.8s linear infinite',
|
|
12898
|
-
},
|
|
12899
13267
|
empty: {
|
|
12900
13268
|
padding: '24px',
|
|
12901
13269
|
textAlign: 'center',
|
|
@@ -12915,7 +13283,7 @@ const TrendingIcon$2 = () => (React.createElement("svg", { width: "20", height:
|
|
|
12915
13283
|
const ArrowIcon$1 = () => (React.createElement("svg", { viewBox: "0 0 24 24", fill: "none", width: "20", height: "20" },
|
|
12916
13284
|
React.createElement("path", { d: "M9 5l-4 4 4 4", stroke: "currentColor", strokeWidth: "2", fill: "none", transform: "rotate(-90 12 12)" })));
|
|
12917
13285
|
const GoogleDropdown = React.forwardRef(function GoogleDropdown(props, ref) {
|
|
12918
|
-
const { query, isOpen = true, loading = false, suggestions = [], recentSearches = [], trendingSearches = [], suggestionFields = { query: 'query' }, theme = {}, showFeelingLucky = true, feelingLuckyText = "I'm Feeling Lucky", showRemoveRecent = true, showVoiceSearch = false, showTrendingIndicator = true, width = '100%', maxHeight = '400px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onRecentClick, onRecentRemove, onVoiceSearch, onFeelingLucky, onClose, onSearchSubmit, header, footer,
|
|
13286
|
+
const { query, isOpen = true, loading = false, suggestions = [], recentSearches = [], trendingSearches = [], suggestionFields = { query: 'query' }, theme = {}, showFeelingLucky = true, feelingLuckyText = "I'm Feeling Lucky", showRemoveRecent = true, showVoiceSearch = false, showTrendingIndicator = true, width = '100%', maxHeight = '400px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onRecentClick, onRecentRemove, onVoiceSearch, onFeelingLucky, onClose, onSearchSubmit, header, footer, renderEmpty, } = props;
|
|
12919
13287
|
// Inject global responsive styles
|
|
12920
13288
|
useInjectResponsiveStyles();
|
|
12921
13289
|
// Responsive state
|
|
@@ -13030,8 +13398,7 @@ const GoogleDropdown = React.forwardRef(function GoogleDropdown(props, ref) {
|
|
|
13030
13398
|
}
|
|
13031
13399
|
`),
|
|
13032
13400
|
header,
|
|
13033
|
-
|
|
13034
|
-
React.createElement("div", { style: styles.spinner })))) : (React.createElement(React.Fragment, null,
|
|
13401
|
+
React.createElement(React.Fragment, null,
|
|
13035
13402
|
React.createElement("ul", { style: styles.list },
|
|
13036
13403
|
showRecent && (React.createElement(React.Fragment, null,
|
|
13037
13404
|
recentQueries.map((q, idx) => {
|
|
@@ -13061,7 +13428,7 @@ const GoogleDropdown = React.forwardRef(function GoogleDropdown(props, ref) {
|
|
|
13061
13428
|
"\"")))),
|
|
13062
13429
|
(showFeelingLucky || showVoiceSearch) && (React.createElement("div", { style: styles.footer }, showFeelingLucky && (React.createElement(React.Fragment, null,
|
|
13063
13430
|
React.createElement("button", { style: mergeStyles(styles.footerButton, hoveredBtn === 'search' ? styles.footerButtonHover : undefined), onMouseEnter: () => setHoveredBtn('search'), onMouseLeave: () => setHoveredBtn(null), onClick: () => onSearchSubmit?.(query || (activeIndex >= 0 ? allItems[activeIndex]?.query : '')) }, "Search"),
|
|
13064
|
-
React.createElement("button", { style: mergeStyles(styles.footerButton, hoveredBtn === 'lucky' ? styles.footerButtonHover : undefined), onMouseEnter: () => setHoveredBtn('lucky'), onMouseLeave: () => setHoveredBtn(null), onClick: () => onFeelingLucky?.(query || (activeIndex >= 0 ? allItems[activeIndex]?.query : '')) }, feelingLuckyText))))))
|
|
13431
|
+
React.createElement("button", { style: mergeStyles(styles.footerButton, hoveredBtn === 'lucky' ? styles.footerButtonHover : undefined), onMouseEnter: () => setHoveredBtn('lucky'), onMouseLeave: () => setHoveredBtn(null), onClick: () => onFeelingLucky?.(query || (activeIndex >= 0 ? allItems[activeIndex]?.query : '')) }, feelingLuckyText)))))),
|
|
13065
13432
|
footer));
|
|
13066
13433
|
});
|
|
13067
13434
|
|
|
@@ -13365,7 +13732,7 @@ const ImageIcon = () => (React.createElement("svg", { viewBox: "0 0 24 24", fill
|
|
|
13365
13732
|
React.createElement("polyline", { points: "21 15 16 10 5 21" })));
|
|
13366
13733
|
const PinterestDropdown = React.forwardRef(function PinterestDropdown(props, ref) {
|
|
13367
13734
|
const { query, isOpen = true, loading = false, suggestions = [], products = [], categories = [], suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, categoryFields = { id: 'id', label: 'label' }, productDisplay = {}, theme = {}, showSaveButton = true, activeCategory: initialActiveCategory, activeTab, // From BaseDropdownProps
|
|
13368
|
-
showPriceOverlay = true, gridColumns = 4, width = '800px', maxHeight = '600px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, onSaveProduct, onViewAll, onClose, header, footer,
|
|
13735
|
+
showPriceOverlay = true, gridColumns = 4, width = '800px', maxHeight = '600px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, onSaveProduct, onViewAll, onClose, header, footer, renderEmpty, } = props;
|
|
13369
13736
|
// Inject global responsive styles
|
|
13370
13737
|
useInjectResponsiveStyles();
|
|
13371
13738
|
// Responsive state
|
|
@@ -13459,47 +13826,47 @@ const PinterestDropdown = React.forwardRef(function PinterestDropdown(props, ref
|
|
|
13459
13826
|
"(",
|
|
13460
13827
|
cat.count,
|
|
13461
13828
|
")"))))))))),
|
|
13462
|
-
React.createElement("div", { style: { ...styles.content, maxHeight } },
|
|
13463
|
-
React.createElement(
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13829
|
+
React.createElement("div", { style: { ...styles.content, maxHeight } },
|
|
13830
|
+
React.createElement(React.Fragment, null,
|
|
13831
|
+
processedSuggestions.length > 0 && (React.createElement("div", { style: styles.suggestionsRow }, processedSuggestions.slice(0, 8).map((s, idx) => (React.createElement("button", { key: s.id || idx, "data-index": idx, style: mergeStyles(styles.suggestionPill, activeIndex === idx ? styles.suggestionPillActive : undefined, hoveredSuggestion === idx && activeIndex !== idx ? styles.suggestionPillHover : undefined), onClick: () => onSuggestionSelect?.(s._raw, idx), onMouseEnter: () => {
|
|
13832
|
+
setHoveredSuggestion(idx);
|
|
13833
|
+
setActiveIndex(idx);
|
|
13834
|
+
}, onMouseLeave: () => setHoveredSuggestion(-1) },
|
|
13835
|
+
React.createElement("span", { style: styles.suggestionIcon }, query ? highlightTextReact(s.query, query, { tag: 'strong' }) : s.query)))))),
|
|
13836
|
+
displayProducts.length > 0 && (React.createElement(React.Fragment, null,
|
|
13837
|
+
React.createElement("div", { style: styles.sectionTitle },
|
|
13838
|
+
React.createElement(TrendingIcon$1, null),
|
|
13839
|
+
"Ideas for you"),
|
|
13840
|
+
React.createElement("div", { style: {
|
|
13841
|
+
...styles.productsGrid,
|
|
13842
|
+
gridTemplateColumns: `repeat(${gridColumns}, 1fr)`,
|
|
13843
|
+
} }, displayProducts.slice(0, 12).map((product, idx) => (React.createElement("div", { key: product.id, style: mergeStyles(styles.productCard, hoveredProduct === product.id ? styles.productCardHover : undefined), onClick: () => onProductClick?.(product._raw, idx), onMouseEnter: () => setHoveredProduct(product.id), onMouseLeave: () => setHoveredProduct(null) },
|
|
13844
|
+
React.createElement("div", { style: styles.productImageContainer },
|
|
13845
|
+
product.image ? (React.createElement("img", { src: product.image, alt: product.title, style: styles.productImage, loading: "lazy" })) : (React.createElement("div", { style: styles.productImagePlaceholder },
|
|
13846
|
+
React.createElement(ImageIcon, null))),
|
|
13847
|
+
showSaveButton && (React.createElement("button", { style: mergeStyles(styles.productSaveBtn, hoveredProduct === product.id ? styles.productSaveBtnVisible : undefined), onClick: (e) => {
|
|
13848
|
+
e.stopPropagation();
|
|
13849
|
+
onSaveProduct?.(product._raw);
|
|
13850
|
+
}, "aria-label": "Save" },
|
|
13851
|
+
React.createElement(HeartIcon, null))),
|
|
13852
|
+
showPriceOverlay && product.price !== undefined && (React.createElement("div", { style: mergeStyles(styles.productOverlay, hoveredProduct === product.id ? styles.productOverlayVisible : undefined) },
|
|
13853
|
+
React.createElement("div", { style: styles.productTitleOverlay }, product.title),
|
|
13854
|
+
React.createElement("div", { style: styles.productPriceOverlay }, formatPrice(product.price, {
|
|
13855
|
+
currency: productDisplay.currency || product.currency || '$'
|
|
13856
|
+
}))))),
|
|
13857
|
+
!showPriceOverlay && (React.createElement("div", { style: styles.productInfo },
|
|
13858
|
+
React.createElement("div", { style: styles.productTitle }, product.title),
|
|
13859
|
+
product.price !== undefined && (React.createElement("div", { style: styles.productPrice }, formatPrice(product.price, {
|
|
13488
13860
|
currency: productDisplay.currency || product.currency || '$'
|
|
13489
|
-
}))))),
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
React.createElement(
|
|
13498
|
-
React.createElement("p", null,
|
|
13499
|
-
"No ideas found for \"",
|
|
13500
|
-
query,
|
|
13501
|
-
"\""),
|
|
13502
|
-
React.createElement("p", { style: { fontSize: '14px', marginTop: '8px' } }, "Try searching for something else"))))))),
|
|
13861
|
+
}))))))))))),
|
|
13862
|
+
!loading && displayProducts.length === 0 && processedSuggestions.length === 0 && (renderEmpty ? renderEmpty(query) : (React.createElement("div", { style: styles.empty },
|
|
13863
|
+
React.createElement("div", { style: styles.emptyIcon },
|
|
13864
|
+
React.createElement(SearchIcon$3, null)),
|
|
13865
|
+
React.createElement("p", null,
|
|
13866
|
+
"No ideas found for \"",
|
|
13867
|
+
query,
|
|
13868
|
+
"\""),
|
|
13869
|
+
React.createElement("p", { style: { fontSize: '14px', marginTop: '8px' } }, "Try searching for something else")))))),
|
|
13503
13870
|
footer !== undefined ? footer : (displayProducts.length > 0 && (React.createElement("div", { style: styles.footer },
|
|
13504
13871
|
React.createElement("span", { style: { color: 'var(--seekora-text-secondary, #767676)', fontSize: '14px' } },
|
|
13505
13872
|
displayProducts.length,
|
|
@@ -13799,7 +14166,7 @@ const ShoppingIcon = () => (React.createElement("svg", { viewBox: "0 0 20 20", f
|
|
|
13799
14166
|
const ClockIcon$1 = () => (React.createElement("svg", { viewBox: "0 0 20 20", fill: "currentColor", width: "16", height: "16" },
|
|
13800
14167
|
React.createElement("path", { fillRule: "evenodd", 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", clipRule: "evenodd" })));
|
|
13801
14168
|
const SpotlightDropdown = React.forwardRef(function SpotlightDropdown(props, ref) {
|
|
13802
|
-
const { query, isOpen = true, loading = false, suggestions = [], products = [], recentSearches = [], suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, productDisplay = {}, theme = {}, showPreview = true, asOverlay = true, placeholder = '
|
|
14169
|
+
const { query, isOpen = true, loading = false, suggestions = [], products = [], recentSearches = [], suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, productDisplay = {}, theme = {}, showPreview = true, asOverlay = true, placeholder = 'Powered by Seekora', showShortcuts = true, actions = [], width = '680px', maxHeight = '500px', zIndex = 9999, className, style, classNames = {}, inputRef, onSuggestionSelect, onProductClick, onRecentClick, onClose, onSearchSubmit, header, footer, renderEmpty, } = props;
|
|
13803
14170
|
// Inject global responsive styles
|
|
13804
14171
|
useInjectResponsiveStyles();
|
|
13805
14172
|
// Responsive state
|
|
@@ -13953,8 +14320,7 @@ const SpotlightDropdown = React.forwardRef(function SpotlightDropdown(props, ref
|
|
|
13953
14320
|
React.createElement("span", { style: styles.kbd }, "K")))),
|
|
13954
14321
|
header,
|
|
13955
14322
|
React.createElement("div", { style: styles.content },
|
|
13956
|
-
React.createElement("div", { ref: listRef, style: { ...styles.resultsColumn, maxHeight } },
|
|
13957
|
-
React.createElement("div", { style: styles.spinner })))) : allItems.length === 0 ? (renderEmpty ? renderEmpty(query) : (React.createElement("div", { style: styles.empty },
|
|
14323
|
+
React.createElement("div", { ref: listRef, style: { ...styles.resultsColumn, maxHeight } }, allItems.length === 0 ? (renderEmpty ? renderEmpty(query) : (React.createElement("div", { style: styles.empty },
|
|
13958
14324
|
"No results for \"",
|
|
13959
14325
|
query,
|
|
13960
14326
|
"\""))) : (React.createElement(React.Fragment, null,
|
|
@@ -14350,7 +14716,7 @@ const SearchIcon$1 = () => (React.createElement("svg", { width: "18", height: "1
|
|
|
14350
14716
|
const ArrowIcon = () => (React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "currentColor" },
|
|
14351
14717
|
React.createElement("path", { fillRule: "evenodd", d: "M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z", clipRule: "evenodd" })));
|
|
14352
14718
|
const ShopifyDropdown = React.forwardRef(function ShopifyDropdown(props, ref) {
|
|
14353
|
-
const { query, isOpen = true, loading = false, suggestions = [], products = [], categories = [], suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, categoryFields = { id: 'id', label: 'label' }, productDisplay = {}, theme = {}, showHeroProduct = true, showCollections = true, showAddToCart = true, addToCartText = 'Quick Add', showComparePrice = true, width = '100%', maxHeight = '500px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, onAddToCart, onViewAll, onClose, header, footer,
|
|
14719
|
+
const { query, isOpen = true, loading = false, suggestions = [], products = [], categories = [], suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, categoryFields = { id: 'id', label: 'label' }, productDisplay = {}, theme = {}, showHeroProduct = true, showCollections = true, showAddToCart = true, addToCartText = 'Quick Add', showComparePrice = true, width = '100%', maxHeight = '500px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, onAddToCart, onViewAll, onClose, header, footer, renderEmpty, } = props;
|
|
14354
14720
|
// Inject global responsive styles
|
|
14355
14721
|
useInjectResponsiveStyles();
|
|
14356
14722
|
// Responsive state
|
|
@@ -14422,8 +14788,7 @@ const ShopifyDropdown = React.forwardRef(function ShopifyDropdown(props, ref) {
|
|
|
14422
14788
|
}
|
|
14423
14789
|
`),
|
|
14424
14790
|
header,
|
|
14425
|
-
|
|
14426
|
-
React.createElement("div", { style: styles.spinner })))) : (React.createElement("div", { style: styles.layout },
|
|
14791
|
+
React.createElement("div", { style: styles.layout },
|
|
14427
14792
|
React.createElement("div", { style: styles.leftPanel },
|
|
14428
14793
|
React.createElement("div", { style: styles.section },
|
|
14429
14794
|
React.createElement("div", { style: styles.sectionHeader },
|
|
@@ -14478,7 +14843,7 @@ const ShopifyDropdown = React.forwardRef(function ShopifyDropdown(props, ref) {
|
|
|
14478
14843
|
React.createElement("div", { style: styles.productTitle }, product.title),
|
|
14479
14844
|
product.price !== undefined && (React.createElement("div", { style: styles.productPrice }, formatPrice(product.price, {
|
|
14480
14845
|
currency: productDisplay.currency || product.currency || '$'
|
|
14481
|
-
})))))))))))
|
|
14846
|
+
}))))))))))),
|
|
14482
14847
|
footer !== undefined ? footer : (React.createElement("div", { style: styles.footer },
|
|
14483
14848
|
React.createElement("span", null,
|
|
14484
14849
|
"Press ",
|
|
@@ -14755,7 +15120,7 @@ const ChevronIcon = () => (React.createElement("svg", { viewBox: "0 0 24 24", fi
|
|
|
14755
15120
|
const ArrowUpIcon = () => (React.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", width: "20", height: "20" },
|
|
14756
15121
|
React.createElement("path", { d: "M7 17l5-5-5-5M13 17l5-5-5-5" })));
|
|
14757
15122
|
const MobileSheetDropdown = React.forwardRef(function MobileSheetDropdown(props, ref) {
|
|
14758
|
-
const { query, isOpen = true, loading = false, suggestions = [], products = [], recentSearches = [], trendingSearches = [], suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, productDisplay = {}, theme = {}, showSearchInput = true, showCancel = true, cancelText = 'Cancel', showDragHandle = true, swipeToDismiss = true, footerButtonText = 'Search', showFooterButton = true, width = '100%', maxHeight = '85vh', zIndex = 9999, className, style, classNames = {}, inputRef, onSuggestionSelect, onProductClick, onRecentClick, onRecentClearAll, onSearchSubmit, onClose, header, footer,
|
|
15123
|
+
const { query, isOpen = true, loading = false, suggestions = [], products = [], recentSearches = [], trendingSearches = [], suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, productDisplay = {}, theme = {}, showSearchInput = true, showCancel = true, cancelText = 'Cancel', showDragHandle = true, swipeToDismiss = true, footerButtonText = 'Search', showFooterButton = true, width = '100%', maxHeight = '85vh', zIndex = 9999, className, style, classNames = {}, inputRef, onSuggestionSelect, onProductClick, onRecentClick, onRecentClearAll, onSearchSubmit, onClose, header, footer, renderEmpty, } = props;
|
|
14759
15124
|
// Inject global responsive styles
|
|
14760
15125
|
useInjectResponsiveStyles();
|
|
14761
15126
|
const styles = React.useMemo(() => createStyles$2(), []);
|
|
@@ -14874,7 +15239,7 @@ const MobileSheetDropdown = React.forwardRef(function MobileSheetDropdown(props,
|
|
|
14874
15239
|
React.createElement("div", { style: styles.searchContainer },
|
|
14875
15240
|
React.createElement("div", { style: styles.searchIcon },
|
|
14876
15241
|
React.createElement(SearchIcon, null)),
|
|
14877
|
-
React.createElement("input", { ref: inputRef, type: "text", placeholder: "
|
|
15242
|
+
React.createElement("input", { ref: inputRef, type: "text", placeholder: "Powered by Seekora", value: inputValue, onChange: (e) => setInputValue(e.target.value), onKeyDown: (e) => {
|
|
14878
15243
|
if (e.key === 'Enter') {
|
|
14879
15244
|
e.preventDefault();
|
|
14880
15245
|
if (activeIndex >= 0) {
|
|
@@ -14897,70 +15262,70 @@ const MobileSheetDropdown = React.forwardRef(function MobileSheetDropdown(props,
|
|
|
14897
15262
|
}
|
|
14898
15263
|
}, style: styles.searchInput, autoFocus: true })),
|
|
14899
15264
|
showCancel && (React.createElement("button", { style: styles.cancelBtn, onClick: onClose }, cancelText)))))),
|
|
14900
|
-
React.createElement("div", { style: styles.content },
|
|
14901
|
-
React.createElement(
|
|
14902
|
-
|
|
14903
|
-
|
|
14904
|
-
|
|
14905
|
-
|
|
14906
|
-
|
|
14907
|
-
onRecentClick?.(q);
|
|
14908
|
-
setInputValue(q);
|
|
14909
|
-
}, onMouseEnter: () => setActiveIndex(idx) },
|
|
14910
|
-
React.createElement("div", { style: styles.itemIcon },
|
|
14911
|
-
React.createElement(ClockIcon, null)),
|
|
14912
|
-
React.createElement("div", { style: styles.itemContent },
|
|
14913
|
-
React.createElement("div", { style: styles.itemTitle }, q)),
|
|
14914
|
-
React.createElement("div", { style: styles.itemAction },
|
|
14915
|
-
React.createElement(ArrowUpIcon, null))))))),
|
|
14916
|
-
showTrending && (React.createElement("div", { style: styles.section },
|
|
14917
|
-
React.createElement("div", { style: styles.sectionHeader },
|
|
14918
|
-
React.createElement("span", { style: styles.sectionTitle }, "Trending")),
|
|
14919
|
-
trendingQueries.map((q, idx) => {
|
|
14920
|
-
const globalIdx = recentQueries.length + idx;
|
|
14921
|
-
return (React.createElement("div", { key: `trending-${q}`, "data-index": globalIdx, style: mergeStyles(styles.item, activeIndex === globalIdx ? styles.itemActive : undefined), onClick: () => {
|
|
15265
|
+
React.createElement("div", { style: styles.content },
|
|
15266
|
+
React.createElement(React.Fragment, null,
|
|
15267
|
+
showRecent && (React.createElement("div", { style: styles.section },
|
|
15268
|
+
React.createElement("div", { style: styles.sectionHeader },
|
|
15269
|
+
React.createElement("span", { style: styles.sectionTitle }, "Recent"),
|
|
15270
|
+
React.createElement("button", { style: styles.clearBtn, onClick: onRecentClearAll }, "Clear")),
|
|
15271
|
+
recentQueries.map((q, idx) => (React.createElement("div", { key: `recent-${q}`, "data-index": idx, style: mergeStyles(styles.item, activeIndex === idx ? styles.itemActive : undefined), onClick: () => {
|
|
14922
15272
|
onRecentClick?.(q);
|
|
14923
15273
|
setInputValue(q);
|
|
14924
|
-
}, onMouseEnter: () => setActiveIndex(
|
|
15274
|
+
}, onMouseEnter: () => setActiveIndex(idx) },
|
|
14925
15275
|
React.createElement("div", { style: styles.itemIcon },
|
|
14926
|
-
React.createElement(
|
|
15276
|
+
React.createElement(ClockIcon, null)),
|
|
14927
15277
|
React.createElement("div", { style: styles.itemContent },
|
|
14928
15278
|
React.createElement("div", { style: styles.itemTitle }, q)),
|
|
14929
15279
|
React.createElement("div", { style: styles.itemAction },
|
|
14930
|
-
React.createElement(
|
|
14931
|
-
}
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
}
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
|
|
15280
|
+
React.createElement(ArrowUpIcon, null))))))),
|
|
15281
|
+
showTrending && (React.createElement("div", { style: styles.section },
|
|
15282
|
+
React.createElement("div", { style: styles.sectionHeader },
|
|
15283
|
+
React.createElement("span", { style: styles.sectionTitle }, "Trending")),
|
|
15284
|
+
trendingQueries.map((q, idx) => {
|
|
15285
|
+
const globalIdx = recentQueries.length + idx;
|
|
15286
|
+
return (React.createElement("div", { key: `trending-${q}`, "data-index": globalIdx, style: mergeStyles(styles.item, activeIndex === globalIdx ? styles.itemActive : undefined), onClick: () => {
|
|
15287
|
+
onRecentClick?.(q);
|
|
15288
|
+
setInputValue(q);
|
|
15289
|
+
}, onMouseEnter: () => setActiveIndex(globalIdx) },
|
|
15290
|
+
React.createElement("div", { style: styles.itemIcon },
|
|
15291
|
+
React.createElement(TrendingIcon, null)),
|
|
15292
|
+
React.createElement("div", { style: styles.itemContent },
|
|
15293
|
+
React.createElement("div", { style: styles.itemTitle }, q)),
|
|
15294
|
+
React.createElement("div", { style: styles.itemAction },
|
|
15295
|
+
React.createElement(ChevronIcon, null))));
|
|
15296
|
+
}))),
|
|
15297
|
+
inputValue && processedSuggestions.length > 0 && (React.createElement("div", { style: styles.section },
|
|
15298
|
+
React.createElement("div", { style: styles.sectionHeader },
|
|
15299
|
+
React.createElement("span", { style: styles.sectionTitle }, "Suggestions")),
|
|
15300
|
+
processedSuggestions.map((s, idx) => {
|
|
15301
|
+
const globalIdx = recentQueries.length + trendingQueries.length + idx;
|
|
15302
|
+
return (React.createElement("div", { key: s.id || idx, "data-index": globalIdx, style: mergeStyles(styles.item, activeIndex === globalIdx ? styles.itemActive : undefined), onClick: () => onSuggestionSelect?.(s._raw, idx), onMouseEnter: () => setActiveIndex(globalIdx) },
|
|
15303
|
+
React.createElement("div", { style: styles.itemIcon },
|
|
15304
|
+
React.createElement(SearchIcon, null)),
|
|
15305
|
+
React.createElement("div", { style: styles.itemContent },
|
|
15306
|
+
React.createElement("div", { style: styles.itemTitle }, highlightTextReact(s.query, inputValue, { tag: 'strong' })),
|
|
15307
|
+
s.count && (React.createElement("div", { style: styles.itemSubtitle },
|
|
15308
|
+
s.count,
|
|
15309
|
+
" results"))),
|
|
15310
|
+
React.createElement("div", { style: styles.itemAction },
|
|
15311
|
+
React.createElement(ChevronIcon, null))));
|
|
15312
|
+
}))),
|
|
15313
|
+
showProducts && (React.createElement("div", { style: styles.section },
|
|
15314
|
+
React.createElement("div", { style: styles.sectionHeader },
|
|
15315
|
+
React.createElement("span", { style: styles.sectionTitle }, "Products")),
|
|
15316
|
+
React.createElement("div", { style: styles.productsScroll }, processedProducts.slice(0, 8).map((p, idx) => (React.createElement("div", { key: p.id, style: styles.productCard, onClick: () => onProductClick?.(p._raw, idx) },
|
|
15317
|
+
p.image ? (React.createElement("img", { src: p.image, alt: p.title, style: styles.productImage, loading: "lazy" })) : (React.createElement("div", { style: styles.productImage })),
|
|
15318
|
+
React.createElement("div", { style: styles.productTitle }, p.title),
|
|
15319
|
+
p.price !== undefined && (React.createElement("div", { style: styles.productPrice }, formatPrice(p.price, {
|
|
15320
|
+
currency: productDisplay.currency || p.currency || '$'
|
|
15321
|
+
}))))))))),
|
|
15322
|
+
inputValue && processedSuggestions.length === 0 && !loading && (renderEmpty ? renderEmpty(inputValue) : (React.createElement("div", { style: styles.empty },
|
|
15323
|
+
React.createElement("div", { style: styles.emptyIcon },
|
|
15324
|
+
React.createElement(SearchIcon, null)),
|
|
15325
|
+
React.createElement("p", null,
|
|
15326
|
+
"No results for \"",
|
|
15327
|
+
inputValue,
|
|
15328
|
+
"\"")))))),
|
|
14964
15329
|
footer !== undefined ? footer : (showFooterButton && inputValue && (React.createElement("div", { style: styles.footer },
|
|
14965
15330
|
React.createElement("button", { style: styles.footerBtn, onClick: () => onSearchSubmit?.(inputValue) },
|
|
14966
15331
|
footerButtonText,
|
|
@@ -15131,7 +15496,7 @@ const createStyles$1 = () => ({
|
|
|
15131
15496
|
},
|
|
15132
15497
|
});
|
|
15133
15498
|
const MinimalDropdown = React.forwardRef(function MinimalDropdown(props, ref) {
|
|
15134
|
-
const { query, isOpen = true, loading = false, suggestions = [], products = [], recentSearches = [], suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, productDisplay = {}, theme = {}, showIndices = false, showTypeLabels = false, showSectionDividers = true, width = '100%', maxHeight = '400px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onProductClick, onRecentClick, onClose, onSearchSubmit, header, footer,
|
|
15499
|
+
const { query, isOpen = true, loading = false, suggestions = [], products = [], recentSearches = [], suggestionFields = { query: 'query' }, productFields = { id: 'id', title: 'title' }, productDisplay = {}, theme = {}, showIndices = false, showTypeLabels = false, showSectionDividers = true, width = '100%', maxHeight = '400px', zIndex = 1000, className, style, classNames = {}, onSuggestionSelect, onProductClick, onRecentClick, onClose, onSearchSubmit, header, footer, renderEmpty, } = props;
|
|
15135
15500
|
// Inject global responsive styles
|
|
15136
15501
|
useInjectResponsiveStyles();
|
|
15137
15502
|
// Responsive state
|
|
@@ -15217,7 +15582,7 @@ const MinimalDropdown = React.forwardRef(function MinimalDropdown(props, ref) {
|
|
|
15217
15582
|
}
|
|
15218
15583
|
`),
|
|
15219
15584
|
header,
|
|
15220
|
-
React.createElement("div", { ref: listRef, style: { maxHeight, overflowY: 'auto' } },
|
|
15585
|
+
React.createElement("div", { ref: listRef, style: { maxHeight, overflowY: 'auto' } }, allItems.length === 0 ? (renderEmpty ? renderEmpty(query) : (React.createElement("div", { style: styles.empty }, query ? `No results for "${query}"` : 'Start typing to search'))) : (React.createElement(React.Fragment, null,
|
|
15221
15586
|
showRecent && (React.createElement(React.Fragment, null,
|
|
15222
15587
|
showSectionDividers && (React.createElement("div", { style: styles.divider }, "Recent")),
|
|
15223
15588
|
recentQueries.map((q, idx) => {
|
|
@@ -15425,24 +15790,12 @@ const createStyles = (isMobile) => ({
|
|
|
15425
15790
|
flexShrink: 0,
|
|
15426
15791
|
transition: 'color 150ms',
|
|
15427
15792
|
},
|
|
15428
|
-
loadingSpinner: {
|
|
15429
|
-
width: '18px',
|
|
15430
|
-
height: '18px',
|
|
15431
|
-
borderWidth: '2px',
|
|
15432
|
-
borderStyle: 'solid',
|
|
15433
|
-
borderColor: '#e5e7eb',
|
|
15434
|
-
borderTopColor: '#f90',
|
|
15435
|
-
borderRadius: '50%',
|
|
15436
|
-
animation: 'seekora-spin 0.8s linear infinite',
|
|
15437
|
-
marginRight: '12px',
|
|
15438
|
-
flexShrink: 0,
|
|
15439
|
-
},
|
|
15440
15793
|
});
|
|
15441
15794
|
// ============================================================================
|
|
15442
15795
|
// Component
|
|
15443
15796
|
// ============================================================================
|
|
15444
15797
|
const SuggestionSearchBar = React.forwardRef(function SuggestionSearchBar(props, ref) {
|
|
15445
|
-
const { client, variant = 'amazon', autoMobileVariant = true, placeholder = '
|
|
15798
|
+
const { client, variant = 'amazon', autoMobileVariant = true, placeholder = 'Powered by Seekora', defaultQuery = '', value, minQueryLength = 1, maxSuggestions = 8, debounceMs = 200, includeDropdownRecommendations = true, includeDropdownProductList = true, includeFilteredTabs = 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;
|
|
15446
15799
|
// Theme: prop overrides context (SearchProvider theme)
|
|
15447
15800
|
const searchContext = useSearchContext();
|
|
15448
15801
|
const effectiveTheme = theme ?? searchContext.theme;
|
|
@@ -15906,8 +16259,7 @@ const SuggestionSearchBar = React.forwardRef(function SuggestionSearchBar(props,
|
|
|
15906
16259
|
React.createElement("svg", { viewBox: "0 0 20 20", fill: "currentColor", width: "20", height: "20" },
|
|
15907
16260
|
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" }))),
|
|
15908
16261
|
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" }),
|
|
15909
|
-
|
|
15910
|
-
currentQuery && !loading && (React.createElement("button", { type: "button", onClick: handleClear, style: styles.clearButton, "aria-label": "Clear search" }, "\u00D7"))),
|
|
16262
|
+
currentQuery && (React.createElement("button", { type: "button", onClick: handleClear, style: styles.clearButton, "aria-label": "Clear search" }, "\u00D7"))),
|
|
15911
16263
|
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: {
|
|
15912
16264
|
enabled: enableAnalytics,
|
|
15913
16265
|
trackSuggestionClicks: analyticsConfig?.trackSuggestionClicks ?? true,
|
|
@@ -15992,18 +16344,16 @@ function Modal({ isOpen, onClose, children }) {
|
|
|
15992
16344
|
return reactDom.createPortal(modalContent, document.body);
|
|
15993
16345
|
}
|
|
15994
16346
|
|
|
15995
|
-
function SearchBox({ value, onChange, onKeyDown, placeholder = 'Search documentation...',
|
|
16347
|
+
function SearchBox({ value, onChange, onKeyDown, placeholder = 'Search documentation...', onClear, }) {
|
|
15996
16348
|
const inputRef = React.useRef(null);
|
|
15997
16349
|
React.useEffect(() => { if (inputRef.current)
|
|
15998
16350
|
inputRef.current.focus(); }, []);
|
|
15999
16351
|
const handleChange = (event) => onChange(event.target.value);
|
|
16000
16352
|
const handleClear = () => { onChange(''); onClear?.(); inputRef.current?.focus(); };
|
|
16001
16353
|
return (React.createElement("div", { className: "seekora-docsearch-searchbox" },
|
|
16002
|
-
React.createElement("label", { className: "seekora-docsearch-searchbox-icon", htmlFor: "seekora-docsearch-input" },
|
|
16003
|
-
React.createElement("svg", { width: "20", height: "20", viewBox: "0 0 20 20" },
|
|
16004
|
-
React.createElement("
|
|
16005
|
-
React.createElement("animateTransform", { attributeName: "transform", type: "rotate", from: "0 10 10", to: "360 10 10", dur: "0.8s", repeatCount: "indefinite" }))))) : (React.createElement("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" },
|
|
16006
|
-
React.createElement("path", { d: "M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z", fill: "currentColor" })))),
|
|
16354
|
+
React.createElement("label", { className: "seekora-docsearch-searchbox-icon", htmlFor: "seekora-docsearch-input" },
|
|
16355
|
+
React.createElement("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" },
|
|
16356
|
+
React.createElement("path", { d: "M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z", fill: "currentColor" }))),
|
|
16007
16357
|
React.createElement("input", { ref: inputRef, id: "seekora-docsearch-input", className: "seekora-docsearch-input", type: "text", value: value, onChange: handleChange, onKeyDown: onKeyDown, placeholder: placeholder, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: false, "aria-autocomplete": "list", "aria-controls": "seekora-docsearch-results" }),
|
|
16008
16358
|
value && (React.createElement("button", { type: "button", className: "seekora-docsearch-clear", onClick: handleClear, "aria-label": "Clear search" },
|
|
16009
16359
|
React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" },
|
|
@@ -16232,7 +16582,7 @@ function getHitKey(hit, index) {
|
|
|
16232
16582
|
return hit.objectID;
|
|
16233
16583
|
return `suggestion-${hit.url}-${index}`;
|
|
16234
16584
|
}
|
|
16235
|
-
function Results({ hits, groupedHits, selectedIndex, onSelect, onHover, scrollSelectionIntoViewRef, query, isLoading,
|
|
16585
|
+
function Results({ hits, groupedHits, selectedIndex, onSelect, onHover, scrollSelectionIntoViewRef, query, isLoading, error, translations = {}, sources: _sources = [], }) {
|
|
16236
16586
|
const listRef = React.useRef(null);
|
|
16237
16587
|
React.useEffect(() => {
|
|
16238
16588
|
if (!listRef.current || hits.length === 0)
|
|
@@ -16266,14 +16616,6 @@ function Results({ hits, groupedHits, selectedIndex, onSelect, onHover, scrollSe
|
|
|
16266
16616
|
return (React.createElement("div", { className: "seekora-docsearch-empty" },
|
|
16267
16617
|
React.createElement("p", { className: "seekora-docsearch-empty-text" }, translations.searchPlaceholder || 'Type to start searching...')));
|
|
16268
16618
|
}
|
|
16269
|
-
if (isLoading && hits.length === 0 && showLoadingState) {
|
|
16270
|
-
return (React.createElement("div", { className: "seekora-docsearch-loading" },
|
|
16271
|
-
React.createElement("div", { className: "seekora-docsearch-loading-spinner" },
|
|
16272
|
-
React.createElement("svg", { width: "24", height: "24", viewBox: "0 0 24 24", "aria-hidden": "true" },
|
|
16273
|
-
React.createElement("circle", { cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "2", fill: "none", strokeDasharray: "50", strokeDashoffset: "15" },
|
|
16274
|
-
React.createElement("animateTransform", { attributeName: "transform", type: "rotate", from: "0 12 12", to: "360 12 12", dur: "0.8s", repeatCount: "indefinite" })))),
|
|
16275
|
-
React.createElement("p", { className: "seekora-docsearch-loading-text" }, translations.loadingText || 'Searching...')));
|
|
16276
|
-
}
|
|
16277
16619
|
if (isLoading && hits.length === 0) {
|
|
16278
16620
|
return React.createElement("div", { className: "seekora-docsearch-empty" });
|
|
16279
16621
|
}
|
|
@@ -16686,6 +17028,12 @@ function useSeekoraSearch$1(options) {
|
|
|
16686
17028
|
readSecret: storeSecret,
|
|
16687
17029
|
logLevel: 'warn',
|
|
16688
17030
|
enableContextCollection: true,
|
|
17031
|
+
enableEventQueue: true, // Enable batching for better performance
|
|
17032
|
+
eventQueue: {
|
|
17033
|
+
batchSize: 10,
|
|
17034
|
+
flushInterval: 5000, // 5 seconds
|
|
17035
|
+
enablePersistence: true,
|
|
17036
|
+
},
|
|
16689
17037
|
};
|
|
16690
17038
|
if (apiEndpoint) {
|
|
16691
17039
|
if (['local', 'stage', 'production'].includes(apiEndpoint)) {
|
|
@@ -16897,7 +17245,7 @@ function DocSearch({ storeId, storeSecret, seekoraApiEndpoint, apiEndpoint, apiK
|
|
|
16897
17245
|
React.createElement(Modal, { isOpen: isOpen, onClose: handleClose },
|
|
16898
17246
|
React.createElement("div", { className: "seekora-docsearch-modal", onKeyDown: handleKeyDown },
|
|
16899
17247
|
React.createElement("header", { className: "seekora-docsearch-header" },
|
|
16900
|
-
React.createElement(SearchBox, { value: query, onChange: setQuery, onKeyDown: handleKeyDown, placeholder: placeholder,
|
|
17248
|
+
React.createElement(SearchBox, { value: query, onChange: setQuery, onKeyDown: handleKeyDown, placeholder: placeholder, onClear: reset }),
|
|
16901
17249
|
React.createElement("button", { type: "button", className: "seekora-docsearch-close", onClick: handleClose, "aria-label": "Close search" },
|
|
16902
17250
|
React.createElement("span", { className: "seekora-docsearch-close-text" }, "esc"))),
|
|
16903
17251
|
React.createElement("div", { className: "seekora-docsearch-body" },
|
|
@@ -18236,7 +18584,6 @@ exports.SearchProvider = SearchProvider;
|
|
|
18236
18584
|
exports.SearchResults = SearchResults;
|
|
18237
18585
|
exports.SectionError = SectionError;
|
|
18238
18586
|
exports.SectionItemGrid = SectionItemGrid;
|
|
18239
|
-
exports.SectionLoading = SectionLoading;
|
|
18240
18587
|
exports.SectionSearchProvider = SectionSearchProvider;
|
|
18241
18588
|
exports.ShopifyDropdown = ShopifyDropdown;
|
|
18242
18589
|
exports.Snippet = Snippet;
|
|
@@ -18249,7 +18596,6 @@ exports.SuggestionList = SuggestionList;
|
|
|
18249
18596
|
exports.SuggestionSearchBar = SuggestionSearchBar;
|
|
18250
18597
|
exports.SuggestionsDropdownComposition = SuggestionsDropdownComposition;
|
|
18251
18598
|
exports.SuggestionsError = SuggestionsError;
|
|
18252
|
-
exports.SuggestionsLoading = SuggestionsLoading;
|
|
18253
18599
|
exports.SuggestionsProvider = SuggestionsProvider;
|
|
18254
18600
|
exports.TrendingItems = TrendingItems;
|
|
18255
18601
|
exports.TrendingList = TrendingList;
|
|
@@ -18299,6 +18645,7 @@ exports.useAnalytics = useAnalytics;
|
|
|
18299
18645
|
exports.useAnalyticsProvider = useAnalyticsProvider;
|
|
18300
18646
|
exports.useDocSearch = useDocSearch;
|
|
18301
18647
|
exports.useDocSearchSeekoraSearch = useSeekoraSearch$1;
|
|
18648
|
+
exports.useFilters = useFilters;
|
|
18302
18649
|
exports.useInjectResponsiveStyles = useInjectResponsiveStyles;
|
|
18303
18650
|
exports.useKeyboard = useKeyboard;
|
|
18304
18651
|
exports.useNaturalLanguageFilters = useNaturalLanguageFilters;
|