@hanzo/commerce 7.3.2 → 7.3.4

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 (83) hide show
  1. package/components/Icons.tsx +34 -34
  2. package/components/add-to-cart-widget.tsx +183 -183
  3. package/components/buy/buy-card.tsx +259 -259
  4. package/components/buy/carousel-buy-card.tsx +242 -242
  5. package/components/buy/multi-family/all-variants-carousel.tsx +261 -261
  6. package/components/buy/multi-family/family-carousel/index.tsx +77 -77
  7. package/components/buy/multi-family/family-carousel/slide.tsx +83 -83
  8. package/components/buy/multi-family/family-carousel/state.ts +87 -87
  9. package/components/buy/multi-family/index.ts +2 -2
  10. package/components/buy/single-family-selector.tsx +90 -90
  11. package/components/buy/title-and-byline.tsx +25 -25
  12. package/components/cart/cart-panel/cart-line-item.tsx +76 -76
  13. package/components/cart/cart-panel/index.tsx +154 -154
  14. package/components/cart/cart-panel/promo-code.tsx +106 -106
  15. package/components/cart/cart-panel/total-area.tsx +60 -60
  16. package/components/checkout/payment-step-form/card-icon-row.tsx +26 -26
  17. package/components/checkout/payment-step-form/card-icons/amex.tsx +32 -32
  18. package/components/checkout/payment-step-form/card-icons/diners-club.tsx +13 -13
  19. package/components/checkout/payment-step-form/card-icons/discover.tsx +25 -25
  20. package/components/checkout/payment-step-form/card-icons/jcb.tsx +26 -26
  21. package/components/checkout/payment-step-form/card-icons/mastercard.tsx +27 -27
  22. package/components/checkout/payment-step-form/card-icons/visa.tsx +25 -25
  23. package/components/checkout/payment-step-form/cc-button.tsx +17 -17
  24. package/components/checkout/payment-step-form/contact-form.tsx +49 -49
  25. package/components/checkout/payment-step-form/crypto-icons/btc.tsx +11 -11
  26. package/components/checkout/payment-step-form/crypto-icons/eth.tsx +20 -20
  27. package/components/checkout/payment-step-form/crypto-icons/usdt.tsx +13 -13
  28. package/components/checkout/payment-step-form/index.tsx +122 -122
  29. package/components/checkout/payment-step-form/methods/bank-transfer.tsx +79 -79
  30. package/components/checkout/payment-step-form/methods/card.tsx +232 -232
  31. package/components/checkout/payment-step-form/methods/crypto.tsx +227 -227
  32. package/components/checkout/payment-step-form/methods/index.ts +22 -22
  33. package/components/checkout/shipping-step-form.tsx +172 -172
  34. package/components/index.ts +11 -11
  35. package/components/item/product-card.tsx +48 -48
  36. package/components/item-selector/button.tsx +188 -188
  37. package/components/item-selector/carousel/index.tsx +197 -197
  38. package/components/item-selector/carousel/slider.tsx +40 -40
  39. package/components/item-selector/index.ts +5 -5
  40. package/components/item-selector/quantity-indicator.tsx +48 -48
  41. package/components/node-tabs/index.tsx +91 -91
  42. package/components/node-tabs/node-image.tsx +31 -31
  43. package/index.ts +12 -12
  44. package/package.json +60 -60
  45. package/service/context.tsx +45 -45
  46. package/service/debug.ts +41 -41
  47. package/service/impls/standalone/actual-line-item.ts +135 -135
  48. package/service/impls/standalone/get-instance.ts +64 -64
  49. package/service/impls/standalone/index.ts +579 -579
  50. package/service/impls/standalone/order/firebase.ts +14 -14
  51. package/service/impls/standalone/order/index.ts +128 -128
  52. package/service/impls/standalone/persistence.ts +33 -33
  53. package/service/path-utils.ts +25 -25
  54. package/service/sep.ts +6 -6
  55. package/tsconfig.json +10 -10
  56. package/types/README.md +1 -1
  57. package/types/category-node.ts +49 -49
  58. package/types/checkout.ts +46 -46
  59. package/types/commerce-config.ts +13 -13
  60. package/types/commerce-service.ts +127 -127
  61. package/types/family.ts +25 -25
  62. package/types/index.ts +15 -15
  63. package/types/item-selector.ts +96 -96
  64. package/types/line-item.ts +29 -29
  65. package/types/multi-family-selector-props.ts +19 -19
  66. package/types/product.ts +20 -20
  67. package/types/promo.ts +9 -9
  68. package/types/selection-ui-specifier.ts +51 -51
  69. package/types/string-mutator.ts +13 -13
  70. package/types/token-separators.ts +7 -7
  71. package/util/analytics.ts +20 -20
  72. package/util/countries.ts +195 -195
  73. package/util/error.ts +34 -34
  74. package/util/index.ts +70 -70
  75. package/util/item-selector-options-accessor.ts +34 -34
  76. package/util/line-item-ref.ts +23 -23
  77. package/util/multi-family-selector-options-accessor.ts +14 -14
  78. package/util/obs-string-mutator.ts +22 -22
  79. package/util/product-media-accessor.ts +58 -58
  80. package/util/promo-codes.ts +108 -108
  81. package/util/selection-ui-specifiers.ts +29 -29
  82. package/util/square-payment.ts +42 -42
  83. package/util/use-sync-sku-param-w-current-item.ts +88 -88
@@ -1,197 +1,197 @@
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 '@hanzo/ui/util'
7
- import type { Dimensions } from '@hanzo/ui/types'
8
-
9
- import {
10
- ApplyTypography,
11
- type CarouselOptions,
12
- type CarouselApi,
13
- Carousel,
14
- CarouselContent,
15
- CarouselItem,
16
- CarouselPrevious,
17
- CarouselNext,
18
- MediaStack,
19
- } from '@hanzo/ui/primitives'
20
-
21
- import type { ItemSelectorProps, LineItem } from '../../../types'
22
- import { formatCurrencyValue, accessItemOptions } from '../../../util'
23
-
24
- import QuantityIndicator from '../quantity-indicator'
25
- import ItemCarouselSlider from './slider'
26
-
27
- const DEFAULT_CONSTRAINT = {w: 250, h: 250}
28
-
29
- interface CarouselItemSelectorPropsExt {
30
- constrainTo: Dimensions
31
- options?: CarouselOptions
32
- /** Do not show Family and / or Item title and Price */
33
- imageOnly?: boolean
34
- }
35
-
36
- const ItemSlide: React.FC<{
37
- item: LineItem,
38
- constrainTo: Dimensions
39
- clx?: string
40
- }> = ({
41
- item,
42
- constrainTo,
43
- clx=''
44
- }) => (
45
- <CarouselItem className={cn('p-2 flex flex-col justify-center items-center', clx)}>
46
- <MediaStack media={item} constrainTo={constrainTo} clx='my-4' />
47
- </CarouselItem>
48
- )
49
-
50
- const CarouselItemSelector: React.FC<ItemSelectorProps> = observer(({
51
- items,
52
- selectSku,
53
- selectedItemRef: itemRef,
54
- clx='',
55
- itemClx='',
56
- options={},
57
- ext={
58
- options: {loop: true},
59
- constrainTo: DEFAULT_CONSTRAINT,
60
- imageOnly: false
61
- } satisfies CarouselItemSelectorPropsExt
62
- }) => {
63
-
64
- const {
65
- showPrice,
66
- showQuantity,
67
- showFamilyInOption,
68
- showByline,
69
- showSlider,
70
- } = accessItemOptions(options)
71
-
72
- const elbaApiRef = useRef<CarouselApi | undefined>(undefined)
73
- const scrollToRef = useRef<((index: number) => void) | undefined>(undefined)
74
- const dontRespondRef = useRef<boolean>(false)
75
-
76
- const setApi = (api: CarouselApi) => {elbaApiRef.current = api}
77
- const setScrollTo = (scrollTo: (index: number) => void) => { scrollToRef.current = scrollTo}
78
-
79
- const carouselOptions = 'options' in ext ? ext.options : undefined
80
- const constrainTo = 'constrainTo' in ext ? ext.constrainTo : DEFAULT_CONSTRAINT
81
- const imageOnly = 'imageOnly' in ext ? ext.imageOnly : false
82
-
83
- const onSelect = useCallback((emblaApi: CarouselApi) => {
84
- if (dontRespondRef.current) {
85
- dontRespondRef.current = false
86
- return
87
- }
88
- const index = emblaApi.selectedScrollSnap()
89
- if (index !== -1) {
90
- selectSku(items[index].sku)
91
- if (scrollToRef.current) {
92
- scrollToRef.current(index)
93
- }
94
- }
95
- dontRespondRef.current = false
96
- }, [scrollToRef.current ])
97
-
98
- useEffect(() => {
99
- return reaction(
100
- () => (itemRef.item),
101
- (item) => {
102
- if (elbaApiRef.current) {
103
- const index = items.findIndex((el) => (el.sku === item?.sku))
104
- if (index !== -1) {
105
- dontRespondRef.current = true
106
- elbaApiRef.current.scrollTo(index)
107
- }
108
- }
109
- }
110
- )
111
- }, [])
112
-
113
- const optionLabel = () => (
114
- showFamilyInOption ?
115
- (itemRef.item?.familyTitle + ', ' + itemRef.item?.optionLabel)
116
- :
117
- itemRef.item?.optionLabel
118
- )
119
-
120
- const debugBorder = (c: 'r' | 'g' | 'b'): string => {
121
- return ''
122
-
123
- switch (c) {
124
- case 'r': return ' border border-[#ffaaaa] '
125
- case 'g': return ' border border-[#aaffaa] '
126
- case 'b': return ' border border-[#aaaaff] '
127
- }
128
- }
129
-
130
- const onScrollIndexChange = (index: number) => {
131
- dontRespondRef.current = true
132
- selectSku(items![index].sku)
133
- elbaApiRef.current?.scrollTo(index)
134
- }
135
-
136
- return (
137
- <div className={cn('w-full flex flex-col items-center', clx)}>
138
- <Carousel
139
- options={showSlider ? {...carouselOptions, loop: false} : carouselOptions }
140
- className={'w-full px-2' + debugBorder('r')}
141
- onCarouselSelect={onSelect}
142
- setApi={setApi}
143
- >
144
- <CarouselContent>
145
- {items.map((item) => (
146
- <ItemSlide key={item.sku} item={item} constrainTo={constrainTo} clx={itemClx} />
147
- ))}
148
- </CarouselContent>
149
- {!showSlider && (<>
150
- <CarouselPrevious className='left-1'/>
151
- <CarouselNext className='right-1'/>
152
- </>)}
153
- </Carousel>
154
-
155
- {(!imageOnly && itemRef.item) && (<>
156
- <ApplyTypography className='flex flex-col items-center [&>*]:!m-0 !gap-1 '>
157
- <div className={
158
- 'flex items-center gap-1 [&>*]:!m-0 ' + debugBorder('g') +
159
- (showFamilyInOption ? 'flex-col' : 'flex-row')
160
- }>
161
- <h6 className='font-semibold'>
162
- {optionLabel() + (showPrice && !showFamilyInOption ? ',' : '')}
163
- </h6>
164
- <div className={
165
- 'flex items-center gap-1 [&>*]:!m-0 flex-row ' + debugBorder('b') +
166
- (showFamilyInOption ? 'w-full justify-between' : '')
167
- }>
168
- {showPrice && (<p>{formatCurrencyValue(itemRef.item.price)}</p>)}
169
- {showQuantity && (
170
- <QuantityIndicator
171
- item={itemRef.item}
172
- clx='h-[22px] ml-4'
173
- iconClx='fill-foreground'
174
- digitClx='not-typography font-semibold text-primary-fg leading-none font-sans text-xs'
175
- />
176
- )}
177
- </div>
178
- </div>
179
- {showByline && itemRef.item.byline && (<p>{itemRef.item.byline}</p>)}
180
- </ApplyTypography>
181
- {showSlider && items.length > 1 && (
182
- <ItemCarouselSlider
183
- clx='mt-5 w-[320px]'
184
- numStops={items.length}
185
- setScrollTo={setScrollTo}
186
- onIndexChange={onScrollIndexChange}
187
- />
188
- )}
189
- </>)}
190
- </div>
191
- )
192
- })
193
-
194
- export {
195
- type CarouselItemSelectorPropsExt,
196
- CarouselItemSelector as default
197
- }
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 '@hanzo/ui/util'
7
+ import type { Dimensions } from '@hanzo/ui/types'
8
+
9
+ import {
10
+ ApplyTypography,
11
+ type CarouselOptions,
12
+ type CarouselApi,
13
+ Carousel,
14
+ CarouselContent,
15
+ CarouselItem,
16
+ CarouselPrevious,
17
+ CarouselNext,
18
+ MediaStack,
19
+ } from '@hanzo/ui/primitives'
20
+
21
+ import type { ItemSelectorProps, LineItem } from '../../../types'
22
+ import { formatCurrencyValue, accessItemOptions } from '../../../util'
23
+
24
+ import QuantityIndicator from '../quantity-indicator'
25
+ import ItemCarouselSlider from './slider'
26
+
27
+ const DEFAULT_CONSTRAINT = {w: 250, h: 250}
28
+
29
+ interface CarouselItemSelectorPropsExt {
30
+ constrainTo: Dimensions
31
+ options?: CarouselOptions
32
+ /** Do not show Family and / or Item title and Price */
33
+ imageOnly?: boolean
34
+ }
35
+
36
+ const ItemSlide: React.FC<{
37
+ item: LineItem,
38
+ constrainTo: Dimensions
39
+ clx?: string
40
+ }> = ({
41
+ item,
42
+ constrainTo,
43
+ clx=''
44
+ }) => (
45
+ <CarouselItem className={cn('p-2 flex flex-col justify-center items-center', clx)}>
46
+ <MediaStack media={item} constrainTo={constrainTo} clx='my-4' />
47
+ </CarouselItem>
48
+ )
49
+
50
+ const CarouselItemSelector: React.FC<ItemSelectorProps> = observer(({
51
+ items,
52
+ selectSku,
53
+ selectedItemRef: itemRef,
54
+ clx='',
55
+ itemClx='',
56
+ options={},
57
+ ext={
58
+ options: {loop: true},
59
+ constrainTo: DEFAULT_CONSTRAINT,
60
+ imageOnly: false
61
+ } satisfies CarouselItemSelectorPropsExt
62
+ }) => {
63
+
64
+ const {
65
+ showPrice,
66
+ showQuantity,
67
+ showFamilyInOption,
68
+ showByline,
69
+ showSlider,
70
+ } = accessItemOptions(options)
71
+
72
+ const elbaApiRef = useRef<CarouselApi | undefined>(undefined)
73
+ const scrollToRef = useRef<((index: number) => void) | undefined>(undefined)
74
+ const dontRespondRef = useRef<boolean>(false)
75
+
76
+ const setApi = (api: CarouselApi) => {elbaApiRef.current = api}
77
+ const setScrollTo = (scrollTo: (index: number) => void) => { scrollToRef.current = scrollTo}
78
+
79
+ const carouselOptions = 'options' in ext ? ext.options : undefined
80
+ const constrainTo = 'constrainTo' in ext ? ext.constrainTo : DEFAULT_CONSTRAINT
81
+ const imageOnly = 'imageOnly' in ext ? ext.imageOnly : false
82
+
83
+ const onSelect = useCallback((emblaApi: CarouselApi) => {
84
+ if (dontRespondRef.current) {
85
+ dontRespondRef.current = false
86
+ return
87
+ }
88
+ const index = emblaApi.selectedScrollSnap()
89
+ if (index !== -1) {
90
+ selectSku(items[index].sku)
91
+ if (scrollToRef.current) {
92
+ scrollToRef.current(index)
93
+ }
94
+ }
95
+ dontRespondRef.current = false
96
+ }, [scrollToRef.current ])
97
+
98
+ useEffect(() => {
99
+ return reaction(
100
+ () => (itemRef.item),
101
+ (item) => {
102
+ if (elbaApiRef.current) {
103
+ const index = items.findIndex((el) => (el.sku === item?.sku))
104
+ if (index !== -1) {
105
+ dontRespondRef.current = true
106
+ elbaApiRef.current.scrollTo(index)
107
+ }
108
+ }
109
+ }
110
+ )
111
+ }, [])
112
+
113
+ const optionLabel = () => (
114
+ showFamilyInOption ?
115
+ (itemRef.item?.familyTitle + ', ' + itemRef.item?.optionLabel)
116
+ :
117
+ itemRef.item?.optionLabel
118
+ )
119
+
120
+ const debugBorder = (c: 'r' | 'g' | 'b'): string => {
121
+ return ''
122
+
123
+ switch (c) {
124
+ case 'r': return ' border border-[#ffaaaa] '
125
+ case 'g': return ' border border-[#aaffaa] '
126
+ case 'b': return ' border border-[#aaaaff] '
127
+ }
128
+ }
129
+
130
+ const onScrollIndexChange = (index: number) => {
131
+ dontRespondRef.current = true
132
+ selectSku(items![index].sku)
133
+ elbaApiRef.current?.scrollTo(index)
134
+ }
135
+
136
+ return (
137
+ <div className={cn('w-full flex flex-col items-center', clx)}>
138
+ <Carousel
139
+ options={showSlider ? {...carouselOptions, loop: false} : carouselOptions }
140
+ className={'w-full px-2' + debugBorder('r')}
141
+ onCarouselSelect={onSelect}
142
+ setApi={setApi}
143
+ >
144
+ <CarouselContent>
145
+ {items.map((item) => (
146
+ <ItemSlide key={item.sku} item={item} constrainTo={constrainTo} clx={itemClx} />
147
+ ))}
148
+ </CarouselContent>
149
+ {!showSlider && (<>
150
+ <CarouselPrevious className='left-1'/>
151
+ <CarouselNext className='right-1'/>
152
+ </>)}
153
+ </Carousel>
154
+
155
+ {(!imageOnly && itemRef.item) && (<>
156
+ <ApplyTypography className='flex flex-col items-center [&>*]:!m-0 !gap-1 '>
157
+ <div className={
158
+ 'flex items-center gap-1 [&>*]:!m-0 ' + debugBorder('g') +
159
+ (showFamilyInOption ? 'flex-col' : 'flex-row')
160
+ }>
161
+ <h6 className='font-semibold'>
162
+ {optionLabel() + (showPrice && !showFamilyInOption ? ',' : '')}
163
+ </h6>
164
+ <div className={
165
+ 'flex items-center gap-1 [&>*]:!m-0 flex-row ' + debugBorder('b') +
166
+ (showFamilyInOption ? 'w-full justify-between' : '')
167
+ }>
168
+ {showPrice && (<p>{formatCurrencyValue(itemRef.item.price)}</p>)}
169
+ {showQuantity && (
170
+ <QuantityIndicator
171
+ item={itemRef.item}
172
+ clx='h-[22px] ml-4'
173
+ iconClx='fill-foreground'
174
+ digitClx='not-typography font-semibold text-primary-fg leading-none font-sans text-xs'
175
+ />
176
+ )}
177
+ </div>
178
+ </div>
179
+ {showByline && itemRef.item.byline && (<p>{itemRef.item.byline}</p>)}
180
+ </ApplyTypography>
181
+ {showSlider && items.length > 1 && (
182
+ <ItemCarouselSlider
183
+ clx='mt-5 w-[320px]'
184
+ numStops={items.length}
185
+ setScrollTo={setScrollTo}
186
+ onIndexChange={onScrollIndexChange}
187
+ />
188
+ )}
189
+ </>)}
190
+ </div>
191
+ )
192
+ })
193
+
194
+ export {
195
+ type CarouselItemSelectorPropsExt,
196
+ CarouselItemSelector as default
197
+ }
@@ -1,40 +1,40 @@
1
- 'use client'
2
- import React, { useEffect, useState } from 'react'
3
-
4
- import { Slider } from '@hanzo/ui/primitives'
5
-
6
- const ItemCarouselSlider: React.FC<{
7
- clx?: string
8
- setScrollTo: (scrollTo: (index: number) => void) => void
9
- onIndexChange: (i: number) => void
10
- numStops: number
11
- }> = ({
12
- clx='',
13
- setScrollTo,
14
- onIndexChange,
15
- numStops,
16
- }) => {
17
-
18
- const [index, setIndex] = useState<number>(0)
19
- useEffect(() => { setScrollTo(setIndex) }, [setScrollTo])
20
-
21
- const onValueChange = (v: number[]) => { setIndex(v[0]); onIndexChange(v[0]) }
22
-
23
- return (
24
- <Slider
25
- className={clx}
26
- thumbClx='w-8 border-muted border-2 bg-level-1 focus-visible:ring-0 focus-visible:ring-offset-0 transition-none'
27
- trackBgClx='bg-level-3'
28
- rangeBgClx='bg-level-3'
29
- thumbSlidingClx='bg-muted-2'
30
- defaultValue={[0]}
31
- min={0}
32
- max={numStops - 1}
33
- step={1}
34
- value={[index]}
35
- onValueChange={onValueChange}
36
- />
37
- )
38
- }
39
-
40
- export default ItemCarouselSlider
1
+ 'use client'
2
+ import React, { useEffect, useState } from 'react'
3
+
4
+ import { Slider } from '@hanzo/ui/primitives'
5
+
6
+ const ItemCarouselSlider: React.FC<{
7
+ clx?: string
8
+ setScrollTo: (scrollTo: (index: number) => void) => void
9
+ onIndexChange: (i: number) => void
10
+ numStops: number
11
+ }> = ({
12
+ clx='',
13
+ setScrollTo,
14
+ onIndexChange,
15
+ numStops,
16
+ }) => {
17
+
18
+ const [index, setIndex] = useState<number>(0)
19
+ useEffect(() => { setScrollTo(setIndex) }, [setScrollTo])
20
+
21
+ const onValueChange = (v: number[]) => { setIndex(v[0]); onIndexChange(v[0]) }
22
+
23
+ return (
24
+ <Slider
25
+ className={clx}
26
+ thumbClx='w-8 border-muted border-2 bg-level-1 focus-visible:ring-0 focus-visible:ring-offset-0 transition-none'
27
+ trackBgClx='bg-level-3'
28
+ rangeBgClx='bg-level-3'
29
+ thumbSlidingClx='bg-muted-2'
30
+ defaultValue={[0]}
31
+ min={0}
32
+ max={numStops - 1}
33
+ step={1}
34
+ value={[index]}
35
+ onValueChange={onValueChange}
36
+ />
37
+ )
38
+ }
39
+
40
+ export default ItemCarouselSlider
@@ -1,5 +1,5 @@
1
- export { default as ButtonItemSelector } from './button'
2
- export {
3
- default as CarouselItemSelector,
4
- type CarouselItemSelectorPropsExt
5
- } from './carousel'
1
+ export { default as ButtonItemSelector } from './button'
2
+ export {
3
+ default as CarouselItemSelector,
4
+ type CarouselItemSelectorPropsExt
5
+ } from './carousel'
@@ -1,48 +1,48 @@
1
- 'use client'
2
- import React from 'react'
3
- import { observer } from 'mobx-react-lite'
4
-
5
- import { type LucideProps } from 'lucide-react'
6
-
7
- import { cn } from '@hanzo/ui/util'
8
- import type { LineItem } from '../../types'
9
-
10
- // Generalize this.
11
- const BagIcon: React.FC<LucideProps> = (props: LucideProps) => (
12
- <svg fill="currentColor" viewBox='0 0 20 23' {...props}>
13
- <path fillRule="evenodd" d="M5 5a5 5 0 0 1 10 0v1h-2V5a3 3 0 1 0-6 0v1H5V5Zm0 1v4h2V6h6v4h2V6h3.5a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1h-17a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1H5Z" clipRule="evenodd"/>
14
- </svg>
15
- )
16
-
17
- const QuantityIndicator: React.FC<{
18
- item: LineItem
19
- clx?: string
20
- iconClx?: string
21
- digitClx?: string
22
- }> = observer(({
23
- item,
24
- clx='',
25
- iconClx='',
26
- digitClx=''
27
- }) => {
28
-
29
- if (!item.isInCart) {
30
- return <div />
31
- }
32
-
33
- return (
34
- <div className={cn('relative aspect-square flex items-center justify-center', clx)} >
35
- <div className={cn(
36
- 'z-above-content flex flex-col justify-center items-center',
37
- 'absolute left-0 right-0 top-0 bottom-0',
38
- digitClx
39
- )}>
40
- <div className='h-[1px] w-full' />
41
- <div style={{color: 'black' /* tailwind bug */}}>{item.quantity}</div>
42
- </div>
43
- <BagIcon className={cn('relative -top-[12%] h-full ', iconClx, )} aria-hidden="true" />
44
- </div>
45
- )
46
- }) // -top-[3px]
47
-
48
- export default QuantityIndicator
1
+ 'use client'
2
+ import React from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import { type LucideProps } from 'lucide-react'
6
+
7
+ import { cn } from '@hanzo/ui/util'
8
+ import type { LineItem } from '../../types'
9
+
10
+ // Generalize this.
11
+ const BagIcon: React.FC<LucideProps> = (props: LucideProps) => (
12
+ <svg fill="currentColor" viewBox='0 0 20 23' {...props}>
13
+ <path fillRule="evenodd" d="M5 5a5 5 0 0 1 10 0v1h-2V5a3 3 0 1 0-6 0v1H5V5Zm0 1v4h2V6h6v4h2V6h3.5a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1h-17a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1H5Z" clipRule="evenodd"/>
14
+ </svg>
15
+ )
16
+
17
+ const QuantityIndicator: React.FC<{
18
+ item: LineItem
19
+ clx?: string
20
+ iconClx?: string
21
+ digitClx?: string
22
+ }> = observer(({
23
+ item,
24
+ clx='',
25
+ iconClx='',
26
+ digitClx=''
27
+ }) => {
28
+
29
+ if (!item.isInCart) {
30
+ return <div />
31
+ }
32
+
33
+ return (
34
+ <div className={cn('relative aspect-square flex items-center justify-center', clx)} >
35
+ <div className={cn(
36
+ 'z-above-content flex flex-col justify-center items-center',
37
+ 'absolute left-0 right-0 top-0 bottom-0',
38
+ digitClx
39
+ )}>
40
+ <div className='h-[1px] w-full' />
41
+ <div style={{color: 'black' /* tailwind bug */}}>{item.quantity}</div>
42
+ </div>
43
+ <BagIcon className={cn('relative -top-[12%] h-full ', iconClx, )} aria-hidden="true" />
44
+ </div>
45
+ )
46
+ }) // -top-[3px]
47
+
48
+ export default QuantityIndicator