@hanzo/commerce 6.2.3 → 6.3.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.
Files changed (35) hide show
  1. package/components/buy/_to_deprecate_select-family-item-card.tsx +0 -1
  2. package/components/buy/_to_deprecate_select-family-item-panel.tsx +1 -1
  3. package/components/buy/add-to-cart-widget.tsx +9 -7
  4. package/components/buy/buy-card.tsx +3 -4
  5. package/components/buy/carousel-buy-card.tsx +121 -87
  6. package/components/buy/multi-family/all-variants-carousel.tsx +257 -0
  7. package/components/{select-family → buy/multi-family}/family-carousel/index.tsx +7 -17
  8. package/components/{select-family → buy/multi-family}/family-carousel/slide.tsx +3 -3
  9. package/components/{select-family → buy/multi-family}/family-carousel/state.ts +6 -2
  10. package/components/buy/multi-family/index.ts +2 -0
  11. package/components/buy/{foo.txt → please-keep.txt} +1 -1
  12. package/components/buy/single-family-selector.tsx +90 -0
  13. package/components/buy/title-and-byline.tsx +25 -0
  14. package/components/cart/cart-panel/cart-line-item.tsx +1 -1
  15. package/components/index.ts +1 -2
  16. package/components/item-selector/button.tsx +28 -18
  17. package/components/item-selector/carousel/index.tsx +197 -0
  18. package/components/item-selector/carousel/slider.tsx +40 -0
  19. package/components/{select-family/level-nodes-widget → node-tabs}/index.tsx +3 -3
  20. package/components/{select-family/level-nodes-widget → node-tabs}/node-image.tsx +1 -1
  21. package/package.json +3 -3
  22. package/service/impls/standalone/actual-line-item.ts +3 -1
  23. package/service/impls/standalone/standalone-service.ts +41 -4
  24. package/types/category-node.ts +21 -9
  25. package/types/commerce-service.ts +12 -13
  26. package/types/family.ts +7 -1
  27. package/types/index.ts +4 -3
  28. package/types/item-selector.ts +46 -9
  29. package/types/multi-family-selector-props.ts +20 -0
  30. package/types/selection-ui-specifier.ts +37 -21
  31. package/util/index.ts +3 -0
  32. package/util/item-selector-options-accessor.ts +32 -0
  33. package/util/multi-family-selector-options-accessor.ts +15 -0
  34. package/util/selection-ui-specifiers.ts +1 -5
  35. package/components/item-selector/carousel.tsx +0 -162
@@ -11,30 +11,20 @@ import {
11
11
  CarouselPrevious,
12
12
  CarouselNext,
13
13
  } from '@hanzo/ui/primitives'
14
- import type { Dimensions } from '@hanzo/ui/types'
15
14
 
16
- import type { Family, FamilyCarouselSlideOptions } from '../../../types'
15
+ import type { MultiFamilySelectorProps } from '../../../../types'
17
16
 
18
17
  import FamilySlide from './slide'
19
- import {
20
- SlideState,
21
- FamilyCarouselState
22
- } from './state'
18
+ import { FamilyCarouselState } from './state'
23
19
 
24
- import { useCommerce } from '../../..'
20
+ import { useCommerce } from '../../../..'
25
21
 
26
- const FamilyCarousel: React.FC<{
27
- families: Family[]
28
- clx?: string
29
- itemClx?: string
30
- slideOptions?: FamilyCarouselSlideOptions
31
- mediaConstraint?: Dimensions
32
- mobile?: boolean
33
- }> = ({
22
+ const FamilyCarousel: React.FC<MultiFamilySelectorProps> = ({
34
23
  families,
35
24
  clx='',
36
25
  itemClx='',
37
- slideOptions,
26
+ itemOptions,
27
+ selectorOptions,
38
28
  mediaConstraint={w: 250, h: 250},
39
29
  mobile=false,
40
30
  }) => {
@@ -70,7 +60,7 @@ const FamilyCarousel: React.FC<{
70
60
  selectedItemRef={slideState}
71
61
  selectSku={slideState.selectSku.bind(slideState)}
72
62
  mediaConstraint={mediaConstraint}
73
- options={slideOptions}
63
+ options={itemOptions}
74
64
  mobile={mobile}
75
65
  clx={itemClx}
76
66
  />
@@ -7,8 +7,8 @@ import {
7
7
  } from '@hanzo/ui/primitives'
8
8
  import type { Dimensions } from '@hanzo/ui/types'
9
9
 
10
- import type { FamilyCarouselSlideOptions, Family, LineItem, ItemSelector } from '../../../types'
11
- import { ButtonItemSelector } from '../..'
10
+ import type { ItemSelectorOptions, Family, LineItem, ItemSelector } from '../../../../types'
11
+ import { ButtonItemSelector } from '../../..'
12
12
  import { cn } from '@hanzo/ui/util'
13
13
 
14
14
  const FamilySlide: React.FC<Omit<ItemSelector, 'items'> & {
@@ -16,7 +16,7 @@ const FamilySlide: React.FC<Omit<ItemSelector, 'items'> & {
16
16
  mediaConstraint: Dimensions
17
17
  clx?: string
18
18
  mobile?: boolean,
19
- options?: FamilyCarouselSlideOptions
19
+ options?: ItemSelectorOptions
20
20
  }> = ({
21
21
  family,
22
22
  selectedItemRef,
@@ -1,4 +1,4 @@
1
- import type { Family, LineItem, ObsLineItemRef, Product } from '../../../types'
1
+ import type { Family, LineItem, ObsLineItemRef, Product } from '../../../../types'
2
2
  import { observable, action, computed, makeObservable } from 'mobx'
3
3
 
4
4
  class SlideState implements ObsLineItemRef {
@@ -49,7 +49,11 @@ class FamilyCarouselState {
49
49
  _map = new Map<string, SlideState>()
50
50
  _syncSku: (sku: string) => void
51
51
 
52
- constructor(fams: Family[], syncSkuToCurrentSlide: (sku: string) => void) {
52
+ constructor(
53
+ fams: Family[],
54
+ // initialFamilyId: string,
55
+ syncSkuToCurrentSlide: (sku: string) => void
56
+ ) {
53
57
 
54
58
  fams.forEach((f, index) => {this._map.set(f.id, new SlideState(
55
59
  f,
@@ -0,0 +1,2 @@
1
+ export { default as FamilyCarousel } from './family-carousel'
2
+ export { default as AllVariantsCarousel } from './all-variants-carousel'
@@ -1,5 +1,5 @@
1
1
  {/*
2
- <LevelNodesWidget
2
+ <NodeTabs
3
3
  className={cn(
4
4
  'grid gap-0 align-stretch justify-normal ' + `grid-cols-${inst.current.parentNode.subNodes!.length}`,
5
5
  'border-b-2 rounded-lg border-level-3 mb-4 -mr-2 -ml-2 max-w-[460px] h-10', // height is needed for iPhone bug
@@ -0,0 +1,90 @@
1
+ import React from 'react'
2
+ import { observer } from 'mobx-react-lite'
3
+
4
+ import { MediaStack, Skeleton } from '@hanzo/ui/primitives'
5
+ import type { Dimensions } from '@hanzo/ui/types'
6
+
7
+ import { useCommerce } from '../..'
8
+
9
+ import type { ItemSelectorOptions, ItemSelectorProps, LineItem } from '../../types'
10
+ import TitleAndByline from './title-and-byline'
11
+ import { accessItemOptions } from '../../util'
12
+ import { cn } from '@hanzo/ui/util'
13
+
14
+
15
+ const DEF_MEDIA_CONSTRAINT = {w: 200, h: 200}
16
+
17
+ const SingleFamilySelector: React.FC<{
18
+ items: LineItem[] // these may be sorted, so are we can't just use currentFamily.products
19
+ Selector: React.ComponentType<ItemSelectorProps>
20
+ selOptions: ItemSelectorOptions | undefined
21
+ scrollable?: boolean
22
+ showItemMedia?: boolean
23
+ mediaConstraint?: Dimensions
24
+ mobile?: boolean
25
+ clx?: string
26
+ titleAreaClx?: string
27
+ }> = ({
28
+ items,
29
+ Selector,
30
+ selOptions,
31
+ scrollable=false,
32
+ showItemMedia=true,
33
+ mediaConstraint=DEF_MEDIA_CONSTRAINT,
34
+ mobile=false,
35
+ clx='',
36
+ titleAreaClx=''
37
+ }) => {
38
+
39
+ const cmmc = useCommerce()
40
+
41
+
42
+ const MyTitleAndByline: React.FC<{clx?: string}> = observer(({
43
+ clx=''
44
+ }) => {
45
+ const { familyTitle, showFamilyByline } = accessItemOptions(selOptions)
46
+ const title = (familyTitle === 'none' ?
47
+ undefined
48
+ :
49
+ (familyTitle === 'long' ?
50
+ cmmc.currentFamily?.title
51
+ :
52
+ (cmmc.currentFamily?.titleShort ?? cmmc.currentFamily?.title)
53
+ )
54
+ )
55
+ const byline = (familyTitle !== 'none') && showFamilyByline ? cmmc.currentFamily?.byline : undefined
56
+ return ( <TitleAndByline clx={clx} title={title} byline={byline}/> )
57
+ })
58
+
59
+ const ItemMedia: React.FC = observer(() => (
60
+ cmmc.currentItem ? (
61
+ <MediaStack
62
+ media={cmmc.currentItem!}
63
+ constrainTo={mediaConstraint}
64
+ clx={'mb-2 ' + (scrollable ? 'shrink-0' : '')}
65
+ />
66
+ ) : (
67
+ <Skeleton className={'w-[200px] h-[200px] my-2 ' + (scrollable ? 'shrink-0' : '')}/>
68
+ )
69
+ ))
70
+
71
+ return (
72
+ <div className={cn(clx, 'flex flex-col items-center', (scrollable ? 'shrink' : ''))}>
73
+ <MyTitleAndByline clx={cn(titleAreaClx, (scrollable ? 'shrink-0' : ''))} />
74
+ {showItemMedia && (<ItemMedia />)}
75
+ {items && (
76
+ <Selector
77
+ items={items}
78
+ selectedItemRef={cmmc}
79
+ selectSku={cmmc.setCurrentItem.bind(cmmc)}
80
+ scrollable={scrollable}
81
+ mobile={mobile}
82
+ options={selOptions}
83
+ clx={(scrollable ? 'shrink' : '')}
84
+ />
85
+ )}
86
+ </div>
87
+ )
88
+ }
89
+
90
+ export default SingleFamilySelector
@@ -0,0 +1,25 @@
1
+ import React from 'react'
2
+
3
+ import { ApplyTypography } from '@hanzo/ui/primitives'
4
+ import { cn } from '@hanzo/ui/util'
5
+
6
+ const TitleAndByline: React.FC<{
7
+ title: string | undefined
8
+ byline?: string | undefined
9
+ clx?: string
10
+ bylineClx?: string
11
+ }> = ({
12
+ title,
13
+ byline,
14
+ clx='',
15
+ bylineClx=''
16
+ }) => ( (title || byline) ? (
17
+ <ApplyTypography className={cn('flex flex-col items-center !gap-0 [&>*]:!m-0 ', clx)} >
18
+ <h4>{title}</h4>
19
+ {byline && (<h6 className={bylineClx}>{byline}</h6>)}
20
+ </ApplyTypography>
21
+ ) : (null)
22
+ )
23
+
24
+ export default TitleAndByline
25
+
@@ -61,7 +61,7 @@ const CartLineItem: React.FC<{
61
61
  </div>
62
62
  <div className='flex flex-row items-center justify-between w-full'>
63
63
  <div className='flex flex-row items-center'>
64
- <AddToCartWidget className='' item={item} size='xs' buttonClx='h-8 md:h-6' variant='minimal'/>
64
+ <AddToCartWidget variant='minimal' item={item} buttonClx='!h-8 md:!h-6' />
65
65
  {item.quantity > 1 && (<span className='pl-2.5'>{'@' + formatCurrencyValue(item.price)}</span>)}
66
66
  </div>
67
67
  <div className='flex flex-row gap-1 items-center justify-end'>
@@ -7,8 +7,7 @@ export { default as BuyDrawer } from './buy/buy-drawer'
7
7
  export { default as CartAccordian } from './cart/cart-accordian'
8
8
  export { default as CartPanel } from './cart/cart-panel'
9
9
 
10
- export { default as LevelNodesWidget } from './select-family/level-nodes-widget'
11
-
10
+ export { default as NodeTabs } from './node-tabs'
12
11
  export { default as PaymentStepForm } from './checkout/payment-step-form'
13
12
  export { default as ShippingStepForm } from './checkout/shipping-step-form'
14
13
 
@@ -1,5 +1,5 @@
1
1
  'use client'
2
- import React from 'react'
2
+ import React, { useEffect, useRef } from 'react'
3
3
  import { observer } from 'mobx-react-lite'
4
4
 
5
5
  import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
@@ -14,7 +14,7 @@ import { cn } from '@hanzo/ui/util'
14
14
  import type { Dimensions } from '@hanzo/ui/types'
15
15
 
16
16
  import type { ItemSelectorProps, LineItem } from '../../types'
17
- import { formatCurrencyValue } from '../../util'
17
+ import { accessItemOptions, formatCurrencyValue } from '../../util'
18
18
 
19
19
  import QuantityIndicator from '../quantity-indicator'
20
20
 
@@ -72,15 +72,21 @@ const ButtonItemSelector: React.FC<ItemSelectorProps> = observer(({
72
72
  options={}
73
73
  }) => {
74
74
 
75
- const imageButtons = 'imageButtons' in options ? options.imageButtons : false
76
- const showFamily = 'showFamily' in options ? options.showFamily : false
77
- const showPrice = 'showPrice' in options ? options.showPrice : true
78
- const showQuantity = 'showQuantity' in options ? options.showQuantity : false
79
- const horizButtons = 'horizButtons' in options ? options.horizButtons : false
75
+ const {
76
+ showPrice,
77
+ showQuantity,
78
+ showFamilyInOption,
79
+ buttonType,
80
+ horizButtons,
81
+ } = accessItemOptions(options)
82
+
83
+ const showImage = buttonType !== 'text'
84
+ const showText = buttonType !== 'image'
85
+ const showBoth = buttonType === 'image-and-text'
80
86
 
81
87
  const labelAndPrice = (item : LineItem) => (
82
- (showFamily ? (item.familyTitle + ', ' + item.optionLabel) : item.optionLabel) +
83
- (showPrice ? ((showFamily ? ': ' : ', ') + formatCurrencyValue(item.price)) : '')
88
+ (showFamilyInOption ? (item.familyTitle + ', ' + item.optionLabel) : item.optionLabel) +
89
+ (showPrice ? ((showFamilyInOption ? ': ' : ', ') + formatCurrencyValue(item.price)) : '')
84
90
  )
85
91
 
86
92
  const Item: React.FC<{
@@ -91,22 +97,24 @@ const ButtonItemSelector: React.FC<ItemSelectorProps> = observer(({
91
97
  selected
92
98
  }) => {
93
99
 
94
- const textClx = (selected) ? 'text-accent ' : 'text-muted'
100
+ const textClx = (selected) ? 'text-accent' : 'text-muted'
95
101
  const cursorClx = (selected) ? 'hover:cursor-default' : 'hover:cursor-pointer'
96
- const justifyClx = (!imageButtons) ? 'justify-center ' : ''
97
- const paddingClx = (imageButtons) ? (scrollable ? 'px-4' : 'px-2' ) : ''
102
+ const justifyClx = (showBoth) ? '' : 'justify-center'
103
+ // If no image, the Label must fill the entire button since it has to be
104
+ // clickable all the way to the border
105
+ const paddingClx = (showImage) ? (scrollable ? 'px-4 py-2' : 'px-2 py-2' ) : ''
98
106
  let bgClx = ''
99
107
  let borderClx = ''
100
108
  if (scrollable) {
101
109
  borderClx += (selected) ? 'border-foreground border ' : 'border-b border-muted-2 '
102
110
  }
103
- else if (!imageButtons) {
111
+ else if (!showImage) {
104
112
  borderClx += 'border rounded-lg '
105
113
  borderClx += (selected) ? 'border-foreground ' : 'border-muted-2 '
106
114
  bgClx += 'hover:bg-level-2'
107
115
  }
108
116
 
109
- const outerClx = ['h-10 py-2 flex items-center', justifyClx, paddingClx, bgClx, borderClx]
117
+ const outerClx = ['h-10 flex items-center', justifyClx, paddingClx, bgClx, borderClx]
110
118
 
111
119
  return (
112
120
  <div className={cn(...outerClx, itemClx )} data-vaul-no-drag >
@@ -116,13 +124,15 @@ const ButtonItemSelector: React.FC<ItemSelectorProps> = observer(({
116
124
  className={cn(
117
125
  cursorClx,
118
126
  (scrollable ? '' : 'border-transparent border-2 data-[state=checked]:border-foreground'),
119
- imageButtons ? 'mr-2' : 'hidden'
127
+ showBoth ? 'mr-2' : (showImage ? '' : 'hidden')
120
128
  )}
121
129
  />
122
130
  <Label htmlFor={item.sku} className={cn(
123
131
  showQuantity ? 'flex items-center' : 'block',
124
132
  textClx,
125
133
  cursorClx,
134
+ (showImage ? '' : 'self-stretch w-full flex items-center justify-center px-3 py-2'),
135
+ (showText ? '' : 'hidden')
126
136
  )}>
127
137
  <div className={showQuantity ? 'grow' : ''}>{labelAndPrice(item)}</div>
128
138
  {showQuantity && (
@@ -140,10 +150,10 @@ const ButtonItemSelector: React.FC<ItemSelectorProps> = observer(({
140
150
 
141
151
  return items.length > 1 ? (
142
152
  <RadioGroup
143
- className={cn('flex',
153
+ className={cn(
144
154
  (scrollable ? 'shrink min-h-0 gap-0' : (mobile ? 'gap-3' : 'gap-1')),
145
- (horizButtons ? 'flex-row gap-0' : 'flex-col'),
146
- (mobile && !imageButtons) ? 'min-w-pr-50' : '',
155
+ (horizButtons ? `grid grid-cols-${items.length} gap-1` : 'flex flex-col'),
156
+ (mobile && showText) ? 'min-w-pr-50' : '',
147
157
  clx,
148
158
  )}
149
159
  onValueChange={selectSku}
@@ -0,0 +1,197 @@
1
+ 'use client'
2
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
3
+ import { reaction } from 'mobx'
4
+ import { observer } from 'mobx-react-lite'
5
+
6
+ import { cn } from '@hanzo/ui/util'
7
+ import type { Dimensions } from '@hanzo/ui/types'
8
+
9
+ import {
10
+ ApplyTypography,
11
+ type CarouselOptions,
12
+ type CarouselApi,
13
+ Carousel,
14
+ CarouselContent,
15
+ CarouselItem,
16
+ CarouselPrevious,
17
+ CarouselNext,
18
+ MediaStack,
19
+ } from '@hanzo/ui/primitives'
20
+
21
+ import type { ItemSelectorProps, LineItem } from '../../../types'
22
+ import { formatCurrencyValue, accessItemOptions } from '../../../util'
23
+
24
+ import QuantityIndicator from '../../quantity-indicator'
25
+ import ItemCarouselSlider from './slider'
26
+
27
+ const DEFAULT_CONSTRAINT = {w: 250, h: 250}
28
+
29
+ interface CarouselItemSelectorPropsExt {
30
+ constrainTo: Dimensions
31
+ options?: CarouselOptions
32
+ /** Do not show Family and / or Item title and Price */
33
+ imageOnly?: boolean
34
+ }
35
+
36
+ const ItemSlide: React.FC<{
37
+ item: LineItem,
38
+ constrainTo: Dimensions
39
+ clx?: string
40
+ }> = ({
41
+ item,
42
+ constrainTo,
43
+ clx=''
44
+ }) => (
45
+ <CarouselItem className={cn('p-2 flex flex-col justify-center items-center', clx)}>
46
+ <MediaStack media={item} constrainTo={constrainTo} clx='my-4' />
47
+ </CarouselItem>
48
+ )
49
+
50
+ const CarouselItemSelector: React.FC<ItemSelectorProps> = observer(({
51
+ items,
52
+ selectSku,
53
+ selectedItemRef: itemRef,
54
+ clx='',
55
+ itemClx='',
56
+ options={},
57
+ ext={
58
+ options: {loop: true},
59
+ constrainTo: DEFAULT_CONSTRAINT,
60
+ imageOnly: false
61
+ } satisfies CarouselItemSelectorPropsExt
62
+ }) => {
63
+
64
+ const {
65
+ showPrice,
66
+ showQuantity,
67
+ showFamilyInOption,
68
+ showByline,
69
+ showSlider,
70
+ } = accessItemOptions(options)
71
+
72
+ const elbaApiRef = useRef<CarouselApi | undefined>(undefined)
73
+ const scrollToRef = useRef<((index: number) => void) | undefined>(undefined)
74
+ const dontRespondRef = useRef<boolean>(false)
75
+
76
+ const setApi = (api: CarouselApi) => {elbaApiRef.current = api}
77
+ const setScrollTo = (scrollTo: (index: number) => void) => { scrollToRef.current = scrollTo}
78
+
79
+ const carouselOptions = 'options' in ext ? ext.options : undefined
80
+ const constrainTo = 'constrainTo' in ext ? ext.constrainTo : DEFAULT_CONSTRAINT
81
+ const imageOnly = 'imageOnly' in ext ? ext.imageOnly : false
82
+
83
+ const onSelect = useCallback((emblaApi: CarouselApi) => {
84
+ if (dontRespondRef.current) {
85
+ dontRespondRef.current = false
86
+ return
87
+ }
88
+ const index = emblaApi.selectedScrollSnap()
89
+ if (index !== -1) {
90
+ selectSku(items[index].sku)
91
+ if (scrollToRef.current) {
92
+ scrollToRef.current(index)
93
+ }
94
+ }
95
+ dontRespondRef.current = false
96
+ }, [scrollToRef.current ])
97
+
98
+ useEffect(() => {
99
+ return reaction(
100
+ () => (itemRef.item),
101
+ (item) => {
102
+ if (elbaApiRef.current) {
103
+ const index = items.findIndex((el) => (el.sku === item?.sku))
104
+ if (index !== -1) {
105
+ dontRespondRef.current = true
106
+ elbaApiRef.current.scrollTo(index)
107
+ }
108
+ }
109
+ }
110
+ )
111
+ }, [])
112
+
113
+ const optionLabel = () => (
114
+ showFamilyInOption ?
115
+ (itemRef.item?.familyTitle + ', ' + itemRef.item?.optionLabel)
116
+ :
117
+ itemRef.item?.optionLabel
118
+ )
119
+
120
+ const debugBorder = (c: 'r' | 'g' | 'b'): string => {
121
+ return ''
122
+
123
+ switch (c) {
124
+ case 'r': return ' border border-[#ffaaaa] '
125
+ case 'g': return ' border border-[#aaffaa] '
126
+ case 'b': return ' border border-[#aaaaff] '
127
+ }
128
+ }
129
+
130
+ const onScrollIndexChange = (index: number) => {
131
+ dontRespondRef.current = true
132
+ selectSku(items![index].sku)
133
+ elbaApiRef.current?.scrollTo(index)
134
+ }
135
+
136
+ return (
137
+ <div className={cn('w-full flex flex-col items-center', clx)}>
138
+ <Carousel
139
+ options={showSlider ? {...carouselOptions, loop: false} : carouselOptions }
140
+ className={'w-full px-2' + debugBorder('r')}
141
+ onCarouselSelect={onSelect}
142
+ setApi={setApi}
143
+ >
144
+ <CarouselContent>
145
+ {items.map((item) => (
146
+ <ItemSlide key={item.sku} item={item} constrainTo={constrainTo} clx={itemClx} />
147
+ ))}
148
+ </CarouselContent>
149
+ {!showSlider && (<>
150
+ <CarouselPrevious className='left-1'/>
151
+ <CarouselNext className='right-1'/>
152
+ </>)}
153
+ </Carousel>
154
+
155
+ {(!imageOnly && itemRef.item) && (<>
156
+ <ApplyTypography className='flex flex-col items-center [&>*]:!m-0 !gap-1 '>
157
+ <div className={
158
+ 'flex items-center gap-1 [&>*]:!m-0 ' + debugBorder('g') +
159
+ (showFamilyInOption ? 'flex-col' : 'flex-row')
160
+ }>
161
+ <h6 className='font-semibold'>
162
+ {optionLabel() + (showPrice && !showFamilyInOption ? ',' : '')}
163
+ </h6>
164
+ <div className={
165
+ 'flex items-center gap-1 [&>*]:!m-0 flex-row ' + debugBorder('b') +
166
+ (showFamilyInOption ? 'w-full justify-between' : '')
167
+ }>
168
+ {showPrice && (<p>{formatCurrencyValue(itemRef.item.price)}</p>)}
169
+ {showQuantity && (
170
+ <QuantityIndicator
171
+ item={itemRef.item}
172
+ clx='h-[22px] ml-4'
173
+ iconClx='fill-foreground'
174
+ digitClx='not-typography font-semibold text-primary-fg leading-none font-sans text-xs'
175
+ />
176
+ )}
177
+ </div>
178
+ </div>
179
+ {showByline && itemRef.item.byline && (<p>{itemRef.item.byline}</p>)}
180
+ </ApplyTypography>
181
+ {showSlider && (
182
+ <ItemCarouselSlider
183
+ clx='mt-5 w-[320px]'
184
+ numStops={items.length}
185
+ setScrollTo={setScrollTo}
186
+ onIndexChange={onScrollIndexChange}
187
+ />
188
+ )}
189
+ </>)}
190
+ </div>
191
+ )
192
+ })
193
+
194
+ export {
195
+ type CarouselItemSelectorPropsExt,
196
+ CarouselItemSelector as default
197
+ }
@@ -0,0 +1,40 @@
1
+ 'use client'
2
+ import React, { useEffect, useState } from 'react'
3
+
4
+ import { Slider } from '@hanzo/ui/primitives'
5
+
6
+ const ItemCarouselSlider: React.FC<{
7
+ clx?: string
8
+ setScrollTo: (scrollTo: (index: number) => void) => void
9
+ onIndexChange: (i: number) => void
10
+ numStops: number
11
+ }> = ({
12
+ clx='',
13
+ setScrollTo,
14
+ onIndexChange,
15
+ numStops,
16
+ }) => {
17
+
18
+ const [index, setIndex] = useState<number>(0)
19
+ useEffect(() => { setScrollTo(setIndex) }, [setScrollTo])
20
+
21
+ const onValueChange = (v: number[]) => { setIndex(v[0]); onIndexChange(v[0]) }
22
+
23
+ return (
24
+ <Slider
25
+ className={clx}
26
+ thumbClx='w-8 border-muted border-2 bg-level-1 focus-visible:ring-0 focus-visible:ring-offset-0 transition-none'
27
+ trackBgClx='bg-level-3'
28
+ rangeBgClx='bg-level-3'
29
+ thumbSlidingClx='bg-muted-2'
30
+ defaultValue={[0]}
31
+ min={0}
32
+ max={numStops - 1}
33
+ step={1}
34
+ value={[index]}
35
+ onValueChange={onValueChange}
36
+ />
37
+ )
38
+ }
39
+
40
+ export default ItemCarouselSlider
@@ -4,10 +4,10 @@ import React, { useState } from 'react'
4
4
  import { ToggleGroup, ToggleGroupItem} from "@hanzo/ui/primitives"
5
5
  import { cn } from '@hanzo/ui/util'
6
6
 
7
- import type { CategoryNode, StringMutator, StringArrayMutator } from '../../../types'
7
+ import type { CategoryNode, StringMutator, StringArrayMutator } from '../../types'
8
8
  import NodeImage from './node-image'
9
9
 
10
- const LevelNodesWidget: React.FC<{
10
+ const NodeTabs: React.FC<{
11
11
  levelNodes: CategoryNode[]
12
12
  mutator: StringMutator | StringArrayMutator
13
13
  multiple?: boolean
@@ -88,4 +88,4 @@ const LevelNodesWidget: React.FC<{
88
88
  )
89
89
  }
90
90
 
91
- export default LevelNodesWidget
91
+ export default NodeTabs
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import Image from 'next/image'
3
3
 
4
- import type { CategoryNode } from '../../../types'
4
+ import type { CategoryNode } from '../../types'
5
5
 
6
6
  const ICON_SIZE = 20
7
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/commerce",
3
- "version": "6.2.3",
3
+ "version": "6.3.0",
4
4
  "description": "e-commerce framework.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -40,8 +40,8 @@
40
40
  "square": "^35.0.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@hanzo/auth": "^2.3.4",
44
- "@hanzo/ui": "^3.5.0",
43
+ "@hanzo/auth": "^2.3.5",
44
+ "@hanzo/ui": "^3.6.0",
45
45
  "@hookform/resolvers": "^3.3.4",
46
46
  "@radix-ui/react-radio-group": "^1.1.3",
47
47
  "firebase": "^10.8.0",
@@ -5,7 +5,7 @@ import {
5
5
  observable,
6
6
  } from 'mobx'
7
7
 
8
- import type { ImageDef, VideoDef } from '@hanzo/ui/types'
8
+ import type { ImageDef, MediaTransform, VideoDef } from '@hanzo/ui/types'
9
9
 
10
10
  import type { Product, LineItem, CommerceService } from '../../../types'
11
11
 
@@ -36,6 +36,7 @@ class ActualLineItem
36
36
  img?: ImageDef
37
37
  video?: VideoDef
38
38
  animation?: string
39
+ mediaTransform?: MediaTransform
39
40
  optionImg?: ImageDef
40
41
  timeAdded: number = 0 // timeAdded of being added to cart
41
42
 
@@ -53,6 +54,7 @@ class ActualLineItem
53
54
  this.video = prod.video
54
55
  this.animation = prod.animation
55
56
  this.optionImg = prod.optionImg
57
+ this.mediaTransform = prod.mediaTransform
56
58
 
57
59
  if (snap) {
58
60
  this.qu = snap.quantity