@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,181 @@
1
+ /**
2
+ * InfiniteHits Component
3
+ *
4
+ * Displays search results with infinite scroll or "Show More" button
5
+ * Accumulates results as user loads more pages
6
+ */
7
+ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
8
+ import { useSearchContext } from './SearchProvider';
9
+ import { useSearchState } from '../hooks/useSearchState';
10
+ import { clsx } from 'clsx';
11
+ export const InfiniteHits = ({ renderHit, renderEmpty, renderLoading, renderShowMore, showMoreButton = true, useInfiniteScroll = false, scrollThreshold = 0.1, fieldMapping, showMoreLabel = 'Show more', loadingLabel = 'Loading...', onHitClick, className, style, theme: customTheme, syncWithState = true, }) => {
12
+ const { theme, stateManager } = useSearchContext();
13
+ const { results, loading, currentPage, setPage } = useSearchState();
14
+ const infiniteHitsTheme = customTheme || {};
15
+ // Accumulated hits across pages
16
+ const [accumulatedHits, setAccumulatedHits] = useState([]);
17
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
18
+ const sentinelRef = useRef(null);
19
+ const prevPageRef = useRef(currentPage);
20
+ // Extract hits from results
21
+ const extractHits = useCallback((response) => {
22
+ if (!response)
23
+ return [];
24
+ if (response.results)
25
+ return response.results;
26
+ if (response.hits)
27
+ return response.hits;
28
+ if (Array.isArray(response))
29
+ return response;
30
+ return [];
31
+ }, []);
32
+ // Get current hits
33
+ const currentHits = useMemo(() => extractHits(results), [results, extractHits]);
34
+ // Calculate if we're on the last page
35
+ const totalResults = results?.totalResults || results?.found || 0;
36
+ const itemsPerPage = stateManager.getState().itemsPerPage || 10;
37
+ const totalPages = Math.ceil(totalResults / itemsPerPage);
38
+ const isLastPage = currentPage >= totalPages;
39
+ // Accumulate hits when results change
40
+ useEffect(() => {
41
+ if (!currentHits.length)
42
+ return;
43
+ if (currentPage === 1) {
44
+ // Reset accumulated hits on new search
45
+ setAccumulatedHits(currentHits);
46
+ }
47
+ else if (currentPage > prevPageRef.current) {
48
+ // Append new hits
49
+ setAccumulatedHits(prev => [...prev, ...currentHits]);
50
+ }
51
+ prevPageRef.current = currentPage;
52
+ setIsLoadingMore(false);
53
+ }, [currentHits, currentPage]);
54
+ // Reset when query changes
55
+ useEffect(() => {
56
+ if (currentPage === 1) {
57
+ setAccumulatedHits(currentHits);
58
+ prevPageRef.current = 1;
59
+ }
60
+ }, [results]);
61
+ // Handle "Show More" click
62
+ const handleShowMore = useCallback(() => {
63
+ if (isLastPage || loading || isLoadingMore)
64
+ return;
65
+ setIsLoadingMore(true);
66
+ if (syncWithState) {
67
+ setPage(currentPage + 1);
68
+ }
69
+ }, [isLastPage, loading, isLoadingMore, syncWithState, setPage, currentPage]);
70
+ // Infinite scroll with IntersectionObserver
71
+ useEffect(() => {
72
+ if (!useInfiniteScroll || !sentinelRef.current)
73
+ return;
74
+ const observer = new IntersectionObserver((entries) => {
75
+ const entry = entries[0];
76
+ if (entry.isIntersecting && !isLastPage && !loading && !isLoadingMore) {
77
+ handleShowMore();
78
+ }
79
+ }, { threshold: scrollThreshold });
80
+ observer.observe(sentinelRef.current);
81
+ return () => observer.disconnect();
82
+ }, [useInfiniteScroll, scrollThreshold, isLastPage, loading, isLoadingMore, handleShowMore]);
83
+ // Extract field from hit
84
+ const extractField = (hit, field) => {
85
+ if (!fieldMapping || !fieldMapping[field]) {
86
+ return hit[field] || '';
87
+ }
88
+ const mappedField = fieldMapping[field];
89
+ return hit[mappedField] || '';
90
+ };
91
+ // Default hit renderer
92
+ const defaultRenderHit = (hit, index) => {
93
+ const title = extractField(hit, 'title') || hit.title || '';
94
+ const description = extractField(hit, 'description') || hit.description || '';
95
+ const image = extractField(hit, 'image') || hit.image || hit.imageUrl || '';
96
+ const price = extractField(hit, 'price') || hit.price;
97
+ return (React.createElement("div", { key: hit.id || index, className: infiniteHitsTheme.item, style: {
98
+ display: 'flex',
99
+ gap: theme.spacing.medium,
100
+ padding: theme.spacing.medium,
101
+ borderBottom: `1px solid ${theme.colors.border}`,
102
+ cursor: onHitClick ? 'pointer' : 'default',
103
+ transition: theme.transitions?.fast || '150ms ease-in-out',
104
+ }, onClick: () => onHitClick?.(hit, index), role: onHitClick ? 'button' : undefined, tabIndex: onHitClick ? 0 : undefined },
105
+ image && (React.createElement("img", { src: image, alt: title, style: {
106
+ width: '80px',
107
+ height: '80px',
108
+ objectFit: 'cover',
109
+ borderRadius: typeof theme.borderRadius === 'string'
110
+ ? theme.borderRadius
111
+ : theme.borderRadius.small,
112
+ } })),
113
+ React.createElement("div", { style: { flex: 1 } },
114
+ React.createElement("h3", { style: {
115
+ margin: 0,
116
+ marginBottom: theme.spacing.small,
117
+ fontSize: theme.typography.fontSize.medium,
118
+ fontWeight: theme.typography.fontWeight?.semibold || 600,
119
+ color: theme.colors.text,
120
+ } }, title),
121
+ description && (React.createElement("p", { style: {
122
+ margin: 0,
123
+ fontSize: theme.typography.fontSize.small,
124
+ color: theme.colors.textSecondary,
125
+ lineHeight: theme.typography.lineHeight?.normal || 1.5,
126
+ } }, description)),
127
+ price && (React.createElement("span", { style: {
128
+ display: 'inline-block',
129
+ marginTop: theme.spacing.small,
130
+ fontSize: theme.typography.fontSize.medium,
131
+ fontWeight: theme.typography.fontWeight?.bold || 700,
132
+ color: theme.colors.primary,
133
+ } },
134
+ "$",
135
+ typeof price === 'number' ? price.toFixed(2) : price)))));
136
+ };
137
+ // Default empty state
138
+ const defaultRenderEmpty = () => (React.createElement("div", { className: infiniteHitsTheme.empty, style: {
139
+ padding: theme.spacing.large,
140
+ textAlign: 'center',
141
+ color: theme.colors.textSecondary,
142
+ } }, "No results found"));
143
+ // Default loading state
144
+ const defaultRenderLoading = () => (React.createElement("div", { className: infiniteHitsTheme.loading, style: {
145
+ padding: theme.spacing.medium,
146
+ textAlign: 'center',
147
+ color: theme.colors.textSecondary,
148
+ } }, loadingLabel));
149
+ // Default "Show More" button
150
+ const defaultRenderShowMore = () => (React.createElement("button", { type: "button", onClick: handleShowMore, disabled: isLastPage || isLoadingMore, className: clsx(infiniteHitsTheme.loadMore, (isLastPage || isLoadingMore) && infiniteHitsTheme.loadMoreDisabled), style: {
151
+ display: 'block',
152
+ width: '100%',
153
+ padding: theme.spacing.medium,
154
+ marginTop: theme.spacing.medium,
155
+ fontSize: theme.typography.fontSize.medium,
156
+ fontWeight: theme.typography.fontWeight?.medium || 500,
157
+ backgroundColor: isLastPage || isLoadingMore ? theme.colors.hover : theme.colors.primary,
158
+ color: isLastPage || isLoadingMore ? theme.colors.textSecondary : '#ffffff',
159
+ border: 'none',
160
+ borderRadius: typeof theme.borderRadius === 'string'
161
+ ? theme.borderRadius
162
+ : theme.borderRadius.medium,
163
+ cursor: isLastPage || isLoadingMore ? 'not-allowed' : 'pointer',
164
+ transition: theme.transitions?.fast || '150ms ease-in-out',
165
+ } }, isLoadingMore ? loadingLabel : isLastPage ? 'No more results' : showMoreLabel));
166
+ // Initial loading state
167
+ if (loading && accumulatedHits.length === 0) {
168
+ return (React.createElement("div", { className: clsx(infiniteHitsTheme.root, className), style: style }, renderLoading ? renderLoading() : defaultRenderLoading()));
169
+ }
170
+ // Empty state
171
+ if (!loading && accumulatedHits.length === 0) {
172
+ return (React.createElement("div", { className: clsx(infiniteHitsTheme.root, className), style: style }, renderEmpty ? renderEmpty() : defaultRenderEmpty()));
173
+ }
174
+ return (React.createElement("div", { className: clsx(infiniteHitsTheme.root, className), style: style },
175
+ React.createElement("div", { className: infiniteHitsTheme.list }, accumulatedHits.map((hit, index) => renderHit ? renderHit(hit, index) : defaultRenderHit(hit, index))),
176
+ showMoreButton && !useInfiniteScroll && !isLastPage && (renderShowMore
177
+ ? renderShowMore({ isLoading: isLoadingMore, isLastPage, onClick: handleShowMore })
178
+ : defaultRenderShowMore()),
179
+ isLoadingMore && (renderLoading ? renderLoading() : defaultRenderLoading()),
180
+ useInfiniteScroll && !isLastPage && (React.createElement("div", { ref: sentinelRef, className: infiniteHitsTheme.sentinel, style: { height: '1px', visibility: 'hidden' }, "aria-hidden": "true" }))));
181
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * MobileFilters Component
3
+ *
4
+ * A full-screen filter drawer optimized for mobile devices
5
+ * Slides in from the side or bottom to display facets and filters
6
+ */
7
+ import React from 'react';
8
+ export interface MobileFiltersTheme {
9
+ overlay?: string;
10
+ drawer?: string;
11
+ header?: string;
12
+ title?: string;
13
+ closeButton?: string;
14
+ content?: string;
15
+ footer?: string;
16
+ applyButton?: string;
17
+ clearButton?: string;
18
+ filterCount?: string;
19
+ }
20
+ export interface MobileFiltersProps {
21
+ /** Whether the drawer is open */
22
+ isOpen: boolean;
23
+ /** Callback when drawer should close */
24
+ onClose: () => void;
25
+ /** Callback when filters are applied */
26
+ onApply?: () => void;
27
+ /** Title text */
28
+ title?: string;
29
+ /** Apply button text */
30
+ applyButtonText?: string;
31
+ /** Clear button text */
32
+ clearButtonText?: string;
33
+ /** Position of the drawer */
34
+ position?: 'left' | 'right' | 'bottom';
35
+ /** Children (filter components) */
36
+ children: React.ReactNode;
37
+ /** Custom className */
38
+ className?: string;
39
+ /** Custom theme */
40
+ theme?: MobileFiltersTheme;
41
+ /** Show header */
42
+ showHeader?: boolean;
43
+ /** Show footer with apply/clear buttons */
44
+ showFooter?: boolean;
45
+ /** Show filter count badge */
46
+ showFilterCount?: boolean;
47
+ /** Close on backdrop click */
48
+ closeOnBackdropClick?: boolean;
49
+ /** Close on escape key */
50
+ closeOnEscape?: boolean;
51
+ }
52
+ export declare const MobileFilters: React.FC<MobileFiltersProps>;
53
+ /**
54
+ * MobileFiltersButton Component
55
+ *
56
+ * A button to toggle the MobileFilters drawer
57
+ */
58
+ export interface MobileFiltersButtonProps {
59
+ /** Callback when clicked */
60
+ onClick: () => void;
61
+ /** Button text */
62
+ text?: string;
63
+ /** Show filter count */
64
+ showCount?: boolean;
65
+ /** Custom className */
66
+ className?: string;
67
+ /** Custom styles */
68
+ style?: React.CSSProperties;
69
+ }
70
+ export declare const MobileFiltersButton: React.FC<MobileFiltersButtonProps>;
71
+ //# sourceMappingURL=MobileFilters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MobileFilters.d.ts","sourceRoot":"","sources":["../../src/components/MobileFilters.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAKxE,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,wCAAwC;IACxC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wBAAwB;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACvC,mCAAmC;IACnC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB;IACnB,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,kBAAkB;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8BAA8B;IAC9B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,8BAA8B;IAC9B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0BAA0B;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA4StD,CAAC;AAEF;;;;GAIG;AAEH,MAAM,WAAW,wBAAwB;IACvC,4BAA4B;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAuDlE,CAAC"}
@@ -0,0 +1,242 @@
1
+ /**
2
+ * MobileFilters Component
3
+ *
4
+ * A full-screen filter drawer optimized for mobile devices
5
+ * Slides in from the side or bottom to display facets and filters
6
+ */
7
+ import React, { useEffect, useCallback, useRef } from 'react';
8
+ import { useSearchContext } from './SearchProvider';
9
+ import { useSearchState } from '../hooks/useSearchState';
10
+ import { clsx } from 'clsx';
11
+ export const MobileFilters = ({ isOpen, onClose, onApply, title = 'Filters', applyButtonText = 'Apply', clearButtonText = 'Clear all', position = 'right', children, className, theme: customTheme, showHeader = true, showFooter = true, showFilterCount = true, closeOnBackdropClick = true, closeOnEscape = true, }) => {
12
+ const { theme } = useSearchContext();
13
+ const { refinements, clearRefinements } = useSearchState();
14
+ const mobileFiltersTheme = customTheme || {};
15
+ const drawerRef = useRef(null);
16
+ // Lock body scroll when open
17
+ useEffect(() => {
18
+ if (isOpen) {
19
+ document.body.style.overflow = 'hidden';
20
+ }
21
+ else {
22
+ document.body.style.overflow = '';
23
+ }
24
+ return () => {
25
+ document.body.style.overflow = '';
26
+ };
27
+ }, [isOpen]);
28
+ // Handle escape key
29
+ useEffect(() => {
30
+ if (!closeOnEscape)
31
+ return;
32
+ const handleKeyDown = (e) => {
33
+ if (e.key === 'Escape' && isOpen) {
34
+ onClose();
35
+ }
36
+ };
37
+ document.addEventListener('keydown', handleKeyDown);
38
+ return () => document.removeEventListener('keydown', handleKeyDown);
39
+ }, [isOpen, onClose, closeOnEscape]);
40
+ // Focus trap
41
+ useEffect(() => {
42
+ if (isOpen && drawerRef.current) {
43
+ const focusableElements = drawerRef.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
44
+ const firstElement = focusableElements[0];
45
+ const lastElement = focusableElements[focusableElements.length - 1];
46
+ const handleTab = (e) => {
47
+ if (e.key !== 'Tab')
48
+ return;
49
+ if (e.shiftKey) {
50
+ if (document.activeElement === firstElement) {
51
+ e.preventDefault();
52
+ lastElement?.focus();
53
+ }
54
+ }
55
+ else {
56
+ if (document.activeElement === lastElement) {
57
+ e.preventDefault();
58
+ firstElement?.focus();
59
+ }
60
+ }
61
+ };
62
+ document.addEventListener('keydown', handleTab);
63
+ firstElement?.focus();
64
+ return () => document.removeEventListener('keydown', handleTab);
65
+ }
66
+ }, [isOpen]);
67
+ const handleBackdropClick = useCallback(() => {
68
+ if (closeOnBackdropClick) {
69
+ onClose();
70
+ }
71
+ }, [closeOnBackdropClick, onClose]);
72
+ const handleApply = useCallback(() => {
73
+ if (onApply) {
74
+ onApply();
75
+ }
76
+ onClose();
77
+ }, [onApply, onClose]);
78
+ const handleClear = useCallback(() => {
79
+ clearRefinements(true);
80
+ }, [clearRefinements]);
81
+ // Get drawer transform based on position
82
+ const getTransform = (isVisible) => {
83
+ if (isVisible)
84
+ return 'translate(0, 0)';
85
+ switch (position) {
86
+ case 'left':
87
+ return 'translateX(-100%)';
88
+ case 'right':
89
+ return 'translateX(100%)';
90
+ case 'bottom':
91
+ return 'translateY(100%)';
92
+ default:
93
+ return 'translateX(100%)';
94
+ }
95
+ };
96
+ // Get drawer styles based on position
97
+ const getDrawerPositionStyles = () => {
98
+ const base = {
99
+ position: 'fixed',
100
+ backgroundColor: theme.colors.background,
101
+ display: 'flex',
102
+ flexDirection: 'column',
103
+ transition: 'transform 300ms ease-in-out',
104
+ transform: getTransform(isOpen),
105
+ zIndex: (theme.zIndex?.modal || 1050) + 1,
106
+ };
107
+ switch (position) {
108
+ case 'left':
109
+ return { ...base, top: 0, left: 0, bottom: 0, width: '80%', maxWidth: '320px' };
110
+ case 'right':
111
+ return { ...base, top: 0, right: 0, bottom: 0, width: '80%', maxWidth: '320px' };
112
+ case 'bottom':
113
+ return { ...base, left: 0, right: 0, bottom: 0, maxHeight: '80vh', borderTopLeftRadius: '16px', borderTopRightRadius: '16px' };
114
+ default:
115
+ return base;
116
+ }
117
+ };
118
+ if (!isOpen)
119
+ return null;
120
+ return (React.createElement(React.Fragment, null,
121
+ React.createElement("div", { className: mobileFiltersTheme.overlay, onClick: handleBackdropClick, style: {
122
+ position: 'fixed',
123
+ top: 0,
124
+ left: 0,
125
+ right: 0,
126
+ bottom: 0,
127
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
128
+ zIndex: theme.zIndex?.modal || 1050,
129
+ opacity: isOpen ? 1 : 0,
130
+ transition: 'opacity 300ms ease-in-out',
131
+ }, "aria-hidden": "true" }),
132
+ React.createElement("div", { ref: drawerRef, className: clsx(mobileFiltersTheme.drawer, className), style: getDrawerPositionStyles(), role: "dialog", "aria-modal": "true", "aria-label": title },
133
+ showHeader && (React.createElement("div", { className: mobileFiltersTheme.header, style: {
134
+ display: 'flex',
135
+ alignItems: 'center',
136
+ justifyContent: 'space-between',
137
+ padding: theme.spacing.medium,
138
+ borderBottom: `1px solid ${theme.colors.border}`,
139
+ } },
140
+ React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: theme.spacing.small } },
141
+ React.createElement("h2", { className: mobileFiltersTheme.title, style: {
142
+ margin: 0,
143
+ fontSize: theme.typography.fontSize.large,
144
+ fontWeight: theme.typography.fontWeight?.semibold || 600,
145
+ color: theme.colors.text,
146
+ } }, title),
147
+ showFilterCount && refinements.length > 0 && (React.createElement("span", { className: mobileFiltersTheme.filterCount, style: {
148
+ display: 'inline-flex',
149
+ alignItems: 'center',
150
+ justifyContent: 'center',
151
+ minWidth: '24px',
152
+ height: '24px',
153
+ padding: '0 6px',
154
+ fontSize: theme.typography.fontSize.small,
155
+ fontWeight: theme.typography.fontWeight?.bold || 700,
156
+ color: '#ffffff',
157
+ backgroundColor: theme.colors.primary,
158
+ borderRadius: '12px',
159
+ } }, refinements.length))),
160
+ React.createElement("button", { type: "button", onClick: onClose, className: mobileFiltersTheme.closeButton, style: {
161
+ padding: theme.spacing.small,
162
+ background: 'none',
163
+ border: 'none',
164
+ cursor: 'pointer',
165
+ fontSize: '1.5rem',
166
+ color: theme.colors.text,
167
+ }, "aria-label": "Close filters" }, "\u00D7"))),
168
+ React.createElement("div", { className: mobileFiltersTheme.content, style: {
169
+ flex: 1,
170
+ overflow: 'auto',
171
+ padding: theme.spacing.medium,
172
+ } }, children),
173
+ showFooter && (React.createElement("div", { className: mobileFiltersTheme.footer, style: {
174
+ display: 'flex',
175
+ gap: theme.spacing.small,
176
+ padding: theme.spacing.medium,
177
+ borderTop: `1px solid ${theme.colors.border}`,
178
+ } },
179
+ React.createElement("button", { type: "button", onClick: handleClear, className: mobileFiltersTheme.clearButton, style: {
180
+ flex: 1,
181
+ padding: theme.spacing.medium,
182
+ fontSize: theme.typography.fontSize.medium,
183
+ fontWeight: theme.typography.fontWeight?.medium || 500,
184
+ color: theme.colors.text,
185
+ backgroundColor: 'transparent',
186
+ border: `1px solid ${theme.colors.border}`,
187
+ borderRadius: typeof theme.borderRadius === 'string'
188
+ ? theme.borderRadius
189
+ : theme.borderRadius.medium,
190
+ cursor: 'pointer',
191
+ } }, clearButtonText),
192
+ React.createElement("button", { type: "button", onClick: handleApply, className: mobileFiltersTheme.applyButton, style: {
193
+ flex: 2,
194
+ padding: theme.spacing.medium,
195
+ fontSize: theme.typography.fontSize.medium,
196
+ fontWeight: theme.typography.fontWeight?.medium || 500,
197
+ color: '#ffffff',
198
+ backgroundColor: theme.colors.primary,
199
+ border: 'none',
200
+ borderRadius: typeof theme.borderRadius === 'string'
201
+ ? theme.borderRadius
202
+ : theme.borderRadius.medium,
203
+ cursor: 'pointer',
204
+ } },
205
+ applyButtonText,
206
+ showFilterCount && refinements.length > 0 && ` (${refinements.length})`))))));
207
+ };
208
+ export const MobileFiltersButton = ({ onClick, text = 'Filters', showCount = true, className, style, }) => {
209
+ const { theme } = useSearchContext();
210
+ const { refinements } = useSearchState();
211
+ return (React.createElement("button", { type: "button", onClick: onClick, className: className, style: {
212
+ display: 'inline-flex',
213
+ alignItems: 'center',
214
+ gap: theme.spacing.small,
215
+ padding: `${theme.spacing.small} ${theme.spacing.medium}`,
216
+ fontSize: theme.typography.fontSize.medium,
217
+ fontWeight: theme.typography.fontWeight?.medium || 500,
218
+ color: theme.colors.text,
219
+ backgroundColor: theme.colors.background,
220
+ border: `1px solid ${theme.colors.border}`,
221
+ borderRadius: typeof theme.borderRadius === 'string'
222
+ ? theme.borderRadius
223
+ : theme.borderRadius.medium,
224
+ cursor: 'pointer',
225
+ ...style,
226
+ } },
227
+ React.createElement("span", null, "\u2630"),
228
+ React.createElement("span", null, text),
229
+ showCount && refinements.length > 0 && (React.createElement("span", { style: {
230
+ display: 'inline-flex',
231
+ alignItems: 'center',
232
+ justifyContent: 'center',
233
+ minWidth: '20px',
234
+ height: '20px',
235
+ padding: '0 4px',
236
+ fontSize: theme.typography.fontSize.small,
237
+ fontWeight: theme.typography.fontWeight?.bold || 700,
238
+ color: '#ffffff',
239
+ backgroundColor: theme.colors.primary,
240
+ borderRadius: '10px',
241
+ } }, refinements.length))));
242
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pagination Component
3
+ *
4
+ * Displays pagination controls for search results
5
+ */
6
+ import React from 'react';
7
+ import type { SearchResponse } from '@seekora-ai/search-sdk';
8
+ export interface PaginationTheme {
9
+ container?: string;
10
+ list?: string;
11
+ item?: string;
12
+ itemActive?: string;
13
+ itemDisabled?: string;
14
+ link?: string;
15
+ ellipsis?: string;
16
+ }
17
+ export interface PaginationProps {
18
+ /** Search results response */
19
+ results?: SearchResponse | null;
20
+ /** Current page number (1-indexed) */
21
+ currentPage?: number;
22
+ /** Items per page */
23
+ itemsPerPage?: number;
24
+ /** Total number of pages */
25
+ totalPages?: number;
26
+ /** Callback when page changes */
27
+ onPageChange?: (page: number) => void;
28
+ /** Maximum number of page buttons to show */
29
+ maxPages?: number;
30
+ /** Show first/last page buttons */
31
+ showFirstLast?: boolean;
32
+ /** Show previous/next buttons */
33
+ showPrevNext?: boolean;
34
+ /** Custom render function for page button */
35
+ renderPageButton?: (page: number, isActive: boolean, isDisabled: boolean) => React.ReactNode;
36
+ /** Custom className */
37
+ className?: string;
38
+ /** Custom styles */
39
+ style?: React.CSSProperties;
40
+ /** Custom theme */
41
+ theme?: PaginationTheme;
42
+ }
43
+ export declare const Pagination: React.FC<PaginationProps>;
44
+ //# sourceMappingURL=Pagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../../src/components/Pagination.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iCAAiC;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IAC7F,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA0OhD,CAAC"}