@ikas/code-components-mcp 1.4.0-beta.192 → 1.4.0-beta.194
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 +8 -47
- package/data/storefront-api.json +167 -13
- package/data/storefront-types.json +63 -3
- package/dist/index.js +47 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/data/framework.json
CHANGED
|
@@ -115,14 +115,7 @@
|
|
|
115
115
|
"title": "CSS Scoping Rules",
|
|
116
116
|
"description": "How CSS is scoped to prevent conflicts between components",
|
|
117
117
|
"content": "CSS files referenced in `ikas.config.json` are automatically scoped at build time.\n\n### How it works:\n- Each component's CSS selectors are prefixed with a unique scope identifier\n- `.my-class` becomes `.[scope]-my-class` in the output\n- This prevents style conflicts between different components on the same page\n\n### Best practices:\n- Use class selectors (`.my-class`) rather than element selectors (`div`, `h1`)\n- Avoid using `!important` - scoping handles isolation\n- Don't use global selectors or `@global` - they won't be scoped\n- Keep CSS in the component's `styles.css` file referenced in config, or in a sub-component's sibling `styles.css` — both are collected and scoped automatically on `dev`/`publish`\n- Nested selectors work: `.parent .child` both get scoped\n\n### Example:\n```css\n/* What you write */\n.product-card {\n padding: 16px;\n border: 1px solid #eee;\n}\n\n.product-card .title {\n font-size: 18px;\n font-weight: bold;\n}\n\n.product-card .price {\n color: #e53e3e;\n}\n```\n\nThe build system transforms this so your `.product-card`, `.title`, and `.price` classes are unique to your component and won't clash with any other component or theme styles.\n\n### Global CSS Exception\n\nThe one intentional exception to scoping is **Global CSS** — project-wide styles defined in `src/global.css` that are loaded unscoped before any component CSS. Global CSS is ideal for CSS custom properties (variables), resets, and shared utility classes. The loading order is: **Global CSS → Shared chunk CSS → Component CSS**, so global styles have the lowest specificity and component styles always win.\n\nSee `get_framework_guide(\"global-css\")` for full details on global CSS setup and usage.",
|
|
118
|
-
"tags": [
|
|
119
|
-
"css",
|
|
120
|
-
"scoping",
|
|
121
|
-
"styles",
|
|
122
|
-
"isolation",
|
|
123
|
-
"classes",
|
|
124
|
-
"global"
|
|
125
|
-
]
|
|
118
|
+
"tags": ["css", "scoping", "styles", "isolation", "classes", "global"]
|
|
126
119
|
},
|
|
127
120
|
"global-css": {
|
|
128
121
|
"title": "Global CSS",
|
|
@@ -161,14 +154,7 @@
|
|
|
161
154
|
"title": "Import Patterns",
|
|
162
155
|
"description": "How to import storefront functions and types",
|
|
163
156
|
"content": "### Storefront Functions & Types\nImport API functions and TypeScript types from `@ikas/bp-storefront`:\n```typescript\nimport {\n // Product functions\n getSelectedProductVariant,\n getDisplayedProductVariantTypes,\n selectVariantValue,\n hasProductVariantStock,\n getProductVariantFormattedFinalPrice,\n \n // Cart functions\n addItemToCart,\n cartStore,\n \n // Navigation\n Router,\n \n // Customer\n customerStore,\n customerLogin,\n hasCustomer,\n \n // Favorites\n addIkasProductToFavorites,\n isFavoriteIkasProduct,\n\n // Type models\n IkasProduct,\n IkasProductVariant,\n IkasCart,\n IkasOrderLineItem,\n IkasCustomer,\n IkasOrder,\n IkasCategory,\n IkasBrand,\n IkasBlog\n} from \"@ikas/bp-storefront\";\n```\n\n### Store Instances\nPre-initialized MobX stores:\n```typescript\nimport { cartStore, customerStore } from \"@ikas/bp-storefront\";\n\n// Cart data\ncartStore.cart?.orderLineItems\n\n// Customer data \ncustomerStore.customer?.email\n```\n\n### Observer (for sub-components with reactive updates)\n```typescript\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\n// Sub-component: needs observer() for independent reactivity\nconst CartBadge = observer(function CartBadge() {\n const itemCount = cartStore.cart?.orderLineItems.length ?? 0;\n return <span>Cart: {itemCount}</span>;\n});\n\n// Root export: plain function — ikas runtime handles reactivity via autorun()\nexport function MySection({ title }: Props) {\n return (\n <div>\n <h1>{title}</h1>\n <CartBadge />\n </div>\n );\n}\n\nexport default MySection;\n```\n\nDo **NOT** wrap root component exports with `observer()`. The ikas runtime handles root reactivity automatically via `autorun()`. Only wrap **sub-components** with `observer()` when they independently read from stores (`cartStore`, `customerStore`).\n\n### ⛔ Never deep-import from the SDK\n\nImport ONLY from the bare package root `@ikas/bp-storefront` (and likewise `@ikas/bp-storefront-models`/`-config`). **NEVER** import a subpath such as `@ikas/bp-storefront/dist/...` or `@ikas/bp-storefront/src/...`. Those private paths bundle without error but have NO window global in the published storefront, so the component throws at runtime — and `ikas-component build`/`dev` now hard-rejects them. If a helper looks missing from the root, it almost always exists there under a public name. Examples:\n\n- Coupon code → `getCouponCodeForm(customerStore)`, `initCouponCodeForm`, `setCouponCodeFormCouponCode`, `submitCouponCodeForm`, `removeCouponCodeForm`\n- Cart note / generic cart save → `saveCart`\n- Cart data → `cartStore`, `getCart`\n\n### Known misspelled export — import it verbatim\n\n`getIkasProductAttrributeProductIds` is spelled with a double **r** (\"Attrribute\"). That IS the real export name: it ships in `@ikas/bp-storefront` and is kept as-is because renaming a published export would break every theme importing it. Use it exactly as written — the correctly spelled `getIkasProductAttributeProductIds` does not exist and will not compile. Do NOT report it as a typo or \"fix\" the spelling. (In the blueprint editor its display alias is `getProductIds`, which hides the misspelling.)",
|
|
164
|
-
"tags": [
|
|
165
|
-
"import",
|
|
166
|
-
"storefront",
|
|
167
|
-
"models",
|
|
168
|
-
"types",
|
|
169
|
-
"observer",
|
|
170
|
-
"stores"
|
|
171
|
-
]
|
|
157
|
+
"tags": ["import", "storefront", "models", "types", "observer", "stores"]
|
|
172
158
|
},
|
|
173
159
|
"sections-vs-components": {
|
|
174
160
|
"title": "Sections vs Components",
|
|
@@ -191,7 +177,7 @@
|
|
|
191
177
|
"common-pitfalls": {
|
|
192
178
|
"title": "Common Pitfalls",
|
|
193
179
|
"description": "Frequent mistakes LLMs and developers make when building ikas code components",
|
|
194
|
-
"content": "## Common Pitfalls\n\nThese are the most common mistakes when building ikas code components. Avoid them for correct, working code.\n\n### 1. Root Component Should NOT Use observer\n\nThe ikas runtime wraps root component renders in `autorun()`, making them automatically reactive. Wrapping a root export with `observer()` is redundant and misleading.\n\n**Wrong** — observer on root export:\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\nconst CartSection = observer(function CartSection({ title }: Props) {\n const items = cartStore.cart?.orderLineItems ?? [];\n return <section>{title}: {items.length} items</section>;\n});\nexport default CartSection;\n```\n\n**Correct** — plain named export:\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\n\nexport function CartSection({ title }: Props) {\n const items = cartStore.cart?.orderLineItems ?? [];\n return <section>{title}: {items.length} items</section>;\n}\n\nexport default CartSection;\n```\n\n### 2. Observer Sub-Component Naming\n\nWhen using `observer()` on **sub-components**, use a named function expression — not an arrow function — for proper DevTools display names.\n\n**Wrong** — arrow function loses display name:\n```tsx\nconst CartBadge = observer(() => {\n return <span>{cartStore.cart?.orderLineItems.length ?? 0}</span>;\n});\n```\n\n**Correct** — named function expression:\n```tsx\nconst CartBadge = observer(function CartBadge() {\n return <span>{cartStore.cart?.orderLineItems.length ?? 0}</span>;\n});\n```\n\n### 3. Mutation Semantics\n\nMany storefront functions (122+) return `void` and **mutate their arguments in place**. Do NOT try to capture a return value:\n\n```tsx\n// WRONG — selectVariantValue returns void, not a new product\nconst updated = selectVariantValue(product, value);\n\n// CORRECT — mutates product in place, observer re-renders automatically\nselectVariantValue(product, dvv.variantValue);\n```\n\nOther mutation functions: `initLoginForm()`, `setLoginFormEmail()`, `clearFilter()`, `selectFilterValue()`.\n\n### 4. CSS Scoping Limits\n\nOnly **class selectors** in `styles.css` are reliably scoped. Element selectors are NOT scoped:\n\n```css\n/* SAFE — scoped to your component */\n.product-card { padding: 16px; }\n.product-card .title { font-size: 18px; }\n\n/* UNSAFE — NOT reliably scoped, may affect other components */\ndiv { margin: 0; }\nh1 { font-size: 24px; }\n```\n\nAlways use class selectors for all styles.\n\n### 5. Prop Null Handling\n\nProps from the editor can be `undefined` when the store owner hasn't set them. Always use optional chaining:\n\n```tsx\n// WRONG — will crash if product is undefined\n<h1>{props.product.name}</h1>\n\n// CORRECT\n<h1>{props.product?.name}</h1>\n{props.heroImage && <img src={getDefaultSrc(props.heroImage)} />}\n```\n\n### 6. IkasProductImage vs IkasImage\n\n`variant.images` is `IkasProductImage[]`, NOT `IkasImage[]`. You must access the `.image` property to get the `IkasImage` needed by CDN helpers:\n\n```tsx\n// WRONG — productImage is IkasProductImage, not IkasImage\ngetDefaultSrc(productImage);\n\n// CORRECT — access .image to get IkasImage\ngetDefaultSrc(productImage.image);\n\n// Full pattern:\nconst images: IkasImage[] = variant.images\n .map((pi) => pi.image)\n .filter((img): img is IkasImage => img != null);\n```\n\n### 7. Type Assertion Pattern\n\nSome storefront functions have type inference gaps. Use `as unknown as` casts when needed — this is a known pattern:\n\n```tsx\nconst inStock = hasProductStock(product) as unknown as boolean;\nconst finalPrice = getProductVariantFormattedFinalPrice(variant) as unknown as string;\nconst canAddToCart = isAddToCartEnabled(product) as unknown as boolean;\n```\n\nThis applies to functions like `hasProductStock`, `hasProductVariantStock`, `isAddToCartEnabled`, `hasProductVariantDiscount`, `getProductVariantDiscountPercentage`, `getProductVariantFormattedFinalPrice`, `getProductVariantFormattedSellPrice`, `getProductVariantFormattedDiscountAmount`, and `getProductVariantFormattedCampaignPrice`.\n\nNote: `getProductVariantMainImage` returns `IkasProductImage | undefined` (NOT `IkasImage`). Access `.image` to get the `IkasImage` for CDN helpers like `getDefaultSrc()`.\n\n### 8. Store Data Null Safety\n\nStore data (`customerStore.customer`, `cartStore.cart`, `baseStore`) is `null` before initialization completes. Always guard access:\n\n```tsx\n// WRONG — crashes if customer is null\n<h1>{customerStore.customer.firstName}</h1>\n\n// CORRECT — null check first\nif (!customerStore.customer) return <div>Loading...</div>;\n<h1>{customerStore.customer.firstName}</h1>\n\n// Also correct — optional chaining\n<h1>{customerStore.customer?.firstName ?? \"Guest\"}</h1>\n```\n\nSame for `cartStore.cart` — always use `cartStore.cart?.orderLineItems ?? []`.\n\n### 9. ProductList/BlogList Data Access\n\n`productList.data` is the correct way to access products in a product list. Similarly, `blogList.data` for blogs. The display names `.products` / `.blogs` are only used by the blueprint editor — they do NOT exist at runtime:\n\n```tsx\n// CORRECT — use .data for both product lists and blog lists\nconst products = productList?.data ?? [];\nconst blogs = blogList?.data ?? [];\n\n// WRONG — .products / .blogs are display names, not actual fields\nconst products = productList?.products ?? [];\nconst blogs = blogList?.blogs ?? [];\n```\n\n### 10. Form Field Access Pattern\n\nForm fields are objects with `.value`, `.label`, `.hasError`, `.message`. Never access the field directly as a primitive:\n\n```tsx\n// WRONG — loginForm.email is a field object, not a string\n<input value={loginForm.email} />\n\n// CORRECT — access .value for the actual value\n<input value={loginForm.email.value} />\n{loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n```\n\n### 11. Optional Chaining for Editor Props\n\nAll props from `ikas.config.json` can be `undefined` in the editor before the store owner sets them. Always use optional chaining and defaults:\n\n```tsx\n// WRONG — will crash in the editor\n<h1>{props.title}</h1>\n<img src={getDefaultSrc(props.image)} />\n{props.links.links.map(...)}\n\n// CORRECT — safe access with defaults\n<h1>{props.title ?? \"Default Title\"}</h1>\n{props.image && <img src={getDefaultSrc(props.image)} />}\n{(props.links?.links ?? []).map(...)}\n```\n\n### 12. Event Handler Typing\n\nPreact uses different event types than React. Use `(e: Event)` not `(e: React.ChangeEvent)`. Access values with casting. Preact uses `onInput` not `onChange` for text inputs:\n\n```tsx\n// WRONG — React patterns don't work in Preact\nonChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}\n\n// CORRECT — Preact event handling\nonInput={(e: Event) => setValue((e.target as HTMLInputElement).value)}\n```\n\nFor select elements:\n```tsx\nonChange={(e: Event) => setOption((e.target as HTMLSelectElement).value)}\n```\n\n### 13. Function Parameter Order\n\nMany storefront functions take specific parameter orders. Always verify with `get_function_doc()` before using:\n\n```tsx\n// WRONG — submitLoginForm only takes the form, not the store\nsubmitLoginForm(customerStore, loginForm);\n\n// CORRECT\nsubmitLoginForm(loginForm);\n\n// WRONG — wrong parameter order for addItemToCart\naddItemToCart(product, variant, 1);\n\n// CORRECT — variant first, then product, then quantity\naddItemToCart(variant, product, 1);\n\n// WRONG — selectVariantValue takes product and variantValue\nselectVariantValue(variant, value);\n\n// CORRECT\nselectVariantValue(product, dvv.variantValue);\n```\n\nWhen in doubt, use the `get_function_doc(functionName)` MCP tool to check the exact signature.\n\n### 14. Missing backgroundColor Prop on Sections\n\nEvery section MUST include a `backgroundColor` COLOR prop so store owners can customize the section background. Without it, the section has a hardcoded background that cannot be changed in the editor.\n\n**Wrong** — section with no backgroundColor prop:\n```json\n// ikas.config.json\n{ \"id\": \"my-section\", \"type\": \"section\", \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\" }\n]}\n```\n```tsx\nexport function MySection({ title }: Props) {\n return <section className=\"my-section\"><h1>{title}</h1></section>;\n}\n```\n\n**Correct** — section with backgroundColor prop:\n```json\n// ikas.config.json\n{ \"id\": \"my-section\", \"type\": \"section\", \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\" },\n { \"name\": \"backgroundColor\", \"displayName\": \"Background Color\", \"type\": \"COLOR\", \"defaultValue\": \"#ffffff\" }\n]}\n```\n```tsx\nexport function MySection({ title, backgroundColor = \"#ffffff\" }: Props) {\n return (\n <section className=\"my-section\" style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>{title}</h1>\n </section>\n );\n}\n```\n\nOptionally, also consider adding `textColor` COLOR props for text elements sitting directly on the section background, so they remain readable when the store owner changes the background color.\n\n### 15. Forgetting to Group Props for Complex Sections\n\nSections with 5+ props should use prop groups to organize the editor sidebar. Without groups, store owners see a long flat list of unrelated props.\n\n**Better** — organize related props into groups:\n```json\n{\n \"propGroups\": [\n { \"id\": \"content\", \"name\": \"Content\" },\n { \"id\": \"appearance\", \"name\": \"Appearance\" }\n ],\n \"props\": [\n { \"name\": \"heading\", \"groupId\": \"content\", ... },\n { \"name\": \"bgColor\", \"groupId\": \"appearance\", ... }\n ]\n}\n```\n\nSee `get_framework_guide(\"prop-groups\")` for full details.\n\n### 16. Hardcoded Static Text in Components\n\nAll user-visible text MUST be TEXT props — never hardcode strings in JSX. Hardcoded text cannot be translated for multilingual storefronts.\n\n**Wrong** — hardcoded strings:\n```tsx\nexport function LoginSection({ backgroundColor }: Props) {\n return (\n <section style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>Sign In</h1>\n <button>{form.isSubmitting ? \"Signing in...\" : \"Sign In\"}</button>\n <p>Don't have an account? <a href=\"/register\">Create one</a></p>\n </section>\n );\n}\n```\n\n**Correct** — all text as TEXT props with defaultValues:\n```json\n// ikas.config.json\n{ \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\", \"defaultValue\": \"Sign In\", \"groupId\": \"texts\" },\n { \"name\": \"submitButtonText\", \"type\": \"TEXT\", \"defaultValue\": \"Sign In\", \"groupId\": \"texts\" },\n { \"name\": \"submittingButtonText\", \"type\": \"TEXT\", \"defaultValue\": \"Signing in...\", \"groupId\": \"texts\" },\n { \"name\": \"noAccountText\", \"type\": \"TEXT\", \"defaultValue\": \"Don't have an account?\", \"groupId\": \"texts\" },\n { \"name\": \"createAccountLinkText\", \"type\": \"TEXT\", \"defaultValue\": \"Create one\", \"groupId\": \"texts\" }\n],\n\"propGroups\": [{ \"id\": \"texts\", \"name\": \"Texts\" }]}\n```\n```tsx\nexport function LoginSection({ title = \"Sign In\", submitButtonText = \"Sign In\",\n submittingButtonText = \"Signing in...\", noAccountText = \"Don't have an account?\",\n createAccountLinkText = \"Create one\", backgroundColor }: Props) {\n return (\n <section style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>{title}</h1>\n <button>{form.isSubmitting ? submittingButtonText : submitButtonText}</button>\n <p>{noAccountText} <a href=\"/register\">{createAccountLinkText}</a></p>\n </section>\n );\n}\n```\n\nFor button loading states, use two separate TEXT props (e.g., `submitButtonText` + `submittingButtonText`). Group text props under a \"Texts\" propGroup when the component has 5+ props.\n\n### 17. IkasOrderLineItemOption.values is an Array, Not a Scalar\n\n`IkasOrderLineItemOption` (the per-item options on cart/order line items) has a `.values` field that is an **array** of `IkasOrderLineItemOptionValue`, NOT a scalar `.value`. Each value in the array has its own `.name` / `.value` / `.formattedPrice`.\n\n```tsx\n// WRONG — there is no .value on IkasOrderLineItemOption\n<span>{option.value}</span>\n\n// CORRECT — .values is an array; map over it\n{option.values.map((v) => (\n <span key={v.name}>{v.name}: {v.value}</span>\n))}\n```\n\nThis is the most common confusion when rendering order summary lines or cart line item options. See `get_migration_guide(\"prop-runtime-shapes\")` for the broader catalog of order-shape gotchas.\n\n### 18. IkasOrderAdjustment Has No .formattedAmount — Use the Helper\n\n`IkasOrderAdjustment` does not expose a `.formattedAmount` property directly. Call the helper `getOrderAdjustmentFormattedAmount(adjustment)` from `@ikas/bp-storefront` — it returns the formatted currency string with the correct sign (negated for decrements).\n\n```tsx\nimport { getOrderAdjustmentFormattedAmount } from \"@ikas/bp-storefront\";\n\n// WRONG — .formattedAmount does not exist\n<td>{adjustment.formattedAmount}</td>\n\n// CORRECT\n<td>{getOrderAdjustmentFormattedAmount(adjustment)}</td>\n```\n\nSee `get_function_doc(\"getOrderAdjustmentFormattedAmount\")` for the exact signature.\n\n### 19. Old-System Property Patterns That Don't Exist in the New System\n\nSeveral `@ikas/storefront` properties look like they should still work but don't exist on the corresponding `@ikas/bp-storefront` types. Use the helper function instead. **If TypeScript reports TS2339 (\"Property X does not exist on type Y\") on a `.selectedVariant`, `.mainImage`, `.href`, or `.checkoutUrl` access, you're hitting one of these.**\n\n| Old pattern | New replacement |\n|---|---|\n| `product.selectedVariant` | `getSelectedProductVariant(product)` → `IkasProductVariant \\| undefined` |\n| `product.href` | `getProductHref(product)` (one of several `get*Href` helpers — see `navigation-patterns`) |\n| `variant.mainImage` | `getProductVariantMainImage(variant)` → `IkasProductImage`, then access `.image` for the `IkasImage` |\n| `cartStore.checkoutUrl` | `getCheckoutUrlFromCartStore(cartStore)` (handles auth-state branching internally) |\n\nThe variant-to-image chain is 4 hops (each carries metadata the next doesn't). See `get_framework_guide(\"image-handling\")` for the canonical recipe.\n\n### 20. Intl.NumberFormat for Currency Amounts\n\nNever format money with `Intl.NumberFormat` — it falls back to the browser locale and ignores the merchant's currency format settings, so its output clashes with platform-formatted prices on the same screen. For model-bound prices use the `get*FormattedPrice` helpers. For free-standing amounts (remaining amount to free shipping, gift wrap threshold, bulk discount limits, cart limit warnings) use `formatCurrency(amount, currencyCode, currencySymbol)` from `@ikas/bp-storefront`.\n\nNote: `Intl.DateTimeFormat(IkasStorefrontConfig.getCurrentLocale(), ...)` IS the correct pattern for dates — do not mirror it for money.\n\n```tsx\n// WRONG — browser-locale format (\"₺14.478,5\") next to platform prices (\"₺ 1,950.00\")\nconst remaining = new Intl.NumberFormat(undefined, { style: \"currency\", currency: \"TRY\" }).format(amount);\n\n// CORRECT — merchant currency format settings\nimport { formatCurrency } from \"@ikas/bp-storefront\";\n\nconst remaining = formatCurrency(amount, cart.currencyCode, cart.currencySymbol);\n```\n\nSee `get_function_doc(\"formatCurrency\")` for the exact signature.\n\n### 21. Loops That Never Yield (Page Freeze)\n\nThree patterns re-queue themselves before the current task ends. The browser never gets to paint or handle input: the tab hard-freezes, and in the editor the whole editor freezes with it. The runtime now slows these loops down instead of freezing, and reports them (`get_runtime_warnings`), but the component is still wrong and still burns CPU.\n\n**Wrong** — MutationObserver that writes into the subtree it observes. Setting `textContent` emits a mutation record even when the value is unchanged:\n```tsx\nconst observer = new MutationObserver(() => {\n label.textContent = title; // re-triggers the observer, forever\n});\nobserver.observe(root, { childList: true, subtree: true });\n```\n\n**Correct** — write only when the value differs (or `disconnect()` around your own writes):\n```tsx\nconst observer = new MutationObserver(() => {\n if (label.textContent !== title) label.textContent = title;\n});\n```\n\n**Wrong** — `setState` on every render from `useLayoutEffect` (no dependency array). Unlike `useEffect`, layout effects run synchronously, so Preact re-renders without ever yielding:\n```tsx\nuseLayoutEffect(() => {\n setHeight(ref.current.offsetHeight);\n});\n```\n\n**Correct** — dependency array, and only set state when it changes:\n```tsx\nuseLayoutEffect(() => {\n const next = ref.current?.offsetHeight ?? 0;\n setHeight((prev) => (prev === next ? prev : next));\n}, [items.length]);\n```\n\n**Wrong** — a `queueMicrotask` / `await Promise.resolve()` chain used as a polling loop. Microtasks run before the next task, so this is a busy loop. The `await` form cannot be caught by the runtime at all — it freezes the page. Repeat with `setTimeout`, `requestAnimationFrame` or an observer instead.\n\n### Deep SDK imports (`@ikas/bp-storefront/dist/...`)\n\n**Wrong** — reaching into a private subpath when a root helper seems missing:\n```tsx\n// breaks at runtime: no window global for this subpath, and the build rejects it\nimport { apiSaveCart } from \"@ikas/bp-storefront/dist/functions/api/cart\";\n```\n\n**Correct** — use the public root export (coupon has dedicated form helpers):\n```tsx\nimport {\n customerStore,\n getCouponCodeForm,\n setCouponCodeFormCouponCode,\n submitCouponCodeForm,\n} from \"@ikas/bp-storefront\";\n```\n\nRule: every import from the storefront SDK must come from the BARE package name — never a `/dist/` or `/src/` subpath.",
|
|
180
|
+
"content": "## Common Pitfalls\n\nThese are the most common mistakes when building ikas code components. Avoid them for correct, working code.\n\n### 1. Root Component Should NOT Use observer\n\nThe ikas runtime wraps root component renders in `autorun()`, making them automatically reactive. Wrapping a root export with `observer()` is redundant and misleading.\n\n**Wrong** — observer on root export:\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\nconst CartSection = observer(function CartSection({ title }: Props) {\n const items = cartStore.cart?.orderLineItems ?? [];\n return <section>{title}: {items.length} items</section>;\n});\nexport default CartSection;\n```\n\n**Correct** — plain named export:\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\n\nexport function CartSection({ title }: Props) {\n const items = cartStore.cart?.orderLineItems ?? [];\n return <section>{title}: {items.length} items</section>;\n}\n\nexport default CartSection;\n```\n\n### 2. Observer Sub-Component Naming\n\nWhen using `observer()` on **sub-components**, use a named function expression — not an arrow function — for proper DevTools display names.\n\n**Wrong** — arrow function loses display name:\n```tsx\nconst CartBadge = observer(() => {\n return <span>{cartStore.cart?.orderLineItems.length ?? 0}</span>;\n});\n```\n\n**Correct** — named function expression:\n```tsx\nconst CartBadge = observer(function CartBadge() {\n return <span>{cartStore.cart?.orderLineItems.length ?? 0}</span>;\n});\n```\n\n### 3. Mutation Semantics\n\nMany storefront functions (122+) return `void` and **mutate their arguments in place**. Do NOT try to capture a return value:\n\n```tsx\n// WRONG — selectVariantValue returns void, not a new product\nconst updated = selectVariantValue(product, value);\n\n// CORRECT — mutates product in place, observer re-renders automatically\nselectVariantValue(product, dvv.variantValue);\n```\n\nOther mutation functions: `initLoginForm()`, `setLoginFormEmail()`, `clearFilter()`, `selectFilterValue()`.\n\n### 4. CSS Scoping Limits\n\nOnly **class selectors** in `styles.css` are reliably scoped. Element selectors are NOT scoped:\n\n```css\n/* SAFE — scoped to your component */\n.product-card { padding: 16px; }\n.product-card .title { font-size: 18px; }\n\n/* UNSAFE — NOT reliably scoped, may affect other components */\ndiv { margin: 0; }\nh1 { font-size: 24px; }\n```\n\nAlways use class selectors for all styles.\n\n### 5. Prop Null Handling\n\nProps from the editor can be `undefined` when the store owner hasn't set them. Always use optional chaining:\n\n```tsx\n// WRONG — will crash if product is undefined\n<h1>{props.product.name}</h1>\n\n// CORRECT\n<h1>{props.product?.name}</h1>\n{props.heroImage && <img src={getDefaultSrc(props.heroImage)} />}\n```\n\n### 6. IkasProductImage vs IkasImage\n\n`variant.images` is `IkasProductImage[]`, NOT `IkasImage[]`. You must access the `.image` property to get the `IkasImage` needed by CDN helpers:\n\n```tsx\n// WRONG — productImage is IkasProductImage, not IkasImage\ngetDefaultSrc(productImage);\n\n// CORRECT — access .image to get IkasImage\ngetDefaultSrc(productImage.image);\n\n// Full pattern:\nconst images: IkasImage[] = variant.images\n .map((pi) => pi.image)\n .filter((img): img is IkasImage => img != null);\n```\n\n### 7. Type Assertion Pattern\n\nSome storefront functions have type inference gaps. Use `as unknown as` casts when needed — this is a known pattern:\n\n```tsx\nconst inStock = hasProductStock(product) as unknown as boolean;\nconst finalPrice = getProductVariantFormattedFinalPrice(variant) as unknown as string;\nconst canAddToCart = isAddToCartEnabled(product) as unknown as boolean;\n```\n\nThis applies to functions like `hasProductStock`, `hasProductVariantStock`, `isAddToCartEnabled`, `hasProductVariantDiscount`, `getProductVariantDiscountPercentage`, `getProductVariantFormattedFinalPrice`, `getProductVariantFormattedSellPrice`, `getProductVariantFormattedDiscountAmount`, and `getProductVariantFormattedCampaignPrice`.\n\nNote: `getProductVariantMainImage` returns `IkasProductImage | undefined` (NOT `IkasImage`). Access `.image` to get the `IkasImage` for CDN helpers like `getDefaultSrc()`.\n\n### 8. Store Data Null Safety\n\nStore data (`customerStore.customer`, `cartStore.cart`, `baseStore`) is `null` before initialization completes. Always guard access:\n\n```tsx\n// WRONG — crashes if customer is null\n<h1>{customerStore.customer.firstName}</h1>\n\n// CORRECT — null check first\nif (!customerStore.customer) return <div>Loading...</div>;\n<h1>{customerStore.customer.firstName}</h1>\n\n// Also correct — optional chaining\n<h1>{customerStore.customer?.firstName ?? \"Guest\"}</h1>\n```\n\nSame for `cartStore.cart` — always use `cartStore.cart?.orderLineItems ?? []`.\n\n### 9. ProductList/BlogList Data Access\n\n`productList.data` is the correct way to access products in a product list. Similarly, `blogList.data` for blogs. The display names `.products` / `.blogs` are only used by the blueprint editor — they do NOT exist at runtime:\n\n```tsx\n// CORRECT — use .data for both product lists and blog lists\nconst products = productList?.data ?? [];\nconst blogs = blogList?.data ?? [];\n\n// WRONG — .products / .blogs are display names, not actual fields\nconst products = productList?.products ?? [];\nconst blogs = blogList?.blogs ?? [];\n```\n\n### 10. Form Field Access Pattern\n\nForm fields are objects with `.value`, `.label`, `.hasError`, `.message`. Never access the field directly as a primitive:\n\n```tsx\n// WRONG — loginForm.email is a field object, not a string\n<input value={loginForm.email} />\n\n// CORRECT — access .value for the actual value\n<input value={loginForm.email.value} />\n{loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n```\n\n### 11. Optional Chaining for Editor Props\n\nAll props from `ikas.config.json` can be `undefined` in the editor before the store owner sets them. Always use optional chaining and defaults:\n\n```tsx\n// WRONG — will crash in the editor\n<h1>{props.title}</h1>\n<img src={getDefaultSrc(props.image)} />\n{props.links.links.map(...)}\n\n// CORRECT — safe access with defaults\n<h1>{props.title ?? \"Default Title\"}</h1>\n{props.image && <img src={getDefaultSrc(props.image)} />}\n{(props.links?.links ?? []).map(...)}\n```\n\n### 12. Event Handler Typing\n\nPreact uses different event types than React. Use `(e: Event)` not `(e: React.ChangeEvent)`. Access values with casting. Preact uses `onInput` not `onChange` for text inputs:\n\n```tsx\n// WRONG — React patterns don't work in Preact\nonChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}\n\n// CORRECT — Preact event handling\nonInput={(e: Event) => setValue((e.target as HTMLInputElement).value)}\n```\n\nFor select elements:\n```tsx\nonChange={(e: Event) => setOption((e.target as HTMLSelectElement).value)}\n```\n\n### 13. Function Parameter Order\n\nMany storefront functions take specific parameter orders. Always verify with `get_function_doc()` before using:\n\n```tsx\n// WRONG — submitLoginForm only takes the form, not the store\nsubmitLoginForm(customerStore, loginForm);\n\n// CORRECT\nsubmitLoginForm(loginForm);\n\n// WRONG — wrong parameter order for addItemToCart\naddItemToCart(product, variant, 1);\n\n// CORRECT — variant first, then product, then quantity\naddItemToCart(variant, product, 1);\n\n// WRONG — selectVariantValue takes product and variantValue\nselectVariantValue(variant, value);\n\n// CORRECT\nselectVariantValue(product, dvv.variantValue);\n```\n\nWhen in doubt, use the `get_function_doc(functionName)` MCP tool to check the exact signature.\n\n### 14. Missing backgroundColor Prop on Sections\n\nEvery section MUST include a `backgroundColor` COLOR prop so store owners can customize the section background. Without it, the section has a hardcoded background that cannot be changed in the editor.\n\n**Wrong** — section with no backgroundColor prop:\n```json\n// ikas.config.json\n{ \"id\": \"my-section\", \"type\": \"section\", \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\" }\n]}\n```\n```tsx\nexport function MySection({ title }: Props) {\n return <section className=\"my-section\"><h1>{title}</h1></section>;\n}\n```\n\n**Correct** — section with backgroundColor prop:\n```json\n// ikas.config.json\n{ \"id\": \"my-section\", \"type\": \"section\", \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\" },\n { \"name\": \"backgroundColor\", \"displayName\": \"Background Color\", \"type\": \"COLOR\", \"defaultValue\": \"#ffffff\" }\n]}\n```\n```tsx\nexport function MySection({ title, backgroundColor = \"#ffffff\" }: Props) {\n return (\n <section className=\"my-section\" style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>{title}</h1>\n </section>\n );\n}\n```\n\nOptionally, also consider adding `textColor` COLOR props for text elements sitting directly on the section background, so they remain readable when the store owner changes the background color.\n\n### 15. Forgetting to Group Props for Complex Sections\n\nSections with 5+ props should use prop groups to organize the editor sidebar. Without groups, store owners see a long flat list of unrelated props.\n\n**Better** — organize related props into groups:\n```json\n{\n \"propGroups\": [\n { \"id\": \"content\", \"name\": \"Content\" },\n { \"id\": \"appearance\", \"name\": \"Appearance\" }\n ],\n \"props\": [\n { \"name\": \"heading\", \"groupId\": \"content\", ... },\n { \"name\": \"bgColor\", \"groupId\": \"appearance\", ... }\n ]\n}\n```\n\nSee `get_framework_guide(\"prop-groups\")` for full details.\n\n### 16. Hardcoded Static Text in Components\n\nAll user-visible text MUST be TEXT props — never hardcode strings in JSX. Hardcoded text cannot be translated for multilingual storefronts.\n\n**Wrong** — hardcoded strings:\n```tsx\nexport function LoginSection({ backgroundColor }: Props) {\n return (\n <section style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>Sign In</h1>\n <button>{form.isSubmitting ? \"Signing in...\" : \"Sign In\"}</button>\n <p>Don't have an account? <a href=\"/register\">Create one</a></p>\n </section>\n );\n}\n```\n\n**Correct** — all text as TEXT props with defaultValues:\n```json\n// ikas.config.json\n{ \"props\": [\n { \"name\": \"title\", \"type\": \"TEXT\", \"defaultValue\": \"Sign In\", \"groupId\": \"texts\" },\n { \"name\": \"submitButtonText\", \"type\": \"TEXT\", \"defaultValue\": \"Sign In\", \"groupId\": \"texts\" },\n { \"name\": \"submittingButtonText\", \"type\": \"TEXT\", \"defaultValue\": \"Signing in...\", \"groupId\": \"texts\" },\n { \"name\": \"noAccountText\", \"type\": \"TEXT\", \"defaultValue\": \"Don't have an account?\", \"groupId\": \"texts\" },\n { \"name\": \"createAccountLinkText\", \"type\": \"TEXT\", \"defaultValue\": \"Create one\", \"groupId\": \"texts\" }\n],\n\"propGroups\": [{ \"id\": \"texts\", \"name\": \"Texts\" }]}\n```\n```tsx\nexport function LoginSection({ title = \"Sign In\", submitButtonText = \"Sign In\",\n submittingButtonText = \"Signing in...\", noAccountText = \"Don't have an account?\",\n createAccountLinkText = \"Create one\", backgroundColor }: Props) {\n return (\n <section style={backgroundColor ? { backgroundColor } : undefined}>\n <h1>{title}</h1>\n <button>{form.isSubmitting ? submittingButtonText : submitButtonText}</button>\n <p>{noAccountText} <a href=\"/register\">{createAccountLinkText}</a></p>\n </section>\n );\n}\n```\n\nFor button loading states, use two separate TEXT props (e.g., `submitButtonText` + `submittingButtonText`). Group text props under a \"Texts\" propGroup when the component has 5+ props.\n\n### 17. IkasOrderLineItemOption.values is an Array, Not a Scalar\n\n`IkasOrderLineItemOption` (the per-item options on cart/order line items) has a `.values` field that is an **array** of `IkasOrderLineItemOptionValue`, NOT a scalar `.value`. Each value in the array has its own `.name` / `.value` / `.formattedPrice`.\n\n```tsx\n// WRONG — there is no .value on IkasOrderLineItemOption\n<span>{option.value}</span>\n\n// CORRECT — .values is an array; map over it\n{option.values.map((v) => (\n <span key={v.name}>{v.name}: {v.value}</span>\n))}\n```\n\nThis is the most common confusion when rendering order summary lines or cart line item options. See `get_migration_guide(\"prop-runtime-shapes\")` for the broader catalog of order-shape gotchas.\n\n### 18. IkasOrderAdjustment Has No .formattedAmount — Use the Helper\n\n`IkasOrderAdjustment` does not expose a `.formattedAmount` property directly. Call the helper `getOrderAdjustmentFormattedAmount(adjustment)` from `@ikas/bp-storefront` — it returns the formatted currency string with the correct sign (negated for decrements).\n\n```tsx\nimport { getOrderAdjustmentFormattedAmount } from \"@ikas/bp-storefront\";\n\n// WRONG — .formattedAmount does not exist\n<td>{adjustment.formattedAmount}</td>\n\n// CORRECT\n<td>{getOrderAdjustmentFormattedAmount(adjustment)}</td>\n```\n\nSee `get_function_doc(\"getOrderAdjustmentFormattedAmount\")` for the exact signature.\n\n### 19. Old-System Property Patterns That Don't Exist in the New System\n\nSeveral `@ikas/storefront` properties look like they should still work but don't exist on the corresponding `@ikas/bp-storefront` types. Use the helper function instead. **If TypeScript reports TS2339 (\"Property X does not exist on type Y\") on a `.selectedVariant`, `.mainImage`, `.href`, or `.checkoutUrl` access, you're hitting one of these.**\n\n| Old pattern | New replacement |\n|---|---|\n| `product.selectedVariant` | `getSelectedProductVariant(product)` → `IkasProductVariant \\| undefined` |\n| `product.href` | `getProductHref(product)` (one of several `get*Href` helpers — see `navigation-patterns`) |\n| `variant.mainImage` | `getProductVariantMainImage(variant)` → `IkasProductImage`, then access `.image` for the `IkasImage` |\n| `cartStore.checkoutUrl` | `getCheckoutUrlFromCartStore(cartStore)` (handles auth-state branching internally) |\n\nThe variant-to-image chain is 4 hops (each carries metadata the next doesn't). See `get_framework_guide(\"image-handling\")` for the canonical recipe.\n\n### 20. Intl.NumberFormat for Currency Amounts\n\nNever format money with `Intl.NumberFormat` — it falls back to the browser locale and ignores the merchant's currency format settings, so its output clashes with platform-formatted prices on the same screen. For model-bound prices use the `get*FormattedPrice` helpers. For free-standing amounts (remaining amount to free shipping, gift wrap threshold, bulk discount limits, cart limit warnings) use `formatCurrency(amount, currencyCode, currencySymbol)` from `@ikas/bp-storefront`.\n\nNote: `Intl.DateTimeFormat(IkasStorefrontConfig.getCurrentLocale(), ...)` IS the correct pattern for dates — do not mirror it for money.\n\n```tsx\n// WRONG — browser-locale format (\"₺14.478,5\") next to platform prices (\"₺ 1,950.00\")\nconst remaining = new Intl.NumberFormat(undefined, { style: \"currency\", currency: \"TRY\" }).format(amount);\n\n// CORRECT — merchant currency format settings\nimport { formatCurrency } from \"@ikas/bp-storefront\";\n\nconst remaining = formatCurrency(amount, cart.currencyCode, cart.currencySymbol);\n```\n\nSee `get_function_doc(\"formatCurrency\")` for the exact signature.\n\n### Deep SDK imports (`@ikas/bp-storefront/dist/...`)\n\n**Wrong** — reaching into a private subpath when a root helper seems missing:\n```tsx\n// breaks at runtime: no window global for this subpath, and the build rejects it\nimport { apiSaveCart } from \"@ikas/bp-storefront/dist/functions/api/cart\";\n```\n\n**Correct** — use the public root export (coupon has dedicated form helpers):\n```tsx\nimport {\n customerStore,\n getCouponCodeForm,\n setCouponCodeFormCouponCode,\n submitCouponCodeForm,\n} from \"@ikas/bp-storefront\";\n```\n\nRule: every import from the storefront SDK must come from the BARE package name — never a `/dist/` or `/src/` subpath.",
|
|
195
181
|
"tags": [
|
|
196
182
|
"pitfalls",
|
|
197
183
|
"gotchas",
|
|
@@ -210,11 +196,7 @@
|
|
|
210
196
|
"currency",
|
|
211
197
|
"formatCurrency",
|
|
212
198
|
"NumberFormat",
|
|
213
|
-
"price-format"
|
|
214
|
-
"MutationObserver",
|
|
215
|
-
"useLayoutEffect",
|
|
216
|
-
"infinite-loop",
|
|
217
|
-
"freeze"
|
|
199
|
+
"price-format"
|
|
218
200
|
]
|
|
219
201
|
},
|
|
220
202
|
"sub-component-patterns": {
|
|
@@ -271,14 +253,7 @@
|
|
|
271
253
|
"title": "Async Data & Loading Patterns",
|
|
272
254
|
"description": "How to handle async operations, loading states, and store data in ikas components",
|
|
273
255
|
"content": "## Async Data & Loading Patterns\n\n### 1. Store State is Null Until Initialized\n\n**Note:** Root component exports are automatically reactive via the ikas runtime's `autorun()` — no `observer()` needed. The examples below show `observer()` on **sub-components** that independently read store data.\n\nStore data (`cartStore.cart`, `customerStore.customer`) is `null` until the store initializes. Use null-safe access:\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\n// Sub-component: needs observer() for independent reactivity\nconst CartBadge = observer(function CartBadge() {\n // cart is null until initialized — use optional chaining\n const itemCount = cartStore.cart?.orderLineItems.length ?? 0;\n return <span className=\"cart-badge\">{itemCount}</span>;\n});\n```\n\n### 2. Loading Pattern for Async Operations\n\nUse `useState` for local loading flags. Always use `try/finally` to clear the loading state:\n\n```tsx\nimport { useState } from \"preact/hooks\";\nimport { addItemToCart } from \"@ikas/bp-storefront\";\n\nconst [isLoading, setIsLoading] = useState(false);\n\nconst handleAddToCart = async () => {\n if (isLoading) return; // prevent double-click\n setIsLoading(true);\n try {\n await addItemToCart(variant, product, 1);\n } finally {\n setIsLoading(false);\n }\n};\n\n// In JSX:\n<button disabled={isLoading} onClick={handleAddToCart}>\n {isLoading ? \"Adding...\" : \"Add to Cart\"}\n</button>\n```\n\n### 3. Cart Operation Results\n\n`addItemToCart` returns a result object. Check for validation errors:\n\n```tsx\nconst result = await addItemToCart(variant, product, 1);\nif (result.success) {\n // Item added successfully\n} else if (result.validationError === \"INSUFFICIENT_STOCK\") {\n // Not enough stock\n} else if (result.validationError === \"INVALID_PRODUCT_OPTION_VALUES\") {\n // Variant options not fully selected\n}\n```\n\n### 4. Form Submission Results\n\nForm submit functions return `boolean` and set status flags on the form:\n\n```tsx\nconst success = await submitLoginForm(loginForm);\nif (success) {\n // loginForm.isSuccess is true\n Router.navigate(\"/account\");\n} else {\n // loginForm.isFailure is true\n // loginForm.responseMessage has the error message\n console.log(loginForm.responseMessage);\n}\n```\n\n### 5. Observer Auto Re-rendering\n\nStore updates automatically trigger re-renders. Root components get this via `autorun()`, sub-components via `observer()`. You do NOT need manual state management for store-derived values:\n\n```tsx\n// WRONG — unnecessary useState for store data\nconst [cartItems, setCartItems] = useState([]);\nuseEffect(() => { setCartItems(cartStore.cart?.orderLineItems ?? []); }, [cartStore.cart]);\n\n// CORRECT — observer sub-component for independent reactivity\nconst CartList = observer(function CartList() {\n const items = cartStore.cart?.orderLineItems ?? [];\n return (\n <div>\n {items.map((item) => (\n <div key={item.id}>{item.variant?.name}</div>\n ))}\n </div>\n );\n});\n\n// Root export uses CartList — no observer needed on root\nexport function CartPage({ title }: Props) {\n return (\n <section>\n <h1>{title}</h1>\n <CartList />\n </section>\n );\n}\n\nexport default CartPage;\n```\n\n### 6. Pagination / Data Fetching\n\nList stores (blog, brand, category) use async pagination functions:\n\n```tsx\nimport { hasBlogListNextPage, getBlogListNextPage } from \"@ikas/bp-storefront\";\n\n// Check if more pages exist\nif (hasBlogListNextPage(blogList)) {\n // Fetch next page — mutates blogList in place, observer re-renders\n await getBlogListNextPage(blogList);\n}\n```\n\nThese functions mutate the list model in place. If your component uses `observer()`, it will re-render automatically when new data arrives.\n\nTo batch-fetch full products by id — e.g. enriching cart lines with the `salesChannels` limits or per-variant `stock` they don't carry — use `bs_searchProductsById(baseStore, { productIds })`: one request for all ids. See the \"Enforcing Limits on Cart Lines\" section in the `cart-patterns` guide for the full pattern.",
|
|
274
|
-
"tags": [
|
|
275
|
-
"async",
|
|
276
|
-
"loading",
|
|
277
|
-
"error",
|
|
278
|
-
"stores",
|
|
279
|
-
"cart",
|
|
280
|
-
"fetch"
|
|
281
|
-
]
|
|
256
|
+
"tags": ["async", "loading", "error", "stores", "cart", "fetch"]
|
|
282
257
|
},
|
|
283
258
|
"product-detail-patterns": {
|
|
284
259
|
"title": "Product Detail Patterns (Serel Reference)",
|
|
@@ -428,14 +403,7 @@
|
|
|
428
403
|
"title": "Customer Review Patterns",
|
|
429
404
|
"description": "Review display, star ratings, review submission form, and login-required checks",
|
|
430
405
|
"content": "## Customer Review Patterns\n\nProduct reviews allow customers to rate and comment on products. The pattern includes displaying existing reviews and a submission form.\n\n### Displaying Reviews\n\n```tsx\nimport {\n getProductCustomerReviews,\n getIkasCustomerReviewFormattedDate,\n} from \"@ikas/bp-storefront\";\n\n// In your component (product prop):\nconst reviews = getProductCustomerReviews(product) ?? [];\n\n{reviews.map((review) => (\n <div key={review.id} className=\"review-card\">\n <div className=\"review-stars\">\n {[1, 2, 3, 4, 5].map((star) => (\n <span key={star} className={star <= review.star ? \"filled\" : \"empty\"}>★</span>\n ))}\n </div>\n <h4>{review.title}</h4>\n <p>{review.comment}</p>\n <span className=\"review-date\">{getIkasCustomerReviewFormattedDate(review)}</span>\n <span className=\"review-author\">{review.customerName}</span>\n </div>\n))}\n```\n\n### Review Submission Form\n\n```tsx\nimport {\n getIkasProductCustomerReviewForm,\n setCustomerReviewFormTitle,\n setCustomerReviewFormStar,\n setCustomerReviewFormComment,\n submitCustomerReviewForm,\n isCustomerReviewLoginRequired,\n customerStore,\n hasCustomer,\n Router,\n} from \"@ikas/bp-storefront\";\n\nconst reviewForm = getIkasProductCustomerReviewForm(product);\nconst loginRequired = isCustomerReviewLoginRequired() as unknown as boolean;\nconst isLoggedIn = hasCustomer(customerStore) as unknown as boolean;\n\n// If login is required and user is not logged in\nif (loginRequired && !isLoggedIn) {\n return (\n <div>\n <p>Please log in to write a review.</p>\n <button onClick={() => Router.navigateToPage(\"LOGIN\")}>Log In</button>\n </div>\n );\n}\n\n// Star rating input\nconst [hoverStar, setHoverStar] = useState(0);\n\n<div className=\"star-input\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n className={star <= (hoverStar || reviewForm.star.value) ? \"filled\" : \"empty\"}\n onMouseEnter={() => setHoverStar(star)}\n onMouseLeave={() => setHoverStar(0)}\n onClick={() => setCustomerReviewFormStar(reviewForm, star)}\n >\n ★\n </button>\n ))}\n</div>\n\n// Title and comment inputs\n<input\n value={reviewForm.title.value}\n onInput={(e) => setCustomerReviewFormTitle(reviewForm, (e.target as HTMLInputElement).value)}\n/>\n<textarea\n value={reviewForm.comment.value}\n onInput={(e) => setCustomerReviewFormComment(reviewForm, (e.target as HTMLTextAreaElement).value)}\n/>\n\n// Submit\nconst handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitCustomerReviewForm(reviewForm);\n if (success) {\n // Review submitted successfully\n }\n};\n```\n\n### Key Functions\n\n| Function | Purpose |\n|----------|---------|\n| `getProductCustomerReviews(product)` | Get reviews for a product |\n| `getIkasProductCustomerReviewForm(product)` | Get the review form model |\n| `setCustomerReviewFormTitle(form, value)` | Set review title |\n| `setCustomerReviewFormStar(form, value)` | Set star rating (1-5) |\n| `setCustomerReviewFormComment(form, value)` | Set review comment |\n| `submitCustomerReviewForm(form)` | Submit the review |\n| `isCustomerReviewLoginRequired()` | Check if login is needed to review |\n| `getIkasCustomerReviewFormattedDate(review)` | Format review date |",
|
|
431
|
-
"tags": [
|
|
432
|
-
"reviews",
|
|
433
|
-
"ratings",
|
|
434
|
-
"stars",
|
|
435
|
-
"feedback",
|
|
436
|
-
"customer",
|
|
437
|
-
"form"
|
|
438
|
-
]
|
|
406
|
+
"tags": ["reviews", "ratings", "stars", "feedback", "customer", "form"]
|
|
439
407
|
},
|
|
440
408
|
"slider-overlay-patterns": {
|
|
441
409
|
"title": "Slider & Overlay Patterns (Serel Reference)",
|
|
@@ -460,14 +428,7 @@
|
|
|
460
428
|
"title": "Blog Patterns",
|
|
461
429
|
"description": "Blog list display, blog post detail, pagination, date formatting, and category filtering",
|
|
462
430
|
"content": "## Blog Patterns\n\nBlog sections display articles with pagination and optional category filtering.\n\n### Blog List Prop Setup\n\nIn `ikas.config.json`, use the `BLOG_LIST` prop type or a dedicated blog list config:\n\n```json\n{\n \"name\": \"blogList\",\n \"displayName\": \"Blog List\",\n \"type\": \"BLOG_LIST\"\n}\n```\n\n### Blog List Display\n\n```tsx\nimport {\n IkasBlogList,\n getIkasBlogFormattedDate,\n getIkasBlogHref,\n hasBlogListNextPage,\n getBlogListNextPage,\n hasBlogListPrevPage,\n getBlogListPrevPage,\n getDefaultSrc,\n} from \"@ikas/bp-storefront\";\n\n// In your component:\nconst blogs = blogList?.data ?? [];\n\n{blogs.map((blog) => (\n <a key={blog.id} href={getIkasBlogHref(blog)} className=\"blog-card\">\n {blog.image && (\n <img src={getDefaultSrc(blog.image)} alt={blog.title} />\n )}\n <div className=\"blog-card-content\">\n <span className=\"blog-date\">{getIkasBlogFormattedDate(blog)}</span>\n <h3>{blog.title}</h3>\n <p>{blog.summary}</p>\n </div>\n </a>\n))}\n```\n\n### Blog Pagination\n\n```tsx\nconst hasNext = hasBlogListNextPage(blogList);\nconst hasPrev = hasBlogListPrevPage(blogList);\n\n<div className=\"blog-pagination\">\n {hasPrev && (\n <button onClick={() => getBlogListPrevPage(blogList)}>Previous</button>\n )}\n {hasNext && (\n <button onClick={() => getBlogListNextPage(blogList)}>Next</button>\n )}\n</div>\n```\n\n### Blog Post Detail\n\nFor a blog post detail page, use the `BLOG` prop type:\n\n```json\n{\n \"name\": \"blogPost\",\n \"displayName\": \"Blog Post\",\n \"type\": \"BLOG\"\n}\n```\n\n```tsx\ninterface Props {\n blogPost: IkasBlog;\n}\n\nfunction BlogPostDetail({ blogPost }: Props) {\n if (!blogPost) return null;\n\n return (\n <article className=\"blog-post\">\n {blogPost.image && (\n <img src={getDefaultSrc(blogPost.image)} alt={blogPost.title} />\n )}\n <h1>{blogPost.title}</h1>\n <span className=\"date\">{getIkasBlogFormattedDate(blogPost)}</span>\n <div\n className=\"blog-content\"\n dangerouslySetInnerHTML={{ __html: blogPost.blogContent.content }}\n />\n </article>\n );\n}\n```\n\n### Key Functions\n\n| Function | Purpose |\n|----------|---------|\n| `getIkasBlogFormattedDate(blog)` | Format blog post date |\n| `getIkasBlogHref(blog)` | Get URL for a blog post |\n| `hasBlogListNextPage(list)` | Check if next page exists |\n| `getBlogListNextPage(list)` | Fetch next page (mutates list) |\n| `hasBlogListPrevPage(list)` | Check if previous page exists |\n| `getBlogListPrevPage(list)` | Fetch previous page (mutates list) |",
|
|
463
|
-
"tags": [
|
|
464
|
-
"blog",
|
|
465
|
-
"article",
|
|
466
|
-
"posts",
|
|
467
|
-
"pagination",
|
|
468
|
-
"date",
|
|
469
|
-
"content"
|
|
470
|
-
]
|
|
431
|
+
"tags": ["blog", "article", "posts", "pagination", "date", "content"]
|
|
471
432
|
},
|
|
472
433
|
"navigation-patterns": {
|
|
473
434
|
"title": "Navigation & Routing Patterns",
|
|
@@ -680,7 +641,7 @@
|
|
|
680
641
|
"theme-globals": {
|
|
681
642
|
"title": "Theme Global Settings & Design Tokens",
|
|
682
643
|
"description": "Read and create the theme's global variables and design tokens (colors, typography, breakpoints, keyframes, color schemes) from code components",
|
|
683
|
-
"content": "The theme developer defines global settings in the editor's \"Styles\" panel. Code components can READ these at runtime and an AI agent can CREATE/LIST them via MCP tools. Prefer reusing the theme's existing tokens (colors, typography) over hardcoding values — it keeps components consistent with the store's design.\n\n### Read at runtime (in your .tsx)\n\nImport from `@ikas/bp-storefront`. These work in SSR, client hydration, and the editor canvas:\n\n```tsx\nimport {\n getThemeSetting, getThemeSettings, // global variables (Theme Settings)\n getThemeColors, getThemeTypography, // design tokens\n getThemeBreakpoints, getThemeKeyframes, getThemeColorSchemes,\n} from \"@ikas/bp-storefront\";\n\nconst brand = getThemeSetting(\"_AbC123XyZ\")?.value; // key = variableName, from list_theme_globals\nconst colors = getThemeColors(); // [{ id, name, resolved, cssVar }]\n// resolved = concrete value (e.g. \"#ff0000\"); cssVar = \"var(--<id>)\" (scheme-aware).\n// e.g. style={{ color: colors[0].cssVar }} or className={getThemeTypography()[0].className}\n```\n\nShapes: colors → `{ id, name, resolved, cssVar }`; typography → `{ id, name, resolved, className }`; breakpoints → `{ id, name, width }`; keyframes → `{ id, name, type, ref }`; color schemes → `{ schemes, values }`. Global variables → `{ name, displayName, type, value }` where `name` is the stable runtime key (`variableName`).\n\n### Responsive breakpoints in CSS\n\n`getThemeBreakpoints()` gives you each breakpoint's `width` in JS, but a CSS media query CANNOT use `var()` in its condition (it parses as valid-looking CSS and then fails SILENTLY). So to make a `@media` query follow a theme breakpoint, write the `min-width`/`max-width` yourself and use a `bp(<breakpointId>)` token for the value — the platform rewrites it to the breakpoint's concrete `<width>px` at render time (in BOTH the editor canvas and the published storefront), against the destination store's LIVE breakpoint width:\n\n```css\n/* styles.css — <breakpointId> from list_theme_globals (theme.breakpoints[].id) */\n.grid { grid-template-columns: repeat(3, 1fr); }\n\n/* applies at <= the breakpoint's width */\n@media (max-width: bp(<breakpointId>)) { .grid { grid-template-columns: 1fr; } }\n\n/* applies at > the breakpoint's width */\n@media (min-width: bp(<breakpointId>)) { .grid { grid-template-columns: repeat(4, 1fr); } }\n```\n\n- `bp(<id>)` resolves to `<width>px`. YOU pick the direction by writing `max-width` or `min-width`.\n- Use the breakpoint **id** (stable across renames), NEVER its name. Get ids from `list_theme_globals` (or the runtime `getThemeBreakpoints()` list).\n- Works anywhere in the condition, including `calc()` — e.g. for a non-overlapping upper bound use `@media (min-width: calc(bp(<id>) + 1px))` so it doesn't collide with a `max-width: bp(<id>)` block at the exact width. Inside `calc()` use PX ARITHMETIC ONLY (`+ 1px`, `* 2`, `- bp(<other>)`): the platform folds it to a concrete px value at render time, and a calc mixing other units (rem/em/vw/%) cannot fold — the whole media block is then silently dropped in production builds.\n- Portable: the token resolves to the destination store's own width, and the breakpoint ships automatically as a dependency when you reference it in CSS. Need one that might not exist everywhere? `create_theme_global` (kind `breakpoint`) it first.\n\n### Using a color scheme\n\nColor schemes have two parts: **slots** (the named color keys, e.g. Background/Text/Primary) and **palettes** (sets of colors for those slots). `getThemeColorSchemes()` returns:\n- `schemes`: the slots — `[{ id, name }]`.\n- `values`: the palettes — `[{ id, name, isDefault, className, colorsByScheme }]`, where `colorsByScheme` maps each **slot id** → `{ resolved, cssVar }`.\n\nA scheme color's `cssVar` is `var(--<slotId>)` and is **palette-scoped**: it only resolves inside an element carrying that palette's `className`. So apply the palette to a wrapper, then reference its slots inside:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0];\n// Reference slots by their id (from list_theme_globals → colorSchemes.schemes), resolving each\n// slot NAME → id ONCE here. NEVER match a slot by .name at runtime: slot names are NOT unique\n// (installing two design assets can create two \"Background\" slots), so\n// schemes.find(x => x.name === \"Background\") may silently pick the WRONG slot. A slot id is stable\n// and travels unchanged across stores, so referencing a slot by id is both correct AND portable.\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\"; // real ids from list_theme_globals\nconst slot = (id: string) => palette?.colorsByScheme[id];\n\n// the palette className makes the slot vars resolve in this subtree\nreturn (\n <section\n className={palette?.className}\n style={{ background: slot(BACKGROUND)?.cssVar, color: slot(TEXT)?.cssVar }}\n >\n <button style={{ background: slot(PRIMARY)?.cssVar }}>Buy</button>\n </section>\n);\n```\n\nUse `colorsByScheme[slotId].resolved` if you need the concrete value instead of the live `var()`. Switching palettes at runtime is just swapping the wrapper's `className`.\n\n### Forcing a palette vs. inheriting the section's scheme\n\nSlot vars (`var(--<slotId>)`) resolve against whichever palette `className` sits on an **ancestor**, so you get two modes:\n\n- **Inherit the section's scheme (default, recommended):** do NOT add a palette `className` — just use the slot `cssVar`s. They resolve to the palette the section is set to. A section's color scheme is chosen in the editor (right panel → \"Color Scheme\"), which renders the section wrapper with that palette's class (`_<schemeId>`); leaving your slots unwrapped lets the merchant re-skin the component by switching the section's scheme, with no code change. The var NAMES are identical across palettes (only the value changes with the active ancestor class), so read the `{slotId → cssVar}` map from any palette and stay unwrapped:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0]; // any palette; var names match across all\n// slot ids from list_theme_globals → colorSchemes.schemes (resolve name → id once; NEVER match a\n// slot by .name at runtime — names are not unique, so a name lookup can pick the wrong slot).\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\";\nconst slot = (id: string) => palette?.colorsByScheme[id]?.cssVar;\n\n// No palette className here → these inherit the SECTION's active scheme.\nreturn (\n <div style={{ background: slot(BACKGROUND), color: slot(TEXT) }}>\n <button style={{ background: slot(PRIMARY) }}>Buy</button>\n </div>\n);\n```\n\n- **Force a specific palette:** wrap the subtree in that palette's `className` (the example above). Use this only for a region that must always use one fixed palette regardless of the section's scheme.\n\n### Create / list via MCP (requires `ikas theme dev` + connected editor)\n\n- `list_theme_globals` — list every global variable and design token in the project (including ones created manually in the editor). **Call this FIRST** so you reuse existing tokens instead of duplicating them.\n- `create_theme_global` — create one, selected by `kind`:\n - `globalVariable` — `display_name` + `type` (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW); `value` optional. Value shapes — TEXT/COLOR: string; RICH_TEXT: HTML string; NUMBER: number; BOOLEAN: boolean; IMAGE: `{ url }`; BORDER: `{ width: { value, unit }, style, color }`; SHADOW: `{ x, y, blur, spread, color, position: \"outside\"|\"inside\" }`.\n - `color` — `name` + `value` (hex).\n - `typography` — `name` + any of `font_family`/`font_size`/`font_weight`/`line_height`/`letter_spacing`.\n - `breakpoint` — `name` + `width`.\n - `keyframe` — `name` + `points` (`[{ point, styles? }]`); each style is `{ property, value }` with a CSS property name (opacity, transform, filter, background, color, …). Apply the keyframe's `ref` as a CSS `animation-name` and set timing (duration/iteration) where you apply it.\n - `colorScheme` — `name` + `colors` (`[{ slotId? | newSlotName?, value }]`); target an existing slot by `slotId` (its id from list_theme_globals → colorSchemes.schemes / colorsByScheme keys), or create/reuse one by `newSlotName` (use the standard slot names like \"Background\"/\"Text\"/\"PrimaryButton/Background\"). Exactly one per entry.\n\n**CLI equivalents** (if you call the ikas-component CLI directly instead of the MCP tools): read with `list-theme-globals`; create with — globalVariable→`create-global-variable`, color→`create-color`, typography→`create-text-style`, breakpoint→`create-breakpoint`, keyframe→`create-keyframe`, colorScheme→`create-color-scheme`. CLI flags are kebab-case (`--display-name`, `--font-size`, `--colors`, …) and the CLI must be run from the project root.\n\n**Update / delete:** `update_theme_global` (fix a global variable's value/type — identify by `name`) and `delete_theme_global` (`kind` globalVariable→`name`, or a design-token kind→`id`). CLI equivalents: `update-global-variable`, `delete-global-variable`, `delete-design-token`.\n\nAfter creating, the new item is readable via the runtime getters above (its key/id comes back in the create result and from `list_theme_globals`).\n\n### Live updates vs snapshots (important)\n\nTheme settings reach your component through two channels — pick the right one or edits won't reflect live:\n\n- **Live (CSS):** use `cssVar` (colors, and color-scheme colors) and `className` (typography text styles). These update **instantly** when the value is edited in the editor, because they map to CSS the editor regenerates live. Prefer these for anything visual.\n - Colors: `style={{ color: token.cssVar }}` (or `background`).\n - Typography: apply the text style's class — `className={t.className}` — do NOT spread `t.resolved` into an inline `style` if you want live updates.\n - Color-scheme colors are scoped to the palette: a `var(--<key>)` only resolves inside an element carrying that palette's `className`, so wrap the row: `<div className={paletteValue.className}>…<span style={{background: colorsByScheme[keyId].cssVar}}/>…</div>`.\n- **Snapshot (JS):** `resolved` values, breakpoint `width`, keyframe metadata, and global-variable `value`s are read from JS at render time. They reflect editor edits only when the component **re-renders/remounts** (e.g. its props change, or another action refreshes the canvas) — NOT instantly on a theme-token edit. This is expected, not a bug. Global-variable value edits do trigger a canvas refresh, so they reflect; design-token `resolved` values lag until the next refresh.\n\nRule of thumb: for visuals that must track editor edits live, reference `cssVar` / apply `className`; treat `resolved` and `value` as point-in-time reads.\n\n### Portability (shipping as a partner design asset)\n\nWhen this component is published as a partner design asset and installed into another store, only the globals it DECLARES as dependencies travel with it. Write components so their global usage stays portable:\n\n- **Expose theme global variables as props — don't hardcode keys.** A prop bound to a global variable (via its `privateVarMap`) is detected and ships with the asset. A `themeValue(\"_xyz\")` / `getThemeSetting(\"_xyz\")` call with a hardcoded key in your source is NOT detected, so that variable won't travel and resolves to nothing in the destination store. If you need a theme setting, add it as a prop and read the prop, not a hardcoded key.\n- **Iterate the runtime lists instead of hardcoding token ids.** `getThemeColors()`, `getThemeTypography()`, `getThemeKeyframes()`, `getThemeBreakpoints()`, and `getThemeColorSchemes()` return the DESTINATION store's own tokens — iterate them so the component adapts to whatever theme it lands in. A specific color/keyframe id hardcoded from the list won't exist in another store. (Color-SCHEME SLOT ids are the exception: a scheme travels in full with its slot ids intact and is matched by id on install — never re-keyed — so referencing a slot by `var(--<slotId>)` / `colorsByScheme[slotId]` stays valid across stores; resolving it by `.name` does not.)\n- **Travels automatically:** color schemes (in full — the slot/palette pattern above keeps working after install), types referenced by your props, nested code components, and shared modules.\n\nRule of thumb: read named settings through props and read colors/typography/etc. by iterating the runtime lists — both are portable; a hardcoded global id in source is not.",
|
|
644
|
+
"content": "The theme developer defines global settings in the editor's \"Styles\" panel. Code components can READ these at runtime and an AI agent can CREATE/LIST them via MCP tools. Prefer reusing the theme's existing tokens (colors, typography) over hardcoding values — it keeps components consistent with the store's design.\n\n### Read at runtime (in your .tsx)\n\nImport from `@ikas/bp-storefront`. These work in SSR, client hydration, and the editor canvas:\n\n```tsx\nimport {\n getThemeSetting, getThemeSettings, // global variables (Theme Settings)\n getThemeColors, getThemeTypography, // design tokens\n getThemeBreakpoints, getThemeKeyframes, getThemeColorSchemes,\n} from \"@ikas/bp-storefront\";\n\nconst brand = getThemeSetting(\"_AbC123XyZ\")?.value; // key = variableName, from list_theme_globals\nconst colors = getThemeColors(); // [{ id, name, resolved, cssVar }]\n// resolved = concrete value (e.g. \"#ff0000\"); cssVar = \"var(--<id>)\" (scheme-aware).\n// e.g. style={{ color: colors[0].cssVar }} or className={getThemeTypography()[0].className}\n```\n\nShapes: colors → `{ id, name, resolved, cssVar }`; typography → `{ id, name, resolved, className }`; breakpoints → `{ id, name, width }`; keyframes → `{ id, name, type, ref }`; color schemes → `{ schemes, values }`. Global variables → `{ name, displayName, type, value }` where `name` is the stable runtime key (`variableName`).\n\n### Responsive breakpoints in CSS\n\n`getThemeBreakpoints()` gives you each breakpoint's `width` in JS, but a CSS media query CANNOT use `var()` in its condition (it parses as valid-looking CSS and then fails SILENTLY). So to make a `@media` query follow a theme breakpoint, write the `min-width`/`max-width` yourself and use a `bp(<breakpointId>)` token for the value — the platform rewrites it to the breakpoint's concrete `<width>px` at render time (in BOTH the editor canvas and the published storefront), against the destination store's LIVE breakpoint width:\n\n```css\n/* styles.css — <breakpointId> from list_theme_globals (theme.breakpoints[].id) */\n.grid { grid-template-columns: repeat(3, 1fr); }\n\n/* applies at <= the breakpoint's width */\n@media (max-width: bp(<breakpointId>)) { .grid { grid-template-columns: 1fr; } }\n\n/* applies at > the breakpoint's width */\n@media (min-width: bp(<breakpointId>)) { .grid { grid-template-columns: repeat(4, 1fr); } }\n```\n\n- `bp(<id>)` resolves to `<width>px`. YOU pick the direction by writing `max-width` or `min-width`.\n- Use the breakpoint **id** (stable across renames), NEVER its name. Get ids from `list_theme_globals` (or the runtime `getThemeBreakpoints()` list).\n- Works anywhere in the condition, including `calc()` — e.g. for a non-overlapping upper bound use `@media (min-width: calc(bp(<id>) + 1px))` so it doesn't collide with a `max-width: bp(<id>)` block at the exact width. Inside `calc()` use PX ARITHMETIC ONLY (`+ 1px`, `* 2`, `- bp(<other>)`): the platform folds it to a concrete px value at render time, and a calc mixing other units (rem/em/vw/%) cannot fold — the whole media block is then silently dropped in production builds.\n- Portable: the token resolves to the destination store's own width, and the breakpoint ships automatically as a dependency when you reference it in CSS. Need one that might not exist everywhere? `create_theme_global` (kind `breakpoint`) it first.\n\n### Using a color scheme\n\nColor schemes have two parts: **slots** (the named color keys, e.g. Background/Text/Primary) and **palettes** (sets of colors for those slots). `getThemeColorSchemes()` returns:\n- `schemes`: the slots — `[{ id, name }]`.\n- `values`: the palettes — `[{ id, name, isDefault, className, colorsByScheme }]`, where `colorsByScheme` maps each **slot id** → `{ resolved, cssVar }`.\n\nA scheme color's `cssVar` is `var(--<slotId>)` and is **palette-scoped**: it only resolves inside an element carrying that palette's `className`. So apply the palette to a wrapper, then reference its slots inside:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0];\n// Reference slots by their id (from list_theme_globals → colorSchemes.schemes), resolving each\n// slot NAME → id ONCE here. NEVER match a slot by .name at runtime: slot names are NOT unique\n// (installing two design assets can create two \"Background\" slots), so\n// schemes.find(x => x.name === \"Background\") may silently pick the WRONG slot. A slot id is stable\n// and travels unchanged across stores, so referencing a slot by id is both correct AND portable.\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\"; // real ids from list_theme_globals\nconst slot = (id: string) => palette?.colorsByScheme[id];\n\n// the palette className makes the slot vars resolve in this subtree\nreturn (\n <section\n className={palette?.className}\n style={{ background: slot(BACKGROUND)?.cssVar, color: slot(TEXT)?.cssVar }}\n >\n <button style={{ background: slot(PRIMARY)?.cssVar }}>Buy</button>\n </section>\n);\n```\n\nUse `colorsByScheme[slotId].resolved` if you need the concrete value instead of the live `var()`. Switching palettes at runtime is just swapping the wrapper's `className`.\n\n### Forcing a palette vs. inheriting the section's scheme\n\nSlot vars (`var(--<slotId>)`) resolve against whichever palette `className` sits on an **ancestor**, so you get two modes:\n\n- **Inherit the section's scheme (default, recommended):** do NOT add a palette `className` — just use the slot `cssVar`s. They resolve to the palette the section is set to. A section's color scheme is chosen in the editor (right panel → \"Color Scheme\"), which renders the section wrapper with that palette's class (`_<schemeId>`); leaving your slots unwrapped lets the merchant re-skin the component by switching the section's scheme, with no code change. The var NAMES are identical across palettes (only the value changes with the active ancestor class), so read the `{slotId → cssVar}` map from any palette and stay unwrapped:\n\n```tsx\nconst { values } = getThemeColorSchemes();\nconst palette = values.find((v) => v.isDefault) ?? values[0]; // any palette; var names match across all\n// slot ids from list_theme_globals → colorSchemes.schemes (resolve name → id once; NEVER match a\n// slot by .name at runtime — names are not unique, so a name lookup can pick the wrong slot).\nconst BACKGROUND = \"aB3xY7kLmN\", TEXT = \"pQ4rS2tUvW\", PRIMARY = \"zX8cV1bN0m\";\nconst slot = (id: string) => palette?.colorsByScheme[id]?.cssVar;\n\n// No palette className here → these inherit the SECTION's active scheme.\nreturn (\n <div style={{ background: slot(BACKGROUND), color: slot(TEXT) }}>\n <button style={{ background: slot(PRIMARY) }}>Buy</button>\n </div>\n);\n```\n\n- **Force a specific palette:** wrap the subtree in that palette's `className` (the example above). Use this only for a region that must always use one fixed palette regardless of the section's scheme.\n\n### Create / list via MCP (requires `ikas theme dev` + connected editor)\n\n- `list_theme_globals` — list every global variable and design token in the project (including ones created manually in the editor). **Call this FIRST** so you reuse existing tokens instead of duplicating them.\n- `create_theme_global` — create one, selected by `kind`:\n - `globalVariable` — `display_name` + `type` (TEXT|RICH_TEXT|IMAGE|COLOR|NUMBER|BOOLEAN|BORDER|SHADOW|ENUM); `value` optional. Value shapes — TEXT/COLOR: string; RICH_TEXT: HTML string; NUMBER: number; BOOLEAN: boolean; IMAGE: `{ url }`; BORDER: `{ width: { value, unit }, style, color }`; SHADOW: `{ x, y, blur, spread, color, position: \"outside\"|\"inside\" }`; ENUM: `enum_type_id` (a project enum id from `list_theme_globals` → `enumTypes[]`) + `value` = one of that enum's option VALUES (not a label). An ENUM setting reads back as `{ type: \"ENUM\", value, enumTypeId, options }`, an ENUM prop of the same enum can be bound to it with `bind_global`, and `themeValue(\"_<id>\")` from `./global-types` narrows to the enum's union type.\n - `color` — `name` + `value` (hex).\n - `typography` — `name` + any of `font_family`/`font_size`/`font_weight`/`line_height`/`letter_spacing`.\n - `breakpoint` — `name` + `width`.\n - `keyframe` — `name` + `points` (`[{ point, styles? }]`); each style is `{ property, value }` with a CSS property name (opacity, transform, filter, background, color, …). Apply the keyframe's `ref` as a CSS `animation-name` and set timing (duration/iteration) where you apply it.\n - `colorScheme` — `name` + `colors` (`[{ slotId? | newSlotName?, value }]`); target an existing slot by `slotId` (its id from list_theme_globals → colorSchemes.schemes / colorsByScheme keys), or create/reuse one by `newSlotName` (use the standard slot names like \"Background\"/\"Text\"/\"PrimaryButton/Background\"). Exactly one per entry.\n\n**CLI equivalents** (if you call the ikas-component CLI directly instead of the MCP tools): read with `list-theme-globals`; create with — globalVariable→`create-global-variable`, color→`create-color`, typography→`create-text-style`, breakpoint→`create-breakpoint`, keyframe→`create-keyframe`, colorScheme→`create-color-scheme`. CLI flags are kebab-case (`--display-name`, `--font-size`, `--colors`, …) and the CLI must be run from the project root.\n\n**Update / delete:** `update_theme_global` (fix a global variable's value/type — identify by `name`) and `delete_theme_global` (`kind` globalVariable→`name`, or a design-token kind→`id`). CLI equivalents: `update-global-variable`, `delete-global-variable`, `delete-design-token`.\n\nAfter creating, the new item is readable via the runtime getters above (its key/id comes back in the create result and from `list_theme_globals`).\n\n### Live updates vs snapshots (important)\n\nTheme settings reach your component through two channels — pick the right one or edits won't reflect live:\n\n- **Live (CSS):** use `cssVar` (colors, and color-scheme colors) and `className` (typography text styles). These update **instantly** when the value is edited in the editor, because they map to CSS the editor regenerates live. Prefer these for anything visual.\n - Colors: `style={{ color: token.cssVar }}` (or `background`).\n - Typography: apply the text style's class — `className={t.className}` — do NOT spread `t.resolved` into an inline `style` if you want live updates.\n - Color-scheme colors are scoped to the palette: a `var(--<key>)` only resolves inside an element carrying that palette's `className`, so wrap the row: `<div className={paletteValue.className}>…<span style={{background: colorsByScheme[keyId].cssVar}}/>…</div>`.\n- **Snapshot (JS):** `resolved` values, breakpoint `width`, keyframe metadata, and global-variable `value`s are read from JS at render time. They reflect editor edits only when the component **re-renders/remounts** (e.g. its props change, or another action refreshes the canvas) — NOT instantly on a theme-token edit. This is expected, not a bug. Global-variable value edits do trigger a canvas refresh, so they reflect; design-token `resolved` values lag until the next refresh.\n\nRule of thumb: for visuals that must track editor edits live, reference `cssVar` / apply `className`; treat `resolved` and `value` as point-in-time reads.\n\n### Portability (shipping as a partner design asset)\n\nWhen this component is published as a partner design asset and installed into another store, only the globals it DECLARES as dependencies travel with it. Write components so their global usage stays portable:\n\n- **Expose theme global variables as props — don't hardcode keys.** A prop bound to a global variable (via its `privateVarMap`) is detected and ships with the asset. A `themeValue(\"_xyz\")` / `getThemeSetting(\"_xyz\")` call with a hardcoded key in your source is NOT detected, so that variable won't travel and resolves to nothing in the destination store. If you need a theme setting, add it as a prop and read the prop, not a hardcoded key.\n- **Iterate the runtime lists instead of hardcoding token ids.** `getThemeColors()`, `getThemeTypography()`, `getThemeKeyframes()`, `getThemeBreakpoints()`, and `getThemeColorSchemes()` return the DESTINATION store's own tokens — iterate them so the component adapts to whatever theme it lands in. A specific color/keyframe id hardcoded from the list won't exist in another store. (Color-SCHEME SLOT ids are the exception: a scheme travels in full with its slot ids intact and is matched by id on install — never re-keyed — so referencing a slot by `var(--<slotId>)` / `colorsByScheme[slotId]` stays valid across stores; resolving it by `.name` does not.)\n- **Travels automatically:** color schemes (in full — the slot/palette pattern above keeps working after install), types referenced by your props, nested code components, and shared modules.\n\nRule of thumb: read named settings through props and read colors/typography/etc. by iterating the runtime lists — both are portable; a hardcoded global id in source is not.",
|
|
684
645
|
"tags": [
|
|
685
646
|
"theme",
|
|
686
647
|
"global variables",
|
package/data/storefront-api.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
2
|
+
"generatedAt": "2026-09-24T12:06:26.149Z",
|
|
3
3
|
"functions": [
|
|
4
4
|
{
|
|
5
5
|
"name": "apiListProductBrand",
|
|
@@ -13507,6 +13507,171 @@
|
|
|
13507
13507
|
"IkasCustomerStore"
|
|
13508
13508
|
]
|
|
13509
13509
|
},
|
|
13510
|
+
{
|
|
13511
|
+
"name": "milesSmilesBind",
|
|
13512
|
+
"signature": "async function milesSmilesBind(customerStore: IkasCustomerStore, redirect?: string | null): Promise<MilesSmilesBindResult>",
|
|
13513
|
+
"description": "Start the Turkish Airlines Miles&Smiles account bind flow for the logged-in customer.\nOpens the THY login page in a popup; once THY confirms, the browser is sent to the\nikas bind callback and lands back on `redirect` with `milesSmilesStatus` query params\n(see handleMilesSmilesBind). Binding is required before miles can be spent at checkout.",
|
|
13514
|
+
"params": [
|
|
13515
|
+
{
|
|
13516
|
+
"name": "customerStore",
|
|
13517
|
+
"description": "- The customer store instance (customer must be logged in)"
|
|
13518
|
+
},
|
|
13519
|
+
{
|
|
13520
|
+
"name": "redirect",
|
|
13521
|
+
"description": "- Relative store path to land on after bind, e.g. \"/account\" or \"/checkout?id=...&step=payment\""
|
|
13522
|
+
}
|
|
13523
|
+
],
|
|
13524
|
+
"returns": "`isSuccess: true` when the bind hand-off started; otherwise an `errorCode`\nsuch as \"AUTHORIZATION_REQUIRED\" or \"MILES_SMILES_LOGIN_CANCELLED_OR_FAILED\"",
|
|
13525
|
+
"returnType": "Promise<MilesSmilesBindResult>",
|
|
13526
|
+
"example": "```typescript\nimport { customerStore, milesSmilesBind } from \"@ikas/bp-storefront\";\n\nasync function onLinkClick() {\n const result = await milesSmilesBind(customerStore, \"/account\");\n if (!result.isSuccess) showToast(result.errorCode || \"UNEXPECTED_ERROR\");\n}\n```",
|
|
13527
|
+
"categories": [
|
|
13528
|
+
"Customer",
|
|
13529
|
+
"Miles&Smiles"
|
|
13530
|
+
],
|
|
13531
|
+
"related": [
|
|
13532
|
+
"handleMilesSmilesBind",
|
|
13533
|
+
"getMilesSmilesLink",
|
|
13534
|
+
"unlinkMilesSmiles",
|
|
13535
|
+
"getMilesSmilesDetails"
|
|
13536
|
+
],
|
|
13537
|
+
"isAsync": true,
|
|
13538
|
+
"isClass": false,
|
|
13539
|
+
"parameterTypes": [
|
|
13540
|
+
"IkasCustomerStore"
|
|
13541
|
+
]
|
|
13542
|
+
},
|
|
13543
|
+
{
|
|
13544
|
+
"name": "handleMilesSmilesBind",
|
|
13545
|
+
"signature": "function handleMilesSmilesBind(clear: boolean = true): HandleMilesSmilesBindReturnType | null",
|
|
13546
|
+
"description": "Read the Miles&Smiles bind result from the current URL after THY redirects back\n(`?milesSmilesStatus=success` or `?milesSmilesStatus=fail&milesSmilesError=CODE`).\nCall this on the landing page (account or checkout). The params are removed from the\nURL so a refresh does not repeat the same message.",
|
|
13547
|
+
"params": [
|
|
13548
|
+
{
|
|
13549
|
+
"name": "clear",
|
|
13550
|
+
"description": "- Remove the params from the URL with history.replaceState (default: true)"
|
|
13551
|
+
}
|
|
13552
|
+
],
|
|
13553
|
+
"returns": "The landing result, or null when the URL carries no bind result",
|
|
13554
|
+
"returnType": "HandleMilesSmilesBindReturnType | null",
|
|
13555
|
+
"example": "```typescript\nimport { handleMilesSmilesBind } from \"@ikas/bp-storefront\";\n\nuseEffect(() => {\n const result = handleMilesSmilesBind();\n if (result?.status === \"success\") showToast(\"Miles&Smiles account linked\");\n if (result?.status === \"fail\") showToast(result.errorCode || \"Bind failed\");\n}, []);\n```",
|
|
13556
|
+
"categories": [
|
|
13557
|
+
"Customer",
|
|
13558
|
+
"Miles&Smiles"
|
|
13559
|
+
],
|
|
13560
|
+
"related": [
|
|
13561
|
+
"milesSmilesBind",
|
|
13562
|
+
"getMilesSmilesLink"
|
|
13563
|
+
],
|
|
13564
|
+
"isAsync": false,
|
|
13565
|
+
"isClass": false
|
|
13566
|
+
},
|
|
13567
|
+
{
|
|
13568
|
+
"name": "getMilesSmilesLink",
|
|
13569
|
+
"signature": "async function getMilesSmilesLink(customerStore: IkasCustomerStore): Promise<APIResponse<ExternalAccountLink | null>>",
|
|
13570
|
+
"description": "Get the customer's durable Miles&Smiles link (TK membership number and link date).\nA link does not guarantee a live checkout session; when getMilesSmilesDetails returns\nMILES_SMILES_LOGIN_REQUIRED, run milesSmilesBind again.",
|
|
13571
|
+
"params": [
|
|
13572
|
+
{
|
|
13573
|
+
"name": "customerStore",
|
|
13574
|
+
"description": "- The customer store instance"
|
|
13575
|
+
}
|
|
13576
|
+
],
|
|
13577
|
+
"returns": "API response whose data is the link, or null when not linked",
|
|
13578
|
+
"returnType": "Promise<APIResponse<ExternalAccountLink | null>>",
|
|
13579
|
+
"example": "```typescript\nimport { customerStore, getMilesSmilesLink } from \"@ikas/bp-storefront\";\n\nconst response = await getMilesSmilesLink(customerStore);\nconst tkNumber = response.data?.externalId;\n```",
|
|
13580
|
+
"categories": [
|
|
13581
|
+
"Customer",
|
|
13582
|
+
"Miles&Smiles"
|
|
13583
|
+
],
|
|
13584
|
+
"related": [
|
|
13585
|
+
"milesSmilesBind",
|
|
13586
|
+
"unlinkMilesSmiles"
|
|
13587
|
+
],
|
|
13588
|
+
"isAsync": true,
|
|
13589
|
+
"isClass": false,
|
|
13590
|
+
"parameterTypes": [
|
|
13591
|
+
"IkasCustomerStore"
|
|
13592
|
+
]
|
|
13593
|
+
},
|
|
13594
|
+
{
|
|
13595
|
+
"name": "unlinkMilesSmiles",
|
|
13596
|
+
"signature": "async function unlinkMilesSmiles(customerStore: IkasCustomerStore): Promise<APIResponse<boolean>>",
|
|
13597
|
+
"description": "Remove the customer's Miles&Smiles link. Also clears the server-side checkout\nsession token, so miles cannot be spent until the customer binds again.",
|
|
13598
|
+
"params": [
|
|
13599
|
+
{
|
|
13600
|
+
"name": "customerStore",
|
|
13601
|
+
"description": "- The customer store instance"
|
|
13602
|
+
}
|
|
13603
|
+
],
|
|
13604
|
+
"returns": "API response whose data is true on success",
|
|
13605
|
+
"returnType": "Promise<APIResponse<boolean>>",
|
|
13606
|
+
"example": "```typescript\nimport { customerStore, unlinkMilesSmiles } from \"@ikas/bp-storefront\";\n\nconst response = await unlinkMilesSmiles(customerStore);\nif (response.isSuccess) showToast(\"Miles&Smiles link removed\");\n```",
|
|
13607
|
+
"categories": [
|
|
13608
|
+
"Customer",
|
|
13609
|
+
"Miles&Smiles"
|
|
13610
|
+
],
|
|
13611
|
+
"related": [
|
|
13612
|
+
"milesSmilesBind",
|
|
13613
|
+
"getMilesSmilesLink"
|
|
13614
|
+
],
|
|
13615
|
+
"isAsync": true,
|
|
13616
|
+
"isClass": false,
|
|
13617
|
+
"parameterTypes": [
|
|
13618
|
+
"IkasCustomerStore"
|
|
13619
|
+
]
|
|
13620
|
+
},
|
|
13621
|
+
{
|
|
13622
|
+
"name": "getMilesSmilesDetails",
|
|
13623
|
+
"signature": "async function getMilesSmilesDetails(customerStore: IkasCustomerStore): Promise<APIResponse<MilesSmilesDetailsResponse | undefined>>",
|
|
13624
|
+
"description": "Get the Miles&Smiles balance and member details for the payment step.\nRequires a recent bind; MILES_SMILES_LOGIN_REQUIRED means the short-lived session\nexpired and milesSmilesBind should be run again. MILES_SMILES_NOT_ENABLED and\nPAYMENT_GATEWAY_NOT_FOUND mean the Miles UI should be hidden.",
|
|
13625
|
+
"params": [
|
|
13626
|
+
{
|
|
13627
|
+
"name": "customerStore",
|
|
13628
|
+
"description": "- The customer store instance"
|
|
13629
|
+
}
|
|
13630
|
+
],
|
|
13631
|
+
"returns": "API response whose data holds availableMiles, memberId, firstName and lastName",
|
|
13632
|
+
"returnType": "Promise<APIResponse<MilesSmilesDetailsResponse | undefined>>",
|
|
13633
|
+
"example": "```typescript\nimport { customerStore, getMilesSmilesDetails } from \"@ikas/bp-storefront\";\n\nconst response = await getMilesSmilesDetails(customerStore);\nif (response.errorCodes.includes(\"MILES_SMILES_LOGIN_REQUIRED\")) {\n await milesSmilesBind(customerStore, window.location.pathname + window.location.search);\n} else if (response.data) {\n console.log(\"Available miles:\", response.data.availableMiles);\n}\n```",
|
|
13634
|
+
"categories": [
|
|
13635
|
+
"Customer",
|
|
13636
|
+
"Miles&Smiles"
|
|
13637
|
+
],
|
|
13638
|
+
"related": [
|
|
13639
|
+
"milesSmilesBind",
|
|
13640
|
+
"getMilesSmilesLink"
|
|
13641
|
+
],
|
|
13642
|
+
"isAsync": true,
|
|
13643
|
+
"isClass": false,
|
|
13644
|
+
"parameterTypes": [
|
|
13645
|
+
"IkasCustomerStore"
|
|
13646
|
+
]
|
|
13647
|
+
},
|
|
13648
|
+
{
|
|
13649
|
+
"name": "isMilesSmilesTransaction",
|
|
13650
|
+
"signature": "function isMilesSmilesTransaction(transaction: IkasOrderTransaction): boolean",
|
|
13651
|
+
"description": "Check whether an order transaction is the Miles&Smiles leg of a multi-tender payment.\nSuch rows are written on the synthetic MILES_SMILES gateway and are not refundable from the storefront.",
|
|
13652
|
+
"params": [
|
|
13653
|
+
{
|
|
13654
|
+
"name": "transaction",
|
|
13655
|
+
"description": "- The order transaction to check"
|
|
13656
|
+
}
|
|
13657
|
+
],
|
|
13658
|
+
"returns": "True when the transaction is a Miles&Smiles SALE",
|
|
13659
|
+
"returnType": "boolean",
|
|
13660
|
+
"example": "```typescript\nimport { isMilesSmilesTransaction } from \"@ikas/bp-storefront\";\n\nconst label = isMilesSmilesTransaction(tx) ? \"Miles&Smiles\" : tx.paymentGatewayName;\n```",
|
|
13661
|
+
"categories": [
|
|
13662
|
+
"Customer",
|
|
13663
|
+
"Orders",
|
|
13664
|
+
"Miles&Smiles"
|
|
13665
|
+
],
|
|
13666
|
+
"related": [
|
|
13667
|
+
"getOrderTransactions"
|
|
13668
|
+
],
|
|
13669
|
+
"isAsync": false,
|
|
13670
|
+
"isClass": false,
|
|
13671
|
+
"parameterTypes": [
|
|
13672
|
+
"IkasOrderTransaction"
|
|
13673
|
+
]
|
|
13674
|
+
},
|
|
13510
13675
|
{
|
|
13511
13676
|
"name": "deleteCustomerAddress",
|
|
13512
13677
|
"signature": "async function deleteCustomerAddress(customerStore: IkasCustomerStore, address: IkasCustomerAddress): Promise<boolean>",
|
|
@@ -15425,7 +15590,7 @@
|
|
|
15425
15590
|
{
|
|
15426
15591
|
"name": "getThemeSetting",
|
|
15427
15592
|
"signature": "function getThemeSetting(name: string): ThemeSetting | undefined",
|
|
15428
|
-
"description": "A single global variable by its stable key (`variableName`, e.g. `_6Q0KV7VGGM`).\nDiscover keys via (each item carries a human `displayName`).\nThe returned `value` is the live blueprint value when available, else the default.\nIts shape is discriminated by `type` (see ); note an\nIMAGE setting's value is an image REFERENCE (no `.url`) — resolve with\n`getDefaultSrc`/`getSrc`.",
|
|
15593
|
+
"description": "A single global variable by its stable key (`variableName`, e.g. `_6Q0KV7VGGM`).\nDiscover keys via (each item carries a human `displayName`).\nThe returned `value` is the live blueprint value when available, else the default.\nIts shape is discriminated by `type` (see ); note an\nIMAGE setting's value is an image REFERENCE (no `.url`) — resolve with\n`getDefaultSrc`/`getSrc`. An ENUM setting's `value` is one of its enum's option VALUES\n(or null) and the setting also carries `enumTypeId` and `options` (`{ label: value }`);\nin a code component prefer the typed `themeValue(\"_key\")` from `./global-types`, which\nnarrows the value to the enum's union.",
|
|
15429
15594
|
"params": [],
|
|
15430
15595
|
"returnType": "ThemeSetting | undefined",
|
|
15431
15596
|
"categories": [],
|
|
@@ -15612,17 +15777,6 @@
|
|
|
15612
15777
|
"isAsync": false,
|
|
15613
15778
|
"isClass": false
|
|
15614
15779
|
},
|
|
15615
|
-
{
|
|
15616
|
-
"name": "createLoopGuard",
|
|
15617
|
-
"signature": "function createLoopGuard(win: GuardWindow, onTrip: (trip: LoopGuardTrip) => void): { pair: (ownerName?: string | undefined, ownerId?: string | undefined) => LoopGuardPair; renders: (options: PreactOptions) => void; }",
|
|
15618
|
-
"description": "",
|
|
15619
|
-
"params": [],
|
|
15620
|
-
"returnType": "{ pair: (ownerName?: string | undefined, ownerId?: string | undefined) => LoopGuardPair; renders: (options: PreactOptions) => void; }",
|
|
15621
|
-
"categories": [],
|
|
15622
|
-
"related": [],
|
|
15623
|
-
"isAsync": false,
|
|
15624
|
-
"isClass": false
|
|
15625
|
-
},
|
|
15626
15780
|
{
|
|
15627
15781
|
"name": "PayWithIkas",
|
|
15628
15782
|
"signature": "function PayWithIkas({ product, quantity = 1, className, style }: PayWithIkasProps): VNode<ClassAttributes<HTMLIFrameElement> & { ref: MutableRef<HTMLIFrameElement | null>; title: string; ... 4 more ...; style: { ...; }; }> | null",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
2
|
+
"generatedAt": "2026-09-24T12:06:26.180Z",
|
|
3
3
|
"types": [
|
|
4
4
|
{
|
|
5
5
|
"name": "TextElementTagTypes",
|
|
@@ -8078,6 +8078,7 @@
|
|
|
8078
8078
|
"relatedFunctions": [
|
|
8079
8079
|
"getOrderTransactionFormattedAmount",
|
|
8080
8080
|
"getOrderTransactionPaymentMethodTranslation",
|
|
8081
|
+
"isMilesSmilesTransaction",
|
|
8081
8082
|
"getOrderTransactions"
|
|
8082
8083
|
]
|
|
8083
8084
|
},
|
|
@@ -8319,6 +8320,12 @@
|
|
|
8319
8320
|
"type": "string | null",
|
|
8320
8321
|
"optional": false
|
|
8321
8322
|
},
|
|
8323
|
+
{
|
|
8324
|
+
"name": "milesSmilesEnabled",
|
|
8325
|
+
"type": "boolean | null",
|
|
8326
|
+
"optional": true,
|
|
8327
|
+
"description": "True when Miles&Smiles can be redeemed on this (TK_PAY) gateway."
|
|
8328
|
+
},
|
|
8322
8329
|
{
|
|
8323
8330
|
"name": "name",
|
|
8324
8331
|
"type": "string",
|
|
@@ -12774,6 +12781,11 @@
|
|
|
12774
12781
|
"type": "string | null",
|
|
12775
12782
|
"optional": false
|
|
12776
12783
|
},
|
|
12784
|
+
{
|
|
12785
|
+
"name": "openAiPixelId",
|
|
12786
|
+
"type": "string | null",
|
|
12787
|
+
"optional": true
|
|
12788
|
+
},
|
|
12777
12789
|
{
|
|
12778
12790
|
"name": "token",
|
|
12779
12791
|
"type": "string | null",
|
|
@@ -14590,7 +14602,8 @@
|
|
|
14590
14602
|
"NUMBER",
|
|
14591
14603
|
"BOOLEAN",
|
|
14592
14604
|
"BORDER",
|
|
14593
|
-
"SHADOW"
|
|
14605
|
+
"SHADOW",
|
|
14606
|
+
"ENUM"
|
|
14594
14607
|
],
|
|
14595
14608
|
"filePath": "theme-globals.ts"
|
|
14596
14609
|
},
|
|
@@ -14620,7 +14633,19 @@
|
|
|
14620
14633
|
"name": "value",
|
|
14621
14634
|
"type": "any",
|
|
14622
14635
|
"optional": false,
|
|
14623
|
-
"description": "Concrete value, discriminated by `type`:\n- TEXT/RICH_TEXT → string · NUMBER → number · BOOLEAN → boolean · COLOR → hex string.\n- IMAGE → an IkasImage REFERENCE, e.g. `{ id: \"theme-images/<uuid>\" }`. It has NO\n `.url` field — resolve to a URL with `getDefaultSrc(value)` / `getSrc(value, size)`\n from `@ikas/bp-storefront`, never read `value.url`.\n- BORDER/SHADOW → object, or `null` when unset."
|
|
14636
|
+
"description": "Concrete value, discriminated by `type`:\n- TEXT/RICH_TEXT → string · NUMBER → number · BOOLEAN → boolean · COLOR → hex string.\n- IMAGE → an IkasImage REFERENCE, e.g. `{ id: \"theme-images/<uuid>\" }`. It has NO\n `.url` field — resolve to a URL with `getDefaultSrc(value)` / `getSrc(value, size)`\n from `@ikas/bp-storefront`, never read `value.url`.\n- BORDER/SHADOW → object, or `null` when unset.\n- ENUM → the selected option VALUE string (one of `Object.values(options)`, never a label),\n or `null` when unset or when that option no longer exists on the enum."
|
|
14637
|
+
},
|
|
14638
|
+
{
|
|
14639
|
+
"name": "enumTypeId",
|
|
14640
|
+
"type": "string",
|
|
14641
|
+
"optional": true,
|
|
14642
|
+
"description": "ENUM only — id of the project enum type (`project.types[].id`) this setting selects from. It is\nthe same id an ENUM component prop carries as `enumTypeId`; matching ids are what make a prop\nbindable to the setting."
|
|
14643
|
+
},
|
|
14644
|
+
{
|
|
14645
|
+
"name": "options",
|
|
14646
|
+
"type": "Record<string, string>",
|
|
14647
|
+
"optional": true,
|
|
14648
|
+
"description": "ENUM only — the enum's options at serialize time, `{ [label]: value }`. `value` above is always\none of these VALUES or `null`. Empty when the enum type could not be found in the project."
|
|
14624
14649
|
}
|
|
14625
14650
|
],
|
|
14626
14651
|
"filePath": "theme-globals.ts"
|
|
@@ -15161,6 +15186,12 @@
|
|
|
15161
15186
|
"type": "string",
|
|
15162
15187
|
"optional": true
|
|
15163
15188
|
},
|
|
15189
|
+
{
|
|
15190
|
+
"name": "typeId",
|
|
15191
|
+
"type": "string",
|
|
15192
|
+
"optional": true,
|
|
15193
|
+
"description": "Blueprint type id. For an ENUM global this is the enum type's id (`project.types[].id`)."
|
|
15194
|
+
},
|
|
15164
15195
|
{
|
|
15165
15196
|
"name": "defaultValue",
|
|
15166
15197
|
"type": "any",
|
|
@@ -15174,6 +15205,29 @@
|
|
|
15174
15205
|
],
|
|
15175
15206
|
"filePath": "theme-globals.ts"
|
|
15176
15207
|
},
|
|
15208
|
+
{
|
|
15209
|
+
"name": "TypeLike",
|
|
15210
|
+
"kind": "type",
|
|
15211
|
+
"domain": "theme",
|
|
15212
|
+
"properties": [
|
|
15213
|
+
{
|
|
15214
|
+
"name": "id",
|
|
15215
|
+
"type": "string",
|
|
15216
|
+
"optional": false
|
|
15217
|
+
},
|
|
15218
|
+
{
|
|
15219
|
+
"name": "type",
|
|
15220
|
+
"type": "unknown",
|
|
15221
|
+
"optional": true
|
|
15222
|
+
},
|
|
15223
|
+
{
|
|
15224
|
+
"name": "enumOptions",
|
|
15225
|
+
"type": "Record<string, any> | null",
|
|
15226
|
+
"optional": true
|
|
15227
|
+
}
|
|
15228
|
+
],
|
|
15229
|
+
"filePath": "theme-globals.ts"
|
|
15230
|
+
},
|
|
15177
15231
|
{
|
|
15178
15232
|
"name": "SerializableProject",
|
|
15179
15233
|
"kind": "type",
|
|
@@ -15184,6 +15238,12 @@
|
|
|
15184
15238
|
"type": "{ module?: { variables?: VariableLike[] } }",
|
|
15185
15239
|
"optional": true
|
|
15186
15240
|
},
|
|
15241
|
+
{
|
|
15242
|
+
"name": "types",
|
|
15243
|
+
"type": "TypeLike[]",
|
|
15244
|
+
"optional": true,
|
|
15245
|
+
"description": "Project custom types; only enum entries are read, to resolve ENUM settings' options."
|
|
15246
|
+
},
|
|
15187
15247
|
{
|
|
15188
15248
|
"name": "theme",
|
|
15189
15249
|
"type": "{\n breakpoints?: BreakpointLike[];\n keyframes?: KeyframeLike[];\n pattern?: {\n values?: PatternValueLike[];\n elements?: PatternElementLike[];\n colorSchemes?: ColorSchemeLike[];\n colorSchemeValues?: ColorSchemeValueLike[];\n };\n }",
|