@hanzo/commerce 6.2.4 → 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.
- package/components/buy/_to_deprecate_select-family-item-card.tsx +0 -1
- package/components/buy/_to_deprecate_select-family-item-panel.tsx +1 -1
- package/components/buy/add-to-cart-widget.tsx +9 -7
- package/components/buy/buy-card.tsx +3 -4
- package/components/buy/carousel-buy-card.tsx +120 -85
- package/components/buy/multi-family/all-variants-carousel.tsx +257 -0
- package/components/{select-family → buy/multi-family}/family-carousel/index.tsx +7 -17
- package/components/{select-family → buy/multi-family}/family-carousel/slide.tsx +3 -3
- package/components/{select-family → buy/multi-family}/family-carousel/state.ts +6 -2
- package/components/buy/multi-family/index.ts +2 -0
- package/components/buy/{foo.txt → please-keep.txt} +1 -1
- package/components/buy/single-family-selector.tsx +90 -0
- package/components/buy/title-and-byline.tsx +25 -0
- package/components/cart/cart-panel/cart-line-item.tsx +1 -1
- package/components/index.ts +1 -2
- package/components/item-selector/button.tsx +28 -18
- package/components/item-selector/carousel/index.tsx +197 -0
- package/components/item-selector/carousel/slider.tsx +40 -0
- package/components/{select-family/level-nodes-widget → node-tabs}/index.tsx +3 -3
- package/components/{select-family/level-nodes-widget → node-tabs}/node-image.tsx +1 -1
- package/package.json +2 -2
- package/service/impls/standalone/actual-line-item.ts +3 -1
- package/service/impls/standalone/standalone-service.ts +41 -4
- package/types/category-node.ts +21 -9
- package/types/commerce-service.ts +12 -13
- package/types/family.ts +7 -1
- package/types/index.ts +4 -3
- package/types/item-selector.ts +46 -9
- package/types/multi-family-selector-props.ts +20 -0
- package/types/selection-ui-specifier.ts +37 -21
- package/util/index.ts +3 -0
- package/util/item-selector-options-accessor.ts +32 -0
- package/util/multi-family-selector-options-accessor.ts +15 -0
- package/util/selection-ui-specifiers.ts +1 -5
- package/components/item-selector/carousel.tsx +0 -162
|
@@ -49,6 +49,7 @@ class StandaloneService
|
|
|
49
49
|
private _promo: Promo | null = null
|
|
50
50
|
|
|
51
51
|
private _options : StandaloneServiceOptions
|
|
52
|
+
private _currentFamily: Family | undefined = undefined
|
|
52
53
|
private _currentItem: ActualLineItem | undefined = undefined
|
|
53
54
|
|
|
54
55
|
constructor(
|
|
@@ -78,10 +79,12 @@ class StandaloneService
|
|
|
78
79
|
StandaloneService,
|
|
79
80
|
'_selectedPaths' |
|
|
80
81
|
'_currentItem' |
|
|
82
|
+
'_currentFamily' |
|
|
81
83
|
'_promo'
|
|
82
84
|
>(this, {
|
|
83
85
|
_selectedPaths : observable.deep,
|
|
84
86
|
_currentItem: observable.shallow,
|
|
87
|
+
_currentFamily: observable.shallow,
|
|
85
88
|
_promo: observable,
|
|
86
89
|
})
|
|
87
90
|
|
|
@@ -95,8 +98,11 @@ class StandaloneService
|
|
|
95
98
|
selectedFamilies: computed,
|
|
96
99
|
hasSelection: computed,
|
|
97
100
|
setCurrentItem: action,
|
|
101
|
+
setCurrentFamily: action,
|
|
98
102
|
currentItem: computed,
|
|
103
|
+
currentFamily: computed,
|
|
99
104
|
item: computed,
|
|
105
|
+
family: computed,
|
|
100
106
|
selectedPaths: computed,
|
|
101
107
|
appliedPromo: computed,
|
|
102
108
|
setAppliedPromo: action,
|
|
@@ -154,7 +160,7 @@ class StandaloneService
|
|
|
154
160
|
let error: string | undefined = undefined
|
|
155
161
|
|
|
156
162
|
try {
|
|
157
|
-
if (node.subNodes && atEnd && node.
|
|
163
|
+
if (node.subNodes && atEnd && node.outermost) {
|
|
158
164
|
role = 'multi-family'
|
|
159
165
|
families = node.subNodes.map((sub) => {
|
|
160
166
|
const familyId = skuPath + sep.tok + sub.skuToken
|
|
@@ -167,7 +173,7 @@ class StandaloneService
|
|
|
167
173
|
}
|
|
168
174
|
else if (!node.subNodes && (atEnd || possibleSKU)) {
|
|
169
175
|
const _skuPath = (possibleSKU) ? getParentPath(skuPath) : skuPath
|
|
170
|
-
if (parent?.
|
|
176
|
+
if (parent?.outermost) {
|
|
171
177
|
role = 'family-in-multi-family'
|
|
172
178
|
const fam = this._familyMap.get(_skuPath)
|
|
173
179
|
if (!fam) {
|
|
@@ -364,12 +370,13 @@ class StandaloneService
|
|
|
364
370
|
}
|
|
365
371
|
}
|
|
366
372
|
return undefined
|
|
367
|
-
})();
|
|
373
|
+
})(); // necessary semi
|
|
368
374
|
|
|
375
|
+
this.setCurrentFamily(this._currentItem ? this._currentItem.familyId : undefined)
|
|
369
376
|
return !!this._currentItem
|
|
370
377
|
}
|
|
371
378
|
|
|
372
|
-
/* ObsLineItemRef */
|
|
379
|
+
/* for ObsLineItemRef */
|
|
373
380
|
get item(): LineItem | undefined {
|
|
374
381
|
return this._currentItem
|
|
375
382
|
}
|
|
@@ -378,6 +385,36 @@ class StandaloneService
|
|
|
378
385
|
return this._currentItem
|
|
379
386
|
}
|
|
380
387
|
|
|
388
|
+
setCurrentFamily(id: string | undefined): boolean {
|
|
389
|
+
|
|
390
|
+
if (id === undefined || id.length === 0) {
|
|
391
|
+
this._currentFamily = undefined
|
|
392
|
+
return true
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const fam = this._familyMap.get(id)
|
|
396
|
+
this._currentFamily = fam // undef ok
|
|
397
|
+
|
|
398
|
+
if (
|
|
399
|
+
this._currentFamily &&
|
|
400
|
+
this._currentItem &&
|
|
401
|
+
this._currentItem.familyId !== this._currentFamily.id
|
|
402
|
+
) {
|
|
403
|
+
this._currentItem = undefined
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return !!this._currentFamily
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
get currentFamily(): Family | undefined {
|
|
410
|
+
return this._currentFamily
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/* for ObsFamilyRef */
|
|
414
|
+
get family(): Family | undefined {
|
|
415
|
+
return this._currentFamily
|
|
416
|
+
}
|
|
417
|
+
|
|
381
418
|
selectPaths(sel: SelectedPaths): Family[] {
|
|
382
419
|
runInAction (() => {
|
|
383
420
|
this._selectedPaths = this._processAndValidate(sel)
|
package/types/category-node.ts
CHANGED
|
@@ -4,19 +4,31 @@ interface CategoryNode {
|
|
|
4
4
|
skuToken: string // a token in the sku
|
|
5
5
|
label: string;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* Normally, a node
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Explicitly marks this node as a "last user-visible" / "outermost" Category.
|
|
8
|
+
* Normally, a node is "outermost" when it has no subnodes
|
|
9
|
+
* (and thus is a family with products -- "single-family")
|
|
10
|
+
* But if it *has* subnodes, and its subNodes are actually sibling families
|
|
11
|
+
* of a Category, it must be marked as "outermost" ("multi-family")
|
|
11
12
|
* */
|
|
12
|
-
|
|
13
|
+
outermost?: boolean;
|
|
13
14
|
img? : string | ReactNode // icon is required
|
|
14
15
|
imgAR? : number // helps with svgs
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
/**
|
|
18
|
+
* One word prompt that describes the 'meaning' of
|
|
19
|
+
* the subnodes.
|
|
20
|
+
* For example, if a node is 'Lux Credit',
|
|
21
|
+
* it's subNodesLabel might be 'Level', so some UI's
|
|
22
|
+
* might be configured to render 'Level: Black'
|
|
23
|
+
*
|
|
24
|
+
* or something like...
|
|
25
|
+
*
|
|
26
|
+
* <Tabs title={parentNode.label + ': ' + parentNode.subNodesLabel} values={families.map((f) => (f.shortName))} />
|
|
27
|
+
*
|
|
28
|
+
* ...which renders
|
|
29
|
+
*
|
|
30
|
+
* Lux Credit Levels:
|
|
31
|
+
* Black Elite Founder Sovereign
|
|
20
32
|
*/
|
|
21
33
|
subNodesLabel?: string
|
|
22
34
|
subNodes?: CategoryNode[]
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { LineItem, ObsLineItemRef } from './line-item'
|
|
2
2
|
import type { CategoryNode, SelectedPaths, CategoryNodeRole } from './category-node'
|
|
3
|
-
import type Family from './family'
|
|
3
|
+
import type { Family, ObsFamilyRef} from './family'
|
|
4
4
|
import type Promo from './promo'
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
interface CommerceService extends ObsLineItemRef {
|
|
7
|
+
interface CommerceService extends ObsLineItemRef, ObsFamilyRef {
|
|
8
8
|
|
|
9
9
|
/** Items in cart */
|
|
10
10
|
get cartItems(): LineItem[]
|
|
@@ -97,23 +97,22 @@ interface CommerceService extends ObsLineItemRef {
|
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* For convenience, so widgets can share state.
|
|
100
|
-
* "current" is unrelated to what
|
|
101
|
-
*
|
|
102
|
-
* */
|
|
103
|
-
setCurrentItem(sku: string | undefined): boolean // valid sku and was set.
|
|
104
|
-
/**
|
|
105
|
-
* For convenience, so widgets can share state.
|
|
106
|
-
* "current" is unrelated to what is "selected",
|
|
107
|
-
* ie, facets' values
|
|
100
|
+
* "current" is unrelated to what branches and items are
|
|
101
|
+
* "selected" (with CategoryNode's and paths)
|
|
108
102
|
*
|
|
109
|
-
*
|
|
103
|
+
* SEE ALSO: from ObsLineItemRef and ObsFamilyRef, there are also
|
|
110
104
|
* get item(): LineItem | undefined
|
|
111
|
-
*
|
|
105
|
+
* get family(): Family | undefined
|
|
106
|
+
*
|
|
107
|
+
* These simply delegate to these functions
|
|
112
108
|
* */
|
|
109
|
+
setCurrentItem(sku: string | undefined): boolean // valid sku and was set.
|
|
113
110
|
get currentItem(): LineItem | undefined
|
|
111
|
+
setCurrentFamily(id: string | undefined): boolean // valid id and was set.
|
|
112
|
+
get currentFamily(): Family | undefined
|
|
114
113
|
|
|
114
|
+
// utility
|
|
115
115
|
getFamily(id: string): Family | undefined
|
|
116
|
-
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
|
package/types/family.ts
CHANGED
package/types/index.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
export type { default as Family } from './family'
|
|
3
2
|
export type { default as CommerceService } from './commerce-service'
|
|
3
|
+
export type { default as MultiFamilySelectorProps } from './multi-family-selector-props'
|
|
4
4
|
export type { default as Product } from './product'
|
|
5
5
|
export type { default as Promo } from './promo'
|
|
6
6
|
export type { default as TokenSeparators } from './token-separators'
|
|
7
7
|
|
|
8
|
+
export * from './category-node'
|
|
8
9
|
export * from './checkout'
|
|
10
|
+
export * from './family'
|
|
9
11
|
export * from './item-selector'
|
|
10
12
|
export * from './line-item'
|
|
11
|
-
export * from './category-node'
|
|
12
|
-
export * from './string-mutator'
|
|
13
13
|
export * from './selection-ui-specifier'
|
|
14
|
+
export * from './string-mutator'
|
package/types/item-selector.ts
CHANGED
|
@@ -6,27 +6,62 @@ interface ItemSelector {
|
|
|
6
6
|
selectSku: (sku: string) => void
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/** default: 'text' */
|
|
10
|
+
type ItemButton = 'text' | 'image-and-text' | 'image'
|
|
11
|
+
|
|
12
|
+
// NOTE: if a field is added here, it should also
|
|
13
|
+
// be added to util/item-selector-options-accessor.ts
|
|
9
14
|
type ItemSelectorOptions = {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* default: short
|
|
18
|
+
* 'short': product.shortTitle if defined
|
|
19
|
+
* 'long': always us product.title, even if .shortTitle is defined
|
|
20
|
+
* 'none': do not show title AND BYLINE
|
|
21
|
+
*
|
|
22
|
+
* (NOTE: *not* the same as buttonType = 'none')
|
|
23
|
+
*/
|
|
24
|
+
familyTitle?: 'none' | 'long' | 'short'
|
|
25
|
+
|
|
26
|
+
/** only if title shown
|
|
27
|
+
* default: false */
|
|
28
|
+
showFamilyByline?: boolean
|
|
29
|
+
|
|
10
30
|
/**
|
|
11
31
|
* Whether the item label includes the Family name.
|
|
12
32
|
* eg, 'Minted Bar, 1oz' vs '1oz'
|
|
13
33
|
* default: false.
|
|
14
34
|
*/
|
|
15
|
-
|
|
35
|
+
showFamilyInOption?: boolean
|
|
36
|
+
/** default: true if it exists */
|
|
37
|
+
showByline?: boolean
|
|
38
|
+
|
|
39
|
+
/** default: true */
|
|
40
|
+
showPrice?: boolean
|
|
41
|
+
|
|
16
42
|
/**
|
|
17
43
|
* Show the current item quantity along with title and price.
|
|
18
44
|
* default: false
|
|
19
45
|
*/
|
|
20
46
|
showQuantity?: boolean
|
|
21
47
|
|
|
22
|
-
|
|
23
|
-
showPrice?: boolean
|
|
24
|
-
|
|
25
|
-
/** default: false */
|
|
26
|
-
imageButtons?: boolean
|
|
48
|
+
buttonType?: ItemButton
|
|
27
49
|
|
|
28
|
-
/** default: false */
|
|
50
|
+
/** (button selector only) default: false */
|
|
29
51
|
horizButtons?: boolean
|
|
52
|
+
|
|
53
|
+
/** (carousel selector only)
|
|
54
|
+
* affects sort. See below.
|
|
55
|
+
* default: false */
|
|
56
|
+
showSlider?: boolean
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Sort by cost.
|
|
60
|
+
* If it's a carousel selector and 'showSlider' is true,
|
|
61
|
+
* defaults to 'asc', unless another value is explicitly specified.
|
|
62
|
+
* Otherwise, defaults to 'none'
|
|
63
|
+
* */
|
|
64
|
+
sort?: 'none' | 'asc' | 'desc'
|
|
30
65
|
}
|
|
31
66
|
|
|
32
67
|
interface _ItemSelectorCompProps {
|
|
@@ -37,8 +72,10 @@ interface _ItemSelectorCompProps {
|
|
|
37
72
|
* eg, Carousel options.
|
|
38
73
|
*/
|
|
39
74
|
ext?: any
|
|
40
|
-
/** List selectors will scroll.
|
|
41
|
-
|
|
75
|
+
/** List selectors will scroll.
|
|
76
|
+
* Used internally
|
|
77
|
+
* default: false */
|
|
78
|
+
scrollable? : boolean
|
|
42
79
|
options?: ItemSelectorOptions
|
|
43
80
|
|
|
44
81
|
mobile?: boolean
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Dimensions } from '@hanzo/ui/types'
|
|
2
|
+
|
|
3
|
+
import type { Family } from './family'
|
|
4
|
+
import type { ItemSelectorOptions } from './item-selector'
|
|
5
|
+
import type { MultiFamilySelectorOptions } from './selection-ui-specifier'
|
|
6
|
+
import type { CategoryNode } from './category-node'
|
|
7
|
+
import type { LineItem } from './line-item'
|
|
8
|
+
|
|
9
|
+
interface MultiFamilySelectorProps {
|
|
10
|
+
families: Family[]
|
|
11
|
+
parent: CategoryNode
|
|
12
|
+
clx?: string
|
|
13
|
+
itemClx?: string
|
|
14
|
+
itemOptions?: ItemSelectorOptions
|
|
15
|
+
selectorOptions?: MultiFamilySelectorOptions
|
|
16
|
+
mediaConstraint?: Dimensions
|
|
17
|
+
mobile?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { type MultiFamilySelectorProps as default }
|
|
@@ -1,36 +1,52 @@
|
|
|
1
1
|
import type { ItemSelectorOptions } from './item-selector'
|
|
2
|
-
type SelectorType = 'buttons' | 'carousel'
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
type SingleFamilySelector = 'buttons' | 'carousel'
|
|
4
|
+
type MultiFamilySelector = 'family-carousel' | 'all-variants-carousel'
|
|
5
|
+
|
|
6
|
+
type MultiFamilySelectorOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Show title of parent node of families.
|
|
9
|
+
* eg,
|
|
10
|
+
* Lux Credit
|
|
11
|
+
* Black Card
|
|
12
|
+
*
|
|
13
|
+
* vs just Black Card
|
|
14
|
+
*
|
|
15
|
+
* ( parent CategoryNode.label)
|
|
16
|
+
*
|
|
17
|
+
* (goes to one line if only one family)
|
|
18
|
+
* default: true
|
|
19
|
+
* */
|
|
20
|
+
showParentTitle?: boolean
|
|
21
|
+
|
|
22
|
+
/** parent CategoryNode.subNodesLabel
|
|
23
|
+
*
|
|
24
|
+
* default: 'none'
|
|
25
|
+
*/
|
|
26
|
+
parentByline?: 'none' | 'own-line' | 'comma-sep' | 'colon-sep'
|
|
27
|
+
|
|
28
|
+
/** show horiz image buttons of
|
|
29
|
+
* all sibling items below item info
|
|
30
|
+
* default: true*/
|
|
31
|
+
showItemSwatches?: boolean
|
|
14
32
|
}
|
|
15
33
|
|
|
16
34
|
interface SelectionUISpecifier {
|
|
17
35
|
multiFamily?: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
slide: {
|
|
22
|
-
type: 'buttons'
|
|
23
|
-
options?: FamilyCarouselSlideOptions
|
|
24
|
-
}
|
|
36
|
+
type: MultiFamilySelector
|
|
37
|
+
selectorOptions?: MultiFamilySelectorOptions
|
|
38
|
+
itemOptions?: ItemSelectorOptions
|
|
25
39
|
}
|
|
26
40
|
singleFamily?: {
|
|
27
|
-
type:
|
|
41
|
+
type: SingleFamilySelector
|
|
28
42
|
options?: ItemSelectorOptions
|
|
29
43
|
}
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
export {
|
|
33
47
|
type SelectionUISpecifier,
|
|
34
|
-
type
|
|
35
|
-
type
|
|
48
|
+
type ItemSelectorOptions,
|
|
49
|
+
type SingleFamilySelector,
|
|
50
|
+
type MultiFamilySelector,
|
|
51
|
+
type MultiFamilySelectorOptions
|
|
36
52
|
}
|
package/util/index.ts
CHANGED
|
@@ -66,3 +66,6 @@ export { default as ProductMediaAccessor } from './product-media-accessor'
|
|
|
66
66
|
export * from './selection-ui-specifiers'
|
|
67
67
|
|
|
68
68
|
export { getErrorMessage } from './error'
|
|
69
|
+
|
|
70
|
+
export { default as accessItemOptions } from './item-selector-options-accessor'
|
|
71
|
+
export { default as accessMultiSelectorOptions } from './multi-family-selector-options-accessor'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ItemSelectorOptions } from '../types'
|
|
2
|
+
|
|
3
|
+
export default (options: ItemSelectorOptions | undefined = {}): Required<ItemSelectorOptions> => {
|
|
4
|
+
|
|
5
|
+
const familyTitle = 'familyTitle' in options ? options.familyTitle! : 'short'
|
|
6
|
+
const showFamilyByline = 'showFamilyByline' in options ? options.showFamilyByline! : false
|
|
7
|
+
|
|
8
|
+
const showFamilyInOption = 'showFamilyInOption' in options ? options.showFamilyInOption! : false
|
|
9
|
+
const showByline = 'showByline' in options ? options.showByline! : true
|
|
10
|
+
|
|
11
|
+
const showPrice = 'showPrice' in options ? options.showPrice! : true
|
|
12
|
+
const showQuantity = 'showQuantity' in options ? options.showQuantity! : false
|
|
13
|
+
|
|
14
|
+
const buttonType = 'buttonType' in options ? options.buttonType! : 'text'
|
|
15
|
+
const horizButtons = 'horizButtons' in options ? options.horizButtons! : false
|
|
16
|
+
const showSlider = 'showSlider' in options ? options.showSlider! : true
|
|
17
|
+
|
|
18
|
+
const sort = 'sort' in options ? options.sort! : 'none'
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
showFamilyInOption,
|
|
22
|
+
familyTitle,
|
|
23
|
+
showFamilyByline,
|
|
24
|
+
showByline,
|
|
25
|
+
showPrice,
|
|
26
|
+
showQuantity,
|
|
27
|
+
buttonType,
|
|
28
|
+
horizButtons,
|
|
29
|
+
showSlider,
|
|
30
|
+
sort,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MultiFamilySelectorOptions } from '../types'
|
|
2
|
+
|
|
3
|
+
export default (options: MultiFamilySelectorOptions | undefined = {}): Required<MultiFamilySelectorOptions> => {
|
|
4
|
+
|
|
5
|
+
const showParentTitle = 'showParentTitle' in options ? options.showParentTitle! : true
|
|
6
|
+
const showItemSwatches = 'showItemSwatches' in options ? options.showItemSwatches! : true
|
|
7
|
+
|
|
8
|
+
const parentByline = 'parentByline' in options ? options.parentByline! : 'none'
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
showParentTitle,
|
|
12
|
+
showItemSwatches,
|
|
13
|
+
parentByline,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -3,11 +3,7 @@ import sep from '../service/sep'
|
|
|
3
3
|
|
|
4
4
|
const DEFAULT = {
|
|
5
5
|
multiFamily: {
|
|
6
|
-
|
|
7
|
-
showParentTitle: false,
|
|
8
|
-
slide: {
|
|
9
|
-
type: 'buttons'
|
|
10
|
-
}
|
|
6
|
+
type: 'all-variants-carousel'
|
|
11
7
|
},
|
|
12
8
|
singleFamily: { type: 'carousel' }
|
|
13
9
|
} satisfies SelectionUISpecifier
|
|
@@ -1,162 +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
|
-
import type { Dimensions } from '@hanzo/ui/types'
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
type CarouselOptions,
|
|
11
|
-
type CarouselApi,
|
|
12
|
-
Carousel,
|
|
13
|
-
CarouselContent,
|
|
14
|
-
CarouselItem,
|
|
15
|
-
CarouselPrevious,
|
|
16
|
-
CarouselNext,
|
|
17
|
-
ApplyTypography,
|
|
18
|
-
MediaStack
|
|
19
|
-
} from '@hanzo/ui/primitives'
|
|
20
|
-
|
|
21
|
-
import type { ItemSelectorProps, LineItem } from '../../types'
|
|
22
|
-
import { formatCurrencyValue } from '../../util'
|
|
23
|
-
|
|
24
|
-
import QuantityIndicator from '../quantity-indicator'
|
|
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
|
-
imageOnly: boolean
|
|
39
|
-
showFamily: boolean
|
|
40
|
-
showPrice: boolean
|
|
41
|
-
showQuantity: boolean
|
|
42
|
-
clx?: string
|
|
43
|
-
}> = ({
|
|
44
|
-
item,
|
|
45
|
-
constrainTo,
|
|
46
|
-
showFamily,
|
|
47
|
-
imageOnly,
|
|
48
|
-
showPrice,
|
|
49
|
-
showQuantity,
|
|
50
|
-
clx=''
|
|
51
|
-
}) => {
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<CarouselItem className={cn('p-2 flex flex-col justify-center items-center', clx)}>
|
|
55
|
-
{!imageOnly && (
|
|
56
|
-
<ApplyTypography className='flex flex-col items-center !gap-2 [&>*]:!m-0'>
|
|
57
|
-
{showFamily && (<h4>{item.familyTitle}</h4>)}
|
|
58
|
-
<h6>{item.optionLabel}</h6>
|
|
59
|
-
{item.byline && (<p>{item.byline}</p>)}
|
|
60
|
-
</ApplyTypography>
|
|
61
|
-
)}
|
|
62
|
-
<MediaStack media={item} constrainTo={constrainTo} clx='my-4' />
|
|
63
|
-
{!imageOnly && (showPrice || showQuantity) && (
|
|
64
|
-
<ApplyTypography className='flex flex-row items-center !gap-2 [&>*]:!m-0'>
|
|
65
|
-
{showPrice && (<p>{formatCurrencyValue(item.price)}</p>)}
|
|
66
|
-
{showQuantity && (
|
|
67
|
-
<QuantityIndicator
|
|
68
|
-
item={item}
|
|
69
|
-
clx='h-[26px] ml-1'
|
|
70
|
-
iconClx='fill-foreground'
|
|
71
|
-
digitClx='not-typography font-semibold text-primary-fg leading-none font-sans text-xs'
|
|
72
|
-
/>
|
|
73
|
-
)}
|
|
74
|
-
</ApplyTypography>
|
|
75
|
-
)}
|
|
76
|
-
</CarouselItem>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const CarouselItemSelector: React.FC<ItemSelectorProps> = observer(({
|
|
81
|
-
items,
|
|
82
|
-
selectSku,
|
|
83
|
-
selectedItemRef: itemRef,
|
|
84
|
-
scrollable, // ignored
|
|
85
|
-
clx='',
|
|
86
|
-
itemClx='',
|
|
87
|
-
options={},
|
|
88
|
-
ext={
|
|
89
|
-
options: {loop: true},
|
|
90
|
-
constrainTo: DEFAULT_CONSTRAINT,
|
|
91
|
-
imageOnly: false
|
|
92
|
-
} satisfies CarouselItemSelectorPropsExt
|
|
93
|
-
}) => {
|
|
94
|
-
|
|
95
|
-
const showFamily = 'showFamily' in options ? options.showFamily! : false
|
|
96
|
-
const showPrice = 'showPrice' in options ? options.showPrice! : true
|
|
97
|
-
const showQuantity = 'showQuantity' in options ? options.showQuantity! : false
|
|
98
|
-
|
|
99
|
-
const carouselOptions = 'options' in ext ? ext.options : undefined
|
|
100
|
-
const constrainTo = 'constrainTo' in ext ? ext.constrainTo : DEFAULT_CONSTRAINT
|
|
101
|
-
const imageOnly = 'imageOnly' in ext ? ext.imageOnly : false
|
|
102
|
-
|
|
103
|
-
const elbaApiRef = useRef<CarouselApi | undefined>(undefined)
|
|
104
|
-
const dontRespondRef = useRef<boolean>(false)
|
|
105
|
-
|
|
106
|
-
const setApi = (api: CarouselApi) => {elbaApiRef.current = api}
|
|
107
|
-
|
|
108
|
-
const onSelect = useCallback((emblaApi: CarouselApi) => {
|
|
109
|
-
if (dontRespondRef.current) {
|
|
110
|
-
dontRespondRef.current = false
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
const index = emblaApi.selectedScrollSnap()
|
|
114
|
-
if (index !== -1) {
|
|
115
|
-
selectSku(items[index].sku)
|
|
116
|
-
}
|
|
117
|
-
dontRespondRef.current = false
|
|
118
|
-
}, [])
|
|
119
|
-
|
|
120
|
-
useEffect(() => {
|
|
121
|
-
return reaction(
|
|
122
|
-
() => ({item: itemRef.item}),
|
|
123
|
-
({item}) => {
|
|
124
|
-
if (elbaApiRef.current) {
|
|
125
|
-
const index = items.findIndex((el) => (el.sku === item?.sku))
|
|
126
|
-
if (index !== -1) {
|
|
127
|
-
dontRespondRef.current = true
|
|
128
|
-
elbaApiRef.current.scrollTo(index)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
)
|
|
133
|
-
}, [])
|
|
134
|
-
|
|
135
|
-
return (
|
|
136
|
-
<Carousel options={carouselOptions} className={cn('w-full px-2', clx)} onCarouselSelect={onSelect} setApi={setApi}>
|
|
137
|
-
<CarouselContent>
|
|
138
|
-
{items.map((item) => (
|
|
139
|
-
<ItemSlide
|
|
140
|
-
key={item.sku}
|
|
141
|
-
item={item}
|
|
142
|
-
{...{
|
|
143
|
-
showFamily,
|
|
144
|
-
showPrice,
|
|
145
|
-
showQuantity,
|
|
146
|
-
constrainTo,
|
|
147
|
-
imageOnly
|
|
148
|
-
}}
|
|
149
|
-
clx={itemClx}
|
|
150
|
-
/>
|
|
151
|
-
))}
|
|
152
|
-
</CarouselContent>
|
|
153
|
-
<CarouselPrevious className='left-1'/>
|
|
154
|
-
<CarouselNext className='right-1'/>
|
|
155
|
-
</Carousel>
|
|
156
|
-
)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
export {
|
|
160
|
-
type CarouselItemSelectorPropsExt,
|
|
161
|
-
CarouselItemSelector as default
|
|
162
|
-
}
|