@hanzo/commerce 6.1.13 → 6.2.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 (55) hide show
  1. package/components/buy/_to_deprecate_select-family-item-card.tsx +3 -4
  2. package/components/buy/_to_deprecate_select-family-item-panel.tsx +4 -5
  3. package/components/buy/add-to-cart-widget.tsx +43 -32
  4. package/components/buy/buy-button.tsx +1 -1
  5. package/components/buy/buy-card.tsx +17 -26
  6. package/components/buy/buy-drawer.tsx +6 -25
  7. package/components/buy/carousel-buy-card.tsx +145 -194
  8. package/components/buy/foo.txt +27 -0
  9. package/components/cart/cart-panel/cart-line-item.tsx +1 -1
  10. package/components/checkout/payment-step-form/contact-form.tsx +2 -2
  11. package/components/checkout/payment-step-form/methods/crypto.tsx +1 -2
  12. package/components/checkout/shipping-step-form.tsx +5 -5
  13. package/components/index.ts +1 -3
  14. package/components/item-selector/button.tsx +175 -0
  15. package/components/item-selector/carousel.tsx +162 -0
  16. package/components/item-selector/index.ts +5 -0
  17. package/components/quantity-indicator.tsx +48 -0
  18. package/components/select-family/family-carousel/index.tsx +47 -38
  19. package/components/select-family/family-carousel/slide.tsx +83 -0
  20. package/components/select-family/family-carousel/state.ts +83 -0
  21. package/components/select-family/level-nodes-widget/index.tsx +2 -2
  22. package/components/select-family/level-nodes-widget/node-image.tsx +2 -2
  23. package/index.ts +7 -7
  24. package/package.json +7 -4
  25. package/service/context.tsx +8 -7
  26. package/service/debug.ts +41 -0
  27. package/service/get-instance.ts +3 -0
  28. package/service/impls/standalone/actual-line-item.ts +5 -7
  29. package/service/impls/standalone/index.ts +11 -3
  30. package/service/impls/standalone/standalone-service.ts +133 -38
  31. package/service/path-utils.ts +26 -0
  32. package/service/sep.ts +7 -0
  33. package/types/category-node.ts +38 -0
  34. package/types/commerce-service.ts +44 -12
  35. package/types/family.ts +4 -1
  36. package/types/index.ts +3 -2
  37. package/types/item-selector.ts +29 -14
  38. package/types/product.ts +5 -1
  39. package/types/selection-ui-specifier.ts +36 -0
  40. package/types/token-separators.ts +7 -0
  41. package/util/error.ts +34 -0
  42. package/util/index.ts +4 -1
  43. package/util/product-media-accessor.ts +58 -0
  44. package/util/promo-codes.ts +1 -2
  45. package/util/selection-ui-specifiers.ts +34 -0
  46. package/util/use-sync-sku-param-w-current-item.ts +2 -2
  47. package/components/select-family/family-carousel/family-slide.tsx +0 -36
  48. package/components/select-product/carousel-selector.tsx +0 -112
  49. package/components/select-product/image-selector.tsx +0 -162
  50. package/components/select-product/radio-selector.tsx +0 -125
  51. package/service/impls/index.ts +0 -3
  52. package/types/buy-ui-spec.ts +0 -8
  53. package/types/tree-node.ts +0 -24
  54. package/util/buy-ui-conf.ts +0 -35
  55. package/util/sep.ts +0 -5
package/types/family.ts CHANGED
@@ -6,9 +6,12 @@ interface Family {
6
6
  title: string // Minted Bar, Lux Elite Card
7
7
  titleShort?: string // Elite
8
8
  parentTitle?: string // Lux Gold, Lux Credit
9
+ byline?: string // literal text, or name of function on 'this' object that returns a string
9
10
  desc?: string
10
11
  img?: ImageDef,
11
- // inbound they're Products and then interally they become LineItems
12
+ // inbound to the service, they're Products
13
+ // when it's initialized, interally they become LineItems
14
+ // always ok to cast up to LineItem thereafter
12
15
  products: Product[]
13
16
  }
14
17
 
package/types/index.ts CHANGED
@@ -3,10 +3,11 @@ export type { default as Family } from './family'
3
3
  export type { default as CommerceService } from './commerce-service'
4
4
  export type { default as Product } from './product'
5
5
  export type { default as Promo } from './promo'
6
- export type { default as BuyUISpec } from './buy-ui-spec'
6
+ export type { default as TokenSeparators } from './token-separators'
7
7
 
8
8
  export * from './checkout'
9
9
  export * from './item-selector'
10
10
  export * from './line-item'
11
- export * from './tree-node'
11
+ export * from './category-node'
12
12
  export * from './string-mutator'
13
+ export * from './selection-ui-specifier'
@@ -6,31 +6,45 @@ interface ItemSelector {
6
6
  selectSku: (sku: string) => void
7
7
  }
8
8
 
9
- interface _ItemSelectorCompProps {
10
- clx?: string
11
- itemClx?: string
12
- soleItemClx?: string
13
- /** type-specific props for ItemSelector's.
14
- * eg, Carousel options.
15
- */
16
- ext?: any
17
- /** List selectors will scroll. Used internally */
18
- scrollList: boolean
9
+ type ItemSelectorOptions = {
19
10
  /**
20
11
  * Whether the item label includes the Family name.
21
12
  * eg, 'Minted Bar, 1oz' vs '1oz'
22
- * default: true in 'allVariants' mode and false otherwise.
23
- * This overrides
13
+ * default: false.
24
14
  */
25
15
  showFamily?: boolean
26
16
  /**
27
17
  * Show the current item quantity along with title and price.
28
18
  * default: false
29
19
  */
30
- showQuantity?: boolean
20
+ showQuantity?: boolean
21
+
22
+ /** default: true */
23
+ showPrice?: boolean
24
+
25
+ /** default: false */
26
+ imageButtons?: boolean
27
+
28
+ /** default: false */
29
+ horizButtons?: boolean
30
+ }
31
+
32
+ interface _ItemSelectorCompProps {
33
+ clx?: string
34
+ itemClx?: string
35
+ soleItemClx?: string
36
+ /** type-specific props for ItemSelector's.
37
+ * eg, Carousel options.
38
+ */
39
+ ext?: any
40
+ /** List selectors will scroll. Used internally */
41
+ scrollable: boolean
42
+ options?: ItemSelectorOptions
43
+
44
+ mobile?: boolean
31
45
  }
32
46
 
33
- type ItemSelectorCompProps = Omit<_ItemSelectorCompProps, 'scrollList'>
47
+ type ItemSelectorCompProps = Omit<_ItemSelectorCompProps, 'scrollable'>
34
48
 
35
49
  interface ItemSelectorProps extends
36
50
  ItemSelector, _ItemSelectorCompProps {}
@@ -38,5 +52,6 @@ interface ItemSelectorProps extends
38
52
  export {
39
53
  type ItemSelector,
40
54
  type ItemSelectorCompProps,
55
+ type ItemSelectorOptions,
41
56
  type ItemSelectorProps
42
57
  }
package/types/product.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { MediaStackDef } from '@hanzo/ui/types'
1
+ import type { ImageDef, MediaStackDef } from '@hanzo/ui/types'
2
2
 
3
3
  interface Product extends MediaStackDef {
4
4
  id: string // DB index // not a logical aspect of our domain. may not be necessary at all
@@ -7,8 +7,12 @@ interface Product extends MediaStackDef {
7
7
  optionLabel: string
8
8
  familyId: string // skuPath, eg LXB-AU-B
9
9
  familyTitle: string
10
+ byline?: string
10
11
  desc?: string
11
12
  price: number
13
+ // image to by uses as an image button in a list of choices
14
+ // defaults to (MediaStackDef's) img
15
+ optionImg?: ImageDef
12
16
  }
13
17
 
14
18
  export {
@@ -0,0 +1,36 @@
1
+ import type { ItemSelectorOptions } from './item-selector'
2
+ type SelectorType = 'buttons' | 'carousel'
3
+
4
+ interface FamilyCarouselSlideOptions extends ItemSelectorOptions {
5
+ /**
6
+ * default: short
7
+ * 'short': product.shortTitle if defined
8
+ * 'long': always us product.title, even if .shortTitle is defined
9
+ * 'none': do not show title AND BYLINE
10
+ */
11
+ title?: 'none' | 'long' | 'short'
12
+ /** default: true */
13
+ showByline?: boolean
14
+ }
15
+
16
+ interface SelectionUISpecifier {
17
+ multiFamily?: {
18
+ familySelector: 'carousel'
19
+ /** default: true */
20
+ showParentTitle: boolean
21
+ slide: {
22
+ type: 'buttons'
23
+ options?: FamilyCarouselSlideOptions
24
+ }
25
+ }
26
+ singleFamily?: {
27
+ type: SelectorType
28
+ options?: ItemSelectorOptions
29
+ }
30
+ }
31
+
32
+ export {
33
+ type SelectionUISpecifier,
34
+ type FamilyCarouselSlideOptions,
35
+ type SelectorType
36
+ }
@@ -0,0 +1,7 @@
1
+ interface TokenSeparators {
2
+ readonly tok: string
3
+ readonly subTok: string
4
+ readonly decimal: string
5
+ }
6
+
7
+ export { type TokenSeparators as default }
package/util/error.ts ADDED
@@ -0,0 +1,34 @@
1
+
2
+ // https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript
3
+
4
+ type ErrorWithMessage = {
5
+ message: string
6
+ }
7
+
8
+ const isErrorWithMessage = (error: unknown): error is ErrorWithMessage => {
9
+ return (
10
+ typeof error === 'object' &&
11
+ error !== null &&
12
+ 'message' in error &&
13
+ typeof (error as Record<string, unknown>).message === 'string'
14
+ )
15
+ }
16
+
17
+ const toErrorWithMessage = (maybeError: unknown): ErrorWithMessage => {
18
+
19
+ if (isErrorWithMessage(maybeError)) {
20
+ return maybeError
21
+ }
22
+
23
+ try {
24
+ return new Error(JSON.stringify(maybeError))
25
+ }
26
+ catch {
27
+ // fallback in case there's an error stringifying the maybeError
28
+ // like with circular references for example.
29
+ return new Error(String(maybeError))
30
+ }
31
+ }
32
+
33
+ export const getErrorMessage = (error: unknown) => (toErrorWithMessage(error).message)
34
+
package/util/index.ts CHANGED
@@ -61,5 +61,8 @@ export const getFacetValuesMutator = (level: number, cmmc: CommerceService): Str
61
61
  export { default as useSyncSkuParamWithCurrentItem } from './use-sync-sku-param-w-current-item'
62
62
  export { default as processSquareCardPayment } from './square-payment'
63
63
  export { default as ObsStringMutator } from './obs-string-mutator'
64
+ export { default as ProductMediaAccessor } from './product-media-accessor'
64
65
 
65
- export { default as getUISpecFromPath } from './buy-ui-conf'
66
+ export * from './selection-ui-specifiers'
67
+
68
+ export { getErrorMessage } from './error'
@@ -0,0 +1,58 @@
1
+ import type { AnimationDef, ImageDef, MediaStackDef, VideoDef } from '@hanzo/ui/types'
2
+
3
+ interface FamilyMediaData {
4
+ img?: Record<string, ImageDef>
5
+ video?: Record<string, VideoDef>
6
+ anim?: Record<string, AnimationDef>
7
+ optionImg?: Record<string, ImageDef>
8
+ }
9
+
10
+ class ProductMediaAccessor {
11
+
12
+ _data: FamilyMediaData
13
+
14
+ constructor(data: FamilyMediaData) {
15
+ this._data = data
16
+ }
17
+
18
+ img = (key: string): ImageDef | undefined => (
19
+ (this._data.img) ? this._data.img[key] : undefined
20
+ )
21
+
22
+ spreadableImg = (key: string): { img?: ImageDef } => (
23
+ (this._data.img) ? {img: this._data.img[key]} : {}
24
+ )
25
+
26
+ video = (key: string): VideoDef | undefined => (
27
+ (this._data.video) ? this._data.video[key] : undefined
28
+ )
29
+
30
+ spreadableVideo = (key: string): { video?: VideoDef } => (
31
+ (this._data.video) ? {video: this._data.video[key]} : {}
32
+ )
33
+
34
+ anim = (key: string): AnimationDef | undefined => (
35
+ (this._data.anim) ? this._data.anim[key] : undefined
36
+ )
37
+
38
+ spreadableAnim = (key: string): { animation?: AnimationDef } => (
39
+ (this._data.anim) ? {animation: this._data.anim[key]} : {}
40
+ )
41
+
42
+ mediaStack = (key: string): MediaStackDef => ({
43
+ ...this.spreadableImg(key),
44
+ ...this.spreadableVideo(key),
45
+ ...this.spreadableAnim(key)
46
+ })
47
+
48
+
49
+ optionImg = (key: string): ImageDef | undefined => (
50
+ (this._data.optionImg) ? this._data.optionImg[key] : undefined
51
+ )
52
+
53
+ spreadableOptionImg = (key: string): { optionImg?: ImageDef } => {
54
+ return (this._data.optionImg) ? { optionImg: this._data.optionImg[key]} : {}
55
+ }
56
+ }
57
+
58
+ export default ProductMediaAccessor
@@ -42,6 +42,5 @@ const getPromoFromApi = async (code: string) => {
42
42
  }
43
43
 
44
44
  export {
45
- getPromoFromApi as default,
46
- PROMO_CODES
45
+ getPromoFromApi as default
47
46
  }
@@ -0,0 +1,34 @@
1
+ import type { SelectionUISpecifier } from '../types'
2
+ import sep from '../service/sep'
3
+
4
+ const DEFAULT = {
5
+ multiFamily: {
6
+ familySelector: 'carousel',
7
+ showParentTitle: false,
8
+ slide: {
9
+ type: 'buttons'
10
+ }
11
+ },
12
+ singleFamily: { type: 'carousel' }
13
+ } satisfies SelectionUISpecifier
14
+
15
+ const map = new Map<string, SelectionUISpecifier>()
16
+
17
+ // key: first two tokens of path
18
+ export const initSelectionUI = (v: Record<string, SelectionUISpecifier>) => {
19
+
20
+ // Might have multiple calls client and server side
21
+ if (map.size > 0) {
22
+ map.clear()
23
+ }
24
+
25
+ for (let [key, value] of Object.entries(v)) {
26
+ map.set(key, value)
27
+ }
28
+ }
29
+
30
+ export const getSelectionUISpecifier = (skuPath: string): SelectionUISpecifier => {
31
+ const key = skuPath.split(sep.tok).slice(0, 2).join(sep.tok)
32
+ const result = map.get(key)
33
+ return result ?? DEFAULT
34
+ }
@@ -10,7 +10,7 @@ import {
10
10
 
11
11
  import { useCommerce } from '../service/context'
12
12
  import type { SelectedPaths } from '../types'
13
- import SEP from './sep'
13
+ import sep from '../service/sep'
14
14
 
15
15
  const PLEASE_SELECT_FACETS = 'Please select an option from each group.'
16
16
 
@@ -55,7 +55,7 @@ const useSyncSkuParamWithCurrentItem = (
55
55
  useEffect(() => {
56
56
 
57
57
  const setCurrentFamilyFromSku = (sku: string) => {
58
- const toks: string[] = sku.split(SEP.TOK)
58
+ const toks: string[] = sku.split(sep.tok)
59
59
  const fv: SelectedPaths = {}
60
60
  // TODO: confirm that extra trailing nonsense tokens won't break selectPaths()
61
61
  for (let i = 1; i < familyLevel; i++) {
@@ -1,36 +0,0 @@
1
- 'use client'
2
- import React, { useCallback, useEffect, useRef } from 'react'
3
- import { reaction } from 'mobx'
4
- import { observer } from 'mobx-react-lite'
5
-
6
- import { cn } from '@hanzo/ui/util'
7
-
8
- import {
9
- ApplyTypography,
10
- MediaStack
11
- } from '@hanzo/ui/primitives'
12
- import type { Family } from '../../../types'
13
-
14
- const FamilySlide: React.FC<{
15
- family: Family
16
- clx?: string
17
- mediaConstraint?: {w: number, h: number}
18
- getBylineText?: (c: Family) => string
19
- variantOrientation?: 'vert' | 'horiz'
20
- showVariantImage?: boolean
21
- showVariantPrice?: boolean
22
- }> = ({
23
- family,
24
- clx='',
25
- mediaConstraint,
26
- getBylineText,
27
- variantOrientation,
28
- showVariantImage,
29
- showVariantPrice
30
- }) => {
31
-
32
-
33
- return <></>
34
- }
35
-
36
- export default FamilySlide
@@ -1,112 +0,0 @@
1
- 'use client'
2
- import React, { useCallback, useEffect, useRef } from 'react'
3
- import { reaction } from 'mobx'
4
- import { observer } from 'mobx-react-lite'
5
-
6
- import { cn } from '@hanzo/ui/util'
7
-
8
- import {
9
- type CarouselOptions,
10
- type CarouselApi,
11
- Carousel,
12
- CarouselContent,
13
- CarouselItem,
14
- CarouselPrevious,
15
- CarouselNext,
16
- ApplyTypography,
17
- MediaStack
18
- } from '@hanzo/ui/primitives'
19
-
20
- import type { ItemSelectorProps, LineItem } from '../../types'
21
- import { formatCurrencyValue } from '../../util'
22
-
23
- interface CarouselItemSelectorPropsExt {
24
- constrainTo: {w: number, h: number}
25
- options?: CarouselOptions
26
- /** Do not show Family and / or Item title and Price */
27
- imageOnly?: boolean
28
- }
29
-
30
- const CarouselItemSelector: React.FC<ItemSelectorProps> = observer(({
31
- items,
32
- selectSku,
33
- selectedItemRef: itemRef,
34
- scrollList, // ignored
35
- clx='',
36
- itemClx='',
37
- showFamily=false,
38
- ext={
39
- options: {loop: true},
40
- constrainTo: {w: 250, h: 250},
41
- imageOnly: false
42
- } satisfies CarouselItemSelectorPropsExt
43
- }) => {
44
-
45
- const { options, constrainTo, imageOnly} = ext
46
-
47
- const elbaApiRef = useRef<CarouselApi | undefined>(undefined)
48
- const dontRespondRef = useRef<boolean>(false)
49
-
50
- const setApi = (api: CarouselApi) => {elbaApiRef.current = api}
51
-
52
- const onSelect = useCallback((emblaApi: CarouselApi) => {
53
- if (dontRespondRef.current) {
54
- dontRespondRef.current = false
55
- return
56
- }
57
- const index = emblaApi.selectedScrollSnap()
58
- if (index !== -1) {
59
- selectSku(items[index].sku)
60
- }
61
- dontRespondRef.current = false
62
- }, [])
63
-
64
- useEffect(() => {
65
- return reaction(
66
- () => ({item: itemRef.item}),
67
- ({item}) => {
68
- if (elbaApiRef.current) {
69
- const index = items.findIndex((el) => (el.sku === item?.sku))
70
- if (index !== -1) {
71
- dontRespondRef.current = true
72
- elbaApiRef.current.scrollTo(index)
73
- }
74
- }
75
- }
76
- )
77
- }, [])
78
-
79
- const ItemInfo: React.FC<{
80
- item: LineItem
81
- clx?: string
82
- }> = ({
83
- item,
84
- clx
85
- }) => (
86
- <ApplyTypography className={cn(clx, 'flex flex-col items-center !gap-2 [&>*]:!m-0')}>
87
- {showFamily && (<h4>{item.familyTitle}</h4>)}
88
- <p>{item.optionLabel}</p>
89
- <p>{formatCurrencyValue(item.price)}</p>
90
- </ApplyTypography>
91
- )
92
-
93
- return (
94
- <Carousel options={options} className={cn('w-full px-2', clx)} onCarouselSelect={onSelect} setApi={setApi}>
95
- <CarouselContent>
96
- {items.map((item, index) => (
97
- <CarouselItem key={index} className={cn('p-2 flex flex-col justify-center items-center', itemClx)}>
98
- <MediaStack media={item} constrainTo={constrainTo} clx='' />
99
- {!imageOnly && (<ItemInfo item={item} clx=''/>)}
100
- </CarouselItem>
101
- ))}
102
- </CarouselContent>
103
- <CarouselPrevious className='left-1'/>
104
- <CarouselNext className='right-1'/>
105
- </Carousel>
106
- )
107
- })
108
-
109
- export {
110
- type CarouselItemSelectorPropsExt,
111
- CarouselItemSelector as default
112
- }
@@ -1,162 +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
- Image,
9
- Label,
10
- RadioGroup,
11
- ScrollArea
12
- } from '@hanzo/ui/primitives'
13
- import { cn } from '@hanzo/ui/util'
14
-
15
- import type { ItemSelectorProps, LineItem } from '../../types'
16
- import { formatCurrencyValue } from '../../util'
17
-
18
- const ImageRadioGroupItem = React.forwardRef<
19
- React.ElementRef<typeof RadioGroupPrimitive.Item>,
20
- Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, 'value' | 'id'> & {
21
- item: LineItem,
22
- constrainTo: {w: number, h: number}
23
- }
24
- >(({
25
- item,
26
- constrainTo,
27
- className,
28
- ...props
29
- }, ref) => {
30
-
31
- return (
32
- <RadioGroupPrimitive.Item
33
- ref={ref}
34
- className={cn(
35
- "ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
36
- className
37
- )}
38
- {...props}
39
- id={item.sku}
40
- value={item.sku}
41
- >
42
- {item.img ? (
43
- <Image def={item.img} constrainTo={constrainTo} preload className=''/>
44
- ) : ( // placeholder so things align
45
- <div style={{height: constrainTo.h, width: constrainTo.w}}/>
46
- )}
47
- </RadioGroupPrimitive.Item>
48
- )
49
- })
50
- ImageRadioGroupItem.displayName = 'ImageRadioGroupItem'
51
-
52
- const ImageItemSelector: React.FC<ItemSelectorProps> = observer(({
53
- items,
54
- selectedItemRef: itemRef,
55
- selectSku,
56
- clx='',
57
- itemClx='',
58
- soleItemClx='',
59
- showFamily=false,
60
- showQuantity=false,
61
- scrollList=false
62
- }) => {
63
-
64
- const LabelText: React.FC<{item: LineItem}> = ({item}) => (
65
- (showFamily ? (item.familyTitle + ', ' + item.optionLabel) : item.optionLabel) +
66
- (showFamily ? ': ' : ', ') + formatCurrencyValue(item.price)
67
- )
68
-
69
- const ItemAndPrice: React.FC<{
70
- item: LineItem
71
- listBoxMode: boolean
72
- selected: boolean
73
- className?: string
74
- }> = ({
75
- item,
76
- listBoxMode,
77
- selected,
78
- className=''
79
- }) => (
80
- <div className={cn(
81
- 'flex items-center',
82
- className,
83
- itemClx,
84
- )}>
85
- <ImageRadioGroupItem
86
- item={item}
87
- constrainTo={{h: 36, w: 72}} // Apple suggest 42px
88
- className={
89
- 'mr-2 ' + (listBoxMode ? '' :
90
- 'border-transparent border-2 rounded-sm data-[state=checked]:border-foreground')
91
- }
92
- />
93
- <Label htmlFor={item.sku} className={selected && listBoxMode ? 'text-accent' : ''}>
94
- <LabelText item={item} />
95
- </Label>
96
- </div>
97
- )
98
-
99
- const Item: React.FC<{
100
- item: LineItem
101
- selected: boolean
102
- listBoxMode: boolean
103
- clx?: string
104
- }> = ({
105
- item,
106
- selected,
107
- listBoxMode,
108
- clx=''
109
- }) => {
110
-
111
- const outClx = [
112
- (listBoxMode ? 'border-b border-muted-3 py-1' : 'mb-3'),
113
- (selected && listBoxMode ? 'border border-foreground rounded-sm' : ''),
114
- ]
115
-
116
- return (showQuantity ? (
117
- <div key={item.sku} className={cn('flex flex-row items-center', ...outClx, clx )}>
118
- <ItemAndPrice item={item} selected={selected} listBoxMode={listBoxMode} className='grow'/>
119
- <div className='grow-0 shrink-0 font-semibold text-sm leading-none px-2'>{ item.quantity > 0 ? `(${item.quantity})` : ' '}</div>
120
- </div>
121
- ) : (
122
- <ItemAndPrice
123
- key={item.sku}
124
- item={item}
125
- selected={selected}
126
- listBoxMode={listBoxMode}
127
- className={cn(...outClx,
128
- listBoxMode ? '' : 'mb-1',
129
- clx)}
130
- />
131
- ))
132
- }
133
-
134
- return items.length > 1 ? (
135
- <RadioGroup
136
- className={cn('flex flex-col gap-0',
137
- (scrollList ? 'shrink min-h-0' : ''),
138
- clx,
139
- )}
140
- onValueChange={selectSku}
141
- value={itemRef.item ? itemRef.item.sku : ''}
142
- >
143
- {scrollList ? (
144
- <ScrollArea className='mt-2 w-full h-full py-0 border border-muted-2 rounded-sm '>
145
- {items.map((item) => (
146
- <Item item={item} listBoxMode={true} selected={itemRef.item?.sku === item.sku}/>
147
- ))}
148
- </ScrollArea>
149
- ) : (<>
150
- {items.map((item) => (
151
- <Item item={item} listBoxMode={false} selected={itemRef.item?.sku === item.sku}/>
152
- ))}
153
- </>)}
154
- </RadioGroup>
155
- ) : (
156
- <div className={soleItemClx}>
157
- <LabelText item={items[0]} />
158
- </div>
159
- )
160
- })
161
-
162
- export default ImageItemSelector