@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.
- package/components/Icons.tsx +34 -34
- package/components/add-to-cart-widget.tsx +183 -183
- package/components/buy/buy-card.tsx +259 -259
- package/components/buy/carousel-buy-card.tsx +242 -242
- package/components/buy/multi-family/all-variants-carousel.tsx +261 -261
- package/components/buy/multi-family/family-carousel/index.tsx +77 -77
- package/components/buy/multi-family/family-carousel/slide.tsx +83 -83
- package/components/buy/multi-family/family-carousel/state.ts +87 -87
- package/components/buy/multi-family/index.ts +2 -2
- package/components/buy/single-family-selector.tsx +90 -90
- package/components/buy/title-and-byline.tsx +25 -25
- package/components/cart/cart-panel/cart-line-item.tsx +76 -76
- package/components/cart/cart-panel/index.tsx +154 -154
- package/components/cart/cart-panel/promo-code.tsx +106 -106
- package/components/cart/cart-panel/total-area.tsx +60 -60
- package/components/checkout/payment-step-form/card-icon-row.tsx +26 -26
- package/components/checkout/payment-step-form/card-icons/amex.tsx +32 -32
- package/components/checkout/payment-step-form/card-icons/diners-club.tsx +13 -13
- package/components/checkout/payment-step-form/card-icons/discover.tsx +25 -25
- package/components/checkout/payment-step-form/card-icons/jcb.tsx +26 -26
- package/components/checkout/payment-step-form/card-icons/mastercard.tsx +27 -27
- package/components/checkout/payment-step-form/card-icons/visa.tsx +25 -25
- package/components/checkout/payment-step-form/cc-button.tsx +17 -17
- package/components/checkout/payment-step-form/contact-form.tsx +49 -49
- package/components/checkout/payment-step-form/crypto-icons/btc.tsx +11 -11
- package/components/checkout/payment-step-form/crypto-icons/eth.tsx +20 -20
- package/components/checkout/payment-step-form/crypto-icons/usdt.tsx +13 -13
- package/components/checkout/payment-step-form/index.tsx +122 -122
- package/components/checkout/payment-step-form/methods/bank-transfer.tsx +79 -79
- package/components/checkout/payment-step-form/methods/card.tsx +232 -232
- package/components/checkout/payment-step-form/methods/crypto.tsx +227 -227
- package/components/checkout/payment-step-form/methods/index.ts +22 -22
- package/components/checkout/shipping-step-form.tsx +172 -172
- package/components/index.ts +11 -11
- package/components/item/product-card.tsx +48 -48
- package/components/item-selector/button.tsx +188 -188
- package/components/item-selector/carousel/index.tsx +197 -197
- package/components/item-selector/carousel/slider.tsx +40 -40
- package/components/item-selector/index.ts +5 -5
- package/components/item-selector/quantity-indicator.tsx +48 -48
- package/components/node-tabs/index.tsx +91 -91
- package/components/node-tabs/node-image.tsx +31 -31
- package/index.ts +12 -12
- package/package.json +60 -60
- package/service/context.tsx +45 -45
- package/service/debug.ts +41 -41
- package/service/impls/standalone/actual-line-item.ts +135 -135
- package/service/impls/standalone/get-instance.ts +64 -64
- package/service/impls/standalone/index.ts +579 -579
- package/service/impls/standalone/order/firebase.ts +14 -14
- package/service/impls/standalone/order/index.ts +128 -128
- package/service/impls/standalone/persistence.ts +33 -33
- package/service/path-utils.ts +25 -25
- package/service/sep.ts +6 -6
- package/tsconfig.json +10 -10
- package/types/README.md +1 -1
- package/types/category-node.ts +49 -49
- package/types/checkout.ts +46 -46
- package/types/commerce-config.ts +13 -13
- package/types/commerce-service.ts +127 -127
- package/types/family.ts +25 -25
- package/types/index.ts +15 -15
- package/types/item-selector.ts +96 -96
- package/types/line-item.ts +29 -29
- package/types/multi-family-selector-props.ts +19 -19
- package/types/product.ts +20 -20
- package/types/promo.ts +9 -9
- package/types/selection-ui-specifier.ts +51 -51
- package/types/string-mutator.ts +13 -13
- package/types/token-separators.ts +7 -7
- package/util/analytics.ts +20 -20
- package/util/countries.ts +195 -195
- package/util/error.ts +34 -34
- package/util/index.ts +70 -70
- package/util/item-selector-options-accessor.ts +34 -34
- package/util/line-item-ref.ts +23 -23
- package/util/multi-family-selector-options-accessor.ts +14 -14
- package/util/obs-string-mutator.ts +22 -22
- package/util/product-media-accessor.ts +58 -58
- package/util/promo-codes.ts +108 -108
- package/util/selection-ui-specifiers.ts +29 -29
- package/util/square-payment.ts +42 -42
- 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
|