@seekora-ai/ui-sdk-react 0.2.7 → 0.2.9

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.
@@ -1074,6 +1074,10 @@ interface RichQuerySuggestionsProps extends QuerySuggestionsEventHandlers {
1074
1074
  debounceMs?: number;
1075
1075
  /** Include dropdown recommendations from API */
1076
1076
  includeDropdownRecommendations?: boolean;
1077
+ /** When false, omit product hits list from dropdown (default true) */
1078
+ includeDropdownProductList?: boolean;
1079
+ /** When false, omit filtered_tabs from dropdown (default true) */
1080
+ includeFilteredTabs?: boolean;
1077
1081
  /** Include categories in suggestions */
1078
1082
  includeCategories?: boolean;
1079
1083
  /** Max categories per suggestion */
@@ -1346,6 +1350,10 @@ interface UseQuerySuggestionsEnhancedOptions {
1346
1350
  minQueryLength?: number;
1347
1351
  /** Include dropdown recommendations (trending, products, etc.) */
1348
1352
  includeDropdownRecommendations?: boolean;
1353
+ /** When false, omit product hits list from dropdown (default true) */
1354
+ includeDropdownProductList?: boolean;
1355
+ /** When false, omit filtered_tabs from dropdown extensions (default true) */
1356
+ includeFilteredTabs?: boolean;
1349
1357
  /** Include categories in suggestions */
1350
1358
  includeCategories?: boolean;
1351
1359
  /** Include facets in suggestions */
@@ -1518,13 +1526,17 @@ interface SearchInputProps {
1518
1526
  placeholder?: string;
1519
1527
  autoFocus?: boolean;
1520
1528
  showClearButton?: boolean;
1529
+ /** When false, blur does not close the dropdown (e.g. for overlay mode). Default true. */
1530
+ closeOnBlur?: boolean;
1531
+ /** Optional icon (e.g. magnifying glass) rendered to the left of the input */
1532
+ leftIcon?: React__default.ReactNode;
1521
1533
  className?: string;
1522
1534
  style?: React__default.CSSProperties;
1523
1535
  inputClassName?: string;
1524
1536
  inputStyle?: React__default.CSSProperties;
1525
1537
  ariaLabel?: string;
1526
1538
  }
1527
- declare function SearchInput({ placeholder, autoFocus, showClearButton, className, style, inputClassName, inputStyle, ariaLabel, }: SearchInputProps): React__default.JSX.Element;
1539
+ declare function SearchInput({ placeholder, autoFocus, showClearButton, closeOnBlur, leftIcon, className, style, inputClassName, inputStyle, ariaLabel, }: SearchInputProps): React__default.JSX.Element;
1528
1540
 
1529
1541
  /**
1530
1542
  * DropdownPanel – floating panel for suggestions (primitive)
@@ -2640,6 +2652,10 @@ interface SuggestionSearchBarProps {
2640
2652
  debounceMs?: number;
2641
2653
  /** Include dropdown recommendations (products, tabs, trending) */
2642
2654
  includeDropdownRecommendations?: boolean;
2655
+ /** When false, omit product hits list from dropdown (default true) */
2656
+ includeDropdownProductList?: boolean;
2657
+ /** When false, omit filtered_tabs from dropdown (default true) */
2658
+ includeFilteredTabs?: boolean;
2643
2659
  /** Include categories per suggestion */
2644
2660
  includeCategories?: boolean;
2645
2661
  /** Filtered tabs configuration */
@@ -4534,15 +4534,16 @@ function parseHighlight(highlighted) {
4534
4534
  .replace(/__\/ais-highlight__/g, '</mark>');
4535
4535
  }
4536
4536
  function transformProduct(raw) {
4537
+ const meta = raw.metadata || {};
4537
4538
  return {
4538
4539
  id: raw.id || raw.objectID,
4539
4540
  objectID: raw.objectID || raw.id,
4540
- title: raw.title || raw.name || raw.productName,
4541
- name: raw.name || raw.title,
4542
- image: raw.image || raw.imageUrl || raw.metadata?.image,
4543
- price: raw.price || raw.sellPrice || raw.metadata?.sellPrice,
4544
- currency: raw.currency || raw.metadata?.currency || '',
4545
- url: raw.url || raw.productId || raw.metadata?.url,
4541
+ title: raw.title || raw.name || raw.productName || meta.name || meta.productName || '',
4542
+ name: raw.name || raw.title || meta.name || meta.productName || '',
4543
+ image: raw.image || raw.imageUrl || meta.image || meta.image_url || meta.images?.[0] || '',
4544
+ price: raw.price ?? raw.sellPrice ?? meta.sellPrice ?? meta.price,
4545
+ currency: raw.currency || meta.currency || '',
4546
+ url: raw.url || raw.productId || meta.url || meta.productId || '',
4546
4547
  clicks: raw.clicks,
4547
4548
  conversions: raw.conversions,
4548
4549
  revenue: raw.revenue,
@@ -4564,7 +4565,7 @@ function transformFilteredTab(raw) {
4564
4565
  // Main Hook
4565
4566
  // ============================================================================
4566
4567
  function useQuerySuggestionsEnhanced(options) {
4567
- const { client, query, enabled = true, debounceMs = 200, maxSuggestions = 10, minQueryLength = 1, includeDropdownRecommendations = false, includeCategories = true, includeFacets = false, maxCategories = 3, maxFacets = 5, filteredTabs, minPopularity, timeRange, disableTypoTolerance, analyticsTags, enableRecentSearches = true, maxRecentSearches = MAX_RECENT_SEARCHES_DEFAULT, recentSearchesKey = RECENT_SEARCHES_DEFAULT_KEY, onSuggestionsLoaded, onError, } = options;
4568
+ const { client, query, enabled = true, debounceMs = 200, maxSuggestions = 10, minQueryLength = 1, includeDropdownRecommendations = false, includeDropdownProductList = true, includeFilteredTabs = true, includeCategories = true, includeFacets = false, maxCategories = 3, maxFacets = 5, filteredTabs, minPopularity, timeRange, disableTypoTolerance, analyticsTags, enableRecentSearches = true, maxRecentSearches = MAX_RECENT_SEARCHES_DEFAULT, recentSearchesKey = RECENT_SEARCHES_DEFAULT_KEY, onSuggestionsLoaded, onError, } = options;
4568
4569
  // State
4569
4570
  const [suggestions, setSuggestions] = useState([]);
4570
4571
  const [loading, setLoading] = useState(false);
@@ -4592,7 +4593,10 @@ function useQuerySuggestionsEnhanced(options) {
4592
4593
  }, [enableRecentSearches, recentSearchesKey, maxRecentSearches]);
4593
4594
  // Fetch suggestions
4594
4595
  const fetchSuggestions = useCallback(async (searchQuery) => {
4595
- if (!client || !searchQuery.trim()) {
4596
+ if (!client)
4597
+ return;
4598
+ // When minQueryLength is 0, allow empty query so overlay can show default/trending recommendations on open
4599
+ if (!searchQuery.trim() && minQueryLength > 0) {
4596
4600
  setSuggestions([]);
4597
4601
  setDropdownRecommendations(null);
4598
4602
  return;
@@ -4608,6 +4612,8 @@ function useQuerySuggestionsEnhanced(options) {
4608
4612
  const response = await client.getSuggestions?.(searchQuery, {
4609
4613
  hitsPerPage: maxSuggestions,
4610
4614
  include_dropdown_recommendations: includeDropdownRecommendations || (filteredTabs && filteredTabs.length > 0),
4615
+ include_dropdown_product_list: includeDropdownProductList,
4616
+ include_filtered_tabs: includeFilteredTabs,
4611
4617
  include_categories: includeCategories,
4612
4618
  include_facets: includeFacets,
4613
4619
  max_categories: maxCategories,
@@ -4627,12 +4633,17 @@ function useQuerySuggestionsEnhanced(options) {
4627
4633
  setSuggestions(transformedSuggestions);
4628
4634
  // Extract dropdown recommendations from extensions
4629
4635
  const extensions = (response.extensions || {});
4636
+ const rawResults = response.results;
4637
+ const secondResultHits = Array.isArray(rawResults?.[1]?.hits) ? rawResults[1].hits : [];
4638
+ // Multi-index response: results[0]=suggestions, results[1]=product hits; expose results[1].hits as product_hits for fallback when extensions have no products
4639
+ const productHits = secondResultHits.length > 0 ? secondResultHits.map((h) => transformProduct(h)) : [];
4630
4640
  const recommendations = {
4631
4641
  trending_searches: Array.isArray(extensions.trending_searches) ? extensions.trending_searches : [],
4632
4642
  top_searches: Array.isArray(extensions.top_searches) ? extensions.top_searches : [],
4633
4643
  related_searches: Array.isArray(extensions.related_searches) ? extensions.related_searches : [],
4634
4644
  trending_products: Array.isArray(extensions.trending_products) ? extensions.trending_products.map(transformProduct) : [],
4635
4645
  item_recommendations: Array.isArray(extensions.item_recommendations) ? extensions.item_recommendations.map(transformProduct) : [],
4646
+ product_hits: productHits.length > 0 ? productHits : undefined,
4636
4647
  popular_brands: Array.isArray(extensions.popular_brands) ? extensions.popular_brands : [],
4637
4648
  filtered_tabs: Array.isArray(extensions.filtered_tabs) ? extensions.filtered_tabs.map(transformFilteredTab) : [],
4638
4649
  processing_time_ms: typeof extensions.processing_time_ms === 'number' ? extensions.processing_time_ms : undefined,
@@ -4692,8 +4703,11 @@ function useQuerySuggestionsEnhanced(options) {
4692
4703
  }
4693
4704
  }, [
4694
4705
  client,
4706
+ minQueryLength,
4695
4707
  maxSuggestions,
4696
4708
  includeDropdownRecommendations,
4709
+ includeDropdownProductList,
4710
+ includeFilteredTabs,
4697
4711
  includeCategories,
4698
4712
  includeFacets,
4699
4713
  maxCategories,
@@ -4768,7 +4782,19 @@ function useQuerySuggestionsEnhanced(options) {
4768
4782
  const relatedSearches = dropdownRecommendations?.related_searches || [];
4769
4783
  const popularBrands = dropdownRecommendations?.popular_brands || [];
4770
4784
  const filteredTabsResult = dropdownRecommendations?.filtered_tabs || [];
4771
- const trendingProducts = dropdownRecommendations?.trending_products || [];
4785
+ // Use trending_products, then item_recommendations, then first filtered_tab's products, then results[1].hits (product_hits) so grid shows for any API shape
4786
+ const trendingProducts = useMemo(() => {
4787
+ const fromTrending = dropdownRecommendations?.trending_products;
4788
+ if (fromTrending && fromTrending.length > 0)
4789
+ return fromTrending;
4790
+ const fromItemRecs = dropdownRecommendations?.item_recommendations;
4791
+ if (fromItemRecs && fromItemRecs.length > 0)
4792
+ return fromItemRecs;
4793
+ const firstTab = dropdownRecommendations?.filtered_tabs?.[0];
4794
+ if (firstTab?.products && firstTab.products.length > 0)
4795
+ return firstTab.products;
4796
+ return dropdownRecommendations?.product_hits ?? [];
4797
+ }, [dropdownRecommendations?.trending_products, dropdownRecommendations?.item_recommendations, dropdownRecommendations?.filtered_tabs, dropdownRecommendations?.product_hits]);
4772
4798
  const hasContent = useMemo(() => {
4773
4799
  return (suggestions.length > 0 ||
4774
4800
  recentSearches.length > 0 ||
@@ -5451,7 +5477,7 @@ const CloseIcon = () => (React.createElement("svg", { viewBox: "0 0 20 20", fill
5451
5477
  // Component
5452
5478
  // ============================================================================
5453
5479
  const RichQuerySuggestions = forwardRef(function RichQuerySuggestions(props, ref) {
5454
- const { query, isOpen = true, sections = DEFAULT_SECTIONS, maxSuggestionsPerSection = 8, minQueryLength = 0, debounceMs = 200, includeDropdownRecommendations = 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;
5480
+ 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;
5455
5481
  const { client } = useSearchContext();
5456
5482
  const containerRef = useRef(null);
5457
5483
  const [activeIndex, setActiveIndex] = useState(-1);
@@ -5465,6 +5491,8 @@ const RichQuerySuggestions = forwardRef(function RichQuerySuggestions(props, ref
5465
5491
  maxSuggestions: maxSuggestionsPerSection,
5466
5492
  minQueryLength,
5467
5493
  includeDropdownRecommendations,
5494
+ includeDropdownProductList,
5495
+ includeFilteredTabs,
5468
5496
  includeCategories,
5469
5497
  maxCategories,
5470
5498
  analyticsTags,
@@ -6969,15 +6997,17 @@ const inputStyles = {
6969
6997
  color: 'var(--seekora-text-primary, #111827)',
6970
6998
  fontFamily: 'inherit',
6971
6999
  };
6972
- function SearchInput({ placeholder = 'Search...', autoFocus = false, showClearButton = true, className, style, inputClassName, inputStyle, ariaLabel = 'Search', }) {
7000
+ function SearchInput({ placeholder = 'Search...', autoFocus = false, showClearButton = true, closeOnBlur = true, leftIcon, className, style, inputClassName, inputStyle, ariaLabel = 'Search', }) {
6973
7001
  const { query, setQuery, isOpen, setIsOpen, navigateNext, navigatePrev, selectActive, close, } = useSuggestionsContext();
6974
7002
  const inputRef = useRef(null);
6975
7003
  const handleFocus = useCallback(() => {
6976
7004
  setIsOpen(true);
6977
7005
  }, [setIsOpen]);
6978
7006
  const handleBlur = useCallback(() => {
7007
+ if (!closeOnBlur)
7008
+ return;
6979
7009
  setTimeout(() => close(), 200);
6980
- }, [close]);
7010
+ }, [close, closeOnBlur]);
6981
7011
  const handleChange = useCallback((e) => {
6982
7012
  setQuery(e.target.value);
6983
7013
  }, [setQuery]);
@@ -7010,6 +7040,7 @@ function SearchInput({ placeholder = 'Search...', autoFocus = false, showClearBu
7010
7040
  }, [setQuery]);
7011
7041
  return (React.createElement("div", { className: clsx('seekora-suggestions-search-input-wrapper', className), style: { ...defaultStyles, ...style } },
7012
7042
  React.createElement("div", { className: "seekora-suggestions-input-wrapper", style: inputWrapperStyles },
7043
+ leftIcon ? (React.createElement("span", { className: "seekora-suggestions-input-left-icon", style: { display: 'flex', flexShrink: 0, color: 'var(--seekora-text-secondary, #6b7280)' } }, leftIcon)) : null,
7013
7044
  React.createElement("input", { ref: inputRef, type: "text", value: query, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, placeholder: placeholder, autoFocus: autoFocus, autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: false, "aria-label": ariaLabel, "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-autocomplete": "list", role: "combobox", className: clsx('seekora-suggestions-input', inputClassName), style: { ...inputStyles, ...inputStyle } }),
7014
7045
  showClearButton && query ? (React.createElement("button", { type: "button", onClick: handleClear, className: "seekora-suggestions-input-clear", "aria-label": "Clear search", style: {
7015
7046
  padding: 4,
@@ -11765,7 +11796,7 @@ const createStyles = (isMobile) => ({
11765
11796
  // Component
11766
11797
  // ============================================================================
11767
11798
  const SuggestionSearchBar = forwardRef(function SuggestionSearchBar(props, ref) {
11768
- const { client, variant = 'amazon', autoMobileVariant = true, placeholder = 'Search...', defaultQuery = '', value, minQueryLength = 1, maxSuggestions = 8, debounceMs = 200, includeDropdownRecommendations = true, includeCategories = true, filteredTabs, analyticsTags, enableRecentSearches = true, maxRecentSearches = 10, showProducts = true, showTrendingOnEmpty = true, enableAnalytics = true, analyticsConfig, suggestionFields, productFields, theme, onSearch, onQueryChange, onSuggestionSelect, onProductClick, onCategoryClick, onTabChange, className, style, inputClassName, dropdownWidth, dropdownMaxHeight = '500px', zIndex = 1000, enableCache = true, cacheTtlMs = 30000, cacheMaxSize = 100, } = props;
11799
+ const { client, variant = 'amazon', autoMobileVariant = true, placeholder = 'Search...', 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;
11769
11800
  // Theme: prop overrides context (SearchProvider theme)
11770
11801
  const searchContext = useSearchContext();
11771
11802
  const effectiveTheme = theme ?? searchContext.theme;
@@ -11848,6 +11879,8 @@ const SuggestionSearchBar = forwardRef(function SuggestionSearchBar(props, ref)
11848
11879
  const cacheOptions = {
11849
11880
  maxSuggestions,
11850
11881
  includeDropdownRecommendations,
11882
+ includeDropdownProductList,
11883
+ includeFilteredTabs,
11851
11884
  includeCategories,
11852
11885
  filteredTabs: filteredTabs?.map(t => t.filter).join(','),
11853
11886
  };
@@ -11868,6 +11901,8 @@ const SuggestionSearchBar = forwardRef(function SuggestionSearchBar(props, ref)
11868
11901
  const response = await client.getSuggestions?.(searchQuery, {
11869
11902
  hitsPerPage: maxSuggestions,
11870
11903
  include_dropdown_recommendations: includeDropdownRecommendations,
11904
+ include_dropdown_product_list: includeDropdownProductList,
11905
+ include_filtered_tabs: includeFilteredTabs,
11871
11906
  include_categories: includeCategories,
11872
11907
  filtered_tabs: filteredTabs,
11873
11908
  analytics_tags: analyticsTags,
@@ -11906,7 +11941,7 @@ const SuggestionSearchBar = forwardRef(function SuggestionSearchBar(props, ref)
11906
11941
  finally {
11907
11942
  setLoading(false);
11908
11943
  }
11909
- }, [client, minQueryLength, maxSuggestions, includeDropdownRecommendations, includeCategories, filteredTabs, analyticsTags, enableAnalytics, analytics, cache]);
11944
+ }, [client, minQueryLength, maxSuggestions, includeDropdownRecommendations, includeDropdownProductList, includeFilteredTabs, includeCategories, filteredTabs, analyticsTags, enableAnalytics, analytics, cache]);
11910
11945
  // Parse API response - handles multiple response formats
11911
11946
  const parseAndSetData = useCallback((response) => {
11912
11947
  // Handle different response structures from the API/SDK