@libreapps/commerce 7.5.1

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 (327) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +13 -0
  3. package/LICENSE.md +21 -0
  4. package/components/Icons.tsx +35 -0
  5. package/components/add-to-cart-widget.tsx +183 -0
  6. package/components/buy/buy-card.tsx +259 -0
  7. package/components/buy/carousel-buy-card.tsx +242 -0
  8. package/components/buy/multi-family/all-variants-carousel.tsx +261 -0
  9. package/components/buy/multi-family/family-carousel/index.tsx +77 -0
  10. package/components/buy/multi-family/family-carousel/slide.tsx +83 -0
  11. package/components/buy/multi-family/family-carousel/state.ts +87 -0
  12. package/components/buy/multi-family/index.ts +2 -0
  13. package/components/buy/single-family-selector.tsx +90 -0
  14. package/components/buy/title-and-byline.tsx +25 -0
  15. package/components/cart/cart-panel/cart-line-item.tsx +76 -0
  16. package/components/cart/cart-panel/index.tsx +154 -0
  17. package/components/cart/cart-panel/promo-code.tsx +109 -0
  18. package/components/cart/cart-panel/total-area.tsx +60 -0
  19. package/components/checkout/payment-step-form/card-icon-row.tsx +26 -0
  20. package/components/checkout/payment-step-form/card-icons/amex.tsx +32 -0
  21. package/components/checkout/payment-step-form/card-icons/diners-club.tsx +13 -0
  22. package/components/checkout/payment-step-form/card-icons/discover.tsx +25 -0
  23. package/components/checkout/payment-step-form/card-icons/jcb.tsx +26 -0
  24. package/components/checkout/payment-step-form/card-icons/mastercard.tsx +27 -0
  25. package/components/checkout/payment-step-form/card-icons/visa.tsx +25 -0
  26. package/components/checkout/payment-step-form/cc-button.tsx +17 -0
  27. package/components/checkout/payment-step-form/contact-form.tsx +50 -0
  28. package/components/checkout/payment-step-form/crypto-icons/btc.tsx +11 -0
  29. package/components/checkout/payment-step-form/crypto-icons/eth.tsx +20 -0
  30. package/components/checkout/payment-step-form/crypto-icons/usdt.tsx +13 -0
  31. package/components/checkout/payment-step-form/index.tsx +122 -0
  32. package/components/checkout/payment-step-form/methods/bank-transfer.tsx +79 -0
  33. package/components/checkout/payment-step-form/methods/card.tsx +232 -0
  34. package/components/checkout/payment-step-form/methods/crypto.tsx +227 -0
  35. package/components/checkout/payment-step-form/methods/index.ts +23 -0
  36. package/components/checkout/shipping-step-form.tsx +175 -0
  37. package/components/index.ts +11 -0
  38. package/components/item/product-card.tsx +48 -0
  39. package/components/item-selector/button.tsx +188 -0
  40. package/components/item-selector/carousel/index.tsx +197 -0
  41. package/components/item-selector/carousel/slider.tsx +40 -0
  42. package/components/item-selector/index.ts +5 -0
  43. package/components/item-selector/quantity-indicator.tsx +48 -0
  44. package/components/node-tabs/index.tsx +91 -0
  45. package/components/node-tabs/node-image.tsx +31 -0
  46. package/dist/components/Icons.d.ts +18 -0
  47. package/dist/components/Icons.js +19 -0
  48. package/dist/components/Icons.js.map +1 -0
  49. package/dist/components/add-to-cart-widget.d.ts +11 -0
  50. package/dist/components/add-to-cart-widget.js +85 -0
  51. package/dist/components/add-to-cart-widget.js.map +1 -0
  52. package/dist/components/buy/buy-card.d.ts +30 -0
  53. package/dist/components/buy/buy-card.js +109 -0
  54. package/dist/components/buy/buy-card.js.map +1 -0
  55. package/dist/components/buy/carousel-buy-card.d.ts +12 -0
  56. package/dist/components/buy/carousel-buy-card.js +94 -0
  57. package/dist/components/buy/carousel-buy-card.js.map +1 -0
  58. package/dist/components/buy/multi-family/all-variants-carousel.d.ts +4 -0
  59. package/dist/components/buy/multi-family/all-variants-carousel.js +115 -0
  60. package/dist/components/buy/multi-family/all-variants-carousel.js.map +1 -0
  61. package/dist/components/buy/multi-family/family-carousel/index.d.ts +4 -0
  62. package/dist/components/buy/multi-family/family-carousel/index.js +27 -0
  63. package/dist/components/buy/multi-family/family-carousel/index.js.map +1 -0
  64. package/dist/components/buy/multi-family/family-carousel/slide.d.ts +11 -0
  65. package/dist/components/buy/multi-family/family-carousel/slide.js +35 -0
  66. package/dist/components/buy/multi-family/family-carousel/slide.js.map +1 -0
  67. package/dist/components/buy/multi-family/family-carousel/state.d.ts +20 -0
  68. package/dist/components/buy/multi-family/family-carousel/state.js +59 -0
  69. package/dist/components/buy/multi-family/family-carousel/state.js.map +1 -0
  70. package/dist/components/buy/multi-family/index.d.ts +2 -0
  71. package/dist/components/buy/multi-family/index.js +3 -0
  72. package/dist/components/buy/multi-family/index.js.map +1 -0
  73. package/dist/components/buy/single-family-selector.d.ts +15 -0
  74. package/dist/components/buy/single-family-selector.js +28 -0
  75. package/dist/components/buy/single-family-selector.js.map +1 -0
  76. package/dist/components/buy/title-and-byline.d.ts +8 -0
  77. package/dist/components/buy/title-and-byline.js +7 -0
  78. package/dist/components/buy/title-and-byline.js.map +1 -0
  79. package/dist/components/cart/cart-panel/cart-line-item.d.ts +11 -0
  80. package/dist/components/cart/cart-panel/cart-line-item.js +25 -0
  81. package/dist/components/cart/cart-panel/cart-line-item.js.map +1 -0
  82. package/dist/components/cart/cart-panel/index.d.ts +19 -0
  83. package/dist/components/cart/cart-panel/index.js +65 -0
  84. package/dist/components/cart/cart-panel/index.js.map +1 -0
  85. package/dist/components/cart/cart-panel/promo-code.d.ts +4 -0
  86. package/dist/components/cart/cart-panel/promo-code.js +62 -0
  87. package/dist/components/cart/cart-panel/promo-code.js.map +1 -0
  88. package/dist/components/cart/cart-panel/total-area.d.ts +7 -0
  89. package/dist/components/cart/cart-panel/total-area.js +14 -0
  90. package/dist/components/cart/cart-panel/total-area.js.map +1 -0
  91. package/dist/components/checkout/payment-step-form/card-icon-row.d.ts +2 -0
  92. package/dist/components/checkout/payment-step-form/card-icon-row.js +14 -0
  93. package/dist/components/checkout/payment-step-form/card-icon-row.js.map +1 -0
  94. package/dist/components/checkout/payment-step-form/card-icons/amex.d.ts +4 -0
  95. package/dist/components/checkout/payment-step-form/card-icons/amex.js +6 -0
  96. package/dist/components/checkout/payment-step-form/card-icons/amex.js.map +1 -0
  97. package/dist/components/checkout/payment-step-form/card-icons/diners-club.d.ts +4 -0
  98. package/dist/components/checkout/payment-step-form/card-icons/diners-club.js +6 -0
  99. package/dist/components/checkout/payment-step-form/card-icons/diners-club.js.map +1 -0
  100. package/dist/components/checkout/payment-step-form/card-icons/discover.d.ts +4 -0
  101. package/dist/components/checkout/payment-step-form/card-icons/discover.js +6 -0
  102. package/dist/components/checkout/payment-step-form/card-icons/discover.js.map +1 -0
  103. package/dist/components/checkout/payment-step-form/card-icons/jcb.d.ts +4 -0
  104. package/dist/components/checkout/payment-step-form/card-icons/jcb.js +6 -0
  105. package/dist/components/checkout/payment-step-form/card-icons/jcb.js.map +1 -0
  106. package/dist/components/checkout/payment-step-form/card-icons/mastercard.d.ts +4 -0
  107. package/dist/components/checkout/payment-step-form/card-icons/mastercard.js +6 -0
  108. package/dist/components/checkout/payment-step-form/card-icons/mastercard.js.map +1 -0
  109. package/dist/components/checkout/payment-step-form/card-icons/visa.d.ts +4 -0
  110. package/dist/components/checkout/payment-step-form/card-icons/visa.js +6 -0
  111. package/dist/components/checkout/payment-step-form/card-icons/visa.js.map +1 -0
  112. package/dist/components/checkout/payment-step-form/cc-button.d.ts +3 -0
  113. package/dist/components/checkout/payment-step-form/cc-button.js +6 -0
  114. package/dist/components/checkout/payment-step-form/cc-button.js.map +1 -0
  115. package/dist/components/checkout/payment-step-form/contact-form.d.ts +5 -0
  116. package/dist/components/checkout/payment-step-form/contact-form.js +6 -0
  117. package/dist/components/checkout/payment-step-form/contact-form.js.map +1 -0
  118. package/dist/components/checkout/payment-step-form/crypto-icons/btc.d.ts +4 -0
  119. package/dist/components/checkout/payment-step-form/crypto-icons/btc.js +6 -0
  120. package/dist/components/checkout/payment-step-form/crypto-icons/btc.js.map +1 -0
  121. package/dist/components/checkout/payment-step-form/crypto-icons/eth.d.ts +4 -0
  122. package/dist/components/checkout/payment-step-form/crypto-icons/eth.js +6 -0
  123. package/dist/components/checkout/payment-step-form/crypto-icons/eth.js.map +1 -0
  124. package/dist/components/checkout/payment-step-form/crypto-icons/usdt.d.ts +4 -0
  125. package/dist/components/checkout/payment-step-form/crypto-icons/usdt.js +6 -0
  126. package/dist/components/checkout/payment-step-form/crypto-icons/usdt.js.map +1 -0
  127. package/dist/components/checkout/payment-step-form/index.d.ts +4 -0
  128. package/dist/components/checkout/payment-step-form/index.js +77 -0
  129. package/dist/components/checkout/payment-step-form/index.js.map +1 -0
  130. package/dist/components/checkout/payment-step-form/methods/bank-transfer.d.ts +4 -0
  131. package/dist/components/checkout/payment-step-form/methods/bank-transfer.js +24 -0
  132. package/dist/components/checkout/payment-step-form/methods/bank-transfer.js.map +1 -0
  133. package/dist/components/checkout/payment-step-form/methods/card.d.ts +4 -0
  134. package/dist/components/checkout/payment-step-form/methods/card.js +160 -0
  135. package/dist/components/checkout/payment-step-form/methods/card.js.map +1 -0
  136. package/dist/components/checkout/payment-step-form/methods/crypto.d.ts +9 -0
  137. package/dist/components/checkout/payment-step-form/methods/crypto.js +137 -0
  138. package/dist/components/checkout/payment-step-form/methods/crypto.js.map +1 -0
  139. package/dist/components/checkout/payment-step-form/methods/index.d.ts +6 -0
  140. package/dist/components/checkout/payment-step-form/methods/index.js +21 -0
  141. package/dist/components/checkout/payment-step-form/methods/index.js.map +1 -0
  142. package/dist/components/checkout/shipping-step-form.d.ts +3 -0
  143. package/dist/components/checkout/shipping-step-form.js +53 -0
  144. package/dist/components/checkout/shipping-step-form.js.map +1 -0
  145. package/dist/components/index.d.ts +8 -0
  146. package/dist/components/index.js +9 -0
  147. package/dist/components/index.js.map +1 -0
  148. package/dist/components/item/product-card.d.ts +7 -0
  149. package/dist/components/item/product-card.js +9 -0
  150. package/dist/components/item/product-card.js.map +1 -0
  151. package/dist/components/item-selector/button.d.ts +4 -0
  152. package/dist/components/item-selector/button.js +47 -0
  153. package/dist/components/item-selector/button.js.map +1 -0
  154. package/dist/components/item-selector/carousel/index.d.ts +12 -0
  155. package/dist/components/item-selector/carousel/index.js +74 -0
  156. package/dist/components/item-selector/carousel/index.js.map +1 -0
  157. package/dist/components/item-selector/carousel/slider.d.ts +8 -0
  158. package/dist/components/item-selector/carousel/slider.js +12 -0
  159. package/dist/components/item-selector/carousel/slider.js.map +1 -0
  160. package/dist/components/item-selector/index.d.ts +2 -0
  161. package/dist/components/item-selector/index.js +3 -0
  162. package/dist/components/item-selector/index.js.map +1 -0
  163. package/dist/components/item-selector/quantity-indicator.d.ts +9 -0
  164. package/dist/components/item-selector/quantity-indicator.js +16 -0
  165. package/dist/components/item-selector/quantity-indicator.js.map +1 -0
  166. package/dist/components/node-tabs/index.d.ts +14 -0
  167. package/dist/components/node-tabs/index.js +42 -0
  168. package/dist/components/node-tabs/index.js.map +1 -0
  169. package/dist/components/node-tabs/node-image.d.ts +6 -0
  170. package/dist/components/node-tabs/node-image.js +13 -0
  171. package/dist/components/node-tabs/node-image.js.map +1 -0
  172. package/dist/index.d.ts +5 -0
  173. package/dist/index.js +5 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/service/context.d.ts +8 -0
  176. package/dist/service/context.js +19 -0
  177. package/dist/service/context.js.map +1 -0
  178. package/dist/service/debug.d.ts +10 -0
  179. package/dist/service/debug.js +30 -0
  180. package/dist/service/debug.js.map +1 -0
  181. package/dist/service/impls/standalone/actual-line-item.d.ts +40 -0
  182. package/dist/service/impls/standalone/actual-line-item.js +84 -0
  183. package/dist/service/impls/standalone/actual-line-item.js.map +1 -0
  184. package/dist/service/impls/standalone/get-instance.d.ts +2 -0
  185. package/dist/service/impls/standalone/get-instance.js +39 -0
  186. package/dist/service/impls/standalone/get-instance.js.map +1 -0
  187. package/dist/service/impls/standalone/index.d.ts +67 -0
  188. package/dist/service/impls/standalone/index.js +416 -0
  189. package/dist/service/impls/standalone/index.js.map +1 -0
  190. package/dist/service/impls/standalone/order/firebase.d.ts +2 -0
  191. package/dist/service/impls/standalone/order/firebase.js +13 -0
  192. package/dist/service/impls/standalone/order/firebase.js.map +1 -0
  193. package/dist/service/impls/standalone/order/index.d.ts +24 -0
  194. package/dist/service/impls/standalone/order/index.js +61 -0
  195. package/dist/service/impls/standalone/order/index.js.map +1 -0
  196. package/dist/service/impls/standalone/persistence.d.ts +4 -0
  197. package/dist/service/impls/standalone/persistence.js +22 -0
  198. package/dist/service/impls/standalone/persistence.js.map +1 -0
  199. package/dist/service/path-utils.d.ts +7 -0
  200. package/dist/service/path-utils.js +16 -0
  201. package/dist/service/path-utils.js.map +1 -0
  202. package/dist/service/sep.d.ts +6 -0
  203. package/dist/service/sep.js +6 -0
  204. package/dist/service/sep.js.map +1 -0
  205. package/dist/types/category-node.d.ts +36 -0
  206. package/dist/types/category-node.js +2 -0
  207. package/dist/types/category-node.js.map +1 -0
  208. package/dist/types/checkout.d.ts +33 -0
  209. package/dist/types/checkout.js +2 -0
  210. package/dist/types/checkout.js.map +1 -0
  211. package/dist/types/commerce-config.d.ts +11 -0
  212. package/dist/types/commerce-config.js +2 -0
  213. package/dist/types/commerce-config.js.map +1 -0
  214. package/dist/types/commerce-service.d.ts +109 -0
  215. package/dist/types/commerce-service.js +2 -0
  216. package/dist/types/commerce-service.js.map +1 -0
  217. package/dist/types/family.d.ts +16 -0
  218. package/dist/types/family.js +2 -0
  219. package/dist/types/family.js.map +1 -0
  220. package/dist/types/index.d.ts +13 -0
  221. package/dist/types/index.js +8 -0
  222. package/dist/types/index.js.map +1 -0
  223. package/dist/types/item-selector.d.ts +72 -0
  224. package/dist/types/item-selector.js +2 -0
  225. package/dist/types/item-selector.js.map +1 -0
  226. package/dist/types/line-item.d.ts +14 -0
  227. package/dist/types/line-item.js +2 -0
  228. package/dist/types/line-item.js.map +1 -0
  229. package/dist/types/multi-family-selector-props.d.ts +16 -0
  230. package/dist/types/multi-family-selector-props.js +2 -0
  231. package/dist/types/multi-family-selector-props.js.map +1 -0
  232. package/dist/types/product.d.ts +15 -0
  233. package/dist/types/product.js +2 -0
  234. package/dist/types/product.js.map +1 -0
  235. package/dist/types/promo.d.ts +7 -0
  236. package/dist/types/promo.js +2 -0
  237. package/dist/types/promo.js.map +1 -0
  238. package/dist/types/selection-ui-specifier.d.ts +40 -0
  239. package/dist/types/selection-ui-specifier.js +2 -0
  240. package/dist/types/selection-ui-specifier.js.map +1 -0
  241. package/dist/types/string-mutator.d.ts +9 -0
  242. package/dist/types/string-mutator.js +2 -0
  243. package/dist/types/string-mutator.js.map +1 -0
  244. package/dist/types/token-separators.d.ts +6 -0
  245. package/dist/types/token-separators.js +2 -0
  246. package/dist/types/token-separators.js.map +1 -0
  247. package/dist/util/analytics.d.ts +9 -0
  248. package/dist/util/analytics.js +10 -0
  249. package/dist/util/analytics.js.map +1 -0
  250. package/dist/util/countries.d.ts +7 -0
  251. package/dist/util/countries.js +197 -0
  252. package/dist/util/countries.js.map +1 -0
  253. package/dist/util/error.d.ts +1 -0
  254. package/dist/util/error.js +22 -0
  255. package/dist/util/error.js.map +1 -0
  256. package/dist/util/index.d.ts +15 -0
  257. package/dist/util/index.js +54 -0
  258. package/dist/util/index.js.map +1 -0
  259. package/dist/util/item-selector-options-accessor.d.ts +3 -0
  260. package/dist/util/item-selector-options-accessor.js +27 -0
  261. package/dist/util/item-selector-options-accessor.js.map +1 -0
  262. package/dist/util/line-item-ref.d.ts +8 -0
  263. package/dist/util/line-item-ref.js +15 -0
  264. package/dist/util/line-item-ref.js.map +1 -0
  265. package/dist/util/multi-family-selector-options-accessor.d.ts +3 -0
  266. package/dist/util/multi-family-selector-options-accessor.js +11 -0
  267. package/dist/util/multi-family-selector-options-accessor.js.map +1 -0
  268. package/dist/util/obs-string-mutator.d.ts +8 -0
  269. package/dist/util/obs-string-mutator.js +15 -0
  270. package/dist/util/obs-string-mutator.js.map +1 -0
  271. package/dist/util/product-media-accessor.d.ts +29 -0
  272. package/dist/util/product-media-accessor.js +22 -0
  273. package/dist/util/product-media-accessor.js.map +1 -0
  274. package/dist/util/promo-codes.d.ts +3 -0
  275. package/dist/util/promo-codes.js +100 -0
  276. package/dist/util/promo-codes.js.map +1 -0
  277. package/dist/util/selection-ui-specifiers.d.ts +3 -0
  278. package/dist/util/selection-ui-specifiers.js +24 -0
  279. package/dist/util/selection-ui-specifiers.js.map +1 -0
  280. package/dist/util/square-payment.d.ts +7 -0
  281. package/dist/util/square-payment.js +37 -0
  282. package/dist/util/square-payment.js.map +1 -0
  283. package/dist/util/use-sync-sku-param-w-current-item.d.ts +2 -0
  284. package/dist/util/use-sync-sku-param-w-current-item.js +61 -0
  285. package/dist/util/use-sync-sku-param-w-current-item.js.map +1 -0
  286. package/index.ts +13 -0
  287. package/libreapps-ui.d.ts +108 -0
  288. package/package.json +67 -0
  289. package/service/context.tsx +45 -0
  290. package/service/debug.ts +41 -0
  291. package/service/impls/standalone/actual-line-item.ts +136 -0
  292. package/service/impls/standalone/get-instance.ts +64 -0
  293. package/service/impls/standalone/index.ts +579 -0
  294. package/service/impls/standalone/order/firebase.ts +14 -0
  295. package/service/impls/standalone/order/index.ts +129 -0
  296. package/service/impls/standalone/persistence.ts +33 -0
  297. package/service/path-utils.ts +26 -0
  298. package/service/sep.ts +7 -0
  299. package/tsconfig.json +17 -0
  300. package/types/README.md +2 -0
  301. package/types/category-node.ts +50 -0
  302. package/types/checkout.ts +47 -0
  303. package/types/commerce-config.ts +13 -0
  304. package/types/commerce-service.ts +128 -0
  305. package/types/family.ts +26 -0
  306. package/types/index.ts +15 -0
  307. package/types/item-selector.ts +97 -0
  308. package/types/line-item.ts +29 -0
  309. package/types/multi-family-selector-props.ts +20 -0
  310. package/types/product.ts +21 -0
  311. package/types/promo.ts +10 -0
  312. package/types/selection-ui-specifier.ts +52 -0
  313. package/types/string-mutator.ts +14 -0
  314. package/types/token-separators.ts +7 -0
  315. package/util/analytics.ts +21 -0
  316. package/util/countries.ts +196 -0
  317. package/util/error.ts +34 -0
  318. package/util/index.ts +71 -0
  319. package/util/item-selector-options-accessor.ts +35 -0
  320. package/util/line-item-ref.ts +23 -0
  321. package/util/multi-family-selector-options-accessor.ts +15 -0
  322. package/util/obs-string-mutator.ts +22 -0
  323. package/util/product-media-accessor.ts +58 -0
  324. package/util/promo-codes.ts +106 -0
  325. package/util/selection-ui-specifiers.ts +30 -0
  326. package/util/square-payment.ts +50 -0
  327. package/util/use-sync-sku-param-w-current-item.ts +88 -0
@@ -0,0 +1,242 @@
1
+ 'use client'
2
+ import React, {
3
+ useRef,
4
+ useEffect,
5
+ type ComponentType,
6
+ useState
7
+ } from 'react'
8
+ import { observer } from 'mobx-react-lite'
9
+
10
+ import { cn } from '@libreapps/ui/util'
11
+
12
+ import type {
13
+ ItemSelectorProps,
14
+ LineItem,
15
+ Family,
16
+ CategoryNode,
17
+ CategoryNodeRole,
18
+ SelectionUISpecifier,
19
+ ItemSelectorOptions,
20
+ MultiFamilySelectorProps,
21
+ MultiFamilySelectorOptions,
22
+ } from '../../types'
23
+
24
+ import { useCommerce } from '../../service/context'
25
+ import { getSelectionUISpecifier } from '../../util'
26
+
27
+ import { CarouselItemSelector, ButtonItemSelector } from '../item-selector'
28
+ import SingleFamilySelector from './single-family-selector'
29
+ import { FamilyCarousel, AllVariantsCarousel } from './multi-family'
30
+
31
+ import AddToCartWidget from '../add-to-cart-widget'
32
+
33
+ const SCROLL = {
34
+ scrollAfter: 5,
35
+ scrollHeightClx: 'h-[65vh]'
36
+ }
37
+
38
+ const MEDIA_CONSTRAINT = {w: 200, h: 200}
39
+
40
+ const sortItems = (items: LineItem[], sort: 'asc' | 'desc' | 'none'): LineItem[] => (
41
+ ((sort === 'asc') ?
42
+ items.sort((a: LineItem, b: LineItem): number => (a.price - b.price))
43
+ :
44
+ ((sort === 'desc') ?
45
+ items.sort((a: LineItem, b: LineItem): number => (b.price - a.price ))
46
+ :
47
+ items
48
+ )
49
+ )
50
+ )
51
+
52
+ const CarouselBuyCard: React.FC<{
53
+ skuPath: string
54
+ checkoutButton: React.ReactNode
55
+ clx?: string
56
+ selectorClx?: string
57
+ addBtnClx?: string
58
+ buttonsAreaClx?: string
59
+ mobile?: boolean
60
+ onQuantityChanged?: (sku: string, oldV: number, newV: number) => void
61
+ }> = ({
62
+ skuPath,
63
+ checkoutButton,
64
+ clx='',
65
+ selectorClx='',
66
+ addBtnClx='',
67
+ buttonsAreaClx='',
68
+ mobile=false,
69
+ onQuantityChanged,
70
+ }) => {
71
+
72
+ const cmmc = useCommerce()
73
+
74
+ const r = useRef<{
75
+ role: CategoryNodeRole
76
+ item: LineItem | undefined
77
+ family: Family | undefined
78
+ families: Family[] | undefined
79
+ node: CategoryNode
80
+ uiSpec: SelectionUISpecifier
81
+ single?: {
82
+ items: LineItem[]
83
+ Selector: ComponentType<ItemSelectorProps>
84
+ selOptions: ItemSelectorOptions | undefined
85
+ scrollable: boolean
86
+ showItemMedia: boolean
87
+ }
88
+ multi?: {
89
+ Selector: ComponentType<MultiFamilySelectorProps>
90
+ selectorOptions: MultiFamilySelectorOptions | undefined
91
+ itemOptions: ItemSelectorOptions | undefined
92
+ initialFamilyId: string
93
+ }
94
+ } | undefined>(undefined)
95
+
96
+ const [changeMeToRerender, setChangeMeToRerender] = useState<boolean>(false)
97
+
98
+ useEffect(() => {
99
+
100
+ if (!skuPath || skuPath.length === 0 ) {
101
+ // The component is being hidden (w an amination)
102
+ // keep things the same so no layout jump
103
+ return
104
+ }
105
+
106
+ const peek = cmmc.peek(skuPath)
107
+ if (typeof peek === 'string') {
108
+ throw new Error(peek)
109
+ }
110
+ if (peek.role === 'non-outermost') {
111
+ throw new Error(`BuyCard: skuPath ${skuPath} isn't an outermost tree node or product family!`)
112
+ }
113
+
114
+ const uiSpec = getSelectionUISpecifier(skuPath)
115
+
116
+ r.current = {
117
+ ...peek,
118
+ node: peek.node!, // else exception was thrown.
119
+ uiSpec,
120
+ }
121
+
122
+ if (peek.role === 'single-family') {
123
+
124
+ const sort = (): 'none' | 'asc' | 'desc' => {
125
+ const options = uiSpec.singleFamily?.options ?? {}
126
+ const showSlider = 'showSlider' in options ? options.showSlider! : true
127
+ return ('sort' in options) ?
128
+ options.sort!
129
+ :
130
+ (showSlider ? 'asc' : 'none')
131
+ }
132
+
133
+ const items = peek.family!.products as LineItem[]
134
+ const Selector: ComponentType<ItemSelectorProps> =
135
+ (uiSpec.singleFamily?.type === 'buttons') ? ButtonItemSelector : CarouselItemSelector
136
+ const selOptions = uiSpec.singleFamily?.options
137
+
138
+ r.current.single = {
139
+ items: sortItems(items, sort()),
140
+ Selector,
141
+ selOptions,
142
+ scrollable: !!(items.length > SCROLL.scrollAfter),
143
+ showItemMedia: uiSpec.singleFamily?.type !== 'carousel'
144
+ }
145
+
146
+ const currItem = peek.item ?? r.current.single.items[0]
147
+ cmmc.setCurrentItem(currItem.sku)
148
+ }
149
+ else {
150
+ const initialFamily = peek.family ? peek.family : peek.families![0]
151
+ // TODO: Does this ever need to be sorted??
152
+ const currItem = peek.item ?? initialFamily.products[0]
153
+
154
+ // sets currentFamily as well
155
+ cmmc.setCurrentItem(currItem.sku)
156
+
157
+ r.current.multi = {
158
+ Selector: (uiSpec.multiFamily?.type === 'family-carousel') ?
159
+ FamilyCarousel
160
+ :
161
+ AllVariantsCarousel,
162
+ itemOptions: uiSpec.multiFamily?.itemOptions,
163
+ selectorOptions: uiSpec.multiFamily?.selectorOptions,
164
+ initialFamilyId: initialFamily.id
165
+ }
166
+ }
167
+
168
+ // Must do this since Dialog code takes this comp out of the React shadow DOM
169
+ // (only in prod apparently.)
170
+ setChangeMeToRerender(!changeMeToRerender)
171
+ }, [skuPath])
172
+
173
+ const MultiFamilyUI: React.FC<{
174
+ Selector: ComponentType<MultiFamilySelectorProps>
175
+ selectorOptions: MultiFamilySelectorOptions | undefined
176
+ itemOptions: ItemSelectorOptions | undefined
177
+ families: Family[]
178
+ parent: CategoryNode
179
+ clx?: string
180
+ }> = ({
181
+ Selector,
182
+ itemOptions,
183
+ selectorOptions,
184
+ families,
185
+ parent,
186
+ clx=''
187
+ }) => (
188
+ <Selector
189
+ families={families}
190
+ parent={parent}
191
+ clx={clx}
192
+ itemOptions={itemOptions}
193
+ selectorOptions={selectorOptions}
194
+ mediaConstraint={MEDIA_CONSTRAINT}
195
+ mobile={mobile}
196
+ />
197
+ )
198
+
199
+ const Buttons: React.FC<{clx?: string}> = observer(({
200
+ clx=''
201
+ }) => (cmmc.currentItem ? (
202
+ <div className={clx}>
203
+ <AddToCartWidget
204
+ item={cmmc.currentItem}
205
+ onQuantityChanged={onQuantityChanged}
206
+ variant={cmmc.cartEmpty ? 'primary' : 'outline'}
207
+ className={addBtnClx}
208
+ />
209
+ {!cmmc.cartEmpty && checkoutButton}
210
+ </div>
211
+ ) : null))
212
+
213
+ return (
214
+ <div className={cn(
215
+ 'px-4 md:px-6 pt-3 pb-4 flex flex-col gap-1 items-center',
216
+ r.current?.single?.scrollable ? SCROLL.scrollHeightClx : 'h-auto',
217
+ clx,
218
+ )}>
219
+ {r.current?.single ? (
220
+ <SingleFamilySelector
221
+ {...r.current.single}
222
+ mediaConstraint={MEDIA_CONSTRAINT}
223
+ mobile={mobile}
224
+ clx={selectorClx}
225
+ />
226
+ ) : (r.current?.multi && r.current.families && /* safegaurd for first render, etc. */ (
227
+ <MultiFamilyUI
228
+ {...r.current.multi}
229
+ families={r.current.families}
230
+ parent={r.current.node}
231
+ clx={selectorClx}
232
+ />
233
+ ))}
234
+ <Buttons clx={cn(
235
+ 'self-stretch mt-8 flex flex-col items-center gap-3 shrink-0 grow-0',
236
+ buttonsAreaClx
237
+ )}/>
238
+ </div >
239
+ )
240
+ }
241
+
242
+ export default CarouselBuyCard
@@ -0,0 +1,261 @@
1
+ 'use client'
2
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
3
+ import { reaction } from 'mobx'
4
+ import { observer } from 'mobx-react-lite'
5
+
6
+ import { cn } from '@libreapps/ui/util'
7
+
8
+ import {
9
+ ApplyTypography,
10
+ type CarouselApi,
11
+ Carousel,
12
+ CarouselContent,
13
+ CarouselItem,
14
+ CarouselPrevious,
15
+ CarouselNext,
16
+ MediaStack,
17
+ } from '@libreapps/ui/primitives'
18
+
19
+ import type { MultiFamilySelectorProps, LineItem } from '../../../types'
20
+ import {
21
+ formatCurrencyValue,
22
+ accessItemOptions,
23
+ accessMultiSelectorOptions
24
+ } from '../../../util'
25
+
26
+ import QuantityIndicator from '../../item-selector/quantity-indicator'
27
+ import { ButtonItemSelector, useCommerce } from '../../..'
28
+
29
+ const debugBorder = (c: 'r' | 'g' | 'b', disable: boolean = true): string => {
30
+
31
+ if (disable) {
32
+ return ''
33
+ }
34
+ switch (c) {
35
+ case 'r': return ' border border-[#ffaaaa] '
36
+ case 'g': return ' border border-[#aaffaa] '
37
+ case 'b': return ' border border-[#aaaaff] '
38
+ }
39
+ }
40
+
41
+ const DEFAULT_CONSTRAINT = {w: 250, h: 250}
42
+
43
+ const AllVariantsCarousel: React.FC<MultiFamilySelectorProps> = ({
44
+ families,
45
+ parent,
46
+ clx='',
47
+ itemClx='',
48
+ itemOptions,
49
+ selectorOptions,
50
+ mediaConstraint=DEFAULT_CONSTRAINT,
51
+ mobile=false, // not relavant to any children
52
+ }) => {
53
+
54
+ const cmmc = useCommerce()
55
+ const r = useRef<{
56
+ api: CarouselApi | undefined
57
+ items: LineItem[]
58
+ dontRespond: boolean
59
+ initialIndex: number
60
+ } | undefined>(undefined)
61
+
62
+
63
+ const [changeMeToRerender, setChangeMeToRerender] = useState<boolean>(false)
64
+ // Safe, since Carousel is only render (and this method passed), once the ref is initialized.
65
+ const setApi = (api: CarouselApi) => { r.current!.api = api }
66
+
67
+ const onSelect = useCallback((emblaApi: CarouselApi) => {
68
+ if (r.current?.dontRespond) {
69
+ r.current.dontRespond = false
70
+ return
71
+ }
72
+ const index = emblaApi.selectedScrollSnap()
73
+ if (index !== -1) {
74
+ const item = r.current?.items[index]
75
+ cmmc.setCurrentItem(item?.sku) // (sets currentFamily as well)
76
+ }
77
+ if (r.current) {
78
+ r.current.dontRespond = false
79
+ }
80
+ }, [])
81
+
82
+ useEffect(() => {
83
+
84
+ const items = families.map((fam) => (fam.products as LineItem[])).flat()
85
+ const foundIndex = cmmc.currentItem ? items.findIndex((item) => (cmmc.currentItem!.sku == item.sku)) : -1
86
+ r.current = {
87
+ items,
88
+ initialIndex: foundIndex === -1 ? 0 : foundIndex,
89
+ dontRespond: false,
90
+ api: undefined
91
+ }
92
+
93
+ setChangeMeToRerender(!changeMeToRerender)
94
+
95
+ // This responds to the swatch clicks
96
+ return reaction(
97
+ () => (cmmc.currentItem),
98
+ (item) => {
99
+ if (r.current && r.current.api) {
100
+ const index = r.current.items.findIndex((_item: LineItem) => (_item.sku === item?.sku))
101
+ if (index && index !== -1) {
102
+ // no need to sync family, since ui only allows selecting within a family
103
+ r.current.dontRespond = true
104
+ r.current.api.scrollTo(index)
105
+ }
106
+ }
107
+ }
108
+ )
109
+ }, [])
110
+
111
+ const Header: React.FC<{clx?: string}> = observer(({
112
+ clx=''
113
+ }) => {
114
+
115
+ const { showParentTitle, parentByline: parentBylineMode } = accessMultiSelectorOptions(selectorOptions)
116
+ const { familyTitle, showFamilyByline } = accessItemOptions(itemOptions)
117
+
118
+ const parentTitleDisplay = showParentTitle ? parent.label : undefined
119
+ const parentBylineDisplay = (parentBylineMode === 'own-line') ? parent.subNodesLabel : ''
120
+
121
+ const title = (familyTitle === 'none' ?
122
+ undefined
123
+ :
124
+ (familyTitle === 'long' ?
125
+ cmmc.currentFamily?.title
126
+ :
127
+ (cmmc.currentFamily?.titleShort ?? cmmc.currentFamily?.title)
128
+ )
129
+ )
130
+
131
+ let titleLinePrefix = (parentBylineMode === 'none' || parentBylineMode === 'own-line') ? '' : (parent.subNodesLabel ?? '')
132
+ if (titleLinePrefix.length > 0 && title && parentBylineMode === 'comma-sep') {
133
+ titleLinePrefix += ', '
134
+ }
135
+ else if (titleLinePrefix.length > 0 && title && parentBylineMode === 'colon-sep') {
136
+ titleLinePrefix += ': '
137
+ }
138
+ const titleDisplay = title ? (titleLinePrefix + title) : undefined
139
+ const bylineDisplay = (familyTitle !== 'none') && showFamilyByline ? cmmc.currentFamily?.byline : undefined
140
+
141
+ return (
142
+ <ApplyTypography className={cn('flex flex-col items-center !gap-0 [&>*]:!m-0 ', clx)} >
143
+ {parentTitleDisplay && <h4>{parentTitleDisplay}</h4>}
144
+ {parentBylineDisplay && <h4>{parentBylineDisplay}</h4>}
145
+ {titleDisplay && <h4>{titleDisplay}</h4>}
146
+ {bylineDisplay && (<h6 >{bylineDisplay}</h6>)}
147
+ </ApplyTypography>
148
+ )
149
+ })
150
+
151
+ const ItemInfo: React.FC<{
152
+ clx?: string
153
+ labelClx?: string
154
+ }> = observer(({
155
+ clx='',
156
+ labelClx=''
157
+ }) => {
158
+
159
+ const {
160
+ showPrice,
161
+ showQuantity,
162
+ showFamilyInOption,
163
+ showByline,
164
+ } = accessItemOptions(itemOptions)
165
+
166
+ const optionLabel = () => (
167
+ showFamilyInOption ?
168
+ (cmmc.currentItem!.familyTitle + ', ' + cmmc.currentItem!.optionLabel)
169
+ :
170
+ cmmc.currentItem!.optionLabel
171
+ )
172
+
173
+ return (cmmc.currentItem && (
174
+ <ApplyTypography className={cn('flex flex-col items-center [&>*]:!m-0 !gap-1 ', clx)}>
175
+ <div className={
176
+ 'flex items-center gap-1 [&>*]:!m-0 ' +
177
+ debugBorder('g') +
178
+ (showFamilyInOption ? 'flex-col' : 'flex-row')
179
+ }>
180
+ <h6 className={cn('font-semibold', labelClx)}>
181
+ {optionLabel() + (showPrice && !showFamilyInOption ? ',' : '')}
182
+ </h6>
183
+ <div className={
184
+ 'flex items-center gap-1 [&>*]:!m-0 flex-row ' + debugBorder('b') +
185
+ (showFamilyInOption ? 'w-full justify-between' : '')
186
+ }>
187
+ {showPrice && (<p>{formatCurrencyValue(cmmc.currentItem.price)}</p>)}
188
+ {showQuantity && (
189
+ <QuantityIndicator
190
+ item={cmmc.currentItem}
191
+ clx='h-[22px] ml-4'
192
+ iconClx='fill-foreground'
193
+ digitClx='not-typography font-semibold text-primary-fg leading-none font-sans text-xs'
194
+ />
195
+ )}
196
+ </div>
197
+ </div>
198
+ {showByline && cmmc.currentItem.byline && (<p>{cmmc.currentItem.byline}</p>)}
199
+ </ApplyTypography>
200
+ ))
201
+ })
202
+
203
+ const Swatches: React.FC<{
204
+ clx?: string
205
+ }> = observer(({
206
+ clx=''
207
+ }) => {
208
+ const { showItemSwatches } = accessMultiSelectorOptions(selectorOptions)
209
+ if (
210
+ !showItemSwatches ||
211
+ !cmmc.currentFamily
212
+ //|| cmmc.currentFamily.products.length === 1
213
+ ) {
214
+ return null
215
+ }
216
+ return <ButtonItemSelector
217
+ items={cmmc.currentFamily.products as LineItem[]}
218
+ selectedItemRef={cmmc}
219
+ selectSku={cmmc.setCurrentItem.bind(cmmc)}
220
+ clx={clx}
221
+ options={{
222
+ buttonType: 'image',
223
+ horizButtons: true
224
+ }}
225
+ />
226
+ })
227
+
228
+ return (
229
+ <div className={cn('w-full flex flex-col items-center', clx)}>
230
+ <Header />
231
+ {r.current && /* Only render once we've set the ref fields, or else bad things! */ (
232
+ <Carousel
233
+ options={{loop: true, startIndex: r.current.initialIndex}}
234
+ className={'w-full px-2' + debugBorder('r')}
235
+ onCarouselSelect={onSelect}
236
+ setApi={setApi}
237
+ >
238
+ <CarouselContent>
239
+ {r.current.items.map((item) => (
240
+ <CarouselItem key={item.sku} className={cn('p-2 flex flex-col justify-center items-center', itemClx)}>
241
+ <MediaStack media={item} constrainTo={mediaConstraint} clx='my-4'/>
242
+ </CarouselItem>
243
+ ))}
244
+ </CarouselContent>
245
+ {r.current.items.length > 1 && (<>
246
+ <CarouselPrevious className='left-1'/>
247
+ <CarouselNext className='right-1'/>
248
+ </>)}
249
+ </Carousel>
250
+ )}
251
+ <div className='flex flex-col items-center justify-start'>
252
+ <ItemInfo labelClx='!text-base font-medium'/>
253
+ <Swatches clx='mt-2'/>
254
+ </div>
255
+ </div>
256
+ )
257
+ }
258
+
259
+ export {
260
+ AllVariantsCarousel as default
261
+ }
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+ import React, { useCallback, useRef } from 'react'
3
+
4
+ import { cn } from '@libreapps/ui/util'
5
+
6
+ import {
7
+ type CarouselApi,
8
+ Carousel,
9
+ CarouselContent,
10
+ CarouselItem,
11
+ CarouselPrevious,
12
+ CarouselNext,
13
+ } from '@libreapps/ui/primitives'
14
+
15
+ import type { MultiFamilySelectorProps } from '../../../../types'
16
+
17
+ import FamilySlide from './slide'
18
+ import { FamilyCarouselState } from './state'
19
+
20
+ import { useCommerce } from '../../../..'
21
+
22
+ const FamilyCarousel: React.FC<MultiFamilySelectorProps> = ({
23
+ families,
24
+ clx='',
25
+ itemClx='',
26
+ itemOptions,
27
+ selectorOptions,
28
+ mediaConstraint={w: 250, h: 250},
29
+ mobile=false,
30
+ }) => {
31
+
32
+ const cmmc = useCommerce()
33
+ const stateRef = useRef<FamilyCarouselState>(
34
+ new FamilyCarouselState(families, cmmc.setCurrentItem.bind(cmmc))
35
+ )
36
+
37
+ const onSelect = useCallback((emblaApi: CarouselApi) => {
38
+ const index = emblaApi.selectedScrollSnap()
39
+ if (index !== -1) {
40
+ stateRef.current.setCurrentFamily(families[index].id)
41
+ }
42
+ }, [stateRef.current])
43
+
44
+ return (
45
+ <Carousel
46
+ className={cn('px-2', clx)}
47
+ options={{loop: false}}
48
+ onCarouselSelect={onSelect}
49
+ >
50
+ <CarouselContent>
51
+ {families.map((family) => {
52
+ const slideState = stateRef.current.getSlideState(family.id)
53
+ if (!slideState) {
54
+ throw new Error(`FamilyCarousel: no SlideState for family '${family.id}'!`)
55
+ }
56
+ return (
57
+ <CarouselItem key={family.id} className='p-2 flex flex-col justify-center items-center'>
58
+ <FamilySlide
59
+ family={family}
60
+ selectedItemRef={slideState}
61
+ selectSku={slideState.selectSku.bind(slideState)}
62
+ mediaConstraint={mediaConstraint}
63
+ options={itemOptions}
64
+ mobile={mobile}
65
+ clx={itemClx}
66
+ />
67
+ </CarouselItem>
68
+ )}
69
+ )}
70
+ </CarouselContent>
71
+ <CarouselPrevious className='left-1'/>
72
+ <CarouselNext className='right-1'/>
73
+ </Carousel>
74
+ )
75
+ }
76
+
77
+ export default FamilyCarousel
@@ -0,0 +1,83 @@
1
+ 'use client'
2
+ import React from 'react'
3
+
4
+ import {
5
+ ApplyTypography,
6
+ MediaStack
7
+ } from '@libreapps/ui/primitives'
8
+ import type { Dimensions } from '@libreapps/ui/types'
9
+
10
+ import type { ItemSelectorOptions, Family, LineItem, ItemSelector } from '../../../../types'
11
+ import { ButtonItemSelector } from '../../..'
12
+ import { cn } from '@libreapps/ui/util'
13
+
14
+ const FamilySlide: React.FC<Omit<ItemSelector, 'items'> & {
15
+ family: Family
16
+ mediaConstraint: Dimensions
17
+ clx?: string
18
+ mobile?: boolean,
19
+ options?: ItemSelectorOptions
20
+ }> = ({
21
+ family,
22
+ selectedItemRef,
23
+ selectSku,
24
+ mediaConstraint: cnst = {w: 200, h: 200},
25
+ clx='',
26
+ mobile=false,
27
+ options={}
28
+ }) => {
29
+
30
+ const titleSpec = 'title' in options ? options.title : 'short'
31
+ const showByline = 'showByline' in options ? options.showByline : true
32
+ const title = titleSpec === 'long' ?
33
+ family.title
34
+ :
35
+ (
36
+ titleSpec === 'short' ?
37
+ (family.titleShort ? family.titleShort : family.title )
38
+ :
39
+ undefined
40
+ )
41
+
42
+ const byline = (showByline && family.byline) ? family.byline : undefined
43
+ /*
44
+ let byline: string | undefined = undefined
45
+ if (showByline) {
46
+ if (family.byline) {
47
+ // if byline names another field thats a function, call it
48
+ if (family.byline in family && typeof (family as any)[family.byline] === 'function') {
49
+ byline = (family as any)[family.byline]() as string
50
+ }
51
+ else {
52
+ byline = family.byline
53
+ }
54
+ }
55
+ }
56
+ */
57
+
58
+ return (
59
+ <div className={cn('flex flex-col items-center gap-4', clx)} data-vaul-no-drag >
60
+ {title && (
61
+ <ApplyTypography className='flex flex-col items-center !gap-1 [&>*]:!m-0' data-vaul-no-drag >
62
+ <h6 className='font-bold text-lg'>{title}</h6>
63
+ {byline && (<p className={''}>{byline}</p>)}
64
+ </ApplyTypography>
65
+ )}
66
+ <MediaStack
67
+ media={selectedItemRef.item!}
68
+ constrainTo={cnst}
69
+ />
70
+ <ButtonItemSelector
71
+ items={family.products as LineItem[]}
72
+ selectedItemRef={selectedItemRef}
73
+ selectSku={selectSku}
74
+ scrollable={false}
75
+ mobile={mobile}
76
+ options={options}
77
+ itemClx='text-sm'
78
+ />
79
+ </div>
80
+ )
81
+ }
82
+
83
+ export default FamilySlide