@ikas/code-components-mcp 0.17.0 → 0.19.0
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 +68 -2
- package/data/section-templates.json +214 -0
- package/data/storefront-api.json +808 -49
- package/data/storefront-types.json +1 -1
- package/dist/index.js +82 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/data/framework.json
CHANGED
|
@@ -136,8 +136,14 @@
|
|
|
136
136
|
"common-pitfalls": {
|
|
137
137
|
"title": "Common Pitfalls",
|
|
138
138
|
"description": "Frequent mistakes LLMs and developers make when building ikas code components",
|
|
139
|
-
"content": "## Common Pitfalls\n\nThese are the most common mistakes when building ikas code components. Avoid them for correct, working code.\n\n### 1. Observer Naming\n\n**Wrong** — arrow function loses display name:\n```tsx\nconst MyComponent = observer(() => {\n return <div>...</div>;\n});\nexport default MyComponent;\n```\n\n**Correct** — named function expression:\n```tsx\nconst MyComponent = observer(function MyComponent({ title }: Props) {\n return <div>{title}</div>;\n});\nexport default MyComponent;\n```\n\nAlways use `const X = observer(function X() {...}); export default X;`. This ensures proper display names in React DevTools and avoids MobX warnings.\n\n### 2. 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### 3. 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### 4. 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?.src && <img src={props.heroImage.src} />}\n```\n\n### 5. 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### 6. 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`, and `getProductVariantMainImage`.",
|
|
140
|
-
"tags": ["pitfalls", "gotchas", "mistakes", "observer", "mutation", "css", "types"]
|
|
139
|
+
"content": "## Common Pitfalls\n\nThese are the most common mistakes when building ikas code components. Avoid them for correct, working code.\n\n### 1. Observer Naming\n\n**Wrong** — arrow function loses display name:\n```tsx\nconst MyComponent = observer(() => {\n return <div>...</div>;\n});\nexport default MyComponent;\n```\n\n**Correct** — named function expression:\n```tsx\nconst MyComponent = observer(function MyComponent({ title }: Props) {\n return <div>{title}</div>;\n});\nexport default MyComponent;\n```\n\nAlways use `const X = observer(function X() {...}); export default X;`. This ensures proper display names in React DevTools and avoids MobX warnings.\n\n### 2. 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### 3. 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### 4. 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?.src && <img src={props.heroImage.src} />}\n```\n\n### 5. 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### 6. 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`, and `getProductVariantMainImage`.\n\n### 7. 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### 8. ProductList/BlogList Data Access\n\n`productList.products` is the correct way to access products in a product list (NOT `productList.data`). Similarly, `blogList.blogs` for blogs:\n\n```tsx\n// CORRECT\nconst products = productList?.products ?? [];\nconst blogs = blogList?.blogs ?? [];\n\n// WRONG — .data does not exist on these types\nconst products = productList?.data ?? [];\n```\n\n### 9. 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### 10. 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={props.image.src} />\n{props.links.links.map(...)}\n\n// CORRECT — safe access with defaults\n<h1>{props.title ?? \"Default Title\"}</h1>\n{props.image?.src && <img src={props.image.src} />}\n{(props.links?.links ?? []).map(...)}\n```\n\n### 11. 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### 12. 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.",
|
|
140
|
+
"tags": ["pitfalls", "gotchas", "mistakes", "observer", "mutation", "css", "types", "null-safety", "forms", "events", "parameters"]
|
|
141
|
+
},
|
|
142
|
+
"ai-workflow": {
|
|
143
|
+
"title": "AI Agent Workflow for Building ikas Components",
|
|
144
|
+
"description": "Step-by-step guide for AI coding agents building ikas storefront components using CLI commands and MCP tools",
|
|
145
|
+
"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### Step 1: Create the Component Scaffold\n\nUse the `ikas config add-component` CLI command:\n```bash\nikas config add-component --name \"HeroSection\" --type section\n```\n\nThis creates the component directory with `index.tsx`, `types.ts`, `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\", \"files\": [...]}\n```\n\n### Step 2: Add Props via CLI\n\nUse `ikas config add-prop` for each prop:\n```bash\nikas config add-prop --component \"HeroSection\" --name \"title\" --displayName \"Title\" --type TEXT --required\nikas config add-prop --component \"HeroSection\" --name \"backgroundImage\" --displayName \"Background Image\" --type IMAGE\nikas config add-prop --component \"HeroSection\" --name \"showButton\" --displayName \"Show Button\" --type BOOLEAN\n```\n\nEach command updates `ikas.config.json` AND regenerates `types.ts` automatically. Never edit `ikas.config.json` or `types.ts` manually — the CLI keeps them in sync.\n\n### Step 3: Get Reference Material\n\nBefore writing component code, use MCP tools to get the right patterns:\n- `get_section_template(sectionType)` — Get a starter template for common section types (product-detail, cart, login, header, footer, etc.)\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 4: Write the Component Code\n\nEdit `src/components/{ComponentName}/index.tsx` with the implementation. Key rules:\n- Import props from `./types` (auto-generated in Step 2)\n- Import storefront functions from `@ikas/bp-storefront`\n- Import `observer` from `@ikas/component-utils` when using stores\n- Use `const X = observer(function X(props) {...}); export default X;` pattern\n- Use optional chaining for all props: `props.title ?? \"Default\"`\n- Use `as unknown as boolean` cast for functions like `hasProductStock`, `isAddToCartEnabled`\n\n### Step 5: 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 6: Verify with Type Checking\n\nRun the lightweight type checker:\n```bash\nikas 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 7: 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 `ikas check --json`\n\n**Common error fixes:**\n- `Property 'x' does not exist on type 'Props'` — Prop wasn't added via CLI. Run `ikas 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 8: Full Build Validation\n\nOnce type checking passes, run the full build:\n```bash\nikas build\n```\n\nThis runs type checking + esbuild compilation + CSS scoping.\n\n### Quick Reference: CLI Commands\n\n| Command | Purpose |\n|---------|--------|\n| `ikas config add-component --name X --type section` | Create component scaffold |\n| `ikas config add-prop --component X --name y --displayName Y --type TEXT` | Add a prop |\n| `ikas config update-prop --component X --prop y --required true` | Update a prop |\n| `ikas config remove-prop --component X --prop y` | Remove a prop |\n| `ikas config remove-component --name X` | Remove a component |\n| `ikas config list` | List all components and props |\n| `ikas check --json` | Type-check with JSON output |\n| `ikas 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 starter code |\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 |",
|
|
146
|
+
"tags": ["ai", "workflow", "agent", "cli", "steps", "guide", "automation"]
|
|
141
147
|
},
|
|
142
148
|
"form-handling": {
|
|
143
149
|
"title": "Form Handling",
|
|
@@ -150,6 +156,66 @@
|
|
|
150
156
|
"description": "How to handle async operations, loading states, and store data in ikas components",
|
|
151
157
|
"content": "## Async Data & Loading Patterns\n\n### 1. Store State is Null Until Initialized\n\nStore data (`cartStore.cart`, `customerStore.customer`) is `null` until the store initializes. Use `observer()` and null-safe access:\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\nconst CartBadge = observer(function CartBadge() {\n // cart is null until initialized — use optional chaining\n const itemCount = cartStore.cart?.orderLineItems.length ?? 0;\n return <span className=\"cart-badge\">{itemCount}</span>;\n});\n```\n\n### 2. Loading Pattern for Async Operations\n\nUse `useState` for local loading flags. Always use `try/finally` to clear the loading state:\n\n```tsx\nimport { useState } from \"preact/hooks\";\nimport { addItemToCart } from \"@ikas/bp-storefront\";\n\nconst [isLoading, setIsLoading] = useState(false);\n\nconst handleAddToCart = async () => {\n if (isLoading) return; // prevent double-click\n setIsLoading(true);\n try {\n await addItemToCart(variant, product, 1);\n } finally {\n setIsLoading(false);\n }\n};\n\n// In JSX:\n<button disabled={isLoading} onClick={handleAddToCart}>\n {isLoading ? \"Adding...\" : \"Add to Cart\"}\n</button>\n```\n\n### 3. Cart Operation Results\n\n`addItemToCart` returns a result object. Check for validation errors:\n\n```tsx\nconst result = await addItemToCart(variant, product, 1);\nif (result.success) {\n // Item added successfully\n} else if (result.validationError === \"INSUFFICIENT_STOCK\") {\n // Not enough stock\n} else if (result.validationError === \"INVALID_PRODUCT_OPTION_VALUES\") {\n // Variant options not fully selected\n}\n```\n\n### 4. Form Submission Results\n\nForm submit functions return `boolean` and set status flags on the form:\n\n```tsx\nconst success = await submitLoginForm(loginForm);\nif (success) {\n // loginForm.isSuccess is true\n Router.navigate(\"/account\");\n} else {\n // loginForm.isFailure is true\n // loginForm.responseMessage has the error message\n console.log(loginForm.responseMessage);\n}\n```\n\n### 5. Observer Auto Re-rendering\n\nStore updates automatically trigger re-renders in `observer()` components. You do NOT need manual state management for store-derived values:\n\n```tsx\n// WRONG — unnecessary useState for store data\nconst [cartItems, setCartItems] = useState([]);\nuseEffect(() => { setCartItems(cartStore.cart?.orderLineItems ?? []); }, [cartStore.cart]);\n\n// CORRECT — observer handles re-rendering automatically\nconst CartList = observer(function CartList() {\n const items = cartStore.cart?.orderLineItems ?? [];\n return (\n <div>\n {items.map((item) => (\n <div key={item.id}>{item.variant?.name}</div>\n ))}\n </div>\n );\n});\n```\n\n### 6. Pagination / Data Fetching\n\nList stores (blog, brand, category) use async pagination functions:\n\n```tsx\nimport { hasBlogListNextPage, getBlogListNextPage } from \"@ikas/bp-storefront\";\n\n// Check if more pages exist\nif (hasBlogListNextPage(blogList)) {\n // Fetch next page — mutates blogList in place, observer re-renders\n await getBlogListNextPage(blogList);\n}\n```\n\nThese functions mutate the list model in place. If your component uses `observer()`, it will re-render automatically when new data arrives.",
|
|
152
158
|
"tags": ["async", "loading", "error", "stores", "cart", "fetch"]
|
|
159
|
+
},
|
|
160
|
+
"product-detail-patterns": {
|
|
161
|
+
"title": "Product Detail Patterns",
|
|
162
|
+
"description": "Variant selection, pricing, stock checks, add-to-cart, favorites, and image gallery patterns for product detail pages",
|
|
163
|
+
"content": "## Product Detail Patterns\n\nA product detail section is one of the most complex sections in an e-commerce theme. It combines variant selection, pricing, stock checks, cart operations, favorites, and image display.\n\n### Core Flow\n\n1. Get the selected variant: `getSelectedProductVariant(product)`\n2. Display variant options: `getDisplayedProductVariantTypes(product)`\n3. Handle variant selection: `selectVariantValue(product, variantValue)` (mutates in place)\n4. Check stock: `hasProductVariantStock(variant)` or `hasProductStock(product)`\n5. Show pricing: `getProductVariantFormattedFinalPrice(variant)`, `getProductVariantFormattedSellPrice(variant)`\n6. Add to cart: `addItemToCart(variant, product, quantity)`\n7. Toggle favorite: `addIkasProductToFavorites(product)` / `removeIkasProductFromFavorites(product)`\n\n### Variant Selection Pattern\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport {\n IkasProduct,\n getSelectedProductVariant,\n getDisplayedProductVariantTypes,\n selectVariantValue,\n hasProductVariantStock,\n getProductVariantFormattedFinalPrice,\n getProductVariantFormattedSellPrice,\n hasProductVariantDiscount,\n getProductVariantDiscountPercentage,\n} from \"@ikas/bp-storefront\";\n\n// Inside observer component:\nconst variant = getSelectedProductVariant(product);\nconst variantTypes = getDisplayedProductVariantTypes(product);\n\n// Render variant options\n{variantTypes.map((dvt) => (\n <div key={dvt.variantType?.name}>\n <span>{dvt.variantType?.name}</span>\n {dvt.variantValues.map((dvv) => (\n <button\n key={dvv.variantValue?.name}\n className={dvv.isSelected ? \"selected\" : \"\"}\n disabled={!dvv.isSelectable}\n onClick={() => selectVariantValue(product, dvv.variantValue)}\n >\n {dvv.variantValue?.name}\n </button>\n ))}\n </div>\n))}\n```\n\n### Pricing Display\n\n```tsx\nconst hasDiscount = hasProductVariantDiscount(variant) as unknown as boolean;\nconst finalPrice = getProductVariantFormattedFinalPrice(variant) as unknown as string;\nconst originalPrice = getProductVariantFormattedSellPrice(variant) as unknown as string;\nconst discountPct = hasDiscount\n ? (getProductVariantDiscountPercentage(variant) as unknown as number)\n : 0;\n\n<div className=\"pricing\">\n <span className=\"final-price\">{finalPrice}</span>\n {hasDiscount && (\n <>\n <span className=\"original-price\">{originalPrice}</span>\n <span className=\"discount-badge\">-{Math.round(discountPct)}%</span>\n </>\n )}\n</div>\n```\n\n### Image Gallery Pattern\n\n```tsx\nimport { getDefaultSrc, IkasImage } from \"@ikas/bp-storefront\";\n\nconst images: IkasImage[] = (variant?.images ?? [])\n .map((pi) => pi.image)\n .filter((img): img is IkasImage => img != null);\n\nconst [selectedIndex, setSelectedIndex] = useState(0);\nconst mainImage = images[selectedIndex];\n\n<div className=\"gallery\">\n {mainImage && <img src={getDefaultSrc(mainImage)} alt={product.name} />}\n <div className=\"thumbnails\">\n {images.map((img, i) => (\n <img\n key={i}\n src={getDefaultSrc(img)}\n className={i === selectedIndex ? \"active\" : \"\"}\n onClick={() => setSelectedIndex(i)}\n />\n ))}\n </div>\n</div>\n```\n\n### Add to Cart with Stock Check\n\n```tsx\nimport { addItemToCart, isAddToCartEnabled, hasProductVariantStock } from \"@ikas/bp-storefront\";\n\nconst inStock = hasProductVariantStock(variant) as unknown as boolean;\nconst canAdd = isAddToCartEnabled(product) as unknown as boolean;\n\nconst handleAddToCart = async () => {\n if (!variant || !canAdd) return;\n setIsLoading(true);\n try {\n await addItemToCart(variant, product, quantity);\n } finally {\n setIsLoading(false);\n }\n};\n\n<button disabled={!inStock || !canAdd || isLoading} onClick={handleAddToCart}>\n {!inStock ? \"Out of Stock\" : isLoading ? \"Adding...\" : \"Add to Cart\"}\n</button>\n```\n\n### Favorites Toggle\n\n```tsx\nimport {\n isFavoriteIkasProduct,\n addIkasProductToFavorites,\n removeIkasProductFromFavorites,\n} from \"@ikas/bp-storefront\";\n\nconst isFav = isFavoriteIkasProduct(product) as unknown as boolean;\n\nconst toggleFavorite = async () => {\n if (isFav) {\n await removeIkasProductFromFavorites(product);\n } else {\n await addIkasProductToFavorites(product);\n }\n};\n```\n\n### Key Functions Reference\n\n| Function | Purpose | Returns |\n|----------|---------|---------|\n| `getSelectedProductVariant(product)` | Get currently selected variant | `IkasProductVariant` |\n| `getDisplayedProductVariantTypes(product)` | Get variant type/value options | Array of variant types with values |\n| `selectVariantValue(product, value)` | Select a variant option | `void` (mutates) |\n| `hasProductVariantStock(variant)` | Check if variant is in stock | `boolean` (cast needed) |\n| `hasProductStock(product)` | Check if any variant in stock | `boolean` (cast needed) |\n| `isAddToCartEnabled(product)` | Check if add-to-cart is allowed | `boolean` (cast needed) |\n| `addItemToCart(variant, product, qty)` | Add to cart | Result object |\n| `getProductVariantFormattedFinalPrice(variant)` | Formatted final price | `string` (cast needed) |\n| `getProductVariantFormattedSellPrice(variant)` | Formatted original price | `string` (cast needed) |\n| `hasProductVariantDiscount(variant)` | Check for discount | `boolean` (cast needed) |\n| `getProductVariantDiscountPercentage(variant)` | Discount percentage | `number` (cast needed) |",
|
|
164
|
+
"tags": ["product", "detail", "variant", "pricing", "stock", "cart", "favorites", "gallery", "images"]
|
|
165
|
+
},
|
|
166
|
+
"product-list-patterns": {
|
|
167
|
+
"title": "Product List & Filtering Patterns",
|
|
168
|
+
"description": "Category pages, product filtering, sorting, pagination, and search patterns",
|
|
169
|
+
"content": "## Product List & Filtering Patterns\n\nProduct list sections power category pages, search results, and collection displays. They combine filtering, sorting, and pagination.\n\n### Product List Prop Setup\n\nIn `ikas.config.json`, use the `PRODUCT_LIST` prop type:\n```json\n{\n \"name\": \"productList\",\n \"displayName\": \"Product List\",\n \"type\": \"PRODUCT_LIST\",\n \"required\": true\n}\n```\n\nThis provides an `IkasProductList` object with products, filters, sorting, and pagination data.\n\n### Displaying Products\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport {\n IkasProductList,\n getSelectedProductVariant,\n getProductVariantFormattedFinalPrice,\n getProductVariantMainImage,\n getSelectedProductVariantHref,\n getDefaultSrc,\n} from \"@ikas/bp-storefront\";\n\n// Inside observer component:\nconst products = productList?.products ?? [];\n\n{products.map((product) => {\n const variant = getSelectedProductVariant(product);\n const image = getProductVariantMainImage(variant) as unknown as IkasImage | null;\n const price = getProductVariantFormattedFinalPrice(variant) as unknown as string;\n const href = getSelectedProductVariantHref(product);\n\n return (\n <a key={product.id} href={href} className=\"product-card\">\n {image && <img src={getDefaultSrc(image)} alt={product.name} />}\n <h3>{product.name}</h3>\n <span>{price}</span>\n </a>\n );\n})}\n```\n\n### Filtering\n\nFilters let customers narrow down products by attributes (size, color, price, etc.).\n\n```tsx\nimport {\n getFilterDisplayedValues,\n handleFilterValueClick,\n getProductListFilterCategories,\n} from \"@ikas/bp-storefront\";\n\n// Get filter categories (e.g., Size, Color, Price)\nconst filterCategories = getProductListFilterCategories(productList);\n\n{filterCategories.map((category) => {\n const values = getFilterDisplayedValues(category);\n return (\n <div key={category.name}>\n <h4>{category.name}</h4>\n {values.map((filterValue) => (\n <label key={filterValue.name}>\n <input\n type=\"checkbox\"\n checked={filterValue.isSelected}\n onChange={() => handleFilterValueClick(productList, category, filterValue)}\n />\n {filterValue.name} ({filterValue.count})\n </label>\n ))}\n </div>\n );\n})}\n```\n\n### Sorting\n\n```tsx\nimport { getProductListSortOptions } from \"@ikas/bp-storefront\";\n\nconst sortOptions = getProductListSortOptions(productList);\n\n<select\n value={productList.sort}\n onChange={(e) => {\n productList.sort = (e.target as HTMLSelectElement).value;\n }}\n>\n {sortOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n</select>\n```\n\n### Pagination\n\n```tsx\nimport {\n hasProductListNextPage,\n hasProductListPrevPage,\n getProductListNextPage,\n getProductListPrevPage,\n} from \"@ikas/bp-storefront\";\n\nconst hasNext = hasProductListNextPage(productList);\nconst hasPrev = hasProductListPrevPage(productList);\n\n<div className=\"pagination\">\n <button disabled={!hasPrev} onClick={() => getProductListPrevPage(productList)}>\n Previous\n </button>\n <button disabled={!hasNext} onClick={() => getProductListNextPage(productList)}>\n Next\n </button>\n</div>\n```\n\n### Key Functions Reference\n\n| Function | Purpose |\n|----------|---------|\n| `getProductListFilterCategories(list)` | Get available filter categories |\n| `getFilterDisplayedValues(category)` | Get filter values for a category |\n| `handleFilterValueClick(list, category, value)` | Toggle a filter value |\n| `getProductListSortOptions(list)` | Get sort dropdown options |\n| `hasProductListNextPage(list)` / `getProductListNextPage(list)` | Next page |\n| `hasProductListPrevPage(list)` / `getProductListPrevPage(list)` | Previous page |",
|
|
170
|
+
"tags": ["product-list", "filtering", "sorting", "pagination", "category", "search", "grid"]
|
|
171
|
+
},
|
|
172
|
+
"cart-patterns": {
|
|
173
|
+
"title": "Cart Management Patterns",
|
|
174
|
+
"description": "Cart display, quantity changes, item removal, coupon codes, and checkout navigation",
|
|
175
|
+
"content": "## Cart Management Patterns\n\nCart management involves displaying cart items, updating quantities, removing items, applying coupons, and navigating to checkout.\n\n### Accessing Cart Data\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\n// Inside observer component:\nconst cart = cartStore.cart;\nconst lineItems = cart?.orderLineItems ?? [];\nconst isEmpty = lineItems.length === 0;\n```\n\n### Cart Item Display\n\n```tsx\nimport {\n getOrderLineItemFormattedFinalPrice,\n getOrderLineItemFormattedUnitPrice,\n getIkasOrderLineVariantMainImage,\n getDefaultSrc,\n IkasOrderLineItem,\n} from \"@ikas/bp-storefront\";\n\n{lineItems.map((item: IkasOrderLineItem) => {\n const image = item.variant ? getIkasOrderLineVariantMainImage(item.variant) : null;\n return (\n <div key={item.id}>\n {image && <img src={getDefaultSrc(image)} alt={item.variant?.name} />}\n <span>{item.variant?.name}</span>\n <span>Unit: {getOrderLineItemFormattedUnitPrice(item)}</span>\n <span>Qty: {item.quantity}</span>\n <span>Total: {getOrderLineItemFormattedFinalPrice(item)}</span>\n </div>\n );\n})}\n```\n\n### Quantity Changes & Removal\n\n```tsx\nimport { changeItemQuantity, removeItem } from \"@ikas/bp-storefront\";\n\nconst handleQuantityChange = async (item: IkasOrderLineItem, delta: number) => {\n const newQty = item.quantity + delta;\n if (newQty < 1) return;\n await changeItemQuantity(item, newQty);\n};\n\nconst handleRemove = async (item: IkasOrderLineItem) => {\n await removeItem(item);\n};\n```\n\n### Cart Totals\n\n```tsx\nimport {\n getIkasOrderFormattedTotalFinalPrice,\n getIkasOrderFormattedTotalPrice,\n} from \"@ikas/bp-storefront\";\n\n<div className=\"totals\">\n <span>Subtotal: {getIkasOrderFormattedTotalPrice(cart!)}</span>\n <span>Total: {getIkasOrderFormattedTotalFinalPrice(cart!)}</span>\n</div>\n```\n\n### Coupon Codes\n\n```tsx\nimport {\n initCouponCodeForm,\n setCouponCode,\n submitCouponCodeForm,\n} from \"@ikas/bp-storefront\";\n\n// Initialize coupon form\ninitCouponCodeForm(cart);\n\n// Set coupon code\nsetCouponCode(cart, couponValue);\n\n// Submit coupon\nconst success = await submitCouponCodeForm(cart);\n```\n\n### Checkout Navigation\n\n```tsx\nimport { Router, getCheckoutUrlFromCartStore } from \"@ikas/bp-storefront\";\n\n// Navigate to checkout page\nRouter.navigateToPage(\"CHECKOUT\");\n\n// Or get checkout URL directly\nconst checkoutUrl = getCheckoutUrlFromCartStore();\n```\n\n### Free Shipping Bar Pattern\n\nA common pattern from production themes is showing progress toward free shipping:\n\n```tsx\nconst freeShippingThreshold = 500; // configurable via props\nconst cartTotal = cart?.totalFinalPrice ?? 0;\nconst remaining = Math.max(0, freeShippingThreshold - cartTotal);\nconst progress = Math.min(100, (cartTotal / freeShippingThreshold) * 100);\n\n<div className=\"shipping-bar\">\n {remaining > 0\n ? `Add ${formatPrice(remaining)} more for free shipping!`\n : \"You qualify for free shipping!\"}\n <div className=\"progress\" style={{ width: `${progress}%` }} />\n</div>\n```",
|
|
176
|
+
"tags": ["cart", "checkout", "quantity", "coupon", "shipping", "remove", "line-items"]
|
|
177
|
+
},
|
|
178
|
+
"account-patterns": {
|
|
179
|
+
"title": "Account & Dashboard Patterns",
|
|
180
|
+
"description": "Orders list, order detail, address management, favorites, and account info forms",
|
|
181
|
+
"content": "## Account & Dashboard Patterns\n\nAccount sections typically use a tab-based layout with orders, addresses, favorites, and account info.\n\n### Orders List\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport {\n customerStore,\n getOrders,\n getIkasOrderFormattedTotalFinalPrice,\n getIkasOrderTotalItemCount,\n getIkasOrderFormattedOrderedAt,\n getIkasOrderPackageStatusTranslation,\n getIkasOrderHref,\n} from \"@ikas/bp-storefront\";\n\n// Fetch orders on mount\nuseEffect(() => {\n getOrders(customerStore);\n}, []);\n\nconst orders = customerStore.orders ?? [];\n\n{orders.map((order) => (\n <a key={order.id} href={getIkasOrderHref(order)} className=\"order-card\">\n <span>Order #{order.orderNumber}</span>\n <span>{getIkasOrderFormattedOrderedAt(order)}</span>\n <span>{getIkasOrderTotalItemCount(order)} items</span>\n <span>{getIkasOrderFormattedTotalFinalPrice(order)}</span>\n <span>{getIkasOrderPackageStatusTranslation(order)}</span>\n </a>\n))}\n```\n\n### Order Detail\n\n```tsx\nimport {\n getIkasOrderLineVariantMainImage,\n getDefaultSrc,\n} from \"@ikas/bp-storefront\";\n\n// order.orderLineItems contains line items\n{order.orderLineItems.map((item) => {\n const image = item.variant\n ? getIkasOrderLineVariantMainImage(item.variant)\n : null;\n return (\n <div key={item.id}>\n {image && <img src={getDefaultSrc(image)} />}\n <span>{item.variant?.name}</span>\n <span>Qty: {item.quantity}</span>\n </div>\n );\n})}\n```\n\n### Address Management\n\n```tsx\nimport {\n getIkasCustomerAddressForm,\n initAddressForm,\n submitAddressForm,\n deleteAddress,\n} from \"@ikas/bp-storefront\";\n\nconst addresses = customerStore.customer?.addresses ?? [];\n\n// Create new address\nconst addressForm = getIkasCustomerAddressForm(customerStore);\ninitAddressForm(addressForm);\n// ... set fields ...\nawait submitAddressForm(addressForm);\n\n// Delete address\nawait deleteAddress(customerStore, addressId);\n```\n\n### Favorites\n\n```tsx\nimport {\n getFavoriteProducts,\n isFavoriteIkasProduct,\n removeIkasProductFromFavorites,\n} from \"@ikas/bp-storefront\";\n\n// Fetch favorites\nuseEffect(() => {\n getFavoriteProducts(customerStore);\n}, []);\n\nconst favorites = customerStore.favoriteProducts ?? [];\n```\n\n### Account Info Form\n\n```tsx\nimport {\n getAccountInfoForm,\n initAccountInfoForm,\n setAccountInfoFormFirstName,\n setAccountInfoFormLastName,\n setAccountInfoFormPhone,\n submitAccountInfoForm,\n} from \"@ikas/bp-storefront\";\n\nconst accountForm = getAccountInfoForm(customerStore);\n\nuseEffect(() => {\n initAccountInfoForm(accountForm);\n}, []);\n\nconst handleSave = async (e: Event) => {\n e.preventDefault();\n await submitAccountInfoForm(accountForm);\n};\n```\n\n### Tab Navigation Pattern\n\nProduction themes use a tab-based layout:\n\n```tsx\nconst [activeTab, setActiveTab] = useState(\"orders\");\n\nconst tabs = [\n { id: \"orders\", label: \"Orders\" },\n { id: \"addresses\", label: \"Addresses\" },\n { id: \"favorites\", label: \"Favorites\" },\n { id: \"account\", label: \"Account Info\" },\n];\n\n<div className=\"account-tabs\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n className={activeTab === tab.id ? \"active\" : \"\"}\n onClick={() => setActiveTab(tab.id)}\n >\n {tab.label}\n </button>\n ))}\n</div>\n\n{activeTab === \"orders\" && <OrdersList />}\n{activeTab === \"addresses\" && <AddressList />}\n{activeTab === \"favorites\" && <FavoritesList />}\n{activeTab === \"account\" && <AccountInfoForm />}\n```",
|
|
182
|
+
"tags": ["account", "orders", "addresses", "favorites", "dashboard", "customer", "tabs"]
|
|
183
|
+
},
|
|
184
|
+
"header-footer-patterns": {
|
|
185
|
+
"title": "Header & Footer Patterns",
|
|
186
|
+
"description": "Navigation links, logo, mobile menu, announcement bar, footer columns, and social links",
|
|
187
|
+
"content": "## Header & Footer Patterns\n\nHeaders and footers appear on virtually every page (36/37 pages in a typical theme). They use `IkasNavigationLink` for navigation and `observer` for cart/customer state.\n\n### Header Section Structure\n\nA production header typically includes:\n1. Announcement bar (optional, with slider)\n2. Logo + navigation links + utility icons (cart, account, search)\n3. Mobile menu overlay\n\n### Navigation Links\n\nUse the `LIST_OF_LINK` prop type for navigation:\n\n```json\n{\n \"name\": \"navigationLinks\",\n \"displayName\": \"Navigation Links\",\n \"type\": \"LIST_OF_LINK\"\n}\n```\n\nRender links:\n```tsx\nimport { IkasNavigationLink } from \"@ikas/bp-storefront\";\n\ninterface Props {\n navigationLinks?: IkasNavigationLink[];\n}\n\n{navigationLinks?.map((link, i) => (\n <a key={i} href={link.href} target={link.isTargetBlank ? \"_blank\" : undefined}>\n {link.label}\n </a>\n))}\n```\n\n### Cart Badge & Customer State\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore, customerStore, hasCustomer, Router } from \"@ikas/bp-storefront\";\n\n// Inside observer component:\nconst itemCount = cartStore.cart?.orderLineItems.length ?? 0;\nconst isLoggedIn = hasCustomer(customerStore) as unknown as boolean;\n\n<div className=\"header-icons\">\n <button onClick={() => Router.navigateToPage(isLoggedIn ? \"ACCOUNT\" : \"LOGIN\")}>\n Account\n </button>\n <button onClick={() => Router.navigateToPage(\"CART\")}>\n Cart ({itemCount})\n </button>\n</div>\n```\n\n### Mobile Menu Overlay\n\n```tsx\nimport { IkasThemeOverlay } from \"@ikas/bp-storefront\";\n\nconst [menuOpen, setMenuOpen] = useState(false);\n\n<button className=\"hamburger\" onClick={() => setMenuOpen(true)}>Menu</button>\n\n<IkasThemeOverlay visible={menuOpen}>\n <div className=\"mobile-menu\">\n <button className=\"close\" onClick={() => setMenuOpen(false)}>Close</button>\n <nav>\n {navigationLinks?.map((link, i) => (\n <a key={i} href={link.href} onClick={() => setMenuOpen(false)}>\n {link.label}\n </a>\n ))}\n </nav>\n </div>\n</IkasThemeOverlay>\n```\n\n### Footer Section Structure\n\nA production footer includes:\n1. Logo and description\n2. Link columns (using `LIST_OF_LINK` or `COMPONENT_LIST` props)\n3. Contact information\n4. Social media links\n5. Copyright text\n\n```tsx\ninterface Props {\n logo?: { src: string; alt?: string };\n linkColumns?: IkasNavigationLink[];\n copyright?: string;\n socialLinks?: Array<{ icon: string; url: string }>;\n}\n\n<footer className=\"footer-section\">\n <div className=\"footer-inner\">\n <div className=\"footer-brand\">\n {logo?.src && <img src={logo.src} alt={logo.alt || \"Logo\"} />}\n </div>\n <div className=\"footer-links\">\n {linkColumns?.map((link, i) => (\n <a key={i} href={link.href}>{link.label}</a>\n ))}\n </div>\n <div className=\"footer-copyright\">\n <p>{copyright}</p>\n </div>\n </div>\n</footer>\n```\n\n### Key Patterns\n\n- Headers and footers are **sections** (`\"type\": \"section\"` in config)\n- Always use `observer` since they read `cartStore` and `customerStore`\n- Use `LIST_OF_LINK` for navigation that store owners can edit\n- Mobile menu uses `IkasThemeOverlay` for slide-in drawer\n- Announcement bars use `IkasThemeSlider` for rotating messages",
|
|
188
|
+
"tags": ["header", "footer", "navigation", "menu", "mobile", "overlay", "links", "logo", "social"]
|
|
189
|
+
},
|
|
190
|
+
"review-patterns": {
|
|
191
|
+
"title": "Customer Review Patterns",
|
|
192
|
+
"description": "Review display, star ratings, review submission form, and login-required checks",
|
|
193
|
+
"content": "## Customer Review Patterns\n\nProduct reviews allow customers to rate and comment on products. The pattern includes displaying existing reviews and a submission form.\n\n### Displaying Reviews\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport {\n getProductCustomerReviews,\n getIkasCustomerReviewFormattedDate,\n} from \"@ikas/bp-storefront\";\n\n// Inside observer component with product prop:\nconst reviews = getProductCustomerReviews(product) ?? [];\n\n{reviews.map((review) => (\n <div key={review.id} className=\"review-card\">\n <div className=\"review-stars\">\n {[1, 2, 3, 4, 5].map((star) => (\n <span key={star} className={star <= review.star ? \"filled\" : \"empty\"}>★</span>\n ))}\n </div>\n <h4>{review.title}</h4>\n <p>{review.comment}</p>\n <span className=\"review-date\">{getIkasCustomerReviewFormattedDate(review)}</span>\n <span className=\"review-author\">{review.customerName}</span>\n </div>\n))}\n```\n\n### Review Submission Form\n\n```tsx\nimport {\n getIkasProductCustomerReviewForm,\n setCustomerReviewFormTitle,\n setCustomerReviewFormStar,\n setCustomerReviewFormComment,\n submitCustomerReviewForm,\n isCustomerReviewLoginRequired,\n customerStore,\n hasCustomer,\n Router,\n} from \"@ikas/bp-storefront\";\n\nconst reviewForm = getIkasProductCustomerReviewForm(product);\nconst loginRequired = isCustomerReviewLoginRequired() as unknown as boolean;\nconst isLoggedIn = hasCustomer(customerStore) as unknown as boolean;\n\n// If login is required and user is not logged in\nif (loginRequired && !isLoggedIn) {\n return (\n <div>\n <p>Please log in to write a review.</p>\n <button onClick={() => Router.navigateToPage(\"LOGIN\")}>Log In</button>\n </div>\n );\n}\n\n// Star rating input\nconst [hoverStar, setHoverStar] = useState(0);\n\n<div className=\"star-input\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n className={star <= (hoverStar || reviewForm.star.value) ? \"filled\" : \"empty\"}\n onMouseEnter={() => setHoverStar(star)}\n onMouseLeave={() => setHoverStar(0)}\n onClick={() => setCustomerReviewFormStar(reviewForm, star)}\n >\n ★\n </button>\n ))}\n</div>\n\n// Title and comment inputs\n<input\n value={reviewForm.title.value}\n onInput={(e) => setCustomerReviewFormTitle(reviewForm, (e.target as HTMLInputElement).value)}\n/>\n<textarea\n value={reviewForm.comment.value}\n onInput={(e) => setCustomerReviewFormComment(reviewForm, (e.target as HTMLTextAreaElement).value)}\n/>\n\n// Submit\nconst handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitCustomerReviewForm(reviewForm);\n if (success) {\n // Review submitted successfully\n }\n};\n```\n\n### Key Functions\n\n| Function | Purpose |\n|----------|---------|\n| `getProductCustomerReviews(product)` | Get reviews for a product |\n| `getIkasProductCustomerReviewForm(product)` | Get the review form model |\n| `setCustomerReviewFormTitle(form, value)` | Set review title |\n| `setCustomerReviewFormStar(form, value)` | Set star rating (1-5) |\n| `setCustomerReviewFormComment(form, value)` | Set review comment |\n| `submitCustomerReviewForm(form)` | Submit the review |\n| `isCustomerReviewLoginRequired()` | Check if login is needed to review |\n| `getIkasCustomerReviewFormattedDate(review)` | Format review date |",
|
|
194
|
+
"tags": ["reviews", "ratings", "stars", "feedback", "customer", "form"]
|
|
195
|
+
},
|
|
196
|
+
"slider-overlay-patterns": {
|
|
197
|
+
"title": "Slider & Overlay Patterns",
|
|
198
|
+
"description": "IkasThemeSlider for carousels, IkasThemeOverlay for modals/drawers, IkasThemeInfiniteScroller for lazy loading",
|
|
199
|
+
"content": "## Slider & Overlay Patterns\n\nikas provides built-in UI components for common interactive patterns.\n\n### IkasThemeSlider\n\nA carousel/slider component for rotating content (announcements, product images, banners).\n\n```tsx\nimport { IkasThemeSlider } from \"@ikas/bp-storefront\";\n\n<IkasThemeSlider\n autoplay={true}\n autoplayInterval={5000}\n loop={true}\n>\n {(sliderProps) => {\n const { current, index, goTo, dotCount, itemCount } = sliderProps;\n\n return (\n <div className=\"slider\">\n <div className=\"slider-track\">\n {items.map((item, i) => (\n <div key={i} className={`slide ${i === current ? \"active\" : \"\"}`}>\n {item.content}\n </div>\n ))}\n </div>\n\n {/* Navigation arrows */}\n <button onClick={() => goTo(current - 1)}>Previous</button>\n <button onClick={() => goTo(current + 1)}>Next</button>\n\n {/* Dot indicators */}\n <div className=\"dots\">\n {Array.from({ length: dotCount }).map((_, i) => (\n <button\n key={i}\n className={i === current ? \"active\" : \"\"}\n onClick={() => goTo(i)}\n />\n ))}\n </div>\n </div>\n );\n }}\n</IkasThemeSlider>\n```\n\n### Slider Props\n\n| Prop | Type | Description |\n|------|------|-------------|\n| `current` | `number` | Currently visible slide index |\n| `index` | `number` | Internal index (may differ with loop) |\n| `goTo(index)` | `function` | Navigate to specific slide |\n| `dotCount` | `number` | Total number of dots/pages |\n| `itemCount` | `number` | Total number of items |\n\n### IkasThemeOverlay\n\nA modal/drawer component for overlays (mobile menu, quick view, search, filters).\n\n```tsx\nimport { IkasThemeOverlay } from \"@ikas/bp-storefront\";\n\nconst [isVisible, setIsVisible] = useState(false);\n\n<button onClick={() => setIsVisible(true)}>Open Menu</button>\n\n<IkasThemeOverlay visible={isVisible}>\n <div className=\"overlay-content\">\n <button className=\"close-btn\" onClick={() => setIsVisible(false)}>×</button>\n {/* Overlay content */}\n </div>\n</IkasThemeOverlay>\n```\n\nThe overlay handles:\n- Backdrop click to close (when you handle the `visible` state)\n- Body scroll lock when visible\n- Z-index management\n\n### IkasThemeInfiniteScroller\n\nA component for infinite scroll / lazy loading patterns.\n\n```tsx\nimport { IkasThemeInfiniteScroller } from \"@ikas/bp-storefront\";\n\n<IkasThemeInfiniteScroller\n hasMore={hasNextPage}\n onLoadMore={async () => {\n await getProductListNextPage(productList);\n }}\n>\n {products.map((product) => (\n <ProductCard key={product.id} product={product} />\n ))}\n</IkasThemeInfiniteScroller>\n```\n\n### Common Usage Patterns\n\n- **Announcement bar**: `IkasThemeSlider` with autoplay for rotating promotional messages\n- **Mobile menu**: `IkasThemeOverlay` as a full-screen or side drawer\n- **Product image gallery**: `IkasThemeSlider` for main product images\n- **Quick view modal**: `IkasThemeOverlay` showing product details\n- **Product list infinite scroll**: `IkasThemeInfiniteScroller` for seamless pagination\n- **Filter drawer (mobile)**: `IkasThemeOverlay` for filter panel on mobile",
|
|
200
|
+
"tags": ["slider", "carousel", "overlay", "modal", "drawer", "infinite-scroll", "lazy-loading"]
|
|
201
|
+
},
|
|
202
|
+
"blog-patterns": {
|
|
203
|
+
"title": "Blog Patterns",
|
|
204
|
+
"description": "Blog list display, blog post detail, pagination, date formatting, and category filtering",
|
|
205
|
+
"content": "## Blog Patterns\n\nBlog sections display articles with pagination and optional category filtering.\n\n### Blog List Prop Setup\n\nIn `ikas.config.json`, use the `BLOG_POST_LIST` prop type or a dedicated blog list config:\n\n```json\n{\n \"name\": \"blogList\",\n \"displayName\": \"Blog List\",\n \"type\": \"BLOG_POST_LIST\"\n}\n```\n\n### Blog List Display\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport {\n IkasBlogList,\n getIkasBlogFormattedDate,\n getIkasBlogHref,\n hasBlogListNextPage,\n getBlogListNextPage,\n hasBlogListPrevPage,\n getBlogListPrevPage,\n getDefaultSrc,\n} from \"@ikas/bp-storefront\";\n\n// Inside observer component:\nconst blogs = blogList?.blogs ?? [];\n\n{blogs.map((blog) => (\n <a key={blog.id} href={getIkasBlogHref(blog)} className=\"blog-card\">\n {blog.image && (\n <img src={getDefaultSrc(blog.image)} alt={blog.title} />\n )}\n <div className=\"blog-card-content\">\n <span className=\"blog-date\">{getIkasBlogFormattedDate(blog)}</span>\n <h3>{blog.title}</h3>\n <p>{blog.summary}</p>\n </div>\n </a>\n))}\n```\n\n### Blog Pagination\n\n```tsx\nconst hasNext = hasBlogListNextPage(blogList);\nconst hasPrev = hasBlogListPrevPage(blogList);\n\n<div className=\"blog-pagination\">\n {hasPrev && (\n <button onClick={() => getBlogListPrevPage(blogList)}>Previous</button>\n )}\n {hasNext && (\n <button onClick={() => getBlogListNextPage(blogList)}>Next</button>\n )}\n</div>\n```\n\n### Blog Post Detail\n\nFor a blog post detail page, use the `BLOG_POST` prop type:\n\n```json\n{\n \"name\": \"blogPost\",\n \"displayName\": \"Blog Post\",\n \"type\": \"BLOG_POST\"\n}\n```\n\n```tsx\ninterface Props {\n blogPost: IkasBlogPost;\n}\n\nfunction BlogPostDetail({ blogPost }: Props) {\n if (!blogPost) return null;\n\n return (\n <article className=\"blog-post\">\n {blogPost.image && (\n <img src={getDefaultSrc(blogPost.image)} alt={blogPost.title} />\n )}\n <h1>{blogPost.title}</h1>\n <span className=\"date\">{getIkasBlogFormattedDate(blogPost)}</span>\n <div\n className=\"blog-content\"\n dangerouslySetInnerHTML={{ __html: blogPost.content }}\n />\n </article>\n );\n}\n```\n\n### Key Functions\n\n| Function | Purpose |\n|----------|---------|\n| `getIkasBlogFormattedDate(blog)` | Format blog post date |\n| `getIkasBlogHref(blog)` | Get URL for a blog post |\n| `hasBlogListNextPage(list)` | Check if next page exists |\n| `getBlogListNextPage(list)` | Fetch next page (mutates list) |\n| `hasBlogListPrevPage(list)` | Check if previous page exists |\n| `getBlogListPrevPage(list)` | Fetch previous page (mutates list) |",
|
|
206
|
+
"tags": ["blog", "article", "posts", "pagination", "date", "content"]
|
|
207
|
+
},
|
|
208
|
+
"navigation-patterns": {
|
|
209
|
+
"title": "Navigation & Routing Patterns",
|
|
210
|
+
"description": "Router.navigate, page navigation, href getters, breadcrumbs, and query parameters",
|
|
211
|
+
"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 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_POST` — 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",
|
|
212
|
+
"tags": ["navigation", "router", "routing", "links", "breadcrumbs", "href", "pages"]
|
|
213
|
+
},
|
|
214
|
+
"real-world-architecture": {
|
|
215
|
+
"title": "Real-World Theme Architecture",
|
|
216
|
+
"description": "How production themes are structured: page types, section composition, component reuse, and prop design patterns",
|
|
217
|
+
"content": "## Real-World Theme Architecture\n\nThis guide describes how a production ikas theme is structured, based on analysis of real deployed themes.\n\n### Page Types\n\nA complete theme typically has these page types:\n\n| Page Type | Purpose | Common Sections |\n|-----------|---------|------------------|\n| `INDEX` | Home page | Header, Hero Banner, Featured Products, Categories, Footer |\n| `CATEGORY` | Category listing | Header, Product List, Footer |\n| `PRODUCT_DETAIL` | Product page | Header, Product Detail, Reviews, Related Products, Footer |\n| `BLOG` | Blog listing | Header, Blog List, Footer |\n| `BLOG_POST` | Blog article | Header, Blog Post Detail, Footer |\n| `CART` | Shopping cart | Header, Cart Section, Footer |\n| `CHECKOUT` | Checkout flow | Minimal Header, Checkout Form |\n| `LOGIN` | Sign in | Header, Login Form, Footer |\n| `REGISTER` | Sign up | Header, Register Form, Footer |\n| `FORGOT_PASSWORD` | Password recovery | Header, Forgot Password Form, Footer |\n| `ACCOUNT` | Customer dashboard | Header, Account Section, Footer |\n| `SEARCH` | Search results | Header, Product List (search mode), Footer |\n| `CUSTOM` | Custom pages (About, Contact, FAQ) | Header, Custom Content, Footer |\n| `NOT_FOUND` | 404 page | Header, 404 Message, Footer |\n\n### Section Composition Pattern\n\nEvery page follows: **Header → Content Section(s) → Footer**\n\n- Header and Footer are shared across 36/37 pages (all except checkout)\n- Content sections are page-specific\n- Some sections are reused across multiple pages (FAQ on 10 pages, Rich Text on 9)\n\n### Section Reuse Strategy\n\nDesign sections to be reusable:\n\n| Section | Reuse Count | Strategy |\n|---------|-------------|----------|\n| Header | 36 pages | Single section, all navigation |\n| Footer | 36 pages | Single section, all links |\n| FAQ | 10 pages | Generic accordion, content via props |\n| Rich Text / HTML | 9 pages | Generic content block |\n| Product Detail | 1 page | Highly specialized |\n| Cart | 1 page | Highly specialized |\n\n### Component Hierarchy\n\nProduction sections have sub-components:\n\n```\nHeader Section (section)\n├── AnnouncementBar (component)\n├── NavigationBar (component)\n│ ├── Logo\n│ ├── NavLinks\n│ └── CartIcon / AccountIcon\n└── MobileMenu (component)\n\nProduct Detail Section (section)\n├── ImageGallery (component)\n├── VariantSelector (component)\n├── PriceDisplay (component)\n├── AddToCartButton (component)\n└── ProductDescription (component)\n```\n\n### Prop Design Patterns\n\nCommon prop patterns seen in production:\n\n1. **Content props**: `TEXT` for headings, descriptions, button labels\n2. **Style props**: `COLOR` for backgrounds, text colors, accents\n3. **Toggle props**: `BOOLEAN` for show/hide sections, features\n4. **Layout props**: `SELECT` for layout variants (grid/list, left/center/right)\n5. **Navigation props**: `LIST_OF_LINK` for editable nav menus\n6. **Data props**: `PRODUCT`, `PRODUCT_LIST`, `BLOG_POST_LIST` for dynamic data\n7. **Media props**: `IMAGE` for logos, banners, icons\n\n### Responsive Design\n\nProduction themes target these breakpoints:\n- Desktop: 1200px+ (max-width container)\n- Tablet: 768px-1199px\n- Mobile: <768px\n\nCommon responsive patterns:\n```css\n.section { width: 100%; padding: 64px 24px; }\n.section-inner { max-width: 1200px; margin: 0 auto; }\n\n@media (max-width: 768px) {\n .section { padding: 32px 16px; }\n .grid { grid-template-columns: 1fr; }\n}\n```\n\n### Essential Sections Checklist\n\nEvery complete theme needs at minimum:\n- [ ] Header (with mobile menu)\n- [ ] Footer\n- [ ] Hero Banner / Home content\n- [ ] Product List (category page)\n- [ ] Product Detail\n- [ ] Cart\n- [ ] Login / Register / Forgot Password\n- [ ] Account (orders, addresses)\n- [ ] 404 Page\n- [ ] Contact Form (optional but common)\n- [ ] FAQ (optional but common)\n- [ ] Blog List + Blog Post (optional)",
|
|
218
|
+
"tags": ["architecture", "theme", "pages", "sections", "composition", "reuse", "responsive", "production"]
|
|
153
219
|
}
|
|
154
220
|
}
|
|
155
221
|
}
|