@seekora-ai/ui-sdk-react 0.2.13 → 0.2.15
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.
- package/dist/components/CurrentRefinements.d.ts +22 -2
- package/dist/components/CurrentRefinements.d.ts.map +1 -1
- package/dist/components/CurrentRefinements.js +259 -47
- package/dist/components/FacetDropdown.d.ts +92 -0
- package/dist/components/FacetDropdown.d.ts.map +1 -0
- package/dist/components/FacetDropdown.js +374 -0
- package/dist/components/Facets.d.ts +56 -1
- package/dist/components/Facets.d.ts.map +1 -1
- package/dist/components/Facets.js +602 -41
- package/dist/components/FederatedDropdown.d.ts.map +1 -1
- package/dist/components/FederatedDropdown.js +45 -31
- package/dist/components/HierarchicalMenu.d.ts.map +1 -1
- package/dist/components/HierarchicalMenu.js +112 -4
- package/dist/components/Pagination.d.ts +47 -1
- package/dist/components/Pagination.d.ts.map +1 -1
- package/dist/components/Pagination.js +166 -28
- package/dist/components/QuerySuggestionsDropdown.d.ts.map +1 -1
- package/dist/components/QuerySuggestionsDropdown.js +32 -18
- package/dist/components/RangeInput.d.ts.map +1 -1
- package/dist/components/RangeInput.js +6 -6
- package/dist/components/RangeSlider.d.ts.map +1 -1
- package/dist/components/RangeSlider.js +101 -32
- package/dist/components/RichQuerySuggestions.d.ts +7 -0
- package/dist/components/RichQuerySuggestions.d.ts.map +1 -1
- package/dist/components/RichQuerySuggestions.js +40 -26
- package/dist/components/SearchBar.d.ts +16 -0
- package/dist/components/SearchBar.d.ts.map +1 -1
- package/dist/components/SearchBar.js +139 -17
- package/dist/components/SearchBarWithSuggestions.js +3 -3
- package/dist/components/SearchLayout.d.ts.map +1 -1
- package/dist/components/SearchLayout.js +10 -1
- package/dist/components/SearchProvider.d.ts +8 -1
- package/dist/components/SearchProvider.d.ts.map +1 -1
- package/dist/components/SearchProvider.js +16 -4
- package/dist/components/SearchResults.d.ts +10 -0
- package/dist/components/SearchResults.d.ts.map +1 -1
- package/dist/components/SearchResults.js +46 -30
- package/dist/components/SortBy.d.ts +44 -4
- package/dist/components/SortBy.d.ts.map +1 -1
- package/dist/components/SortBy.js +154 -29
- package/dist/components/Stats.d.ts +14 -0
- package/dist/components/Stats.d.ts.map +1 -1
- package/dist/components/Stats.js +172 -23
- package/dist/components/primitives/ActionButtons.d.ts.map +1 -1
- package/dist/components/primitives/ActionButtons.js +34 -10
- package/dist/components/primitives/BadgeList.d.ts.map +1 -1
- package/dist/components/primitives/BadgeList.js +33 -13
- package/dist/components/primitives/ImageDisplay.d.ts.map +1 -1
- package/dist/components/primitives/ImageDisplay.js +11 -8
- package/dist/components/primitives/ImageZoom.js +26 -26
- package/dist/components/primitives/VariantSelector.js +10 -10
- package/dist/components/primitives/VariantSwatches.js +3 -3
- package/dist/components/product-page/ProductGallery.d.ts +8 -1
- package/dist/components/product-page/ProductGallery.d.ts.map +1 -1
- package/dist/components/product-page/ProductGallery.js +2 -2
- package/dist/components/section-primitives/SectionSearchProvider.d.ts +3 -1
- package/dist/components/section-primitives/SectionSearchProvider.d.ts.map +1 -1
- package/dist/components/section-primitives/SectionSearchProvider.js +3 -2
- package/dist/components/suggestions/AmazonDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/AmazonDropdown.js +2 -4
- package/dist/components/suggestions/GoogleDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/GoogleDropdown.js +2 -6
- package/dist/components/suggestions/MinimalDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/MinimalDropdown.js +2 -4
- package/dist/components/suggestions/MobileSheetDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/MobileSheetDropdown.js +20 -22
- package/dist/components/suggestions/PinterestDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/PinterestDropdown.js +2 -6
- package/dist/components/suggestions/ShopifyDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/ShopifyDropdown.js +39 -41
- package/dist/components/suggestions/SpotlightDropdown.d.ts.map +1 -1
- package/dist/components/suggestions/SpotlightDropdown.js +2 -4
- package/dist/components/suggestions/utils.d.ts +10 -1
- package/dist/components/suggestions/utils.d.ts.map +1 -1
- package/dist/components/suggestions/utils.js +36 -0
- package/dist/components/suggestions-primitives/DropdownPanel.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/DropdownPanel.js +15 -2
- package/dist/components/suggestions-primitives/ItemCard.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ItemCard.js +21 -8
- package/dist/components/suggestions-primitives/ItemGrid.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ItemGrid.js +9 -3
- package/dist/components/suggestions-primitives/ProductCard.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ProductCard.js +25 -10
- package/dist/components/suggestions-primitives/ProductCardLayouts.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/ProductCardLayouts.js +24 -12
- package/dist/components/suggestions-primitives/SearchInput.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/SearchInput.js +28 -9
- package/dist/components/suggestions-primitives/SuggestionItem.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/SuggestionItem.js +3 -0
- package/dist/components/suggestions-primitives/highlightMarkup.d.ts +16 -4
- package/dist/components/suggestions-primitives/highlightMarkup.d.ts.map +1 -1
- package/dist/components/suggestions-primitives/highlightMarkup.js +42 -4
- package/dist/hooks/useClickTracking.d.ts +36 -0
- package/dist/hooks/useClickTracking.d.ts.map +1 -0
- package/dist/hooks/useClickTracking.js +96 -0
- package/dist/hooks/useExperiment.d.ts +25 -0
- package/dist/hooks/useExperiment.d.ts.map +1 -0
- package/dist/hooks/useExperiment.js +146 -0
- package/dist/hooks/useKeyboardNavigation.d.ts +51 -0
- package/dist/hooks/useKeyboardNavigation.d.ts.map +1 -0
- package/dist/hooks/useKeyboardNavigation.js +113 -0
- package/dist/hooks/useQuerySuggestions.d.ts.map +1 -1
- package/dist/hooks/useQuerySuggestions.js +19 -3
- package/dist/hooks/useQuerySuggestionsEnhanced.d.ts.map +1 -1
- package/dist/hooks/useQuerySuggestionsEnhanced.js +23 -6
- package/dist/hooks/useSuggestionsAnalytics.d.ts.map +1 -1
- package/dist/hooks/useSuggestionsAnalytics.js +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.umd.js +1 -1
- package/dist/src/index.d.ts +345 -19
- package/dist/src/index.esm.js +2869 -787
- package/dist/src/index.esm.js.map +1 -1
- package/dist/src/index.js +2868 -785
- package/dist/src/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -29,8 +29,8 @@ const styles = {
|
|
|
29
29
|
transition: 'border-color 150ms ease, box-shadow 150ms ease',
|
|
30
30
|
},
|
|
31
31
|
inputWrapperFocused: {
|
|
32
|
-
borderColor: 'var(--seekora-primary, #3b82f6)',
|
|
33
|
-
boxShadow: '0 0 0 3px var(--seekora-primary-light, rgba(59, 130, 246, 0.1))',
|
|
32
|
+
borderColor: 'var(--seekora-border-focus, var(--seekora-primary, #3b82f6))',
|
|
33
|
+
boxShadow: '0 0 0 3px var(--seekora-border-focus-alpha, var(--seekora-primary-light, rgba(59, 130, 246, 0.1)))',
|
|
34
34
|
},
|
|
35
35
|
input: {
|
|
36
36
|
flex: 1,
|
|
@@ -73,7 +73,7 @@ const styles = {
|
|
|
73
73
|
margin: '4px',
|
|
74
74
|
border: 'none',
|
|
75
75
|
backgroundColor: 'var(--seekora-primary, #3b82f6)',
|
|
76
|
-
color: 'white',
|
|
76
|
+
color: 'var(--seekora-primary-text, white)',
|
|
77
77
|
borderRadius: 'var(--seekora-border-radius, 6px)',
|
|
78
78
|
fontSize: '14px',
|
|
79
79
|
fontWeight: 600,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchLayout.d.ts","sourceRoot":"","sources":["../../src/components/SearchLayout.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,qBAAqB;IACrB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,qBAAqB;IACrB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,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,iBAAiB,CAAC;IAC1B,6BAA6B;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"SearchLayout.d.ts","sourceRoot":"","sources":["../../src/components/SearchLayout.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,qBAAqB;IACrB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,qBAAqB;IACrB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,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,iBAAiB,CAAC;IAC1B,6BAA6B;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA2GpD,CAAC"}
|
|
@@ -22,6 +22,8 @@ export const SearchLayout = ({ sidebar, children, header, footer, sidebarWidth =
|
|
|
22
22
|
display: 'flex',
|
|
23
23
|
flexDirection: 'column',
|
|
24
24
|
minHeight: '100vh',
|
|
25
|
+
width: '100%',
|
|
26
|
+
maxWidth: '100%',
|
|
25
27
|
backgroundColor: theme.colors.background,
|
|
26
28
|
...style,
|
|
27
29
|
} },
|
|
@@ -33,20 +35,27 @@ export const SearchLayout = ({ sidebar, children, header, footer, sidebarWidth =
|
|
|
33
35
|
React.createElement("div", { style: {
|
|
34
36
|
display: 'flex',
|
|
35
37
|
flex: 1,
|
|
38
|
+
width: '100%',
|
|
39
|
+
maxWidth: '100%',
|
|
36
40
|
gap: theme.spacing.large,
|
|
37
41
|
padding: theme.spacing.medium,
|
|
38
42
|
backgroundColor: theme.colors.background,
|
|
39
43
|
color: theme.colors.text,
|
|
44
|
+
overflow: 'hidden',
|
|
40
45
|
} },
|
|
41
46
|
sidebar && (!isMobile || showSidebarOnMobile) && (React.createElement("aside", { className: layoutTheme.sidebar, style: {
|
|
42
47
|
width: sidebarWidth,
|
|
43
48
|
minWidth: sidebarWidth,
|
|
49
|
+
flexShrink: 0,
|
|
44
50
|
} }, sidebar)),
|
|
45
51
|
React.createElement("main", { className: layoutTheme.main, style: {
|
|
46
52
|
flex: 1,
|
|
47
|
-
minWidth: 0,
|
|
53
|
+
minWidth: 0,
|
|
54
|
+
width: '100%',
|
|
55
|
+
maxWidth: '100%',
|
|
48
56
|
backgroundColor: theme.colors.background,
|
|
49
57
|
color: theme.colors.text,
|
|
58
|
+
overflow: 'auto',
|
|
50
59
|
} }, children)),
|
|
51
60
|
footer && (React.createElement("footer", { className: layoutTheme.footer, style: {
|
|
52
61
|
padding: theme.spacing.medium,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SearchProvider Component
|
|
3
3
|
*
|
|
4
|
-
* Provides Seekora client and context to child components
|
|
4
|
+
* Provides Seekora client and context to child components.
|
|
5
|
+
* Supports A/B testing via abTestId/abVariant props.
|
|
5
6
|
*/
|
|
6
7
|
import React, { ReactNode } from 'react';
|
|
7
8
|
import type { SeekoraClient } from '@seekora-ai/search-sdk';
|
|
@@ -21,6 +22,12 @@ export interface SearchProviderProps {
|
|
|
21
22
|
autoTrackSearch?: boolean;
|
|
22
23
|
stateManager?: SearchStateManagerConfig;
|
|
23
24
|
children: ReactNode;
|
|
25
|
+
/** A/B test experiment ID to include in all analytics events */
|
|
26
|
+
abTestId?: string;
|
|
27
|
+
/** A/B test variant to include in all analytics events */
|
|
28
|
+
abVariant?: string;
|
|
29
|
+
/** Auto-fetch experiment assignments on mount (default: false) */
|
|
30
|
+
experiments?: boolean;
|
|
24
31
|
}
|
|
25
32
|
export declare const SearchProvider: React.FC<SearchProviderProps>;
|
|
26
33
|
export declare const useSearchContext: () => SearchContextValue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchProvider.d.ts","sourceRoot":"","sources":["../../src/components/SearchProvider.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"SearchProvider.d.ts","sourceRoot":"","sources":["../../src/components/SearchProvider.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,EAA6B,SAAS,EAAsB,MAAM,OAAO,CAAC;AACxF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAO,kBAAkB,EAAE,KAAK,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACjG,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAI1D,UAAU,kBAAkB;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,EAAE,kBAAkB,CAAC;CAClC;AAID,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,wBAAwB,CAAC;IACxC,QAAQ,EAAE,SAAS,CAAC;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAoDxD,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAO,kBAQnC,CAAC"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SearchProvider Component
|
|
3
3
|
*
|
|
4
|
-
* Provides Seekora client and context to child components
|
|
4
|
+
* Provides Seekora client and context to child components.
|
|
5
|
+
* Supports A/B testing via abTestId/abVariant props.
|
|
5
6
|
*/
|
|
6
|
-
import React, { createContext, useContext, useMemo } from 'react';
|
|
7
|
+
import React, { createContext, useContext, useMemo, useEffect } from 'react';
|
|
7
8
|
import { log, SearchStateManager } from '@seekora-ai/ui-sdk-core';
|
|
8
9
|
import { defaultTheme } from '../themes/default';
|
|
9
10
|
import { createTheme } from '../themes/createTheme';
|
|
10
11
|
const SearchContext = createContext(null);
|
|
11
|
-
export const SearchProvider = ({ client, theme: themeConfig, enableAnalytics = true, autoTrackSearch = true, stateManager: stateManagerConfig, children, }) => {
|
|
12
|
+
export const SearchProvider = ({ client, theme: themeConfig, enableAnalytics = true, autoTrackSearch = true, stateManager: stateManagerConfig, children, abTestId, abVariant, experiments: _experiments, }) => {
|
|
12
13
|
const theme = useMemo(() => {
|
|
13
14
|
return themeConfig ? createTheme(themeConfig) : defaultTheme;
|
|
14
15
|
}, [themeConfig]);
|
|
@@ -21,8 +22,19 @@ export const SearchProvider = ({ client, theme: themeConfig, enableAnalytics = t
|
|
|
21
22
|
itemsPerPage: 10,
|
|
22
23
|
defaultSearchOptions: { widget_mode: true },
|
|
23
24
|
...stateManagerConfig,
|
|
25
|
+
abTestId,
|
|
26
|
+
abVariant,
|
|
24
27
|
});
|
|
25
|
-
}, [client, stateManagerConfig]);
|
|
28
|
+
}, [client, stateManagerConfig, abTestId, abVariant]);
|
|
29
|
+
// Update A/B test fields on state manager and SDK client when props change
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (abTestId !== undefined || abVariant !== undefined) {
|
|
32
|
+
stateManager.setAbTest(abTestId, abVariant);
|
|
33
|
+
if (typeof client.setAbTest === 'function') {
|
|
34
|
+
client.setAbTest(abTestId, abVariant);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}, [stateManager, client, abTestId, abVariant]);
|
|
26
38
|
const value = useMemo(() => ({
|
|
27
39
|
client,
|
|
28
40
|
theme,
|
|
@@ -21,6 +21,10 @@ export interface SearchResultsTheme {
|
|
|
21
21
|
loadingState?: string;
|
|
22
22
|
errorState?: string;
|
|
23
23
|
pagination?: string;
|
|
24
|
+
/** Custom min-height class override */
|
|
25
|
+
minHeight?: string;
|
|
26
|
+
/** Custom min-width class override */
|
|
27
|
+
minWidth?: string;
|
|
24
28
|
}
|
|
25
29
|
export interface SearchResultsProps {
|
|
26
30
|
results?: SearchResponse | null;
|
|
@@ -48,6 +52,12 @@ export interface SearchResultsProps {
|
|
|
48
52
|
enableKeyboardNavigation?: boolean;
|
|
49
53
|
/** Auto-focus the results container */
|
|
50
54
|
autoFocus?: boolean;
|
|
55
|
+
/** Minimum height to prevent container collapse when empty (default: '400px') */
|
|
56
|
+
minHeight?: string;
|
|
57
|
+
/** Minimum width to prevent container shrinking (default: '100%') */
|
|
58
|
+
minWidth?: string;
|
|
59
|
+
/** Opacity applied to results while loading new results (default: 0.7, set to 1 to disable) */
|
|
60
|
+
loadingOpacity?: number;
|
|
51
61
|
}
|
|
52
62
|
export declare const SearchResults: React.FC<SearchResultsProps>;
|
|
53
63
|
//# sourceMappingURL=SearchResults.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchResults.d.ts","sourceRoot":"","sources":["../../src/components/SearchResults.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAKxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEnF,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"SearchResults.d.ts","sourceRoot":"","sources":["../../src/components/SearchResults.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAKxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEnF,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1F,WAAW,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACpC,uHAAuH;IACvH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACtC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,mEAAmE;IACnE,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,gFAAgF;IAChF,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,GAAG,EAAE,CAAC;IAC1C,+DAA+D;IAC/D,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,uCAAuC;IACvC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAoBD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAknBtD,CAAC"}
|
|
@@ -29,7 +29,7 @@ const formatPrice = (value, currency = '₹') => {
|
|
|
29
29
|
}
|
|
30
30
|
return String(value);
|
|
31
31
|
};
|
|
32
|
-
export const SearchResults = ({ results: resultsProp, loading: loadingProp, error: errorProp, onResultClick, renderResult, renderEmpty, showLoadingState = false, renderLoading, renderError, className, style, theme: customTheme, itemsPerPage = 20, showPagination = false, viewMode = 'list', fieldMapping, extractResults, enableKeyboardNavigation = true, autoFocus = false, }) => {
|
|
32
|
+
export const SearchResults = ({ results: resultsProp, loading: loadingProp, error: errorProp, onResultClick, renderResult, renderEmpty, showLoadingState = false, renderLoading, renderError, className, style, theme: customTheme, itemsPerPage = 20, showPagination = false, viewMode = 'list', fieldMapping, extractResults, enableKeyboardNavigation = true, autoFocus = false, minHeight = '400px', minWidth = '100%', loadingOpacity = 0.7, }) => {
|
|
33
33
|
const { theme, client, enableAnalytics } = useSearchContext();
|
|
34
34
|
const { results: stateResults, loading: stateLoading, error: stateError, currentPage, itemsPerPage: stateItemsPerPage } = useSearchState();
|
|
35
35
|
const searchResultsTheme = customTheme || {};
|
|
@@ -53,6 +53,43 @@ export const SearchResults = ({ results: resultsProp, loading: loadingProp, erro
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}, [activeIndex, enableKeyboardNavigation]);
|
|
56
|
+
// Global keyboard listener for seamless navigation from search box to results
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!enableKeyboardNavigation)
|
|
59
|
+
return;
|
|
60
|
+
const handleGlobalKeyDown = (e) => {
|
|
61
|
+
// Only handle if we have results and the container exists
|
|
62
|
+
if (!containerRef.current)
|
|
63
|
+
return;
|
|
64
|
+
const resultElements = containerRef.current.querySelectorAll('[data-result-index]');
|
|
65
|
+
const maxIndex = resultElements.length - 1;
|
|
66
|
+
if (maxIndex < 0)
|
|
67
|
+
return;
|
|
68
|
+
// Check if focus is on an input/textarea (search box)
|
|
69
|
+
const activeElement = document.activeElement;
|
|
70
|
+
const isInputFocused = activeElement?.tagName === 'INPUT' || activeElement?.tagName === 'TEXTAREA';
|
|
71
|
+
if (e.key === 'ArrowDown') {
|
|
72
|
+
// If in search box or no result selected, select first result
|
|
73
|
+
if (isInputFocused || activeIndex === -1) {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
setActiveIndex(0);
|
|
76
|
+
containerRef.current?.focus();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (e.key === 'ArrowUp') {
|
|
80
|
+
// If first result is selected, go back to search box
|
|
81
|
+
if (activeIndex === 0) {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
setActiveIndex(-1);
|
|
84
|
+
// Find and focus the search input
|
|
85
|
+
const searchInput = document.querySelector('input[type="text"], input[type="search"]');
|
|
86
|
+
searchInput?.focus();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
document.addEventListener('keydown', handleGlobalKeyDown);
|
|
91
|
+
return () => document.removeEventListener('keydown', handleGlobalKeyDown);
|
|
92
|
+
}, [enableKeyboardNavigation, activeIndex]);
|
|
56
93
|
// Keyboard navigation handler
|
|
57
94
|
const handleKeyDown = useCallback((e) => {
|
|
58
95
|
if (!enableKeyboardNavigation)
|
|
@@ -373,6 +410,10 @@ export const SearchResults = ({ results: resultsProp, loading: loadingProp, erro
|
|
|
373
410
|
});
|
|
374
411
|
// Determine container style based on view mode
|
|
375
412
|
const containerStyle = {
|
|
413
|
+
minHeight: `var(--seekora-results-min-height, ${minHeight})`,
|
|
414
|
+
minWidth: `var(--seekora-results-min-width, ${minWidth})`,
|
|
415
|
+
transition: 'opacity 200ms ease-in-out',
|
|
416
|
+
opacity: loading && resultItems.length > 0 ? loadingOpacity : 1,
|
|
376
417
|
...style,
|
|
377
418
|
};
|
|
378
419
|
// Determine results list style based on view mode
|
|
@@ -399,19 +440,19 @@ export const SearchResults = ({ results: resultsProp, loading: loadingProp, erro
|
|
|
399
440
|
// When loading with no previous results, show loading only if showLoadingState (default: show previous results, no loading screen)
|
|
400
441
|
if (loading && resultItems.length === 0 && showLoadingState) {
|
|
401
442
|
log.verbose('SearchResults: Rendering loading state');
|
|
402
|
-
return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style:
|
|
443
|
+
return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style: containerStyle }, renderLoading ? renderLoading() : defaultRenderLoading()));
|
|
403
444
|
}
|
|
404
|
-
// When loading with previous results, fall through and render them (
|
|
445
|
+
// When loading with previous results, fall through and render them (with opacity transition)
|
|
405
446
|
if (error) {
|
|
406
447
|
log.error('SearchResults: Rendering error state', {
|
|
407
448
|
error: error.message,
|
|
408
449
|
stack: error.stack,
|
|
409
450
|
});
|
|
410
|
-
return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style:
|
|
451
|
+
return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style: containerStyle }, renderError ? renderError(error) : defaultRenderError(error)));
|
|
411
452
|
}
|
|
412
453
|
if (!results || resultItems.length === 0) {
|
|
413
454
|
log.verbose('SearchResults: No results to display');
|
|
414
|
-
return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style:
|
|
455
|
+
return (React.createElement("div", { className: clsx(searchResultsTheme.container, className), style: containerStyle }, renderEmpty ? renderEmpty() : defaultRenderEmpty()));
|
|
415
456
|
}
|
|
416
457
|
const renderFn = renderResult || defaultRenderResult;
|
|
417
458
|
return (React.createElement("div", { ref: containerRef, className: clsx(searchResultsTheme.container, className), style: containerStyle, tabIndex: enableKeyboardNavigation ? 0 : undefined, onKeyDown: handleKeyDown, role: "listbox", "aria-label": "Search results", "aria-activedescendant": activeIndex >= 0 ? `result-${activeIndex}` : undefined },
|
|
@@ -424,30 +465,5 @@ export const SearchResults = ({ results: resultsProp, loading: loadingProp, erro
|
|
|
424
465
|
results?.totalResults || results?.data?.total_results || 0,
|
|
425
466
|
" result",
|
|
426
467
|
(results?.totalResults || results?.data?.total_results || 0) !== 1 ? 's' : '')),
|
|
427
|
-
enableKeyboardNavigation && resultItems.length > 0 && (React.createElement("div", { style: {
|
|
428
|
-
fontSize: '12px',
|
|
429
|
-
color: theme.colors.text,
|
|
430
|
-
opacity: 0.6,
|
|
431
|
-
marginBottom: theme.spacing.small,
|
|
432
|
-
display: 'flex',
|
|
433
|
-
gap: '8px',
|
|
434
|
-
alignItems: 'center',
|
|
435
|
-
} },
|
|
436
|
-
React.createElement("span", { style: {
|
|
437
|
-
display: 'inline-flex',
|
|
438
|
-
gap: '4px',
|
|
439
|
-
padding: '2px 6px',
|
|
440
|
-
backgroundColor: theme.colors.hover,
|
|
441
|
-
borderRadius: '4px',
|
|
442
|
-
fontSize: '11px',
|
|
443
|
-
} }, "\u2191\u2193 navigate"),
|
|
444
|
-
React.createElement("span", { style: {
|
|
445
|
-
display: 'inline-flex',
|
|
446
|
-
gap: '4px',
|
|
447
|
-
padding: '2px 6px',
|
|
448
|
-
backgroundColor: theme.colors.hover,
|
|
449
|
-
borderRadius: '4px',
|
|
450
|
-
fontSize: '11px',
|
|
451
|
-
} }, "\u21B5 select"))),
|
|
452
468
|
React.createElement("div", { className: searchResultsTheme.resultsList, style: resultsListStyle }, resultItems.map((result, index) => renderFn(result, index, index === activeIndex)))));
|
|
453
469
|
};
|
|
@@ -1,18 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SortBy Component
|
|
3
3
|
*
|
|
4
|
-
* Displays sort options for search results
|
|
5
|
-
*
|
|
4
|
+
* Displays sort options for search results in multiple display variants:
|
|
5
|
+
* - dropdown (default) — native select element
|
|
6
|
+
* - button-group — horizontal row of toggle buttons
|
|
7
|
+
* - radio-group — vertical list of radio inputs
|
|
8
|
+
*
|
|
9
|
+
* Integrates with SearchStateManager for automatic state sync.
|
|
10
|
+
*
|
|
11
|
+
* CSS Variables (apply on a parent element to customize):
|
|
12
|
+
* --seekora-sort-bg — background color
|
|
13
|
+
* --seekora-sort-color — text color
|
|
14
|
+
* --seekora-sort-border — border color
|
|
15
|
+
* --seekora-sort-active-bg — active item background
|
|
16
|
+
* --seekora-sort-active-color — active item text color
|
|
6
17
|
*/
|
|
7
18
|
import React from 'react';
|
|
8
19
|
export interface SortOption {
|
|
9
20
|
value: string;
|
|
10
21
|
label: string;
|
|
11
22
|
}
|
|
23
|
+
export type SortByVariant = 'dropdown' | 'button-group' | 'radio-group';
|
|
24
|
+
export type SortBySize = 'small' | 'medium' | 'large';
|
|
12
25
|
export interface SortByTheme {
|
|
26
|
+
/** Root container */
|
|
13
27
|
container?: string;
|
|
28
|
+
/** Dropdown select element */
|
|
14
29
|
select?: string;
|
|
30
|
+
/** Dropdown option element */
|
|
15
31
|
option?: string;
|
|
32
|
+
/** Optional label above the control */
|
|
33
|
+
label?: string;
|
|
34
|
+
/** Button group wrapper */
|
|
35
|
+
buttonGroup?: string;
|
|
36
|
+
/** Individual button in the group */
|
|
37
|
+
buttonGroupItem?: string;
|
|
38
|
+
/** Active button in the group */
|
|
39
|
+
buttonGroupItemActive?: string;
|
|
40
|
+
/** Radio group wrapper */
|
|
41
|
+
radioGroup?: string;
|
|
42
|
+
/** Individual radio row (input + label) */
|
|
43
|
+
radioItem?: string;
|
|
44
|
+
/** Active radio row */
|
|
45
|
+
radioItemActive?: string;
|
|
46
|
+
/** Radio label text */
|
|
47
|
+
radioLabel?: string;
|
|
16
48
|
}
|
|
17
49
|
export interface SortByProps {
|
|
18
50
|
/** Available sort options */
|
|
@@ -23,7 +55,7 @@ export interface SortByProps {
|
|
|
23
55
|
defaultValue?: string;
|
|
24
56
|
/** Callback when sort changes */
|
|
25
57
|
onSortChange?: (value: string) => void;
|
|
26
|
-
/** Custom render function for select */
|
|
58
|
+
/** Custom render function for select (only used in dropdown variant) */
|
|
27
59
|
renderSelect?: (props: {
|
|
28
60
|
value: string;
|
|
29
61
|
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
|
@@ -35,10 +67,18 @@ export interface SortByProps {
|
|
|
35
67
|
style?: React.CSSProperties;
|
|
36
68
|
/** Custom theme */
|
|
37
69
|
theme?: SortByTheme;
|
|
38
|
-
/** Placeholder text */
|
|
70
|
+
/** Placeholder text (dropdown variant) */
|
|
39
71
|
placeholder?: string;
|
|
40
72
|
/** Whether to sync with SearchStateManager (default: true) */
|
|
41
73
|
syncWithState?: boolean;
|
|
74
|
+
/** Display variant (default: 'dropdown') */
|
|
75
|
+
variant?: SortByVariant;
|
|
76
|
+
/** Optional label text displayed above the control */
|
|
77
|
+
label?: string;
|
|
78
|
+
/** Whether to show the label (default: true when label is provided) */
|
|
79
|
+
showLabel?: boolean;
|
|
80
|
+
/** Size variant affecting padding and font size (default: 'medium') */
|
|
81
|
+
size?: SortBySize;
|
|
42
82
|
}
|
|
43
83
|
export declare const SortBy: React.FC<SortByProps>;
|
|
44
84
|
//# sourceMappingURL=SortBy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SortBy.d.ts","sourceRoot":"","sources":["../../src/components/SortBy.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"SortBy.d.ts","sourceRoot":"","sources":["../../src/components/SortBy.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAwC,MAAM,OAAO,CAAC;AAS7D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,CAAC;AACxE,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,WAAW;IAC1B,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iCAAiC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,wEAAwE;IACxE,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,OAAO,EAAE,UAAU,EAAE,CAAC;KACvB,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,WAAW,CAAC;IACpB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AA8BD,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,WAAW,CAmSxC,CAAC"}
|
|
@@ -1,61 +1,186 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SortBy Component
|
|
3
3
|
*
|
|
4
|
-
* Displays sort options for search results
|
|
5
|
-
*
|
|
4
|
+
* Displays sort options for search results in multiple display variants:
|
|
5
|
+
* - dropdown (default) — native select element
|
|
6
|
+
* - button-group — horizontal row of toggle buttons
|
|
7
|
+
* - radio-group — vertical list of radio inputs
|
|
8
|
+
*
|
|
9
|
+
* Integrates with SearchStateManager for automatic state sync.
|
|
10
|
+
*
|
|
11
|
+
* CSS Variables (apply on a parent element to customize):
|
|
12
|
+
* --seekora-sort-bg — background color
|
|
13
|
+
* --seekora-sort-color — text color
|
|
14
|
+
* --seekora-sort-border — border color
|
|
15
|
+
* --seekora-sort-active-bg — active item background
|
|
16
|
+
* --seekora-sort-active-color — active item text color
|
|
6
17
|
*/
|
|
7
|
-
import React, { useEffect } from 'react';
|
|
18
|
+
import React, { useCallback, useEffect, useId } from 'react';
|
|
8
19
|
import { useSearchContext } from './SearchProvider';
|
|
9
20
|
import { useSearchState } from '../hooks/useSearchState';
|
|
10
21
|
import { clsx } from 'clsx';
|
|
11
|
-
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/** Map a size token to theme fontSize and spacing values. */
|
|
26
|
+
function sizeStyles(size, theme) {
|
|
27
|
+
switch (size) {
|
|
28
|
+
case 'small':
|
|
29
|
+
return { fontSize: theme.typography.fontSize.small, padding: theme.spacing.small };
|
|
30
|
+
case 'large':
|
|
31
|
+
return { fontSize: theme.typography.fontSize.large, padding: theme.spacing.medium };
|
|
32
|
+
case 'medium':
|
|
33
|
+
default:
|
|
34
|
+
return { fontSize: theme.typography.fontSize.medium, padding: theme.spacing.small };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function resolveBorderRadius(br) {
|
|
38
|
+
return typeof br === 'string' ? br : br.medium;
|
|
39
|
+
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Component
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
export const SortBy = ({ options, value: valueProp, defaultValue, onSortChange, renderSelect, className, style, theme: customTheme, placeholder = 'Sort by...', syncWithState = true, variant = 'dropdown', label, showLabel, size = 'medium', }) => {
|
|
12
44
|
const { theme } = useSearchContext();
|
|
13
45
|
const { sortBy: stateManagerSortBy, setSortBy } = useSearchState();
|
|
14
46
|
const sortByTheme = customTheme || {};
|
|
15
|
-
|
|
47
|
+
const instanceId = useId();
|
|
48
|
+
// Determine whether the label should render.
|
|
49
|
+
const shouldShowLabel = showLabel !== undefined ? showLabel : !!label;
|
|
50
|
+
// ------ State ----------------------------------------------------------
|
|
16
51
|
const [internalValue, setInternalValue] = React.useState(defaultValue || options[0]?.value || '');
|
|
17
52
|
// Sync with StateManager on mount if defaultValue is set
|
|
18
53
|
useEffect(() => {
|
|
19
54
|
if (syncWithState && defaultValue && !stateManagerSortBy) {
|
|
20
55
|
setSortBy(defaultValue, false); // Don't trigger search on initial sync
|
|
21
56
|
}
|
|
22
|
-
}, []);
|
|
57
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
23
58
|
// Determine the current value: controlled prop > StateManager > internal
|
|
24
59
|
const value = valueProp !== undefined
|
|
25
60
|
? valueProp
|
|
26
|
-
:
|
|
61
|
+
: syncWithState && stateManagerSortBy
|
|
27
62
|
? stateManagerSortBy
|
|
28
63
|
: internalValue;
|
|
64
|
+
// ------ Handlers -------------------------------------------------------
|
|
29
65
|
const handleChange = (e) => {
|
|
30
|
-
|
|
66
|
+
applyValue(e.target.value);
|
|
67
|
+
};
|
|
68
|
+
const applyValue = useCallback((newValue) => {
|
|
31
69
|
setInternalValue(newValue);
|
|
32
|
-
// Update StateManager (automatically triggers search)
|
|
33
70
|
if (syncWithState) {
|
|
34
71
|
setSortBy(newValue);
|
|
35
72
|
}
|
|
36
|
-
// Call callback for backwards compatibility
|
|
37
73
|
if (onSortChange) {
|
|
38
74
|
onSortChange(newValue);
|
|
39
75
|
}
|
|
76
|
+
}, [syncWithState, setSortBy, onSortChange]);
|
|
77
|
+
// ------ Derived styles -------------------------------------------------
|
|
78
|
+
const { fontSize, padding } = sizeStyles(size, theme);
|
|
79
|
+
const borderRadius = resolveBorderRadius(theme.borderRadius);
|
|
80
|
+
const cssVarStyle = {
|
|
81
|
+
'--seekora-sort-bg': theme.colors.background,
|
|
82
|
+
'--seekora-sort-color': theme.colors.text,
|
|
83
|
+
'--seekora-sort-border': theme.colors.border,
|
|
84
|
+
'--seekora-sort-active-bg': theme.colors.primary,
|
|
85
|
+
'--seekora-sort-active-color': theme.colors.background,
|
|
40
86
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
87
|
+
// ------ Label ----------------------------------------------------------
|
|
88
|
+
const labelElement = shouldShowLabel && label ? (React.createElement("span", { className: clsx(sortByTheme.label), style: {
|
|
89
|
+
display: 'block',
|
|
90
|
+
marginBottom: theme.spacing.small,
|
|
91
|
+
fontSize,
|
|
92
|
+
color: 'var(--seekora-sort-color)',
|
|
93
|
+
fontWeight: 500,
|
|
94
|
+
} }, label)) : null;
|
|
95
|
+
// ------ Dropdown variant (original) ------------------------------------
|
|
96
|
+
if (variant === 'dropdown') {
|
|
97
|
+
if (renderSelect) {
|
|
98
|
+
return (React.createElement("div", { className: clsx(sortByTheme.container, className), style: { ...cssVarStyle, ...style } },
|
|
99
|
+
labelElement,
|
|
100
|
+
renderSelect({
|
|
101
|
+
value,
|
|
102
|
+
onChange: handleChange,
|
|
103
|
+
options,
|
|
104
|
+
})));
|
|
105
|
+
}
|
|
106
|
+
return (React.createElement("div", { className: clsx(sortByTheme.container, className), style: { ...cssVarStyle, ...style } },
|
|
107
|
+
labelElement,
|
|
108
|
+
React.createElement("select", { value: value, onChange: handleChange, className: clsx(sortByTheme.select), style: {
|
|
109
|
+
padding,
|
|
110
|
+
paddingRight: theme.spacing.medium,
|
|
111
|
+
fontSize,
|
|
112
|
+
border: '1px solid var(--seekora-sort-border)',
|
|
113
|
+
borderRadius,
|
|
114
|
+
backgroundColor: 'var(--seekora-sort-bg)',
|
|
115
|
+
color: 'var(--seekora-sort-color)',
|
|
116
|
+
cursor: 'pointer',
|
|
117
|
+
outline: 'none',
|
|
118
|
+
width: '100%',
|
|
119
|
+
}, "aria-label": label || 'Sort results' }, options.map((option) => (React.createElement("option", { key: option.value, value: option.value, className: sortByTheme.option }, option.label))))));
|
|
120
|
+
}
|
|
121
|
+
// ------ Button group variant -------------------------------------------
|
|
122
|
+
if (variant === 'button-group') {
|
|
123
|
+
return (React.createElement("div", { className: clsx(sortByTheme.container, className), style: { ...cssVarStyle, ...style } },
|
|
124
|
+
labelElement,
|
|
125
|
+
React.createElement("div", { role: "group", "aria-label": label || 'Sort results', className: clsx(sortByTheme.buttonGroup), style: {
|
|
126
|
+
display: 'inline-flex',
|
|
127
|
+
borderRadius,
|
|
128
|
+
overflow: 'hidden',
|
|
129
|
+
border: '1px solid var(--seekora-sort-border)',
|
|
130
|
+
} }, options.map((option, index) => {
|
|
131
|
+
const isActive = option.value === value;
|
|
132
|
+
return (React.createElement("button", { key: option.value, type: "button", role: "button", "aria-pressed": isActive, onClick: () => applyValue(option.value), className: clsx(sortByTheme.buttonGroupItem, isActive && sortByTheme.buttonGroupItemActive), style: {
|
|
133
|
+
padding,
|
|
134
|
+
fontSize,
|
|
135
|
+
border: 'none',
|
|
136
|
+
borderRight: index < options.length - 1
|
|
137
|
+
? '1px solid var(--seekora-sort-border)'
|
|
138
|
+
: 'none',
|
|
139
|
+
backgroundColor: isActive
|
|
140
|
+
? 'var(--seekora-sort-active-bg)'
|
|
141
|
+
: 'var(--seekora-sort-bg)',
|
|
142
|
+
color: isActive
|
|
143
|
+
? 'var(--seekora-sort-active-color)'
|
|
144
|
+
: 'var(--seekora-sort-color)',
|
|
145
|
+
cursor: 'pointer',
|
|
146
|
+
fontWeight: isActive ? 600 : 400,
|
|
147
|
+
transition: 'background-color 0.15s ease, color 0.15s ease',
|
|
148
|
+
outline: 'none',
|
|
149
|
+
} }, option.label));
|
|
150
|
+
}))));
|
|
151
|
+
}
|
|
152
|
+
// ------ Radio group variant --------------------------------------------
|
|
153
|
+
if (variant === 'radio-group') {
|
|
154
|
+
const radioName = `seekora-sort-${instanceId}`;
|
|
155
|
+
return (React.createElement("div", { className: clsx(sortByTheme.container, className), style: { ...cssVarStyle, ...style } },
|
|
156
|
+
labelElement,
|
|
157
|
+
React.createElement("div", { role: "radiogroup", "aria-label": label || 'Sort results', className: clsx(sortByTheme.radioGroup), style: {
|
|
158
|
+
display: 'flex',
|
|
159
|
+
flexDirection: 'column',
|
|
160
|
+
gap: theme.spacing.small,
|
|
161
|
+
} }, options.map((option) => {
|
|
162
|
+
const isActive = option.value === value;
|
|
163
|
+
const radioId = `${radioName}-${option.value}`;
|
|
164
|
+
return (React.createElement("label", { key: option.value, htmlFor: radioId, className: clsx(sortByTheme.radioItem, isActive && sortByTheme.radioItemActive), style: {
|
|
165
|
+
display: 'flex',
|
|
166
|
+
alignItems: 'center',
|
|
167
|
+
gap: theme.spacing.small,
|
|
168
|
+
padding,
|
|
169
|
+
borderRadius,
|
|
170
|
+
cursor: 'pointer',
|
|
171
|
+
backgroundColor: isActive
|
|
172
|
+
? 'var(--seekora-sort-active-bg)'
|
|
173
|
+
: 'transparent',
|
|
174
|
+
color: isActive
|
|
175
|
+
? 'var(--seekora-sort-active-color)'
|
|
176
|
+
: 'var(--seekora-sort-color)',
|
|
177
|
+
fontWeight: isActive ? 600 : 400,
|
|
178
|
+
transition: 'background-color 0.15s ease, color 0.15s ease',
|
|
179
|
+
} },
|
|
180
|
+
React.createElement("input", { type: "radio", id: radioId, name: radioName, value: option.value, checked: isActive, onChange: () => applyValue(option.value), style: { margin: 0 } }),
|
|
181
|
+
React.createElement("span", { className: clsx(sortByTheme.radioLabel), style: { fontSize } }, option.label)));
|
|
182
|
+
}))));
|
|
59
183
|
}
|
|
60
|
-
|
|
184
|
+
// Fallback — should never reach here, but satisfies TS exhaustiveness
|
|
185
|
+
return null;
|
|
61
186
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Stats Component
|
|
3
3
|
*
|
|
4
4
|
* Displays search statistics (total results, processing time, etc.)
|
|
5
|
+
* Supports inline, badge, and detailed display variants.
|
|
5
6
|
*/
|
|
6
7
|
import React from 'react';
|
|
7
8
|
import type { SearchResponse } from '@seekora-ai/search-sdk';
|
|
@@ -10,6 +11,13 @@ export interface StatsTheme {
|
|
|
10
11
|
text?: string;
|
|
11
12
|
highlight?: string;
|
|
12
13
|
separator?: string;
|
|
14
|
+
badge?: string;
|
|
15
|
+
badgeLabel?: string;
|
|
16
|
+
badgeValue?: string;
|
|
17
|
+
detailed?: string;
|
|
18
|
+
detailedRow?: string;
|
|
19
|
+
detailedLabel?: string;
|
|
20
|
+
detailedValue?: string;
|
|
13
21
|
}
|
|
14
22
|
export interface StatsProps {
|
|
15
23
|
/** Search results response */
|
|
@@ -32,6 +40,12 @@ export interface StatsProps {
|
|
|
32
40
|
showQuery?: boolean;
|
|
33
41
|
/** Custom separator between stats */
|
|
34
42
|
separator?: string;
|
|
43
|
+
/** Display variant */
|
|
44
|
+
variant?: 'inline' | 'badge' | 'detailed';
|
|
45
|
+
/** Show result count (default: true) */
|
|
46
|
+
showResultCount?: boolean;
|
|
47
|
+
/** Custom number formatter (default: toLocaleString) */
|
|
48
|
+
formatNumber?: (n: number) => string;
|
|
35
49
|
}
|
|
36
50
|
export declare const Stats: React.FC<StatsProps>;
|
|
37
51
|
//# sourceMappingURL=Stats.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stats.d.ts","sourceRoot":"","sources":["../../src/components/Stats.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Stats.d.ts","sourceRoot":"","sources":["../../src/components/Stats.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAsC,MAAM,OAAO,CAAC;AAI3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,6BAA6B;IAC7B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,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,UAAU,CAAC;IACnB,2BAA2B;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iBAAiB;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;IAC1C,wCAAwC;IACxC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wDAAwD;IACxD,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC;AA8BD,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,UAAU,CAsTtC,CAAC"}
|