@seekora-ai/ui-sdk-react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/components/Breadcrumb.d.ts +43 -0
  2. package/dist/components/Breadcrumb.d.ts.map +1 -0
  3. package/dist/components/Breadcrumb.js +119 -0
  4. package/dist/components/ClearRefinements.d.ts +42 -0
  5. package/dist/components/ClearRefinements.d.ts.map +1 -0
  6. package/dist/components/ClearRefinements.js +80 -0
  7. package/dist/components/CurrentRefinements.d.ts +41 -0
  8. package/dist/components/CurrentRefinements.d.ts.map +1 -0
  9. package/dist/components/CurrentRefinements.js +83 -0
  10. package/dist/components/Facets.d.ts +53 -0
  11. package/dist/components/Facets.d.ts.map +1 -0
  12. package/dist/components/Facets.js +195 -0
  13. package/dist/components/FederatedDropdown.d.ts +92 -0
  14. package/dist/components/FederatedDropdown.d.ts.map +1 -0
  15. package/dist/components/FederatedDropdown.js +510 -0
  16. package/dist/components/HierarchicalMenu.d.ts +55 -0
  17. package/dist/components/HierarchicalMenu.d.ts.map +1 -0
  18. package/dist/components/HierarchicalMenu.js +168 -0
  19. package/dist/components/Highlight.d.ts +51 -0
  20. package/dist/components/Highlight.d.ts.map +1 -0
  21. package/dist/components/Highlight.js +155 -0
  22. package/dist/components/HitsPerPage.d.ts +41 -0
  23. package/dist/components/HitsPerPage.d.ts.map +1 -0
  24. package/dist/components/HitsPerPage.js +72 -0
  25. package/dist/components/InfiniteHits.d.ts +56 -0
  26. package/dist/components/InfiniteHits.d.ts.map +1 -0
  27. package/dist/components/InfiniteHits.js +181 -0
  28. package/dist/components/MobileFilters.d.ts +71 -0
  29. package/dist/components/MobileFilters.d.ts.map +1 -0
  30. package/dist/components/MobileFilters.js +242 -0
  31. package/dist/components/Pagination.d.ts +44 -0
  32. package/dist/components/Pagination.d.ts.map +1 -0
  33. package/dist/components/Pagination.js +142 -0
  34. package/dist/components/QuerySuggestions.d.ts +38 -0
  35. package/dist/components/QuerySuggestions.d.ts.map +1 -0
  36. package/dist/components/QuerySuggestions.js +86 -0
  37. package/dist/components/QuerySuggestionsDropdown.d.ts +86 -0
  38. package/dist/components/QuerySuggestionsDropdown.d.ts.map +1 -0
  39. package/dist/components/QuerySuggestionsDropdown.js +395 -0
  40. package/dist/components/RangeInput.d.ts +58 -0
  41. package/dist/components/RangeInput.d.ts.map +1 -0
  42. package/dist/components/RangeInput.js +203 -0
  43. package/dist/components/RangeSlider.d.ts +51 -0
  44. package/dist/components/RangeSlider.d.ts.map +1 -0
  45. package/dist/components/RangeSlider.js +193 -0
  46. package/dist/components/Recommendations.d.ts +90 -0
  47. package/dist/components/Recommendations.d.ts.map +1 -0
  48. package/dist/components/Recommendations.js +270 -0
  49. package/dist/components/RichQuerySuggestions.d.ts +77 -0
  50. package/dist/components/RichQuerySuggestions.d.ts.map +1 -0
  51. package/dist/components/RichQuerySuggestions.js +492 -0
  52. package/dist/components/SearchBar.d.ts +40 -0
  53. package/dist/components/SearchBar.d.ts.map +1 -0
  54. package/dist/components/SearchBar.js +217 -0
  55. package/dist/components/SearchBarWithSuggestions.d.ts +99 -0
  56. package/dist/components/SearchBarWithSuggestions.d.ts.map +1 -0
  57. package/dist/components/SearchBarWithSuggestions.js +275 -0
  58. package/dist/components/SearchLayout.d.ts +35 -0
  59. package/dist/components/SearchLayout.d.ts.map +1 -0
  60. package/dist/components/SearchLayout.js +56 -0
  61. package/dist/components/SearchProvider.d.ts +28 -0
  62. package/dist/components/SearchProvider.d.ts.map +1 -0
  63. package/dist/components/SearchProvider.js +43 -0
  64. package/dist/components/SearchResults.d.ts +51 -0
  65. package/dist/components/SearchResults.d.ts.map +1 -0
  66. package/dist/components/SearchResults.js +485 -0
  67. package/dist/components/SortBy.d.ts +44 -0
  68. package/dist/components/SortBy.d.ts.map +1 -0
  69. package/dist/components/SortBy.js +61 -0
  70. package/dist/components/Stats.d.ts +37 -0
  71. package/dist/components/Stats.d.ts.map +1 -0
  72. package/dist/components/Stats.js +52 -0
  73. package/dist/components/suggestions/AmazonDropdown.d.ts +30 -0
  74. package/dist/components/suggestions/AmazonDropdown.d.ts.map +1 -0
  75. package/dist/components/suggestions/AmazonDropdown.js +529 -0
  76. package/dist/components/suggestions/GoogleDropdown.d.ts +31 -0
  77. package/dist/components/suggestions/GoogleDropdown.d.ts.map +1 -0
  78. package/dist/components/suggestions/GoogleDropdown.js +370 -0
  79. package/dist/components/suggestions/MinimalDropdown.d.ts +24 -0
  80. package/dist/components/suggestions/MinimalDropdown.d.ts.map +1 -0
  81. package/dist/components/suggestions/MinimalDropdown.js +314 -0
  82. package/dist/components/suggestions/MobileSheetDropdown.d.ts +31 -0
  83. package/dist/components/suggestions/MobileSheetDropdown.d.ts.map +1 -0
  84. package/dist/components/suggestions/MobileSheetDropdown.js +485 -0
  85. package/dist/components/suggestions/PinterestDropdown.d.ts +29 -0
  86. package/dist/components/suggestions/PinterestDropdown.d.ts.map +1 -0
  87. package/dist/components/suggestions/PinterestDropdown.js +450 -0
  88. package/dist/components/suggestions/ShopifyDropdown.d.ts +27 -0
  89. package/dist/components/suggestions/ShopifyDropdown.d.ts.map +1 -0
  90. package/dist/components/suggestions/ShopifyDropdown.js +451 -0
  91. package/dist/components/suggestions/SpotlightDropdown.d.ts +33 -0
  92. package/dist/components/suggestions/SpotlightDropdown.d.ts.map +1 -0
  93. package/dist/components/suggestions/SpotlightDropdown.js +547 -0
  94. package/dist/components/suggestions/SuggestionSearchBar.d.ts +123 -0
  95. package/dist/components/suggestions/SuggestionSearchBar.d.ts.map +1 -0
  96. package/dist/components/suggestions/SuggestionSearchBar.js +652 -0
  97. package/dist/components/suggestions/index.d.ts +37 -0
  98. package/dist/components/suggestions/index.d.ts.map +1 -0
  99. package/dist/components/suggestions/index.js +59 -0
  100. package/dist/components/suggestions/styles/index.d.ts +11 -0
  101. package/dist/components/suggestions/styles/index.d.ts.map +1 -0
  102. package/dist/components/suggestions/styles/index.js +289 -0
  103. package/dist/components/suggestions/styles/responsive.d.ts +107 -0
  104. package/dist/components/suggestions/styles/responsive.d.ts.map +1 -0
  105. package/dist/components/suggestions/styles/responsive.js +237 -0
  106. package/dist/components/suggestions/types.d.ts +489 -0
  107. package/dist/components/suggestions/types.d.ts.map +1 -0
  108. package/dist/components/suggestions/types.js +6 -0
  109. package/dist/components/suggestions/utils.d.ts +213 -0
  110. package/dist/components/suggestions/utils.d.ts.map +1 -0
  111. package/dist/components/suggestions/utils.js +514 -0
  112. package/dist/hooks/useAnalytics.d.ts +20 -0
  113. package/dist/hooks/useAnalytics.d.ts.map +1 -0
  114. package/dist/hooks/useAnalytics.js +62 -0
  115. package/dist/hooks/useNaturalLanguageFilters.d.ts +48 -0
  116. package/dist/hooks/useNaturalLanguageFilters.d.ts.map +1 -0
  117. package/dist/hooks/useNaturalLanguageFilters.js +221 -0
  118. package/dist/hooks/useQuerySuggestions.d.ts +21 -0
  119. package/dist/hooks/useQuerySuggestions.d.ts.map +1 -0
  120. package/dist/hooks/useQuerySuggestions.js +68 -0
  121. package/dist/hooks/useQuerySuggestionsEnhanced.d.ts +114 -0
  122. package/dist/hooks/useQuerySuggestionsEnhanced.d.ts.map +1 -0
  123. package/dist/hooks/useQuerySuggestionsEnhanced.js +376 -0
  124. package/dist/hooks/useSearchState.d.ts +35 -0
  125. package/dist/hooks/useSearchState.d.ts.map +1 -0
  126. package/dist/hooks/useSearchState.js +68 -0
  127. package/dist/hooks/useSeekoraSearch.d.ts +20 -0
  128. package/dist/hooks/useSeekoraSearch.d.ts.map +1 -0
  129. package/dist/hooks/useSeekoraSearch.js +63 -0
  130. package/dist/hooks/useSmartSuggestions.d.ts +55 -0
  131. package/dist/hooks/useSmartSuggestions.d.ts.map +1 -0
  132. package/dist/hooks/useSmartSuggestions.js +236 -0
  133. package/dist/hooks/useSuggestionsAnalytics.d.ts +91 -0
  134. package/dist/hooks/useSuggestionsAnalytics.d.ts.map +1 -0
  135. package/dist/hooks/useSuggestionsAnalytics.js +226 -0
  136. package/dist/index.d.ts +80 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +86 -0
  139. package/dist/index.umd.js +1 -0
  140. package/dist/src/index.d.ts +2849 -0
  141. package/dist/src/index.esm.js +11679 -0
  142. package/dist/src/index.esm.js.map +1 -0
  143. package/dist/src/index.js +11761 -0
  144. package/dist/src/index.js.map +1 -0
  145. package/dist/themes/createTheme.d.ts +8 -0
  146. package/dist/themes/createTheme.d.ts.map +1 -0
  147. package/dist/themes/createTheme.js +10 -0
  148. package/dist/themes/dark.d.ts +6 -0
  149. package/dist/themes/dark.d.ts.map +1 -0
  150. package/dist/themes/dark.js +34 -0
  151. package/dist/themes/default.d.ts +6 -0
  152. package/dist/themes/default.d.ts.map +1 -0
  153. package/dist/themes/default.js +71 -0
  154. package/dist/themes/mergeThemes.d.ts +7 -0
  155. package/dist/themes/mergeThemes.d.ts.map +1 -0
  156. package/dist/themes/mergeThemes.js +6 -0
  157. package/dist/themes/minimal.d.ts +6 -0
  158. package/dist/themes/minimal.d.ts.map +1 -0
  159. package/dist/themes/minimal.js +34 -0
  160. package/dist/themes/suggestions.d.ts +216 -0
  161. package/dist/themes/suggestions.d.ts.map +1 -0
  162. package/dist/themes/suggestions.js +546 -0
  163. package/dist/themes/types.d.ts +7 -0
  164. package/dist/themes/types.d.ts.map +1 -0
  165. package/dist/themes/types.js +6 -0
  166. package/dist/types/index.d.ts +33 -0
  167. package/dist/types/index.d.ts.map +1 -0
  168. package/dist/types/index.js +4 -0
  169. package/package.json +65 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * SearchLayout Component
3
+ *
4
+ * Provides a layout structure for search interfaces with sidebar and main content
5
+ */
6
+ import React, { useEffect, useState } from 'react';
7
+ import { useSearchContext } from './SearchProvider';
8
+ import { clsx } from 'clsx';
9
+ export const SearchLayout = ({ sidebar, children, header, footer, sidebarWidth = '300px', className, style, theme: customTheme, showSidebarOnMobile = false, }) => {
10
+ const { theme } = useSearchContext();
11
+ const layoutTheme = customTheme || {};
12
+ const [isMobile, setIsMobile] = useState(false);
13
+ useEffect(() => {
14
+ const checkMobile = () => {
15
+ setIsMobile(window.innerWidth <= 768);
16
+ };
17
+ checkMobile();
18
+ window.addEventListener('resize', checkMobile);
19
+ return () => window.removeEventListener('resize', checkMobile);
20
+ }, []);
21
+ return (React.createElement("div", { className: clsx(layoutTheme.container, className), style: {
22
+ display: 'flex',
23
+ flexDirection: 'column',
24
+ minHeight: '100vh',
25
+ backgroundColor: theme.colors.background,
26
+ ...style,
27
+ } },
28
+ header && (React.createElement("header", { className: layoutTheme.header, style: {
29
+ padding: theme.spacing.medium,
30
+ borderBottom: `1px solid ${theme.colors.border}`,
31
+ backgroundColor: theme.colors.background,
32
+ } }, header)),
33
+ React.createElement("div", { style: {
34
+ display: 'flex',
35
+ flex: 1,
36
+ gap: theme.spacing.large,
37
+ padding: theme.spacing.medium,
38
+ backgroundColor: theme.colors.background,
39
+ color: theme.colors.text,
40
+ } },
41
+ sidebar && (!isMobile || showSidebarOnMobile) && (React.createElement("aside", { className: layoutTheme.sidebar, style: {
42
+ width: sidebarWidth,
43
+ minWidth: sidebarWidth,
44
+ } }, sidebar)),
45
+ React.createElement("main", { className: layoutTheme.main, style: {
46
+ flex: 1,
47
+ minWidth: 0, // Prevents flex item from overflowing
48
+ backgroundColor: theme.colors.background,
49
+ color: theme.colors.text,
50
+ } }, children)),
51
+ footer && (React.createElement("footer", { className: layoutTheme.footer, style: {
52
+ padding: theme.spacing.medium,
53
+ borderTop: `1px solid ${theme.colors.border}`,
54
+ backgroundColor: theme.colors.background,
55
+ } }, footer))));
56
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * SearchProvider Component
3
+ *
4
+ * Provides Seekora client and context to child components
5
+ */
6
+ import React, { ReactNode } from 'react';
7
+ import type { SeekoraClient } from '@seekora-ai/search-sdk';
8
+ import { SearchStateManager, type SearchStateManagerConfig } from '@seekora-ai/ui-sdk-core';
9
+ import type { Theme, ThemeConfig } from '../themes/types';
10
+ interface SearchContextValue {
11
+ client: SeekoraClient;
12
+ theme: Theme;
13
+ enableAnalytics: boolean;
14
+ autoTrackSearch: boolean;
15
+ stateManager: SearchStateManager;
16
+ }
17
+ export interface SearchProviderProps {
18
+ client: SeekoraClient;
19
+ theme?: ThemeConfig;
20
+ enableAnalytics?: boolean;
21
+ autoTrackSearch?: boolean;
22
+ stateManager?: SearchStateManagerConfig;
23
+ children: ReactNode;
24
+ }
25
+ export declare const SearchProvider: React.FC<SearchProviderProps>;
26
+ export declare const useSearchContext: () => SearchContextValue;
27
+ export {};
28
+ //# sourceMappingURL=SearchProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchProvider.d.ts","sourceRoot":"","sources":["../../src/components/SearchProvider.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,EAA6B,SAAS,EAAW,MAAM,OAAO,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAO,kBAAkB,EAAE,KAAK,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACjG,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAI1D,UAAU,kBAAkB;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,EAAE,kBAAkB,CAAC;CAClC;AAID,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,wBAAwB,CAAC;IACxC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAqCxD,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,kBAQnC,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * SearchProvider Component
3
+ *
4
+ * Provides Seekora client and context to child components
5
+ */
6
+ import React, { createContext, useContext, useMemo } from 'react';
7
+ import { log, SearchStateManager } from '@seekora-ai/ui-sdk-core';
8
+ import { defaultTheme } from '../themes/default';
9
+ import { createTheme } from '../themes/createTheme';
10
+ const SearchContext = createContext(null);
11
+ export const SearchProvider = ({ client, theme: themeConfig, enableAnalytics = true, autoTrackSearch = true, stateManager: stateManagerConfig, children, }) => {
12
+ const theme = useMemo(() => {
13
+ return themeConfig ? createTheme(themeConfig) : defaultTheme;
14
+ }, [themeConfig]);
15
+ // Create state manager instance (memoized to avoid recreating on every render)
16
+ const stateManager = useMemo(() => {
17
+ return new SearchStateManager({
18
+ client,
19
+ autoSearch: true,
20
+ debounceMs: 300,
21
+ itemsPerPage: 10,
22
+ defaultSearchOptions: { widget_mode: true },
23
+ ...stateManagerConfig,
24
+ });
25
+ }, [client, stateManagerConfig]);
26
+ const value = useMemo(() => ({
27
+ client,
28
+ theme,
29
+ enableAnalytics,
30
+ autoTrackSearch,
31
+ stateManager,
32
+ }), [client, theme, enableAnalytics, autoTrackSearch, stateManager]);
33
+ return (React.createElement(SearchContext.Provider, { value: value }, children));
34
+ };
35
+ export const useSearchContext = () => {
36
+ const context = useContext(SearchContext);
37
+ if (!context) {
38
+ const error = new Error('useSearchContext must be used within a SearchProvider');
39
+ log.error('SearchProvider: Context not available', { error: error.message });
40
+ throw error;
41
+ }
42
+ return context;
43
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * SearchResults Component
3
+ *
4
+ * Displays search results with customizable rendering
5
+ * Includes keyboard navigation support (arrow keys, Enter to select)
6
+ */
7
+ import React from 'react';
8
+ import type { SearchResponse } from '@seekora-ai/search-sdk';
9
+ import type { ResultItem, FieldMapping, ViewMode } from '@seekora-ai/ui-sdk-types';
10
+ export interface SearchResultsTheme {
11
+ container?: string;
12
+ header?: string;
13
+ resultsList?: string;
14
+ resultItem?: string;
15
+ resultItemHover?: string;
16
+ resultTitle?: string;
17
+ resultDescription?: string;
18
+ resultImage?: string;
19
+ resultPrice?: string;
20
+ emptyState?: string;
21
+ loadingState?: string;
22
+ errorState?: string;
23
+ pagination?: string;
24
+ }
25
+ export interface SearchResultsProps {
26
+ results?: SearchResponse | null;
27
+ loading?: boolean;
28
+ error?: Error | null;
29
+ onResultClick?: (result: ResultItem, index: number) => void;
30
+ renderResult?: (result: ResultItem, index: number, isActive?: boolean) => React.ReactNode;
31
+ renderEmpty?: () => React.ReactNode;
32
+ renderLoading?: () => React.ReactNode;
33
+ renderError?: (error: Error) => React.ReactNode;
34
+ className?: string;
35
+ style?: React.CSSProperties;
36
+ theme?: SearchResultsTheme;
37
+ itemsPerPage?: number;
38
+ showPagination?: boolean;
39
+ /** Display mode: 'list' (default), 'card', or 'grid' */
40
+ viewMode?: ViewMode;
41
+ /** Field mapping configuration for extracting data from results */
42
+ fieldMapping?: FieldMapping;
43
+ /** Function to extract results array from the response (if custom structure) */
44
+ extractResults?: (response: any) => any[];
45
+ /** Enable keyboard navigation (arrow keys, Enter to select) */
46
+ enableKeyboardNavigation?: boolean;
47
+ /** Auto-focus the results container */
48
+ autoFocus?: boolean;
49
+ }
50
+ export declare const SearchResults: React.FC<SearchResultsProps>;
51
+ //# sourceMappingURL=SearchResults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchResults.d.ts","sourceRoot":"","sources":["../../src/components/SearchResults.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAKxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEnF,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1F,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACtC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,mEAAmE;IACnE,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,gFAAgF;IAChF,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,GAAG,EAAE,CAAC;IAC1C,+DAA+D;IAC/D,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAoBD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA+oBtD,CAAC"}
@@ -0,0 +1,485 @@
1
+ /**
2
+ * SearchResults Component
3
+ *
4
+ * Displays search results with customizable rendering
5
+ * Includes keyboard navigation support (arrow keys, Enter to select)
6
+ */
7
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
8
+ import { useSearchContext } from './SearchProvider';
9
+ import { useSearchState } from '../hooks/useSearchState';
10
+ import { log } from '@seekora-ai/ui-sdk-core';
11
+ import { clsx } from 'clsx';
12
+ // Helper function to get nested value from object using dot notation
13
+ const getNestedValue = (obj, path) => {
14
+ if (!path)
15
+ return undefined;
16
+ return path.split('.').reduce((current, key) => current?.[key], obj);
17
+ };
18
+ // Helper function to format price
19
+ const formatPrice = (value, currency = '₹') => {
20
+ if (value === undefined || value === null)
21
+ return undefined;
22
+ if (typeof value === 'number')
23
+ return `${currency}${value}`;
24
+ if (typeof value === 'string') {
25
+ const num = parseFloat(value);
26
+ if (!isNaN(num))
27
+ return `${currency}${num}`;
28
+ return value;
29
+ }
30
+ return String(value);
31
+ };
32
+ export const SearchResults = ({ results: resultsProp, loading: loadingProp, error: errorProp, onResultClick, renderResult, renderEmpty, renderLoading, renderError, className, style, theme: customTheme, itemsPerPage = 20, showPagination = false, viewMode = 'list', fieldMapping, extractResults, enableKeyboardNavigation = true, autoFocus = false, }) => {
33
+ const { theme, client, enableAnalytics } = useSearchContext();
34
+ const { results: stateResults, loading: stateLoading, error: stateError, currentPage, itemsPerPage: stateItemsPerPage } = useSearchState();
35
+ const searchResultsTheme = customTheme || {};
36
+ // Keyboard navigation state
37
+ const [activeIndex, setActiveIndex] = useState(-1);
38
+ const containerRef = useRef(null);
39
+ // Use props if provided, otherwise use state from state manager
40
+ const results = resultsProp !== undefined ? resultsProp : stateResults;
41
+ const loading = loadingProp !== undefined ? loadingProp : stateLoading;
42
+ const error = errorProp !== undefined ? errorProp : stateError;
43
+ // Reset active index when results change
44
+ useEffect(() => {
45
+ setActiveIndex(-1);
46
+ }, [results]);
47
+ // Scroll active item into view
48
+ useEffect(() => {
49
+ if (activeIndex >= 0 && containerRef.current && enableKeyboardNavigation) {
50
+ const activeEl = containerRef.current.querySelector(`[data-result-index="${activeIndex}"]`);
51
+ if (activeEl) {
52
+ activeEl.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
53
+ }
54
+ }
55
+ }, [activeIndex, enableKeyboardNavigation]);
56
+ // Keyboard navigation handler
57
+ const handleKeyDown = useCallback((e) => {
58
+ if (!enableKeyboardNavigation)
59
+ return;
60
+ // We need to get the result count dynamically
61
+ const resultElements = containerRef.current?.querySelectorAll('[data-result-index]');
62
+ const maxIndex = resultElements ? resultElements.length - 1 : -1;
63
+ switch (e.key) {
64
+ case 'ArrowDown':
65
+ e.preventDefault();
66
+ setActiveIndex(prev => Math.min(prev + 1, maxIndex));
67
+ break;
68
+ case 'ArrowUp':
69
+ e.preventDefault();
70
+ setActiveIndex(prev => Math.max(prev - 1, -1));
71
+ break;
72
+ case 'Enter':
73
+ e.preventDefault();
74
+ if (activeIndex >= 0) {
75
+ // Trigger click on active item
76
+ const activeEl = containerRef.current?.querySelector(`[data-result-index="${activeIndex}"]`);
77
+ if (activeEl) {
78
+ activeEl.click();
79
+ }
80
+ }
81
+ break;
82
+ case 'Escape':
83
+ setActiveIndex(-1);
84
+ containerRef.current?.blur();
85
+ break;
86
+ case 'Home':
87
+ e.preventDefault();
88
+ setActiveIndex(0);
89
+ break;
90
+ case 'End':
91
+ e.preventDefault();
92
+ setActiveIndex(maxIndex);
93
+ break;
94
+ }
95
+ }, [enableKeyboardNavigation, activeIndex]);
96
+ const defaultRenderResultList = (result, index, isActive = false) => {
97
+ const handleClick = async () => {
98
+ // Track analytics event if enabled
99
+ if (enableAnalytics && result.id) {
100
+ try {
101
+ const results = resultsProp || stateResults;
102
+ const state = { currentPage, itemsPerPage: stateItemsPerPage };
103
+ // Calculate absolute position (1-based) accounting for pagination
104
+ const absolutePosition = (state.currentPage - 1) * state.itemsPerPage + index + 1;
105
+ // Build search context from current state and response
106
+ const searchContext = results?.context || (results ? {
107
+ query: '', // Will be filled from state if needed
108
+ page: state.currentPage,
109
+ } : undefined);
110
+ console.log('🔵 SearchResults: Tracking analytics event', {
111
+ resultId: result.id,
112
+ resultIndex: index,
113
+ absolutePosition,
114
+ currentPage: state.currentPage,
115
+ itemsPerPage: state.itemsPerPage,
116
+ hasContext: !!searchContext,
117
+ });
118
+ await client.trackEvent?.({
119
+ event_name: 'product_click',
120
+ clicked_item_id: result.id,
121
+ metadata: {
122
+ result: result,
123
+ position: absolutePosition,
124
+ },
125
+ }, searchContext);
126
+ console.log('🟢 SearchResults: Analytics event tracked successfully', {
127
+ resultId: result.id,
128
+ position: absolutePosition,
129
+ });
130
+ }
131
+ catch (err) {
132
+ const error = err instanceof Error ? err : new Error(String(err));
133
+ console.error('SearchResults: Error tracking analytics event', {
134
+ resultId: result.id,
135
+ error: error.message,
136
+ });
137
+ }
138
+ }
139
+ // Call user-provided callback
140
+ if (onResultClick) {
141
+ onResultClick(result, index);
142
+ }
143
+ };
144
+ return (React.createElement("div", { key: result.id || index, "data-result-index": index, className: clsx(searchResultsTheme.resultItem, isActive && searchResultsTheme.resultItemHover), onClick: handleClick, onMouseEnter: () => enableKeyboardNavigation && setActiveIndex(index), style: {
145
+ padding: theme.spacing.medium,
146
+ border: `1px solid ${theme.colors.border}`,
147
+ borderBottom: `1px solid ${theme.colors.border}`,
148
+ borderRadius: 0,
149
+ marginBottom: 0,
150
+ cursor: onResultClick ? 'pointer' : 'default',
151
+ transition: theme.transitions?.normal || '250ms ease-in-out',
152
+ backgroundColor: isActive ? theme.colors.hover : theme.colors.background,
153
+ display: 'flex',
154
+ alignItems: 'flex-start',
155
+ gap: theme.spacing.medium,
156
+ outline: isActive ? `2px solid ${theme.colors.primary}` : 'none',
157
+ outlineOffset: '-2px',
158
+ } },
159
+ result.image && (React.createElement("img", { src: result.image, alt: result.title, className: searchResultsTheme.resultImage, style: {
160
+ width: '100px',
161
+ height: '100px',
162
+ objectFit: 'cover',
163
+ borderRadius: typeof theme.borderRadius === 'string' ? theme.borderRadius : theme.borderRadius.medium,
164
+ flexShrink: 0,
165
+ } })),
166
+ React.createElement("div", { style: { flex: 1, minWidth: 0 } },
167
+ React.createElement("h3", { className: searchResultsTheme.resultTitle, style: {
168
+ fontSize: theme.typography.fontSize.large,
169
+ fontWeight: 'bold',
170
+ margin: 0,
171
+ marginBottom: theme.spacing.small,
172
+ color: theme.colors.text,
173
+ } }, result.title),
174
+ result.description && (React.createElement("p", { className: searchResultsTheme.resultDescription, style: {
175
+ fontSize: theme.typography.fontSize.medium,
176
+ color: theme.colors.text,
177
+ margin: 0,
178
+ marginBottom: theme.spacing.small,
179
+ opacity: 0.8,
180
+ } }, result.description)),
181
+ result.price && (React.createElement("div", { className: searchResultsTheme.resultPrice, style: {
182
+ fontSize: theme.typography.fontSize.medium,
183
+ fontWeight: 'bold',
184
+ color: theme.colors.primary,
185
+ } }, result.price)))));
186
+ };
187
+ const defaultRenderResultCard = (result, index, isActive = false) => {
188
+ const handleClick = async () => {
189
+ // Track analytics event if enabled
190
+ if (enableAnalytics && result.id) {
191
+ try {
192
+ const results = resultsProp || stateResults;
193
+ const state = { currentPage, itemsPerPage: stateItemsPerPage };
194
+ // Calculate absolute position (1-based) accounting for pagination
195
+ const absolutePosition = (state.currentPage - 1) * state.itemsPerPage + index + 1;
196
+ // Build search context from current state and response
197
+ const searchContext = results?.context || (results ? {
198
+ query: '', // Will be filled from state if needed
199
+ page: state.currentPage,
200
+ } : undefined);
201
+ console.log('🔵 SearchResults: Tracking analytics event', {
202
+ resultId: result.id,
203
+ resultIndex: index,
204
+ absolutePosition,
205
+ currentPage: state.currentPage,
206
+ itemsPerPage: state.itemsPerPage,
207
+ hasContext: !!searchContext,
208
+ });
209
+ await client.trackEvent?.({
210
+ event_name: 'product_click',
211
+ clicked_item_id: result.id,
212
+ metadata: {
213
+ result: result,
214
+ position: absolutePosition,
215
+ },
216
+ }, searchContext);
217
+ console.log('🟢 SearchResults: Analytics event tracked successfully', {
218
+ resultId: result.id,
219
+ position: absolutePosition,
220
+ });
221
+ }
222
+ catch (err) {
223
+ const error = err instanceof Error ? err : new Error(String(err));
224
+ console.error('SearchResults: Error tracking analytics event', {
225
+ resultId: result.id,
226
+ error: error.message,
227
+ });
228
+ }
229
+ }
230
+ // Call user-provided callback
231
+ if (onResultClick) {
232
+ onResultClick(result, index);
233
+ }
234
+ };
235
+ // Use widget view fields if available, otherwise fall back to regular fields
236
+ const primaryText = result.primaryText || result.title;
237
+ const secondaryText = result.secondaryText || result.description;
238
+ const tertiaryText = result.tertiaryText || result.price;
239
+ const imageUrl = result.imageUrl || result.image;
240
+ return (React.createElement("div", { key: result.id || index, "data-result-index": index, className: clsx(searchResultsTheme.resultItem, isActive && searchResultsTheme.resultItemHover), onClick: handleClick, onMouseEnter: () => enableKeyboardNavigation && setActiveIndex(index), style: {
241
+ border: `1px solid ${isActive ? theme.colors.primary : theme.colors.border}`,
242
+ borderRadius: typeof theme.borderRadius === 'string' ? theme.borderRadius : theme.borderRadius.medium,
243
+ overflow: 'hidden',
244
+ cursor: onResultClick ? 'pointer' : 'default',
245
+ transition: theme.transitions?.normal || '250ms ease-in-out',
246
+ backgroundColor: isActive ? theme.colors.hover : theme.colors.background,
247
+ display: 'flex',
248
+ flexDirection: 'column',
249
+ padding: theme.spacing.medium,
250
+ boxShadow: isActive ? theme.shadows.medium : theme.shadows.small,
251
+ outline: isActive ? `2px solid ${theme.colors.primary}` : 'none',
252
+ outlineOffset: '-2px',
253
+ } },
254
+ imageUrl && (React.createElement("div", { style: {
255
+ width: '100%',
256
+ aspectRatio: '16/9',
257
+ overflow: 'hidden',
258
+ backgroundColor: theme.colors.hover,
259
+ } },
260
+ React.createElement("img", { src: imageUrl, alt: primaryText, style: {
261
+ width: '100%',
262
+ height: '100%',
263
+ objectFit: 'cover',
264
+ } }))),
265
+ React.createElement("div", { style: { padding: theme.spacing.medium } },
266
+ primaryText && (React.createElement("h3", { className: searchResultsTheme.resultTitle, style: {
267
+ fontSize: theme.typography.fontSize.large,
268
+ fontWeight: 'bold',
269
+ margin: 0,
270
+ marginBottom: theme.spacing.small,
271
+ color: theme.colors.text,
272
+ } }, primaryText)),
273
+ secondaryText && (React.createElement("p", { className: searchResultsTheme.resultDescription, style: {
274
+ fontSize: theme.typography.fontSize.medium,
275
+ color: theme.colors.text,
276
+ margin: 0,
277
+ marginBottom: theme.spacing.small,
278
+ opacity: 0.8,
279
+ display: '-webkit-box',
280
+ WebkitLineClamp: 2,
281
+ WebkitBoxOrient: 'vertical',
282
+ overflow: 'hidden',
283
+ } }, secondaryText)),
284
+ tertiaryText && (React.createElement("div", { className: searchResultsTheme.resultPrice, style: {
285
+ fontSize: theme.typography.fontSize.medium,
286
+ fontWeight: 'bold',
287
+ color: theme.colors.primary,
288
+ marginTop: 'auto',
289
+ } }, tertiaryText)))));
290
+ };
291
+ const defaultRenderResult = viewMode === 'card' || viewMode === 'grid'
292
+ ? defaultRenderResultCard
293
+ : defaultRenderResultList;
294
+ const defaultRenderEmpty = () => (React.createElement("div", { className: searchResultsTheme.emptyState, style: {
295
+ padding: theme.spacing.large,
296
+ textAlign: 'center',
297
+ color: theme.colors.text,
298
+ } }, "No results found"));
299
+ const defaultRenderLoading = () => (React.createElement("div", { className: searchResultsTheme.loadingState, style: {
300
+ padding: theme.spacing.large,
301
+ textAlign: 'center',
302
+ color: theme.colors.text,
303
+ } }, "Loading results..."));
304
+ const defaultRenderError = (err) => (React.createElement("div", { className: searchResultsTheme.errorState, style: {
305
+ padding: theme.spacing.large,
306
+ textAlign: 'center',
307
+ color: theme.colors.error,
308
+ } },
309
+ "Error: ",
310
+ err.message));
311
+ // Extract results array from response
312
+ let rawResults = [];
313
+ if (extractResults) {
314
+ // Use custom extraction function if provided
315
+ rawResults = extractResults(results) || [];
316
+ }
317
+ else if (results) {
318
+ const res = results;
319
+ if (Array.isArray(res.results) && res.results.length > 0) {
320
+ rawResults = res.results;
321
+ }
322
+ else if (res.data && Array.isArray(res.data.results) && res.data.results.length > 0) {
323
+ rawResults = res.data.results;
324
+ }
325
+ else if (res.data?.data && Array.isArray(res.data.data.results) && res.data.data.results.length > 0) {
326
+ rawResults = res.data.data.results;
327
+ }
328
+ else if (Array.isArray(results)) {
329
+ rawResults = results;
330
+ }
331
+ else if (Array.isArray(res.results)) {
332
+ rawResults = res.results;
333
+ }
334
+ }
335
+ // Default field mapping (can be overridden via props)
336
+ const defaultFieldMapping = {
337
+ title: 'document.productName',
338
+ description: 'document.brandDesc',
339
+ image: 'document.image',
340
+ price: 'document.mrp',
341
+ url: 'document.url',
342
+ id: 'id',
343
+ ...fieldMapping,
344
+ };
345
+ // Extract field value using dot notation path
346
+ const extractField = (item, path) => {
347
+ if (!path)
348
+ return undefined;
349
+ return getNestedValue(item, path);
350
+ };
351
+ const resultItems = rawResults.map((item, index) => {
352
+ try {
353
+ // Widget view fields (when widget_mode is enabled) - check these first
354
+ const primaryText = extractField(item, defaultFieldMapping.primaryText);
355
+ const secondaryText = extractField(item, defaultFieldMapping.secondaryText);
356
+ const tertiaryText = extractField(item, defaultFieldMapping.tertiaryText);
357
+ const imageUrl = extractField(item, defaultFieldMapping.imageUrl);
358
+ // Use primaryText as fallback for title if title is not found
359
+ const title = extractField(item, defaultFieldMapping.title) || primaryText || 'Untitled';
360
+ const description = extractField(item, defaultFieldMapping.description) || secondaryText;
361
+ const image = extractField(item, defaultFieldMapping.image) || imageUrl;
362
+ const priceValue = extractField(item, defaultFieldMapping.price) || tertiaryText;
363
+ const url = extractField(item, defaultFieldMapping.url);
364
+ const id = extractField(item, defaultFieldMapping.id) || String(index);
365
+ // Format price if it's a number
366
+ const price = priceValue !== undefined ? formatPrice(priceValue) : undefined;
367
+ // Extract custom fields if configured
368
+ const customFields = {};
369
+ if (defaultFieldMapping.custom) {
370
+ Object.entries(defaultFieldMapping.custom).forEach(([key, path]) => {
371
+ customFields[key] = extractField(item, path);
372
+ });
373
+ }
374
+ const resultItem = {
375
+ id,
376
+ title,
377
+ description,
378
+ url,
379
+ image,
380
+ price,
381
+ metadata: item,
382
+ ...customFields,
383
+ // Widget view fields
384
+ ...(primaryText && { primaryText }),
385
+ ...(secondaryText && { secondaryText }),
386
+ ...(tertiaryText && { tertiaryText }),
387
+ ...(imageUrl && { imageUrl }),
388
+ // Include original item for full access
389
+ _raw: item,
390
+ };
391
+ return resultItem;
392
+ }
393
+ catch (err) {
394
+ const error = err instanceof Error ? err : new Error(String(err));
395
+ log.warn('SearchResults: Error extracting result item', {
396
+ index,
397
+ error: error.message,
398
+ item: item?.id || 'unknown',
399
+ });
400
+ // Return a fallback result item
401
+ return {
402
+ id: String(index),
403
+ title: 'Error loading result',
404
+ metadata: item,
405
+ };
406
+ }
407
+ });
408
+ // Determine container style based on view mode
409
+ const containerStyle = {
410
+ ...style,
411
+ };
412
+ // Determine results list style based on view mode
413
+ const resultsListStyle = {
414
+ ...(viewMode === 'grid' && {
415
+ display: 'grid',
416
+ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
417
+ gap: theme.spacing.medium,
418
+ }),
419
+ ...(viewMode === 'card' && {
420
+ display: 'grid',
421
+ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
422
+ gap: theme.spacing.medium,
423
+ }),
424
+ };
425
+ // Log results extraction
426
+ log.verbose('SearchResults: Extracted results', {
427
+ rawResultsCount: rawResults.length,
428
+ resultItemsCount: resultItems.length,
429
+ viewMode,
430
+ hasError: !!error,
431
+ isLoading: loading,
432
+ });
433
+ if (loading) {
434
+ log.verbose('SearchResults: Rendering loading state');
435
+ return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style: style }, renderLoading ? renderLoading() : defaultRenderLoading()));
436
+ }
437
+ if (error) {
438
+ log.error('SearchResults: Rendering error state', {
439
+ error: error.message,
440
+ stack: error.stack,
441
+ });
442
+ return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style: style }, renderError ? renderError(error) : defaultRenderError(error)));
443
+ }
444
+ if (!results || resultItems.length === 0) {
445
+ log.verbose('SearchResults: No results to display');
446
+ return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style: style }, renderEmpty ? renderEmpty() : defaultRenderEmpty()));
447
+ }
448
+ const renderFn = renderResult || defaultRenderResult;
449
+ return (React.createElement("div", { ref: containerRef, className: clsx(searchResultsTheme.container, className), style: containerStyle, tabIndex: enableKeyboardNavigation ? 0 : undefined, onKeyDown: handleKeyDown, role: "listbox", "aria-label": "Search results", "aria-activedescendant": activeIndex >= 0 ? `result-${activeIndex}` : undefined },
450
+ (results?.totalResults !== undefined || results?.data?.total_results !== undefined) && resultItems.length > 0 && (React.createElement("div", { className: searchResultsTheme.header, style: {
451
+ marginBottom: theme.spacing.medium,
452
+ fontSize: theme.typography.fontSize.medium,
453
+ color: theme.colors.text,
454
+ } },
455
+ "Found ",
456
+ results?.totalResults || results?.data?.total_results || 0,
457
+ " result",
458
+ (results?.totalResults || results?.data?.total_results || 0) !== 1 ? 's' : '')),
459
+ enableKeyboardNavigation && resultItems.length > 0 && (React.createElement("div", { style: {
460
+ fontSize: '12px',
461
+ color: theme.colors.text,
462
+ opacity: 0.6,
463
+ marginBottom: theme.spacing.small,
464
+ display: 'flex',
465
+ gap: '8px',
466
+ alignItems: 'center',
467
+ } },
468
+ React.createElement("span", { style: {
469
+ display: 'inline-flex',
470
+ gap: '4px',
471
+ padding: '2px 6px',
472
+ backgroundColor: theme.colors.hover,
473
+ borderRadius: '4px',
474
+ fontSize: '11px',
475
+ } }, "\u2191\u2193 navigate"),
476
+ React.createElement("span", { style: {
477
+ display: 'inline-flex',
478
+ gap: '4px',
479
+ padding: '2px 6px',
480
+ backgroundColor: theme.colors.hover,
481
+ borderRadius: '4px',
482
+ fontSize: '11px',
483
+ } }, "\u21B5 select"))),
484
+ React.createElement("div", { className: searchResultsTheme.resultsList, style: resultsListStyle }, resultItems.map((result, index) => renderFn(result, index, index === activeIndex)))));
485
+ };