@ikas/code-components-mcp 0.28.0 → 0.30.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 +141 -24
- package/data/section-templates.json +16 -16
- package/data/storefront-api.json +44 -44
- package/data/storefront-types.json +1 -1
- package/dist/index.js +15 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/data/framework.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"project-structure": {
|
|
5
5
|
"title": "Project Structure",
|
|
6
6
|
"description": "How an ikas code components project is organized",
|
|
7
|
-
"content": "An ikas code components project follows this structure:\n\n```\nmy-project/\n
|
|
7
|
+
"content": "An ikas code components project follows this structure:\n\n```\nmy-project/\n├── src/\n│ └── components/\n│ ├── MyComponent/\n│ │ ├── index.tsx # Component implementation (Preact)\n│ │ ├── types.ts # TypeScript props interface\n│ │ └── styles.css # Scoped component styles\n│ └── index.ts # Component exports barrel\n├── ikas.config.json # Component definitions, props, metadata\n├── package.json\n├── tsconfig.json\n└── vite.config.ts\n```\n\nEach component lives in its own directory under `src/components/`. The component's entry point is `index.tsx`, which exports a default Preact function component. The `types.ts` file defines the props interface matching the props declared in `ikas.config.json`. The `styles.css` file contains CSS that is automatically scoped to the component.",
|
|
8
8
|
"tags": [
|
|
9
9
|
"structure",
|
|
10
10
|
"project",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"ikas-config": {
|
|
17
17
|
"title": "ikas.config.json Schema",
|
|
18
18
|
"description": "Configuration file that defines components, their props, and metadata",
|
|
19
|
-
"content": "The `ikas.config.json` file is the central configuration for your code components project.\n\n```json\n{\n \"name\": \"my-project\",\n \"version\": \"1.0.0\",\n \"components\": [\n {\n \"id\": \"my-component\",\n \"name\": \"My Component\",\n \"entry\": \"./src/components/MyComponent/index.tsx\",\n \"styles\": \"./src/components/MyComponent/styles.css\",\n \"props\": [\n {\n \"name\": \"title\",\n \"displayName\": \"Title\",\n \"type\": \"TEXT\",\n \"required\": true,\n \"defaultValue\": \"Hello\",\n \"description\": \"The main heading text\"\n }\n ]\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 \"name\": \"heading\",\n \"displayName\": \"Heading\",\n \"type\": \"TEXT\",\n \"required\": true,\n \"defaultValue\": \"Welcome\"\n }\n ]\n }\n ]\n}\n```\n\n### Top-level fields:\n- `name` (string): Project name\n- `version` (string): Project version\n- `components` (array): List of component definitions\n\n### Component fields:\n- `id` (string): Unique identifier (kebab-case)\n- `name` (string): Display name in the editor\n- `type` (string, optional): `\"component\"` (default) or `\"section\"`
|
|
19
|
+
"content": "The `ikas.config.json` file is the central configuration for your code components project.\n\n```json\n{\n \"name\": \"my-project\",\n \"version\": \"1.0.0\",\n \"components\": [\n {\n \"id\": \"my-component\",\n \"name\": \"My Component\",\n \"entry\": \"./src/components/MyComponent/index.tsx\",\n \"styles\": \"./src/components/MyComponent/styles.css\",\n \"props\": [\n {\n \"name\": \"title\",\n \"displayName\": \"Title\",\n \"type\": \"TEXT\",\n \"required\": true,\n \"defaultValue\": \"Hello\",\n \"description\": \"The main heading text\"\n }\n ]\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 \"name\": \"heading\",\n \"displayName\": \"Heading\",\n \"type\": \"TEXT\",\n \"required\": true,\n \"defaultValue\": \"Welcome\"\n }\n ]\n }\n ]\n}\n```\n\n### Top-level fields:\n- `name` (string): Project name\n- `version` (string): Project version\n- `components` (array): List of component definitions\n\n### Component fields:\n- `id` (string): Unique identifier (kebab-case)\n- `name` (string): Display name in the editor\n- `type` (string, optional): `\"component\"` (default) or `\"section\"` — sections are page-level containers (e.g. header, hero banner, footer), components are child elements placed inside sections (e.g. button, card, badge)\n- `entry` (string): Path to the component's entry file (index.tsx)\n- `styles` (string): Path to the component's CSS file\n- `props` (array): List of prop definitions\n\n### Prop fields:\n- `name` (string): Prop name used in code (camelCase)\n- `displayName` (string): Label shown in the editor UI\n- `type` (string): One of the available prop types\n- `required` (boolean): Whether the prop must be set\n- `defaultValue` (any): Default value matching the prop type\n- `description` (string): Help text shown in editor\n- `options` (array, SELECT only): Array of `{ label, value }` for SELECT type",
|
|
20
20
|
"tags": [
|
|
21
21
|
"config",
|
|
22
22
|
"configuration",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"sections-vs-components": {
|
|
123
123
|
"title": "Sections vs Components",
|
|
124
124
|
"description": "The difference between sections (page-level containers) and components (child elements)",
|
|
125
|
-
"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\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## How to Decide\n- Will it span the full width of the page and sit at the top level?
|
|
125
|
+
"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\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## 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**",
|
|
126
126
|
"tags": [
|
|
127
127
|
"section",
|
|
128
128
|
"component",
|
|
@@ -137,85 +137,202 @@
|
|
|
137
137
|
"title": "Common Pitfalls",
|
|
138
138
|
"description": "Frequent mistakes LLMs and developers make when building ikas code components",
|
|
139
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. 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 default function CartSection({ title }: Props) {\n const items = cartStore.cart?.orderLineItems ?? [];\n return <section>{title}: {items.length} items</section>;\n}\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.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### 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.",
|
|
140
|
-
"tags": [
|
|
140
|
+
"tags": [
|
|
141
|
+
"pitfalls",
|
|
142
|
+
"gotchas",
|
|
143
|
+
"mistakes",
|
|
144
|
+
"observer",
|
|
145
|
+
"mutation",
|
|
146
|
+
"css",
|
|
147
|
+
"types",
|
|
148
|
+
"null-safety",
|
|
149
|
+
"forms",
|
|
150
|
+
"events",
|
|
151
|
+
"parameters"
|
|
152
|
+
]
|
|
141
153
|
},
|
|
142
154
|
"ai-workflow": {
|
|
143
155
|
"title": "AI Agent Workflow for Building ikas Components",
|
|
144
156
|
"description": "Step-by-step guide for AI coding agents building ikas storefront components using CLI commands and MCP tools",
|
|
145
157
|
"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\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 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 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 be a plain named function: `export default function X({ ... }: Props) { ... }`\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\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` | 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 starter code + CLI command |\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": [
|
|
158
|
+
"tags": [
|
|
159
|
+
"ai",
|
|
160
|
+
"workflow",
|
|
161
|
+
"agent",
|
|
162
|
+
"cli",
|
|
163
|
+
"steps",
|
|
164
|
+
"guide",
|
|
165
|
+
"automation"
|
|
166
|
+
]
|
|
147
167
|
},
|
|
148
168
|
"form-handling": {
|
|
149
169
|
"title": "Form Handling",
|
|
150
170
|
"description": "How to use the form model pattern for login, registration, address, and other forms",
|
|
151
171
|
"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 default 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```\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 | — | `setCouponCode()` | `applyCouponCode()` |\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",
|
|
152
|
-
"tags": [
|
|
172
|
+
"tags": [
|
|
173
|
+
"form",
|
|
174
|
+
"login",
|
|
175
|
+
"register",
|
|
176
|
+
"address",
|
|
177
|
+
"validation",
|
|
178
|
+
"input"
|
|
179
|
+
]
|
|
153
180
|
},
|
|
154
181
|
"async-data-patterns": {
|
|
155
182
|
"title": "Async Data & Loading Patterns",
|
|
156
183
|
"description": "How to handle async operations, loading states, and store data in ikas components",
|
|
157
184
|
"content": "## Async Data & Loading Patterns\n\n### 1. Store State is Null Until Initialized\n\n**Note:** Root component exports are automatically reactive via the ikas runtime's `autorun()` — no `observer()` needed. The examples below show `observer()` on **sub-components** that independently read store data.\n\nStore data (`cartStore.cart`, `customerStore.customer`) is `null` until the store initializes. Use null-safe access:\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport { cartStore } from \"@ikas/bp-storefront\";\n\n// Sub-component: needs observer() for independent reactivity\nconst CartBadge = observer(function CartBadge() {\n // cart is null until initialized — use optional chaining\n const itemCount = cartStore.cart?.orderLineItems.length ?? 0;\n return <span className=\"cart-badge\">{itemCount}</span>;\n});\n```\n\n### 2. Loading Pattern for Async Operations\n\nUse `useState` for local loading flags. Always use `try/finally` to clear the loading state:\n\n```tsx\nimport { useState } from \"preact/hooks\";\nimport { addItemToCart } from \"@ikas/bp-storefront\";\n\nconst [isLoading, setIsLoading] = useState(false);\n\nconst handleAddToCart = async () => {\n if (isLoading) return; // prevent double-click\n setIsLoading(true);\n try {\n await addItemToCart(variant, product, 1);\n } finally {\n setIsLoading(false);\n }\n};\n\n// In JSX:\n<button disabled={isLoading} onClick={handleAddToCart}>\n {isLoading ? \"Adding...\" : \"Add to Cart\"}\n</button>\n```\n\n### 3. Cart Operation Results\n\n`addItemToCart` returns a result object. Check for validation errors:\n\n```tsx\nconst result = await addItemToCart(variant, product, 1);\nif (result.success) {\n // Item added successfully\n} else if (result.validationError === \"INSUFFICIENT_STOCK\") {\n // Not enough stock\n} else if (result.validationError === \"INVALID_PRODUCT_OPTION_VALUES\") {\n // Variant options not fully selected\n}\n```\n\n### 4. Form Submission Results\n\nForm submit functions return `boolean` and set status flags on the form:\n\n```tsx\nconst success = await submitLoginForm(loginForm);\nif (success) {\n // loginForm.isSuccess is true\n Router.navigate(\"/account\");\n} else {\n // loginForm.isFailure is true\n // loginForm.responseMessage has the error message\n console.log(loginForm.responseMessage);\n}\n```\n\n### 5. Observer Auto Re-rendering\n\nStore updates automatically trigger re-renders. Root components get this via `autorun()`, sub-components via `observer()`. You do NOT need manual state management for store-derived values:\n\n```tsx\n// WRONG — unnecessary useState for store data\nconst [cartItems, setCartItems] = useState([]);\nuseEffect(() => { setCartItems(cartStore.cart?.orderLineItems ?? []); }, [cartStore.cart]);\n\n// CORRECT — observer sub-component for independent reactivity\nconst CartList = observer(function CartList() {\n const items = cartStore.cart?.orderLineItems ?? [];\n return (\n <div>\n {items.map((item) => (\n <div key={item.id}>{item.variant?.name}</div>\n ))}\n </div>\n );\n});\n\n// Root export uses CartList — no observer needed on root\nexport default function CartPage({ title }: Props) {\n return (\n <section>\n <h1>{title}</h1>\n <CartList />\n </section>\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.",
|
|
158
|
-
"tags": [
|
|
185
|
+
"tags": [
|
|
186
|
+
"async",
|
|
187
|
+
"loading",
|
|
188
|
+
"error",
|
|
189
|
+
"stores",
|
|
190
|
+
"cart",
|
|
191
|
+
"fetch"
|
|
192
|
+
]
|
|
159
193
|
},
|
|
160
194
|
"product-detail-patterns": {
|
|
161
195
|
"title": "Product Detail Patterns",
|
|
162
196
|
"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 {
|
|
164
|
-
"tags": [
|
|
197
|
+
"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 {\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// In your 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, createMediaSrcset, 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 && (\n <img\n src={getDefaultSrc(mainImage)}\n srcSet={createMediaSrcset(mainImage)}\n sizes=\"(max-width: 768px) 100vw, 50vw\"\n alt={product.name}\n />\n )}\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) |\n| `createMediaSrcset(image)` | Generate responsive srcset for all sizes (180–3840px) | `string` |",
|
|
198
|
+
"tags": [
|
|
199
|
+
"product",
|
|
200
|
+
"detail",
|
|
201
|
+
"variant",
|
|
202
|
+
"pricing",
|
|
203
|
+
"stock",
|
|
204
|
+
"cart",
|
|
205
|
+
"favorites",
|
|
206
|
+
"gallery",
|
|
207
|
+
"images"
|
|
208
|
+
]
|
|
165
209
|
},
|
|
166
210
|
"product-list-patterns": {
|
|
167
211
|
"title": "Product List & Filtering Patterns",
|
|
168
212
|
"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 {
|
|
170
|
-
"tags": [
|
|
213
|
+
"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 {\n IkasProductList,\n getSelectedProductVariant,\n getProductVariantFormattedFinalPrice,\n getProductVariantMainImage,\n getSelectedProductVariantHref,\n getDefaultSrc,\n} from \"@ikas/bp-storefront\";\n\n// In your component:\nconst products = productList?.products ?? [];\n\n{products.map((product) => {\n const variant = getSelectedProductVariant(product);\n const productImage = getProductVariantMainImage(variant);\n const image = productImage?.image ?? 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 |",
|
|
214
|
+
"tags": [
|
|
215
|
+
"product-list",
|
|
216
|
+
"filtering",
|
|
217
|
+
"sorting",
|
|
218
|
+
"pagination",
|
|
219
|
+
"category",
|
|
220
|
+
"search",
|
|
221
|
+
"grid"
|
|
222
|
+
]
|
|
171
223
|
},
|
|
172
224
|
"cart-patterns": {
|
|
173
225
|
"title": "Cart Management Patterns",
|
|
174
226
|
"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 {
|
|
176
|
-
"tags": [
|
|
227
|
+
"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 { cartStore } from \"@ikas/bp-storefront\";\n\n// In your 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```",
|
|
228
|
+
"tags": [
|
|
229
|
+
"cart",
|
|
230
|
+
"checkout",
|
|
231
|
+
"quantity",
|
|
232
|
+
"coupon",
|
|
233
|
+
"shipping",
|
|
234
|
+
"remove",
|
|
235
|
+
"line-items"
|
|
236
|
+
]
|
|
177
237
|
},
|
|
178
238
|
"account-patterns": {
|
|
179
239
|
"title": "Account & Dashboard Patterns",
|
|
180
240
|
"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 {
|
|
182
|
-
"tags": [
|
|
241
|
+
"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 {\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```",
|
|
242
|
+
"tags": [
|
|
243
|
+
"account",
|
|
244
|
+
"orders",
|
|
245
|
+
"addresses",
|
|
246
|
+
"favorites",
|
|
247
|
+
"dashboard",
|
|
248
|
+
"customer",
|
|
249
|
+
"tabs"
|
|
250
|
+
]
|
|
183
251
|
},
|
|
184
252
|
"header-footer-patterns": {
|
|
185
253
|
"title": "Header & Footer Patterns",
|
|
186
254
|
"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
|
|
188
|
-
"tags": [
|
|
255
|
+
"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. Cart/customer state is automatically reactive in root components via the ikas runtime's `autorun()`.\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 { cartStore, customerStore, hasCustomer, Router } from \"@ikas/bp-storefront\";\n\n// In your 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- Root exports are automatically reactive (reads from `cartStore` and `customerStore` are tracked by `autorun()`)\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",
|
|
256
|
+
"tags": [
|
|
257
|
+
"header",
|
|
258
|
+
"footer",
|
|
259
|
+
"navigation",
|
|
260
|
+
"menu",
|
|
261
|
+
"mobile",
|
|
262
|
+
"overlay",
|
|
263
|
+
"links",
|
|
264
|
+
"logo",
|
|
265
|
+
"social"
|
|
266
|
+
]
|
|
189
267
|
},
|
|
190
268
|
"review-patterns": {
|
|
191
269
|
"title": "Customer Review Patterns",
|
|
192
270
|
"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 {
|
|
194
|
-
"tags": [
|
|
271
|
+
"content": "## Customer Review Patterns\n\nProduct reviews allow customers to rate and comment on products. The pattern includes displaying existing reviews and a submission form.\n\n### Displaying Reviews\n\n```tsx\nimport {\n getProductCustomerReviews,\n getIkasCustomerReviewFormattedDate,\n} from \"@ikas/bp-storefront\";\n\n// In your component (product prop):\nconst reviews = getProductCustomerReviews(product) ?? [];\n\n{reviews.map((review) => (\n <div key={review.id} className=\"review-card\">\n <div className=\"review-stars\">\n {[1, 2, 3, 4, 5].map((star) => (\n <span key={star} className={star <= review.star ? \"filled\" : \"empty\"}>★</span>\n ))}\n </div>\n <h4>{review.title}</h4>\n <p>{review.comment}</p>\n <span className=\"review-date\">{getIkasCustomerReviewFormattedDate(review)}</span>\n <span className=\"review-author\">{review.customerName}</span>\n </div>\n))}\n```\n\n### Review Submission Form\n\n```tsx\nimport {\n getIkasProductCustomerReviewForm,\n setCustomerReviewFormTitle,\n setCustomerReviewFormStar,\n setCustomerReviewFormComment,\n submitCustomerReviewForm,\n isCustomerReviewLoginRequired,\n customerStore,\n hasCustomer,\n Router,\n} from \"@ikas/bp-storefront\";\n\nconst reviewForm = getIkasProductCustomerReviewForm(product);\nconst loginRequired = isCustomerReviewLoginRequired() as unknown as boolean;\nconst isLoggedIn = hasCustomer(customerStore) as unknown as boolean;\n\n// If login is required and user is not logged in\nif (loginRequired && !isLoggedIn) {\n return (\n <div>\n <p>Please log in to write a review.</p>\n <button onClick={() => Router.navigateToPage(\"LOGIN\")}>Log In</button>\n </div>\n );\n}\n\n// Star rating input\nconst [hoverStar, setHoverStar] = useState(0);\n\n<div className=\"star-input\">\n {[1, 2, 3, 4, 5].map((star) => (\n <button\n key={star}\n className={star <= (hoverStar || reviewForm.star.value) ? \"filled\" : \"empty\"}\n onMouseEnter={() => setHoverStar(star)}\n onMouseLeave={() => setHoverStar(0)}\n onClick={() => setCustomerReviewFormStar(reviewForm, star)}\n >\n ★\n </button>\n ))}\n</div>\n\n// Title and comment inputs\n<input\n value={reviewForm.title.value}\n onInput={(e) => setCustomerReviewFormTitle(reviewForm, (e.target as HTMLInputElement).value)}\n/>\n<textarea\n value={reviewForm.comment.value}\n onInput={(e) => setCustomerReviewFormComment(reviewForm, (e.target as HTMLTextAreaElement).value)}\n/>\n\n// Submit\nconst handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitCustomerReviewForm(reviewForm);\n if (success) {\n // Review submitted successfully\n }\n};\n```\n\n### Key Functions\n\n| Function | Purpose |\n|----------|---------|\n| `getProductCustomerReviews(product)` | Get reviews for a product |\n| `getIkasProductCustomerReviewForm(product)` | Get the review form model |\n| `setCustomerReviewFormTitle(form, value)` | Set review title |\n| `setCustomerReviewFormStar(form, value)` | Set star rating (1-5) |\n| `setCustomerReviewFormComment(form, value)` | Set review comment |\n| `submitCustomerReviewForm(form)` | Submit the review |\n| `isCustomerReviewLoginRequired()` | Check if login is needed to review |\n| `getIkasCustomerReviewFormattedDate(review)` | Format review date |",
|
|
272
|
+
"tags": [
|
|
273
|
+
"reviews",
|
|
274
|
+
"ratings",
|
|
275
|
+
"stars",
|
|
276
|
+
"feedback",
|
|
277
|
+
"customer",
|
|
278
|
+
"form"
|
|
279
|
+
]
|
|
195
280
|
},
|
|
196
281
|
"slider-overlay-patterns": {
|
|
197
282
|
"title": "Slider & Overlay Patterns",
|
|
198
283
|
"description": "IkasThemeSlider for carousels, IkasThemeOverlay for modals/drawers, IkasThemeInfiniteScroller for lazy loading",
|
|
199
284
|
"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": [
|
|
285
|
+
"tags": [
|
|
286
|
+
"slider",
|
|
287
|
+
"carousel",
|
|
288
|
+
"overlay",
|
|
289
|
+
"modal",
|
|
290
|
+
"drawer",
|
|
291
|
+
"infinite-scroll",
|
|
292
|
+
"lazy-loading"
|
|
293
|
+
]
|
|
201
294
|
},
|
|
202
295
|
"blog-patterns": {
|
|
203
296
|
"title": "Blog Patterns",
|
|
204
297
|
"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 {
|
|
206
|
-
"tags": [
|
|
298
|
+
"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 {\n IkasBlogList,\n getIkasBlogFormattedDate,\n getIkasBlogHref,\n hasBlogListNextPage,\n getBlogListNextPage,\n hasBlogListPrevPage,\n getBlogListPrevPage,\n getDefaultSrc,\n} from \"@ikas/bp-storefront\";\n\n// In your component:\nconst blogs = blogList?.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) |",
|
|
299
|
+
"tags": [
|
|
300
|
+
"blog",
|
|
301
|
+
"article",
|
|
302
|
+
"posts",
|
|
303
|
+
"pagination",
|
|
304
|
+
"date",
|
|
305
|
+
"content"
|
|
306
|
+
]
|
|
207
307
|
},
|
|
208
308
|
"navigation-patterns": {
|
|
209
309
|
"title": "Navigation & Routing Patterns",
|
|
210
310
|
"description": "Router.navigate, page navigation, href getters, breadcrumbs, and query parameters",
|
|
211
311
|
"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": [
|
|
312
|
+
"tags": [
|
|
313
|
+
"navigation",
|
|
314
|
+
"router",
|
|
315
|
+
"routing",
|
|
316
|
+
"links",
|
|
317
|
+
"breadcrumbs",
|
|
318
|
+
"href",
|
|
319
|
+
"pages"
|
|
320
|
+
]
|
|
213
321
|
},
|
|
214
322
|
"real-world-architecture": {
|
|
215
323
|
"title": "Real-World Theme Architecture",
|
|
216
324
|
"description": "How production themes are structured: page types, section composition, component reuse, and prop design patterns",
|
|
217
325
|
"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": [
|
|
326
|
+
"tags": [
|
|
327
|
+
"architecture",
|
|
328
|
+
"theme",
|
|
329
|
+
"pages",
|
|
330
|
+
"sections",
|
|
331
|
+
"composition",
|
|
332
|
+
"reuse",
|
|
333
|
+
"responsive",
|
|
334
|
+
"production"
|
|
335
|
+
]
|
|
219
336
|
}
|
|
220
337
|
}
|
|
221
338
|
}
|