@hanzo/commerce 1.0.8

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.
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import type CommerceCatAndItemBlock from '../def/commerce-cat-and-item-block'
3
+
4
+ /*
5
+
6
+ interface CommerceCatAndItemBlock extends Block {
7
+ blockType: 'commerce-cat-and-item'
8
+ parentPath: string // SKU path of parent of Category ... eg, in Market, LXB-AU or LXB-AG. Next level is Cat.
9
+ defaultCatPath?: string // SKU path of default Category... eg, in Market, LXB-AU-B (Minted Bar)
10
+ }
11
+
12
+ */
13
+
@@ -0,0 +1,9 @@
1
+ import type { Block } from '@hanzo/ui/blocks'
2
+
3
+ interface CommerceCatAndItemBlock extends Block {
4
+ blockType: 'commerce-cat-and-item'
5
+ parentPath: string // SKU path of parent of Category ... eg, in Market, LXB-AU or LXB-AG. Next level is Cat.
6
+ defaultCatPath?: string // SKU path of default Category... eg, in Market, LXB-AU-B (Minted Bar)
7
+ }
8
+
9
+ export { type CommerceCatAndItemBlock as default }
@@ -0,0 +1,35 @@
1
+ import {
2
+ ShoppingCart,
3
+ Menu,
4
+ ChevronLeft,
5
+ ChevronRight,
6
+ Loader2,
7
+ Check,
8
+ Plus,
9
+ Minus,
10
+ ChevronDown,
11
+ ChevronsLeft,
12
+ ChevronsRight,
13
+ Image,
14
+ Trash2,
15
+ Search,
16
+ Barcode
17
+ } from "lucide-react"
18
+
19
+ export const Icons = {
20
+ shoppingCart: ShoppingCart,
21
+ menu: Menu,
22
+ chevronLeft: ChevronLeft,
23
+ chevronRight: ChevronRight,
24
+ spinner: Loader2,
25
+ check: Check,
26
+ plus: Plus,
27
+ minus: Minus,
28
+ chevronDown: ChevronDown,
29
+ chevronsLeft: ChevronsLeft,
30
+ chevronsRight: ChevronsRight,
31
+ placeholder: Image,
32
+ trash: Trash2,
33
+ search: Search,
34
+ barcode: Barcode
35
+ }
@@ -0,0 +1,83 @@
1
+ 'use client'
2
+ import React from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import { Button, type ButtonSizes } from '@hanzo/ui/primitives'
6
+ import { cn } from '@hanzo/ui/util'
7
+
8
+ import { Icons } from './Icons'
9
+ import type { LineItem } from '../types'
10
+
11
+ const AddToCartWidget: React.FC<{
12
+ item: LineItem
13
+ ghost?: boolean
14
+ disabled?: boolean
15
+ className?: string
16
+ isMobile?: boolean
17
+ wide?: boolean
18
+ size?: ButtonSizes
19
+ }> = observer(({
20
+ item,
21
+ ghost=false,
22
+ disabled=false,
23
+ className='',
24
+ size='xs'
25
+ }) => {
26
+
27
+ const iconClx = ghost ? 'h-3 w-3 text-muted-3 hover:text-foreground' : 'h-5 w-5 mr-1'
28
+ const digitClx = ghost ? 'px-1' : 'px-2 font-bold '
29
+
30
+ if (disabled) {
31
+ return (
32
+ <div className={cn('flex flex-row items-stretch ' + (ghost ? 'bg-transparent rounded-xl' : 'bg-secondary rounded-xl'), className)}>
33
+ <div className={'text-sm flex items-center cursor-default ' + digitClx} >{item.quantity}</div>
34
+ </div>
35
+ )
36
+ }
37
+
38
+ return ( item.isInCart ? (
39
+ <div className={cn('flex flex-row items-stretch justify-center ' + (ghost ? 'bg-transparent rounded-xl' : 'bg-secondary rounded-xl'), className)}>
40
+ <Button
41
+ aria-label={'Remove a ' + item.title + ' from the cart'}
42
+ size={size}
43
+ variant={ghost ? 'ghost' : 'secondary'}
44
+ rounded={ghost ? 'full' : 'xl'}
45
+ className='px-1 lg:min-w-0 lg:px-2 xs:grow xs:justify-end'
46
+ key='left'
47
+ onClick={item.decrement.bind(item)}
48
+ >
49
+ {(item.quantity > 1) ? (
50
+ <Icons.minus className={iconClx} aria-hidden='true'/>
51
+ ) : (
52
+ <Icons.trash className={iconClx} aria-hidden='true'/>
53
+ )}
54
+ </Button>
55
+ <div className={'text-sm flex items-center cursor-default xs:px-2 ' + digitClx} >{item.quantity}</div>
56
+ <Button
57
+ aria-label={'Add another ' + item.title + ' to the cart'}
58
+ size={size}
59
+ variant={ghost ? 'ghost' : 'secondary'}
60
+ rounded={ghost ? 'full' : 'xl'}
61
+ className='px-1 lg:min-w-0 lg:px-2 xs:grow xs:justify-start'
62
+ onClick={item.increment.bind(item)}
63
+ key='right'
64
+ >
65
+ <Icons.plus className={iconClx} aria-hidden='true'/>
66
+ </Button>
67
+ </div>
68
+ ) : (
69
+ <Button
70
+ aria-label={'Add a ' + item.title + ' to cart'}
71
+ size={size}
72
+ variant='secondary'
73
+ rounded='xl'
74
+ className={className}
75
+ onClick={item.increment.bind(item)}
76
+ >
77
+ <Icons.plus className='h-5 w-5 mr-1' aria-hidden='true'/>
78
+ <span className='mr-1'>Add</span>
79
+ </Button>
80
+ ))
81
+ })
82
+
83
+ export default AddToCartWidget
@@ -0,0 +1,10 @@
1
+ import React from 'react'
2
+ import { type LucideProps } from 'lucide-react'
3
+
4
+ const CartIcon: React.FC<LucideProps> = (props: LucideProps) => (
5
+ <svg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' {...props}>
6
+ <path d='m3.731 8-1.231 20h27l-1.231-20zm1.991 2.103h20.557l.972 15.794h-22.502l.972-15.794z'/>
7
+ <path d='m20.718 8.134c0-2.375-1.932-4.307-4.307-4.307s-4.307 1.932-4.307 4.307h-2.084c0-3.524 2.867-6.391 6.391-6.391s6.391 2.867 6.391 6.391z'/>
8
+ </svg>)
9
+
10
+ export default CartIcon
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+ import { observer } from 'mobx-react-lite'
3
+ import Image from 'next/image'
4
+
5
+ import { cn } from '@hanzo/ui/util'
6
+
7
+ import type { LineItem } from '../types'
8
+ import AddToCartWidget from './add-to-cart-widget'
9
+ import { formatPrice } from '../util'
10
+
11
+ const IMG_SIZE=40
12
+
13
+ const CartLineItem: React.FC<{
14
+ item: LineItem
15
+ className?: string
16
+ isMobile?: boolean
17
+ }> = observer(({
18
+ item,
19
+ className='',
20
+ isMobile=false
21
+ }) => {
22
+
23
+ const imgSize = (isMobile) ? IMG_SIZE * 0.65 : IMG_SIZE
24
+
25
+ return (
26
+ <div className='flex flex-row justify-between items-center gap-2 sm:gap-4'>
27
+ {!!item.img ? (
28
+ <Image src={item.img} alt={item.title + ' image'} height={imgSize} width={imgSize} className='self-start'/>
29
+ ) : (
30
+ <div style={{height: imgSize, width: imgSize}}/ >
31
+ )}
32
+ <div className={cn('text-sm font-sans grow', className)} >
33
+ <div className=''>{item.title}</div>
34
+ <div className='flex flex-row items-center justify-between'>
35
+ <div className='flex flex-row items-center'>
36
+ <AddToCartWidget isMobile={isMobile} className='mr-2' item={item} ghost/>{item.quantity > 1 && '@' + formatPrice(item.price)}
37
+ </div>
38
+ <div className='flex flex-row items-center justify-end'>
39
+ {formatPrice(item.price * item.quantity)}
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ )
45
+ })
46
+
47
+ export default CartLineItem
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+ import React, { type PropsWithChildren, useState } from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import { Button } from '@hanzo/ui/primitives'
6
+ import { cn } from '@hanzo/ui/util'
7
+
8
+ import { persistCart, useCommerce } from '../service'
9
+ import { formatPrice } from '../util'
10
+
11
+ import CartLineItem from './cart-line-item'
12
+ import { useRouter } from 'next/navigation'
13
+ import { useAuth } from '@hanzo/auth/service'
14
+
15
+
16
+ const Cart: React.FC<PropsWithChildren & {
17
+ className?: string
18
+ isMobile?: boolean,
19
+ hideCheckout?: boolean
20
+ }> = observer(({
21
+ children,
22
+ className='',
23
+ isMobile=false,
24
+ hideCheckout=false
25
+ }) => {
26
+ const [loadingCheckout, setLoadingCheckout] = useState(false)
27
+ const c = useCommerce()
28
+ const router = useRouter()
29
+ const auth = useAuth()
30
+
31
+ const checkout = async () => {
32
+ setLoadingCheckout(true)
33
+ if (auth.user) {
34
+ await persistCart(c.cartItems, auth.user.email)
35
+ }
36
+ router.push('/checkout')
37
+ }
38
+
39
+ return (
40
+ <div className={cn('border p-6 rounded-lg', className)}>
41
+ {children}
42
+ <div className='mt-2'>
43
+ {!!children && <div className='h-[1px] w-pr-80 mx-auto bg-muted-3'/>}
44
+ {c.cartItems.length === 0 ? (
45
+ <p className='text-center mt-4'>No items in cart</p>
46
+ ) : (<>
47
+ {c.cartItems.map((item, i) => (<CartLineItem isMobile={isMobile} item={item} key={item.sku} className='mb-4 sm:mb-2'/>))}
48
+ <p className='mt-6 text-right border-t pt-1'>TOTAL: {c.cartTotal === 0 ? '0' : formatPrice(c.cartTotal)}</p>
49
+ </>)}
50
+ </div>
51
+ {c.cartItems.length > 0 && !hideCheckout && (
52
+ <>
53
+ {!auth.loggedIn ? (
54
+ <Button size='lg' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={() => router.push('/login?redirectUrl=checkout')}>Login</Button>
55
+ ) : (
56
+ <Button size='lg' variant='secondary' rounded='lg' className='mt-12 mx-auto' onClick={checkout} disabled={loadingCheckout}>Checkout</Button>
57
+ )}
58
+ </>
59
+ )}
60
+ </div>
61
+ )
62
+ })
63
+
64
+ export default Cart
@@ -0,0 +1,58 @@
1
+ import React from 'react'
2
+ import Image from 'next/image'
3
+
4
+ import type { FacetValueDesc } from '../../types'
5
+
6
+ const ICON_SIZE = 16
7
+ const SVG_MULT = 1.5
8
+
9
+ const FacetImage: React.FC<{
10
+ facetValueDesc: FacetValueDesc
11
+ }> = ({
12
+ facetValueDesc
13
+ }) => {
14
+ if (!facetValueDesc.img) {
15
+ return null
16
+ }
17
+ const isURL = typeof facetValueDesc.img === 'string'
18
+ if (isURL) {
19
+ return (
20
+ <Image
21
+ src={facetValueDesc.img as string}
22
+ alt={`Toggle ${facetValueDesc.label}`}
23
+ className={'block mr-1 aspect-square rounded border border-muted-2'}
24
+ width={ICON_SIZE}
25
+ height={ICON_SIZE}
26
+ />
27
+ )
28
+ }
29
+ const svgStyle: any = { position: 'relative' }
30
+ if (facetValueDesc.imgAR && facetValueDesc.imgAR < 1) {
31
+ const w = ICON_SIZE * SVG_MULT
32
+ svgStyle.top = (( 1 / facetValueDesc.imgAR - 1) * w) / 2
33
+ }
34
+ else if (facetValueDesc.imgAR && facetValueDesc.imgAR > 1) {
35
+ const h = ICON_SIZE * SVG_MULT
36
+ svgStyle.left = (((1 - facetValueDesc.imgAR) * h) / 2)
37
+ }
38
+
39
+ return (
40
+ <span
41
+ className='block overflow-hidden '
42
+ style={{
43
+ color: 'inherit',
44
+ width: ICON_SIZE * SVG_MULT,
45
+ height: ICON_SIZE * SVG_MULT,
46
+ }}
47
+ >
48
+ {React.cloneElement(facetValueDesc.img as React.ReactElement<any>, {
49
+ width: (facetValueDesc.imgAR ?? 1) * ICON_SIZE * SVG_MULT,
50
+ height: SVG_MULT * ICON_SIZE,
51
+ style: svgStyle
52
+ })}
53
+ </span>
54
+ )
55
+ }
56
+
57
+ export default FacetImage
58
+
@@ -0,0 +1,82 @@
1
+ 'use client'
2
+ import React, { useState } from 'react'
3
+
4
+ import { ToggleGroup, ToggleGroupItem,} from "@hanzo/ui/primitives"
5
+ import { cn } from '@hanzo/ui/util'
6
+
7
+ import type { FacetValueDesc, StringMutator, StringArrayMutator } from '../../types'
8
+ import FacetImage from './facet-image'
9
+
10
+ const FacetTogglesWidget: React.FC<{
11
+ facetValues: FacetValueDesc[]
12
+ mutator: StringMutator | StringArrayMutator
13
+ multiple?: boolean
14
+ className?: string
15
+ isMobile?: boolean
16
+ }> = ({
17
+ facetValues,
18
+ mutator,
19
+ multiple='',
20
+ className='',
21
+ isMobile=false
22
+ }) => {
23
+
24
+ const [last, setLast] = useState<string | undefined>(undefined)
25
+
26
+ const handleChangeMultiple = (selected: string[]) => {
27
+ (mutator as StringArrayMutator).set(selected)
28
+ setLast(selected.length === 1 ? selected[0] : undefined)
29
+ }
30
+
31
+ const handleChangeSingle = (selected: string) => {
32
+ (mutator as StringMutator).set(selected)
33
+ if (selected) { setLast(selected) }
34
+ }
35
+
36
+ const roundedToSpread: any = {}
37
+ if (multiple) {
38
+ roundedToSpread.rounded = 'xl'
39
+ }
40
+
41
+ const val = multiple ?
42
+ (mutator as StringArrayMutator).get()
43
+ :
44
+ (mutator as StringMutator).get()
45
+
46
+ return (
47
+ <ToggleGroup
48
+ type={multiple ? 'multiple' : 'single'}
49
+ value={val}
50
+ variant='default'
51
+ size={isMobile ? 'sm' : 'default'}
52
+ onValueChange={multiple ? handleChangeMultiple : handleChangeSingle}
53
+ className={className}
54
+ {...roundedToSpread}
55
+ >
56
+ {facetValues.map((fv, index) => {
57
+ const roundedToSpread: any = {}
58
+ if (!multiple) {
59
+ roundedToSpread.rounded = 'none'
60
+ if (index === 0) { roundedToSpread.rounded = 'llg' }
61
+ else if (index === facetValues.length - 1) { roundedToSpread.rounded = 'rlg' }
62
+ }
63
+ return (
64
+ <ToggleGroupItem
65
+ key={fv.value}
66
+ value={fv.value}
67
+ disabled={(last && last === fv.value || fv.value === mutator.get())}
68
+ aria-label={`Select ${fv.label}`}
69
+ {...roundedToSpread}
70
+ >
71
+ <span className={cn('flex flex-row justify-center gap-1 h-4 items-center')} >
72
+ <FacetImage facetValueDesc={fv} />
73
+ <span className='hidden md:block whitespace-nowrap'>{fv.label}</span>
74
+ </span>
75
+ </ToggleGroupItem>
76
+ )
77
+ })}
78
+ </ToggleGroup>
79
+ )
80
+ }
81
+
82
+ export default FacetTogglesWidget
@@ -0,0 +1,46 @@
1
+ 'use client'
2
+ import React, { type PropsWithChildren } from 'react'
3
+
4
+ import { cn } from '@hanzo/ui/util'
5
+
6
+ import type { FacetsDesc, StringMutator, StringArrayMutator } from '../types'
7
+
8
+ import FacetTogglesWidget from './facet-toggles-widget'
9
+
10
+ const FacetsWidget: React.FC<PropsWithChildren & {
11
+ facets: FacetsDesc
12
+ facetClassNames?: string[]
13
+ mutators: StringMutator[] | StringArrayMutator[]
14
+ multiple?: boolean
15
+ isMobile?: boolean
16
+ id?: string
17
+ className?: string
18
+ }> = ({
19
+ children,
20
+ facets,
21
+ mutators,
22
+ multiple=false,
23
+ facetClassNames,
24
+ isMobile=false,
25
+ className='',
26
+ id='FacetsWidget'
27
+ }) => {
28
+ const horiz = className.includes('flex-row')
29
+ return (
30
+ <div id={id} className={className} >
31
+ {Object.keys(facets).map((key, i) => (
32
+ <FacetTogglesWidget
33
+ key={i}
34
+ multiple={multiple}
35
+ mutator={mutators[i]}
36
+ isMobile={isMobile}
37
+ facetValues={facets[parseInt(key)]}
38
+ className={cn((horiz ? '' : 'mb-2'), (i !== 0 && !horiz) ? 'mt-2' : '', (facetClassNames?.[i]) ?? '')}
39
+ />
40
+ ))}
41
+ {children}
42
+ </div>
43
+ )
44
+ }
45
+
46
+ export default FacetsWidget
@@ -0,0 +1,14 @@
1
+ export { default as AddToCartWidget } from './add-to-cart-widget'
2
+ export { default as Cart } from './cart'
3
+ export { default as CartLineItem } from './cart-line-item'
4
+ export { default as FacetsWidget } from './facets-widget'
5
+ export { default as FacetTogglesWidget,
6
+ type FacetMutatorSingle,
7
+ type FacetMutatorMultiple
8
+ } from './facet-toggles-widget'
9
+ export { default as ProductCard } from './product-card'
10
+ export { default as ProductSelectionMobilePicker } from './product-selection-mobile-picker'
11
+ export { default as ProductSelectionRadioGroup } from './product-selection-radio-group'
12
+ export { default as SelectCategoryAndItemWidget } from './select-category-and-item-widget'
13
+ export { default as SelectItemInCategoryView } from './select-item-in-category-view'
14
+ export * as Icons from './Icons'
@@ -0,0 +1,79 @@
1
+ import React from 'react'
2
+ import Image from 'next/image'
3
+
4
+ import {
5
+ AspectRatio,
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardFooter,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from '@hanzo/ui/primitives'
13
+ import type { ImageDef } from '@hanzo/ui/types'
14
+ import { cn } from '@hanzo/ui/util'
15
+
16
+ import { formatPrice } from '../util'
17
+ import type { LineItem } from '../types'
18
+ import { Icons } from './Icons'
19
+
20
+ import AddToCartWidget from './add-to-cart-widget'
21
+
22
+ interface ProductCardProps extends React.HTMLAttributes<HTMLDivElement> {
23
+ item: LineItem
24
+ }
25
+
26
+ const ProductCard: React.FC<ProductCardProps> = ({
27
+ item,
28
+ className,
29
+ ...props
30
+ }) => {
31
+
32
+ const ProductImage: React.FC<{className?: string}> = ({className=''}) => (
33
+ <Image
34
+ src={item.img!}
35
+ alt={item.title}
36
+ className={className}
37
+ loading='lazy'
38
+ // width={700}
39
+ // height={700}
40
+ fill
41
+ style={{
42
+ objectFit: 'contain',
43
+ }}
44
+ />
45
+ )
46
+
47
+ return (
48
+ <Card
49
+ className={cn('max-h-[360px] lg:min-w-[200px] max-w-[260px] overflow-hidden', className)}
50
+ {...props}
51
+ >
52
+ <CardHeader className='w-full border-b p-6 min-h-[180px] max-h-[240px] relative'>
53
+ {item.img ? (
54
+ <ProductImage className='p-6' />
55
+ ) : (
56
+ <div
57
+ aria-label='Placeholder'
58
+ role='img'
59
+ aria-roledescription='placeholder'
60
+ className='flex h-full items-center justify-center bg-secondary'
61
+ >
62
+ <Icons.barcode className='h-9 w-9 text-muted' aria-hidden='true' />
63
+ </div>
64
+ )}
65
+ </CardHeader>
66
+ <CardContent className='grid gap-2.5 p-4'>
67
+ <CardTitle className='text-sm sm:text-base flex flex-col justify-start items-center line-clap-3'>
68
+ {item.title.split(', ').map((e, i) => (<p key={i}>{e}</p>))}
69
+ <p className='mt-1 font-semibold'>{formatPrice(item.price)}</p>
70
+ </CardTitle>
71
+ </CardContent>
72
+ <CardFooter className='p-4 flex flex-row justify-center'>
73
+ <AddToCartWidget item={item}/>
74
+ </CardFooter>
75
+ </Card>
76
+ )
77
+ }
78
+
79
+ export default ProductCard
@@ -0,0 +1,78 @@
1
+ import React, { useState } from 'react'
2
+ import Picker from 'react-mobile-picker'
3
+
4
+ import type { Product } from '../types'
5
+ import { formatPrice } from '../util'
6
+ import { cn } from '@hanzo/ui/util'
7
+
8
+ // from source code
9
+ interface PickerItemRenderProps {
10
+ selected: boolean;
11
+ }
12
+
13
+ function renderOptions(options: string[], selectedColor: string) {
14
+ return options.map((option) => (
15
+ <Picker.Item key={option} value={option}>
16
+ {({ selected }: PickerItemRenderProps) => (
17
+ <div className={selected ? `font-semibold ${selectedColor}` : 'text-neutral-400'}>{option}</div>
18
+ )}
19
+ </Picker.Item>
20
+ ))
21
+ }
22
+
23
+ // const DEFAULT_HEIGHT = 216
24
+ // const DEFAULT_ITEM_HEIGHT = 36
25
+
26
+
27
+ const ProductSelectionMobilePicker: React.FC<{
28
+ products: Product[]
29
+ selectedSku: string | undefined
30
+ onValueChange: (v: string) => void
31
+ height?: number
32
+ itemHeight?: number
33
+ outerClx?: string
34
+ itemClx?: string
35
+ }> = ({
36
+ products,
37
+ selectedSku,
38
+ onValueChange,
39
+ height,
40
+ itemHeight,
41
+ outerClx='',
42
+ itemClx=''
43
+ }) => {
44
+
45
+ // @ts-ignore
46
+ const onChange = (val, ignore) => {
47
+ console.log("VAL", val)
48
+ console.log("key")
49
+ onValueChange(val.item)
50
+ }
51
+
52
+ return (
53
+ <div className={cn('border border-muted-4 rounded-lg px-2', outerClx)}>
54
+ <Picker
55
+ className=''
56
+ value={selectedSku ? {item: selectedSku} : {item: undefined}}
57
+ onChange={onChange}
58
+ wheelMode="natural"
59
+ height={height}
60
+ itemHeight={itemHeight}
61
+ >
62
+ <Picker.Column name="item">
63
+ {products.map((prod) => (
64
+ <Picker.Item key={prod.sku} value={prod.sku}>
65
+ {({ selected }: PickerItemRenderProps) => (
66
+ <div className={cn(selected ? 'font-semibold text-accent' : 'text-muted', itemClx)}>
67
+ {prod.titleAsOption + ', ' + formatPrice(prod.price)}
68
+ </div>
69
+ )}
70
+ </Picker.Item>
71
+ ))}
72
+ </Picker.Column>
73
+ </Picker>
74
+ </div>
75
+ )
76
+ }
77
+
78
+ export default ProductSelectionMobilePicker
@@ -0,0 +1,34 @@
1
+ 'use client'
2
+
3
+ import { Label, RadioGroup, RadioGroupItem } from '@hanzo/ui/primitives'
4
+ import type { Product } from '../types'
5
+ import { formatPrice } from '../util'
6
+
7
+ const ProductSelectionRadioGroup: React.FC<{
8
+ products: Product[]
9
+ selectedSku: string | undefined
10
+ onValueChange: (v: string) => void
11
+ groupClx?: string
12
+ itemClx?: string
13
+ }> = ({
14
+ products,
15
+ selectedSku,
16
+ onValueChange,
17
+ groupClx='',
18
+ itemClx=''
19
+ }) => (
20
+ <RadioGroup
21
+ className={groupClx}
22
+ onValueChange={onValueChange}
23
+ value={selectedSku}
24
+ >
25
+ {products.map((prod) => (
26
+ <div className={itemClx} key={prod.sku}>
27
+ <RadioGroupItem value={prod.sku} id={prod.sku} />
28
+ <Label htmlFor={prod.sku}>{prod.titleAsOption + ', ' + formatPrice(prod.price)}</Label>
29
+ </div>
30
+ ))}
31
+ </RadioGroup>
32
+ )
33
+
34
+ export default ProductSelectionRadioGroup