@seekora-ai/ui-sdk-react 0.2.12 → 0.2.14

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 (102) hide show
  1. package/dist/components/CurrentRefinements.d.ts +22 -2
  2. package/dist/components/CurrentRefinements.d.ts.map +1 -1
  3. package/dist/components/CurrentRefinements.js +199 -47
  4. package/dist/components/Facets.d.ts +30 -1
  5. package/dist/components/Facets.d.ts.map +1 -1
  6. package/dist/components/Facets.js +418 -46
  7. package/dist/components/HierarchicalMenu.d.ts.map +1 -1
  8. package/dist/components/HierarchicalMenu.js +112 -4
  9. package/dist/components/InfiniteHits.d.ts +2 -0
  10. package/dist/components/InfiniteHits.d.ts.map +1 -1
  11. package/dist/components/InfiniteHits.js +6 -3
  12. package/dist/components/Pagination.d.ts +47 -1
  13. package/dist/components/Pagination.d.ts.map +1 -1
  14. package/dist/components/Pagination.js +166 -28
  15. package/dist/components/QuerySuggestions.d.ts +2 -0
  16. package/dist/components/QuerySuggestions.d.ts.map +1 -1
  17. package/dist/components/QuerySuggestions.js +4 -3
  18. package/dist/components/QuerySuggestionsDropdown.d.ts +1 -1
  19. package/dist/components/QuerySuggestionsDropdown.d.ts.map +1 -1
  20. package/dist/components/QuerySuggestionsDropdown.js +4 -4
  21. package/dist/components/RangeSlider.d.ts.map +1 -1
  22. package/dist/components/RangeSlider.js +49 -2
  23. package/dist/components/Recommendations.d.ts +6 -0
  24. package/dist/components/Recommendations.d.ts.map +1 -1
  25. package/dist/components/Recommendations.js +12 -6
  26. package/dist/components/RichQuerySuggestions.d.ts +11 -0
  27. package/dist/components/RichQuerySuggestions.d.ts.map +1 -1
  28. package/dist/components/RichQuerySuggestions.js +2 -3
  29. package/dist/components/SearchBar.d.ts +18 -0
  30. package/dist/components/SearchBar.d.ts.map +1 -1
  31. package/dist/components/SearchBar.js +134 -24
  32. package/dist/components/SearchProvider.d.ts +8 -1
  33. package/dist/components/SearchProvider.d.ts.map +1 -1
  34. package/dist/components/SearchProvider.js +16 -4
  35. package/dist/components/SearchResults.d.ts +12 -0
  36. package/dist/components/SearchResults.d.ts.map +1 -1
  37. package/dist/components/SearchResults.js +11 -5
  38. package/dist/components/SortBy.d.ts +44 -4
  39. package/dist/components/SortBy.d.ts.map +1 -1
  40. package/dist/components/SortBy.js +154 -29
  41. package/dist/components/Stats.d.ts +14 -0
  42. package/dist/components/Stats.d.ts.map +1 -1
  43. package/dist/components/Stats.js +172 -23
  44. package/dist/components/section-primitives/SectionItemGrid.d.ts +3 -1
  45. package/dist/components/section-primitives/SectionItemGrid.d.ts.map +1 -1
  46. package/dist/components/section-primitives/SectionItemGrid.js +3 -2
  47. package/dist/components/suggestions/AmazonDropdown.d.ts.map +1 -1
  48. package/dist/components/suggestions/AmazonDropdown.js +4 -6
  49. package/dist/components/suggestions/GoogleDropdown.d.ts.map +1 -1
  50. package/dist/components/suggestions/GoogleDropdown.js +4 -8
  51. package/dist/components/suggestions/MinimalDropdown.d.ts.map +1 -1
  52. package/dist/components/suggestions/MinimalDropdown.js +4 -6
  53. package/dist/components/suggestions/MobileSheetDropdown.d.ts.map +1 -1
  54. package/dist/components/suggestions/MobileSheetDropdown.js +4 -6
  55. package/dist/components/suggestions/PinterestDropdown.d.ts.map +1 -1
  56. package/dist/components/suggestions/PinterestDropdown.js +4 -8
  57. package/dist/components/suggestions/ShopifyDropdown.d.ts.map +1 -1
  58. package/dist/components/suggestions/ShopifyDropdown.js +4 -6
  59. package/dist/components/suggestions/SpotlightDropdown.d.ts.map +1 -1
  60. package/dist/components/suggestions/SpotlightDropdown.js +4 -6
  61. package/dist/components/suggestions/SuggestionSearchBar.d.ts.map +1 -1
  62. package/dist/components/suggestions/SuggestionSearchBar.js +1 -0
  63. package/dist/components/suggestions/types.d.ts +2 -0
  64. package/dist/components/suggestions/types.d.ts.map +1 -1
  65. package/dist/components/suggestions/utils.d.ts +10 -1
  66. package/dist/components/suggestions/utils.d.ts.map +1 -1
  67. package/dist/components/suggestions/utils.js +36 -0
  68. package/dist/components/suggestions-primitives/SuggestionList.d.ts +8 -1
  69. package/dist/components/suggestions-primitives/SuggestionList.d.ts.map +1 -1
  70. package/dist/components/suggestions-primitives/SuggestionList.js +7 -4
  71. package/dist/components/suggestions-primitives/SuggestionsDropdownComposition.d.ts.map +1 -1
  72. package/dist/components/suggestions-primitives/SuggestionsDropdownComposition.js +0 -2
  73. package/dist/components/suggestions-primitives/highlightMarkup.d.ts +16 -4
  74. package/dist/components/suggestions-primitives/highlightMarkup.d.ts.map +1 -1
  75. package/dist/components/suggestions-primitives/highlightMarkup.js +42 -4
  76. package/dist/docsearch/components/Results.d.ts +3 -1
  77. package/dist/docsearch/components/Results.d.ts.map +1 -1
  78. package/dist/docsearch/components/Results.js +6 -2
  79. package/dist/hooks/useClickTracking.d.ts +36 -0
  80. package/dist/hooks/useClickTracking.d.ts.map +1 -0
  81. package/dist/hooks/useClickTracking.js +96 -0
  82. package/dist/hooks/useExperiment.d.ts +25 -0
  83. package/dist/hooks/useExperiment.d.ts.map +1 -0
  84. package/dist/hooks/useExperiment.js +146 -0
  85. package/dist/hooks/useKeyboardNavigation.d.ts +51 -0
  86. package/dist/hooks/useKeyboardNavigation.d.ts.map +1 -0
  87. package/dist/hooks/useKeyboardNavigation.js +113 -0
  88. package/dist/hooks/useQuerySuggestions.d.ts.map +1 -1
  89. package/dist/hooks/useQuerySuggestions.js +19 -3
  90. package/dist/hooks/useQuerySuggestionsEnhanced.d.ts.map +1 -1
  91. package/dist/hooks/useQuerySuggestionsEnhanced.js +25 -7
  92. package/dist/hooks/useSuggestionsAnalytics.d.ts.map +1 -1
  93. package/dist/hooks/useSuggestionsAnalytics.js +6 -1
  94. package/dist/index.d.ts +4 -1
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.umd.js +1 -1
  97. package/dist/src/index.d.ts +249 -19
  98. package/dist/src/index.esm.js +1659 -305
  99. package/dist/src/index.esm.js.map +1 -1
  100. package/dist/src/index.js +1658 -304
  101. package/dist/src/index.js.map +1 -1
  102. package/package.json +3 -3
@@ -4,7 +4,7 @@
4
4
  * Displays a hierarchical menu for nested category navigation
5
5
  * Example: Electronics > Phones > iPhone
6
6
  */
7
- import React, { useState, useMemo, useCallback } from 'react';
7
+ import React, { useState, useMemo, useCallback, useRef } from 'react';
8
8
  import { useSearchContext } from './SearchProvider';
9
9
  import { useSearchState } from '../hooks/useSearchState';
10
10
  import { clsx } from 'clsx';
@@ -113,6 +113,112 @@ export const HierarchicalMenu = ({ attributes, separator = ' > ', limit = 10, sh
113
113
  const toggleShowMore = (level) => {
114
114
  setExpanded(prev => ({ ...prev, [level]: !prev[level] }));
115
115
  };
116
+ const containerRef = useRef(null);
117
+ // Collect all visible treeitem elements within the container
118
+ const getVisibleTreeItems = useCallback(() => {
119
+ if (!containerRef.current)
120
+ return [];
121
+ return Array.from(containerRef.current.querySelectorAll('[role="treeitem"]'));
122
+ }, []);
123
+ // Keyboard navigation handler for the tree
124
+ const handleKeyDown = useCallback((e) => {
125
+ const items = getVisibleTreeItems();
126
+ if (items.length === 0)
127
+ return;
128
+ const activeElement = document.activeElement;
129
+ // Find the treeitem that is or contains the active element
130
+ const currentItem = items.find(item => item === activeElement || item.contains(activeElement));
131
+ const currentIndex = currentItem ? items.indexOf(currentItem) : -1;
132
+ switch (e.key) {
133
+ case 'ArrowDown': {
134
+ e.preventDefault();
135
+ const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
136
+ const button = items[nextIndex].querySelector('button');
137
+ if (button)
138
+ button.focus();
139
+ else
140
+ items[nextIndex].focus();
141
+ break;
142
+ }
143
+ case 'ArrowUp': {
144
+ e.preventDefault();
145
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
146
+ const button = items[prevIndex].querySelector('button');
147
+ if (button)
148
+ button.focus();
149
+ else
150
+ items[prevIndex].focus();
151
+ break;
152
+ }
153
+ case 'ArrowRight': {
154
+ if (!currentItem)
155
+ break;
156
+ const isExpanded = currentItem.getAttribute('aria-expanded');
157
+ if (isExpanded === 'false') {
158
+ // Expand (select) this item
159
+ e.preventDefault();
160
+ const button = currentItem.querySelector('button');
161
+ if (button)
162
+ button.click();
163
+ }
164
+ else if (isExpanded === 'true') {
165
+ // Move to first child
166
+ e.preventDefault();
167
+ const childList = currentItem.querySelector('[role="group"]');
168
+ if (childList) {
169
+ const firstChild = childList.querySelector('[role="treeitem"]');
170
+ if (firstChild) {
171
+ const button = firstChild.querySelector('button');
172
+ if (button)
173
+ button.focus();
174
+ else
175
+ firstChild.focus();
176
+ }
177
+ }
178
+ }
179
+ break;
180
+ }
181
+ case 'ArrowLeft': {
182
+ if (!currentItem)
183
+ break;
184
+ const isExpanded = currentItem.getAttribute('aria-expanded');
185
+ if (isExpanded === 'true') {
186
+ // Collapse this item
187
+ e.preventDefault();
188
+ const button = currentItem.querySelector('button');
189
+ if (button)
190
+ button.click();
191
+ }
192
+ else {
193
+ // Move focus to parent treeitem
194
+ e.preventDefault();
195
+ const parentGroup = currentItem.closest('[role="group"]');
196
+ if (parentGroup) {
197
+ const parentItem = parentGroup.closest('[role="treeitem"]');
198
+ if (parentItem) {
199
+ const button = parentItem.querySelector(':scope > button');
200
+ if (button)
201
+ button.focus();
202
+ else
203
+ parentItem.focus();
204
+ }
205
+ }
206
+ }
207
+ break;
208
+ }
209
+ case 'Enter': {
210
+ if (!currentItem)
211
+ break;
212
+ e.preventDefault();
213
+ const button = currentItem.querySelector('button');
214
+ if (button)
215
+ button.click();
216
+ break;
217
+ }
218
+ default:
219
+ break;
220
+ }
221
+ }, [getVisibleTreeItems]);
116
222
  // Render a level of the hierarchy
117
223
  const renderLevel = (items, level) => {
118
224
  if (!items || items.length === 0)
@@ -121,12 +227,14 @@ export const HierarchicalMenu = ({ attributes, separator = ' > ', limit = 10, sh
121
227
  const displayLimit = isExpanded ? showMoreLimit : limit;
122
228
  const displayItems = items.slice(0, displayLimit);
123
229
  const hasMore = items.length > displayLimit;
124
- return (React.createElement("ul", { className: hierarchicalTheme.list, style: {
230
+ return (React.createElement("ul", { role: level === 0 ? 'tree' : 'group', className: hierarchicalTheme.list, style: {
125
231
  listStyle: 'none',
126
232
  margin: 0,
127
233
  padding: level > 0 ? `0 0 0 ${theme.spacing.medium}` : 0,
128
234
  } },
129
- displayItems.map((item, index) => (React.createElement("li", { key: item.value, className: clsx(hierarchicalTheme.item, item.isRefined && hierarchicalTheme.itemSelected, item.data && item.data.length > 0 && hierarchicalTheme.itemParent), style: {
235
+ displayItems.map((item, index) => (React.createElement("li", { key: item.value, role: "treeitem", ...(item.data && item.data.length > 0
236
+ ? { 'aria-expanded': !!item.isRefined }
237
+ : {}), className: clsx(hierarchicalTheme.item, item.isRefined && hierarchicalTheme.itemSelected, item.data && item.data.length > 0 && hierarchicalTheme.itemParent), style: {
130
238
  padding: `${theme.spacing.small} 0`,
131
239
  } }, renderItem ? (renderItem(item, level)) : (React.createElement(React.Fragment, null,
132
240
  React.createElement("button", { type: "button", onClick: () => handleItemClick(item, level), className: hierarchicalTheme.link, style: {
@@ -164,5 +272,5 @@ export const HierarchicalMenu = ({ attributes, separator = ' > ', limit = 10, sh
164
272
  if (processedItems.length === 0) {
165
273
  return null;
166
274
  }
167
- return (React.createElement("div", { className: clsx(hierarchicalTheme.root, className), style: style }, renderLevel(processedItems, 0)));
275
+ return (React.createElement("div", { ref: containerRef, className: clsx(hierarchicalTheme.root, className), style: style, tabIndex: 0, onKeyDown: handleKeyDown }, renderLevel(processedItems, 0)));
168
276
  };
@@ -21,6 +21,8 @@ export interface InfiniteHitsProps {
21
21
  renderHit?: (hit: ResultItem, index: number) => React.ReactNode;
22
22
  /** Custom render for empty state */
23
23
  renderEmpty?: () => React.ReactNode;
24
+ /** Show initial loading when fetching and no hits yet (default false: no loading screen) */
25
+ showInitialLoading?: boolean;
24
26
  /** Custom render for loading state */
25
27
  renderLoading?: () => React.ReactNode;
26
28
  /** Custom render for "Show More" button */
@@ -1 +1 @@
1
- {"version":3,"file":"InfiniteHits.d.ts","sourceRoot":"","sources":["../../src/components/InfiniteHits.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAIjF,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAEzE,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAChE,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACpC,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACtC,2CAA2C;IAC3C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,OAAO,CAAC;QACpB,OAAO,EAAE,MAAM,IAAI,CAAC;KACrB,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAoSpD,CAAC"}
1
+ {"version":3,"file":"InfiniteHits.d.ts","sourceRoot":"","sources":["../../src/components/InfiniteHits.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAA4D,MAAM,OAAO,CAAC;AAIjF,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAEzE,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAChE,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACpC,4FAA4F;IAC5F,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACtC,2CAA2C;IAC3C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,SAAS,EAAE,OAAO,CAAC;QACnB,UAAU,EAAE,OAAO,CAAC;QACpB,OAAO,EAAE,MAAM,IAAI,CAAC;KACrB,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAwSpD,CAAC"}
@@ -8,7 +8,7 @@ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
8
8
  import { useSearchContext } from './SearchProvider';
9
9
  import { useSearchState } from '../hooks/useSearchState';
10
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, }) => {
11
+ export const InfiniteHits = ({ renderHit, renderEmpty, showInitialLoading = false, renderLoading, renderShowMore, showMoreButton = true, useInfiniteScroll = false, scrollThreshold = 0.1, fieldMapping, showMoreLabel = 'Show more', loadingLabel = 'Loading...', onHitClick, className, style, theme: customTheme, syncWithState = true, }) => {
12
12
  const { theme, stateManager } = useSearchContext();
13
13
  const { results, loading, currentPage, setPage } = useSearchState();
14
14
  const infiniteHitsTheme = customTheme || {};
@@ -163,10 +163,13 @@ export const InfiniteHits = ({ renderHit, renderEmpty, renderLoading, renderShow
163
163
  cursor: isLastPage || isLoadingMore ? 'not-allowed' : 'pointer',
164
164
  transition: theme.transitions?.fast || '150ms ease-in-out',
165
165
  } }, isLoadingMore ? loadingLabel : isLastPage ? 'No more results' : showMoreLabel));
166
- // Initial loading state
167
- if (loading && accumulatedHits.length === 0) {
166
+ // Initial loading state (only when showInitialLoading: default no loading screen)
167
+ if (loading && accumulatedHits.length === 0 && showInitialLoading) {
168
168
  return (React.createElement("div", { className: clsx(infiniteHitsTheme.root, className), style: style }, renderLoading ? renderLoading() : defaultRenderLoading()));
169
169
  }
170
+ if (loading && accumulatedHits.length === 0) {
171
+ return React.createElement("div", { className: clsx(infiniteHitsTheme.root, className), style: style });
172
+ }
170
173
  // Empty state
171
174
  if (!loading && accumulatedHits.length === 0) {
172
175
  return (React.createElement("div", { className: clsx(infiniteHitsTheme.root, className), style: style }, renderEmpty ? renderEmpty() : defaultRenderEmpty()));
@@ -1,7 +1,16 @@
1
1
  /**
2
2
  * Pagination Component
3
3
  *
4
- * Displays pagination controls for search results
4
+ * Displays pagination controls for search results.
5
+ * Supports three display variants: numbered, load-more, and simple.
6
+ *
7
+ * CSS Variables (applied to the container element):
8
+ * --seekora-pagination-bg
9
+ * --seekora-pagination-color
10
+ * --seekora-pagination-active-bg
11
+ * --seekora-pagination-active-color
12
+ * --seekora-pagination-border
13
+ * --seekora-pagination-radius
5
14
  */
6
15
  import React from 'react';
7
16
  import type { SearchResponse } from '@seekora-ai/search-sdk';
@@ -13,6 +22,18 @@ export interface PaginationTheme {
13
22
  itemDisabled?: string;
14
23
  link?: string;
15
24
  ellipsis?: string;
25
+ /** Class for the Load More button */
26
+ loadMoreButton?: string;
27
+ /** Class for the Load More descriptive text (e.g. "X remaining") */
28
+ loadMoreText?: string;
29
+ /** Class for the simple variant outer container */
30
+ simpleContainer?: string;
31
+ /** Class for the "Page X of Y" text in the simple variant */
32
+ simpleText?: string;
33
+ /** Class for Previous/Next buttons in the simple variant */
34
+ simpleButton?: string;
35
+ /** Class for the "Page X of Y" info text (usable in any variant) */
36
+ pageInfo?: string;
16
37
  }
17
38
  export interface PaginationProps {
18
39
  /** Search results response */
@@ -39,6 +60,31 @@ export interface PaginationProps {
39
60
  style?: React.CSSProperties;
40
61
  /** Custom theme */
41
62
  theme?: PaginationTheme;
63
+ /**
64
+ * Display variant.
65
+ * - 'numbered' (default): numbered page buttons with prev/next
66
+ * - 'load-more': single "Load More" button
67
+ * - 'simple': Previous / Next buttons with "Page X of Y" text
68
+ */
69
+ variant?: 'numbered' | 'load-more' | 'simple';
70
+ /** Label for the Load More button (default: "Load More") */
71
+ loadMoreText?: string;
72
+ /**
73
+ * Button / text sizing.
74
+ * - 'small': compact
75
+ * - 'medium' (default): standard
76
+ * - 'large': spacious
77
+ */
78
+ size?: 'small' | 'medium' | 'large';
79
+ /**
80
+ * Show "Page X of Y" text.
81
+ * Defaults to false for numbered, true for simple.
82
+ */
83
+ showPageInfo?: boolean;
84
+ /** Label for the Previous button (default: "Previous") */
85
+ previousLabel?: string;
86
+ /** Label for the Next button (default: "Next") */
87
+ nextLabel?: string;
42
88
  }
43
89
  export declare const Pagination: React.FC<PaginationProps>;
44
90
  //# sourceMappingURL=Pagination.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../../src/components/Pagination.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;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;IAClB,qCAAqC;IACrC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,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;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC9C,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AASD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAkbhD,CAAC"}
@@ -1,13 +1,28 @@
1
1
  /**
2
2
  * Pagination Component
3
3
  *
4
- * Displays pagination controls for search results
4
+ * Displays pagination controls for search results.
5
+ * Supports three display variants: numbered, load-more, and simple.
6
+ *
7
+ * CSS Variables (applied to the container element):
8
+ * --seekora-pagination-bg
9
+ * --seekora-pagination-color
10
+ * --seekora-pagination-active-bg
11
+ * --seekora-pagination-active-color
12
+ * --seekora-pagination-border
13
+ * --seekora-pagination-radius
5
14
  */
6
15
  import React from 'react';
7
16
  import { useSearchContext } from './SearchProvider';
8
17
  import { useSearchState } from '../hooks/useSearchState';
9
18
  import { clsx } from 'clsx';
10
- export const Pagination = ({ results: resultsProp, currentPage: currentPageProp, itemsPerPage: itemsPerPageProp, totalPages: totalPagesProp, onPageChange, maxPages = 7, showFirstLast = true, showPrevNext = true, renderPageButton, className, style, theme: customTheme, }) => {
19
+ /** Size-specific style tokens */
20
+ const SIZE_TOKENS = {
21
+ small: { paddingKey: 'small', fontSizeKey: 'small', minWidth: '32px' },
22
+ medium: { paddingKey: 'small', fontSizeKey: 'medium', minWidth: '40px' },
23
+ large: { paddingKey: 'medium', fontSizeKey: 'large', minWidth: '48px' },
24
+ };
25
+ export const Pagination = ({ results: resultsProp, currentPage: currentPageProp, itemsPerPage: itemsPerPageProp, totalPages: totalPagesProp, onPageChange, maxPages = 7, showFirstLast = true, showPrevNext = true, renderPageButton, className, style, theme: customTheme, variant = 'numbered', loadMoreText = 'Load More', size = 'medium', showPageInfo, previousLabel = 'Previous', nextLabel = 'Next', }) => {
11
26
  const { theme } = useSearchContext();
12
27
  const { results: stateResults, currentPage: stateCurrentPage, setPage } = useSearchState();
13
28
  const paginationTheme = customTheme || {};
@@ -30,6 +45,19 @@ export const Pagination = ({ results: resultsProp, currentPage: currentPageProp,
30
45
  || res?.data?.total_pages
31
46
  || res?.data?.data?.total_pages
32
47
  || Math.ceil(totalResults / itemsPerPage);
48
+ // Resolve whether to show page info text
49
+ const resolvedShowPageInfo = showPageInfo !== undefined
50
+ ? showPageInfo
51
+ : variant === 'simple';
52
+ // Size tokens
53
+ const sizeTokens = SIZE_TOKENS[size];
54
+ // CSS variable aware helpers — allow overrides via custom properties
55
+ const cssVarBg = 'var(--seekora-pagination-bg, ' + theme.colors.background + ')';
56
+ const cssVarColor = 'var(--seekora-pagination-color, ' + theme.colors.text + ')';
57
+ const cssVarActiveBg = 'var(--seekora-pagination-active-bg, ' + theme.colors.primary + ')';
58
+ const cssVarActiveColor = 'var(--seekora-pagination-active-color, #fff)';
59
+ const cssVarBorder = 'var(--seekora-pagination-border, ' + theme.colors.border + ')';
60
+ const cssVarRadius = 'var(--seekora-pagination-radius, ' + (typeof theme.borderRadius === 'string' ? theme.borderRadius : theme.borderRadius.medium) + ')';
33
61
  const handlePageChange = (page) => {
34
62
  if (page < 1 || page > totalPages || page === currentPage)
35
63
  return;
@@ -40,22 +68,26 @@ export const Pagination = ({ results: resultsProp, currentPage: currentPageProp,
40
68
  onPageChange(page);
41
69
  }
42
70
  };
43
- const defaultRenderPageButton = (page, isActive, isDisabled) => (React.createElement("button", { type: "button", disabled: isDisabled, onClick: () => !isDisabled && handlePageChange(page), className: clsx(paginationTheme.item, isActive && paginationTheme.itemActive, isDisabled && paginationTheme.itemDisabled), style: {
44
- padding: theme.spacing.small,
71
+ const defaultRenderPageButton = (page, isActive, isDisabled) => (React.createElement("button", { type: "button", disabled: isDisabled, onClick: () => !isDisabled && handlePageChange(page), "aria-current": isActive ? 'page' : undefined, "aria-label": `Page ${page}`, className: clsx(paginationTheme.item, isActive && paginationTheme.itemActive, isDisabled && paginationTheme.itemDisabled), style: {
72
+ padding: theme.spacing[sizeTokens.paddingKey],
45
73
  margin: `0 ${theme.spacing.small}`,
46
- border: `1px solid ${theme.colors.border}`,
47
- borderRadius: typeof theme.borderRadius === 'string' ? theme.borderRadius : theme.borderRadius.medium,
48
- backgroundColor: isActive ? theme.colors.primary : theme.colors.background,
49
- color: isActive ? '#fff' : theme.colors.text,
74
+ border: `1px solid ${cssVarBorder}`,
75
+ borderRadius: cssVarRadius,
76
+ backgroundColor: isActive ? cssVarActiveBg : cssVarBg,
77
+ color: isActive ? cssVarActiveColor : cssVarColor,
50
78
  cursor: isDisabled ? 'not-allowed' : 'pointer',
51
79
  opacity: isDisabled ? 0.5 : 1,
52
- fontSize: theme.typography.fontSize.medium,
53
- minWidth: '40px',
80
+ fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
81
+ minWidth: sizeTokens.minWidth,
54
82
  ...(isActive && {
55
83
  fontWeight: 'bold',
56
84
  }),
57
85
  } }, page));
58
- if (totalPages <= 1) {
86
+ if (totalPages <= 1 && variant !== 'load-more') {
87
+ return null;
88
+ }
89
+ // For load-more, hide when there are no more pages to load
90
+ if (variant === 'load-more' && currentPage >= totalPages) {
59
91
  return null;
60
92
  }
61
93
  // Calculate page range to display
@@ -88,6 +120,94 @@ export const Pagination = ({ results: resultsProp, currentPage: currentPageProp,
88
120
  }
89
121
  return pages;
90
122
  };
123
+ // ── Page info element ──────────────────────────────────────────────
124
+ const pageInfoElement = resolvedShowPageInfo ? (React.createElement("span", { className: clsx(paginationTheme.pageInfo), style: {
125
+ fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
126
+ color: cssVarColor,
127
+ padding: `0 ${theme.spacing.small}`,
128
+ whiteSpace: 'nowrap',
129
+ }, "aria-live": "polite" },
130
+ "Page ",
131
+ currentPage,
132
+ " of ",
133
+ totalPages)) : null;
134
+ // ── Load More variant ──────────────────────────────────────────────
135
+ if (variant === 'load-more') {
136
+ const remaining = Math.max(0, totalResults - currentPage * itemsPerPage);
137
+ return (React.createElement("nav", { className: clsx(paginationTheme.container, className), style: {
138
+ display: 'flex',
139
+ flexDirection: 'column',
140
+ alignItems: 'center',
141
+ gap: theme.spacing.small,
142
+ ...style,
143
+ }, "aria-label": "Pagination" },
144
+ React.createElement("button", { type: "button", onClick: () => handlePageChange(currentPage + 1), className: clsx(paginationTheme.loadMoreButton), style: {
145
+ padding: `${theme.spacing[sizeTokens.paddingKey]} ${theme.spacing.large}`,
146
+ border: 'none',
147
+ borderRadius: cssVarRadius,
148
+ backgroundColor: cssVarActiveBg,
149
+ color: cssVarActiveColor,
150
+ cursor: 'pointer',
151
+ fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
152
+ fontWeight: theme.typography.fontWeight?.medium ?? 500,
153
+ transition: theme.transitions?.fast ?? '150ms ease-in-out',
154
+ minWidth: sizeTokens.minWidth,
155
+ }, "aria-label": remaining > 0 ? `${loadMoreText} (${remaining} remaining)` : loadMoreText },
156
+ loadMoreText,
157
+ remaining > 0 && (React.createElement("span", { className: clsx(paginationTheme.loadMoreText), style: {
158
+ marginLeft: theme.spacing.small,
159
+ opacity: 0.85,
160
+ fontSize: theme.typography.fontSize.small,
161
+ } },
162
+ "(",
163
+ remaining,
164
+ " remaining)"))),
165
+ pageInfoElement));
166
+ }
167
+ // ── Simple variant ─────────────────────────────────────────────────
168
+ if (variant === 'simple') {
169
+ return (React.createElement("nav", { className: clsx(paginationTheme.container, paginationTheme.simpleContainer, className), style: {
170
+ display: 'flex',
171
+ alignItems: 'center',
172
+ justifyContent: 'center',
173
+ gap: theme.spacing.medium,
174
+ ...style,
175
+ }, "aria-label": "Pagination" },
176
+ React.createElement("button", { type: "button", disabled: currentPage === 1, onClick: () => handlePageChange(currentPage - 1), className: clsx(paginationTheme.simpleButton, currentPage === 1 && paginationTheme.itemDisabled), style: {
177
+ padding: `${theme.spacing[sizeTokens.paddingKey]} ${theme.spacing.medium}`,
178
+ border: `1px solid ${cssVarBorder}`,
179
+ borderRadius: cssVarRadius,
180
+ backgroundColor: cssVarBg,
181
+ color: cssVarColor,
182
+ cursor: currentPage === 1 ? 'not-allowed' : 'pointer',
183
+ opacity: currentPage === 1 ? 0.5 : 1,
184
+ fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
185
+ minWidth: sizeTokens.minWidth,
186
+ transition: theme.transitions?.fast ?? '150ms ease-in-out',
187
+ }, "aria-label": "Previous page" }, previousLabel),
188
+ React.createElement("span", { className: clsx(paginationTheme.simpleText, paginationTheme.pageInfo), style: {
189
+ fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
190
+ color: cssVarColor,
191
+ whiteSpace: 'nowrap',
192
+ }, "aria-live": "polite" },
193
+ "Page ",
194
+ currentPage,
195
+ " of ",
196
+ totalPages),
197
+ React.createElement("button", { type: "button", disabled: currentPage === totalPages, onClick: () => handlePageChange(currentPage + 1), className: clsx(paginationTheme.simpleButton, currentPage === totalPages && paginationTheme.itemDisabled), style: {
198
+ padding: `${theme.spacing[sizeTokens.paddingKey]} ${theme.spacing.medium}`,
199
+ border: `1px solid ${cssVarBorder}`,
200
+ borderRadius: cssVarRadius,
201
+ backgroundColor: cssVarBg,
202
+ color: cssVarColor,
203
+ cursor: currentPage === totalPages ? 'not-allowed' : 'pointer',
204
+ opacity: currentPage === totalPages ? 0.5 : 1,
205
+ fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
206
+ minWidth: sizeTokens.minWidth,
207
+ transition: theme.transitions?.fast ?? '150ms ease-in-out',
208
+ }, "aria-label": "Next page" }, nextLabel)));
209
+ }
210
+ // ── Numbered variant (default — original behavior) ─────────────────
91
211
  const pageNumbers = getPageNumbers();
92
212
  return (React.createElement("nav", { className: clsx(paginationTheme.container, className), style: style, "aria-label": "Pagination" },
93
213
  React.createElement("ul", { className: paginationTheme.list, style: {
@@ -98,27 +218,44 @@ export const Pagination = ({ results: resultsProp, currentPage: currentPageProp,
98
218
  padding: 0,
99
219
  margin: 0,
100
220
  flexWrap: 'wrap',
221
+ }, tabIndex: 0, onKeyDown: (e) => {
222
+ if (e.key === 'ArrowLeft') {
223
+ e.preventDefault();
224
+ handlePageChange(currentPage - 1);
225
+ }
226
+ else if (e.key === 'ArrowRight') {
227
+ e.preventDefault();
228
+ handlePageChange(currentPage + 1);
229
+ }
230
+ else if (e.key === 'Home') {
231
+ e.preventDefault();
232
+ handlePageChange(1);
233
+ }
234
+ else if (e.key === 'End') {
235
+ e.preventDefault();
236
+ handlePageChange(totalPages);
237
+ }
101
238
  } },
102
239
  showPrevNext && (React.createElement("li", null,
103
240
  React.createElement("button", { type: "button", disabled: currentPage === 1, onClick: () => handlePageChange(currentPage - 1), className: clsx(paginationTheme.item, currentPage === 1 && paginationTheme.itemDisabled), style: {
104
- padding: theme.spacing.small,
241
+ padding: theme.spacing[sizeTokens.paddingKey],
105
242
  margin: `0 ${theme.spacing.small}`,
106
- border: `1px solid ${theme.colors.border}`,
107
- borderRadius: typeof theme.borderRadius === 'string' ? theme.borderRadius : theme.borderRadius.medium,
108
- backgroundColor: theme.colors.background,
109
- color: theme.colors.text,
243
+ border: `1px solid ${cssVarBorder}`,
244
+ borderRadius: cssVarRadius,
245
+ backgroundColor: cssVarBg,
246
+ color: cssVarColor,
110
247
  cursor: currentPage === 1 ? 'not-allowed' : 'pointer',
111
248
  opacity: currentPage === 1 ? 0.5 : 1,
112
- fontSize: theme.typography.fontSize.medium,
113
- }, "aria-label": "Previous page" }, "Previous"))),
249
+ fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
250
+ }, "aria-label": "Previous page" }, previousLabel))),
114
251
  pageNumbers.map((page, index) => {
115
252
  if (page === 'ellipsis') {
116
253
  return (React.createElement("li", { key: `ellipsis-${index}` },
117
254
  React.createElement("span", { className: paginationTheme.ellipsis, style: {
118
- padding: theme.spacing.small,
255
+ padding: theme.spacing[sizeTokens.paddingKey],
119
256
  margin: `0 ${theme.spacing.small}`,
120
- color: theme.colors.text,
121
- fontSize: theme.typography.fontSize.medium,
257
+ color: cssVarColor,
258
+ fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
122
259
  } }, "...")));
123
260
  }
124
261
  const isActive = page === currentPage;
@@ -129,14 +266,15 @@ export const Pagination = ({ results: resultsProp, currentPage: currentPageProp,
129
266
  }),
130
267
  showPrevNext && (React.createElement("li", null,
131
268
  React.createElement("button", { type: "button", disabled: currentPage === totalPages, onClick: () => handlePageChange(currentPage + 1), className: clsx(paginationTheme.item, currentPage === totalPages && paginationTheme.itemDisabled), style: {
132
- padding: theme.spacing.small,
269
+ padding: theme.spacing[sizeTokens.paddingKey],
133
270
  margin: `0 ${theme.spacing.small}`,
134
- border: `1px solid ${theme.colors.border}`,
135
- borderRadius: typeof theme.borderRadius === 'string' ? theme.borderRadius : theme.borderRadius.medium,
136
- backgroundColor: theme.colors.background,
137
- color: theme.colors.text,
271
+ border: `1px solid ${cssVarBorder}`,
272
+ borderRadius: cssVarRadius,
273
+ backgroundColor: cssVarBg,
274
+ color: cssVarColor,
138
275
  cursor: currentPage === totalPages ? 'not-allowed' : 'pointer',
139
276
  opacity: currentPage === totalPages ? 0.5 : 1,
140
- fontSize: theme.typography.fontSize.medium,
141
- }, "aria-label": "Next page" }, "Next"))))));
277
+ fontSize: theme.typography.fontSize[sizeTokens.fontSizeKey],
278
+ }, "aria-label": "Next page" }, nextLabel))),
279
+ resolvedShowPageInfo && (React.createElement("li", { style: { marginLeft: theme.spacing.small } }, pageInfoElement)))));
142
280
  };
@@ -26,6 +26,8 @@ export interface QuerySuggestionsProps {
26
26
  minQueryLength?: number;
27
27
  onSuggestionClick?: (suggestion: string) => void;
28
28
  renderSuggestion?: (suggestion: SuggestionItem, index: number) => React.ReactNode;
29
+ /** Show loading state when fetching and no previous suggestions (default false: show previous results until new render) */
30
+ showLoadingState?: boolean;
29
31
  renderLoading?: () => React.ReactNode;
30
32
  renderEmpty?: () => React.ReactNode;
31
33
  showTitle?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"QuerySuggestions.d.ts","sourceRoot":"","sources":["../../src/components/QuerySuggestions.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAMxC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAClF,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,KAAK,CAAC,EAAE,qBAAqB,CAAC;CAC/B;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAwK5D,CAAC"}
1
+ {"version":3,"file":"QuerySuggestions.d.ts","sourceRoot":"","sources":["../../src/components/QuerySuggestions.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAMxC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAClF,2HAA2H;IAC3H,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,KAAK,CAAC,EAAE,qBAAqB,CAAC;CAC/B;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CA0K5D,CAAC"}
@@ -7,7 +7,7 @@ import React, { useState } from 'react';
7
7
  import { useSearchContext } from './SearchProvider';
8
8
  import { useQuerySuggestions } from '../hooks/useQuerySuggestions';
9
9
  import { clsx } from 'clsx';
10
- export const QuerySuggestions = ({ query = '', maxSuggestions = 10, debounceMs = 300, minQueryLength = 2, onSuggestionClick, renderSuggestion, renderLoading, renderEmpty, showTitle = false, title = 'Suggestions', className, style, theme: customTheme, }) => {
10
+ export const QuerySuggestions = ({ query = '', maxSuggestions = 10, debounceMs = 300, minQueryLength = 2, onSuggestionClick, renderSuggestion, showLoadingState = false, renderLoading, renderEmpty, showTitle = false, title = 'Suggestions', className, style, theme: customTheme, }) => {
11
11
  const { client, theme } = useSearchContext();
12
12
  const [selectedIndex, setSelectedIndex] = useState(-1);
13
13
  const { suggestions, loading, error } = useQuerySuggestions({
@@ -44,7 +44,8 @@ export const QuerySuggestions = ({ query = '', maxSuggestions = 10, debounceMs =
44
44
  if (query.length < minQueryLength) {
45
45
  return null;
46
46
  }
47
- if (loading) {
47
+ // When loading with no previous results, show loading only if showLoadingState (default: show previous results, no loading screen)
48
+ if (loading && displayedSuggestions.length === 0 && showLoadingState) {
48
49
  return (React.createElement("div", { className: clsx(suggestionsTheme.container, className), style: style },
49
50
  showTitle && (React.createElement("div", { className: suggestionsTheme.title, style: {
50
51
  fontSize: theme.typography.fontSize.large,
@@ -54,7 +55,7 @@ export const QuerySuggestions = ({ query = '', maxSuggestions = 10, debounceMs =
54
55
  } }, title)),
55
56
  renderLoading ? renderLoading() : defaultRenderLoading()));
56
57
  }
57
- if (error || displayedSuggestions.length === 0) {
58
+ if (error || (!loading && displayedSuggestions.length === 0)) {
58
59
  return (React.createElement("div", { className: clsx(suggestionsTheme.container, className), style: style },
59
60
  showTitle && (React.createElement("div", { className: suggestionsTheme.title, style: {
60
61
  fontSize: theme.typography.fontSize.large,
@@ -28,7 +28,7 @@ export interface QuerySuggestionsDropdownProps extends QuerySuggestionsEventHand
28
28
  maxRecentSearches?: number;
29
29
  /** Show suggestion counts */
30
30
  showCounts?: boolean;
31
- /** Show loading state */
31
+ /** Show loading state (default false: show previous results until new results render) */
32
32
  showLoading?: boolean;
33
33
  /** Show empty state when no results */
34
34
  showEmptyState?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"QuerySuggestionsDropdown.d.ts","sourceRoot":"","sources":["../../src/components/QuerySuggestionsDropdown.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"QuerySuggestionsDropdown.d.ts","sourceRoot":"","sources":["../../src/components/QuerySuggestionsDropdown.tsx"],"names":[],"mappings":"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"}
@@ -158,7 +158,7 @@ const LoadingSpinner = ({ style }) => (React.createElement("svg", { style: { ani
158
158
  // Component
159
159
  // ============================================================================
160
160
  export const QuerySuggestionsDropdown = forwardRef(function QuerySuggestionsDropdown(props, ref) {
161
- const { query, isOpen = true, maxSuggestions = 8, minQueryLength = 1, debounceMs = 200, showRecentSearches = true, maxRecentSearches = 5, showCounts = true, showLoading = true, showEmptyState = true, highlight = { enabled: true, preTag: '<mark>', postTag: '</mark>' }, keyboardNav = { enabled: true }, animation = { enabled: true, duration: 150, entrance: 'fade' }, classNames = {}, style, renderSuggestion, renderRecentSearch, renderLoading, renderEmpty, footer, position = 'absolute', width = '100%', zIndex = 1000, closeOnClickOutside = true, closeOnEscape = true, ariaLabel = 'Search suggestions', onSuggestionSelect, onRecentSearchClick, onRecentSearchRemove, onOpen, onClose, onNavigate, } = props;
161
+ const { query, isOpen = true, maxSuggestions = 8, minQueryLength = 1, debounceMs = 200, showRecentSearches = true, maxRecentSearches = 5, showCounts = true, showLoading = false, showEmptyState = true, highlight = { enabled: true, preTag: '<mark>', postTag: '</mark>' }, keyboardNav = { enabled: true }, animation = { enabled: true, duration: 150, entrance: 'fade' }, classNames = {}, style, renderSuggestion, renderRecentSearch, renderLoading, renderEmpty, footer, position = 'absolute', width = '100%', zIndex = 1000, closeOnClickOutside = true, closeOnEscape = true, ariaLabel = 'Search suggestions', onSuggestionSelect, onRecentSearchClick, onRecentSearchRemove, onOpen, onClose, onNavigate, } = props;
162
162
  const { client, theme } = useSearchContext();
163
163
  const containerRef = useRef(null);
164
164
  const [activeIndex, setActiveIndex] = useState(-1);
@@ -343,7 +343,7 @@ export const QuerySuggestionsDropdown = forwardRef(function QuerySuggestionsDrop
343
343
  loading && showLoading && (React.createElement("div", { className: classNames.loadingState, style: defaultStyles.loadingState }, renderLoading ? renderLoading() : (React.createElement(React.Fragment, null,
344
344
  React.createElement(LoadingSpinner, null),
345
345
  React.createElement("span", null, "Searching..."))))),
346
- !loading && showRecent && (React.createElement("div", { className: clsx('seekora-suggestions-section', classNames.section, classNames.recentSearches) },
346
+ showRecent && (React.createElement("div", { className: clsx('seekora-suggestions-section', classNames.section, classNames.recentSearches) },
347
347
  React.createElement("div", { className: classNames.sectionTitle, style: defaultStyles.sectionTitle }, "Recent Searches"),
348
348
  recentSearches.slice(0, maxRecentSearches).map((search, index) => {
349
349
  const isActive = activeIndex === index;
@@ -351,8 +351,8 @@ export const QuerySuggestionsDropdown = forwardRef(function QuerySuggestionsDrop
351
351
  onRecentSearchClick?.(search);
352
352
  }, onMouseEnter: () => setActiveIndex(index) }, renderRecentSearchItem(search, index, isActive)));
353
353
  }))),
354
- !loading && showRecent && showSuggestions && (React.createElement("div", { style: defaultStyles.divider })),
355
- !loading && showSuggestions && (React.createElement("div", { className: clsx('seekora-suggestions-section', classNames.section, classNames.suggestionsList) },
354
+ showRecent && showSuggestions && (React.createElement("div", { style: defaultStyles.divider })),
355
+ showSuggestions && (React.createElement("div", { className: clsx('seekora-suggestions-section', classNames.section, classNames.suggestionsList) },
356
356
  query.length > 0 && (React.createElement("div", { className: classNames.sectionTitle, style: defaultStyles.sectionTitle }, "Suggestions")),
357
357
  suggestions.map((suggestion, index) => {
358
358
  const itemIndex = showRecent ? recentSearches.length + index : index;