@ikas/code-components-mcp 1.4.0-beta.140 → 1.4.0-beta.142
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/data/framework.json +40 -3
- package/data/migration.json +9 -3
- package/data/section-templates/add-to-cart/index.tsx +16 -2
- package/data/section-templates/add-to-cart/sub-components/QuantitySelector/index.tsx +11 -3
- package/data/section-templates/add-to-cart/utils/cartLimits.ts +23 -0
- package/data/section-templates/cart-section/sub-components/CartItem/index.tsx +57 -24
- package/data/section-templates/cart-section/sub-components/CartItem/styles.css +14 -0
- package/data/section-templates/cart-section/sub-components/QuantitySelector/index.tsx +11 -3
- package/data/section-templates/header-section/sub-components/CartItem/index.tsx +57 -24
- package/data/section-templates/header-section/sub-components/CartItem/styles.css +14 -0
- package/data/section-templates/header-section/sub-components/QuantitySelector/index.tsx +11 -3
- package/data/section-templates/image-handling/ikas-config-snippet.json +1 -0
- package/data/section-templates/navigation/sub-components/CartItem/index.tsx +57 -24
- package/data/section-templates/navigation/sub-components/CartItem/styles.css +14 -0
- package/data/section-templates/navigation/sub-components/QuantitySelector/index.tsx +11 -3
- package/data/section-templates/product-detail-section/children/ProductDetailAddToCart/index.tsx +16 -2
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/components/BackInStockForm/index.tsx +92 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/ikas-config-snippet.json +159 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/index.tsx +141 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/styles.css +47 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/types.ts +25 -0
- package/data/section-templates/product-detail-section/ikas-config-snippet.json +1 -0
- package/data/section-templates/product-detail-section/sub-components/Modal/index.tsx +120 -0
- package/data/section-templates/product-detail-section/sub-components/Modal/styles.css +70 -0
- package/data/section-templates/product-detail-section/sub-components/QuantitySelector/index.tsx +11 -3
- package/data/section-templates/product-detail-section/utils/cartLimits.ts +23 -0
- package/data/storefront-api.json +1 -1
- package/data/storefront-types.json +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
getOrderLineItemFormattedOverridenPriceWithQuantity,
|
|
15
15
|
hasOrderLineItemDiscount,
|
|
16
16
|
getOrderLineItemOverridenPriceWithQuantity,
|
|
17
|
+
cartStore,
|
|
18
|
+
isOrderLineItemAutoCreated,
|
|
17
19
|
} from "@ikas/bp-storefront";
|
|
18
20
|
import { resolveAspectRatio, resolveObjectFit } from "../../utils/media";
|
|
19
21
|
import { cx } from "../../utils/cx";
|
|
@@ -25,6 +27,7 @@ interface CartItemProps {
|
|
|
25
27
|
variant?: "sidebar" | "page";
|
|
26
28
|
aspectRatio?: AspectRatio;
|
|
27
29
|
objectFit?: ObjectFit;
|
|
30
|
+
giftLabel?: string;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
const CartItem = observer(function CartItem({
|
|
@@ -33,9 +36,25 @@ const CartItem = observer(function CartItem({
|
|
|
33
36
|
variant = "sidebar",
|
|
34
37
|
aspectRatio,
|
|
35
38
|
objectFit,
|
|
39
|
+
giftLabel = "Gift",
|
|
36
40
|
}: CartItemProps) {
|
|
37
41
|
const isPage = variant === "page";
|
|
38
42
|
|
|
43
|
+
// Lines a campaign added automatically (e.g. a "buy 1 get 1" gift) cannot be
|
|
44
|
+
// edited or removed on their own, so render them read-only with a badge.
|
|
45
|
+
const isAutoCreated =
|
|
46
|
+
!!cartStore.cart && isOrderLineItemAutoCreated(cartStore.cart, item);
|
|
47
|
+
const giftBadge = isAutoCreated ? (
|
|
48
|
+
<span className="kombos-cart-item__gift-badge text-xs-medium">
|
|
49
|
+
{giftLabel}
|
|
50
|
+
</span>
|
|
51
|
+
) : null;
|
|
52
|
+
const staticQuantity = (
|
|
53
|
+
<span className="kombos-cart-item__qty-static text-sm-medium">
|
|
54
|
+
×{item.quantity}
|
|
55
|
+
</span>
|
|
56
|
+
);
|
|
57
|
+
|
|
39
58
|
const mediaStyle: Record<string, string> = {
|
|
40
59
|
aspectRatio: resolveAspectRatio(aspectRatio),
|
|
41
60
|
objectFit: resolveObjectFit(objectFit),
|
|
@@ -103,6 +122,7 @@ const CartItem = observer(function CartItem({
|
|
|
103
122
|
<a href={href} className={nameClass}>
|
|
104
123
|
{item.variant?.name}
|
|
105
124
|
</a>
|
|
125
|
+
{giftBadge}
|
|
106
126
|
{variantValues.length > 0 && (
|
|
107
127
|
<p className="kombos-cart-item__variant-text text-sm-regular">
|
|
108
128
|
{variantValues
|
|
@@ -113,19 +133,22 @@ const CartItem = observer(function CartItem({
|
|
|
113
133
|
</p>
|
|
114
134
|
)}
|
|
115
135
|
</div>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
{!isAutoCreated && (
|
|
137
|
+
<button
|
|
138
|
+
className="kombos-cart-item__remove"
|
|
139
|
+
onClick={() => onRemove(item)}
|
|
140
|
+
aria-label="Remove"
|
|
141
|
+
>
|
|
142
|
+
<TrashSVG />
|
|
143
|
+
</button>
|
|
144
|
+
)}
|
|
123
145
|
</div>
|
|
124
146
|
) : (
|
|
125
147
|
<>
|
|
126
148
|
<a href={href} className={nameClass}>
|
|
127
149
|
{item.variant?.name}
|
|
128
150
|
</a>
|
|
151
|
+
{giftBadge}
|
|
129
152
|
|
|
130
153
|
{variantValues.length > 0 && (
|
|
131
154
|
<div className="kombos-cart-item__variants">
|
|
@@ -151,11 +174,15 @@ const CartItem = observer(function CartItem({
|
|
|
151
174
|
|
|
152
175
|
{isPage ? (
|
|
153
176
|
<div className="kombos-cart-item__bottom">
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
{isAutoCreated ? (
|
|
178
|
+
staticQuantity
|
|
179
|
+
) : (
|
|
180
|
+
<QuantitySelector
|
|
181
|
+
size="sm"
|
|
182
|
+
value={item.quantity}
|
|
183
|
+
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
159
186
|
<div className="kombos-cart-item__prices">
|
|
160
187
|
{hasDiscount && (
|
|
161
188
|
<span className="kombos-cart-item__original-price text-sm-regular-strike">
|
|
@@ -193,18 +220,24 @@ const CartItem = observer(function CartItem({
|
|
|
193
220
|
</div>
|
|
194
221
|
|
|
195
222
|
<div className="kombos-cart-item__actions">
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
{isAutoCreated ? (
|
|
224
|
+
staticQuantity
|
|
225
|
+
) : (
|
|
226
|
+
<>
|
|
227
|
+
<QuantitySelector
|
|
228
|
+
size="sm"
|
|
229
|
+
value={item.quantity}
|
|
230
|
+
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
231
|
+
/>
|
|
232
|
+
<button
|
|
233
|
+
className="kombos-cart-item__remove"
|
|
234
|
+
onClick={() => onRemove(item)}
|
|
235
|
+
aria-label="Remove"
|
|
236
|
+
>
|
|
237
|
+
<TrashSVG />
|
|
238
|
+
</button>
|
|
239
|
+
</>
|
|
240
|
+
)}
|
|
208
241
|
</div>
|
|
209
242
|
</>
|
|
210
243
|
)}
|
|
@@ -168,3 +168,17 @@
|
|
|
168
168
|
flex-shrink: 0;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/* Auto-created (campaign gift) line: badge + read-only quantity */
|
|
172
|
+
.kombos-cart-item__gift-badge {
|
|
173
|
+
display: inline-flex;
|
|
174
|
+
align-self: flex-start;
|
|
175
|
+
padding: 0.125rem 0.5rem;
|
|
176
|
+
border-radius: 999px;
|
|
177
|
+
background: var(--kombos-gray-100);
|
|
178
|
+
color: var(--kombos-gray-500);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.kombos-cart-item__qty-static {
|
|
182
|
+
color: var(--kombos-gray-500);
|
|
183
|
+
}
|
|
184
|
+
|
|
@@ -5,20 +5,27 @@ interface Props {
|
|
|
5
5
|
value: number;
|
|
6
6
|
onChange: (value: number) => void;
|
|
7
7
|
size?: "default" | "sm";
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export default function QuantitySelector({
|
|
11
13
|
value,
|
|
12
14
|
onChange,
|
|
13
15
|
size = "default",
|
|
16
|
+
min = 1,
|
|
17
|
+
max,
|
|
14
18
|
}: Props) {
|
|
19
|
+
const canDecrease = value > min;
|
|
20
|
+
const canIncrease = max == null || value < max;
|
|
21
|
+
|
|
15
22
|
return (
|
|
16
23
|
<div className={cx("kombos-pd__qty", size === "sm" && "kombos-pd__qty--sm")}>
|
|
17
24
|
<button
|
|
18
25
|
type="button"
|
|
19
26
|
className="kombos-pd__qty-btn"
|
|
20
|
-
onClick={() => onChange(Math.max(
|
|
21
|
-
disabled={
|
|
27
|
+
onClick={() => onChange(Math.max(min, value - 1))}
|
|
28
|
+
disabled={!canDecrease}
|
|
22
29
|
aria-label="Decrease quantity"
|
|
23
30
|
>
|
|
24
31
|
<MinusSVG />
|
|
@@ -31,7 +38,8 @@ export default function QuantitySelector({
|
|
|
31
38
|
<button
|
|
32
39
|
type="button"
|
|
33
40
|
className="kombos-pd__qty-btn"
|
|
34
|
-
onClick={() => onChange(value + 1)}
|
|
41
|
+
onClick={() => onChange(max == null ? value + 1 : Math.min(max, value + 1))}
|
|
42
|
+
disabled={!canIncrease}
|
|
35
43
|
aria-label="Increase quantity"
|
|
36
44
|
>
|
|
37
45
|
<PlusSVG />
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
getOrderLineItemFormattedOverridenPriceWithQuantity,
|
|
15
15
|
hasOrderLineItemDiscount,
|
|
16
16
|
getOrderLineItemOverridenPriceWithQuantity,
|
|
17
|
+
cartStore,
|
|
18
|
+
isOrderLineItemAutoCreated,
|
|
17
19
|
} from "@ikas/bp-storefront";
|
|
18
20
|
import { resolveAspectRatio, resolveObjectFit } from "../../utils/media";
|
|
19
21
|
import { cx } from "../../utils/cx";
|
|
@@ -25,6 +27,7 @@ interface CartItemProps {
|
|
|
25
27
|
variant?: "sidebar" | "page";
|
|
26
28
|
aspectRatio?: AspectRatio;
|
|
27
29
|
objectFit?: ObjectFit;
|
|
30
|
+
giftLabel?: string;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
const CartItem = observer(function CartItem({
|
|
@@ -33,9 +36,25 @@ const CartItem = observer(function CartItem({
|
|
|
33
36
|
variant = "sidebar",
|
|
34
37
|
aspectRatio,
|
|
35
38
|
objectFit,
|
|
39
|
+
giftLabel = "Gift",
|
|
36
40
|
}: CartItemProps) {
|
|
37
41
|
const isPage = variant === "page";
|
|
38
42
|
|
|
43
|
+
// Lines a campaign added automatically (e.g. a "buy 1 get 1" gift) cannot be
|
|
44
|
+
// edited or removed on their own, so render them read-only with a badge.
|
|
45
|
+
const isAutoCreated =
|
|
46
|
+
!!cartStore.cart && isOrderLineItemAutoCreated(cartStore.cart, item);
|
|
47
|
+
const giftBadge = isAutoCreated ? (
|
|
48
|
+
<span className="kombos-cart-item__gift-badge text-xs-medium">
|
|
49
|
+
{giftLabel}
|
|
50
|
+
</span>
|
|
51
|
+
) : null;
|
|
52
|
+
const staticQuantity = (
|
|
53
|
+
<span className="kombos-cart-item__qty-static text-sm-medium">
|
|
54
|
+
×{item.quantity}
|
|
55
|
+
</span>
|
|
56
|
+
);
|
|
57
|
+
|
|
39
58
|
const mediaStyle: Record<string, string> = {
|
|
40
59
|
aspectRatio: resolveAspectRatio(aspectRatio),
|
|
41
60
|
objectFit: resolveObjectFit(objectFit),
|
|
@@ -103,6 +122,7 @@ const CartItem = observer(function CartItem({
|
|
|
103
122
|
<a href={href} className={nameClass}>
|
|
104
123
|
{item.variant?.name}
|
|
105
124
|
</a>
|
|
125
|
+
{giftBadge}
|
|
106
126
|
{variantValues.length > 0 && (
|
|
107
127
|
<p className="kombos-cart-item__variant-text text-sm-regular">
|
|
108
128
|
{variantValues
|
|
@@ -113,19 +133,22 @@ const CartItem = observer(function CartItem({
|
|
|
113
133
|
</p>
|
|
114
134
|
)}
|
|
115
135
|
</div>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
{!isAutoCreated && (
|
|
137
|
+
<button
|
|
138
|
+
className="kombos-cart-item__remove"
|
|
139
|
+
onClick={() => onRemove(item)}
|
|
140
|
+
aria-label="Remove"
|
|
141
|
+
>
|
|
142
|
+
<TrashSVG />
|
|
143
|
+
</button>
|
|
144
|
+
)}
|
|
123
145
|
</div>
|
|
124
146
|
) : (
|
|
125
147
|
<>
|
|
126
148
|
<a href={href} className={nameClass}>
|
|
127
149
|
{item.variant?.name}
|
|
128
150
|
</a>
|
|
151
|
+
{giftBadge}
|
|
129
152
|
|
|
130
153
|
{variantValues.length > 0 && (
|
|
131
154
|
<div className="kombos-cart-item__variants">
|
|
@@ -151,11 +174,15 @@ const CartItem = observer(function CartItem({
|
|
|
151
174
|
|
|
152
175
|
{isPage ? (
|
|
153
176
|
<div className="kombos-cart-item__bottom">
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
{isAutoCreated ? (
|
|
178
|
+
staticQuantity
|
|
179
|
+
) : (
|
|
180
|
+
<QuantitySelector
|
|
181
|
+
size="sm"
|
|
182
|
+
value={item.quantity}
|
|
183
|
+
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
159
186
|
<div className="kombos-cart-item__prices">
|
|
160
187
|
{hasDiscount && (
|
|
161
188
|
<span className="kombos-cart-item__original-price text-sm-regular-strike">
|
|
@@ -193,18 +220,24 @@ const CartItem = observer(function CartItem({
|
|
|
193
220
|
</div>
|
|
194
221
|
|
|
195
222
|
<div className="kombos-cart-item__actions">
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
{isAutoCreated ? (
|
|
224
|
+
staticQuantity
|
|
225
|
+
) : (
|
|
226
|
+
<>
|
|
227
|
+
<QuantitySelector
|
|
228
|
+
size="sm"
|
|
229
|
+
value={item.quantity}
|
|
230
|
+
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
231
|
+
/>
|
|
232
|
+
<button
|
|
233
|
+
className="kombos-cart-item__remove"
|
|
234
|
+
onClick={() => onRemove(item)}
|
|
235
|
+
aria-label="Remove"
|
|
236
|
+
>
|
|
237
|
+
<TrashSVG />
|
|
238
|
+
</button>
|
|
239
|
+
</>
|
|
240
|
+
)}
|
|
208
241
|
</div>
|
|
209
242
|
</>
|
|
210
243
|
)}
|
|
@@ -168,3 +168,17 @@
|
|
|
168
168
|
flex-shrink: 0;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/* Auto-created (campaign gift) line: badge + read-only quantity */
|
|
172
|
+
.kombos-cart-item__gift-badge {
|
|
173
|
+
display: inline-flex;
|
|
174
|
+
align-self: flex-start;
|
|
175
|
+
padding: 0.125rem 0.5rem;
|
|
176
|
+
border-radius: 999px;
|
|
177
|
+
background: var(--kombos-gray-100);
|
|
178
|
+
color: var(--kombos-gray-500);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.kombos-cart-item__qty-static {
|
|
182
|
+
color: var(--kombos-gray-500);
|
|
183
|
+
}
|
|
184
|
+
|
|
@@ -5,20 +5,27 @@ interface Props {
|
|
|
5
5
|
value: number;
|
|
6
6
|
onChange: (value: number) => void;
|
|
7
7
|
size?: "default" | "sm";
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export default function QuantitySelector({
|
|
11
13
|
value,
|
|
12
14
|
onChange,
|
|
13
15
|
size = "default",
|
|
16
|
+
min = 1,
|
|
17
|
+
max,
|
|
14
18
|
}: Props) {
|
|
19
|
+
const canDecrease = value > min;
|
|
20
|
+
const canIncrease = max == null || value < max;
|
|
21
|
+
|
|
15
22
|
return (
|
|
16
23
|
<div className={cx("kombos-pd__qty", size === "sm" && "kombos-pd__qty--sm")}>
|
|
17
24
|
<button
|
|
18
25
|
type="button"
|
|
19
26
|
className="kombos-pd__qty-btn"
|
|
20
|
-
onClick={() => onChange(Math.max(
|
|
21
|
-
disabled={
|
|
27
|
+
onClick={() => onChange(Math.max(min, value - 1))}
|
|
28
|
+
disabled={!canDecrease}
|
|
22
29
|
aria-label="Decrease quantity"
|
|
23
30
|
>
|
|
24
31
|
<MinusSVG />
|
|
@@ -31,7 +38,8 @@ export default function QuantitySelector({
|
|
|
31
38
|
<button
|
|
32
39
|
type="button"
|
|
33
40
|
className="kombos-pd__qty-btn"
|
|
34
|
-
onClick={() => onChange(value + 1)}
|
|
41
|
+
onClick={() => onChange(max == null ? value + 1 : Math.min(max, value + 1))}
|
|
42
|
+
disabled={!canIncrease}
|
|
35
43
|
aria-label="Increase quantity"
|
|
36
44
|
>
|
|
37
45
|
<PlusSVG />
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"<id-of-ProductDetailProductGroup>",
|
|
40
40
|
"<id-of-ProductDetailVariant>",
|
|
41
41
|
"<id-of-ProductDetailAddToCart>",
|
|
42
|
+
"<id-of-ProductDetailBackInStock>",
|
|
42
43
|
"<id-of-ProductDetailFeatures>",
|
|
43
44
|
"<id-of-ProductDetailDescription>",
|
|
44
45
|
"<id-of-ProductDetailBundleProduct>",
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
getOrderLineItemFormattedOverridenPriceWithQuantity,
|
|
15
15
|
hasOrderLineItemDiscount,
|
|
16
16
|
getOrderLineItemOverridenPriceWithQuantity,
|
|
17
|
+
cartStore,
|
|
18
|
+
isOrderLineItemAutoCreated,
|
|
17
19
|
} from "@ikas/bp-storefront";
|
|
18
20
|
import { resolveAspectRatio, resolveObjectFit } from "../../utils/media";
|
|
19
21
|
import { cx } from "../../utils/cx";
|
|
@@ -25,6 +27,7 @@ interface CartItemProps {
|
|
|
25
27
|
variant?: "sidebar" | "page";
|
|
26
28
|
aspectRatio?: AspectRatio;
|
|
27
29
|
objectFit?: ObjectFit;
|
|
30
|
+
giftLabel?: string;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
const CartItem = observer(function CartItem({
|
|
@@ -33,9 +36,25 @@ const CartItem = observer(function CartItem({
|
|
|
33
36
|
variant = "sidebar",
|
|
34
37
|
aspectRatio,
|
|
35
38
|
objectFit,
|
|
39
|
+
giftLabel = "Gift",
|
|
36
40
|
}: CartItemProps) {
|
|
37
41
|
const isPage = variant === "page";
|
|
38
42
|
|
|
43
|
+
// Lines a campaign added automatically (e.g. a "buy 1 get 1" gift) cannot be
|
|
44
|
+
// edited or removed on their own, so render them read-only with a badge.
|
|
45
|
+
const isAutoCreated =
|
|
46
|
+
!!cartStore.cart && isOrderLineItemAutoCreated(cartStore.cart, item);
|
|
47
|
+
const giftBadge = isAutoCreated ? (
|
|
48
|
+
<span className="kombos-cart-item__gift-badge text-xs-medium">
|
|
49
|
+
{giftLabel}
|
|
50
|
+
</span>
|
|
51
|
+
) : null;
|
|
52
|
+
const staticQuantity = (
|
|
53
|
+
<span className="kombos-cart-item__qty-static text-sm-medium">
|
|
54
|
+
×{item.quantity}
|
|
55
|
+
</span>
|
|
56
|
+
);
|
|
57
|
+
|
|
39
58
|
const mediaStyle: Record<string, string> = {
|
|
40
59
|
aspectRatio: resolveAspectRatio(aspectRatio),
|
|
41
60
|
objectFit: resolveObjectFit(objectFit),
|
|
@@ -103,6 +122,7 @@ const CartItem = observer(function CartItem({
|
|
|
103
122
|
<a href={href} className={nameClass}>
|
|
104
123
|
{item.variant?.name}
|
|
105
124
|
</a>
|
|
125
|
+
{giftBadge}
|
|
106
126
|
{variantValues.length > 0 && (
|
|
107
127
|
<p className="kombos-cart-item__variant-text text-sm-regular">
|
|
108
128
|
{variantValues
|
|
@@ -113,19 +133,22 @@ const CartItem = observer(function CartItem({
|
|
|
113
133
|
</p>
|
|
114
134
|
)}
|
|
115
135
|
</div>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
{!isAutoCreated && (
|
|
137
|
+
<button
|
|
138
|
+
className="kombos-cart-item__remove"
|
|
139
|
+
onClick={() => onRemove(item)}
|
|
140
|
+
aria-label="Remove"
|
|
141
|
+
>
|
|
142
|
+
<TrashSVG />
|
|
143
|
+
</button>
|
|
144
|
+
)}
|
|
123
145
|
</div>
|
|
124
146
|
) : (
|
|
125
147
|
<>
|
|
126
148
|
<a href={href} className={nameClass}>
|
|
127
149
|
{item.variant?.name}
|
|
128
150
|
</a>
|
|
151
|
+
{giftBadge}
|
|
129
152
|
|
|
130
153
|
{variantValues.length > 0 && (
|
|
131
154
|
<div className="kombos-cart-item__variants">
|
|
@@ -151,11 +174,15 @@ const CartItem = observer(function CartItem({
|
|
|
151
174
|
|
|
152
175
|
{isPage ? (
|
|
153
176
|
<div className="kombos-cart-item__bottom">
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
{isAutoCreated ? (
|
|
178
|
+
staticQuantity
|
|
179
|
+
) : (
|
|
180
|
+
<QuantitySelector
|
|
181
|
+
size="sm"
|
|
182
|
+
value={item.quantity}
|
|
183
|
+
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
159
186
|
<div className="kombos-cart-item__prices">
|
|
160
187
|
{hasDiscount && (
|
|
161
188
|
<span className="kombos-cart-item__original-price text-sm-regular-strike">
|
|
@@ -193,18 +220,24 @@ const CartItem = observer(function CartItem({
|
|
|
193
220
|
</div>
|
|
194
221
|
|
|
195
222
|
<div className="kombos-cart-item__actions">
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
{isAutoCreated ? (
|
|
224
|
+
staticQuantity
|
|
225
|
+
) : (
|
|
226
|
+
<>
|
|
227
|
+
<QuantitySelector
|
|
228
|
+
size="sm"
|
|
229
|
+
value={item.quantity}
|
|
230
|
+
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
231
|
+
/>
|
|
232
|
+
<button
|
|
233
|
+
className="kombos-cart-item__remove"
|
|
234
|
+
onClick={() => onRemove(item)}
|
|
235
|
+
aria-label="Remove"
|
|
236
|
+
>
|
|
237
|
+
<TrashSVG />
|
|
238
|
+
</button>
|
|
239
|
+
</>
|
|
240
|
+
)}
|
|
208
241
|
</div>
|
|
209
242
|
</>
|
|
210
243
|
)}
|
|
@@ -168,3 +168,17 @@
|
|
|
168
168
|
flex-shrink: 0;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/* Auto-created (campaign gift) line: badge + read-only quantity */
|
|
172
|
+
.kombos-cart-item__gift-badge {
|
|
173
|
+
display: inline-flex;
|
|
174
|
+
align-self: flex-start;
|
|
175
|
+
padding: 0.125rem 0.5rem;
|
|
176
|
+
border-radius: 999px;
|
|
177
|
+
background: var(--kombos-gray-100);
|
|
178
|
+
color: var(--kombos-gray-500);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.kombos-cart-item__qty-static {
|
|
182
|
+
color: var(--kombos-gray-500);
|
|
183
|
+
}
|
|
184
|
+
|
|
@@ -5,20 +5,27 @@ interface Props {
|
|
|
5
5
|
value: number;
|
|
6
6
|
onChange: (value: number) => void;
|
|
7
7
|
size?: "default" | "sm";
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export default function QuantitySelector({
|
|
11
13
|
value,
|
|
12
14
|
onChange,
|
|
13
15
|
size = "default",
|
|
16
|
+
min = 1,
|
|
17
|
+
max,
|
|
14
18
|
}: Props) {
|
|
19
|
+
const canDecrease = value > min;
|
|
20
|
+
const canIncrease = max == null || value < max;
|
|
21
|
+
|
|
15
22
|
return (
|
|
16
23
|
<div className={cx("kombos-pd__qty", size === "sm" && "kombos-pd__qty--sm")}>
|
|
17
24
|
<button
|
|
18
25
|
type="button"
|
|
19
26
|
className="kombos-pd__qty-btn"
|
|
20
|
-
onClick={() => onChange(Math.max(
|
|
21
|
-
disabled={
|
|
27
|
+
onClick={() => onChange(Math.max(min, value - 1))}
|
|
28
|
+
disabled={!canDecrease}
|
|
22
29
|
aria-label="Decrease quantity"
|
|
23
30
|
>
|
|
24
31
|
<MinusSVG />
|
|
@@ -31,7 +38,8 @@ export default function QuantitySelector({
|
|
|
31
38
|
<button
|
|
32
39
|
type="button"
|
|
33
40
|
className="kombos-pd__qty-btn"
|
|
34
|
-
onClick={() => onChange(value + 1)}
|
|
41
|
+
onClick={() => onChange(max == null ? value + 1 : Math.min(max, value + 1))}
|
|
42
|
+
disabled={!canIncrease}
|
|
35
43
|
aria-label="Increase quantity"
|
|
36
44
|
>
|
|
37
45
|
<PlusSVG />
|
package/data/section-templates/product-detail-section/children/ProductDetailAddToCart/index.tsx
CHANGED
|
@@ -10,13 +10,14 @@ import {
|
|
|
10
10
|
IkasBundleSettings,
|
|
11
11
|
IkasStorefrontConfig,
|
|
12
12
|
} from "@ikas/bp-storefront";
|
|
13
|
-
import { useState } from "preact/hooks";
|
|
13
|
+
import { useEffect, useState } from "preact/hooks";
|
|
14
14
|
import { Props } from "./types";
|
|
15
15
|
import Button from "../../sub-components/Button";
|
|
16
16
|
import QuantitySelector from "../../sub-components/QuantitySelector";
|
|
17
17
|
import PayWithIkas from "./components/PayWithIkas";
|
|
18
18
|
import { isBundleOutOfStock } from "../../utils/bundle";
|
|
19
19
|
import { validateOptionSet } from "../../utils/optionSet";
|
|
20
|
+
import { getProductCartLimits } from "../../utils/cartLimits";
|
|
20
21
|
import { showToast } from "../../utils/toast";
|
|
21
22
|
|
|
22
23
|
export function ProductDetailAddToCart({
|
|
@@ -37,6 +38,12 @@ export function ProductDetailAddToCart({
|
|
|
37
38
|
const [quantity, setQuantity] = useState(1);
|
|
38
39
|
const [isAddingToCart, setIsAddingToCart] = useState(false);
|
|
39
40
|
|
|
41
|
+
// Seed the quantity to the product's minimum-per-cart when the product changes.
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!product) return;
|
|
44
|
+
setQuantity(getProductCartLimits(product).min);
|
|
45
|
+
}, [product?.id]);
|
|
46
|
+
|
|
40
47
|
if (!product) return null;
|
|
41
48
|
|
|
42
49
|
const editLineID =
|
|
@@ -59,6 +66,8 @@ export function ProductDetailAddToCart({
|
|
|
59
66
|
|
|
60
67
|
const isDisabled = isOutOfStock || isAddingToCart;
|
|
61
68
|
|
|
69
|
+
const { min: minQuantity, max: maxQuantity } = getProductCartLimits(product);
|
|
70
|
+
|
|
62
71
|
const payWithIkasUrl = IkasStorefrontConfig.getPayWithIkasUrl();
|
|
63
72
|
const routing = IkasStorefrontConfig.getCurrentRouting();
|
|
64
73
|
const showPayWithIkas =
|
|
@@ -119,7 +128,12 @@ export function ProductDetailAddToCart({
|
|
|
119
128
|
>
|
|
120
129
|
<div className="kombos-pd-atc__actions">
|
|
121
130
|
{!hideQuantityInput && !isOutOfStock && (
|
|
122
|
-
<QuantitySelector
|
|
131
|
+
<QuantitySelector
|
|
132
|
+
value={quantity}
|
|
133
|
+
onChange={setQuantity}
|
|
134
|
+
min={minQuantity}
|
|
135
|
+
max={maxQuantity}
|
|
136
|
+
/>
|
|
123
137
|
)}
|
|
124
138
|
<Button
|
|
125
139
|
variant="primary"
|