@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.
Files changed (31) hide show
  1. package/data/framework.json +95 -14
  2. package/data/migration.json +10 -4
  3. package/data/section-templates/add-to-cart/index.tsx +16 -2
  4. package/data/section-templates/add-to-cart/sub-components/QuantitySelector/index.tsx +11 -3
  5. package/data/section-templates/add-to-cart/utils/cartLimits.ts +23 -0
  6. package/data/section-templates/cart-section/sub-components/CartItem/index.tsx +57 -24
  7. package/data/section-templates/cart-section/sub-components/CartItem/styles.css +14 -0
  8. package/data/section-templates/cart-section/sub-components/QuantitySelector/index.tsx +11 -3
  9. package/data/section-templates/header-section/sub-components/CartItem/index.tsx +57 -24
  10. package/data/section-templates/header-section/sub-components/CartItem/styles.css +14 -0
  11. package/data/section-templates/header-section/sub-components/QuantitySelector/index.tsx +11 -3
  12. package/data/section-templates/image-handling/ikas-config-snippet.json +1 -0
  13. package/data/section-templates/navigation/sub-components/CartItem/index.tsx +57 -24
  14. package/data/section-templates/navigation/sub-components/CartItem/styles.css +14 -0
  15. package/data/section-templates/navigation/sub-components/QuantitySelector/index.tsx +11 -3
  16. package/data/section-templates/product-detail-section/children/ProductDetailAddToCart/index.tsx +16 -2
  17. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/components/BackInStockForm/index.tsx +92 -0
  18. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/ikas-config-snippet.json +159 -0
  19. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/index.tsx +141 -0
  20. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/styles.css +47 -0
  21. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/types.ts +25 -0
  22. package/data/section-templates/product-detail-section/ikas-config-snippet.json +1 -0
  23. package/data/section-templates/product-detail-section/sub-components/Modal/index.tsx +120 -0
  24. package/data/section-templates/product-detail-section/sub-components/Modal/styles.css +70 -0
  25. package/data/section-templates/product-detail-section/sub-components/QuantitySelector/index.tsx +11 -3
  26. package/data/section-templates/product-detail-section/utils/cartLimits.ts +23 -0
  27. package/data/storefront-api.json +207 -27
  28. package/data/storefront-types.json +9071 -5740
  29. package/dist/index.js +1939 -246
  30. package/dist/index.js.map +1 -1
  31. package/package.json +1 -1
@@ -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
+ }
@@ -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(1, value - 1))}
21
- disabled={value <= 1}
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
+ }