@hanzo/commerce 6.0.0 → 6.1.0

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.
@@ -1,5 +1,4 @@
1
1
  import React from 'react'
2
- import Image from 'next/image'
3
2
 
4
3
  import {
5
4
  Card,
@@ -13,9 +12,9 @@ import { cn } from '@hanzo/ui/util'
13
12
 
14
13
  import { formatCurrencyValue } from '../../util'
15
14
  import type { LineItem } from '../../types'
16
- import { Icons } from '../Icons'
17
15
 
18
16
  import AddToCartWidget from '../buy/add-to-cart-widget'
17
+ import ItemMedia from './item-media'
19
18
 
20
19
  interface ProductCardProps extends React.HTMLAttributes<HTMLDivElement> {
21
20
  item: LineItem
@@ -25,53 +24,25 @@ const ProductCard: React.FC<ProductCardProps> = ({
25
24
  item,
26
25
  className,
27
26
  ...props
28
- }) => {
27
+ }) => (
28
+ <Card
29
+ className={cn('max-h-[360px] lg:min-w-[200px] max-w-[260px] overflow-hidden', className)}
30
+ {...props}
31
+ >
32
+ <CardHeader className='w-full border-b p-6 min-h-[180px] max-h-[240px] relative'>
33
+ <ItemMedia item={item} constrainTo={{w: 700, h:700}} clx='p-6' />
34
+ </CardHeader>
35
+ <CardContent className='grid gap-2.5 p-4'>
36
+ <CardTitle className='text-sm sm:text-base flex flex-col justify-start items-center line-clap-3'>
37
+ {item.title.split(', ').map((e, i) => (<p key={i}>{e}</p>))}
38
+ <p className='mt-1 font-semibold'>{formatCurrencyValue(item.price)}</p>
39
+ </CardTitle>
40
+ </CardContent>
41
+ <CardFooter className='p-4 flex flex-row justify-center'>
42
+ <AddToCartWidget item={item}/>
43
+ </CardFooter>
44
+ </Card>
45
+ )
29
46
 
30
- const ProductImage: React.FC<{className?: string}> = ({className=''}) => (
31
- <Image
32
- src={item.img!}
33
- alt={item.title}
34
- className={className}
35
- loading='lazy'
36
- // width={700}
37
- // height={700}
38
- fill
39
- style={{
40
- objectFit: 'contain',
41
- }}
42
- />
43
- )
44
-
45
- return (
46
- <Card
47
- className={cn('max-h-[360px] lg:min-w-[200px] max-w-[260px] overflow-hidden', className)}
48
- {...props}
49
- >
50
- <CardHeader className='w-full border-b p-6 min-h-[180px] max-h-[240px] relative'>
51
- {item.img ? (
52
- <ProductImage className='p-6' />
53
- ) : (
54
- <div
55
- aria-label='Placeholder'
56
- role='img'
57
- aria-roledescription='placeholder'
58
- className='flex h-full items-center justify-center bg-background'
59
- >
60
- <Icons.barcode className='h-9 w-9 text-muted' aria-hidden='true' />
61
- </div>
62
- )}
63
- </CardHeader>
64
- <CardContent className='grid gap-2.5 p-4'>
65
- <CardTitle className='text-sm sm:text-base flex flex-col justify-start items-center line-clap-3'>
66
- {item.title.split(', ').map((e, i) => (<p key={i}>{e}</p>))}
67
- <p className='mt-1 font-semibold'>{formatCurrencyValue(item.price)}</p>
68
- </CardTitle>
69
- </CardContent>
70
- <CardFooter className='p-4 flex flex-row justify-center'>
71
- <AddToCartWidget item={item}/>
72
- </CardFooter>
73
- </Card>
74
- )
75
- }
76
47
 
77
48
  export default ProductCard
@@ -5,10 +5,10 @@ import { ToggleGroup, ToggleGroupItem} from "@hanzo/ui/primitives"
5
5
  import { cn } from '@hanzo/ui/util'
6
6
 
7
7
  import type { ProductTreeNode, StringMutator, StringArrayMutator } from '../../types'
8
- import FacetImage from './facet-image'
8
+ import NodeImage from './node-image'
9
9
 
10
- const FacetValuesWidget: React.FC<{
11
- facetValues: ProductTreeNode[]
10
+ const LevelNodesWidget: React.FC<{
11
+ levelNodes: ProductTreeNode[]
12
12
  mutator: StringMutator | StringArrayMutator
13
13
  multiple?: boolean
14
14
  className?: string
@@ -18,7 +18,7 @@ const FacetValuesWidget: React.FC<{
18
18
  tabSize?: string
19
19
  show?: 'image' | 'label' | 'image-and-label'
20
20
  }> = ({
21
- facetValues,
21
+ levelNodes,
22
22
  mutator,
23
23
  multiple=false,
24
24
  buttonClx='',
@@ -61,25 +61,25 @@ const FacetValuesWidget: React.FC<{
61
61
  className={className}
62
62
  {...roundedToSpread}
63
63
  >
64
- {facetValues.map((fv, index) => {
64
+ {levelNodes.map((treeNode, index) => {
65
65
  const roundedToSpread: any = {}
66
66
  if (!multiple) {
67
67
  roundedToSpread.rounded = 'none'
68
68
  if (index === 0) { roundedToSpread.rounded = 'llg' }
69
- else if (index === facetValues.length - 1) { roundedToSpread.rounded = 'rlg' }
69
+ else if (index === levelNodes.length - 1) { roundedToSpread.rounded = 'rlg' }
70
70
  }
71
71
  return (
72
72
  <ToggleGroupItem
73
- key={fv.skuToken}
74
- value={fv.skuToken}
75
- disabled={(last && last === fv.skuToken || fv.skuToken === mutator.get())}
76
- aria-label={`Select ${fv.label}`}
73
+ key={treeNode.skuToken}
74
+ value={treeNode.skuToken}
75
+ disabled={(last && last === treeNode.skuToken || treeNode.skuToken === mutator.get())}
76
+ aria-label={`Select ${treeNode.label}`}
77
77
  {...roundedToSpread}
78
78
  className={buttonClx}
79
79
  >
80
80
  <span className={cn('flex flex-row justify-center gap-1 h-6 items-center', itemClx)} >
81
- {!(show === 'label') && (<FacetImage facetValueDesc={fv} />) }
82
- {(!(show === 'image') || !fv.img) && (<span className='whitespace-nowrap'>{fv.label}</span>)}
81
+ {!(show === 'label') && (<NodeImage treeNode={treeNode} />) }
82
+ {(!(show === 'image') || !treeNode.img) && (<span className='whitespace-nowrap'>{treeNode.label}</span>)}
83
83
  </span>
84
84
  </ToggleGroupItem>
85
85
  )
@@ -88,4 +88,4 @@ const FacetValuesWidget: React.FC<{
88
88
  )
89
89
  }
90
90
 
91
- export default FacetValuesWidget
91
+ export default LevelNodesWidget
@@ -0,0 +1,31 @@
1
+ import React from 'react'
2
+ import Image from 'next/image'
3
+
4
+ import type { ProductTreeNode } from '../../types'
5
+
6
+ const ICON_SIZE = 20
7
+
8
+ const NodeImage: React.FC<{
9
+ treeNode: ProductTreeNode
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
@@ -1,54 +1,102 @@
1
- import React from 'react'
1
+ 'use client'
2
+ import React, { useCallback, useEffect, useRef } from 'react'
3
+ import { reaction } from 'mobx'
4
+ import { observer } from 'mobx-react-lite'
2
5
 
3
- import Spline from '@splinetool/react-spline'
4
-
5
- import type { Dimensions } from '@hanzo/ui/types'
6
6
  import { cn } from '@hanzo/ui/util'
7
7
  import ItemMedia from '../item/item-media'
8
8
 
9
9
  import {
10
10
  type CarouselOptionsType,
11
+ type CarouselApi,
11
12
  Carousel,
12
13
  CarouselContent,
13
14
  CarouselItem,
14
15
  CarouselPrevious,
15
- CarouselNext
16
+ CarouselNext,
17
+ ApplyTypography
16
18
  } from '@hanzo/ui/primitives'
17
19
 
18
- import {
19
- VideoBlockComponent,
20
- ImageBlockComponent,
21
- type ImageBlock,
22
- type Block,
23
- type VideoBlock
24
- } from '@hanzo/ui/blocks'
25
-
26
- import type { ItemSelectorProps } from '../../types'
20
+ import type { ItemSelectorProps, LineItem } from '../../types'
21
+ import { formatCurrencyValue } from '../../util'
27
22
 
28
23
  interface CarouselItemSelectorPropsExt {
29
- constrainTo: Dimensions
24
+ constrainTo: {w: number, h: number}
30
25
  options?: CarouselOptionsType
31
- noSelection?: boolean // "display only" mode
26
+ /** Do not show Category and / or Item title and Price */
27
+ imageOnly?: boolean
32
28
  }
33
29
 
34
- const CarouselItemSelector: React.FC<ItemSelectorProps> = ({
30
+ const CarouselItemSelector: React.FC<ItemSelectorProps> = observer(({
35
31
  items,
32
+ selectSku,
33
+ selectedItemRef: itemRef,
34
+ scrollList, // ignored
36
35
  clx='',
37
36
  itemClx='',
37
+ showCategory=false,
38
38
  ext={
39
- options: {},
40
- constrainTo: {w: 250, h: 250}
39
+ options: {loop: true},
40
+ constrainTo: {w: 250, h: 250},
41
+ imageOnly: false
41
42
  } satisfies CarouselItemSelectorPropsExt
42
43
  }) => {
43
44
 
44
- const { options, constrainTo} = ext
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
+ {showCategory && (<h4>{item.categoryTitle}</h4>)}
88
+ <p>{item.optionLabel}</p>
89
+ <p>{formatCurrencyValue(item.price)}</p>
90
+ </ApplyTypography>
91
+ )
45
92
 
46
93
  return (
47
- <Carousel options={options} className={cn('px-2', clx)} >
94
+ <Carousel options={options} className={cn('w-full px-2', clx)} onSelect={onSelect} setApi={setApi}>
48
95
  <CarouselContent>
49
96
  {items.map((item, index) => (
50
- <CarouselItem key={index} className={cn('p-2 flex flex-row justify-center items-center', itemClx)}>
97
+ <CarouselItem key={index} className={cn('p-2 flex flex-col justify-center items-center', itemClx)}>
51
98
  <ItemMedia item={item} constrainTo={constrainTo} clx='' />
99
+ {!imageOnly && (<ItemInfo item={item} clx=''/>)}
52
100
  </CarouselItem>
53
101
  ))}
54
102
  </CarouselContent>
@@ -56,7 +104,7 @@ const CarouselItemSelector: React.FC<ItemSelectorProps> = ({
56
104
  <CarouselNext className='right-1'/>
57
105
  </Carousel>
58
106
  )
59
- }
107
+ })
60
108
 
61
109
  export {
62
110
  type CarouselItemSelectorPropsExt,
@@ -1,50 +1,33 @@
1
1
  'use client'
2
2
  import React from 'react'
3
- import Image from 'next/image'
4
3
  import { observer } from 'mobx-react-lite'
5
4
 
6
5
  import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
7
- import { Label, RadioGroup, RadioGroupItem } from '@hanzo/ui/primitives'
6
+
7
+ import {
8
+ Image,
9
+ Label,
10
+ RadioGroup,
11
+ ScrollArea
12
+ } from '@hanzo/ui/primitives'
13
+ import { cn } from '@hanzo/ui/util'
14
+
8
15
  import type { ItemSelectorProps, LineItem } from '../../types'
9
16
  import { formatCurrencyValue } from '../../util'
10
- import { cn } from '@hanzo/ui/util'
11
- import type { Dimensions } from '@hanzo/ui/types'
12
17
 
13
18
  const ImageRadioGroupItem = React.forwardRef<
14
19
  React.ElementRef<typeof RadioGroupPrimitive.Item>,
15
20
  Omit<React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>, 'value' | 'id'> & {
16
21
  item: LineItem,
17
- imgSizePx: number
22
+ constrainTo: {w: number, h: number}
18
23
  }
19
24
  >(({
20
25
  item,
21
- imgSizePx,
26
+ constrainTo,
22
27
  className,
23
28
  ...props
24
29
  }, ref) => {
25
30
 
26
- let dim: Dimensions
27
- if (item.imgAR) {
28
- if (item.imgAR >= 1) {
29
- dim = {
30
- w: imgSizePx,
31
- h: imgSizePx / item.imgAR
32
- }
33
- }
34
- else {
35
- dim = {
36
- w: imgSizePx * item.imgAR,
37
- h: imgSizePx
38
- }
39
- }
40
- }
41
- else {
42
- dim = {
43
- w: imgSizePx,
44
- h: imgSizePx
45
- }
46
- }
47
-
48
31
  return (
49
32
  <RadioGroupPrimitive.Item
50
33
  ref={ref}
@@ -56,10 +39,10 @@ const ImageRadioGroupItem = React.forwardRef<
56
39
  id={item.sku}
57
40
  value={item.sku}
58
41
  >
59
- {!!item.img ? (
60
- <Image src={item.img} alt={item.title + ' image'} height={dim.h} width={dim.w} className=''/>
42
+ {item.img ? (
43
+ <Image def={item.img} constrainTo={constrainTo} preload className=''/>
61
44
  ) : ( // placeholder so things align
62
- <div style={{height: dim.h, width: dim.w}}/>
45
+ <div style={{height: constrainTo.h, width: constrainTo.w}}/>
63
46
  )}
64
47
  </RadioGroupPrimitive.Item>
65
48
  )
@@ -73,52 +56,106 @@ const ImageItemSelector: React.FC<ItemSelectorProps> = observer(({
73
56
  clx='',
74
57
  itemClx='',
75
58
  soleItemClx='',
76
- showPrice=true,
77
- showQuantity=true
59
+ showCategory=false,
60
+ showQuantity=false,
61
+ scrollList=false
78
62
  }) => {
79
63
 
80
- const Choice: React.FC<{
64
+ const LabelText: React.FC<{item: LineItem}> = ({item}) => (
65
+ (showCategory ? (item.categoryTitle + ', ' + item.optionLabel) : item.optionLabel) +
66
+ (showCategory ? ': ' : ', ') + formatCurrencyValue(item.price)
67
+ )
68
+
69
+ const ItemAndPrice: React.FC<{
81
70
  item: LineItem
71
+ listBoxMode: boolean
72
+ selected: boolean
82
73
  className?: string
83
74
  }> = ({
84
75
  item,
76
+ listBoxMode,
77
+ selected,
85
78
  className=''
86
79
  }) => (
87
- <div className={cn('flex items-center mb-1', className, itemClx)}>
88
- <ImageRadioGroupItem item={item} imgSizePx={40} className='mr-2 border-2 border-transparent rounded-sm data-[state=checked]:border-foreground'/>
89
- <Label htmlFor={item.sku}>{item.titleAsOption + (showPrice ? (', ' + formatCurrencyValue(item.price)) : '')}</Label>
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>
90
96
  </div>
91
97
  )
92
-
93
- const SoleChoice: React.FC<{
98
+
99
+ const Item: React.FC<{
94
100
  item: LineItem
95
- className?: string
101
+ selected: boolean
102
+ listBoxMode: boolean
103
+ clx?: string
96
104
  }> = ({
97
105
  item,
98
- className=''
99
- }) => (
100
- <div className={cn(className, soleItemClx)}>
101
- {item.titleAsOption + (showPrice ? (', ' + formatCurrencyValue(item.price)) : '')}
102
- </div>
103
- )
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
+ }
104
133
 
105
134
  return items.length > 1 ? (
106
135
  <RadioGroup
107
- className={cn('gap-0', showQuantity ? 'table' : '', clx)}
136
+ className={cn('flex flex-col gap-0',
137
+ (scrollList ? 'shrink min-h-0' : ''),
138
+ clx,
139
+ )}
108
140
  onValueChange={selectSku}
109
141
  value={itemRef.item ? itemRef.item.sku : ''}
110
142
  >
111
- {items.map((item) => (showQuantity ? (
112
- <div className='table-row' key={item.sku}>
113
- <Choice item={item} className='table-cell pr-2 align-text-top pb-3'/>
114
- <div className='table-cell font-semibold text-sm leading-none align-text-top pb-3'>{ item.quantity > 0 ? `(${item.quantity})` : ' '}</div>
115
- </div>
116
- ) : (
117
- <Choice item={item} className='mb-3' key={item.sku}/>
118
- )))}
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
+ </>)}
119
154
  </RadioGroup>
120
155
  ) : (
121
- <SoleChoice item={items[0]} className=''/>
156
+ <div className={soleItemClx}>
157
+ <LabelText item={items[0]} />
158
+ </div>
122
159
  )
123
160
  })
124
161
 
@@ -2,10 +2,16 @@
2
2
  import React from 'react'
3
3
  import { observer } from 'mobx-react-lite'
4
4
 
5
- import { Label, RadioGroup, RadioGroupItem } from '@hanzo/ui/primitives'
5
+ import {
6
+ Label,
7
+ RadioGroup,
8
+ RadioGroupItem,
9
+ ScrollArea
10
+ } from '@hanzo/ui/primitives'
11
+ import { cn } from '@hanzo/ui/util'
12
+
6
13
  import type { ItemSelectorProps, LineItem } from '../../types'
7
14
  import { formatCurrencyValue } from '../../util'
8
- import { cn } from '@hanzo/ui/util'
9
15
 
10
16
  const RadioItemSelector: React.FC<ItemSelectorProps> = observer(({
11
17
  items,
@@ -14,52 +20,105 @@ const RadioItemSelector: React.FC<ItemSelectorProps> = observer(({
14
20
  clx='',
15
21
  itemClx='',
16
22
  soleItemClx='',
17
- showPrice=true,
18
- showQuantity=true
23
+ showCategory=false,
24
+ showQuantity=false,
25
+ scrollList=false
19
26
  }) => {
20
27
 
21
- const Choice: React.FC<{
28
+ const LabelText: React.FC<{item: LineItem}> = ({item}) => (
29
+ (showCategory ? (item.categoryTitle + ', ' + item.optionLabel) : item.optionLabel) +
30
+ (showCategory ? ': ' : ', ') + formatCurrencyValue(item.price)
31
+ )
32
+
33
+ const ItemAndPrice: React.FC<{
22
34
  item: LineItem
35
+ listBoxMode: boolean
36
+ selected: boolean
23
37
  className?: string
24
38
  }> = ({
25
39
  item,
40
+ listBoxMode,
41
+ selected,
26
42
  className=''
27
43
  }) => (
28
- <div className={cn(className, itemClx)}>
29
- <RadioGroupItem value={item.sku} id={item.sku} className='mr-2'/>
30
- <Label htmlFor={item.sku}>{item.titleAsOption + (showPrice ? (', ' + formatCurrencyValue(item.price)) : '')}</Label>
44
+ <div className={cn(
45
+ 'flex items-center',
46
+ className,
47
+ itemClx,
48
+ )}>
49
+ <RadioGroupItem
50
+ value={item.sku}
51
+ larger
52
+ id={item.sku}
53
+ className={'mr-2 ' + (listBoxMode ? 'hidden' : '')}
54
+ />
55
+ <Label htmlFor={item.sku} className={selected ? 'text-accent' : ''}>
56
+ <LabelText item={item} />
57
+ </Label>
31
58
  </div>
32
59
  )
33
-
34
- const SoleChoice: React.FC<{
60
+
61
+ const Item: React.FC<{
35
62
  item: LineItem
36
- className?: string
63
+ selected: boolean
64
+ listBoxMode: boolean
65
+ clx?: string
37
66
  }> = ({
38
67
  item,
39
- className=''
40
- }) => (
41
- <div className={cn(className, soleItemClx)}>
42
- {item.titleAsOption + (showPrice ? (', ' + formatCurrencyValue(item.price)) : '')}
43
- </div>
44
- )
68
+ selected,
69
+ listBoxMode,
70
+ clx=''
71
+ }) => {
72
+
73
+ const outClx = [
74
+ 'h-10',
75
+ (listBoxMode ? 'border-b border-muted-3 py-1 pl-2' : 'mb-3'),
76
+ (selected && listBoxMode ? 'border border-foreground rounded-sm' : ''),
77
+ ]
78
+
79
+ return (showQuantity ? (
80
+ <div key={item.sku} className={cn('flex flex-row items-center', ...outClx, clx )}>
81
+ <ItemAndPrice item={item} selected={selected} listBoxMode={listBoxMode} className='grow'/>
82
+ <div className='grow-0 shrink-0 font-semibold text-sm leading-none px-2'>{ item.quantity > 0 ? `(${item.quantity})` : ' '}</div>
83
+ </div>
84
+ ) : (
85
+ <ItemAndPrice
86
+ key={item.sku}
87
+ item={item}
88
+ selected={selected}
89
+ listBoxMode={listBoxMode}
90
+ className={cn(...outClx,
91
+ listBoxMode ? '' : 'mb-1',
92
+ clx)}
93
+ />
94
+ ))
95
+ }
45
96
 
46
97
  return items.length > 1 ? (
47
98
  <RadioGroup
48
- className={cn('gap-0', showQuantity ? 'table' : '', clx)}
99
+ className={cn('flex flex-col gap-0',
100
+ (scrollList ? 'shrink min-h-0' : ''),
101
+ clx,
102
+ )}
49
103
  onValueChange={selectSku}
50
104
  value={itemRef.item ? itemRef.item.sku : ''}
51
105
  >
52
- {items.map((item) => (showQuantity ? (
53
- <div className='table-row' key={item.sku}>
54
- <Choice item={item} className='table-cell pr-2 align-text-top pb-2'/>
55
- <div className='table-cell font-semibold text-sm leading-none align-text-top pb-2'>{ item.quantity > 0 ? `(${item.quantity})` : ' '}</div>
56
- </div>
57
- ) : (
58
- <Choice item={item} className='mb-2' key={item.sku}/>
59
- )))}
106
+ {scrollList ? (
107
+ <ScrollArea className='mt-2 w-full h-full py-0 border border-muted-2 rounded-sm '>
108
+ {items.map((item) => (
109
+ <Item item={item} listBoxMode={true} selected={itemRef.item?.sku === item.sku}/>
110
+ ))}
111
+ </ScrollArea>
112
+ ) : (<>
113
+ {items.map((item) => (
114
+ <Item item={item} listBoxMode={false} selected={itemRef.item?.sku === item.sku}/>
115
+ ))}
116
+ </>)}
60
117
  </RadioGroup>
61
118
  ) : (
62
- <SoleChoice item={items[0]} className=''/>
119
+ <div className={soleItemClx}>
120
+ <LabelText item={items[0]} />
121
+ </div>
63
122
  )
64
123
  })
65
124