@ikas/code-components-mcp 1.4.0-beta.1 → 1.4.0-beta.100
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 +44 -10
- package/data/migration-examples/complex-header-migration/_meta.json +4 -0
- package/data/migration-examples/complex-header-migration/after-config-snippet.json +55 -0
- package/data/migration-examples/complex-header-migration/after-section.tsx +64 -0
- package/data/migration-examples/complex-header-migration/before-props-summary.json +42 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/_meta.json +4 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-child-styles.css +38 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-child.tsx +22 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-config-snippet.json +31 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-section-styles.css +25 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/after-section.tsx +17 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/before-component.tsx +32 -0
- package/data/migration-examples/custom-dynamic-list-to-component-list/before-theme-snippet.json +53 -0
- package/data/migration-examples/full-component-with-tailwind/_meta.json +4 -0
- package/data/migration-examples/full-component-with-tailwind/after-component.tsx +43 -0
- package/data/migration-examples/full-component-with-tailwind/after-config-snippet.json +25 -0
- package/data/migration-examples/full-component-with-tailwind/after-styles.css +99 -0
- package/data/migration-examples/full-component-with-tailwind/before-component.tsx +60 -0
- package/data/migration-examples/object-custom-to-inline-props/_meta.json +4 -0
- package/data/migration-examples/object-custom-to-inline-props/after-component.tsx +34 -0
- package/data/migration-examples/object-custom-to-inline-props/after-config-snippet.json +23 -0
- package/data/migration-examples/object-custom-to-inline-props/after-styles.css +38 -0
- package/data/migration-examples/object-custom-to-inline-props/before-component.tsx +30 -0
- package/data/migration-examples/object-custom-to-inline-props/before-theme-snippet.json +26 -0
- package/data/migration-examples/slider-library-replacement/_meta.json +4 -0
- package/data/migration-examples/slider-library-replacement/after-child.tsx +13 -0
- package/data/migration-examples/slider-library-replacement/after-config-snippet.json +29 -0
- package/data/migration-examples/slider-library-replacement/after-section.tsx +38 -0
- package/data/migration-examples/slider-library-replacement/after-styles.css +43 -0
- package/data/migration-examples/slider-library-replacement/before-component.tsx +39 -0
- package/data/migration-examples/slider-library-replacement/before-types-snippet.ts +14 -0
- package/data/migration.json +260 -0
- package/data/section-templates/account-info-section/children/AccountFavorites/ikas-config-snippet.json +3 -3
- package/data/section-templates/account-info-section/ikas-config-snippet.json +5 -5
- package/data/section-templates/category-images-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/category-list-section/ikas-config-snippet.json +3 -3
- package/data/section-templates/component-renderer/ikas-config-snippet.json +3 -3
- package/data/section-templates/features-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/footer-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/header-section/children/Announcements/ikas-config-snippet.json +1 -1
- package/data/section-templates/header-section/children/Navbar/ikas-config-snippet.json +3 -3
- package/data/section-templates/header-section/ikas-config-snippet.json +3 -3
- package/data/section-templates/hero-slider-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/image-handling/ikas-config-snippet.json +13 -13
- package/data/section-templates/navigation/ikas-config-snippet.json +3 -3
- package/data/section-templates/product-detail-section/children/ProductDetailDescription/ikas-config-snippet.json +1 -1
- package/data/section-templates/product-detail-section/children/ProductDetailFeatures/ikas-config-snippet.json +1 -1
- package/data/section-templates/product-detail-section/ikas-config-snippet.json +13 -13
- package/data/section-templates/product-slider-section/ikas-config-snippet.json +3 -3
- package/data/storefront-api.json +205 -1362
- package/data/storefront-types.json +622 -124
- package/dist/index.js +4013 -897
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/data/framework.json
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"prop-types": {
|
|
36
36
|
"title": "Available Prop Types",
|
|
37
37
|
"description": "All prop types that can be used in ikas.config.json",
|
|
38
|
-
"content": "Props define what the store editor can configure for each component. Each prop has a `type` that determines the editor UI and the TypeScript type received in your component.\n\n| Type | Editor UI | TypeScript Type | Description |\n|------|-----------|----------------|-------------|\n| `TEXT` | Text input | `string` | Single-line text |\n| `RICH_TEXT` | Rich text editor | `string` | HTML rich text content |\n| `NUMBER` | Number input | `number` | Numeric value |\n| `NUMBER_RANGE` | Number range input | `IkasNumberRange` | Number range with min, max, interval, and unit |\n| `BOOLEAN` | Toggle switch | `boolean` | True/false toggle |\n| `IMAGE` | Image picker | `IkasImage | null` | Image from editor. Use `getDefaultSrc(image)` for URL |\n| `IMAGE_LIST` | Image list picker | `IkasImageList` | Multiple images from editor |\n| `VIDEO` | Video picker | `IkasVideo | null` | Video from editor |\n| `DATE` | Date picker | `Date | string` | Date value |\n| `LINK` | Link editor | `IkasNavigationLink | null` | Navigation link with href, label, subLinks |\n| `LIST_OF_LINK` | Link list editor | `IkasNavigationLinkList` | List of navigation links |\n| `COLOR` | Color picker | `string` | CSS color value (hex, rgb, etc.) |\n| `PRODUCT` | Product picker | `IkasProduct | null` | Single product reference |\n| `PRODUCT_LIST` | Product list picker | `IkasProductList` | Product list with `.data` (products array), `.filters`, `.sort`, `.page`, etc. |\n| `PRODUCT_ATTRIBUTE` | Product attribute picker | `IkasProductAttributeValue | null` | Single product attribute value |\n| `PRODUCT_ATTRIBUTE_LIST` | Product attribute list picker | `IkasProductAttributeValue[]` | Multiple product attribute values |\n| `CATEGORY` | Category picker | `IkasCategory | null` | Single category reference |\n| `CATEGORY_LIST` | Category list picker | `IkasCategoryList` | Multiple category references |\n| `BRAND` | Brand picker | `IkasBrand | null` | Single brand reference |\n| `BRAND_LIST` | Brand list picker | `IkasBrandList` | Multiple brand references |\n| `BLOG` | Blog post picker | `IkasBlog | null` | Single blog post reference |\n| `BLOG_LIST` | Blog post list picker | `IkasBlogList` | Multiple blog post references |\n| `BLOG_CATEGORY` | Blog category picker | `IkasBlogCategory | null` | Single blog category reference |\n| `BLOG_CATEGORY_LIST` | Blog category list picker | `IkasBlogCategoryList` | Multiple blog category references |\n| `TYPE` | Type selector | Depends on typeId | Structured type (padding, margin, size, etc.). Available for both components and sections (sections have a restricted whitelist). |\n| `ENUM` | Dropdown selector | `string` | Enum-based style type (flex-direction, justify-content, align-items, etc.). Uses `enumTypeId`. Available for both components and sections. |\n| `COMPONENT` | Component slot | `any` | A single child component slot. Store owners can place a component in this slot from the editor. Render with `<IkasComponentRenderer>`. |\n| `COMPONENT_LIST` | Component list slot | `any` | A list of child components. Store owners can add multiple components from the editor. Render with `<IkasComponentRenderer>`. |\n\n### COMPONENT & COMPONENT_LIST props (child component slots)\n\nThese prop types enable **slot-based** architectures where store owners can drag child components into your section/component from the editor.\n\n- `COMPONENT` — a single child component slot\n- `COMPONENT_LIST` — a list of child components\n\n**Rendering:** Use the `<IkasComponentRenderer>` wrapper from `@ikas/bp-storefront`:\n```tsx\nimport { IkasComponentRenderer } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\nexport function MySection({ title, cardList, ...props }: Props) {\n return (\n <section>\n <h2>{title}</h2>\n <div className=\"cards\">\n {/* COMPONENT_LIST — render a list of child components */}\n <IkasComponentRenderer\n id=\"card-list\"\n components={cardList as any[]}\n parentProps={props}\n />\n </div>\n </section>\n );\n}\nexport default MySection;\n```\n\n**Key rules:**\n- Always pass `parentProps={props}` so child components can access parent data via dynamic values\n- Cast the prop value: `components={myList as any[]}` for COMPONENT_LIST, `components={[myComp] as any[]}` for COMPONENT\n- `<IkasComponentRenderer>` handles rendering, styling, and reactivity of child components automatically\n\n**Config example (COMPONENT_LIST):**\n```json\n{\n \"name\": \"cardList\",\n \"displayName\": \"Card List\",\n \"type\": \"COMPONENT_LIST\"\n}\n```\n\n**Config example (COMPONENT):**\n```json\n{\n \"name\": \"headerSlot\",\n \"displayName\": \"Header Slot\",\n \"type\": \"COMPONENT\"\n}\n```\n\n### IMAGE type example:\n```json\n{\n \"name\": \"heroImage\",\n \"displayName\": \"Hero Image\",\n \"type\": \"IMAGE\",\n \"required\": false\n}\n```\nAccess in component: `getDefaultSrc(props.heroImage)` (import `getDefaultSrc` from `@ikas/bp-storefront`)\n\n### Prop grouping\nProps can be assigned to groups via `groupId` for organized editor sidebar display. See `get_framework_guide(\"prop-groups\")` for details.\n\n### Style Props: TYPE and ENUM\n\nThere are two prop types for style values:\n\n- **TYPE** — Structured types with numeric values and units (padding, margin, border-radius, sizes, etc.). Uses `typeId`.\n- **ENUM** — Enum types rendered as dropdown selectors (flex-direction, justify-content, align-items, etc.). Uses `enumTypeId`.\n\nBoth are available for components and sections. For sections, TYPE props are limited to a whitelist of style types. Use `list-types --component-type section` to see section-allowed types.\n\n### TYPE prop (structured types)\nThe `TYPE` prop lets you use structured storefront types like PaddingStyleType, MarginStyleType, SizeStyleType, etc. Available for both components and sections (sections have a restricted whitelist of style types).\n\n**Workflow:**\n1. Run `npx ikas-component config list-types` to get available types (requires dev server running with editor connected)\n2. Use the `typeId` from the output when adding the prop\n3. For sections: `npx ikas-component config list-types --component-type section` to see only section-allowed types\n\n**Example config:**\n```json\n{\n \"name\": \"spacing\",\n \"displayName\": \"Spacing\",\n \"type\": \"TYPE\",\n \"typeId\": \"@ikas/bp-storefront-models-PaddingStyleType\"\n}\n```\n\n**Array example** (append `_array` to typeId):\n```json\n{\n \"name\": \"margins\",\n \"displayName\": \"Margins\",\n \"type\": \"TYPE\",\n \"typeId\": \"@ikas/bp-storefront-models-MarginStyleType_array\"\n}\n```\nThis generates `margins?: MarginStyleType[]` in types.ts.\n\n**CLI command:**\n```bash\nnpx ikas-component config add-prop --component MyComp --name spacing --displayName Spacing --type TYPE --typeId \"@ikas/bp-storefront-models-PaddingStyleType\"\n```\n\n### ENUM prop (enum style types)\nThe `ENUM` prop lets you use enum-based types that render as dropdown selectors. There are two kinds of enum types:\n\n1. **Built-in enums** (prefix `@ikas/`): FlexDirectionStyleType, JustifyContentStyleType, AlignItemsStyleType, etc. Available from `list-types` when editor is connected.\n2. **Custom enums**: Created with `config add-enum`. Works offline, no editor needed.\n\n**IMPORTANT:** `add-prop --type ENUM` validates that the `enumTypeId` references an existing enum. You MUST create custom enums first with `config add-enum` before using them. The command will reject unknown enum IDs.\n\n**Custom enum workflow (recommended for AI-driven generation):**\n```bash\n# Step 1: Create the enum FIRST\nnpx ikas-component config add-enum --name \"AspectRatio\" --options '{\"Square\":\"1/1\",\"Landscape\":\"16/9\",\"Portrait\":\"3/4\"}'\n# Returns: {\"success\":true,\"enumId\":\"aBcDeFgHiJ\",...}\n\n# Step 2: Use the returned enumId in add-prop\nnpx ikas-component config add-prop --component MyComp --name aspectRatio --displayName \"Aspect Ratio\" --type ENUM --enumTypeId aBcDeFgHiJ\n```\n\n**Built-in enum workflow:**\n1. Run `npx ikas-component config list-types` — enum types have `category: \"enum\"` in the output\n2. Use the `enumTypeId` from the output when adding the prop\n\n**Example config (built-in enum):**\n```json\n{\n \"name\": \"direction\",\n \"displayName\": \"Direction\",\n \"type\": \"ENUM\",\n \"enumTypeId\": \"@ikas/bp-storefront-models-FlexDirectionStyleType\"\n}\n```\nThis generates `direction?: string` in types.ts.\n\n**CLI command (built-in enum):**\n```bash\nnpx ikas-component config add-prop --component MyComp --name direction --displayName Direction --type ENUM --enumTypeId \"@ikas/bp-storefront-models-FlexDirectionStyleType\"\n```\n\n**Note:** `list-types` requires dev server with editor connected. Custom enums (`config add-enum`) work offline.",
|
|
38
|
+
"content": "Props define what the store editor can configure for each component. Each prop has a `type` that determines the editor UI and the TypeScript type received in your component.\n\n| Type | Editor UI | TypeScript Type | Description |\n|------|-----------|----------------|-------------|\n| `TEXT` | Text input | `string` | Single-line text |\n| `RICH_TEXT` | Rich text editor | `string` | HTML rich text content |\n| `NUMBER` | Number input | `number` | Numeric value |\n| `NUMBER_RANGE` | Number range input | `IkasNumberRange` | Number range with min, max, interval, and unit |\n| `BOOLEAN` | Toggle switch | `boolean` | True/false toggle |\n| `IMAGE` | Image picker | `IkasImage | null` | Image from editor. Use `getDefaultSrc(image)` for URL |\n| `IMAGE_LIST` | Image list picker | `IkasImageList` | Multiple images from editor |\n| `VIDEO` | Video picker | `IkasVideo | null` | Video from editor |\n| `SVG` | SVG upload | `string` | Raw inline SVG markup (the `<svg>…</svg>` source). Normalize with `normalizeSvg(value, { idPrefix })` before rendering via `dangerouslySetInnerHTML`. NOT served through the image CDN — this is the way to ship logos/icons as vectors. |\n| `SVG_LIST` | SVG list upload | `string[]` | List of raw inline SVG markup strings. Normalize each with `normalizeSvg(svg, { idPrefix })` (unique per item) before rendering with `dangerouslySetInnerHTML`. |\n| `DATE` | Date picker | `Date | string` | Date value |\n| `LINK` | Link editor | `IkasNavigationLink | null` | Navigation link. Read at runtime as `.href`/`.label`/`.subLinks`; authored `defaultValue` is a typed object (`linkType`+`externalLink`/`pageType`+`subLinks`). See the LINK section below. |\n| `LIST_OF_LINK` | Link list editor | `IkasNavigationLinkList` | List of navigation links. Authored `defaultValue` is `{ \"links\": [ <link>, … ] }`. See the LINK section below. |\n| `COLOR` | Color picker | `string` | CSS color value (hex, rgb, etc.) |\n| `PRODUCT` | Product picker | `IkasProduct | null` | Single product reference |\n| `PRODUCT_LIST` | Product list picker | `IkasProductList` | Product list with `.data` (products array), `.filters`, `.sort`, `.page`, etc. |\n| `PRODUCT_ATTRIBUTE` | Product attribute picker | `IkasProductAttributeValue | null` | Single product attribute value |\n| `PRODUCT_ATTRIBUTE_LIST` | Product attribute list picker | `IkasProductAttributeValue[]` | Multiple product attribute values |\n| `CATEGORY` | Category picker | `IkasCategory | null` | Single category reference |\n| `CATEGORY_LIST` | Category list picker | `IkasCategoryList` | Multiple category references |\n| `BRAND` | Brand picker | `IkasBrand | null` | Single brand reference |\n| `BRAND_LIST` | Brand list picker | `IkasBrandList` | Multiple brand references |\n| `BLOG` | Blog post picker | `IkasBlog | null` | Single blog post reference |\n| `BLOG_LIST` | Blog post list picker | `IkasBlogList` | Multiple blog post references |\n| `BLOG_CATEGORY` | Blog category picker | `IkasBlogCategory | null` | Single blog category reference |\n| `BLOG_CATEGORY_LIST` | Blog category list picker | `IkasBlogCategoryList` | Multiple blog category references |\n| `TYPE` | Type selector | Depends on typeId | Structured type (padding, margin, size, etc.). Available for both components and sections (sections have a restricted whitelist). |\n| `ENUM` | Dropdown selector | `string` | Enum-based style type (flex-direction, justify-content, align-items, etc.). Uses `enumTypeId`. Available for both components and sections. |\n| `COMPONENT` | Component slot | `any` | A single child component slot. Store owners can place a component in this slot from the editor. Render with `<IkasComponentRenderer>`. Before using, see `get_migration_guide(\"component-composition-decision-guide\")`. |\n| `COMPONENT_LIST` | Component list slot | `any` | A list of child components. Store owners can add multiple components from the editor. Render with `<IkasComponentRenderer>`. Before using, see `get_migration_guide(\"component-composition-decision-guide\")`. |\n\n### COMPONENT & COMPONENT_LIST props (child component slots)\n\nThese prop types enable **slot-based** architectures where store owners can drag child components into your section/component from the editor.\n\n- `COMPONENT` — a single child component slot\n- `COMPONENT_LIST` — a list of child components\n\n**Rendering:** Use the `<IkasComponentRenderer>` wrapper from `@ikas/bp-storefront`:\n```tsx\nimport { IkasComponentRenderer } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\nexport function MySection({ title, cardList, ...props }: Props) {\n return (\n <section>\n <h2>{title}</h2>\n <div className=\"cards\">\n {/* COMPONENT_LIST — render a list of child components */}\n <IkasComponentRenderer\n id=\"card-list\"\n components={cardList as any[]}\n parentProps={props}\n />\n </div>\n </section>\n );\n}\nexport default MySection;\n```\n\n**Key rules:**\n- Always pass `parentProps={props}` so child components can access parent data via dynamic values\n- Cast the prop value: `components={myList as any[]}` for COMPONENT_LIST, `components={[myComp] as any[]}` for COMPONENT\n- `<IkasComponentRenderer>` handles rendering, styling, and reactivity of child components automatically\n\n**Config example (COMPONENT_LIST):**\n```json\n{\n \"name\": \"cardList\",\n \"displayName\": \"Card List\",\n \"type\": \"COMPONENT_LIST\"\n}\n```\n\n**Config example (COMPONENT):**\n```json\n{\n \"name\": \"headerSlot\",\n \"displayName\": \"Header Slot\",\n \"type\": \"COMPONENT\"\n}\n```\n\n### Restricting which components can appear (`filteredComponentIds`)\n\nA COMPONENT or COMPONENT_LIST prop may include a `filteredComponentIds: string[]` field to limit which sibling components can be placed inside it. Each entry must be the **opaque random id** of a component already defined in `ikas.config.json` — e.g. `\"7ojrigep-Eml9n5sN3i\"`.\n\n**Ids are not derivable from component names. Do NOT compose them as `${projectId}-${name}`.** The CLI will reject any unknown id with a structured error listing the valid `{id, name}` pairs.\n\n**Recommended workflow:**\n\n1. Create the child component(s) first. The CLI prints the new id in its JSON response:\n ```bash\n npx ikas-component config add-component --name Navbar --type component\n # → { \"success\": true, \"componentId\": \"7ojrigep-Eml9n5sN3i\", ... }\n ```\n2. Capture the `componentId` and reuse it in the parent's `filteredComponentIds`:\n ```bash\n npx ikas-component config add-component --name Header --type section \\\n --props '[{\"name\":\"components\",\"type\":\"COMPONENT_LIST\",\"filteredComponentIds\":[\"7ojrigep-Eml9n5sN3i\"]}]'\n ```\n\nOr create the parent first without `filteredComponentIds`, then attach it once children exist:\n```bash\nnpx ikas-component config add-prop Header components --type COMPONENT_LIST \\\n --filteredComponentIds '[\"7ojrigep-Eml9n5sN3i\"]'\n```\n\n**Discovering existing ids:** `npx ikas-component config list` returns a JSON document with every component's `{ id, name, type, props… }`. Use that when joining a project mid-flight.\n\n### IMAGE type example:\n```json\n{\n \"name\": \"heroImage\",\n \"displayName\": \"Hero Image\",\n \"type\": \"IMAGE\",\n \"required\": false\n}\n```\nAccess in component: `getDefaultSrc(props.heroImage)` (import `getDefaultSrc` from `@ikas/bp-storefront`)\n\n### SVG and SVG_LIST props (inline vector graphics)\n\nUse `SVG` / `SVG_LIST` when you need a **vector** asset (logo, icon, decorative shape) that must scale crisply and be styleable with CSS — things the raster image CDN cannot do. The editor uploads the file and stores the **raw `<svg>…</svg>` markup as a string**; it is NOT uploaded to the image CDN, so there is no URL and no `IkasImage` wrapper. Uploads are sanitized by the editor at upload time — XSS vectors (`<script>`, `on*` handlers, `javascript:` hrefs, `<foreignObject>`) are stripped — in addition to the existing 64 KB cap and rejection of SVGs containing embedded base64 raster images.\n\n- `SVG` → `string` (one SVG's markup)\n- `SVG_LIST` → `string[]` (multiple SVGs; the editor allows selecting several files at once)\n\n**Rendering:** inject the markup directly. Because it is the same `<svg>` source the user uploaded, you can target it with CSS (e.g. `.icon svg { width: 24px; fill: currentColor; }`).\n\n**Normalize before injecting:** call `normalizeSvg(value, { idPrefix })` from `@ikas/bp-storefront` before setting `dangerouslySetInnerHTML`, so internal ids/`clipPath`/`url(#…)` references get scoped per instance and don't collide when the same SVG (or multiple `SVG_LIST` items) render more than once on a page. For `SVG_LIST`, give each item a deterministic per-item `idPrefix`, e.g. `` `${propKey}-${i}` ``. By default `normalizeSvg` also strips fixed `width`/`height` and ensures a `viewBox` (so CSS controls size); pass `{ color: \"currentColor\" }` to recolor single-color icons via CSS `color` (omit it for multi-color SVGs). This is opt-in for code-components — the theme's built-in SVG element normalizes automatically, but your own `dangerouslySetInnerHTML` injections must call it explicitly.\n```tsx\nimport { normalizeSvg } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\nexport function Logo({ logo, partnerLogos }: Props) {\n return (\n <div className=\"logos\">\n {logo && (\n <span className=\"icon\" dangerouslySetInnerHTML={{ __html: normalizeSvg(logo, { idPrefix: \"logo\" }) }} />\n )}\n {(partnerLogos ?? []).map((svg, i) => (\n <span key={i} className=\"icon\" dangerouslySetInnerHTML={{ __html: normalizeSvg(svg, { idPrefix: `partner-logo-${i}` }) }} />\n ))}\n </div>\n );\n}\nexport default Logo;\n```\n\n**Config example (SVG):**\n```json\n{\n \"name\": \"logo\",\n \"displayName\": \"Logo\",\n \"type\": \"SVG\"\n}\n```\n\n**Config example (SVG_LIST):**\n```json\n{\n \"name\": \"partnerLogos\",\n \"displayName\": \"Partner Logos\",\n \"type\": \"SVG_LIST\"\n}\n```\n\n**CLI command:**\n```bash\nnpx ikas-component config add-prop --component MyComp --name logo --displayName Logo --type SVG\n```\n\n**When NOT to use:** for photos/raster artwork or anything that should be optimized/resized by the CDN, use `IMAGE` / `IMAGE_LIST` instead. SVG is only for hand-authored vector markup.\n\n**Providing a default value:** A default value is a raw <svg>\u2026</svg> string (SVG) or an array of them (SVG_LIST). It must be a single well-formed <svg> root, \u2264 64 KB, with no embedded base64 raster images; it is sanitized on load. Invalid defaults are rejected by the CLI (add-prop / add-component hard-fail with the reason) and silently dropped by the editor when the component is placed, so verify your default is valid. Example: \"defaultValue\": \"<svg viewBox=\\\"0 0 24 24\\\"><path d=\\\"M4 12h16\\\"/></svg>\".\n\n### SVG normalization & sanitization -- the theme-developer standard\n\n**Responsibility split (read this first):**\n- **Security/sanitization is the editor's job, not yours.** Every SVG is sanitized (DOMPurify) on upload, on programmatic writes, AND at render -- you never sanitize in component code.\n- **Normalization (size / color / id-scoping) is `normalizeSvg`'s job.** The theme's built-in SVG element applies it automatically; in your own code component you MUST call `normalizeSvg(value, { idPrefix })` before `dangerouslySetInnerHTML`. There is ONE shared util -- do not reinvent per theme: `import { normalizeSvg } from \"@ikas/bp-storefront\"`.\n\n`normalizeSvg(svg, options)` options: `{ idPrefix: string; scopeIds?: boolean = true; color?: \"currentColor\"; size?: boolean = true }`.\n\n**1. Size.** `size: true` (default) synthesizes a `viewBox` from numeric (unitless/px) `width`/`height` when missing, then strips `width`/`height` so CSS controls size. If there is no viewBox AND size cannot be synthesized (e.g. `%`/`em` dims), width/height are kept (so the icon is not left with zero intrinsic size). Merchants are NOT required to upload viewBox'd or size-less SVGs -- normalize handles it. Size icons with CSS: `.icon svg { width: 24px; height: 24px; }`.\n\n**2. Color.** Opt-in `{ color: \"currentColor\" }` rewrites `fill`/`stroke` (attributes AND `style:` declarations) to `currentColor`, but PRESERVES `none` and `url(...)` (gradients/patterns). So outline icons (`fill=\"none\" stroke=\"...\"`) keep `fill:none` and get `stroke:currentColor` -- no special-casing needed. For MULTI-COLOR SVGs, OMIT `color` to keep the original colors. Rule of thumb: single-color icons -> pass `currentColor`; multi-color -> omit.\n\n**3. Collision (id / clipPath / url(#...)).** Solved in code, not by the editor. `scopeIds: true` (default) prefixes every declared `id` and every `url(#...)` / `href=\"#...\"` reference with `idPrefix`, so the same SVG rendered multiple times (or multiple SVG_LIST items) cannot collide. Give each render an instance-unique prefix -- the built-in element uses its element id; for SVG_LIST use a per-item prefix like `` `${propKey}-${i}` ``.\n\n**4. Sanitization -- what the editor strips.** Beyond the 64 KB cap and base64-raster rejection, the editor strips `<script>`, `on*` event handlers (onload/onclick/onerror/...), `javascript:` hrefs, and `<foreignObject>`. This runs on file upload, on programmatic `update_section_prop` writes, and again at render -- so stored and rendered markup is always safe. `normalizeSvg` is NOT a sanitizer (pure string transforms); security lives in the separate sanitize/validate layer and is automatic.\n\n**5. One shared pattern.** Yes -- `normalizeSvg` from `@ikas/bp-storefront` is the single, isomorphic (SSR/browser/node) util. Do not hand-roll size/color/id-scoping per theme.\n\n**Naming.** The editor shows each SVG's `class` attribute as its display name in the list; SVG / SVG_LIST items with no class render as `svg-1`, `svg-2`, .... Set `class=\"my-icon\"` in the markup to name it.\n\n### LINK and LIST_OF_LINK props (navigation links)\n\n**CRITICAL — the authored `defaultValue` shape is NOT the runtime read shape.** In component code you READ a link as `props.myLink.href` / `.label` / `.subLinks`. But the `defaultValue` you author in `ikas.config.json` MUST be the typed object shown below. Do NOT pass `{ \"href\": \"…\", \"label\": \"…\" }` (legacy shape) and do NOT pass the value as a JSON string — the CLI rejects both.\n\n**LINK** `defaultValue` is a single link object:\n```json\n{ \"linkType\": \"EXTERNAL\", \"label\": \"Shop now\", \"externalLink\": \"https://example.com\", \"subLinks\": [] }\n```\nor, to link to a store page:\n```json\n{ \"linkType\": \"PAGE\", \"label\": \"Home\", \"pageType\": \"INDEX\", \"subLinks\": [] }\n```\n\n**LIST_OF_LINK** `defaultValue` wraps an array of link objects in `{ \"links\": [...] }`:\n```json\n{ \"links\": [\n { \"linkType\": \"PAGE\", \"label\": \"Home\", \"pageType\": \"INDEX\", \"subLinks\": [] },\n { \"linkType\": \"EXTERNAL\", \"label\": \"Blog\", \"externalLink\": \"https://example.com/blog\", \"subLinks\": [] }\n] }\n```\n\n**Rules for every link object:**\n- `linkType` is REQUIRED and must be one of `PAGE`, `EXTERNAL`, `FILE`.\n- `EXTERNAL` → set `externalLink` (a URL). `PAGE` → set `pageType` (e.g. `INDEX`; add `pageId` for a specific page). `FILE` → set `fileUrl`.\n- `subLinks` must be an array (use `[]` when empty); each entry is itself a link object with the same shape (for dropdown/nested menus).\n- NEVER use a JSON string, and NEVER use the legacy `{ label, href }` shape.\n\n**Config example (LINK):**\n```json\n{ \"name\": \"ctaLink\", \"displayName\": \"CTA Link\", \"type\": \"LINK\", \"defaultValue\": { \"linkType\": \"EXTERNAL\", \"label\": \"Shop now\", \"externalLink\": \"https://example.com\", \"subLinks\": [] } }\n```\n\n**Config example (LIST_OF_LINK):**\n```json\n{ \"name\": \"menuLinks\", \"displayName\": \"Menu Links\", \"type\": \"LIST_OF_LINK\", \"defaultValue\": { \"links\": [ { \"linkType\": \"PAGE\", \"label\": \"Home\", \"pageType\": \"INDEX\", \"subLinks\": [] } ] } }\n```\n\n**CLI command (LIST_OF_LINK):**\n```bash\nnpx ikas-component config add-prop --component MyComp --name menuLinks --displayName \"Menu Links\" --type LIST_OF_LINK --defaultValue '{\"links\":[{\"linkType\":\"PAGE\",\"label\":\"Home\",\"pageType\":\"INDEX\",\"subLinks\":[]}]}'\n```\n\n### Prop grouping\nProps can be assigned to groups via `groupId` for organized editor sidebar display. See `get_framework_guide(\"prop-groups\")` for details.\n\n### Style Props: TYPE and ENUM\n\nThere are two prop types for style values:\n\n- **TYPE** — Structured types with numeric values and units (padding, margin, border-radius, sizes, etc.). Uses `typeId`.\n- **ENUM** — Enum types rendered as dropdown selectors (flex-direction, justify-content, align-items, etc.). Uses `enumTypeId`.\n\nBoth are available for components and sections. For sections, TYPE props are limited to a whitelist of style types. Use `list-types --component-type section` to see section-allowed types.\n\n### TYPE prop (structured types)\nThe `TYPE` prop lets you use structured storefront types like PaddingStyleType, MarginStyleType, SizeStyleType, etc. Available for both components and sections (sections have a restricted whitelist of style types).\n\n**Workflow:**\n1. Run `npx ikas-component config list-types` to get available types (requires dev server running with editor connected)\n2. Use the `typeId` from the output when adding the prop\n3. For sections: `npx ikas-component config list-types --component-type section` to see only section-allowed types\n\n**Example config:**\n```json\n{\n \"name\": \"spacing\",\n \"displayName\": \"Spacing\",\n \"type\": \"TYPE\",\n \"typeId\": \"@ikas/bp-storefront-models-PaddingStyleType\"\n}\n```\n\n**Array example** (append `_array` to typeId):\n```json\n{\n \"name\": \"margins\",\n \"displayName\": \"Margins\",\n \"type\": \"TYPE\",\n \"typeId\": \"@ikas/bp-storefront-models-MarginStyleType_array\"\n}\n```\nThis generates `margins?: MarginStyleType[]` in types.ts.\n\n**CLI command:**\n```bash\nnpx ikas-component config add-prop --component MyComp --name spacing --displayName Spacing --type TYPE --typeId \"@ikas/bp-storefront-models-PaddingStyleType\"\n```\n\n### ENUM prop (enum style types)\nThe `ENUM` prop lets you use enum-based types that render as dropdown selectors. There are two kinds of enum types:\n\n1. **Built-in enums** (prefix `@ikas/`): FlexDirectionStyleType, JustifyContentStyleType, AlignItemsStyleType, etc. Available from `list-types` when editor is connected.\n2. **Custom enums**: Created with `config add-enum`. Works offline, no editor needed.\n\n**IMPORTANT:** `add-prop --type ENUM` validates that the `enumTypeId` references an existing enum. You MUST create custom enums first with `config add-enum` before using them. The command will reject unknown enum IDs.\n\n**Custom enum workflow (recommended for AI-driven generation):**\n```bash\n# Step 1: Create the enum FIRST\nnpx ikas-component config add-enum --name \"AspectRatio\" --options '{\"Square\":\"1/1\",\"Landscape\":\"16/9\",\"Portrait\":\"3/4\"}'\n# Returns: {\"success\":true,\"enumId\":\"aBcDeFgHiJ\",...}\n\n# Step 2: Use the returned enumId in add-prop\nnpx ikas-component config add-prop --component MyComp --name aspectRatio --displayName \"Aspect Ratio\" --type ENUM --enumTypeId aBcDeFgHiJ\n```\n\n**Built-in enum workflow:**\n1. Run `npx ikas-component config list-types` — enum types have `category: \"enum\"` in the output\n2. Use the `enumTypeId` from the output when adding the prop\n\n**Example config (built-in enum):**\n```json\n{\n \"name\": \"direction\",\n \"displayName\": \"Direction\",\n \"type\": \"ENUM\",\n \"enumTypeId\": \"@ikas/bp-storefront-models-FlexDirectionStyleType\"\n}\n```\nThis generates `direction?: string` in types.ts.\n\n**CLI command (built-in enum):**\n```bash\nnpx ikas-component config add-prop --component MyComp --name direction --displayName Direction --type ENUM --enumTypeId \"@ikas/bp-storefront-models-FlexDirectionStyleType\"\n```\n\n**Note:** `list-types` requires dev server with editor connected. Custom enums (`config add-enum`) work offline.",
|
|
39
39
|
"tags": [
|
|
40
40
|
"props",
|
|
41
41
|
"types",
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
"imports": {
|
|
143
143
|
"title": "Import Patterns",
|
|
144
144
|
"description": "How to import storefront functions and types",
|
|
145
|
-
"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`).",
|
|
145
|
+
"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`",
|
|
146
146
|
"tags": [
|
|
147
147
|
"import",
|
|
148
148
|
"storefront",
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
"sections-vs-components": {
|
|
156
156
|
"title": "Sections vs Components",
|
|
157
157
|
"description": "The difference between sections (page-level containers) and components (child elements)",
|
|
158
|
-
"content": "ikas code components have two types: **sections** and **components**.\n\n## Sections\nSections are page-level, full-width containers that make up the structure of a page. Examples: Header, Hero Banner, Footer, Product Grid, Featured Collection.\n\n- Set `\"type\": \"section\"` in `ikas.config.json`\n- Use a `<section>` root element with full-width styling\n- Name the props interface `Props` in `types.ts`\n- Typically have full-width layout with inner max-width container\n- Styles: `width: 100%; padding: 64px 24px;` with `.inner { max-width: 1200px; margin: 0 auto; }`\n- **MUST include a `backgroundColor` COLOR prop** (default: `#ffffff`). Apply via inline style on the `<section>` element: `style={backgroundColor ? { backgroundColor } : undefined}`\n- Consider adding `textColor` COLOR props for text elements sitting directly on the section background, so they remain readable when the background changes\n\n## Components\nComponents are child elements that are placed inside sections. Examples: Button, Product Card, Badge, Price Display, Image Gallery.\n\n- No `type` field needed in `ikas.config.json` (defaults to `\"component\"`)\n- Use a `<div>` root element\n- Name the props interface `Props` in `types.ts`\n- Sized by their content or parent container\n\n## Config Difference\nThe only config difference is the `type` field on the component definition:\n\n```json\n{\n \"components\": [\n {\n \"id\": \"product-card\",\n \"name\": \"Product Card\",\n \"entry\": \"./src/components/ProductCard/index.tsx\",\n \"styles\": \"./src/components/ProductCard/styles.css\",\n \"props\": []\n },\n {\n \"id\": \"hero-banner\",\n \"name\": \"Hero Banner\",\n \"type\": \"section\",\n \"entry\": \"./src/components/HeroBanner/index.tsx\",\n \"styles\": \"./src/components/HeroBanner/styles.css\",\n \"props\": []\n }\n ]\n}\n```\n\n## Required Section Prop: backgroundColor\n\nEvery section MUST include a `backgroundColor` COLOR prop so store owners can customize the section background.\n\n### Config:\n```json\n{ \"name\": \"backgroundColor\", \"displayName\": \"Background Color\", \"type\": \"COLOR\", \"defaultValue\": \"#ffffff\" }\n```\n\n### Usage in index.tsx:\n```tsx\nexport function MySection({ title, backgroundColor = \"#ffffff\" }: Props) {\n return (\n <section className=\"my-section\" style={backgroundColor ? { backgroundColor } : undefined}>\n <div className=\"my-section-inner\">\n <h1>{title}</h1>\n </div>\n </section>\n );\n}\n```\n\nOptionally, also add `textColor` and/or `headingColor` COLOR props for text elements sitting directly on the section background. This ensures text remains readable when the store owner changes the background color.\n\n## Header & Footer Sections\n\nTo designate a section as the store's header or footer, add `isHeader: true` or `isFooter: true` in `ikas.config.json`. This tells the editor to treat the section as a common header/footer that appears on all pages automatically.\n\n```json\n{\n \"id\": \"header\",\n \"name\": \"Header\",\n \"type\": \"section\",\n \"isHeader\": true,\n \"entry\": \"./src/components/Header/index.tsx\",\n \"styles\": \"./src/components/Header/styles.css\",\n \"props\": [...]\n}\n```\n\nOnly one component should have `isHeader: true` and one should have `isFooter: true`. These flags only apply to section-type components.\n\n## How to Decide\n- Will it span the full width of the page and sit at the top level? → **Section**\n- Will it be placed inside another container or repeated in a list? → **Component**\n- Is it the store's main header or footer? → **Section** with `isHeader`/`isFooter`",
|
|
158
|
+
"content": "ikas code components have two types: **sections** and **components**.\n\n## Sections\nSections are page-level, full-width containers that make up the structure of a page. Examples: Header, Hero Banner, Footer, Product Grid, Featured Collection.\n\n- Set `\"type\": \"section\"` in `ikas.config.json`\n- Use a `<section>` root element with full-width styling\n- Name the props interface `Props` in `types.ts`\n- Typically have full-width layout with inner max-width container\n- Styles: `width: 100%; padding: 64px 24px;` with `.inner { max-width: 1200px; margin: 0 auto; }`\n- **MUST include a `backgroundColor` COLOR prop** (default: `#ffffff`). Apply via inline style on the `<section>` element: `style={backgroundColor ? { backgroundColor } : undefined}`\n- Consider adding `textColor` COLOR props for text elements sitting directly on the section background, so they remain readable when the background changes\n\n## Components\nComponents are child elements that are placed inside sections. Examples: Button, Product Card, Badge, Price Display, Image Gallery.\n\n- No `type` field needed in `ikas.config.json` (defaults to `\"component\"`)\n- Use a `<div>` root element\n- Name the props interface `Props` in `types.ts`\n- Sized by their content or parent container\n\n## Config Difference\nThe only config difference is the `type` field on the component definition:\n\n```json\n{\n \"components\": [\n {\n \"id\": \"product-card\",\n \"name\": \"Product Card\",\n \"entry\": \"./src/components/ProductCard/index.tsx\",\n \"styles\": \"./src/components/ProductCard/styles.css\",\n \"props\": []\n },\n {\n \"id\": \"hero-banner\",\n \"name\": \"Hero Banner\",\n \"type\": \"section\",\n \"entry\": \"./src/components/HeroBanner/index.tsx\",\n \"styles\": \"./src/components/HeroBanner/styles.css\",\n \"props\": []\n }\n ]\n}\n```\n\n## Required Section Prop: backgroundColor\n\nEvery section MUST include a `backgroundColor` COLOR prop so store owners can customize the section background.\n\n### Config:\n```json\n{ \"name\": \"backgroundColor\", \"displayName\": \"Background Color\", \"type\": \"COLOR\", \"defaultValue\": \"#ffffff\" }\n```\n\n### Usage in index.tsx:\n```tsx\nexport function MySection({ title, backgroundColor = \"#ffffff\" }: Props) {\n return (\n <section className=\"my-section\" style={backgroundColor ? { backgroundColor } : undefined}>\n <div className=\"my-section-inner\">\n <h1>{title}</h1>\n </div>\n </section>\n );\n}\n```\n\nOptionally, also add `textColor` and/or `headingColor` COLOR props for text elements sitting directly on the section background. This ensures text remains readable when the store owner changes the background color.\n\n## Header & Footer Sections\n\nTo designate a section as the store's header or footer, add `isHeader: true` or `isFooter: true` in `ikas.config.json`. This tells the editor to treat the section as a common header/footer that appears on all pages automatically.\n\n```json\n{\n \"id\": \"header\",\n \"name\": \"Header\",\n \"type\": \"section\",\n \"isHeader\": true,\n \"entry\": \"./src/components/Header/index.tsx\",\n \"styles\": \"./src/components/Header/styles.css\",\n \"props\": [...]\n}\n```\n\nOnly one component should have `isHeader: true` and one should have `isFooter: true`. These flags only apply to section-type components.\n\n## Container Sections (sections that host children via COMPONENT_LIST)\n\nSome sections are **containers** — they don't render content directly, they host other components in editable slots. Headers, footers, and product-detail sections are the canonical examples.\n\nA container section's config has a `COMPONENT_LIST` (or `COMPONENT`) prop with `filteredComponentIds` restricting which siblings can be placed in the slot:\n\n```json\n{\n \"name\": \"components\",\n \"type\": \"COMPONENT_LIST\",\n \"filteredComponentIds\": [\"<NAVBAR_ID>\", \"<ANNOUNCEMENTS_ID>\"]\n}\n```\n\nBuilding a container section is **always** a 3-step recipe — you cannot do it in one CLI call because the parent's `filteredComponentIds` must reference child ids that don't exist until the children are created:\n\n1. **Create each child component first** with `config add-component --type component`. Capture the `componentId` returned in each JSON response — those are the opaque ids you'll wire into the parent.\n2. **Create the parent section** with `config add-component --type section` (omit `filteredComponentIds` at this point — they're wired in step 3).\n3. **Wire the slot** with `config update-prop --component <Parent> --prop <slotName> --filteredComponentIds '[\"<CHILD_1_ID>\", \"<CHILD_2_ID>\"]'`.\n\nSkipping step 3 leaves the slot unrestricted and the section non-functional. The CLI rejects unknown ids with a structured error listing valid `{id, name}` pairs, so there is no silent failure mode. See `get_framework_guide(\"header-footer-patterns\")` for a complete worked example, or call `get_section_template(\"header-section\")` for an auto-generated recipe.\n\n## How to Decide\n- Will it span the full width of the page and sit at the top level? → **Section**\n- Will it be placed inside another container or repeated in a list? → **Component**\n- Is it the store's main header or footer, or a multi-slot layout? → **Container section** with one or more COMPONENT_LIST props — follow the 3-step recipe above\n- Is it the store's main header or footer? → **Section** with `isHeader`/`isFooter`",
|
|
159
159
|
"tags": [
|
|
160
160
|
"section",
|
|
161
161
|
"component",
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
"common-pitfalls": {
|
|
174
174
|
"title": "Common Pitfalls",
|
|
175
175
|
"description": "Frequent mistakes LLMs and developers make when building ikas code components",
|
|
176
|
-
"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.",
|
|
176
|
+
"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### 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.",
|
|
177
177
|
"tags": [
|
|
178
178
|
"pitfalls",
|
|
179
179
|
"gotchas",
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
"ai-workflow": {
|
|
210
210
|
"title": "AI Agent Workflow for Building ikas Components",
|
|
211
211
|
"description": "Step-by-step guide for AI coding agents building ikas storefront components using CLI commands and MCP tools",
|
|
212
|
-
"content": "## AI Agent Workflow\n\nThis is the recommended step-by-step workflow for AI agents building ikas code components. Follow these steps in order for reliable, error-free results.\n\n**IMPORTANT: NEVER create or edit `types.ts` manually — it is auto-generated by the CLI.** The CLI commands below update BOTH `ikas.config.json` AND `types.ts` automatically.\n\n### Step 1: Create Component with Props\n\nUse `get_section_template(sectionType)` to get a starter template — it includes a ready-to-run CLI command with `--props`.\n\n**Important:** When creating a section, always include a `backgroundColor` COLOR prop (default: `#ffffff`). Optionally consider adding `textColor` for text elements directly on the section background.\n\n**Important:** When creating a header or footer section, use `--isHeader` or `--isFooter` flags on the `add-component` command. This marks the section as the store's common header/footer so it appears on all pages automatically:\n```bash\nnpx ikas-component config add-component --name \"Header\" --type section --isHeader --props '[...]'\nnpx ikas-component config add-component --name \"Footer\" --type section --isFooter --props '[...]'\n```\n\n**Important:** Ensure ALL user-visible text (headings, buttons, labels, empty states, loading text) is exposed as TEXT props. Never hardcode text strings in JSX. Use `defaultValue` for English defaults. For button loading states, use two separate props (e.g., `submitButtonText` + `submittingButtonText`). Group text props under a \"Texts\" propGroup when 5+ props exist.\n\nThen run the single `add-component --props` command to create the component scaffold WITH all props in one step:\n```bash\nnpx ikas-component config add-component --name \"HeroSection\" --type section --props '[{\"name\":\"title\",\"displayName\":\"Title\",\"type\":\"TEXT\",\"required\":true},{\"name\":\"backgroundImage\",\"displayName\":\"Background Image\",\"type\":\"IMAGE\"},{\"name\":\"showButton\",\"displayName\":\"Show Button\",\"type\":\"BOOLEAN\"}]'\n```\n\nThis creates the component directory with `index.tsx`, `types.ts` (with correct Props interface), `styles.css`, updates `ikas.config.json`, and updates the barrel export. The output is JSON:\n```json\n{\"success\": true, \"componentId\": \"abc123-hero-section\", \"componentName\": \"HeroSection\", \"type\": \"section\", \"propsCount\": 3, \"files\": [...]}\n```\n\nThe `--props` flag accepts a JSON array of prop objects. Each prop needs `name` + `type` at minimum. `displayName` is auto-generated from camelCase name if omitted (e.g. `backgroundImage` → `\"Background Image\"`).\n\nTo add more props later, use `add-prop`:\n```bash\nnpx ikas-component config add-prop --component \"HeroSection\" --name \"subtitle\" --displayName \"Subtitle\" --type TEXT\n```\n\n### Step 2: Get Reference Material\n\nBefore writing component code, use MCP tools to get the right patterns:\n- `get_section_template(sectionType)` — Get the API integration pattern reference for common section types (product-detail, cart, login, header, footer, etc.). Study the function calls and data access patterns only.\n- `get_framework_guide(\"common-pitfalls\")` — Review common mistakes to avoid\n- `get_framework_guide(\"component-structure\")` — Review component structure patterns\n- `get_function_doc(functionName)` — Look up exact function signatures before using them\n- `get_model_guide(typeName)` — Get comprehensive info about a model type\n\n### Step 2.5: Design Your Component (IMPORTANT)\n\nBefore writing code, plan an ORIGINAL visual design:\n- Do NOT reproduce layout, CSS class names, or HTML structure from templates/examples\n- Templates and examples demonstrate correct API usage only — they are not designs to copy\n- **KEEP from references:** imports, function calls, data access patterns, form handling flows, type casts, null safety patterns\n- **CREATE fresh:** all JSX/HTML structure, all CSS class names and rules, all visual layout and spacing, all UX patterns (loading states, empty states, transitions, responsive breakpoints)\n- Think about the specific store's brand and design language when creating your component\n\n### Step 3: Write the Component Code\n\nEdit `src/components/{ComponentName}/index.tsx` with the implementation. **Do NOT edit `types.ts`** — it was already generated correctly in Step 1. Key rules:\n- Import props from `./types` (auto-generated in Step 1)\n- Import storefront functions from `@ikas/bp-storefront`\n- Root export should use named + default export: `export function X({ ... }: Props) { ... }` + `export default X;`\n- Only wrap sub-components with `observer()` when they independently read MobX stores\n- Use optional chaining for all props: `props.title ?? \"Default\"`\n- Use `as unknown as boolean` cast for functions like `hasProductStock`, `isAddToCartEnabled`\n- **Sections MUST apply backgroundColor**: destructure `backgroundColor = \"#ffffff\"` and add `style={backgroundColor ? { backgroundColor } : undefined}` on the root `<section>` element\n\n### Step 3.5: Organize Props into Groups (if 5+ props)\n\nIf the component has 5 or more props, organize them into groups for better editor UX:\n```bash\nnpx ikas-component config add-prop-group --component \"HeroSection\" --id content --name \"Content\"\nnpx ikas-component config add-prop-group --component \"HeroSection\" --id appearance --name \"Appearance\"\nnpx ikas-component config update-prop --component \"HeroSection\" --prop title --group content\nnpx ikas-component config update-prop --component \"HeroSection\" --prop backgroundImage --group appearance\n```\n\n### Step 4: Write Styles\n\nEdit `src/components/{ComponentName}/styles.css`. Key rules:\n- Use class selectors only (`.my-class`), never bare element selectors\n- CSS is auto-scoped at build time — no manual namespacing needed\n- For sections: `width: 100%; padding: 64px 24px;` with inner max-width container\n\n### Step 5: Verify with Type Checking\n\nRun the lightweight type checker:\n```bash\nnpx ikas-component check --json\n```\n\nSuccess output:\n```json\n{\"success\": true, \"errors\": []}\n```\n\nError output:\n```json\n{\"success\": false, \"errorCount\": 2, \"errors\": [{\"file\": \"src/components/Hero/index.tsx\", \"line\": 15, \"column\": 3, \"code\": \"TS2339\", \"message\": \"Property 'title' does not exist on type 'Props'\"}]}\n```\n\n### Step 6: Fix Errors and Re-check\n\nFor each error:\n1. Read the file and line number from the error\n2. Fix the issue (common fixes below)\n3. Re-run `npx ikas-component check --json`\n\n**Common error fixes:**\n- `Property 'x' does not exist on type 'Props'` — Prop wasn't added via CLI. Run `npx ikas-component config add-prop`.\n- `Cannot find module '@ikas/bp-storefront'` — Normal in type-check if node_modules not fully installed. Focus on component-level errors.\n- `Type 'X' is not assignable to type 'Y'` — Check function signature with `get_function_doc()`.\n\n### Step 7: Full Build Validation\n\nOnce type checking passes, run the full build:\n```bash\nnpx ikas-component build\n```\n\nThis runs type checking + esbuild compilation + CSS scoping.\n\n### Quick Reference: CLI Commands\n\n| Command | Purpose |\n|---------|--------|\n| `npx ikas-component config add-component --name X --type section --props '[...]'` | **Primary** — create component with all props |\n| `npx ikas-component config add-component --name X --type section --isHeader --props '[...]'` | Create header section |\n| `npx ikas-component config add-component --name X --type section --isFooter --props '[...]'` | Create footer section |\n| `npx ikas-component config add-component --name X --type section` | Create component with no props |\n| `npx ikas-component config add-prop --component X --name y --displayName Y --type TEXT` | Add a prop incrementally |\n| `npx ikas-component config update-prop --component X --prop y --required true` | Update a prop |\n| `npx ikas-component config remove-prop --component X --prop y` | Remove a prop |\n| `npx ikas-component config remove-component --name X` | Remove a component |\n| `npx ikas-component config list` | List all components and props |\n| `npx ikas-component check --json` | Type-check with JSON output |\n| `npx ikas-component build` | Full production build |\n\n### Quick Reference: MCP Tools\n\n| Tool | When to Use |\n|------|------------|\n| `get_section_template(type)` | Starting a new section — get API integration pattern reference + CLI command (create original design, don't copy layout) |\n| `get_framework_guide(topic)` | Understanding patterns, pitfalls, architecture |\n| `get_function_doc(name)` | Looking up exact function signature |\n| `get_model_guide(type)` | Working with a model type (IkasProduct, IkasCart, etc.) |\n| `get_prop_types()` | Checking available prop types for ikas.config.json |\n| `search_docs(query)` | Finding relevant functions/docs by keyword
|
|
212
|
+
"content": "## AI Agent Workflow\n\nThis is the recommended step-by-step workflow for AI agents building ikas code components. Follow these steps in order for reliable, error-free results.\n\n**IMPORTANT: NEVER create or edit `types.ts` manually — it is auto-generated by the CLI.** The CLI commands below update BOTH `ikas.config.json` AND `types.ts` automatically.\n\n### Step 1: Create Component with Props\n\nUse `get_section_template(sectionType)` to get a starter template — it includes a ready-to-run CLI command with `--props`.\n\n**Important:** When creating a section, always include a `backgroundColor` COLOR prop (default: `#ffffff`). Optionally consider adding `textColor` for text elements directly on the section background.\n\n**Important:** When creating a header or footer section, use `--isHeader` or `--isFooter` flags on the `add-component` command. This marks the section as the store's common header/footer so it appears on all pages automatically:\n```bash\nnpx ikas-component config add-component --name \"Header\" --type section --isHeader --props '[...]'\nnpx ikas-component config add-component --name \"Footer\" --type section --isFooter --props '[...]'\n```\n\n**Important:** Ensure ALL user-visible text (headings, buttons, labels, empty states, loading text) is exposed as TEXT props. Never hardcode text strings in JSX. Use `defaultValue` for English defaults. For button loading states, use two separate props (e.g., `submitButtonText` + `submittingButtonText`). Group text props under a \"Texts\" propGroup when 5+ props exist.\n\nThen run the single `add-component --props` command to create the component scaffold WITH all props in one step:\n```bash\nnpx ikas-component config add-component --name \"HeroSection\" --type section --props '[{\"name\":\"title\",\"displayName\":\"Title\",\"type\":\"TEXT\",\"required\":true},{\"name\":\"backgroundImage\",\"displayName\":\"Background Image\",\"type\":\"IMAGE\"},{\"name\":\"showButton\",\"displayName\":\"Show Button\",\"type\":\"BOOLEAN\"}]'\n```\n\nThis creates the component directory with `index.tsx`, `types.ts` (with correct Props interface), `styles.css`, updates `ikas.config.json`, and updates the barrel export. The output is JSON:\n```json\n{\"success\": true, \"componentId\": \"abc123-hero-section\", \"componentName\": \"HeroSection\", \"type\": \"section\", \"propsCount\": 3, \"files\": [...]}\n```\n\nThe `--props` flag accepts a JSON array of prop objects. Each prop needs `name` + `type` at minimum. `displayName` is auto-generated from camelCase name if omitted (e.g. `backgroundImage` → `\"Background Image\"`).\n\nTo add more props later, use `add-prop`:\n```bash\nnpx ikas-component config add-prop --component \"HeroSection\" --name \"subtitle\" --displayName \"Subtitle\" --type TEXT\n```\n\n### Step 2: Get Reference Material\n\nBefore writing component code, use MCP tools to get the right patterns:\n- `get_section_template(sectionType)` — Get the API integration pattern reference for common section types (product-detail, cart, login, header, footer, etc.). Study the function calls and data access patterns only.\n- `get_framework_guide(\"common-pitfalls\")` — Review common mistakes to avoid\n- `get_framework_guide(\"component-structure\")` — Review component structure patterns\n- `get_function_doc(functionName)` — Look up exact function signatures before using them\n- `get_model_guide(typeName)` — Get comprehensive info about a model type\n\n### Step 2.5: Design Your Component (IMPORTANT)\n\nBefore writing code, plan an ORIGINAL visual design:\n- Do NOT reproduce layout, CSS class names, or HTML structure from templates/examples\n- Templates and examples demonstrate correct API usage only — they are not designs to copy\n- **KEEP from references:** imports, function calls, data access patterns, form handling flows, type casts, null safety patterns\n- **CREATE fresh:** all JSX/HTML structure, all CSS class names and rules, all visual layout and spacing, all UX patterns (loading states, empty states, transitions, responsive breakpoints)\n- Think about the specific store's brand and design language when creating your component\n\n### Step 3: Write the Component Code\n\nEdit `src/components/{ComponentName}/index.tsx` with the implementation. **Do NOT edit `types.ts`** — it was already generated correctly in Step 1. Key rules:\n- Import props from `./types` (auto-generated in Step 1)\n- Import storefront functions from `@ikas/bp-storefront`\n- Root export should use named + default export: `export function X({ ... }: Props) { ... }` + `export default X;`\n- Only wrap sub-components with `observer()` when they independently read MobX stores\n- Use optional chaining for all props: `props.title ?? \"Default\"`\n- Use `as unknown as boolean` cast for functions like `hasProductStock`, `isAddToCartEnabled`\n- **Sections MUST apply backgroundColor**: destructure `backgroundColor = \"#ffffff\"` and add `style={backgroundColor ? { backgroundColor } : undefined}` on the root `<section>` element\n\n### Step 3.5: Organize Props into Groups (if 5+ props)\n\nIf the component has 5 or more props, organize them into groups for better editor UX:\n```bash\nnpx ikas-component config add-prop-group --component \"HeroSection\" --id content --name \"Content\"\nnpx ikas-component config add-prop-group --component \"HeroSection\" --id appearance --name \"Appearance\"\nnpx ikas-component config update-prop --component \"HeroSection\" --prop title --group content\nnpx ikas-component config update-prop --component \"HeroSection\" --prop backgroundImage --group appearance\n```\n\n### Step 4: Write Styles\n\nEdit `src/components/{ComponentName}/styles.css`. Key rules:\n- Use class selectors only (`.my-class`), never bare element selectors\n- CSS is auto-scoped at build time — no manual namespacing needed\n- For sections: `width: 100%; padding: 64px 24px;` with inner max-width container\n\n### Step 5: Verify with Type Checking\n\nRun the lightweight type checker:\n```bash\nnpx ikas-component check --json\n```\n\nSuccess output:\n```json\n{\"success\": true, \"errors\": []}\n```\n\nError output:\n```json\n{\"success\": false, \"errorCount\": 2, \"errors\": [{\"file\": \"src/components/Hero/index.tsx\", \"line\": 15, \"column\": 3, \"code\": \"TS2339\", \"message\": \"Property 'title' does not exist on type 'Props'\"}]}\n```\n\n### Step 6: Fix Errors and Re-check\n\nFor each error:\n1. Read the file and line number from the error\n2. Fix the issue (common fixes below)\n3. Re-run `npx ikas-component check --json`\n\n**Common error fixes:**\n- `Property 'x' does not exist on type 'Props'` — Prop wasn't added via CLI. Run `npx ikas-component config add-prop`.\n- `Cannot find module '@ikas/bp-storefront'` — Normal in type-check if node_modules not fully installed. Focus on component-level errors.\n- `Type 'X' is not assignable to type 'Y'` — Check function signature with `get_function_doc()`.\n\n### Step 7: Full Build Validation\n\nOnce type checking passes, run the full build:\n```bash\nnpx ikas-component build\n```\n\nThis runs type checking + esbuild compilation + CSS scoping.\n\n### Quick Reference: CLI Commands\n\n| Command | Purpose |\n|---------|--------|\n| `npx ikas-component config add-component --name X --type section --props '[...]'` | **Primary** — create component with all props |\n| `npx ikas-component config add-component --name X --type section --isHeader --props '[...]'` | Create header section |\n| `npx ikas-component config add-component --name X --type section --isFooter --props '[...]'` | Create footer section |\n| `npx ikas-component config add-component --name X --type section` | Create component with no props |\n| `npx ikas-component config add-prop --component X --name y --displayName Y --type TEXT` | Add a prop incrementally |\n| `npx ikas-component config update-prop --component X --prop y --required true` | Update a prop |\n| `npx ikas-component config remove-prop --component X --prop y` | Remove a prop |\n| `npx ikas-component config remove-component --name X` | Remove a component |\n| `npx ikas-component config list` | List all components and props |\n| `npx ikas-component check --json` | Type-check with JSON output |\n| `npx ikas-component build` | Full production build |\n\n### Quick Reference: MCP Tools\n\n| Tool | When to Use |\n|------|------------|\n| `get_section_template(type)` | Starting a new section — get API integration pattern reference + CLI command (create original design, don't copy layout) |\n| `get_framework_guide(topic)` | Understanding patterns, pitfalls, architecture |\n| `get_function_doc(name)` | Looking up exact function signature |\n| `get_model_guide(type)` | Working with a model type (IkasProduct, IkasCart, etc.) |\n| `get_prop_types()` | Checking available prop types for ikas.config.json |\n| `search_docs(query)` | Finding relevant functions/docs by keyword |\n\n### Reuse the theme's global settings\n\nBefore hardcoding colors or typography, call `list_theme_globals` to discover the theme's existing global variables and design tokens, and read them at runtime via `getThemeColors()` / `getThemeSetting()` / `getThemeTypography()` from `@ikas/bp-storefront`. You can also create new ones with `create_theme_global`. See `get_framework_guide(\"theme-globals\")`.",
|
|
213
213
|
"tags": [
|
|
214
214
|
"ai",
|
|
215
215
|
"workflow",
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
"form-handling": {
|
|
224
224
|
"title": "Form Handling",
|
|
225
225
|
"description": "How to use the form model pattern for login, registration, address, and other forms",
|
|
226
|
-
"content": "## Form Handling Pattern\n\nikas storefront uses a consistent form model pattern across all form types: `init*Form()` → `set*FormField()` → `submit*Form()`.\n\n### Form Model Structure\n\nEach form field has:\n```typescript\n{\n value: string; // Current field value\n hasError: boolean; // Whether validation failed\n message?: string; // Error message to display\n label: string; // Display label\n placeholder: string; // Input placeholder\n}\n```\n\nEach form tracks:\n```typescript\n{\n isSubmitted: boolean; // Has submit been attempted\n isSubmitting: boolean; // Is submission in progress\n isSuccess?: boolean; // Did last submit succeed\n isFailure?: boolean; // Did last submit fail\n responseMessage?: string; // Server response message\n}\n```\n\n### Login Form Example\n\n```tsx\nimport { useEffect } from \"preact/hooks\";\nimport {\n customerStore,\n initLoginForm,\n setLoginFormEmail,\n setLoginFormPassword,\n submitLoginForm,\n Router,\n} from \"@ikas/bp-storefront\";\n\n// Root export — no observer() needed, ikas runtime handles reactivity via autorun()\nexport function LoginForm() {\n const loginForm = customerStore.loginForm;\n\n useEffect(() => {\n initLoginForm(loginForm);\n }, []);\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitLoginForm(loginForm);\n if (success) {\n Router.navigate(\"/account\");\n }\n };\n\n return (\n <form onSubmit={handleSubmit}>\n {loginForm.isFailure && <div>{loginForm.responseMessage}</div>}\n\n <input\n type=\"email\"\n value={loginForm.email.value}\n onInput={(e) => setLoginFormEmail(loginForm, e.target.value)}\n />\n {loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n\n <input\n type=\"password\"\n value={loginForm.password.value}\n onInput={(e) => setLoginFormPassword(loginForm, e.target.value)}\n />\n {loginForm.password.hasError && <span>{loginForm.password.message}</span>}\n\n <button type=\"submit\" disabled={loginForm.isSubmitting}>\n {loginForm.isSubmitting ? \"Signing in...\" : \"Sign In\"}\n </button>\n </form>\n );\n}\n\nexport default LoginForm;\n```\n\n### Available Form Types\n\n| Form | Init | Setter Functions | Submit |\n|------|------|-----------------|--------|\n| Login | `initLoginForm(form)` | `setLoginFormEmail()`, `setLoginFormPassword()` | `submitLoginForm(form)` |\n| Register | `initRegisterForm(form)` | `setRegisterFormEmail()`, `setRegisterFormPassword()`, `setRegisterFormFirstName()`, `setRegisterFormLastName()` | `submitRegisterForm(form)` |\n| Forgot Password | `initForgotPasswordForm(form)` | `setForgotPasswordFormEmail()` | `submitForgotPasswordForm(form)` |\n| Address | `initAddressForm(form)` | `setAddressFormField()` for each field | `submitAddressForm(form)` |\n| Contact | `initContactForm(form)` | `setContactFormField()` for each field | `submitContactForm(form)` |\n| Account Info | `initAccountInfoForm(form)` | `setAccountInfoFormField()` for each field | `submitAccountInfoForm(form)` |\n| Newsletter | `initNewsletterSubscriptionForm(form)` | `setNewsletterEmail()` | `submitNewsletterSubscriptionForm(form)` |\n| Coupon Code |
|
|
226
|
+
"content": "## Form Handling Pattern\n\nikas storefront uses a consistent form model pattern across all form types: `init*Form()` → `set*FormField()` → `submit*Form()`.\n\n### Form Model Structure\n\nEach form field has:\n```typescript\n{\n value: string; // Current field value\n hasError: boolean; // Whether validation failed\n message?: string; // Error message to display\n label: string; // Display label\n placeholder: string; // Input placeholder\n}\n```\n\nEach form tracks:\n```typescript\n{\n isSubmitted: boolean; // Has submit been attempted\n isSubmitting: boolean; // Is submission in progress\n isSuccess?: boolean; // Did last submit succeed\n isFailure?: boolean; // Did last submit fail\n responseMessage?: string; // Server response message\n}\n```\n\n### Login Form Example\n\n```tsx\nimport { useEffect } from \"preact/hooks\";\nimport {\n customerStore,\n initLoginForm,\n setLoginFormEmail,\n setLoginFormPassword,\n submitLoginForm,\n Router,\n} from \"@ikas/bp-storefront\";\n\n// Root export — no observer() needed, ikas runtime handles reactivity via autorun()\nexport function LoginForm() {\n const loginForm = customerStore.loginForm;\n\n useEffect(() => {\n initLoginForm(loginForm);\n }, []);\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitLoginForm(loginForm);\n if (success) {\n Router.navigate(\"/account\");\n }\n };\n\n return (\n <form onSubmit={handleSubmit}>\n {loginForm.isFailure && <div>{loginForm.responseMessage}</div>}\n\n <input\n type=\"email\"\n value={loginForm.email.value}\n onInput={(e) => setLoginFormEmail(loginForm, e.target.value)}\n />\n {loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n\n <input\n type=\"password\"\n value={loginForm.password.value}\n onInput={(e) => setLoginFormPassword(loginForm, e.target.value)}\n />\n {loginForm.password.hasError && <span>{loginForm.password.message}</span>}\n\n <button type=\"submit\" disabled={loginForm.isSubmitting}>\n {loginForm.isSubmitting ? \"Signing in...\" : \"Sign In\"}\n </button>\n </form>\n );\n}\n\nexport default LoginForm;\n```\n\n### Available Form Types\n\n| Form | Init | Setter Functions | Submit |\n|------|------|-----------------|--------|\n| Login | `initLoginForm(form)` | `setLoginFormEmail()`, `setLoginFormPassword()` | `submitLoginForm(form)` |\n| Register | `initRegisterForm(form)` | `setRegisterFormEmail()`, `setRegisterFormPassword()`, `setRegisterFormFirstName()`, `setRegisterFormLastName()` | `submitRegisterForm(form)` |\n| Forgot Password | `initForgotPasswordForm(form)` | `setForgotPasswordFormEmail()` | `submitForgotPasswordForm(form)` |\n| Address | `initAddressForm(form)` | `setAddressFormField()` for each field | `submitAddressForm(form)` |\n| Contact | `initContactForm(form)` | `setContactFormField()` for each field | `submitContactForm(form)` |\n| Account Info | `initAccountInfoForm(form)` | `setAccountInfoFormField()` for each field | `submitAccountInfoForm(form)` |\n| Newsletter | `initNewsletterSubscriptionForm(form)` | `setNewsletterEmail()` | `submitNewsletterSubscriptionForm(form)` |\n| Coupon Code | `getCouponCodeForm(customerStore)` → `initCouponCodeForm(form)` | `setCouponCodeFormCouponCode(form, value)` | `submitCouponCodeForm(form)` (remove with `removeCouponCodeForm(form)`) |\n\n### Key Rules\n\n1. **Always call init first** — `init*Form()` sets up default field values, labels, and placeholders\n2. **Setter functions auto-validate** — if `form.isSubmitted` is true, calling any setter re-validates the form automatically\n3. **Check field.hasError for display** — show error messages only when `field.hasError` is true\n4. **Root components are automatically reactive** — forms use MobX observables which are automatically tracked in root components via `autorun()`. Use `observer()` only if you extract form logic into a sub-component\n5. **Submit returns boolean** — `submit*Form()` returns `true` on success, `false` on validation error or server failure",
|
|
227
227
|
"tags": [
|
|
228
228
|
"form",
|
|
229
229
|
"login",
|
|
@@ -283,7 +283,7 @@
|
|
|
283
283
|
"cart-patterns": {
|
|
284
284
|
"title": "Cart Management Patterns (Serel Reference)",
|
|
285
285
|
"description": "Serel CartPage section with CartItem sub-component, OrderSummary/CouponCode/EmptyState local components, cartStore patterns, and checkout flow",
|
|
286
|
-
"content": "## Cart Management Patterns (Serel Reference)\n\nThe serel CartPage is a section that uses local internal components (OrderSummary, CouponCode, EmptyState) and the shared `CartItem` sub-component from `src/sub-components/CartItem/`. Unlike most sections, CartPage does NOT use IkasComponentRenderer — it directly composes its UI from sub-components.\n\n### CartPage Section Structure\n\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\nimport CartItem from \"../../sub-components/CartItem\";\nimport { Props } from \"./types\";\n\nexport function CartPage({ backgroundColor, emptyCartText, ...props }: Props) {\n const cart = cartStore.cart;\n const lineItems = cart?.orderLineItems ?? [];\n const isEmpty = lineItems.length === 0;\n\n if (isEmpty) return <EmptyState text={emptyCartText} />;\n\n return (\n <section className=\"cart-page\" style={backgroundColor ? { backgroundColor } : undefined}>\n <div className=\"cart-page-inner\">\n <div className=\"cart-items\">\n {lineItems.map((item) => (\n <CartItem key={item.id} item={item} />\n ))}\n </div>\n <div className=\"cart-sidebar\">\n <CouponCode cart={cart} />\n <OrderSummary cart={cart} />\n </div>\n </div>\n </section>\n );\n}\nexport default CartPage;\n```\n\n### CartItem Sub-Component\n\nThe `CartItem` sub-component (in `src/sub-components/CartItem/`) displays a single line item with image, variant name, quantity controls, price, and remove button:\n\n```tsx\nimport {\n getOrderLineItemFormattedFinalPrice,\n getOrderLineItemFormattedUnitPrice,\n getIkasOrderLineVariantMainImage,\n getDefaultSrc,\n changeItemQuantity,\n removeItem,\n IkasOrderLineItem,\n} from \"@ikas/bp-storefront\";\nimport QuantitySelector from \"../QuantitySelector\";\n\ninterface Props { item: IkasOrderLineItem; }\n\nexport default function CartItem({ item }: Props) {\n const image = item.variant ? getIkasOrderLineVariantMainImage(item.variant) : null;\n\n return (\n <div className=\"cart-item\">\n {image && <img src={getDefaultSrc(image)} alt={item.variant?.name} />}\n <div className=\"cart-item-info\">\n <span>{item.variant?.name}</span>\n <span>{getOrderLineItemFormattedUnitPrice(item)}</span>\n </div>\n <QuantitySelector\n quantity={item.quantity}\n onIncrement={() => changeItemQuantity(item, item.quantity + 1)}\n onDecrement={() => changeItemQuantity(item, Math.max(1, item.quantity - 1))}\n />\n <span>{getOrderLineItemFormattedFinalPrice(item)}</span>\n <button onClick={() => removeItem(item)}>Remove</button>\n </div>\n );\n}\n```\n\n### OrderSummary Local Component\n\nDisplays cart totals using `getIkasOrderFormattedTotalFinalPrice(cart)` and `getIkasOrderFormattedTotalPrice(cart)`, plus a checkout button:\n\n```tsx\nimport { Router, getIkasOrderFormattedTotalFinalPrice } from \"@ikas/bp-storefront\";\n\nfunction OrderSummary({ cart }) {\n return (\n <div className=\"order-summary\">\n <span>Total: {getIkasOrderFormattedTotalFinalPrice(cart)}</span>\n <button onClick={() => Router.navigateToPage(\"CHECKOUT\")}>Checkout</button>\n </div>\n );\n}\n```\n\n### CouponCode Local Component\n\
|
|
286
|
+
"content": "## Cart Management Patterns (Serel Reference)\n\nThe serel CartPage is a section that uses local internal components (OrderSummary, CouponCode, EmptyState) and the shared `CartItem` sub-component from `src/sub-components/CartItem/`. Unlike most sections, CartPage does NOT use IkasComponentRenderer — it directly composes its UI from sub-components.\n\n### CartPage Section Structure\n\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\nimport CartItem from \"../../sub-components/CartItem\";\nimport { Props } from \"./types\";\n\nexport function CartPage({ backgroundColor, emptyCartText, ...props }: Props) {\n const cart = cartStore.cart;\n const lineItems = cart?.orderLineItems ?? [];\n const isEmpty = lineItems.length === 0;\n\n if (isEmpty) return <EmptyState text={emptyCartText} />;\n\n return (\n <section className=\"cart-page\" style={backgroundColor ? { backgroundColor } : undefined}>\n <div className=\"cart-page-inner\">\n <div className=\"cart-items\">\n {lineItems.map((item) => (\n <CartItem key={item.id} item={item} />\n ))}\n </div>\n <div className=\"cart-sidebar\">\n <CouponCode cart={cart} />\n <OrderSummary cart={cart} />\n </div>\n </div>\n </section>\n );\n}\nexport default CartPage;\n```\n\n### CartItem Sub-Component\n\nThe `CartItem` sub-component (in `src/sub-components/CartItem/`) displays a single line item with image, variant name, quantity controls, price, and remove button:\n\n```tsx\nimport {\n getOrderLineItemFormattedFinalPrice,\n getOrderLineItemFormattedUnitPrice,\n getIkasOrderLineVariantMainImage,\n getDefaultSrc,\n changeItemQuantity,\n removeItem,\n IkasOrderLineItem,\n} from \"@ikas/bp-storefront\";\nimport QuantitySelector from \"../QuantitySelector\";\n\ninterface Props { item: IkasOrderLineItem; }\n\nexport default function CartItem({ item }: Props) {\n const image = item.variant ? getIkasOrderLineVariantMainImage(item.variant) : null;\n\n return (\n <div className=\"cart-item\">\n {image && <img src={getDefaultSrc(image)} alt={item.variant?.name} />}\n <div className=\"cart-item-info\">\n <span>{item.variant?.name}</span>\n <span>{getOrderLineItemFormattedUnitPrice(item)}</span>\n </div>\n <QuantitySelector\n quantity={item.quantity}\n onIncrement={() => changeItemQuantity(item, item.quantity + 1)}\n onDecrement={() => changeItemQuantity(item, Math.max(1, item.quantity - 1))}\n />\n <span>{getOrderLineItemFormattedFinalPrice(item)}</span>\n <button onClick={() => removeItem(item)}>Remove</button>\n </div>\n );\n}\n```\n\n### OrderSummary Local Component\n\nDisplays cart totals using `getIkasOrderFormattedTotalFinalPrice(cart)` and `getIkasOrderFormattedTotalPrice(cart)`, plus a checkout button:\n\n```tsx\nimport { Router, getIkasOrderFormattedTotalFinalPrice } from \"@ikas/bp-storefront\";\n\nfunction OrderSummary({ cart }) {\n return (\n <div className=\"order-summary\">\n <span>Total: {getIkasOrderFormattedTotalFinalPrice(cart)}</span>\n <button onClick={() => Router.navigateToPage(\"CHECKOUT\")}>Checkout</button>\n </div>\n );\n}\n```\n\n### CouponCode Local Component\n\nCoupon code uses the standard form model. Get the form from `customerStore` via `getCouponCodeForm`, init it once, drive the input with `setCouponCodeFormCouponCode`, and submit/remove with the form helpers. Do NOT call `saveCart`/`apiSaveCart` or any deep `@ikas/bp-storefront/dist/...` import — the coupon has dedicated public exports:\n\n```tsx\nimport { useEffect } from \"preact/hooks\";\nimport {\n customerStore,\n getCouponCodeForm,\n initCouponCodeForm,\n setCouponCodeFormCouponCode,\n submitCouponCodeForm,\n removeCouponCodeForm,\n} from \"@ikas/bp-storefront\";\nimport { observer } from \"@ikas/component-utils\";\n\n// observer() because this sub-component reads the MobX coupon form\nconst CouponCode = observer(function CouponCode({ appliedCoupon }) {\n const couponForm = getCouponCodeForm(customerStore);\n\n useEffect(() => {\n initCouponCodeForm(couponForm);\n }, [couponForm]);\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault();\n await submitCouponCodeForm(couponForm);\n };\n\n if (appliedCoupon) {\n return (\n <div className=\"coupon\">\n <span>{appliedCoupon}</span>\n <button type=\"button\" onClick={() => removeCouponCodeForm(couponForm)}>Remove</button>\n </div>\n );\n }\n\n return (\n <form className=\"coupon\" onSubmit={handleSubmit}>\n <input\n placeholder={couponForm.couponCode?.placeholder ?? \"Coupon code\"}\n value={couponForm.couponCode?.value ?? \"\"}\n onInput={(e) =>\n setCouponCodeFormCouponCode(couponForm, (e.target as HTMLInputElement).value)\n }\n />\n <button type=\"submit\" disabled={couponForm.isSubmitting}>\n {couponForm.isSubmitting ? \"Applying...\" : \"Apply\"}\n </button>\n {couponForm.isFailure && couponForm.responseMessage && (\n <p className=\"coupon-error\">{couponForm.responseMessage}</p>\n )}\n </form>\n );\n});\n```\n\n### cartStore Usage Patterns\n\nThe `cartStore` is a MobX store available globally. Root components are automatically reactive via autorun, so reads from `cartStore.cart` trigger re-renders:\n\n```tsx\nimport { cartStore } from \"@ikas/bp-storefront\";\n\n// Always null-safe:\nconst cart = cartStore.cart;\nconst lineItems = cart?.orderLineItems ?? [];\nconst itemCount = lineItems.length;\nconst isEmpty = itemCount === 0;\n```\n\n### Key Pattern: Section Without IkasComponentRenderer\n\nCartPage demonstrates that not all sections need COMPONENT_LIST slots. Simple sections can directly compose sub-components and local components when the layout is fixed and does not need editor-level child component placement.",
|
|
287
287
|
"tags": [
|
|
288
288
|
"cart",
|
|
289
289
|
"checkout",
|
|
@@ -313,7 +313,7 @@
|
|
|
313
313
|
"header-footer-patterns": {
|
|
314
314
|
"title": "Header & Footer Patterns (Serel Reference)",
|
|
315
315
|
"description": "IkasComponentRenderer-based Header with Navbar/Announcements/CookieBar children, Footer with SocialMediaIcon children, Toast system, isHeader/isFooter flags",
|
|
316
|
-
"content": "## Header & Footer Patterns (Serel Reference)\n\nIn the serel theme, the Header and Footer are sections that use `IkasComponentRenderer` to render child components. This is the recommended production pattern.\n\n### Header Architecture\n\nThe serel Header section declares a `COMPONENT_LIST` prop with `filteredComponentIds` restricting it to three child components: **Navbar**, **Announcements**, and **CookieBar**.\n\n```tsx\nimport { IkasComponentRenderer } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\nexport function Header({ components, backgroundColor, ...props }: Props) {\n return (\n <header className=\"header\" style={backgroundColor ? { backgroundColor } : undefined}>\n <IkasComponentRenderer id=\"header-components\" components={components} parentProps={props} />\n <ToastContainer />\n </header>\n );\n}\nexport default Header;\n```\n\nConfig setup with `isHeader: true` and `filteredComponentIds`:\n```json\n{\n \"id\": \"
|
|
316
|
+
"content": "## How to build a container section (Header/Footer pattern)\n\nContainer sections are NOT self-contained. The pattern is **always** 3 steps:\n\n1. **Create each child component first.** Capture the `componentId` from each CLI response:\n ```bash\n npx ikas-component config add-component --name Navbar --type component --props '[...]'\n # → { \"success\": true, \"componentId\": \"7ojrigep-Eml9n5sN3i\", ... }\n npx ikas-component config add-component --name Announcements --type component --props '[...]'\n npx ikas-component config add-component --name CookieBar --type component --props '[...]'\n ```\n\n2. **Create the parent section** (without `filteredComponentIds` — those reference ids that don't exist yet):\n ```bash\n npx ikas-component config add-component --name Header --type section --isHeader \\\n --props '[{\"name\":\"components\",\"type\":\"COMPONENT_LIST\"},{\"name\":\"backgroundColor\",\"type\":\"COLOR\"}]'\n ```\n\n3. **Wire the parent's COMPONENT_LIST slot** to the captured child ids:\n ```bash\n npx ikas-component config update-prop --component Header --prop components \\\n --filteredComponentIds '[\"7ojrigep-Eml9n5sN3i\", \"<ANNOUNCEMENTS_ID>\", \"<COOKIEBAR_ID>\"]'\n ```\n\nIf you skip Step 3 the slot is empty and the editor will let the store owner drag in ANY component — usually not what you want. Component ids are opaque random strings; you cannot guess or derive them from names. Use `config list` to look up existing ids at any time.\n\n---\n\n## Header & Footer Patterns (Serel Reference)\n\nIn the serel theme, the Header and Footer are sections that use `IkasComponentRenderer` to render child components. This is the recommended production pattern.\n\n### Header Architecture\n\nThe serel Header section declares a `COMPONENT_LIST` prop with `filteredComponentIds` restricting it to three child components: **Navbar**, **Announcements**, and **CookieBar**.\n\n```tsx\nimport { IkasComponentRenderer } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\nexport function Header({ components, backgroundColor, ...props }: Props) {\n return (\n <header className=\"header\" style={backgroundColor ? { backgroundColor } : undefined}>\n <IkasComponentRenderer id=\"header-components\" components={components} parentProps={props} />\n <ToastContainer />\n </header>\n );\n}\nexport default Header;\n```\n\nConfig setup with `isHeader: true` and `filteredComponentIds`:\n```json\n{\n \"id\": \"7ojrigep-IBBg5nI1PC\",\n \"name\": \"Header\",\n \"type\": \"section\",\n \"isHeader\": true,\n \"props\": [\n {\n \"name\": \"components\",\n \"type\": \"COMPONENT_LIST\",\n \"filteredComponentIds\": [\"<NAVBAR_ID>\", \"<ANNOUNCEMENTS_ID>\", \"<COOKIEBAR_ID>\"]\n }\n ]\n}\n```\n\nThe `isHeader: true` flag ensures this section appears on every page automatically.\n\n### Toast Notification System\n\nThe serel Header hosts a `ToastContainer` component. The `useToast` hook provides a global notification system:\n\n```tsx\nimport { useToast } from \"../../hooks/useToast\";\n\n// In any component:\nconst { showToast } = useToast();\nshowToast({ message: \"Added to cart!\", type: \"success\" });\n```\n\nThe ToastContainer renders as a portal to ensure toasts appear above all content. Toasts stack vertically and auto-dismiss after a timeout.\n\n### Navbar Child Component\n\nThe Navbar child component handles: logo display, navigation links (`LIST_OF_LINK`), cart badge (reads `cartStore`), account icon (reads `customerStore`), search functionality, and a mobile hamburger menu. It also has its own `COMPONENT_LIST` slot for product search results using CardProductName/CardProductVariants/CardProductPrice with `privateVarMap` for the product variable.\n\n### Announcements Child Component\n\nThe Announcements child component uses `IkasThemeSlider` for a rotating announcement bar. It has its own child component slot for individual Announcement items.\n\n### CookieBar Child Component\n\nThe CookieBar manages cookie consent with accept/decline actions and persists the choice to localStorage.\n\n### Footer Architecture\n\nThe serel Footer section uses `IkasComponentRenderer` with a `COMPONENT_LIST` filtered to **SocialMediaIcon** children:\n\n```tsx\nimport { IkasComponentRenderer } from \"@ikas/bp-storefront\";\nimport { Props } from \"./types\";\n\nexport function Footer({ components, backgroundColor, ...props }: Props) {\n return (\n <footer className=\"footer\" style={backgroundColor ? { backgroundColor } : undefined}>\n <div className=\"footer-inner\">\n {/* Logo, link columns, contact info */}\n <div className=\"footer-social\">\n <IkasComponentRenderer id=\"footer-components\" components={components} parentProps={props} />\n </div>\n {/* Copyright */}\n </div>\n </footer>\n );\n}\nexport default Footer;\n```\n\nConfig with `isFooter: true`:\n```json\n{\n \"id\": \"7ojrigep-fTZb2qP9aV\",\n \"name\": \"Footer\",\n \"type\": \"section\",\n \"isFooter\": true,\n \"props\": [\n {\n \"name\": \"components\",\n \"type\": \"COMPONENT_LIST\",\n \"filteredComponentIds\": [\"<SOCIALMEDIAICON_ID>\"]\n }\n ]\n}\n```\n\n### Key Patterns\n\n- Headers and footers use `IkasComponentRenderer` with `filteredComponentIds` for controlled child composition\n- `isHeader: true` / `isFooter: true` flags in config make them appear on all pages\n- Root exports are automatically reactive (cartStore/customerStore tracked by autorun)\n- Toast system lives in the Header for global accessibility\n- `parentProps` passes section data to child components\n- The Navbar child component itself nests additional child components via its own COMPONENT_LIST\n\n### Mega-Menu / MenuItem (When Per-Link Items Are Rich)\n\nIf the header needs items with metadata beyond `href + label` (image, badge, column count, etc.), `LIST_OF_LINK` cannot carry it. Build a `MenuItem` child and wire it into a `COMPONENT_LIST` slot exactly like Navbar/Announcements/CookieBar above. For the full pick-the-right-prop-type tree (and when `LIST_OF_LINK` IS sufficient), see `get_migration_guide(\"link-prop-decision-guide\")` and `get_migration_guide(\"component-composition-decision-guide\")`.\n",
|
|
317
317
|
"tags": [
|
|
318
318
|
"header",
|
|
319
319
|
"footer",
|
|
@@ -378,7 +378,7 @@
|
|
|
378
378
|
"navigation-patterns": {
|
|
379
379
|
"title": "Navigation & Routing Patterns",
|
|
380
380
|
"description": "Router.navigate, page navigation, href getters, breadcrumbs, and query parameters",
|
|
381
|
-
"content": "## Navigation & Routing Patterns\n\nikas provides a `Router` utility and various href getter functions for navigation.\n\n### Basic Navigation\n\n```tsx\nimport { Router } from \"@ikas/bp-storefront\";\n\n// Navigate to a URL\nRouter.navigate(\"/products\");\n\n// Navigate with options\nRouter.navigate(\"/products\", true); // shallow navigation (no full page reload)\nRouter.navigate(\"/products\", false, true); // open in new tab\n// Signature: Router.navigate(path: string, shallow?: boolean, openInNewTab?: boolean)\n\n// Navigate to a named page\nRouter.navigateToPage(\"LOGIN\");\nRouter.navigateToPage(\"REGISTER\");\nRouter.navigateToPage(\"FORGOT_PASSWORD\");\nRouter.navigateToPage(\"ACCOUNT\");\nRouter.navigateToPage(\"CART\");\nRouter.navigateToPage(\"CHECKOUT\");\n\n// Navigate with options\nRouter.navigateToPage(\"CATEGORY\", {\n queryParams: { sort: \"price_asc\" },\n shallow: false,\n newTab: false,\n});\n\n// Go back\nRouter.goBack();\n```\n\n### Page Type Constants\n\nAvailable page types for `navigateToPage()`:\n- `INDEX` — Home page\n- `CATEGORY` — Category listing\n- `PRODUCT_DETAIL` — Product detail\n- `BLOG` — Blog listing\n- `BLOG` — Blog post detail\n- `CART` — Shopping cart\n- `CHECKOUT` — Checkout flow\n- `LOGIN` — Login page\n- `REGISTER` — Registration page\n- `FORGOT_PASSWORD` — Password recovery\n- `ACCOUNT` — Customer account\n- `SEARCH` — Search results\n- `CUSTOM` — Custom pages\n\n### Href Getter Functions\n\nThese return URL strings for use in `<a href>` tags:\n\n```tsx\nimport {\n getSelectedProductVariantHref,\n getIkasCategoryHref,\n getIkasOrderHref,\n getIkasBlogHref,\n getIkasBrandHref,\n} from \"@ikas/bp-storefront\";\n\n// Product link\n<a href={getSelectedProductVariantHref(product)}>{product.name}</a>\n\n// Category link\n<a href={getIkasCategoryHref(category)}>{category.name}</a>\n\n// Order link\n<a href={getIkasOrderHref(order)}>Order #{order.orderNumber}</a>\n\n// Blog link\n<a href={getIkasBlogHref(blog)}>{blog.title}</a>\n\n// Brand link\n<a href={getIkasBrandHref(brand)}>{brand.name}</a>\n```\n\n### Breadcrumbs\n\n```tsx\nimport {\n getCategoryPath,\n getProductCategoryPath,\n getIkasCategoryHref,\n} from \"@ikas/bp-storefront\";\n\n// For category pages\nconst categoryPath = getCategoryPath(category);\n\n// For product pages\nconst productPath = getProductCategoryPath(product);\n\n<nav className=\"breadcrumbs\">\n <a href=\"/\">Home</a>\n {categoryPath?.map((cat, i) => (\n <span key={cat.id}>\n <span className=\"separator\">/</span>\n <a href={getIkasCategoryHref(cat)}>{cat.name}</a>\n </span>\n ))}\n</nav>\n```\n\n### Using Links vs Navigation\n\n- Use `<a href={getXxxHref(...)}>` for standard links (SEO-friendly, right-click works)\n- Use `Router.navigate()` for programmatic navigation after form submission or actions\n- Use `Router.navigateToPage()` for navigating to known page types\n- Use `Router.goBack()` for back buttons",
|
|
381
|
+
"content": "## Navigation & Routing Patterns\n\nikas provides a `Router` utility and various href getter functions for navigation.\n\n### Basic Navigation\n\n```tsx\nimport { Router } from \"@ikas/bp-storefront\";\n\n// Navigate to a URL\nRouter.navigate(\"/products\");\n\n// Navigate with options\nRouter.navigate(\"/products\", true); // shallow navigation (no full page reload)\nRouter.navigate(\"/products\", false, true); // open in new tab\n// Signature: Router.navigate(path: string, shallow?: boolean, openInNewTab?: boolean)\n\n// Navigate to a named page\nRouter.navigateToPage(\"LOGIN\");\nRouter.navigateToPage(\"REGISTER\");\nRouter.navigateToPage(\"FORGOT_PASSWORD\");\nRouter.navigateToPage(\"ACCOUNT\");\nRouter.navigateToPage(\"CART\");\nRouter.navigateToPage(\"CHECKOUT\");\n\n// Navigate with options\nRouter.navigateToPage(\"CATEGORY\", {\n queryParams: { sort: \"price_asc\" },\n shallow: false,\n newTab: false,\n});\n\n// Go back\nRouter.goBack();\n```\n\n### Page Type Constants\n\nAvailable page types for `navigateToPage()`:\n- `INDEX` — Home page\n- `CATEGORY` — Category listing\n- `PRODUCT_DETAIL` — Product detail\n- `BLOG` — Blog listing\n- `BLOG` — Blog post detail\n- `CART` — Shopping cart\n- `CHECKOUT` — Checkout flow\n- `LOGIN` — Login page\n- `REGISTER` — Registration page\n- `FORGOT_PASSWORD` — Password recovery\n- `ACCOUNT` — Customer account\n- `SEARCH` — Search results\n- `CUSTOM` — Custom pages\n\n### Href Getter Functions\n\nThese return URL strings for use in `<a href>` tags:\n\n```tsx\nimport {\n getSelectedProductVariantHref,\n getIkasCategoryHref,\n getIkasOrderHref,\n getIkasBlogHref,\n getIkasBrandHref,\n} from \"@ikas/bp-storefront\";\n\n// Product link\n<a href={getSelectedProductVariantHref(product)}>{product.name}</a>\n\n// Category link\n<a href={getIkasCategoryHref(category)}>{category.name}</a>\n\n// Order link\n<a href={getIkasOrderHref(order)}>Order #{order.orderNumber}</a>\n\n// Blog link\n<a href={getIkasBlogHref(blog)}>{blog.title}</a>\n\n// Brand link\n<a href={getIkasBrandHref(brand)}>{brand.name}</a>\n```\n\n### Breadcrumbs\n\n```tsx\nimport {\n getCategoryPath,\n getProductCategoryPath,\n getIkasCategoryHref,\n} from \"@ikas/bp-storefront\";\n\n// For category pages\nconst categoryPath = getCategoryPath(category);\n\n// For product pages\nconst productPath = getProductCategoryPath(product);\n\n<nav className=\"breadcrumbs\">\n <a href=\"/\">Home</a>\n {categoryPath?.map((cat, i) => (\n <span key={cat.id}>\n <span className=\"separator\">/</span>\n <a href={getIkasCategoryHref(cat)}>{cat.name}</a>\n </span>\n ))}\n</nav>\n```\n\n### Using Links vs Navigation\n\n- Use `<a href={getXxxHref(...)}>` for standard links (SEO-friendly, right-click works)\n- Use `Router.navigate()` for programmatic navigation after form submission or actions\n- Use `Router.navigateToPage()` for navigating to known page types\n- Use `Router.goBack()` for back buttons\n\n### Rich Navigation Items (When LIST_OF_LINK Is Insufficient)\n\n`LIST_OF_LINK` gives you `IkasNavigationLinkList` → an array of `IkasNavigationLink` items with `{ href, label, subLinks, openInNewTab, itemId, type }`. There is **no slot for an image, color, icon, column count, or any other custom field per link**.\n\nIf the navigation needs richer per-item data — a mega-menu with thumbnails, colored category chips, multi-column submenus, badges, custom icons — `LIST_OF_LINK` is the wrong tool. Build a dedicated component instead:\n\n```bash\nnpx ikas-component config add-component --name \"MenuItem\" --type component \\\n --props '[{\"name\":\"link\",\"type\":\"LINK\"},{\"name\":\"image\",\"type\":\"IMAGE\"},{\"name\":\"title\",\"type\":\"TEXT\"}]'\n```\n\nThen wire the parent header/navbar with a `COMPONENT_LIST` prop and `filteredComponentIds: [\"<MenuItem id>\"]`. Each menu item becomes a self-contained component instance with its own link, image, and title.\n\nSee `get_framework_guide(\"header-footer-patterns\")` for the COMPONENT_LIST recipe and `get_migration_guide(\"custom-data-conversion\")` for the broader enum-vs-component decision framework (the mega-menu case is one of the worked examples).\n",
|
|
382
382
|
"tags": [
|
|
383
383
|
"navigation",
|
|
384
384
|
"router",
|
|
@@ -528,6 +528,40 @@
|
|
|
528
528
|
"offline",
|
|
529
529
|
"CLI"
|
|
530
530
|
]
|
|
531
|
+
},
|
|
532
|
+
"image-handling": {
|
|
533
|
+
"title": "Image Handling",
|
|
534
|
+
"description": "How to render images from IkasImage, IkasProduct variants, and IkasProductImage wrappers. Covers the canonical variant-to-image chain, all-variant-images mapping, non-product images, background images, and responsive srcsets.",
|
|
535
|
+
"content": "## Image Handling\n\nThe canonical recipe for rendering an image. The product-variant case is 4 hops because each step carries metadata the next doesn't (variant selection → that variant's image set → metadata wrapper → CDN-ready image):\n\n```tsx\nimport {\n getSelectedProductVariant,\n getProductVariantMainImage,\n getDefaultSrc,\n getSrc,\n createMediaSrcset,\n IkasImage,\n} from \"@ikas/bp-storefront\";\n\nconst variant = getSelectedProductVariant(product); // IkasProductVariant | undefined\nconst productImage = variant && getProductVariantMainImage(variant); // IkasProductImage | undefined\nconst image = productImage?.image; // IkasImage | undefined — what CDN helpers need\n\nif (image) {\n return (\n <>\n <img src={getDefaultSrc(image)} alt={product.name} /> {/* default size */}\n <img src={getSrc(image, { width: 400 })} alt=\"\" /> {/* explicit width */}\n <img srcSet={createMediaSrcset(image)} alt=\"\" /> {/* responsive srcset */}\n </>\n );\n}\n```\n\n## All Variant Images (gallery)\n\nMap over `variant.images` and unwrap each `.image`. `IkasProductImage` carries metadata like `order` and `isMain`; the actual `IkasImage` (what the CDN helpers need) is the `.image` property on each.\n\n```tsx\nconst images: IkasImage[] = (variant?.images ?? [])\n .map((pi) => pi.image)\n .filter((img): img is IkasImage => img != null);\n```\n\n## Non-Product Images (logo, banner, IMAGE prop)\n\nThese skip the variant hop — they're already `IkasImage`:\n\n```tsx\n{props.logo && <img src={getDefaultSrc(props.logo)} alt=\"\" />}\n```\n\n## Background Images\n\n```tsx\n<div style={{ backgroundImage: image ? `url(${getDefaultSrc(image)})` : \"none\" }} />\n```\n\n## See Also\n\n- `get_framework_guide(\"common-pitfalls\")` §19 — full property-rename catalog (`selectedVariant`, `mainImage`, `href`, `checkoutUrl`)\n- `get_section_template(\"image-handling\")` — full Product Detail section with gallery + zoom + srcset\n- `get_function_doc(\"getDefaultSrc\")` / `get_function_doc(\"getSrc\")` / `get_function_doc(\"createMediaSrcset\")` — exact signatures\n- `get_type_definition(\"IkasImage\")` / `get_type_definition(\"IkasProductImage\")` — exact shapes",
|
|
536
|
+
"tags": [
|
|
537
|
+
"image",
|
|
538
|
+
"rendering",
|
|
539
|
+
"getDefaultSrc",
|
|
540
|
+
"getSrc",
|
|
541
|
+
"createMediaSrcset",
|
|
542
|
+
"IkasImage",
|
|
543
|
+
"IkasProductImage",
|
|
544
|
+
"variant",
|
|
545
|
+
"srcset",
|
|
546
|
+
"gallery"
|
|
547
|
+
]
|
|
548
|
+
},
|
|
549
|
+
"theme-globals": {
|
|
550
|
+
"title": "Theme Global Settings & Design Tokens",
|
|
551
|
+
"description": "Read and create the theme's global variables and design tokens (colors, typography, breakpoints, keyframes, color schemes) from code components",
|
|
552
|
+
"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.\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-component 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.",
|
|
553
|
+
"tags": [
|
|
554
|
+
"theme",
|
|
555
|
+
"global variables",
|
|
556
|
+
"design tokens",
|
|
557
|
+
"colors",
|
|
558
|
+
"typography",
|
|
559
|
+
"breakpoints",
|
|
560
|
+
"keyframes",
|
|
561
|
+
"color schemes",
|
|
562
|
+
"getThemeSetting",
|
|
563
|
+
"getThemeColors"
|
|
564
|
+
]
|
|
531
565
|
}
|
|
532
566
|
}
|
|
533
|
-
}
|
|
567
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components": [
|
|
3
|
+
{
|
|
4
|
+
"id": "my-theme-header",
|
|
5
|
+
"name": "HeaderSection",
|
|
6
|
+
"type": "section",
|
|
7
|
+
"isHeader": true,
|
|
8
|
+
"entry": "./src/components/HeaderSection/index.tsx",
|
|
9
|
+
"styles": "./src/components/HeaderSection/styles.css",
|
|
10
|
+
"props": [
|
|
11
|
+
{ "name": "logo", "displayName": "Logo", "type": "IMAGE", "required": true },
|
|
12
|
+
{ "name": "logoWidth", "displayName": "Logo Width (px)", "type": "NUMBER", "required": false, "defaultValue": 120 },
|
|
13
|
+
{ "name": "mobileLogoWidth", "displayName": "Mobile Logo Width (px)", "type": "NUMBER", "required": false, "defaultValue": 80 },
|
|
14
|
+
{ "name": "menuLinks", "displayName": "Menu Links", "type": "COMPONENT_LIST", "required": false, "filteredComponentIds": ["my-theme-menu-link"] },
|
|
15
|
+
{ "name": "rightIcons", "displayName": "Right Icons", "type": "COMPONENT_LIST", "required": false, "filteredComponentIds": ["my-theme-icon-link"] },
|
|
16
|
+
{ "name": "announcement", "displayName": "Announcement", "type": "TEXT", "required": false },
|
|
17
|
+
{ "name": "announcementLink", "displayName": "Announcement Link", "type": "LINK", "required": false },
|
|
18
|
+
{ "name": "announcementBg", "displayName": "Announcement Background", "type": "COLOR", "required": false },
|
|
19
|
+
{ "name": "searchPlaceholder", "displayName": "Search Placeholder", "type": "TEXT", "required": false, "defaultValue": "Search..." },
|
|
20
|
+
{ "name": "navBackground", "displayName": "Nav Background", "type": "COLOR", "required": false },
|
|
21
|
+
{ "name": "stickyNav", "displayName": "Sticky Navigation", "type": "BOOLEAN", "required": false, "defaultValue": true },
|
|
22
|
+
{ "name": "searchProducts", "displayName": "Search Popular Products", "type": "PRODUCT_LIST", "required": false }
|
|
23
|
+
],
|
|
24
|
+
"propGroups": [
|
|
25
|
+
{ "id": "appearance", "name": "Appearance" },
|
|
26
|
+
{ "id": "search", "name": "Search" }
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "my-theme-menu-link",
|
|
31
|
+
"name": "MenuLink",
|
|
32
|
+
"type": "component",
|
|
33
|
+
"entry": "./src/components/MenuLink/index.tsx",
|
|
34
|
+
"styles": "./src/components/MenuLink/styles.css",
|
|
35
|
+
"props": [
|
|
36
|
+
{ "name": "link", "displayName": "Link", "type": "LINK", "required": true },
|
|
37
|
+
{ "name": "icon", "displayName": "Icon", "type": "IMAGE", "required": false },
|
|
38
|
+
{ "name": "color", "displayName": "Text Color", "type": "COLOR", "required": false },
|
|
39
|
+
{ "name": "subLinks", "displayName": "Sub Links", "type": "LIST_OF_LINK", "required": false }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "my-theme-icon-link",
|
|
44
|
+
"name": "IconLink",
|
|
45
|
+
"type": "component",
|
|
46
|
+
"entry": "./src/components/IconLink/index.tsx",
|
|
47
|
+
"styles": "./src/components/IconLink/styles.css",
|
|
48
|
+
"props": [
|
|
49
|
+
{ "name": "image", "displayName": "Icon Image", "type": "IMAGE", "required": true },
|
|
50
|
+
{ "name": "link", "displayName": "Link", "type": "LINK", "required": true },
|
|
51
|
+
{ "name": "aspectRatio", "displayName": "Aspect Ratio", "type": "TEXT", "required": false, "defaultValue": "1/1" }
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useState } from "preact/hooks";
|
|
2
|
+
import { getDefaultSrc, IkasComponentRenderer } from "@ikas/bp-storefront";
|
|
3
|
+
import { Props } from "./types";
|
|
4
|
+
|
|
5
|
+
export default function HeaderSection({
|
|
6
|
+
logo,
|
|
7
|
+
logoWidth,
|
|
8
|
+
mobileLogoWidth,
|
|
9
|
+
menuLinks,
|
|
10
|
+
rightIcons,
|
|
11
|
+
announcement,
|
|
12
|
+
announcementLink,
|
|
13
|
+
announcementBg,
|
|
14
|
+
searchPlaceholder,
|
|
15
|
+
navBackground,
|
|
16
|
+
stickyNav,
|
|
17
|
+
...props
|
|
18
|
+
}: Props) {
|
|
19
|
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<header className={`header ${stickyNav ? "header--sticky" : ""}`}>
|
|
23
|
+
{announcement && (
|
|
24
|
+
<div className="announcement-bar" style={{ backgroundColor: announcementBg || "#000" }}>
|
|
25
|
+
{announcementLink ? (
|
|
26
|
+
<a href={announcementLink.href} className="announcement-link">{announcement}</a>
|
|
27
|
+
) : (
|
|
28
|
+
<span>{announcement}</span>
|
|
29
|
+
)}
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
|
|
33
|
+
<nav className="nav-bar" style={{ backgroundColor: navBackground || "#fff" }}>
|
|
34
|
+
<div className="nav-inner">
|
|
35
|
+
<button className="mobile-menu-btn" onClick={() => setMobileMenuOpen(!mobileMenuOpen)}>
|
|
36
|
+
<span className="hamburger-icon" />
|
|
37
|
+
</button>
|
|
38
|
+
|
|
39
|
+
{logo && (
|
|
40
|
+
<a href="/" className="logo-link" style={{ "--logo-w": `${logoWidth || 120}px`, "--logo-mw": `${mobileLogoWidth || 80}px` }}>
|
|
41
|
+
<img src={getDefaultSrc(logo)} alt="Logo" className="logo-image" />
|
|
42
|
+
</a>
|
|
43
|
+
)}
|
|
44
|
+
|
|
45
|
+
<div className="menu-links">
|
|
46
|
+
<IkasComponentRenderer id="menu-links" components={menuLinks as any[]} parentProps={props} />
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div className="right-icons">
|
|
50
|
+
<IkasComponentRenderer id="right-icons" components={rightIcons as any[]} parentProps={props} />
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</nav>
|
|
54
|
+
|
|
55
|
+
{mobileMenuOpen && (
|
|
56
|
+
<div className="mobile-menu-overlay" onClick={() => setMobileMenuOpen(false)}>
|
|
57
|
+
<div className="mobile-menu" onClick={(e) => e.stopPropagation()}>
|
|
58
|
+
<IkasComponentRenderer id="mobile-menu-links" components={menuLinks as any[]} parentProps={props} />
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
</header>
|
|
63
|
+
);
|
|
64
|
+
}
|