@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
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
|
+
}
|
package/data/storefront-api.json
CHANGED
package/dist/index.js
CHANGED
|
@@ -3788,7 +3788,7 @@ server.tool("get_section_values", "Get the CURRENT prop values of one or MANY pl
|
|
|
3788
3788
|
return callEditorAction(project_root, args);
|
|
3789
3789
|
});
|
|
3790
3790
|
// Tool: update_section_prop
|
|
3791
|
-
server.tool("update_section_prop", 'Change a single prop value of a section placed on a page. This is the tool for FILLING/SETTING content (heading, text, image, link, etc.) on an existing section — it is pure data entry: it writes NO component code and needs NO rebuild. Do not author a new prop or component to set a value the section already supports; first check the section\'s existing `props` via `list_page_sections`. Target the specific placement by its `element_id` (from `list_page_sections`) and the prop by `prop_id` or `prop_name` (also from `list_page_sections`). `value` is the prop value object as stored by the editor — for scalar props it is wrapped as `{ "value": <scalar> }` (e.g. `{ "value": "Hello" }` for TEXT, `{ "value": true }` for BOOLEAN, `{ "value": 12 }` for NUMBER, `{ "value": "#FF0000" }` for COLOR, `{ "value": "<enum-key>" }` for ENUM). A COLOR prop can ALSO bind to a global theme color instead of a literal hex — pass `{ "patternValueId": "<id>" }` (the color id from `list_theme_globals` → `colors[].id`) and the prop tracks that global color live (resolved to `var(--<id>)` at render); the id is validated and an unknown one is REJECTED. Richer object prop types use their own object shape and are NOT `{ "value": ... }`-wrapped (full catalog in `get_editor_workflow`): IMAGE = `{ "id": "<asset-id>", "altText"?: "<alt>", "isVideo"?: false }` (the `id` MUST reference an already-uploaded asset — call `upload_image` with a `file_path`/`image_url` to get one, or reuse an existing `id` from another section\'s `propValues`); IMAGE_LIST = `{ "images": [ <image>, ... ] }`; VIDEO = `{ "video": { "id": "<asset-id>" }, "thumbnailImage"?: { "id": "..." }, "autoplay"?: false, "controls"?: true, "loop"?: false, "muted"?: false }` (NOT a bare `{ "id": ... }`); SVG = `{ "value": "<svg markup>" }` (value-wrapped, like a scalar); SVG_LIST = `{ "svgs": [ "<svg>", ... ] }`; TYPE (style props such as BorderRadiusStyleType/PaddingStyleType/GapStyleType) = the type-shaped object ITSELF, e.g. `{ "value": 8, "unit": "px" }` — get the exact field structure from the prop\'s `writeExample`/`typeName` in `get_component_props` or `get_type_definition("<typeName>")`; unknown fields, wrong field types, and invalid union values (e.g. `"unit": "banana"`) are REJECTED; entity props PRODUCT/CATEGORY/BRAND/BLOG/BLOG_CATEGORY/RAFFLE = `{ "<entity>Id": "...", "usePageData"?: false }` (e.g. `{ "categoryId": "..." }`) — get real ids from `search_products` (PRODUCT), `list_categories`, `list_brands`, `list_blogs`, `list_blog_categories`; list/collection props are typed objects keyed by a `<entity>ListType` discriminator: PRODUCT_LIST = `{ "id": "<unique>", "productListType": "STATIC", "productIds": [ { "productId": "...", "variantId": "..." } ], "initialLimit"?: 12 }` — note productIds is an array of {productId, variantId} PAIRS (from search_products), not bare ids; productListType ∈ ALL|STATIC|DISCOUNTED|RECOMMENDED|CATEGORY|SEARCH|LAST_VIEWED|RELATED_PRODUCTS|VIEWED_TOGETHER|PURCHASED_TOGETHER, and for a dynamic type use e.g. `{ "id": "<unique>", "productListType": "CATEGORY", "category": "<id from list_categories>" }` (the field is `category`, NOT `categoryId` — `categoryId` is only the standalone CATEGORY entity prop; no productIds here) or `"ALL"` (no ids). Unknown sub-fields inside a PRODUCT_LIST (e.g. `categoryId`) are REJECTED, not silently stored. CATEGORY_LIST = `{ "categoryListType": "STATIC", "categoryIds": ["..."] }` or `{ "categoryListType": "ALL" }`. BRAND_LIST = `{ "brandListType": "STATIC", "brandIds": ["..."] }` or ALL. BLOG_LIST = `{ "blogListType": "STATIC", "blogIds": ["..."] }`, `{ "blogListType": "CATEGORY", "categoryId": "..." }`, or ALL. BLOG_CATEGORY_LIST = `{ "blogCategoryListType": "STATIC", "blogCategoryIds": ["..."] }` or ALL. (Get the entity ids from search_products / list_*; you can also read an existing value with `get_section_values` to copy its exact shape.) LINK = a single link object `{ "id": "<unique-short-id>", "linkType": "PAGE"|"EXTERNAL"|"FILE", "label": "...", "openInNewTab"?: false, "subLinks": [] }` plus the target for its type — for PAGE add `"pageId"` AND `"pageType"` (both from `list_editor_pages`, e.g. pageType "INDEX"; for dynamic page types like PRODUCT/CATEGORY/BLOG also add the target `"itemId"`), for EXTERNAL add `"externalLink": "https://..."`. Examples: PAGE → `{ "id": "k3p9x", "linkType": "PAGE", "label": "Home", "pageId": "<page-id>", "pageType": "INDEX", "openInNewTab": false, "subLinks": [] }`; EXTERNAL → `{ "id": "m7q2z", "linkType": "EXTERNAL", "label": "Docs", "externalLink": "https://example.com", "openInNewTab": true, "subLinks": [] }`. To link to an ENTITY (category/brand/blog/blog-category) do NOT guess a slug — get the entity id from `list_categories`/`list_brands`/`list_blogs`/`list_blog_categories` and the page id from `get_page_by_type("<TYPE>")`, then build `{ "id": "<unique>", "linkType": "PAGE", "label": "...", "pageId": <pageId>, "pageType": "<TYPE>", "itemId": <entityId>, "subLinks": [] }`. LIST_OF_LINK = `{ "links": [ <link>, ... ] }` (each item is a link object as above). PRODUCT = `{ "productId": "...", "variantId"?: "..." }`. This SAME unwrapped shape applies whether the prop sits at section level OR nested inside a COMPONENT_LIST entry\'s `propValues` — there is no section-vs-nested difference. IMPORTANT: pass `value` as the parsed JSON object/array itself, NEVER as a JSON string (a stringified value is double-encoded and stored as a useless string). Values are validated server-side (deeply, including nested COMPONENT_LIST children): wrong shapes AND wrong semantics are REJECTED with an explanatory error instead of being silently stored. This includes: a `{ "value": [...] }` wrapper for a COMPONENT_LIST; a `{ "value": ... }`-wrapped IMAGE; `props` instead of `propValues`; a missing/duplicate entry `id`; a child referenced by the wrong key (a code component MUST use `codeComponentId`, a theme component `componentId`) or by an id that does not exist (child ids are opaque — take them from `get_section_template` / `list_imported_sections`, never invent them); and any `propValues` key that is not a real prop of that child component (the error lists the valid prop names). Auto-import: a referenced child code component that has been BUILT but not yet imported into the theme is imported automatically before the value is set (the response lists any such ids under `autoImported`); only children that are not built at all fail with a "no imported code component" error — build them first (`ikas-component build`/`dev`). For any prop type you are unsure about, inspect the existing value with `get_section_values` and match its exact shape. The change is applied with undo support.\n\n' +
|
|
3791
|
+
server.tool("update_section_prop", 'Change a single prop value of a section placed on a page. This is the tool for FILLING/SETTING content (heading, text, image, link, etc.) on an existing section — it is pure data entry: it writes NO component code and needs NO rebuild. Do not author a new prop or component to set a value the section already supports; first check the section\'s existing `props` via `list_page_sections`. Target the specific placement by its `element_id` (from `list_page_sections`) and the prop by `prop_id` or `prop_name` (also from `list_page_sections`). `value` is the prop value object as stored by the editor — for scalar props it is wrapped as `{ "value": <scalar> }` (e.g. `{ "value": "Hello" }` for TEXT, `{ "value": true }` for BOOLEAN, `{ "value": 12 }` for NUMBER, `{ "value": "#FF0000" }` for COLOR, `{ "value": "<enum-key>" }` for ENUM). A COLOR prop can ALSO bind to a global theme color instead of a literal hex — pass `{ "patternValueId": "<id>" }` (the color id from `list_theme_globals` → `colors[].id`) and the prop tracks that global color live (resolved to `var(--<id>)` at render); the id is validated and an unknown one is REJECTED. Richer object prop types use their own object shape and are NOT `{ "value": ... }`-wrapped (full catalog in `get_editor_workflow`): IMAGE = `{ "id": "<asset-id>", "altText"?: "<alt>", "isVideo"?: false }` (the `id` MUST reference an already-uploaded asset — call `upload_image` with a `file_path`/`image_url` to get one, or reuse an existing `id` from another section\'s `propValues`); IMAGE_LIST = `{ "images": [ <image>, ... ] }`; VIDEO = `{ "video": { "id": "<asset-id>" }, "thumbnailImage"?: { "id": "..." }, "autoplay"?: false, "controls"?: true, "loop"?: false, "muted"?: false }` (NOT a bare `{ "id": ... }`); SVG = `{ "value": "<svg markup>" }` (value-wrapped, like a scalar); SVG_LIST = `{ "svgs": [ "<svg>", ... ] }`; TYPE (style props such as BorderRadiusStyleType/PaddingStyleType/GapStyleType) = the type-shaped object ITSELF, e.g. `{ "value": 8, "unit": "px" }` — get the exact field structure from the prop\'s `writeExample`/`typeName` in `get_component_props` or `get_type_definition("<typeName>")`; unknown fields, wrong field types, and invalid union values (e.g. `"unit": "banana"`) are REJECTED; entity props PRODUCT/CATEGORY/BRAND/BLOG/BLOG_CATEGORY/RAFFLE = `{ "<entity>Id": "...", "usePageData"?: false }` (e.g. `{ "categoryId": "..." }`) — get real ids from `search_products` (PRODUCT), `list_categories`, `list_brands`, `list_blogs`, `list_blog_categories`; PRODUCT_ATTRIBUTE = `{ "productId": "...", "variantId": "...", "attributeId": "..." }` — `variantId` is REQUIRED alongside `productId` (without it the value resolves with `product: null` and the component renders nothing), or bind to the page product with `{ "attributeId": "...", "usePageData": true }` on a product detail page; PRODUCT_ATTRIBUTE_LIST = the same with `"attributeIds": [ "..." ]`, and note the component receives a SINGLE wrapper object, never an array (read both with `getAttributeDetailValues()` / `getAttributeListValues()` — see `get_framework_guide("prop-types")`); list/collection props are typed objects keyed by a `<entity>ListType` discriminator: PRODUCT_LIST = `{ "id": "<unique>", "productListType": "STATIC", "productIds": [ { "productId": "...", "variantId": "..." } ], "initialLimit"?: 12 }` — note productIds is an array of {productId, variantId} PAIRS (from search_products), not bare ids; productListType ∈ ALL|STATIC|DISCOUNTED|RECOMMENDED|CATEGORY|SEARCH|LAST_VIEWED|RELATED_PRODUCTS|VIEWED_TOGETHER|PURCHASED_TOGETHER, and for a dynamic type use e.g. `{ "id": "<unique>", "productListType": "CATEGORY", "category": "<id from list_categories>" }` (the field is `category`, NOT `categoryId` — `categoryId` is only the standalone CATEGORY entity prop; no productIds here) or `"ALL"` (no ids). Unknown sub-fields inside a PRODUCT_LIST (e.g. `categoryId`) are REJECTED, not silently stored. CATEGORY_LIST = `{ "categoryListType": "STATIC", "categoryIds": ["..."] }` or `{ "categoryListType": "ALL" }`. BRAND_LIST = `{ "brandListType": "STATIC", "brandIds": ["..."] }` or ALL. BLOG_LIST = `{ "blogListType": "STATIC", "blogIds": ["..."] }`, `{ "blogListType": "CATEGORY", "categoryId": "..." }`, or ALL. BLOG_CATEGORY_LIST = `{ "blogCategoryListType": "STATIC", "blogCategoryIds": ["..."] }` or ALL. (Get the entity ids from search_products / list_*; you can also read an existing value with `get_section_values` to copy its exact shape.) LINK = a single link object `{ "id": "<unique-short-id>", "linkType": "PAGE"|"EXTERNAL"|"FILE", "label": "...", "openInNewTab"?: false, "subLinks": [] }` plus the target for its type — for PAGE add `"pageId"` AND `"pageType"` (both from `list_editor_pages`, e.g. pageType "INDEX"; for dynamic page types like PRODUCT/CATEGORY/BLOG also add the target `"itemId"`), for EXTERNAL add `"externalLink": "https://..."`. Examples: PAGE → `{ "id": "k3p9x", "linkType": "PAGE", "label": "Home", "pageId": "<page-id>", "pageType": "INDEX", "openInNewTab": false, "subLinks": [] }`; EXTERNAL → `{ "id": "m7q2z", "linkType": "EXTERNAL", "label": "Docs", "externalLink": "https://example.com", "openInNewTab": true, "subLinks": [] }`. To link to an ENTITY (category/brand/blog/blog-category) do NOT guess a slug — get the entity id from `list_categories`/`list_brands`/`list_blogs`/`list_blog_categories` and the page id from `get_page_by_type("<TYPE>")`, then build `{ "id": "<unique>", "linkType": "PAGE", "label": "...", "pageId": <pageId>, "pageType": "<TYPE>", "itemId": <entityId>, "subLinks": [] }`. LIST_OF_LINK = `{ "links": [ <link>, ... ] }` (each item is a link object as above). PRODUCT = `{ "productId": "...", "variantId"?: "..." }`. This SAME unwrapped shape applies whether the prop sits at section level OR nested inside a COMPONENT_LIST entry\'s `propValues` — there is no section-vs-nested difference. IMPORTANT: pass `value` as the parsed JSON object/array itself, NEVER as a JSON string (a stringified value is double-encoded and stored as a useless string). Values are validated server-side (deeply, including nested COMPONENT_LIST children): wrong shapes AND wrong semantics are REJECTED with an explanatory error instead of being silently stored. This includes: a `{ "value": [...] }` wrapper for a COMPONENT_LIST; a `{ "value": ... }`-wrapped IMAGE; `props` instead of `propValues`; a missing/duplicate entry `id`; a child referenced by the wrong key (a code component MUST use `codeComponentId`, a theme component `componentId`) or by an id that does not exist (child ids are opaque — take them from `get_section_template` / `list_imported_sections`, never invent them); and any `propValues` key that is not a real prop of that child component (the error lists the valid prop names). Auto-import: a referenced child code component that has been BUILT but not yet imported into the theme is imported automatically before the value is set (the response lists any such ids under `autoImported`); only children that are not built at all fail with a "no imported code component" error — build them first (`ikas-component build`/`dev`). For any prop type you are unsure about, inspect the existing value with `get_section_values` and match its exact shape. The change is applied with undo support.\n\n' +
|
|
3792
3792
|
'BIND TO A GLOBAL VARIABLE (Theme Setting): instead of a static `value`, pass `bind_global` = the global\'s `name` (its `variableName`, the `_<id>` form) from `list_theme_globals`. The prop becomes a LIVE dynamic value that always reflects that global\'s current value — editing the global in Theme Settings updates every bound prop. Use this to wire content to a project-wide setting (e.g. a shared announcement text, brand color, or logo image) rather than duplicating a literal. Pick a global whose type matches the prop (TEXT↔TEXT/RICH_TEXT, NUMBER↔NUMBER, BOOLEAN↔BOOLEAN, IMAGE↔IMAGE, COLOR↔COLOR, …). Provide `value` OR `bind_global`, not both. To bind a prop of a COMPONENT_LIST CHILD, also pass `list_path` (the `{ propId, index }` steps down to the child) and set `prop_id` to the child\'s prop id. Works for every section type, including built-in / code-component sections that have no blueprint.\n\n' +
|
|
3793
3793
|
'COMPONENT_LIST / COMPONENT props: the value is NOT wrapped in `{ "value": ... }`. A COMPONENT_LIST value is `{ "components": [ <entry>, ... ] }`; a single COMPONENT value is one `<entry>`. Each entry = `{ "id": "<unique-id>", "codeComponentId": "<child-id>" | "componentId": "<child-id>", "propValues": { "<childPropName>": <wrapped-value>, ... } }`. Rules: (1) `id` is a unique short alphanumeric string identifying THIS entry — generate a fresh one per new entry and NEVER reuse an existing entry\'s id. (2) Use `codeComponentId` for code components, `componentId` for built-in theme components. (3) `propValues` is keyed by the CHILD component\'s prop NAMES, each using the same scalar wrappers as above (nesting is recursive — a child may itself hold a COMPONENT_LIST). (4) `update_section_prop` REPLACES the whole prop value — there is NO partial/deep merge. ALWAYS send the COMPLETE, fully-nested value for the prop: every existing entry (with its id), every nested `propValues`, and every nested COMPONENT_LIST at every depth. To change anything (even one deeply-nested scalar, or to add/remove/reorder a child), READ-MODIFY-WRITE: take the current `{ "components": [...] }` from `get_section_values`, edit it in place (preserving all other entries, ids, and nested structures), then send the ENTIRE updated object back. Sending only the changed part — a single entry, or a child without its siblings — wipes everything you omitted. (5) You can only add child component types the prop permits — call `get_component_props(parentComponentId)` to read the COMPONENT_LIST prop\'s `allowedComponentIds`, then `get_component_props(childId)` to learn that child\'s props (names, types, ENUM `options`/valid values) instead of guessing or reading ikas.config.json. The allowed set is wired at build/config time per the `get_section_template` setup recipe (`config update-prop`), not here.\n\n' +
|
|
3794
3794
|
'Example — a full COMPONENT_LIST `value` with two children, where the second child itself nests another COMPONENT_LIST (note: scalar leaves are `{ "value": ... }`-wrapped, COMPONENT_LIST values are `{ "components": [...] }` and are NOT wrapped, every entry has a unique `id`, and this is the COMPLETE value you would send even to change just one field): ' +
|