@hanzo/commerce 7.7.0 → 7.7.2
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/dist/components/checkout/payment-step-form/methods/card.js +20 -2
- package/dist/components/checkout/payment-step-form/methods/card.js.map +1 -1
- package/package.json +38 -9
- package/billing.ts +0 -2
- package/client.ts +0 -783
- package/components/Icons.tsx +0 -35
- package/components/add-to-cart-widget.tsx +0 -195
- package/components/buy/buy-card.tsx +0 -269
- package/components/buy/carousel-buy-card.tsx +0 -243
- package/components/buy/multi-family/all-variants-carousel.tsx +0 -259
- package/components/buy/multi-family/family-carousel/index.tsx +0 -75
- package/components/buy/multi-family/family-carousel/slide.tsx +0 -82
- package/components/buy/multi-family/family-carousel/state.ts +0 -87
- package/components/buy/multi-family/index.ts +0 -2
- package/components/buy/single-family-selector.tsx +0 -91
- package/components/buy/title-and-byline.tsx +0 -26
- package/components/cart/cart-panel/cart-line-item.tsx +0 -77
- package/components/cart/cart-panel/index.tsx +0 -157
- package/components/cart/cart-panel/promo-code.tsx +0 -105
- package/components/cart/cart-panel/total-area.tsx +0 -61
- package/components/checkout/payment-step-form/card-icon-row.tsx +0 -26
- package/components/checkout/payment-step-form/card-icons/amex.tsx +0 -32
- package/components/checkout/payment-step-form/card-icons/diners-club.tsx +0 -13
- package/components/checkout/payment-step-form/card-icons/discover.tsx +0 -25
- package/components/checkout/payment-step-form/card-icons/jcb.tsx +0 -26
- package/components/checkout/payment-step-form/card-icons/mastercard.tsx +0 -27
- package/components/checkout/payment-step-form/card-icons/visa.tsx +0 -25
- package/components/checkout/payment-step-form/cc-button.tsx +0 -18
- package/components/checkout/payment-step-form/contact-form.tsx +0 -48
- package/components/checkout/payment-step-form/crypto-icons/btc.tsx +0 -11
- package/components/checkout/payment-step-form/crypto-icons/eth.tsx +0 -20
- package/components/checkout/payment-step-form/crypto-icons/usdt.tsx +0 -13
- package/components/checkout/payment-step-form/index.tsx +0 -113
- package/components/checkout/payment-step-form/methods/bank-transfer.tsx +0 -81
- package/components/checkout/payment-step-form/methods/card.tsx +0 -230
- package/components/checkout/payment-step-form/methods/crypto.tsx +0 -226
- package/components/checkout/payment-step-form/methods/index.ts +0 -23
- package/components/checkout/shipping-step-form.tsx +0 -171
- package/components/index.ts +0 -11
- package/components/item/product-card.tsx +0 -66
- package/components/item-selector/button.tsx +0 -213
- package/components/item-selector/carousel/index.tsx +0 -196
- package/components/item-selector/carousel/slider.tsx +0 -37
- package/components/item-selector/index.ts +0 -5
- package/components/item-selector/quantity-indicator.tsx +0 -50
- package/components/node-tabs/index.tsx +0 -90
- package/components/node-tabs/node-image.tsx +0 -31
- package/index.ts +0 -55
- package/metering.test.ts +0 -199
- package/metering.ts +0 -289
- package/product-card.test.tsx +0 -104
- package/service/context.tsx +0 -45
- package/service/debug.ts +0 -41
- package/service/impls/standalone/actual-line-item.ts +0 -136
- package/service/impls/standalone/get-instance.ts +0 -64
- package/service/impls/standalone/index.ts +0 -579
- package/service/impls/standalone/order/firebase.ts +0 -5
- package/service/impls/standalone/order/index.ts +0 -58
- package/service/impls/standalone/persistence.ts +0 -33
- package/service/path-utils.ts +0 -26
- package/service/sep.ts +0 -7
- package/tsconfig.json +0 -17
- package/types/README.md +0 -2
- package/types/category-node.ts +0 -50
- package/types/checkout.ts +0 -47
- package/types/commerce-config.ts +0 -13
- package/types/commerce-service.ts +0 -128
- package/types/family.ts +0 -26
- package/types/index.ts +0 -15
- package/types/item-selector.ts +0 -97
- package/types/line-item.ts +0 -29
- package/types/multi-family-selector-props.ts +0 -20
- package/types/product.ts +0 -21
- package/types/promo.ts +0 -10
- package/types/selection-ui-specifier.ts +0 -52
- package/types/string-mutator.ts +0 -14
- package/types/token-separators.ts +0 -7
- package/util/analytics.ts +0 -21
- package/util/countries.ts +0 -196
- package/util/error.ts +0 -34
- package/util/index.ts +0 -71
- package/util/item-selector-options-accessor.ts +0 -35
- package/util/line-item-ref.ts +0 -23
- package/util/multi-family-selector-options-accessor.ts +0 -15
- package/util/obs-string-mutator.ts +0 -22
- package/util/product-media-accessor.ts +0 -58
- package/util/promo-codes.ts +0 -106
- package/util/selection-ui-specifiers.ts +0 -30
- package/util/square-payment.ts +0 -50
- package/util/use-sync-sku-param-w-current-item.ts +0 -88
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { observer } from 'mobx-react-lite'
|
|
4
|
-
|
|
5
|
-
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
Label,
|
|
9
|
-
RadioGroup,
|
|
10
|
-
ScrollArea,
|
|
11
|
-
cn,
|
|
12
|
-
MediaStack,
|
|
13
|
-
} from '@hanzo/ui'
|
|
14
|
-
import type { Dimensions } from '@hanzo/ui/types'
|
|
15
|
-
|
|
16
|
-
import type { ItemSelectorProps, LineItem } from '../../types'
|
|
17
|
-
import { accessItemOptions, formatCurrencyValue } from '../../util'
|
|
18
|
-
|
|
19
|
-
import QuantityIndicator from './quantity-indicator'
|
|
20
|
-
|
|
21
|
-
const DEFAULT_CONSTRAINT = {h: 36, w: 72} // // Apple suggest 42px for clickability
|
|
22
|
-
|
|
23
|
-
// `ImageDef.rounded` is a corner of the radius scale, spelled by whoever wrote
|
|
24
|
-
// the catalog. It used to be pasted straight into a class — `rounded-${...}` —
|
|
25
|
-
// and Tailwind reads source text, so no such rule was ever generated: setting
|
|
26
|
-
// `rounded: 'full'` produced LESS rounding than leaving it unset, because the
|
|
27
|
-
// truthy branch handed back a class name with nothing behind it while the
|
|
28
|
-
// default branch at least said `rounded-sm`. Writing the scale out is what makes
|
|
29
|
-
// the extractor see it. Anything off the scale falls back to the default, which
|
|
30
|
-
// is what the old code did by accident for every value.
|
|
31
|
-
const ROUNDED: Record<string, string> = {
|
|
32
|
-
none: 'rounded-none',
|
|
33
|
-
sm: 'rounded-sm',
|
|
34
|
-
md: 'rounded-md',
|
|
35
|
-
lg: 'rounded-lg',
|
|
36
|
-
xl: 'rounded-xl',
|
|
37
|
-
'2xl': 'rounded-2xl',
|
|
38
|
-
'3xl': 'rounded-3xl',
|
|
39
|
-
full: 'rounded-full',
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const ImageRadioGroupItem = React.forwardRef<
|
|
43
|
-
React.ElementRef<typeof RadioGroupPrimitive.Item>,
|
|
44
|
-
Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, 'value' | 'id'> & {
|
|
45
|
-
item: LineItem,
|
|
46
|
-
constrainTo: Dimensions
|
|
47
|
-
}
|
|
48
|
-
>(({
|
|
49
|
-
item,
|
|
50
|
-
constrainTo,
|
|
51
|
-
className,
|
|
52
|
-
...props
|
|
53
|
-
}, ref) => {
|
|
54
|
-
|
|
55
|
-
const img = item.optionImg ? item.optionImg : item.img
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<RadioGroupPrimitive.Item
|
|
59
|
-
ref={ref}
|
|
60
|
-
data-vaul-no-drag
|
|
61
|
-
className={cn(
|
|
62
|
-
'ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
63
|
-
'focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
64
|
-
className,
|
|
65
|
-
'overflow-hidden',
|
|
66
|
-
(img?.rounded && ROUNDED[img.rounded]) || 'rounded-sm'
|
|
67
|
-
)}
|
|
68
|
-
{...props}
|
|
69
|
-
id={item.sku}
|
|
70
|
-
value={item.sku}
|
|
71
|
-
>
|
|
72
|
-
{img ? (
|
|
73
|
-
<MediaStack media={{img}} constrainTo={constrainTo} />
|
|
74
|
-
) : ( // placeholder so things align
|
|
75
|
-
<div style={{height: constrainTo.h, width: constrainTo.w}}/>
|
|
76
|
-
)}
|
|
77
|
-
</RadioGroupPrimitive.Item>
|
|
78
|
-
)
|
|
79
|
-
})
|
|
80
|
-
ImageRadioGroupItem.displayName = 'ImageRadioGroupItem'
|
|
81
|
-
|
|
82
|
-
const ButtonItemSelector: React.FC<ItemSelectorProps> = observer(({
|
|
83
|
-
items,
|
|
84
|
-
selectedItemRef: itemRef,
|
|
85
|
-
selectSku,
|
|
86
|
-
clx='',
|
|
87
|
-
itemClx='',
|
|
88
|
-
soleItemClx='',
|
|
89
|
-
scrollable=false,
|
|
90
|
-
mobile=false,
|
|
91
|
-
options={}
|
|
92
|
-
}) => {
|
|
93
|
-
|
|
94
|
-
const {
|
|
95
|
-
showPrice,
|
|
96
|
-
showQuantity,
|
|
97
|
-
showFamilyInOption,
|
|
98
|
-
buttonType,
|
|
99
|
-
horizButtons,
|
|
100
|
-
showButtonIfOnlyOne
|
|
101
|
-
} = accessItemOptions(options)
|
|
102
|
-
|
|
103
|
-
const showImage = buttonType !== 'text'
|
|
104
|
-
const showText = buttonType !== 'image'
|
|
105
|
-
const showBoth = buttonType === 'image-and-text'
|
|
106
|
-
|
|
107
|
-
const labelAndPrice = (item : LineItem) => (
|
|
108
|
-
(showFamilyInOption ? (item.familyTitle + ', ' + item.optionLabel) : item.optionLabel) +
|
|
109
|
-
(showPrice ? ((showFamilyInOption ? ': ' : ', ') + formatCurrencyValue(item.price)) : '')
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
const Item: React.FC<{
|
|
113
|
-
item: LineItem
|
|
114
|
-
selected: boolean
|
|
115
|
-
}> = observer(({
|
|
116
|
-
item,
|
|
117
|
-
selected
|
|
118
|
-
}) => {
|
|
119
|
-
|
|
120
|
-
const textClx = (selected) ? 'text-accent' : 'text-muted'
|
|
121
|
-
const cursorClx = (selected) ? 'hover:cursor-default' : 'hover:cursor-pointer'
|
|
122
|
-
const justifyClx = (showBoth) ? '' : 'justify-center'
|
|
123
|
-
// If no image, the Label must fill the entire button since it has to be
|
|
124
|
-
// clickable all the way to the border
|
|
125
|
-
const paddingClx = (showImage) ? (scrollable ? 'px-4 py-2' : 'px-2 py-2' ) : ''
|
|
126
|
-
let bgClx = ''
|
|
127
|
-
let borderClx = ''
|
|
128
|
-
if (scrollable) {
|
|
129
|
-
borderClx += (selected) ? 'border-foreground border ' : 'border-b border-muted-2 '
|
|
130
|
-
}
|
|
131
|
-
else if (!showImage) {
|
|
132
|
-
borderClx += 'border rounded-lg '
|
|
133
|
-
borderClx += (selected) ? 'border-foreground ' : 'border-muted-2 '
|
|
134
|
-
bgClx += 'hover:bg-level-2'
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const outerClx = ['h-10 flex items-center', justifyClx, paddingClx, bgClx, borderClx]
|
|
138
|
-
|
|
139
|
-
return (
|
|
140
|
-
<div className={cn(...outerClx, itemClx )} data-vaul-no-drag >
|
|
141
|
-
<ImageRadioGroupItem
|
|
142
|
-
item={item}
|
|
143
|
-
constrainTo={DEFAULT_CONSTRAINT}
|
|
144
|
-
className={cn(
|
|
145
|
-
cursorClx,
|
|
146
|
-
(scrollable ? '' : 'border-transparent border-2 data-[state=checked]:border-foreground'),
|
|
147
|
-
showBoth ? 'mr-2' : (showImage ? '' : 'hidden')
|
|
148
|
-
)}
|
|
149
|
-
/>
|
|
150
|
-
<Label htmlFor={item.sku} className={cn(
|
|
151
|
-
showQuantity ? 'flex items-center' : 'block',
|
|
152
|
-
textClx,
|
|
153
|
-
cursorClx,
|
|
154
|
-
(showImage ? '' : 'self-stretch w-full flex items-center justify-center px-3 py-2'),
|
|
155
|
-
(showText ? '' : 'hidden')
|
|
156
|
-
)}>
|
|
157
|
-
<div className={showQuantity ? 'grow' : ''}>{labelAndPrice(item)}</div>
|
|
158
|
-
{showQuantity && (
|
|
159
|
-
<QuantityIndicator
|
|
160
|
-
item={item}
|
|
161
|
-
clx='grow-0 shrink-0 h-[20px] ml-2'
|
|
162
|
-
iconClx={selected ? 'fill-foreground' : 'fill-muted'}
|
|
163
|
-
digitClx='font-semibold text-primary-fg leading-none font-sans text-xxs'
|
|
164
|
-
/>
|
|
165
|
-
)}
|
|
166
|
-
</Label>
|
|
167
|
-
</div>
|
|
168
|
-
)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
return showButtonIfOnlyOne || items.length > 1 ? (
|
|
172
|
-
<RadioGroup
|
|
173
|
-
className={cn(
|
|
174
|
-
(scrollable ? 'shrink min-h-0 gap-0' : (mobile ? 'gap-3' : 'gap-1')),
|
|
175
|
-
(horizButtons ? 'grid gap-1' : 'flex flex-col'),
|
|
176
|
-
(mobile && showText) ? 'min-w-pr-50' : '',
|
|
177
|
-
clx,
|
|
178
|
-
)}
|
|
179
|
-
// One column per item, same as the family tabs: the count is data, and
|
|
180
|
-
// data cannot be a class name. Interpolating it produced a rule only when
|
|
181
|
-
// the number happened to be 3, because two other files in this package
|
|
182
|
-
// spell that one literally — so this laid out correctly in a three-option
|
|
183
|
-
// selector and stacked in every other.
|
|
184
|
-
style={horizButtons ? { gridTemplateColumns: `repeat(${items.length}, minmax(0, 1fr))` } : undefined}
|
|
185
|
-
onValueChange={selectSku}
|
|
186
|
-
value={itemRef.item ? itemRef.item.sku : ''}
|
|
187
|
-
data-vaul-no-drag
|
|
188
|
-
>
|
|
189
|
-
{scrollable ? (
|
|
190
|
-
<ScrollArea className='mt-2 w-full h-full py-0 border border-muted-2' data-vaul-no-drag>
|
|
191
|
-
{items.map((item) => (
|
|
192
|
-
<Item item={item} selected={itemRef.item?.sku === item.sku} key={item.sku}/>
|
|
193
|
-
))}
|
|
194
|
-
</ScrollArea>
|
|
195
|
-
) : (<>
|
|
196
|
-
{items.map((item) => (
|
|
197
|
-
<Item item={item} selected={itemRef.item?.sku === item.sku} key={item.sku}/>
|
|
198
|
-
))}
|
|
199
|
-
</>)}
|
|
200
|
-
</RadioGroup>
|
|
201
|
-
) : (
|
|
202
|
-
<div className={cn(showQuantity ? 'flex items-center' : 'block', soleItemClx )}>
|
|
203
|
-
<div className={showQuantity ? 'grow' : ''}>
|
|
204
|
-
{labelAndPrice(items[0])}
|
|
205
|
-
</div>
|
|
206
|
-
{showQuantity && (
|
|
207
|
-
<QuantityIndicator item={items[0]} clx='grow-0 shrink-0 font-semibold text-sm leading-none px-2' />
|
|
208
|
-
)}
|
|
209
|
-
</div>
|
|
210
|
-
)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
export default ButtonItemSelector
|
|
@@ -1,196 +0,0 @@
|
|
|
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 {
|
|
7
|
-
cn,
|
|
8
|
-
ApplyTypography,
|
|
9
|
-
type CarouselOptions,
|
|
10
|
-
type CarouselApi,
|
|
11
|
-
Carousel,
|
|
12
|
-
CarouselContent,
|
|
13
|
-
CarouselItem,
|
|
14
|
-
CarouselPrevious,
|
|
15
|
-
CarouselNext,
|
|
16
|
-
MediaStack,
|
|
17
|
-
} from '@hanzo/ui'
|
|
18
|
-
import type { Dimensions } from '@hanzo/ui/types'
|
|
19
|
-
|
|
20
|
-
import type { ItemSelectorProps, LineItem } from '../../../types'
|
|
21
|
-
import { formatCurrencyValue, accessItemOptions } from '../../../util'
|
|
22
|
-
|
|
23
|
-
import QuantityIndicator from '../quantity-indicator'
|
|
24
|
-
import ItemCarouselSlider from './slider'
|
|
25
|
-
|
|
26
|
-
const DEFAULT_CONSTRAINT = {w: 250, h: 250}
|
|
27
|
-
|
|
28
|
-
interface CarouselItemSelectorPropsExt {
|
|
29
|
-
constrainTo: Dimensions
|
|
30
|
-
options?: CarouselOptions
|
|
31
|
-
/** Do not show Family and / or Item title and Price */
|
|
32
|
-
imageOnly?: boolean
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const ItemSlide: React.FC<{
|
|
36
|
-
item: LineItem,
|
|
37
|
-
constrainTo: Dimensions
|
|
38
|
-
clx?: string
|
|
39
|
-
}> = ({
|
|
40
|
-
item,
|
|
41
|
-
constrainTo,
|
|
42
|
-
clx=''
|
|
43
|
-
}) => (
|
|
44
|
-
<CarouselItem className={cn('p-2 flex flex-col justify-center items-center', clx)}>
|
|
45
|
-
<MediaStack media={item} constrainTo={constrainTo} className='my-4' />
|
|
46
|
-
</CarouselItem>
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
const CarouselItemSelector: React.FC<ItemSelectorProps> = observer(({
|
|
50
|
-
items,
|
|
51
|
-
selectSku,
|
|
52
|
-
selectedItemRef: itemRef,
|
|
53
|
-
clx='',
|
|
54
|
-
itemClx='',
|
|
55
|
-
options={},
|
|
56
|
-
ext={
|
|
57
|
-
options: {loop: true},
|
|
58
|
-
constrainTo: DEFAULT_CONSTRAINT,
|
|
59
|
-
imageOnly: false
|
|
60
|
-
} satisfies CarouselItemSelectorPropsExt
|
|
61
|
-
}) => {
|
|
62
|
-
|
|
63
|
-
const {
|
|
64
|
-
showPrice,
|
|
65
|
-
showQuantity,
|
|
66
|
-
showFamilyInOption,
|
|
67
|
-
showByline,
|
|
68
|
-
showSlider,
|
|
69
|
-
} = accessItemOptions(options)
|
|
70
|
-
|
|
71
|
-
const elbaApiRef = useRef<CarouselApi | undefined>(undefined)
|
|
72
|
-
const scrollToRef = useRef<((index: number) => void) | undefined>(undefined)
|
|
73
|
-
const dontRespondRef = useRef<boolean>(false)
|
|
74
|
-
|
|
75
|
-
const setApi = (api: CarouselApi) => {elbaApiRef.current = api}
|
|
76
|
-
const setScrollTo = (scrollTo: (index: number) => void) => { scrollToRef.current = scrollTo}
|
|
77
|
-
|
|
78
|
-
const carouselOptions = 'options' in ext ? ext.options : undefined
|
|
79
|
-
const constrainTo = 'constrainTo' in ext ? ext.constrainTo : DEFAULT_CONSTRAINT
|
|
80
|
-
const imageOnly = 'imageOnly' in ext ? ext.imageOnly : false
|
|
81
|
-
|
|
82
|
-
const onSelect = useCallback((emblaApi: CarouselApi) => {
|
|
83
|
-
if (dontRespondRef.current) {
|
|
84
|
-
dontRespondRef.current = false
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
const index = emblaApi.selectedScrollSnap()
|
|
88
|
-
if (index !== -1) {
|
|
89
|
-
selectSku(items[index].sku)
|
|
90
|
-
if (scrollToRef.current) {
|
|
91
|
-
scrollToRef.current(index)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
dontRespondRef.current = false
|
|
95
|
-
}, [scrollToRef.current ])
|
|
96
|
-
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
return reaction(
|
|
99
|
-
() => (itemRef.item),
|
|
100
|
-
(item) => {
|
|
101
|
-
if (elbaApiRef.current) {
|
|
102
|
-
const index = items.findIndex((el) => (el.sku === item?.sku))
|
|
103
|
-
if (index !== -1) {
|
|
104
|
-
dontRespondRef.current = true
|
|
105
|
-
elbaApiRef.current.scrollTo(index)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
)
|
|
110
|
-
}, [])
|
|
111
|
-
|
|
112
|
-
const optionLabel = () => (
|
|
113
|
-
showFamilyInOption ?
|
|
114
|
-
(itemRef.item?.familyTitle + ', ' + itemRef.item?.optionLabel)
|
|
115
|
-
:
|
|
116
|
-
itemRef.item?.optionLabel
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
const debugBorder = (c: 'r' | 'g' | 'b'): string => {
|
|
120
|
-
return ''
|
|
121
|
-
|
|
122
|
-
switch (c) {
|
|
123
|
-
case 'r': return ' border border-[#ffaaaa] '
|
|
124
|
-
case 'g': return ' border border-[#aaffaa] '
|
|
125
|
-
case 'b': return ' border border-[#aaaaff] '
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const onScrollIndexChange = (index: number) => {
|
|
130
|
-
dontRespondRef.current = true
|
|
131
|
-
selectSku(items![index].sku)
|
|
132
|
-
elbaApiRef.current?.scrollTo(index)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return (
|
|
136
|
-
<div className={cn('w-full flex flex-col items-center', clx)}>
|
|
137
|
-
<Carousel
|
|
138
|
-
options={showSlider ? {...carouselOptions, loop: false} : carouselOptions }
|
|
139
|
-
className={'w-full px-2' + debugBorder('r')}
|
|
140
|
-
onCarouselSelect={onSelect}
|
|
141
|
-
setApi={setApi}
|
|
142
|
-
>
|
|
143
|
-
<CarouselContent>
|
|
144
|
-
{items.map((item) => (
|
|
145
|
-
<ItemSlide key={item.sku} item={item} constrainTo={constrainTo} clx={itemClx} />
|
|
146
|
-
))}
|
|
147
|
-
</CarouselContent>
|
|
148
|
-
{!showSlider && (<>
|
|
149
|
-
<CarouselPrevious className='left-1'/>
|
|
150
|
-
<CarouselNext className='right-1'/>
|
|
151
|
-
</>)}
|
|
152
|
-
</Carousel>
|
|
153
|
-
|
|
154
|
-
{(!imageOnly && itemRef.item) && (<>
|
|
155
|
-
<ApplyTypography className='flex flex-col items-center [&>*]:!m-0 !gap-1 '>
|
|
156
|
-
<div className={
|
|
157
|
-
'flex items-center gap-1 [&>*]:!m-0 ' + debugBorder('g') +
|
|
158
|
-
(showFamilyInOption ? 'flex-col' : 'flex-row')
|
|
159
|
-
}>
|
|
160
|
-
<h6 className='font-semibold'>
|
|
161
|
-
{optionLabel() + (showPrice && !showFamilyInOption ? ',' : '')}
|
|
162
|
-
</h6>
|
|
163
|
-
<div className={
|
|
164
|
-
'flex items-center gap-1 [&>*]:!m-0 flex-row ' + debugBorder('b') +
|
|
165
|
-
(showFamilyInOption ? 'w-full justify-between' : '')
|
|
166
|
-
}>
|
|
167
|
-
{showPrice && (<p>{formatCurrencyValue(itemRef.item.price)}</p>)}
|
|
168
|
-
{showQuantity && (
|
|
169
|
-
<QuantityIndicator
|
|
170
|
-
item={itemRef.item}
|
|
171
|
-
clx='h-[22px] ml-4'
|
|
172
|
-
iconClx='fill-foreground'
|
|
173
|
-
digitClx='not-typography font-semibold text-primary-fg leading-none font-sans text-xs'
|
|
174
|
-
/>
|
|
175
|
-
)}
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
{showByline && itemRef.item.byline && (<p>{itemRef.item.byline}</p>)}
|
|
179
|
-
</ApplyTypography>
|
|
180
|
-
{showSlider && items.length > 1 && (
|
|
181
|
-
<ItemCarouselSlider
|
|
182
|
-
clx='mt-5 w-[320px]'
|
|
183
|
-
numStops={items.length}
|
|
184
|
-
setScrollTo={setScrollTo}
|
|
185
|
-
onIndexChange={onScrollIndexChange}
|
|
186
|
-
/>
|
|
187
|
-
)}
|
|
188
|
-
</>)}
|
|
189
|
-
</div>
|
|
190
|
-
)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
export {
|
|
194
|
-
type CarouselItemSelectorPropsExt,
|
|
195
|
-
CarouselItemSelector as default
|
|
196
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React, { useEffect, useState } from 'react'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
Slider,
|
|
6
|
-
} from '@hanzo/ui'
|
|
7
|
-
const ItemCarouselSlider: React.FC<{
|
|
8
|
-
clx?: string
|
|
9
|
-
setScrollTo: (scrollTo: (index: number) => void) => void
|
|
10
|
-
onIndexChange: (i: number) => void
|
|
11
|
-
numStops: number
|
|
12
|
-
}> = ({
|
|
13
|
-
clx='',
|
|
14
|
-
setScrollTo,
|
|
15
|
-
onIndexChange,
|
|
16
|
-
numStops,
|
|
17
|
-
}) => {
|
|
18
|
-
|
|
19
|
-
const [index, setIndex] = useState<number>(0)
|
|
20
|
-
useEffect(() => { setScrollTo(setIndex) }, [setScrollTo])
|
|
21
|
-
|
|
22
|
-
const onValueChange = (v: number[]) => { setIndex(v[0]); onIndexChange(v[0]) }
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<Slider
|
|
26
|
-
className={clx}
|
|
27
|
-
defaultValue={[0]}
|
|
28
|
-
min={0}
|
|
29
|
-
max={numStops - 1}
|
|
30
|
-
step={1}
|
|
31
|
-
value={[index]}
|
|
32
|
-
onValueChange={onValueChange}
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export default ItemCarouselSlider
|
|
@@ -1,50 +0,0 @@
|
|
|
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 {
|
|
8
|
-
cn,
|
|
9
|
-
} from '@hanzo/ui'
|
|
10
|
-
import type { LineItem } from '../../types'
|
|
11
|
-
|
|
12
|
-
// Generalize this.
|
|
13
|
-
const BagIcon: React.FC<LucideProps> = (props: LucideProps) => (
|
|
14
|
-
<svg fill="currentColor" viewBox='0 0 20 23' {...props}>
|
|
15
|
-
<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"/>
|
|
16
|
-
</svg>
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
const QuantityIndicator: React.FC<{
|
|
20
|
-
item: LineItem
|
|
21
|
-
clx?: string
|
|
22
|
-
iconClx?: string
|
|
23
|
-
digitClx?: string
|
|
24
|
-
}> = observer(({
|
|
25
|
-
item,
|
|
26
|
-
clx='',
|
|
27
|
-
iconClx='',
|
|
28
|
-
digitClx=''
|
|
29
|
-
}) => {
|
|
30
|
-
|
|
31
|
-
if (!item.isInCart) {
|
|
32
|
-
return <div />
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div className={cn('relative aspect-square flex items-center justify-center', clx)} >
|
|
37
|
-
<div className={cn(
|
|
38
|
-
'z-above-content flex flex-col justify-center items-center',
|
|
39
|
-
'absolute left-0 right-0 top-0 bottom-0',
|
|
40
|
-
digitClx
|
|
41
|
-
)}>
|
|
42
|
-
<div className='h-[1px] w-full' />
|
|
43
|
-
<div style={{color: 'black' /* tailwind bug */}}>{item.quantity}</div>
|
|
44
|
-
</div>
|
|
45
|
-
<BagIcon className={cn('relative -top-[12%] h-full ', iconClx, )} aria-hidden="true" />
|
|
46
|
-
</div>
|
|
47
|
-
)
|
|
48
|
-
}) // -top-[3px]
|
|
49
|
-
|
|
50
|
-
export default QuantityIndicator
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React, { useState } from 'react'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
ToggleGroup,
|
|
6
|
-
ToggleGroupItem,
|
|
7
|
-
cn,
|
|
8
|
-
type ToggleGroupSize,
|
|
9
|
-
} from '@hanzo/ui'
|
|
10
|
-
import type { CategoryNode, StringMutator, StringArrayMutator } from '../../types'
|
|
11
|
-
import NodeImage from './node-image'
|
|
12
|
-
|
|
13
|
-
const NodeTabs: React.FC<{
|
|
14
|
-
levelNodes: CategoryNode[]
|
|
15
|
-
mutator: StringMutator | StringArrayMutator
|
|
16
|
-
multiple?: boolean
|
|
17
|
-
className?: string
|
|
18
|
-
/** Layout the caller computes from data. A column count is a number, and a
|
|
19
|
-
* number cannot be a class name — Tailwind reads source text, so a template
|
|
20
|
-
* literal never becomes a rule. */
|
|
21
|
-
style?: React.CSSProperties
|
|
22
|
-
buttonClx?: string
|
|
23
|
-
itemClx?: string
|
|
24
|
-
mobile?: boolean
|
|
25
|
-
tabSize?: ToggleGroupSize
|
|
26
|
-
show?: 'image' | 'label' | 'image-and-label'
|
|
27
|
-
}> = ({
|
|
28
|
-
levelNodes,
|
|
29
|
-
mutator,
|
|
30
|
-
multiple=false,
|
|
31
|
-
buttonClx='',
|
|
32
|
-
itemClx='',
|
|
33
|
-
className='',
|
|
34
|
-
style,
|
|
35
|
-
mobile=false,
|
|
36
|
-
tabSize,
|
|
37
|
-
show='image-and-label'
|
|
38
|
-
}) => {
|
|
39
|
-
|
|
40
|
-
const [last, setLast] = useState<string | undefined>(undefined)
|
|
41
|
-
|
|
42
|
-
const handleChangeMultiple = (selected: string[]) => {
|
|
43
|
-
(mutator as StringArrayMutator).set(selected)
|
|
44
|
-
setLast(selected.length === 1 ? selected[0] : undefined)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const handleChangeSingle = (selected: string) => {
|
|
48
|
-
(mutator as StringMutator).set(selected)
|
|
49
|
-
if (selected) { setLast(selected) }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const val = multiple
|
|
53
|
-
? (mutator as StringArrayMutator).get()
|
|
54
|
-
: (mutator as StringMutator).get()
|
|
55
|
-
|
|
56
|
-
// The per-item `rounded` that used to ride here named a 5.x variant. The
|
|
57
|
-
// group owns the segmented look now, so passing it did nothing but say it did.
|
|
58
|
-
return (
|
|
59
|
-
<ToggleGroup
|
|
60
|
-
type={multiple ? 'multiple' : 'single'}
|
|
61
|
-
value={val}
|
|
62
|
-
variant='default'
|
|
63
|
-
size={tabSize ? tabSize : (mobile ? 'sm' : 'default')}
|
|
64
|
-
onValueChange={multiple ? handleChangeMultiple : handleChangeSingle}
|
|
65
|
-
className={className}
|
|
66
|
-
// The two type systems meet here. gui types `style` as a native view
|
|
67
|
-
// style; on web it hands the object straight to the DOM element, and a
|
|
68
|
-
// `grid-template-columns` written this way arrives as inline CSS
|
|
69
|
-
// (measured). The cast says which of the two platforms this is.
|
|
70
|
-
style={style as React.ComponentProps<typeof ToggleGroup>['style']}
|
|
71
|
-
>
|
|
72
|
-
{levelNodes.map((treeNode) => (
|
|
73
|
-
<ToggleGroupItem
|
|
74
|
-
key={treeNode.skuToken}
|
|
75
|
-
value={treeNode.skuToken}
|
|
76
|
-
disabled={(last && last === treeNode.skuToken || treeNode.skuToken === mutator.get())}
|
|
77
|
-
aria-label={`Select ${treeNode.label}`}
|
|
78
|
-
className={buttonClx}
|
|
79
|
-
>
|
|
80
|
-
<span className={cn('flex flex-row justify-center gap-1 h-6 items-center', itemClx)} >
|
|
81
|
-
{!(show === 'label') && (<NodeImage treeNode={treeNode} />) }
|
|
82
|
-
{(!(show === 'image') || !treeNode.img) && (<span className='whitespace-nowrap'>{treeNode.label}</span>)}
|
|
83
|
-
</span>
|
|
84
|
-
</ToggleGroupItem>
|
|
85
|
-
))}
|
|
86
|
-
</ToggleGroup>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export default NodeTabs
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import Image from 'next/image'
|
|
3
|
-
|
|
4
|
-
import type { CategoryNode } from '../../types'
|
|
5
|
-
|
|
6
|
-
const ICON_SIZE = 20
|
|
7
|
-
|
|
8
|
-
const NodeImage: React.FC<{
|
|
9
|
-
treeNode: CategoryNode
|
|
10
|
-
}> = ({
|
|
11
|
-
treeNode: {
|
|
12
|
-
img,
|
|
13
|
-
imgAR: ar,
|
|
14
|
-
label
|
|
15
|
-
}
|
|
16
|
-
}) => {
|
|
17
|
-
|
|
18
|
-
if (!img) { return null }
|
|
19
|
-
// treat as URL
|
|
20
|
-
return (typeof img === 'string') ? (
|
|
21
|
-
<Image
|
|
22
|
-
src={img as string}
|
|
23
|
-
alt={`Toggle ${label}`}
|
|
24
|
-
className={'block mr-1 '}
|
|
25
|
-
width={ar ? ar * ICON_SIZE : ICON_SIZE}
|
|
26
|
-
height={ICON_SIZE}
|
|
27
|
-
/>
|
|
28
|
-
) : (img as React.ReactNode)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export default NodeImage
|
package/index.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export * from './service/context'
|
|
2
|
-
export * from './components'
|
|
3
|
-
// Impl-dependent, so leave w impl
|
|
4
|
-
export type { StandaloneServiceOptions as ServiceOptions } from './service/impls/standalone'
|
|
5
|
-
export {
|
|
6
|
-
useSyncSkuParamWithCurrentItem,
|
|
7
|
-
getFacetValuesMutator,
|
|
8
|
-
formatCurrencyValue,
|
|
9
|
-
ProductMediaAccessor,
|
|
10
|
-
LineItemRef
|
|
11
|
-
} from './util'
|
|
12
|
-
|
|
13
|
-
export * from './util/selection-ui-specifiers'
|
|
14
|
-
|
|
15
|
-
// Commerce API client
|
|
16
|
-
export { Commerce, CommerceApiError, hanzoCommerce } from './client'
|
|
17
|
-
export type {
|
|
18
|
-
CommerceClientConfig,
|
|
19
|
-
Balance,
|
|
20
|
-
TierResponse,
|
|
21
|
-
Transaction,
|
|
22
|
-
Subscription,
|
|
23
|
-
Plan,
|
|
24
|
-
Payment,
|
|
25
|
-
UsageRecord,
|
|
26
|
-
Coupon,
|
|
27
|
-
CouponType,
|
|
28
|
-
CouponValidateResult,
|
|
29
|
-
Discount,
|
|
30
|
-
CheckoutItem,
|
|
31
|
-
CheckoutSessionRequest,
|
|
32
|
-
CheckoutSessionResponse,
|
|
33
|
-
PaymentMethod,
|
|
34
|
-
PaymentMethodType,
|
|
35
|
-
Referral,
|
|
36
|
-
Referrer,
|
|
37
|
-
Affiliate,
|
|
38
|
-
CreditGrant,
|
|
39
|
-
} from './client'
|
|
40
|
-
|
|
41
|
-
// Server-side usage metering (the one way every product meters to commerce)
|
|
42
|
-
export {
|
|
43
|
-
Metering,
|
|
44
|
-
DEFAULT_COMMERCE_URL,
|
|
45
|
-
identityFromHeaders,
|
|
46
|
-
HEADER_USER_ID,
|
|
47
|
-
HEADER_ORG_ID,
|
|
48
|
-
} from './metering'
|
|
49
|
-
export type {
|
|
50
|
-
MeteringConfig,
|
|
51
|
-
MeteringIdentity,
|
|
52
|
-
UsageEvent,
|
|
53
|
-
RecordResult,
|
|
54
|
-
AuthDecision,
|
|
55
|
-
} from './metering'
|