@instockng/storefront-ui 1.0.75 → 1.0.77

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 (255) hide show
  1. package/dist/components/ProductAddOns.d.ts +3 -1
  2. package/dist/components/ProductAddOns.d.ts.map +1 -1
  3. package/dist/components/RecommendedProducts.d.ts.map +1 -1
  4. package/dist/components/VariantPickerModal.d.ts +28 -0
  5. package/dist/components/VariantPickerModal.d.ts.map +1 -0
  6. package/dist/contexts/CartContext.d.ts.map +1 -1
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.mjs +104 -102
  10. package/dist/index10.mjs +162 -121
  11. package/dist/index100.mjs +20 -69
  12. package/dist/index101.mjs +55 -34
  13. package/dist/index102.mjs +32 -42
  14. package/dist/index103.mjs +2 -2
  15. package/dist/index104.mjs +2 -28
  16. package/dist/index105.mjs +2 -18
  17. package/dist/index106.mjs +26 -213
  18. package/dist/index107.mjs +13 -175
  19. package/dist/index108.mjs +210 -17
  20. package/dist/index109.mjs +174 -17
  21. package/dist/index11.mjs +116 -88
  22. package/dist/index110.mjs +14 -25
  23. package/dist/index111.mjs +17 -150
  24. package/dist/index112.mjs +26 -13
  25. package/dist/index113.mjs +149 -24
  26. package/dist/index114.mjs +13 -77
  27. package/dist/index115.mjs +20 -27
  28. package/dist/index116.mjs +76 -137
  29. package/dist/index117.mjs +33 -50
  30. package/dist/index118.mjs +141 -19
  31. package/dist/index119.mjs +50 -22
  32. package/dist/index12.mjs +86 -90
  33. package/dist/index120.mjs +14 -14
  34. package/dist/index121.mjs +20 -18
  35. package/dist/index122.mjs +16 -14
  36. package/dist/index123.mjs +17 -14
  37. package/dist/index124.mjs +12 -12
  38. package/dist/index125.mjs +15 -58
  39. package/dist/index126.mjs +14 -11
  40. package/dist/index127.mjs +56 -30
  41. package/dist/index128.mjs +9 -15
  42. package/dist/index129.mjs +31 -26
  43. package/dist/index13.mjs +83 -185
  44. package/dist/index130.mjs +13 -16
  45. package/dist/index131.mjs +26 -11
  46. package/dist/index132.mjs +18 -12
  47. package/dist/index133.mjs +12 -40
  48. package/dist/index134.mjs +14 -16
  49. package/dist/index135.mjs +42 -263
  50. package/dist/index136.mjs +15 -62
  51. package/dist/index137.mjs +267 -7
  52. package/dist/index138.mjs +70 -2
  53. package/dist/index139.mjs +8 -2
  54. package/dist/index14.mjs +196 -94
  55. package/dist/index140.mjs +2 -33
  56. package/dist/index141.mjs +2 -2
  57. package/dist/index142.mjs +33 -2
  58. package/dist/index143.mjs +2 -21
  59. package/dist/index144.mjs +19 -54
  60. package/dist/index145.mjs +55 -28
  61. package/dist/index146.mjs +27 -5
  62. package/dist/index147.mjs +6 -51
  63. package/dist/index148.mjs +51 -5
  64. package/dist/index149.mjs +6 -12
  65. package/dist/index15.mjs +82 -713
  66. package/dist/index150.mjs +12 -7
  67. package/dist/index151.mjs +6 -27
  68. package/dist/index152.mjs +28 -2
  69. package/dist/index153.mjs +2 -70
  70. package/dist/index154.mjs +67 -164
  71. package/dist/index155.mjs +167 -2
  72. package/dist/index159.mjs +1 -1
  73. package/dist/index16.mjs +722 -53
  74. package/dist/index160.mjs +1 -1
  75. package/dist/index161.mjs +1 -1
  76. package/dist/index162.mjs +1 -1
  77. package/dist/index163.mjs +1 -1
  78. package/dist/index165.mjs +3 -3
  79. package/dist/index166.mjs +1 -1
  80. package/dist/index168.mjs +3 -3
  81. package/dist/index169.mjs +1 -1
  82. package/dist/index17.mjs +58 -60
  83. package/dist/index174.mjs +2 -2
  84. package/dist/index176.mjs +1 -1
  85. package/dist/index178.mjs +2 -2
  86. package/dist/index179.mjs +5 -5
  87. package/dist/index18.mjs +60 -22
  88. package/dist/index182.mjs +1 -1
  89. package/dist/index185.mjs +1 -1
  90. package/dist/index188.mjs +3 -3
  91. package/dist/index189.mjs +1 -1
  92. package/dist/index19.mjs +22 -107
  93. package/dist/index193.mjs +2 -2
  94. package/dist/index195.mjs +2 -2
  95. package/dist/index199.mjs +1 -1
  96. package/dist/index2.mjs +2 -2
  97. package/dist/index20.mjs +105 -38
  98. package/dist/index201.mjs +1 -1
  99. package/dist/index21.mjs +37 -37
  100. package/dist/index210.mjs +2 -2
  101. package/dist/index212.mjs +1 -1
  102. package/dist/index213.mjs +1 -1
  103. package/dist/index214.mjs +2 -31
  104. package/dist/index215.mjs +30 -10
  105. package/dist/index216.mjs +10 -3
  106. package/dist/index217.mjs +3 -3
  107. package/dist/index218.mjs +4 -13
  108. package/dist/index219.mjs +13 -7
  109. package/dist/index22.mjs +42 -71
  110. package/dist/index220.mjs +7 -12
  111. package/dist/index221.mjs +11 -4
  112. package/dist/index222.mjs +5 -33
  113. package/dist/index223.mjs +33 -31
  114. package/dist/index224.mjs +29 -26
  115. package/dist/index225.mjs +26 -59
  116. package/dist/index226.mjs +61 -2
  117. package/dist/index227.mjs +2 -108
  118. package/dist/index228.mjs +2 -2
  119. package/dist/index229.mjs +2 -2
  120. package/dist/index23.mjs +67 -28
  121. package/dist/index230.mjs +2 -37
  122. package/dist/index231.mjs +108 -2
  123. package/dist/index232.mjs +2 -2
  124. package/dist/index233.mjs +2 -2
  125. package/dist/index234.mjs +2 -2
  126. package/dist/index236.mjs +2 -2
  127. package/dist/index237.mjs +2 -244
  128. package/dist/index238.mjs +37 -2
  129. package/dist/index239.mjs +2 -33
  130. package/dist/index24.mjs +29 -6
  131. package/dist/index240.mjs +2 -65
  132. package/dist/index241.mjs +243 -24
  133. package/dist/index242.mjs +2 -2
  134. package/dist/index243.mjs +33 -2
  135. package/dist/index244.mjs +65 -2
  136. package/dist/index245.mjs +25 -2
  137. package/dist/index246.mjs +2 -2
  138. package/dist/index247.mjs +2 -2
  139. package/dist/index248.mjs +2 -2
  140. package/dist/index249.mjs +2 -2
  141. package/dist/index25.mjs +8 -21
  142. package/dist/index250.mjs +2 -2
  143. package/dist/index251.mjs +2 -2
  144. package/dist/index253.mjs +2 -2
  145. package/dist/index254.mjs +2 -2
  146. package/dist/index255.mjs +4 -2
  147. package/dist/index256.mjs +2 -4
  148. package/dist/index258.mjs +3 -2
  149. package/dist/index259.mjs +2 -18
  150. package/dist/index26.mjs +19 -34
  151. package/dist/index260.mjs +2 -47
  152. package/dist/index261.mjs +17 -2
  153. package/dist/index262.mjs +13 -2
  154. package/dist/index263.mjs +6 -2
  155. package/dist/index264.mjs +30 -2
  156. package/dist/index265.mjs +2 -91
  157. package/dist/index266.mjs +2 -3
  158. package/dist/index267.mjs +18 -2
  159. package/dist/index268.mjs +47 -2
  160. package/dist/index269.mjs +2 -17
  161. package/dist/index27.mjs +34 -37
  162. package/dist/index270.mjs +2 -13
  163. package/dist/index271.mjs +2 -6
  164. package/dist/index272.mjs +2 -30
  165. package/dist/index273.mjs +91 -2
  166. package/dist/index274.mjs +2 -2
  167. package/dist/index276.mjs +2 -2
  168. package/dist/index277.mjs +5 -0
  169. package/dist/index28.mjs +28 -103
  170. package/dist/index29.mjs +104 -20
  171. package/dist/index3.mjs +90 -90
  172. package/dist/index30.mjs +29 -9
  173. package/dist/index31.mjs +9 -9
  174. package/dist/index32.mjs +11 -116
  175. package/dist/index33.mjs +116 -25
  176. package/dist/index34.mjs +25 -81
  177. package/dist/index35.mjs +81 -112
  178. package/dist/index36.mjs +109 -8
  179. package/dist/index37.mjs +9 -33
  180. package/dist/index38.mjs +27 -20
  181. package/dist/index39.mjs +26 -9
  182. package/dist/index4.mjs +1 -1
  183. package/dist/index40.mjs +10 -122
  184. package/dist/index41.mjs +114 -380
  185. package/dist/index42.mjs +384 -20
  186. package/dist/index43.mjs +24 -31
  187. package/dist/index44.mjs +32 -7
  188. package/dist/index45.mjs +6 -1432
  189. package/dist/index46.mjs +1432 -69
  190. package/dist/index47.mjs +70 -2
  191. package/dist/index48.mjs +2 -60
  192. package/dist/index49.mjs +57 -48
  193. package/dist/index5.mjs +1 -1
  194. package/dist/index50.mjs +51 -33
  195. package/dist/index51.mjs +33 -15
  196. package/dist/index52.mjs +12 -2260
  197. package/dist/index53.mjs +2263 -36
  198. package/dist/index54.mjs +36 -44
  199. package/dist/index55.mjs +44 -99
  200. package/dist/index56.mjs +99 -81
  201. package/dist/index57.mjs +75 -13
  202. package/dist/index58.mjs +15 -125
  203. package/dist/index59.mjs +93 -89
  204. package/dist/index6.mjs +1 -1
  205. package/dist/index60.mjs +102 -56
  206. package/dist/index61.mjs +55 -89
  207. package/dist/index62.mjs +82 -76
  208. package/dist/index63.mjs +100 -13
  209. package/dist/index64.mjs +17 -92
  210. package/dist/index65.mjs +70 -56
  211. package/dist/index66.mjs +62 -44
  212. package/dist/index67.mjs +47 -46
  213. package/dist/index68.mjs +48 -121
  214. package/dist/index69.mjs +133 -22
  215. package/dist/index7.mjs +6 -6
  216. package/dist/index70.mjs +4 -152
  217. package/dist/index71.mjs +21 -20
  218. package/dist/index72.mjs +2 -5
  219. package/dist/index73.mjs +23 -2
  220. package/dist/index74.mjs +149 -71
  221. package/dist/index75.mjs +74 -14
  222. package/dist/index76.mjs +14 -62
  223. package/dist/index77.mjs +63 -2
  224. package/dist/index78.mjs +234 -5
  225. package/dist/index79.mjs +5 -1133
  226. package/dist/index8.mjs +4 -4
  227. package/dist/index80.mjs +131 -17
  228. package/dist/index81.mjs +67 -54
  229. package/dist/index82.mjs +84 -30
  230. package/dist/index83.mjs +29 -2
  231. package/dist/index84.mjs +9 -235
  232. package/dist/index85.mjs +74 -5
  233. package/dist/index86.mjs +3 -133
  234. package/dist/index87.mjs +2 -68
  235. package/dist/index88.mjs +79 -83
  236. package/dist/index89.mjs +52 -27
  237. package/dist/index9.mjs +3 -3
  238. package/dist/index90.mjs +5 -8
  239. package/dist/index91.mjs +4 -74
  240. package/dist/index92.mjs +178 -3
  241. package/dist/index93.mjs +53 -2
  242. package/dist/index94.mjs +68 -82
  243. package/dist/index95.mjs +33 -53
  244. package/dist/index96.mjs +42 -5
  245. package/dist/index97.mjs +2 -5
  246. package/dist/index98.mjs +5 -178
  247. package/dist/index99.mjs +1134 -53
  248. package/dist/styles.css +1 -1
  249. package/package.json +1 -1
  250. package/src/components/ProductAddOns.stories.tsx +10 -3
  251. package/src/components/ProductAddOns.tsx +167 -112
  252. package/src/components/RecommendedProducts.tsx +130 -88
  253. package/src/components/VariantPickerModal.tsx +154 -0
  254. package/src/contexts/CartContext.tsx +8 -1
  255. package/src/index.ts +3 -0
@@ -14,6 +14,7 @@ import { formatCurrency, cn } from '../lib/utils';
14
14
  import { Button } from './ui/button';
15
15
  import { ShoppingCart, Loader2, Package } from 'lucide-react';
16
16
  import { useState, useEffect } from 'react';
17
+ import { VariantPickerModal, type VariantPickerProduct, type Variant } from './VariantPickerModal';
17
18
 
18
19
  export interface RecommendedProductsProps {
19
20
  /** Number of recommendations to fetch */
@@ -32,6 +33,8 @@ export function RecommendedProducts({
32
33
  const [fadingProductIds, setFadingProductIds] = useState<string[]>([]);
33
34
  const [collapsingProductIds, setCollapsingProductIds] = useState<string[]>([]);
34
35
  const [hiddenProductIds, setHiddenProductIds] = useState<string[]>([]);
36
+ const [variantPickerProduct, setVariantPickerProduct] = useState<NonNullable<typeof recommendations>[number] | null>(null);
37
+ const [isAddingFromModal, setIsAddingFromModal] = useState(false);
35
38
 
36
39
  // Reset hidden products when recommendations change (e.g., when cart items are removed)
37
40
  useEffect(() => {
@@ -87,100 +90,139 @@ export function RecommendedProducts({
87
90
  }
88
91
  };
89
92
 
93
+ const handleAddClick = (product: typeof visibleRecommendations[0]) => {
94
+ // For multi-variant products, open modal
95
+ if (product.variants.length > 1) {
96
+ setVariantPickerProduct(product);
97
+ return;
98
+ }
99
+ // For single-variant, add directly
100
+ const firstVariant = product.variants[0];
101
+ if (firstVariant) {
102
+ handleAddToCart(product, firstVariant);
103
+ }
104
+ };
105
+
106
+ const handleVariantConfirm = async (product: VariantPickerProduct, variant: Variant) => {
107
+ setIsAddingFromModal(true);
108
+ try {
109
+ const fullProduct = visibleRecommendations.find(p => p.id === product.id);
110
+ const fullVariant = fullProduct?.variants.find(v => v.id === variant.id);
111
+ if (fullProduct && fullVariant) {
112
+ await handleAddToCart(fullProduct, fullVariant);
113
+ }
114
+ setVariantPickerProduct(null);
115
+ } finally {
116
+ setIsAddingFromModal(false);
117
+ }
118
+ };
119
+
90
120
  return (
91
- <div className={cn('pt-2', className)}>
92
- {/* Heading */}
93
- <div className="mb-2 px-4">
94
- <h3 className="text-lg font-semibold text-gray-900">Frequently bought together</h3>
95
- </div>
121
+ <>
122
+ <div className={cn('pt-2', className)}>
123
+ {/* Heading */}
124
+ <div className="mb-2 px-4">
125
+ <h3 className="text-lg font-semibold text-gray-900">Frequently bought together</h3>
126
+ </div>
96
127
 
97
- {/* Horizontal Scrollable Product List */}
98
- <div className="overflow-x-auto">
99
- <div className="flex gap-4 pb-2 transition-all duration-500">
100
- {/* Left spacer */}
101
- <div className="flex-shrink-0 w-1" />
102
-
103
- {visibleRecommendations?.map((product) => {
104
- const firstVariant = product.variants[0];
105
- if (!firstVariant) return null;
106
-
107
- const isAdding = addingProductIds.includes(product.id);
108
- const isFading = fadingProductIds.includes(product.id);
109
- const isCollapsing = collapsingProductIds.includes(product.id);
110
-
111
- return (
112
- <div
113
- key={product.id}
114
- className={cn(
115
- "flex flex-row flex-shrink-0 w-64 h-24 bg-white border border-gray-300 rounded-lg overflow-hidden transition-all duration-500",
116
- isFading && "opacity-0 blur-sm scale-95 pointer-events-none",
117
- isCollapsing && "w-0 h-0 mr-[-1rem] overflow-hidden"
118
- )}
119
- >
120
- {/* Clickable Product Link */}
121
- <a
122
- href={`/product/${product.slug}`}
123
- className="flex flex-row flex-1 hover:bg-gray-50 transition-colors"
128
+ {/* Horizontal Scrollable Product List */}
129
+ <div className="overflow-x-auto">
130
+ <div className="flex gap-4 pb-2 transition-all duration-500">
131
+ {/* Left spacer */}
132
+ <div className="flex-shrink-0 w-1" />
133
+
134
+ {visibleRecommendations?.map((product) => {
135
+ const firstVariant = product.variants[0];
136
+ if (!firstVariant) return null;
137
+
138
+ const isAdding = addingProductIds.includes(product.id);
139
+ const isFading = fadingProductIds.includes(product.id);
140
+ const isCollapsing = collapsingProductIds.includes(product.id);
141
+
142
+ return (
143
+ <div
144
+ key={product.id}
145
+ className={cn(
146
+ "flex flex-row flex-shrink-0 w-64 h-24 bg-white border border-gray-300 rounded-lg overflow-hidden transition-all duration-500",
147
+ isFading && "opacity-0 blur-sm scale-95 pointer-events-none",
148
+ isCollapsing && "w-0 h-0 mr-[-1rem] overflow-hidden"
149
+ )}
124
150
  >
125
- {/* Product Image */}
126
- <div className="relative w-20 h-full flex-shrink-0 bg-gray-100">
127
- {product.thumbnailUrl ? (
128
- <img
129
- src={product.thumbnailUrl}
130
- alt={product.name}
131
- className="w-full h-full object-cover"
132
- />
133
- ) : (
134
- <div className="w-full h-full flex items-center justify-center">
135
- <Package className="h-8 w-8 text-gray-300" />
136
- </div>
137
- )}
138
- </div>
139
-
140
- {/* Product Info */}
141
- <div className="p-2 flex flex-col flex-1 justify-between">
142
- <div>
143
- <h4 className="text-xs font-medium text-gray-900 line-clamp-2 leading-tight">
144
- {product.name}
145
- </h4>
146
- <p className="text-xs font-semibold text-accent-600 mt-0.5">
147
- {formatCurrency(firstVariant.price)}
148
- </p>
149
- </div>
150
-
151
- {/* Add to Cart Button */}
152
- <Button
153
- size="sm"
154
- onClick={(e) => {
155
- e.preventDefault();
156
- e.stopPropagation();
157
- handleAddToCart(product, firstVariant);
158
- }}
159
- disabled={isAdding}
160
- className="w-full bg-accent-500 hover:bg-accent-600 text-white text-xs h-6 mt-1"
161
- >
162
- {isAdding ? (
163
- <>
164
- <Loader2 className="h-3 w-3 animate-spin" />
165
- Adding...
166
- </>
151
+ {/* Clickable Product Link */}
152
+ <a
153
+ href={`/product/${product.slug}`}
154
+ className="flex flex-row flex-1 hover:bg-gray-50 transition-colors"
155
+ >
156
+ {/* Product Image */}
157
+ <div className="relative w-20 h-full flex-shrink-0 bg-gray-100">
158
+ {product.thumbnailUrl ? (
159
+ <img
160
+ src={product.thumbnailUrl}
161
+ alt={product.name}
162
+ className="w-full h-full object-cover"
163
+ />
167
164
  ) : (
168
- <>
169
- <ShoppingCart className="h-3 w-3" />
170
- Add
171
- </>
165
+ <div className="w-full h-full flex items-center justify-center">
166
+ <Package className="h-8 w-8 text-gray-300" />
167
+ </div>
172
168
  )}
173
- </Button>
174
- </div>
175
- </a>
176
- </div>
177
- );
178
- })}
179
-
180
- {/* Right spacer */}
181
- <div className="flex-shrink-0 w-1" />
169
+ </div>
170
+
171
+ {/* Product Info */}
172
+ <div className="p-2 flex flex-col flex-1 justify-between">
173
+ <div>
174
+ <h4 className="text-xs font-medium text-gray-900 line-clamp-2 leading-tight">
175
+ {product.name}
176
+ </h4>
177
+ <p className="text-xs font-semibold text-accent-600 mt-0.5">
178
+ {formatCurrency(firstVariant.price)}
179
+ </p>
180
+ </div>
181
+
182
+ {/* Add to Cart Button */}
183
+ <Button
184
+ size="sm"
185
+ onClick={(e) => {
186
+ e.preventDefault();
187
+ e.stopPropagation();
188
+ handleAddClick(product);
189
+ }}
190
+ disabled={isAdding}
191
+ className="w-full bg-accent-500 hover:bg-accent-600 text-white text-xs h-6 mt-1"
192
+ >
193
+ {isAdding ? (
194
+ <>
195
+ <Loader2 className="h-3 w-3 animate-spin" />
196
+ Adding...
197
+ </>
198
+ ) : (
199
+ <>
200
+ <ShoppingCart className="h-3 w-3" />
201
+ Add
202
+ </>
203
+ )}
204
+ </Button>
205
+ </div>
206
+ </a>
207
+ </div>
208
+ );
209
+ })}
210
+
211
+ {/* Right spacer */}
212
+ <div className="flex-shrink-0 w-1" />
213
+ </div>
182
214
  </div>
183
215
  </div>
184
- </div>
216
+
217
+ {/* Variant Picker Modal */}
218
+ <VariantPickerModal
219
+ isOpen={!!variantPickerProduct}
220
+ onClose={() => setVariantPickerProduct(null)}
221
+ product={variantPickerProduct}
222
+ onConfirm={handleVariantConfirm}
223
+ isLoading={isAddingFromModal}
224
+ />
225
+ </>
185
226
  );
186
227
  }
228
+
@@ -0,0 +1,154 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * VariantPickerModal Component
5
+ *
6
+ * Modal for selecting a product variant before adding to cart.
7
+ * Used by ProductAddOns and RecommendedProducts for multi-variant products.
8
+ */
9
+
10
+ import { useState } from 'react';
11
+ import { Modal } from './ui/modal';
12
+ import { Button } from './ui/button';
13
+ import { formatCurrency, cn } from '../lib/utils';
14
+ import { ShoppingCart, Loader2, Package } from 'lucide-react';
15
+
16
+ export interface Variant {
17
+ id: string;
18
+ name?: string | null;
19
+ price: number;
20
+ sku: string;
21
+ thumbnailUrl?: string | null;
22
+ }
23
+
24
+ export interface VariantPickerProduct {
25
+ id: string;
26
+ name: string;
27
+ slug: string;
28
+ thumbnailUrl?: string | null;
29
+ variants: Variant[];
30
+ }
31
+
32
+ export interface VariantPickerModalProps {
33
+ /** Controls modal visibility */
34
+ isOpen: boolean;
35
+ /** Callback when modal should close */
36
+ onClose: () => void;
37
+ /** Product to select variant for */
38
+ product: VariantPickerProduct | null;
39
+ /** Callback when variant is selected and confirmed */
40
+ onConfirm: (product: VariantPickerProduct, variant: Variant) => void;
41
+ /** Whether add operation is in progress */
42
+ isLoading?: boolean;
43
+ }
44
+
45
+ export function VariantPickerModal({
46
+ isOpen,
47
+ onClose,
48
+ product,
49
+ onConfirm,
50
+ isLoading = false,
51
+ }: VariantPickerModalProps) {
52
+ const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
53
+
54
+ // Reset selection when product changes
55
+ const effectiveVariantId = selectedVariantId || product?.variants[0]?.id || null;
56
+ const selectedVariant = product?.variants.find(v => v.id === effectiveVariantId);
57
+
58
+ const handleConfirm = () => {
59
+ if (product && selectedVariant) {
60
+ onConfirm(product, selectedVariant);
61
+ }
62
+ };
63
+
64
+ if (!product) return null;
65
+
66
+ return (
67
+ <Modal
68
+ isOpen={isOpen}
69
+ onClose={onClose}
70
+ title="Select Option"
71
+ size="sm"
72
+ footer={
73
+ <Button
74
+ onClick={handleConfirm}
75
+ disabled={!selectedVariant || isLoading}
76
+ className="w-full bg-accent-500 hover:bg-accent-600 text-white"
77
+ size="lg"
78
+ >
79
+ {isLoading ? (
80
+ <>
81
+ <Loader2 className="h-4 w-4 animate-spin mr-2" />
82
+ Adding...
83
+ </>
84
+ ) : (
85
+ <>
86
+ <ShoppingCart className="h-4 w-4 mr-2" />
87
+ Add to Cart - {selectedVariant && formatCurrency(selectedVariant.price)}
88
+ </>
89
+ )}
90
+ </Button>
91
+ }
92
+ >
93
+ <div className="space-y-4">
94
+ {/* Product Info */}
95
+ <div className="flex gap-4">
96
+ {/* Thumbnail - shows variant image if available, otherwise product image */}
97
+ <div className="w-20 h-20 flex-shrink-0 rounded-lg overflow-hidden bg-gray-100">
98
+ {(selectedVariant?.thumbnailUrl || product.thumbnailUrl) ? (
99
+ <img
100
+ src={selectedVariant?.thumbnailUrl || product.thumbnailUrl || ''}
101
+ alt={selectedVariant?.name || product.name}
102
+ className="w-full h-full object-cover"
103
+ />
104
+ ) : (
105
+ <div className="w-full h-full flex items-center justify-center">
106
+ <Package className="h-8 w-8 text-gray-300" />
107
+ </div>
108
+ )}
109
+ </div>
110
+
111
+ {/* Name */}
112
+ <div className="flex-1">
113
+ <h3 className="font-semibold text-gray-900">{product.name}</h3>
114
+ <p className="text-sm text-gray-500 mt-1">
115
+ {product.variants.length} options available
116
+ </p>
117
+ </div>
118
+ </div>
119
+
120
+ {/* Variant Buttons */}
121
+ <div className="space-y-2">
122
+ <p className="text-sm font-medium text-gray-700">Choose an option:</p>
123
+ <div className="flex flex-wrap gap-2">
124
+ {product.variants.map((variant) => {
125
+ const isSelected = variant.id === effectiveVariantId;
126
+ const variantImage = variant.thumbnailUrl || product.thumbnailUrl;
127
+ return (
128
+ <button
129
+ key={variant.id}
130
+ onClick={() => setSelectedVariantId(variant.id)}
131
+ className={cn(
132
+ "flex items-center gap-2 px-3 py-2 rounded-full text-sm font-medium border-2 transition-all",
133
+ isSelected
134
+ ? "border-accent-500 bg-accent-50 text-accent-700"
135
+ : "border-gray-200 bg-white text-gray-700 hover:border-gray-300"
136
+ )}
137
+ >
138
+ {variantImage && (
139
+ <img
140
+ src={variantImage}
141
+ alt={variant.name || 'Variant'}
142
+ className="w-6 h-6 rounded-full object-cover flex-shrink-0"
143
+ />
144
+ )}
145
+ {variant.name || 'Default'} - {formatCurrency(variant.price)}
146
+ </button>
147
+ );
148
+ })}
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </Modal>
153
+ );
154
+ }
@@ -264,6 +264,7 @@ export function CartProvider({ children, brandSlug, initialCartId, shoppingCartP
264
264
 
265
265
  // Add item to cart via API
266
266
  // Refresh recommendations only if cart is not visible
267
+ const shouldRefreshRecommendations = !isOpen;
267
268
  const updatedCart = await addItemMutation.mutateAsync({
268
269
  sku,
269
270
  quantity,
@@ -271,9 +272,15 @@ export function CartProvider({ children, brandSlug, initialCartId, shoppingCartP
271
272
  fbp,
272
273
  ttp,
273
274
  ttclid,
274
- refreshRecommendations: !isOpen
275
+ refreshRecommendations: shouldRefreshRecommendations
275
276
  });
276
277
 
278
+ // Invalidate recommendations cache only if cart was closed when adding
279
+ // (to avoid refreshing while user is viewing the cart)
280
+ if (shouldRefreshRecommendations && cartId) {
281
+ queryClient.invalidateQueries({ queryKey: queryKeys.public.carts.recommendations(cartId) });
282
+ }
283
+
277
284
  // Skip tracking if error response
278
285
  if ('error' in updatedCart) return;
279
286
 
package/src/index.ts CHANGED
@@ -37,6 +37,9 @@ export type { ProductAddOnsProps, AddOnProduct } from './components/ProductAddOn
37
37
  export { RecommendedProducts } from './components/RecommendedProducts';
38
38
  export type { RecommendedProductsProps } from './components/RecommendedProducts';
39
39
 
40
+ export { VariantPickerModal } from './components/VariantPickerModal';
41
+ export type { VariantPickerModalProps, VariantPickerProduct, Variant } from './components/VariantPickerModal';
42
+
40
43
  export { CartItem } from './components/CartItem';
41
44
  export type { CartItemProps } from './components/CartItem';
42
45