@ikas/code-components-mcp 1.4.0-beta.15 → 1.4.0-beta.151
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 +95 -14
- package/data/migration.json +10 -4
- 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 +207 -27
- package/data/storefront-types.json +9071 -5740
- package/dist/index.js +1939 -246
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/types.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// This file is auto-generated — do not edit manually.
|
|
2
|
+
import type {
|
|
3
|
+
IkasProduct,
|
|
4
|
+
MarginTopStyleType,
|
|
5
|
+
MarginBottomStyleType,
|
|
6
|
+
} from "@ikas/bp-storefront";
|
|
7
|
+
|
|
8
|
+
export interface Props {
|
|
9
|
+
product?: IkasProduct | null;
|
|
10
|
+
notifyButtonText?: string;
|
|
11
|
+
reminderSavedText?: string;
|
|
12
|
+
alreadySavedMessage?: string;
|
|
13
|
+
savedMessage?: string;
|
|
14
|
+
loginRequiredMessage?: string;
|
|
15
|
+
errorMessage?: string;
|
|
16
|
+
modalTitle?: string;
|
|
17
|
+
modalDescription?: string;
|
|
18
|
+
emailLabel?: string;
|
|
19
|
+
submitButtonText?: string;
|
|
20
|
+
submittingText?: string;
|
|
21
|
+
mobileMarginTop?: MarginTopStyleType;
|
|
22
|
+
mobileMarginBottom?: MarginBottomStyleType;
|
|
23
|
+
desktopMarginTop?: MarginTopStyleType;
|
|
24
|
+
desktopMarginBottom?: MarginBottomStyleType;
|
|
25
|
+
}
|
|
@@ -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>",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useId } from "preact/hooks";
|
|
2
|
+
import { createPortal } from "preact/compat";
|
|
3
|
+
import type { ComponentChildren } from "preact";
|
|
4
|
+
import { useScrollLock } from "../../hooks/useScrollLock";
|
|
5
|
+
import { cx } from "../../utils/cx";
|
|
6
|
+
import { XSVG } from "../icons";
|
|
7
|
+
import Button, { type ButtonProps } from "../Button";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
title: string;
|
|
12
|
+
maxWidth?: string;
|
|
13
|
+
children: ComponentChildren;
|
|
14
|
+
className?: string;
|
|
15
|
+
okText?: string;
|
|
16
|
+
cancelText?: string;
|
|
17
|
+
onOk?: (e: Event) => void;
|
|
18
|
+
okButtonProps?: ButtonProps;
|
|
19
|
+
cancelButtonProps?: ButtonProps;
|
|
20
|
+
footer?: ComponentChildren | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function Modal({
|
|
24
|
+
onClose,
|
|
25
|
+
title,
|
|
26
|
+
maxWidth = "37.5rem",
|
|
27
|
+
children,
|
|
28
|
+
className,
|
|
29
|
+
okText,
|
|
30
|
+
cancelText,
|
|
31
|
+
onOk,
|
|
32
|
+
okButtonProps,
|
|
33
|
+
cancelButtonProps,
|
|
34
|
+
footer,
|
|
35
|
+
}: Props) {
|
|
36
|
+
const titleId = useId();
|
|
37
|
+
const [visible, setVisible] = useState(false);
|
|
38
|
+
const onCloseRef = useRef(onClose);
|
|
39
|
+
onCloseRef.current = onClose;
|
|
40
|
+
|
|
41
|
+
const handleCloseRef = useRef(() => {});
|
|
42
|
+
handleCloseRef.current = () => {
|
|
43
|
+
setVisible(false);
|
|
44
|
+
setTimeout(() => onCloseRef.current(), 300);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
useScrollLock();
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const rafId = requestAnimationFrame(() => setVisible(true));
|
|
51
|
+
return () => cancelAnimationFrame(rafId);
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
56
|
+
if (e.key === "Escape") handleCloseRef.current();
|
|
57
|
+
};
|
|
58
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
59
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
return createPortal(
|
|
63
|
+
<div
|
|
64
|
+
className={cx("kombos-modal", visible && "kombos-modal--open", className)}
|
|
65
|
+
role="dialog"
|
|
66
|
+
aria-modal="true"
|
|
67
|
+
aria-labelledby={titleId}
|
|
68
|
+
>
|
|
69
|
+
<div className="kombos-modal__panel" style={{ maxWidth }}>
|
|
70
|
+
<div className="kombos-modal__header">
|
|
71
|
+
<h2 id={titleId} className="kombos-modal__title text-md-medium">
|
|
72
|
+
{title}
|
|
73
|
+
</h2>
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
className="kombos-modal__close"
|
|
77
|
+
onClick={() => handleCloseRef.current()}
|
|
78
|
+
aria-label="Close"
|
|
79
|
+
>
|
|
80
|
+
<XSVG />
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div className="kombos-modal__body">{children}</div>
|
|
85
|
+
|
|
86
|
+
{footer !== null &&
|
|
87
|
+
(footer !== undefined ? (
|
|
88
|
+
<div className="kombos-modal__footer">{footer}</div>
|
|
89
|
+
) : (
|
|
90
|
+
(cancelText || okText) && (
|
|
91
|
+
<div className="kombos-modal__footer">
|
|
92
|
+
{cancelText && (
|
|
93
|
+
<Button
|
|
94
|
+
variant="secondary"
|
|
95
|
+
size="s"
|
|
96
|
+
type="button"
|
|
97
|
+
onClick={() => handleCloseRef.current()}
|
|
98
|
+
{...cancelButtonProps}
|
|
99
|
+
>
|
|
100
|
+
{cancelText}
|
|
101
|
+
</Button>
|
|
102
|
+
)}
|
|
103
|
+
{okText && (
|
|
104
|
+
<Button
|
|
105
|
+
variant="primary"
|
|
106
|
+
size="s"
|
|
107
|
+
onClick={onOk}
|
|
108
|
+
{...okButtonProps}
|
|
109
|
+
>
|
|
110
|
+
{okText}
|
|
111
|
+
</Button>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
))}
|
|
116
|
+
</div>
|
|
117
|
+
</div>,
|
|
118
|
+
document.body,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* ── Modal ── */
|
|
2
|
+
.kombos-modal {
|
|
3
|
+
position: fixed;
|
|
4
|
+
inset: 0;
|
|
5
|
+
z-index: var(--kombos-z-overlay);
|
|
6
|
+
background: rgba(0, 0, 0, 0.35);
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
opacity: 0;
|
|
11
|
+
transition: opacity 0.3s;
|
|
12
|
+
padding: 1rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.kombos-modal--open {
|
|
16
|
+
opacity: 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.kombos-modal__panel {
|
|
20
|
+
background: var(--kombos-white);
|
|
21
|
+
border-radius: 6px;
|
|
22
|
+
width: 100%;
|
|
23
|
+
max-height: 90vh;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
transform: translateY(1rem);
|
|
27
|
+
transition: transform 0.3s;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.kombos-modal--open .kombos-modal__panel {
|
|
31
|
+
transform: translateY(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.kombos-modal__header {
|
|
35
|
+
display: flex;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
align-items: center;
|
|
38
|
+
background: var(--kombos-white);
|
|
39
|
+
padding: 1.5rem;
|
|
40
|
+
border-bottom: 1px solid var(--kombos-gray-200);
|
|
41
|
+
border-radius: 6px 6px 0 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.kombos-modal__title {
|
|
45
|
+
color: var(--kombos-gray-900);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.kombos-modal__close {
|
|
49
|
+
background: none;
|
|
50
|
+
border: none;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
padding: 0;
|
|
53
|
+
color: var(--kombos-gray-700);
|
|
54
|
+
font-size: 1.25rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.kombos-modal__body {
|
|
58
|
+
padding: 1.5rem;
|
|
59
|
+
overflow-y: auto;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.kombos-modal__footer {
|
|
63
|
+
background: var(--kombos-white);
|
|
64
|
+
padding: 1.5rem;
|
|
65
|
+
border-top: 1px solid var(--kombos-gray-200);
|
|
66
|
+
border-radius: 0 0 6px 6px;
|
|
67
|
+
display: flex;
|
|
68
|
+
gap: 0.75rem;
|
|
69
|
+
justify-content: flex-end;
|
|
70
|
+
}
|
package/data/section-templates/product-detail-section/sub-components/QuantitySelector/index.tsx
CHANGED
|
@@ -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 />
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IkasProduct, IkasStorefrontConfig } from "@ikas/bp-storefront";
|
|
2
|
+
|
|
3
|
+
export interface CartQuantityLimits {
|
|
4
|
+
min: number;
|
|
5
|
+
max?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the per-cart quantity limits for a product from its active sales
|
|
10
|
+
* channel. Falls back to a minimum of 1 and no maximum when the channel or its
|
|
11
|
+
* limits are not set. `addItemToCart` clamps to the same limits internally, so
|
|
12
|
+
* applying these in the stepper keeps the UI honest instead of letting the
|
|
13
|
+
* quantity snap silently.
|
|
14
|
+
*/
|
|
15
|
+
export function getProductCartLimits(product: IkasProduct): CartQuantityLimits {
|
|
16
|
+
const channel = product.salesChannels?.find(
|
|
17
|
+
(sc) => sc.id === IkasStorefrontConfig.salesChannelId,
|
|
18
|
+
);
|
|
19
|
+
return {
|
|
20
|
+
min: channel?.minQuantityPerCart ?? 1,
|
|
21
|
+
max: channel?.maxQuantityPerCart ?? undefined,
|
|
22
|
+
};
|
|
23
|
+
}
|