@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,168 @@
1
+ /**
2
+ * HierarchicalMenu Component
3
+ *
4
+ * Displays a hierarchical menu for nested category navigation
5
+ * Example: Electronics > Phones > iPhone
6
+ */
7
+ import React, { useState, useMemo, useCallback } from 'react';
8
+ import { useSearchContext } from './SearchProvider';
9
+ import { useSearchState } from '../hooks/useSearchState';
10
+ import { clsx } from 'clsx';
11
+ export const HierarchicalMenu = ({ attributes, separator = ' > ', limit = 10, showMore = true, showMoreLimit = 20, renderItem, onSelect, className, style, theme: customTheme, rootPath = null, sortBy = 'name', items: itemsProp, }) => {
12
+ const { theme, stateManager } = useSearchContext();
13
+ const { results, refinements, addRefinement, removeRefinement } = useSearchState();
14
+ const hierarchicalTheme = customTheme || {};
15
+ const [expanded, setExpanded] = useState({});
16
+ // Get hierarchical facet data from results
17
+ const getHierarchicalData = useCallback(() => {
18
+ if (itemsProp)
19
+ return itemsProp;
20
+ if (!results)
21
+ return [];
22
+ // Try to find hierarchical facet data in various response formats
23
+ const facets = results.facets || results.facet_counts || {};
24
+ // Build hierarchy from flat facet data
25
+ const rootAttribute = attributes[0];
26
+ const rootFacet = facets[rootAttribute] || {};
27
+ const items = [];
28
+ Object.entries(rootFacet).forEach(([value, count]) => {
29
+ const isRefined = refinements.some(r => r.field === rootAttribute && r.value === value);
30
+ items.push({
31
+ label: value,
32
+ value,
33
+ count: count,
34
+ isRefined,
35
+ data: getChildItems(value, 1),
36
+ });
37
+ });
38
+ return items;
39
+ }, [results, attributes, refinements, itemsProp]);
40
+ // Get child items for a parent value
41
+ const getChildItems = useCallback((parentValue, level) => {
42
+ if (level >= attributes.length)
43
+ return [];
44
+ if (!results)
45
+ return [];
46
+ const facets = results.facets || results.facet_counts || {};
47
+ const attribute = attributes[level];
48
+ const facet = facets[attribute] || {};
49
+ const items = [];
50
+ Object.entries(facet).forEach(([value, count]) => {
51
+ // Check if this item belongs to the parent
52
+ if (value.startsWith(parentValue + separator)) {
53
+ const label = value.replace(parentValue + separator, '').split(separator)[0];
54
+ const isRefined = refinements.some(r => r.field === attribute && r.value === value);
55
+ // Only add direct children
56
+ const remainingPath = value.replace(parentValue + separator, '');
57
+ if (!remainingPath.includes(separator)) {
58
+ items.push({
59
+ label,
60
+ value,
61
+ count: count,
62
+ isRefined,
63
+ data: getChildItems(value, level + 1),
64
+ });
65
+ }
66
+ }
67
+ });
68
+ return items;
69
+ }, [results, attributes, separator, refinements]);
70
+ // Get sorted and limited items
71
+ const processedItems = useMemo(() => {
72
+ let items = getHierarchicalData();
73
+ // Sort
74
+ items = [...items].sort((a, b) => {
75
+ if (sortBy === 'count') {
76
+ return (b.count || 0) - (a.count || 0);
77
+ }
78
+ if (sortBy === 'isRefined') {
79
+ if (a.isRefined && !b.isRefined)
80
+ return -1;
81
+ if (!a.isRefined && b.isRefined)
82
+ return 1;
83
+ }
84
+ return a.label.localeCompare(b.label);
85
+ });
86
+ return items;
87
+ }, [getHierarchicalData, sortBy]);
88
+ // Handle item click
89
+ const handleItemClick = useCallback((item, level) => {
90
+ const attribute = attributes[level] || attributes[0];
91
+ if (item.isRefined) {
92
+ // Remove this and all child refinements
93
+ attributes.slice(level).forEach((attr) => {
94
+ refinements
95
+ .filter(r => r.field === attr && r.value.startsWith(item.value))
96
+ .forEach(r => removeRefinement(r.field, r.value, false));
97
+ });
98
+ stateManager.search();
99
+ }
100
+ else {
101
+ // Clear sibling refinements at this level
102
+ refinements
103
+ .filter(r => r.field === attribute && r.value !== item.value)
104
+ .forEach(r => removeRefinement(r.field, r.value, false));
105
+ // Add this refinement
106
+ addRefinement(attribute, item.value, true);
107
+ }
108
+ if (onSelect) {
109
+ onSelect(item.value, level);
110
+ }
111
+ }, [attributes, refinements, addRefinement, removeRefinement, stateManager, onSelect]);
112
+ // Toggle show more
113
+ const toggleShowMore = (level) => {
114
+ setExpanded(prev => ({ ...prev, [level]: !prev[level] }));
115
+ };
116
+ // Render a level of the hierarchy
117
+ const renderLevel = (items, level) => {
118
+ if (!items || items.length === 0)
119
+ return null;
120
+ const isExpanded = expanded[level] || false;
121
+ const displayLimit = isExpanded ? showMoreLimit : limit;
122
+ const displayItems = items.slice(0, displayLimit);
123
+ const hasMore = items.length > displayLimit;
124
+ return (React.createElement("ul", { className: hierarchicalTheme.list, style: {
125
+ listStyle: 'none',
126
+ margin: 0,
127
+ padding: level > 0 ? `0 0 0 ${theme.spacing.medium}` : 0,
128
+ } },
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: {
130
+ padding: `${theme.spacing.small} 0`,
131
+ } }, renderItem ? (renderItem(item, level)) : (React.createElement(React.Fragment, null,
132
+ React.createElement("button", { type: "button", onClick: () => handleItemClick(item, level), className: hierarchicalTheme.link, style: {
133
+ display: 'flex',
134
+ alignItems: 'center',
135
+ gap: theme.spacing.small,
136
+ width: '100%',
137
+ padding: 0,
138
+ background: 'none',
139
+ border: 'none',
140
+ cursor: 'pointer',
141
+ textAlign: 'left',
142
+ fontFamily: 'inherit',
143
+ fontSize: theme.typography.fontSize.small,
144
+ color: item.isRefined ? theme.colors.primary : theme.colors.text,
145
+ fontWeight: item.isRefined ? 600 : 400,
146
+ } },
147
+ React.createElement("span", { className: hierarchicalTheme.label, style: { flex: 1 } }, item.label),
148
+ item.count !== undefined && (React.createElement("span", { className: hierarchicalTheme.count, style: {
149
+ color: theme.colors.textSecondary,
150
+ fontSize: theme.typography.fontSize.small,
151
+ } }, item.count))),
152
+ item.isRefined && item.data && item.data.length > 0 && (renderLevel(item.data, level + 1))))))),
153
+ showMore && hasMore && (React.createElement("li", null,
154
+ React.createElement("button", { type: "button", onClick: () => toggleShowMore(level), className: hierarchicalTheme.showMore, style: {
155
+ padding: `${theme.spacing.small} 0`,
156
+ background: 'none',
157
+ border: 'none',
158
+ cursor: 'pointer',
159
+ fontSize: theme.typography.fontSize.small,
160
+ color: theme.colors.primary,
161
+ textDecoration: 'underline',
162
+ } }, isExpanded ? 'Show less' : `Show ${items.length - displayLimit} more`)))));
163
+ };
164
+ if (processedItems.length === 0) {
165
+ return null;
166
+ }
167
+ return (React.createElement("div", { className: clsx(hierarchicalTheme.root, className), style: style }, renderLevel(processedItems, 0)));
168
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Highlight Component
3
+ *
4
+ * Renders highlighted search result text with matching terms emphasized
5
+ */
6
+ import React from 'react';
7
+ import { HighlightPart } from '@seekora-ai/ui-sdk-core';
8
+ export interface HighlightTheme {
9
+ root?: string;
10
+ highlighted?: string;
11
+ nonHighlighted?: string;
12
+ }
13
+ export interface HighlightProps {
14
+ /** The search hit containing highlight data */
15
+ hit: Record<string, any>;
16
+ /** The attribute to highlight */
17
+ attribute: string;
18
+ /** Custom className */
19
+ className?: string;
20
+ /** Custom styles */
21
+ style?: React.CSSProperties;
22
+ /** Custom theme */
23
+ theme?: HighlightTheme;
24
+ /** Tag name for root element (default: 'span') */
25
+ tagName?: keyof JSX.IntrinsicElements;
26
+ /** Custom render for highlighted parts */
27
+ renderHighlighted?: (part: HighlightPart, index: number) => React.ReactNode;
28
+ /** Custom render for non-highlighted parts */
29
+ renderNonHighlighted?: (part: HighlightPart, index: number) => React.ReactNode;
30
+ /** Fallback query for client-side highlighting (when server highlight not available) */
31
+ query?: string;
32
+ }
33
+ export declare const Highlight: React.FC<HighlightProps>;
34
+ /**
35
+ * Snippet Component
36
+ *
37
+ * Renders a truncated, highlighted excerpt from search results
38
+ */
39
+ export interface SnippetTheme extends HighlightTheme {
40
+ ellipsis?: string;
41
+ }
42
+ export interface SnippetProps extends Omit<HighlightProps, 'theme'> {
43
+ /** Maximum length of snippet (default: 100) */
44
+ maxLength?: number;
45
+ /** Ellipsis to use when truncating (default: '...') */
46
+ ellipsis?: string;
47
+ /** Custom theme */
48
+ theme?: SnippetTheme;
49
+ }
50
+ export declare const Snippet: React.FC<SnippetProps>;
51
+ //# sourceMappingURL=Highlight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Highlight.d.ts","sourceRoot":"","sources":["../../src/components/Highlight.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,EAIL,aAAa,EACd,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;IACtC,0CAA0C;IAC1C,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5E,8CAA8C;IAC9C,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAC/E,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA8E9C,CAAC;AAEF;;;;GAIG;AAEH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;IACjE,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CA2G1C,CAAC"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Highlight Component
3
+ *
4
+ * Renders highlighted search result text with matching terms emphasized
5
+ */
6
+ import React, { useMemo } from 'react';
7
+ import { useSearchContext } from './SearchProvider';
8
+ import { getHighlightedValue, parseHighlightedParts, parseQueryHighlightParts, } from '@seekora-ai/ui-sdk-core';
9
+ export const Highlight = ({ hit, attribute, className, style, theme: customTheme, tagName: Tag = 'span', renderHighlighted, renderNonHighlighted, query, }) => {
10
+ const { theme } = useSearchContext();
11
+ const highlightTheme = customTheme || {};
12
+ // Get highlighted value from hit
13
+ const highlightedValue = useMemo(() => {
14
+ return getHighlightedValue({
15
+ hit,
16
+ attribute,
17
+ preTag: '<mark>',
18
+ postTag: '</mark>',
19
+ });
20
+ }, [hit, attribute]);
21
+ // Parse into parts
22
+ const parts = useMemo(() => {
23
+ // First try server-provided highlights
24
+ const serverParts = parseHighlightedParts(highlightedValue, '<mark>', '</mark>');
25
+ // If no highlights found and we have a query, do client-side highlighting
26
+ if (serverParts.every((p) => !p.isHighlighted) && query) {
27
+ const plainValue = hit[attribute] || highlightedValue;
28
+ return parseQueryHighlightParts(String(plainValue), query);
29
+ }
30
+ return serverParts;
31
+ }, [highlightedValue, query, hit, attribute]);
32
+ // Default highlight style
33
+ const defaultHighlightStyle = {
34
+ backgroundColor: theme.colors.warning || '#fff59d',
35
+ fontWeight: theme.typography.fontWeight?.semibold || 600,
36
+ padding: '0 2px',
37
+ borderRadius: '2px',
38
+ };
39
+ // Render parts
40
+ const renderedParts = parts.map((part, index) => {
41
+ if (part.isHighlighted) {
42
+ if (renderHighlighted) {
43
+ return React.createElement(React.Fragment, { key: index }, renderHighlighted(part, index));
44
+ }
45
+ return (React.createElement("mark", { key: index, className: highlightTheme.highlighted, style: defaultHighlightStyle }, part.value));
46
+ }
47
+ if (renderNonHighlighted) {
48
+ return React.createElement(React.Fragment, { key: index }, renderNonHighlighted(part, index));
49
+ }
50
+ return (React.createElement("span", { key: index, className: highlightTheme.nonHighlighted }, part.value));
51
+ });
52
+ return (React.createElement(Tag, { className: className || highlightTheme.root, style: style }, renderedParts));
53
+ };
54
+ export const Snippet = ({ hit, attribute, maxLength = 100, ellipsis = '...', className, style, theme: customTheme, tagName: Tag = 'span', renderHighlighted, renderNonHighlighted, query, }) => {
55
+ const { theme } = useSearchContext();
56
+ const snippetTheme = customTheme || {};
57
+ // Get snippet or truncated highlighted value
58
+ const snippetValue = useMemo(() => {
59
+ // Check for server-provided snippet
60
+ const snippetResult = hit._snippetResult?.[attribute]?.value ||
61
+ hit.snippet_result?.[attribute]?.value ||
62
+ hit._snippet?.[attribute] ||
63
+ hit.snippet?.[attribute];
64
+ if (snippetResult) {
65
+ return snippetResult
66
+ .replace(/<em>/g, '<mark>')
67
+ .replace(/<\/em>/g, '</mark>');
68
+ }
69
+ // Fall back to highlighted value, truncated
70
+ let text = getHighlightedValue({
71
+ hit,
72
+ attribute,
73
+ preTag: '<mark>',
74
+ postTag: '</mark>',
75
+ });
76
+ // Remove tags for length calculation
77
+ const plainText = text.replace(/<\/?mark>/g, '');
78
+ if (plainText.length > maxLength) {
79
+ // Need to truncate while preserving tags
80
+ const truncated = truncateWithTags(text, maxLength);
81
+ return truncated + ellipsis;
82
+ }
83
+ return text;
84
+ }, [hit, attribute, maxLength, ellipsis]);
85
+ // Parse into parts
86
+ const parts = useMemo(() => {
87
+ const serverParts = parseHighlightedParts(snippetValue, '<mark>', '</mark>');
88
+ if (serverParts.every((p) => !p.isHighlighted) && query) {
89
+ const plainValue = hit[attribute] || snippetValue;
90
+ let text = String(plainValue);
91
+ if (text.length > maxLength) {
92
+ text = text.substring(0, maxLength) + ellipsis;
93
+ }
94
+ return parseQueryHighlightParts(text, query);
95
+ }
96
+ return serverParts;
97
+ }, [snippetValue, query, hit, attribute, maxLength, ellipsis]);
98
+ // Default highlight style
99
+ const defaultHighlightStyle = {
100
+ backgroundColor: theme.colors.warning || '#fff59d',
101
+ fontWeight: theme.typography.fontWeight?.semibold || 600,
102
+ padding: '0 2px',
103
+ borderRadius: '2px',
104
+ };
105
+ // Render parts
106
+ const renderedParts = parts.map((part, index) => {
107
+ if (part.isHighlighted) {
108
+ if (renderHighlighted) {
109
+ return React.createElement(React.Fragment, { key: index }, renderHighlighted(part, index));
110
+ }
111
+ return (React.createElement("mark", { key: index, className: snippetTheme.highlighted, style: defaultHighlightStyle }, part.value));
112
+ }
113
+ if (renderNonHighlighted) {
114
+ return React.createElement(React.Fragment, { key: index }, renderNonHighlighted(part, index));
115
+ }
116
+ return (React.createElement("span", { key: index, className: snippetTheme.nonHighlighted }, part.value));
117
+ });
118
+ return (React.createElement(Tag, { className: className || snippetTheme.root, style: style }, renderedParts));
119
+ };
120
+ /**
121
+ * Truncate text while preserving HTML tags
122
+ */
123
+ function truncateWithTags(text, maxLength) {
124
+ let visibleLength = 0;
125
+ let result = '';
126
+ let inTag = false;
127
+ for (let i = 0; i < text.length; i++) {
128
+ const char = text[i];
129
+ if (char === '<') {
130
+ inTag = true;
131
+ result += char;
132
+ }
133
+ else if (char === '>') {
134
+ inTag = false;
135
+ result += char;
136
+ }
137
+ else if (inTag) {
138
+ result += char;
139
+ }
140
+ else {
141
+ if (visibleLength >= maxLength) {
142
+ break;
143
+ }
144
+ result += char;
145
+ visibleLength++;
146
+ }
147
+ }
148
+ // Close any unclosed tags
149
+ const openTags = result.match(/<mark>/g)?.length || 0;
150
+ const closeTags = result.match(/<\/mark>/g)?.length || 0;
151
+ for (let i = 0; i < openTags - closeTags; i++) {
152
+ result += '</mark>';
153
+ }
154
+ return result;
155
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * HitsPerPage Component
3
+ *
4
+ * A dropdown to let users change the number of displayed hits per page
5
+ * Integrates with SearchStateManager for automatic state sync
6
+ */
7
+ import React from 'react';
8
+ export interface HitsPerPageItem {
9
+ value: number;
10
+ label: string;
11
+ default?: boolean;
12
+ }
13
+ export interface HitsPerPageTheme {
14
+ root?: string;
15
+ select?: string;
16
+ option?: string;
17
+ }
18
+ export interface HitsPerPageProps {
19
+ /** Available items per page options */
20
+ items: HitsPerPageItem[];
21
+ /** Callback when hits per page changes */
22
+ onHitsPerPageChange?: (value: number) => void;
23
+ /** Custom render function for select */
24
+ renderSelect?: (props: {
25
+ value: number;
26
+ onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
27
+ items: HitsPerPageItem[];
28
+ }) => React.ReactNode;
29
+ /** Custom className */
30
+ className?: string;
31
+ /** Custom styles */
32
+ style?: React.CSSProperties;
33
+ /** Custom theme */
34
+ theme?: HitsPerPageTheme;
35
+ /** Label text before the select */
36
+ label?: string;
37
+ /** Whether to sync with SearchStateManager (default: true) */
38
+ syncWithState?: boolean;
39
+ }
40
+ export declare const HitsPerPage: React.FC<HitsPerPageProps>;
41
+ //# sourceMappingURL=HitsPerPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HitsPerPage.d.ts","sourceRoot":"","sources":["../../src/components/HitsPerPage.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAoB,MAAM,OAAO,CAAC;AAKzC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,0CAA0C;IAC1C,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,wCAAwC;IACxC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;QAC5D,KAAK,EAAE,eAAe,EAAE,CAAC;KAC1B,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,mBAAmB;IACnB,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA8GlD,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * HitsPerPage Component
3
+ *
4
+ * A dropdown to let users change the number of displayed hits per page
5
+ * Integrates with SearchStateManager for automatic state sync
6
+ */
7
+ import React, { useEffect } from 'react';
8
+ import { useSearchContext } from './SearchProvider';
9
+ import { useSearchState } from '../hooks/useSearchState';
10
+ import { clsx } from 'clsx';
11
+ export const HitsPerPage = ({ items, onHitsPerPageChange, renderSelect, className, style, theme: customTheme, label = 'Show', syncWithState = true, }) => {
12
+ const { theme } = useSearchContext();
13
+ const { itemsPerPage, setPage } = useSearchState();
14
+ const { stateManager } = useSearchContext();
15
+ const hitsPerPageTheme = customTheme || {};
16
+ // Find the default item or first item
17
+ const defaultItem = items.find(i => i.default) || items[0];
18
+ const [internalValue, setInternalValue] = React.useState(defaultItem?.value || 10);
19
+ // Initialize StateManager on mount if default is set
20
+ useEffect(() => {
21
+ if (syncWithState && defaultItem && itemsPerPage !== defaultItem.value) {
22
+ stateManager.setItemsPerPage(defaultItem.value, false);
23
+ }
24
+ }, []);
25
+ // Determine the current value from StateManager or internal
26
+ const value = syncWithState ? itemsPerPage : internalValue;
27
+ const handleChange = (e) => {
28
+ const newValue = parseInt(e.target.value, 10);
29
+ setInternalValue(newValue);
30
+ // Update StateManager
31
+ if (syncWithState) {
32
+ stateManager.setItemsPerPage(newValue, false);
33
+ // Reset to page 1 when changing items per page
34
+ setPage(1, true);
35
+ }
36
+ // Call callback
37
+ if (onHitsPerPageChange) {
38
+ onHitsPerPageChange(newValue);
39
+ }
40
+ };
41
+ // Custom render function
42
+ if (renderSelect) {
43
+ return (React.createElement("div", { className: clsx(hitsPerPageTheme.root, className), style: style }, renderSelect({
44
+ value,
45
+ onChange: handleChange,
46
+ items,
47
+ })));
48
+ }
49
+ return (React.createElement("div", { className: clsx(hitsPerPageTheme.root, className), style: {
50
+ display: 'flex',
51
+ alignItems: 'center',
52
+ gap: theme.spacing.small,
53
+ ...style,
54
+ } },
55
+ label && (React.createElement("label", { style: {
56
+ fontSize: theme.typography.fontSize.medium,
57
+ color: theme.colors.text,
58
+ } }, label)),
59
+ React.createElement("select", { value: value, onChange: handleChange, className: hitsPerPageTheme.select, style: {
60
+ padding: theme.spacing.small,
61
+ paddingRight: theme.spacing.medium,
62
+ fontSize: theme.typography.fontSize.medium,
63
+ border: `1px solid ${theme.colors.border}`,
64
+ borderRadius: typeof theme.borderRadius === 'string'
65
+ ? theme.borderRadius
66
+ : theme.borderRadius.medium,
67
+ backgroundColor: theme.colors.background,
68
+ color: theme.colors.text,
69
+ cursor: 'pointer',
70
+ outline: 'none',
71
+ }, "aria-label": "Results per page" }, items.map((item) => (React.createElement("option", { key: item.value, value: item.value, className: hitsPerPageTheme.option }, item.label))))));
72
+ };
@@ -0,0 +1,56 @@
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 from 'react';
8
+ import type { ResultItem, FieldMapping } from '@seekora-ai/ui-sdk-types';
9
+ export interface InfiniteHitsTheme {
10
+ root?: string;
11
+ list?: string;
12
+ item?: string;
13
+ loadMore?: string;
14
+ loadMoreDisabled?: string;
15
+ loading?: string;
16
+ empty?: string;
17
+ sentinel?: string;
18
+ }
19
+ export interface InfiniteHitsProps {
20
+ /** Custom render function for each hit */
21
+ renderHit?: (hit: ResultItem, index: number) => React.ReactNode;
22
+ /** Custom render for empty state */
23
+ renderEmpty?: () => React.ReactNode;
24
+ /** Custom render for loading state */
25
+ renderLoading?: () => React.ReactNode;
26
+ /** Custom render for "Show More" button */
27
+ renderShowMore?: (props: {
28
+ isLoading: boolean;
29
+ isLastPage: boolean;
30
+ onClick: () => void;
31
+ }) => React.ReactNode;
32
+ /** Whether to show the "Show More" button (default: true) */
33
+ showMoreButton?: boolean;
34
+ /** Whether to use infinite scroll with IntersectionObserver (default: false) */
35
+ useInfiniteScroll?: boolean;
36
+ /** Threshold for IntersectionObserver (default: 0.1) */
37
+ scrollThreshold?: number;
38
+ /** Field mapping for extracting data from hits */
39
+ fieldMapping?: FieldMapping;
40
+ /** Text for "Show More" button */
41
+ showMoreLabel?: string;
42
+ /** Text for loading state */
43
+ loadingLabel?: string;
44
+ /** Callback when a hit is clicked */
45
+ onHitClick?: (hit: ResultItem, index: number) => void;
46
+ /** Custom className */
47
+ className?: string;
48
+ /** Custom styles */
49
+ style?: React.CSSProperties;
50
+ /** Custom theme */
51
+ theme?: InfiniteHitsTheme;
52
+ /** Whether to sync with SearchStateManager (default: true) */
53
+ syncWithState?: boolean;
54
+ }
55
+ export declare const InfiniteHits: React.FC<InfiniteHitsProps>;
56
+ //# sourceMappingURL=InfiniteHits.d.ts.map
@@ -0,0 +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"}