@seekora-ai/ui-sdk-react 0.2.11 → 0.2.13

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 (120) hide show
  1. package/dist/components/InfiniteHits.d.ts +2 -0
  2. package/dist/components/InfiniteHits.d.ts.map +1 -1
  3. package/dist/components/InfiniteHits.js +6 -3
  4. package/dist/components/QuerySuggestions.d.ts +2 -0
  5. package/dist/components/QuerySuggestions.d.ts.map +1 -1
  6. package/dist/components/QuerySuggestions.js +4 -3
  7. package/dist/components/QuerySuggestionsDropdown.d.ts +1 -1
  8. package/dist/components/QuerySuggestionsDropdown.d.ts.map +1 -1
  9. package/dist/components/QuerySuggestionsDropdown.js +4 -4
  10. package/dist/components/Recommendations.d.ts +6 -0
  11. package/dist/components/Recommendations.d.ts.map +1 -1
  12. package/dist/components/Recommendations.js +12 -6
  13. package/dist/components/RichQuerySuggestions.d.ts +4 -0
  14. package/dist/components/RichQuerySuggestions.d.ts.map +1 -1
  15. package/dist/components/RichQuerySuggestions.js +2 -3
  16. package/dist/components/SearchBar.d.ts +2 -0
  17. package/dist/components/SearchBar.d.ts.map +1 -1
  18. package/dist/components/SearchBar.js +5 -9
  19. package/dist/components/SearchResults.d.ts +2 -0
  20. package/dist/components/SearchResults.d.ts.map +1 -1
  21. package/dist/components/SearchResults.js +4 -2
  22. package/dist/components/primitives/ActionButtons.d.ts +27 -0
  23. package/dist/components/primitives/ActionButtons.d.ts.map +1 -0
  24. package/dist/components/primitives/ActionButtons.js +78 -0
  25. package/dist/components/primitives/AnalyticsProvider.d.ts +22 -0
  26. package/dist/components/primitives/AnalyticsProvider.d.ts.map +1 -0
  27. package/dist/components/primitives/AnalyticsProvider.js +87 -0
  28. package/dist/components/primitives/BadgeList.d.ts +14 -0
  29. package/dist/components/primitives/BadgeList.d.ts.map +1 -0
  30. package/dist/components/primitives/BadgeList.js +45 -0
  31. package/dist/components/primitives/ImageDisplay.d.ts +10 -1
  32. package/dist/components/primitives/ImageDisplay.d.ts.map +1 -1
  33. package/dist/components/primitives/ImageDisplay.js +49 -9
  34. package/dist/components/primitives/ImageZoom.d.ts +33 -0
  35. package/dist/components/primitives/ImageZoom.d.ts.map +1 -0
  36. package/dist/components/primitives/ImageZoom.js +357 -0
  37. package/dist/components/primitives/PriceDisplay.d.ts +21 -0
  38. package/dist/components/primitives/PriceDisplay.d.ts.map +1 -0
  39. package/dist/components/primitives/PriceDisplay.js +44 -0
  40. package/dist/components/primitives/RatingDisplay.d.ts +43 -0
  41. package/dist/components/primitives/RatingDisplay.d.ts.map +1 -0
  42. package/dist/components/primitives/RatingDisplay.js +114 -0
  43. package/dist/components/primitives/VariantSelector.d.ts +30 -0
  44. package/dist/components/primitives/VariantSelector.d.ts.map +1 -0
  45. package/dist/components/primitives/VariantSelector.js +162 -0
  46. package/dist/components/primitives/VariantSwatches.d.ts +28 -0
  47. package/dist/components/primitives/VariantSwatches.d.ts.map +1 -0
  48. package/dist/components/primitives/VariantSwatches.js +173 -0
  49. package/dist/components/primitives/index.d.ts +9 -0
  50. package/dist/components/primitives/index.d.ts.map +1 -1
  51. package/dist/components/primitives/index.js +9 -0
  52. package/dist/components/primitives/withAnalytics.d.ts +24 -0
  53. package/dist/components/primitives/withAnalytics.d.ts.map +1 -0
  54. package/dist/components/primitives/withAnalytics.js +73 -0
  55. package/dist/components/product-page/ProductInfo.d.ts +25 -2
  56. package/dist/components/product-page/ProductInfo.d.ts.map +1 -1
  57. package/dist/components/product-page/ProductInfo.js +20 -5
  58. package/dist/components/section-primitives/SectionItemGrid.d.ts +3 -1
  59. package/dist/components/section-primitives/SectionItemGrid.d.ts.map +1 -1
  60. package/dist/components/section-primitives/SectionItemGrid.js +3 -2
  61. package/dist/components/suggestions/AmazonDropdown.d.ts.map +1 -1
  62. package/dist/components/suggestions/AmazonDropdown.js +2 -2
  63. package/dist/components/suggestions/GoogleDropdown.d.ts.map +1 -1
  64. package/dist/components/suggestions/GoogleDropdown.js +2 -2
  65. package/dist/components/suggestions/MinimalDropdown.d.ts.map +1 -1
  66. package/dist/components/suggestions/MinimalDropdown.js +2 -2
  67. package/dist/components/suggestions/MobileSheetDropdown.d.ts.map +1 -1
  68. package/dist/components/suggestions/MobileSheetDropdown.js +2 -2
  69. package/dist/components/suggestions/PinterestDropdown.d.ts.map +1 -1
  70. package/dist/components/suggestions/PinterestDropdown.js +2 -2
  71. package/dist/components/suggestions/ShopifyDropdown.d.ts.map +1 -1
  72. package/dist/components/suggestions/ShopifyDropdown.js +2 -2
  73. package/dist/components/suggestions/SpotlightDropdown.d.ts.map +1 -1
  74. package/dist/components/suggestions/SpotlightDropdown.js +2 -2
  75. package/dist/components/suggestions/SuggestionSearchBar.d.ts.map +1 -1
  76. package/dist/components/suggestions/SuggestionSearchBar.js +1 -0
  77. package/dist/components/suggestions/types.d.ts +26 -0
  78. package/dist/components/suggestions/types.d.ts.map +1 -1
  79. package/dist/components/suggestions/utils.d.ts +37 -0
  80. package/dist/components/suggestions/utils.d.ts.map +1 -1
  81. package/dist/components/suggestions/utils.js +118 -0
  82. package/dist/components/suggestions-primitives/ItemCard.d.ts +10 -1
  83. package/dist/components/suggestions-primitives/ItemCard.d.ts.map +1 -1
  84. package/dist/components/suggestions-primitives/ItemCard.js +20 -6
  85. package/dist/components/suggestions-primitives/ProductCard.d.ts +27 -3
  86. package/dist/components/suggestions-primitives/ProductCard.d.ts.map +1 -1
  87. package/dist/components/suggestions-primitives/ProductCard.js +124 -17
  88. package/dist/components/suggestions-primitives/ProductCardLayouts.d.ts +44 -0
  89. package/dist/components/suggestions-primitives/ProductCardLayouts.d.ts.map +1 -0
  90. package/dist/components/suggestions-primitives/ProductCardLayouts.js +105 -0
  91. package/dist/components/suggestions-primitives/ProductGrid.d.ts +6 -1
  92. package/dist/components/suggestions-primitives/ProductGrid.d.ts.map +1 -1
  93. package/dist/components/suggestions-primitives/ProductGrid.js +2 -2
  94. package/dist/components/suggestions-primitives/SuggestionList.d.ts +8 -1
  95. package/dist/components/suggestions-primitives/SuggestionList.d.ts.map +1 -1
  96. package/dist/components/suggestions-primitives/SuggestionList.js +7 -4
  97. package/dist/components/suggestions-primitives/SuggestionsDropdownComposition.d.ts.map +1 -1
  98. package/dist/components/suggestions-primitives/SuggestionsDropdownComposition.js +0 -2
  99. package/dist/docsearch/components/Results.d.ts +3 -1
  100. package/dist/docsearch/components/Results.d.ts.map +1 -1
  101. package/dist/docsearch/components/Results.js +6 -2
  102. package/dist/hooks/useProductAnalytics.d.ts +49 -0
  103. package/dist/hooks/useProductAnalytics.d.ts.map +1 -0
  104. package/dist/hooks/useProductAnalytics.js +116 -0
  105. package/dist/hooks/useQuerySuggestionsEnhanced.js +2 -1
  106. package/dist/hooks/useSuggestionsAnalytics.d.ts.map +1 -1
  107. package/dist/hooks/useSuggestionsAnalytics.js +6 -0
  108. package/dist/hooks/useVariantSelection.d.ts +28 -0
  109. package/dist/hooks/useVariantSelection.d.ts.map +1 -0
  110. package/dist/hooks/useVariantSelection.js +44 -0
  111. package/dist/index.d.ts +8 -3
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +5 -1
  114. package/dist/index.umd.js +1 -1
  115. package/dist/src/index.d.ts +1138 -681
  116. package/dist/src/index.esm.js +2407 -723
  117. package/dist/src/index.esm.js.map +1 -1
  118. package/dist/src/index.js +2423 -722
  119. package/dist/src/index.js.map +1 -1
  120. package/package.json +3 -3
@@ -0,0 +1,162 @@
1
+ /**
2
+ * VariantSelector – full variant selector for product detail pages
3
+ *
4
+ * Three render modes per option:
5
+ * - swatch: color circles (auto-detected for "Color" option or when colorMap provided)
6
+ * - button: rectangular buttons (default for most options)
7
+ * - dropdown: <select> for options with many values
8
+ */
9
+ import React from 'react';
10
+ import { clsx } from 'clsx';
11
+ const COLOR_NAMES = {
12
+ black: '#000', white: '#fff', red: '#ef4444', blue: '#3b82f6',
13
+ green: '#22c55e', yellow: '#eab308', orange: '#f97316', purple: '#a855f7',
14
+ pink: '#ec4899', brown: '#92400e', grey: '#6b7280', gray: '#6b7280',
15
+ navy: '#1e3a5f', beige: '#d4c5a9', cream: '#fffdd0', ivory: '#fffff0',
16
+ teal: '#0d9488', coral: '#ff7f50', maroon: '#800000', charcoal: '#36454f',
17
+ sage: '#9caf88', lavender: '#e6e6fa', mint: '#98fb98', rust: '#b7410e',
18
+ plum: '#8e4585', slate: '#708090', indigo: '#4b0082', gold: '#ffd700',
19
+ silver: '#c0c0c0', rose: '#ff007f', raven: '#0a0a0a', natural: '#f5f0e1',
20
+ bone: '#e3dac9', sand: '#c2b280', olive: '#808000', khaki: '#c3b091',
21
+ burgundy: '#800020', wine: '#722f37', mauve: '#e0b0ff', tan: '#d2b48c',
22
+ };
23
+ const isColorOption = (name) => {
24
+ const lower = name.toLowerCase();
25
+ return lower === 'color' || lower === 'colour' || lower === 'colors' || lower === 'colours';
26
+ };
27
+ const resolveColor = (value, colorMap) => {
28
+ if (colorMap?.[value])
29
+ return colorMap[value];
30
+ const lower = value.toLowerCase();
31
+ if (colorMap?.[lower])
32
+ return colorMap[lower];
33
+ return COLOR_NAMES[lower] ?? null;
34
+ };
35
+ const getAvailability = (optionName, value, options, variants, selections) => {
36
+ const optionIndex = options.findIndex((o) => o.name === optionName);
37
+ if (optionIndex === -1)
38
+ return true;
39
+ const optionKey = `option${optionIndex + 1}`;
40
+ return variants.some((variant) => {
41
+ if (variant[optionKey] !== value)
42
+ return false;
43
+ if (variant.available === false)
44
+ return false;
45
+ for (const [selName, selValue] of Object.entries(selections)) {
46
+ if (selName === optionName)
47
+ continue;
48
+ const selIdx = options.findIndex((o) => o.name === selName);
49
+ if (selIdx === -1)
50
+ continue;
51
+ const selKey = `option${selIdx + 1}`;
52
+ if (variant[selKey] !== selValue)
53
+ return false;
54
+ }
55
+ return true;
56
+ });
57
+ };
58
+ export function VariantSelector({ options, variants, selections, onSelectionChange, optionRenderModes, dropdownThreshold = 8, colorMap, showAvailability = true, selectedVariant: _selectedVariant, className, style, }) {
59
+ if (!options || options.length === 0)
60
+ return null;
61
+ const getRenderMode = (option) => {
62
+ if (optionRenderModes?.[option.name])
63
+ return optionRenderModes[option.name];
64
+ if (isColorOption(option.name))
65
+ return 'swatch';
66
+ if (option.values.length > dropdownThreshold)
67
+ return 'dropdown';
68
+ return 'button';
69
+ };
70
+ return (React.createElement("div", { className: clsx('seekora-variant-selector', className), style: { display: 'flex', flexDirection: 'column', gap: 16, ...style } }, options.map((option) => {
71
+ const mode = getRenderMode(option);
72
+ const selected = selections[option.name];
73
+ return (React.createElement("div", { key: option.name, className: "seekora-variant-option-group" },
74
+ React.createElement("label", { className: "seekora-variant-option-label", style: {
75
+ display: 'block',
76
+ fontSize: '0.875rem',
77
+ fontWeight: 600,
78
+ marginBottom: 8,
79
+ color: 'var(--seekora-text-primary, #111827)',
80
+ } },
81
+ option.name,
82
+ selected && (React.createElement("span", { style: { fontWeight: 400, marginLeft: 6, color: 'var(--seekora-text-secondary, #6b7280)' } }, selected))),
83
+ mode === 'dropdown' ? (React.createElement("select", { className: "seekora-variant-dropdown", value: selected ?? '', onChange: (e) => {
84
+ e.stopPropagation();
85
+ onSelectionChange(option.name, e.target.value);
86
+ }, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), style: {
87
+ padding: '8px 12px',
88
+ fontSize: '0.875rem',
89
+ borderRadius: 6,
90
+ border: '1px solid var(--seekora-border-color, #e5e7eb)',
91
+ backgroundColor: 'var(--seekora-bg-surface, #fff)',
92
+ color: 'var(--seekora-text-primary, #111827)',
93
+ cursor: 'pointer',
94
+ minWidth: 120,
95
+ } },
96
+ React.createElement("option", { value: "" },
97
+ "Select ",
98
+ option.name),
99
+ option.values.map((value) => {
100
+ const available = showAvailability
101
+ ? getAvailability(option.name, value, options, variants, selections)
102
+ : true;
103
+ return (React.createElement("option", { key: value, value: value, disabled: !available },
104
+ value,
105
+ !available ? ' (Unavailable)' : ''));
106
+ }))) : (React.createElement("div", { className: "seekora-variant-buttons", style: { display: 'flex', flexWrap: 'wrap', gap: 8 } }, option.values.map((value) => {
107
+ const isActive = selected === value;
108
+ const available = showAvailability
109
+ ? getAvailability(option.name, value, options, variants, selections)
110
+ : true;
111
+ const color = mode === 'swatch' ? resolveColor(value, colorMap) : null;
112
+ if (mode === 'swatch' && color) {
113
+ return (React.createElement("button", { key: value, type: "button", className: clsx('seekora-variant-color-swatch', isActive && 'seekora-variant-button--active', !available && 'seekora-variant-button--unavailable'), title: value, onMouseDown: (e) => {
114
+ e.stopPropagation();
115
+ e.preventDefault();
116
+ }, onClick: (e) => {
117
+ e.stopPropagation();
118
+ onSelectionChange(option.name, value);
119
+ }, disabled: !available, style: {
120
+ width: 32,
121
+ height: 32,
122
+ borderRadius: '50%',
123
+ backgroundColor: color,
124
+ border: isActive
125
+ ? '2px solid var(--seekora-primary, #111827)'
126
+ : '1px solid var(--seekora-border-color, #e5e7eb)',
127
+ outline: isActive ? '2px solid var(--seekora-primary, #111827)' : 'none',
128
+ outlineOffset: 2,
129
+ cursor: available ? 'pointer' : 'not-allowed',
130
+ opacity: available ? 1 : 0.4,
131
+ position: 'relative',
132
+ padding: 0,
133
+ }, "aria-label": `${option.name}: ${value}`, "aria-pressed": isActive }));
134
+ }
135
+ return (React.createElement("button", { key: value, type: "button", className: clsx('seekora-variant-button', isActive && 'seekora-variant-button--active', !available && 'seekora-variant-button--unavailable'), onMouseDown: (e) => {
136
+ e.stopPropagation();
137
+ e.preventDefault();
138
+ }, onClick: (e) => {
139
+ e.stopPropagation();
140
+ onSelectionChange(option.name, value);
141
+ }, disabled: !available, style: {
142
+ padding: '6px 16px',
143
+ fontSize: '0.875rem',
144
+ borderRadius: 6,
145
+ border: isActive
146
+ ? '2px solid var(--seekora-primary, #111827)'
147
+ : '1px solid var(--seekora-border-color, #e5e7eb)',
148
+ backgroundColor: isActive
149
+ ? 'var(--seekora-primary, #111827)'
150
+ : 'var(--seekora-bg-surface, #fff)',
151
+ color: isActive
152
+ ? '#fff'
153
+ : 'var(--seekora-text-primary, #111827)',
154
+ cursor: available ? 'pointer' : 'not-allowed',
155
+ opacity: available ? 1 : 0.5,
156
+ textDecoration: !available ? 'line-through' : 'none',
157
+ fontWeight: isActive ? 600 : 400,
158
+ transition: 'all 120ms ease',
159
+ }, "aria-label": `${option.name}: ${value}`, "aria-pressed": isActive }, value));
160
+ })))));
161
+ })));
162
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * VariantSwatches – inline variant indicators for card display
3
+ *
4
+ * Shows color dots, size labels, and "+N more" overflow for compact card views.
5
+ */
6
+ import React from 'react';
7
+ import type { ProductOption, ProductVariant } from '@seekora-ai/ui-sdk-types';
8
+ export interface VariantSwatchesProps {
9
+ options: ProductOption[];
10
+ /** Which option names to show (default: all) */
11
+ visibleOptions?: string[];
12
+ /** Max values per option before "+N more" (default: 5) */
13
+ maxValues?: number;
14
+ /** Map option value names to CSS color strings */
15
+ colorMap?: Record<string, string>;
16
+ /** Currently selected value per option (e.g., { Color: 'Black', Size: 'M' }) */
17
+ selectedValues?: Record<string, string>;
18
+ /** Product variants for availability checking */
19
+ variants?: ProductVariant[];
20
+ /** Show availability cross-out for unavailable combos (default: true) */
21
+ showAvailability?: boolean;
22
+ onSwatchHover?: (optionName: string, value: string) => void;
23
+ onSwatchClick?: (optionName: string, value: string) => void;
24
+ className?: string;
25
+ style?: React.CSSProperties;
26
+ }
27
+ export declare function VariantSwatches({ options, visibleOptions, maxValues, colorMap, selectedValues, variants, showAvailability, onSwatchHover, onSwatchClick, className, style, }: VariantSwatchesProps): React.JSX.Element | null;
28
+ //# sourceMappingURL=VariantSwatches.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VariantSwatches.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/VariantSwatches.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE9E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,gDAAgD;IAChD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AA4DD,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,cAAc,EACd,SAAa,EACb,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,gBAAuB,EACvB,aAAa,EACb,aAAa,EACb,SAAS,EACT,KAAK,GACN,EAAE,oBAAoB,4BAyKtB"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * VariantSwatches – inline variant indicators for card display
3
+ *
4
+ * Shows color dots, size labels, and "+N more" overflow for compact card views.
5
+ */
6
+ import React, { useState } from 'react';
7
+ import { clsx } from 'clsx';
8
+ const COLOR_NAMES = {
9
+ black: '#000', white: '#fff', red: '#ef4444', blue: '#3b82f6',
10
+ green: '#22c55e', yellow: '#eab308', orange: '#f97316', purple: '#a855f7',
11
+ pink: '#ec4899', brown: '#92400e', grey: '#6b7280', gray: '#6b7280',
12
+ navy: '#1e3a5f', beige: '#d4c5a9', cream: '#fffdd0', ivory: '#fffff0',
13
+ khaki: '#c3b091', olive: '#808000', teal: '#0d9488', coral: '#ff7f50',
14
+ maroon: '#800000', tan: '#d2b48c', charcoal: '#36454f', burgundy: '#800020',
15
+ sage: '#9caf88', lavender: '#e6e6fa', mint: '#98fb98', rust: '#b7410e',
16
+ plum: '#8e4585', slate: '#708090', indigo: '#4b0082', gold: '#ffd700',
17
+ silver: '#c0c0c0', rose: '#ff007f', mauve: '#e0b0ff', wine: '#722f37',
18
+ raven: '#0a0a0a', natural: '#f5f0e1', bone: '#e3dac9', sand: '#c2b280',
19
+ };
20
+ const isColorOption = (name) => {
21
+ const lower = name.toLowerCase();
22
+ return lower === 'color' || lower === 'colour' || lower === 'colors' || lower === 'colours';
23
+ };
24
+ const resolveColor = (value, colorMap) => {
25
+ if (colorMap?.[value])
26
+ return colorMap[value];
27
+ const lower = value.toLowerCase();
28
+ if (colorMap?.[lower])
29
+ return colorMap[lower];
30
+ return COLOR_NAMES[lower] ?? null;
31
+ };
32
+ const getAvailability = (optionName, value, options, variants, selectedValues) => {
33
+ if (!variants || variants.length === 0)
34
+ return true;
35
+ const optionIndex = options.findIndex((o) => o.name === optionName);
36
+ if (optionIndex === -1)
37
+ return true;
38
+ const optionKey = `option${optionIndex + 1}`;
39
+ return variants.some((variant) => {
40
+ // Must match the current option value
41
+ if (variant[optionKey] !== value)
42
+ return false;
43
+ // Must be available
44
+ if (variant.available === false)
45
+ return false;
46
+ // Must match all other selected values
47
+ if (selectedValues) {
48
+ for (const [selName, selValue] of Object.entries(selectedValues)) {
49
+ if (selName === optionName)
50
+ continue; // Skip current option
51
+ const selIdx = options.findIndex((o) => o.name === selName);
52
+ if (selIdx === -1)
53
+ continue;
54
+ const selKey = `option${selIdx + 1}`;
55
+ if (variant[selKey] !== selValue)
56
+ return false;
57
+ }
58
+ }
59
+ return true;
60
+ });
61
+ };
62
+ export function VariantSwatches({ options, visibleOptions, maxValues = 5, colorMap, selectedValues, variants, showAvailability = true, onSwatchHover, onSwatchClick, className, style, }) {
63
+ const [expandedOptions, setExpandedOptions] = useState(new Set());
64
+ if (!options || options.length === 0)
65
+ return null;
66
+ const filtered = visibleOptions
67
+ ? options.filter((o) => visibleOptions.includes(o.name))
68
+ : options;
69
+ if (filtered.length === 0)
70
+ return null;
71
+ const toggleExpanded = (optionName, e) => {
72
+ e.stopPropagation();
73
+ setExpandedOptions((prev) => {
74
+ const next = new Set(prev);
75
+ if (next.has(optionName)) {
76
+ next.delete(optionName);
77
+ }
78
+ else {
79
+ next.add(optionName);
80
+ }
81
+ return next;
82
+ });
83
+ };
84
+ return (React.createElement("div", { className: clsx('seekora-variant-swatches', className), style: { display: 'flex', flexDirection: 'column', gap: 4, ...style } }, filtered.map((option) => {
85
+ const isColor = isColorOption(option.name);
86
+ const isExpanded = expandedOptions.has(option.name);
87
+ const visible = isExpanded ? option.values : option.values.slice(0, maxValues);
88
+ const overflow = option.values.length - maxValues;
89
+ const hasOverflow = overflow > 0;
90
+ const selectedValue = selectedValues?.[option.name];
91
+ return (React.createElement("div", { key: option.name, className: "seekora-variant-swatch-group", style: { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' } },
92
+ visible.map((value) => {
93
+ const color = isColor ? resolveColor(value, colorMap) : null;
94
+ const isSelected = selectedValue === value;
95
+ const isAvailable = showAvailability
96
+ ? getAvailability(option.name, value, options, variants, selectedValues)
97
+ : true;
98
+ if (color) {
99
+ return (React.createElement("span", { key: value, className: clsx('seekora-variant-swatch', 'seekora-variant-swatch--color', isSelected && 'seekora-variant-swatch--selected', !isAvailable && 'seekora-variant-swatch--unavailable'), title: `${value}${!isAvailable ? ' (Unavailable)' : ''}`, style: {
100
+ display: 'inline-block',
101
+ width: 14,
102
+ height: 14,
103
+ borderRadius: '50%',
104
+ backgroundColor: color,
105
+ border: isSelected
106
+ ? '2px solid var(--seekora-primary, #111827)'
107
+ : '1px solid var(--seekora-border-color, #e5e7eb)',
108
+ outline: isSelected ? '2px solid var(--seekora-primary, #111827)' : 'none',
109
+ outlineOffset: 2,
110
+ cursor: onSwatchClick && isAvailable ? 'pointer' : 'not-allowed',
111
+ flexShrink: 0,
112
+ boxShadow: isSelected ? '0 0 0 1px #fff' : 'none',
113
+ opacity: isAvailable ? 1 : 0.3,
114
+ position: 'relative',
115
+ }, onMouseEnter: () => isAvailable && onSwatchHover?.(option.name, value), onMouseDown: (e) => {
116
+ e.stopPropagation();
117
+ e.preventDefault();
118
+ }, onClick: (e) => {
119
+ e.stopPropagation();
120
+ if (isAvailable) {
121
+ onSwatchClick?.(option.name, value);
122
+ }
123
+ } }, !isAvailable && (React.createElement("span", { style: {
124
+ position: 'absolute',
125
+ top: '50%',
126
+ left: '-2px',
127
+ right: '-2px',
128
+ height: 1,
129
+ backgroundColor: '#ef4444',
130
+ transform: 'translateY(-50%) rotate(-45deg)',
131
+ } }))));
132
+ }
133
+ return (React.createElement("span", { key: value, className: clsx('seekora-variant-swatch', 'seekora-variant-swatch--text', isSelected && 'seekora-variant-swatch--selected', !isAvailable && 'seekora-variant-swatch--unavailable'), style: {
134
+ display: 'inline-block',
135
+ padding: '1px 6px',
136
+ fontSize: '0.6875rem',
137
+ borderRadius: 3,
138
+ border: isSelected
139
+ ? '1px solid var(--seekora-primary, #111827)'
140
+ : '1px solid var(--seekora-border-color, #e5e7eb)',
141
+ backgroundColor: isSelected
142
+ ? 'var(--seekora-primary, #111827)'
143
+ : 'transparent',
144
+ color: isSelected
145
+ ? '#fff'
146
+ : 'var(--seekora-text-secondary, #6b7280)',
147
+ cursor: onSwatchClick && isAvailable ? 'pointer' : 'not-allowed',
148
+ whiteSpace: 'nowrap',
149
+ fontWeight: isSelected ? 600 : 400,
150
+ opacity: isAvailable ? 1 : 0.4,
151
+ textDecoration: !isAvailable ? 'line-through' : 'none',
152
+ }, onMouseEnter: () => isAvailable && onSwatchHover?.(option.name, value), onMouseDown: (e) => {
153
+ e.stopPropagation();
154
+ e.preventDefault();
155
+ }, onClick: (e) => {
156
+ e.stopPropagation();
157
+ if (isAvailable) {
158
+ onSwatchClick?.(option.name, value);
159
+ }
160
+ } }, value));
161
+ }),
162
+ hasOverflow && (React.createElement("button", { type: "button", className: "seekora-variant-swatch--overflow", onClick: (e) => toggleExpanded(option.name, e), style: {
163
+ fontSize: '0.6875rem',
164
+ color: 'var(--seekora-primary, #2563eb)',
165
+ background: 'none',
166
+ border: 'none',
167
+ padding: 0,
168
+ cursor: 'pointer',
169
+ textDecoration: 'underline',
170
+ fontWeight: 500,
171
+ } }, isExpanded ? 'Show less' : `+${overflow} more`))));
172
+ })));
173
+ }
@@ -1,2 +1,11 @@
1
1
  export { ImageDisplay, type ImageDisplayVariant, type ImageDisplayProps } from './ImageDisplay';
2
+ export { ImageZoom, type ImageZoomProps, type ImageZoomMode } from './ImageZoom';
3
+ export { PriceDisplay, type PriceDisplayProps } from './PriceDisplay';
4
+ export { BadgeList, type BadgeListProps } from './BadgeList';
5
+ export { RatingDisplay, type RatingDisplayProps, type RatingVariant, type RatingSize } from './RatingDisplay';
6
+ export { VariantSwatches, type VariantSwatchesProps } from './VariantSwatches';
7
+ export { VariantSelector, type VariantSelectorProps } from './VariantSelector';
8
+ export { ActionButtons, type ActionButtonsProps, type ActionButton, type ActionButtonType } from './ActionButtons';
9
+ export { withAnalytics, type WithAnalyticsConfig, type WithAnalyticsInjectedProps } from './withAnalytics';
10
+ export { AnalyticsProvider, useAnalyticsProvider, type AnalyticsProviderProps } from './AnalyticsProvider';
2
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnH,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1 +1,10 @@
1
1
  export { ImageDisplay } from './ImageDisplay';
2
+ export { ImageZoom } from './ImageZoom';
3
+ export { PriceDisplay } from './PriceDisplay';
4
+ export { BadgeList } from './BadgeList';
5
+ export { RatingDisplay } from './RatingDisplay';
6
+ export { VariantSwatches } from './VariantSwatches';
7
+ export { VariantSelector } from './VariantSelector';
8
+ export { ActionButtons } from './ActionButtons';
9
+ export { withAnalytics } from './withAnalytics';
10
+ export { AnalyticsProvider, useAnalyticsProvider } from './AnalyticsProvider';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * withAnalytics – HOC that wraps any React component to inject analytics tracking
3
+ */
4
+ import React from 'react';
5
+ import type { SeekoraClient, SearchContext } from '@seekora-ai/search-sdk';
6
+ import type { ProductItem } from '@seekora-ai/ui-sdk-types';
7
+ export interface WithAnalyticsConfig {
8
+ /** Track clicks on the component */
9
+ trackClick?: boolean;
10
+ /** Track impressions (visibility) */
11
+ trackImpression?: boolean;
12
+ /** Custom event name for click */
13
+ clickEventName?: string;
14
+ /** Extract product/item data from props */
15
+ getProductFromProps?: (props: any) => ProductItem | null;
16
+ /** Extract position from props */
17
+ getPositionFromProps?: (props: any) => number;
18
+ }
19
+ export interface WithAnalyticsInjectedProps {
20
+ seekoraClient?: SeekoraClient;
21
+ seekoraContext?: SearchContext | Partial<SearchContext>;
22
+ }
23
+ export declare function withAnalytics<P extends object>(Component: React.ComponentType<P>, config?: WithAnalyticsConfig): React.ComponentType<P & WithAnalyticsInjectedProps>;
24
+ //# sourceMappingURL=withAnalytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withAnalytics.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/withAnalytics.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAG5D,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qCAAqC;IACrC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kCAAkC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2CAA2C;IAC3C,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,WAAW,GAAG,IAAI,CAAC;IACzD,kCAAkC;IAClC,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAC;CAC/C;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACzD;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAC5C,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,EACjC,MAAM,GAAE,mBAAwB,GAC/B,KAAK,CAAC,aAAa,CAAC,CAAC,GAAG,0BAA0B,CAAC,CAmGrD"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * withAnalytics – HOC that wraps any React component to inject analytics tracking
3
+ */
4
+ import React, { useRef, useEffect, useCallback } from 'react';
5
+ import { log } from '@seekora-ai/ui-sdk-core';
6
+ export function withAnalytics(Component, config = {}) {
7
+ const { trackClick = true, trackImpression = false, clickEventName = 'product.click', getProductFromProps = () => null, getPositionFromProps = () => 0, } = config;
8
+ const displayName = Component.displayName || Component.name || 'Component';
9
+ const Wrapped = React.forwardRef((props, ref) => {
10
+ const { seekoraClient, seekoraContext, ...rest } = props;
11
+ const containerRef = useRef(null);
12
+ const impressionTrackedRef = useRef(false);
13
+ const product = getProductFromProps(rest);
14
+ const position = getPositionFromProps(rest);
15
+ const sendEvent = useCallback(async (eventName, metadata) => {
16
+ if (!seekoraClient)
17
+ return;
18
+ try {
19
+ await seekoraClient.trackEvent?.({
20
+ event_name: eventName,
21
+ metadata: {
22
+ product_id: product?.id || product?.objectID,
23
+ product_title: product?.title || product?.name,
24
+ product_price: product?.price,
25
+ position,
26
+ timestamp: Date.now(),
27
+ source: 'with_analytics_hoc',
28
+ ...metadata,
29
+ },
30
+ }, seekoraContext);
31
+ }
32
+ catch (error) {
33
+ log.warn(`withAnalytics: Failed to track ${eventName}`, { error });
34
+ }
35
+ }, [seekoraClient, seekoraContext, product, position]);
36
+ const handleClick = useCallback(() => {
37
+ if (!trackClick || !product)
38
+ return;
39
+ sendEvent(clickEventName, {});
40
+ if (seekoraClient?.trackClick && product) {
41
+ Promise.resolve(seekoraClient.trackClick(product.id || product.objectID || '', position + 1, seekoraContext)).catch(() => { });
42
+ }
43
+ }, [trackClick, product, sendEvent, seekoraClient, seekoraContext, position]);
44
+ // Impression tracking
45
+ useEffect(() => {
46
+ if (!trackImpression || !seekoraClient || !containerRef.current || impressionTrackedRef.current)
47
+ return;
48
+ if (typeof IntersectionObserver === 'undefined')
49
+ return;
50
+ const observer = new IntersectionObserver((entries) => {
51
+ for (const entry of entries) {
52
+ if (entry.isIntersecting && !impressionTrackedRef.current) {
53
+ impressionTrackedRef.current = true;
54
+ sendEvent('product.impression', {});
55
+ observer.disconnect();
56
+ }
57
+ }
58
+ }, { threshold: 0.5 });
59
+ observer.observe(containerRef.current);
60
+ return () => observer.disconnect();
61
+ }, [trackImpression, seekoraClient, sendEvent]);
62
+ return (React.createElement("div", { ref: (node) => {
63
+ containerRef.current = node;
64
+ if (typeof ref === 'function')
65
+ ref(node);
66
+ else if (ref)
67
+ ref.current = node;
68
+ }, onClick: handleClick, style: { display: 'contents' } },
69
+ React.createElement(Component, { ...rest })));
70
+ });
71
+ Wrapped.displayName = `withAnalytics(${displayName})`;
72
+ return Wrapped;
73
+ }
@@ -3,19 +3,42 @@
3
3
  *
4
4
  * Title, description, price, optional variant selector and CTA. Minimal layout;
5
5
  * override with className/style. For use on individual product page.
6
+ *
7
+ * When options/variants/selections/onSelectionChange are provided and
8
+ * renderVariantSelector is NOT provided, renders the built-in VariantSelector.
9
+ * renderVariantSelector still takes priority as an override.
6
10
  */
7
11
  import React from 'react';
12
+ import type { ProductBadge, ProductOption, ProductVariant } from '@seekora-ai/ui-sdk-types';
8
13
  export interface ProductInfoProps {
9
14
  title: string;
10
15
  description?: string;
11
16
  price?: number | string;
12
17
  currency?: string;
13
- /** Optional variant selector (e.g. size/color) */
18
+ /** Compare/original price for strikethrough display */
19
+ comparePrice?: number | string;
20
+ /** Product brand */
21
+ brand?: string;
22
+ /** Whether product is available */
23
+ available?: boolean;
24
+ /** Badges to display */
25
+ badges?: ProductBadge[];
26
+ /** Product option axes (Color, Size, etc.) */
27
+ options?: ProductOption[];
28
+ /** Product variants */
29
+ variants?: ProductVariant[];
30
+ /** Currently selected variant */
31
+ selectedVariant?: ProductVariant | null;
32
+ /** Current option selections */
33
+ selections?: Record<string, string>;
34
+ /** Called when an option value is selected */
35
+ onSelectionChange?: (optionName: string, value: string) => void;
36
+ /** Optional variant selector override (takes priority over built-in) */
14
37
  renderVariantSelector?: () => React.ReactNode;
15
38
  /** Optional CTA (e.g. Add to cart) */
16
39
  renderCTA?: () => React.ReactNode;
17
40
  className?: string;
18
41
  style?: React.CSSProperties;
19
42
  }
20
- export declare function ProductInfo({ title, description, price, currency, renderVariantSelector, renderCTA, className, style, }: ProductInfoProps): React.JSX.Element;
43
+ export declare function ProductInfo({ title, description, price, currency, comparePrice, brand, available, badges, options, variants, selectedVariant: _selectedVariant, selections, onSelectionChange, renderVariantSelector, renderCTA, className, style, }: ProductInfoProps): React.JSX.Element;
21
44
  //# sourceMappingURL=ProductInfo.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProductInfo.d.ts","sourceRoot":"","sources":["../../../src/components/product-page/ProductInfo.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,qBAAqB,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAC9C,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAW,EACX,KAAK,EACL,QAAc,EACd,qBAAqB,EACrB,SAAS,EACT,SAAS,EACT,KAAK,GACN,EAAE,gBAAgB,qBAsBlB"}
1
+ {"version":3,"file":"ProductInfo.d.ts","sourceRoot":"","sources":["../../../src/components/product-page/ProductInfo.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE5F,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wBAAwB;IACxB,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,uBAAuB;IACvB,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,iCAAiC;IACjC,eAAe,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACxC,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,wEAAwE;IACxE,qBAAqB,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAC9C,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAW,EACX,KAAK,EACL,QAAc,EACd,YAAY,EACZ,KAAK,EACL,SAAS,EACT,MAAM,EACN,OAAO,EACP,QAAQ,EACR,eAAe,EAAE,gBAAgB,EACjC,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,SAAS,EACT,SAAS,EACT,KAAK,GACN,EAAE,gBAAgB,qBA2DlB"}
@@ -3,17 +3,32 @@
3
3
  *
4
4
  * Title, description, price, optional variant selector and CTA. Minimal layout;
5
5
  * override with className/style. For use on individual product page.
6
+ *
7
+ * When options/variants/selections/onSelectionChange are provided and
8
+ * renderVariantSelector is NOT provided, renders the built-in VariantSelector.
9
+ * renderVariantSelector still takes priority as an override.
6
10
  */
7
11
  import React from 'react';
8
12
  import { clsx } from 'clsx';
9
- export function ProductInfo({ title, description, price, currency = '$', renderVariantSelector, renderCTA, className, style, }) {
13
+ import { PriceDisplay } from '../primitives/PriceDisplay';
14
+ import { BadgeList } from '../primitives/BadgeList';
15
+ import { VariantSelector } from '../primitives/VariantSelector';
16
+ export function ProductInfo({ title, description, price, currency = '$', comparePrice, brand, available, badges, options, variants, selectedVariant: _selectedVariant, selections, onSelectionChange, renderVariantSelector, renderCTA, className, style, }) {
10
17
  const priceNum = price != null ? (typeof price === 'number' ? price : parseFloat(String(price))) : null;
18
+ const comparePriceNum = comparePrice != null ? (typeof comparePrice === 'number' ? comparePrice : parseFloat(String(comparePrice))) : null;
19
+ const showBuiltInVariantSelector = !renderVariantSelector && options && options.length > 0 && variants && selections && onSelectionChange;
11
20
  return (React.createElement("div", { className: clsx('seekora-product-info', className), style: { display: 'flex', flexDirection: 'column', gap: 12, ...style } },
21
+ badges && badges.length > 0 && (React.createElement(BadgeList, { badges: badges, position: "inline" })),
22
+ brand && (React.createElement("span", { className: "seekora-product-info-brand", style: { fontSize: '0.8125rem', color: 'var(--seekora-text-secondary)', textTransform: 'uppercase', letterSpacing: '0.02em' } }, brand)),
12
23
  React.createElement("h1", { className: "seekora-product-info-title", style: { fontSize: '1.25rem', fontWeight: 600, margin: 0 } }, title),
13
- priceNum != null && !Number.isNaN(priceNum) ? (React.createElement("span", { className: "seekora-product-info-price", style: { fontSize: '1.125rem', fontWeight: 600 } },
14
- currency,
15
- priceNum.toFixed(2))) : null,
24
+ (priceNum != null && !Number.isNaN(priceNum)) && (React.createElement("div", { className: "seekora-product-info-price" },
25
+ React.createElement(PriceDisplay, { price: priceNum, comparePrice: comparePriceNum ?? undefined, currency: currency, style: { fontSize: '1.125rem' } }))),
26
+ available != null && (React.createElement("span", { className: "seekora-product-info-availability", style: {
27
+ fontSize: '0.875rem',
28
+ color: available ? 'var(--seekora-success, #22c55e)' : 'var(--seekora-error, #ef4444)',
29
+ } }, available ? 'In Stock' : 'Out of Stock')),
16
30
  description ? (React.createElement("p", { className: "seekora-product-info-description", style: { fontSize: '0.875rem', color: 'var(--seekora-text-secondary)', margin: 0, lineHeight: 1.5 } }, description)) : null,
17
- renderVariantSelector?.(),
31
+ renderVariantSelector ? renderVariantSelector() : null,
32
+ showBuiltInVariantSelector && (React.createElement(VariantSelector, { options: options, variants: variants, selections: selections, onSelectionChange: onSelectionChange, showAvailability: true })),
18
33
  renderCTA?.()));
19
34
  }
@@ -7,6 +7,8 @@ export interface SectionItemGridProps {
7
7
  maxItems?: number;
8
8
  className?: string;
9
9
  style?: React.CSSProperties;
10
+ /** Show loading state when fetching and no previous items (default false: show previous results until new render) */
11
+ showLoadingState?: boolean;
10
12
  getItemId?: (item: unknown) => string;
11
13
  getItemTitle?: (item: unknown) => string;
12
14
  getItemImage?: (item: unknown) => string | undefined;
@@ -14,5 +16,5 @@ export interface SectionItemGridProps {
14
16
  getItemUrl?: (item: unknown) => string | undefined;
15
17
  renderItem?: (item: unknown, index: number) => React.ReactNode;
16
18
  }
17
- export declare function SectionItemGrid({ columns, maxItems, className, style, getItemId, getItemTitle, getItemImage, getItemDescription, getItemUrl, renderItem, }: SectionItemGridProps): React.JSX.Element;
19
+ export declare function SectionItemGrid({ columns, maxItems, className, style, showLoadingState, getItemId, getItemTitle, getItemImage, getItemDescription, getItemUrl, renderItem, }: SectionItemGridProps): React.JSX.Element;
18
20
  //# sourceMappingURL=SectionItemGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SectionItemGrid.d.ts","sourceRoot":"","sources":["../../../src/components/section-primitives/SectionItemGrid.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;IACtC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACrD,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IAC3D,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAChE;AAED,wBAAgB,eAAe,CAAC,EAC9B,OAAW,EACX,QAAa,EACb,SAAS,EACT,KAAK,EACL,SAA8E,EAC9E,YAAyE,EACzE,YAAmE,EACnE,kBAAqF,EACrF,UAA6D,EAC7D,UAAU,GACX,EAAE,oBAAoB,qBAsBtB"}
1
+ {"version":3,"file":"SectionItemGrid.d.ts","sourceRoot":"","sources":["../../../src/components/section-primitives/SectionItemGrid.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,qHAAqH;IACrH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;IACtC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACrD,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IAC3D,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAChE;AAED,wBAAgB,eAAe,CAAC,EAC9B,OAAW,EACX,QAAa,EACb,SAAS,EACT,KAAK,EACL,gBAAwB,EACxB,SAA8E,EAC9E,YAAyE,EACzE,YAAmE,EACnE,kBAAqF,EACrF,UAA6D,EAC7D,UAAU,GACX,EAAE,oBAAoB,qBAuBtB"}
@@ -6,11 +6,12 @@ import { useSectionSearchContext } from './SectionSearchContext';
6
6
  import { ItemGrid } from '../suggestions-primitives/ItemGrid';
7
7
  import { SectionLoading } from './SectionLoading';
8
8
  import { SectionError } from './SectionError';
9
- export function SectionItemGrid({ columns = 4, maxItems = 12, className, style, getItemId = (i) => i.id ?? String(i?.objectID ?? ''), getItemTitle = (i) => i.title ?? i?.title ?? '', getItemImage = (i) => i.image ?? i?.image, getItemDescription = (i) => i.description ?? i?.description, getItemUrl = (i) => i.url ?? i?.url, renderItem, }) {
9
+ export function SectionItemGrid({ columns = 4, maxItems = 12, className, style, showLoadingState = false, getItemId = (i) => i.id ?? String(i?.objectID ?? ''), getItemTitle = (i) => i.title ?? i?.title ?? '', getItemImage = (i) => i.image ?? i?.image, getItemDescription = (i) => i.description ?? i?.description, getItemUrl = (i) => i.url ?? i?.url, renderItem, }) {
10
10
  const { items, loading, error, trackClick } = useSectionSearchContext();
11
- if (loading)
11
+ if (loading && items.length === 0 && showLoadingState)
12
12
  return React.createElement(SectionLoading, { className: className, style: style });
13
13
  if (error)
14
14
  return React.createElement(SectionError, { className: className, style: style });
15
+ // When loading with previous items, show them (no loading screen)
15
16
  return (React.createElement(ItemGrid, { items: items, maxItems: maxItems, columns: columns, className: className, style: style, getItemId: getItemId, getItemTitle: getItemTitle, getItemImage: getItemImage, getItemDescription: getItemDescription, getItemUrl: getItemUrl, renderItem: renderItem, onItemClick: (item, index) => trackClick(item, index) }));
16
17
  }