@feeef.dev/cli 0.2.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/LICENSE +6 -0
- package/README.md +94 -0
- package/dist/bin.js +2752 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2730 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
- package/scaffolds/blank/.cursor/rules/feeef-theme.mdc +34 -0
- package/scaffolds/blank/.cursor/rules/filterator.mdc +13 -0
- package/scaffolds/blank/.cursor/rules/order-form.mdc +17 -0
- package/scaffolds/blank/.cursor/rules/theme-source.mdc +32 -0
- package/scaffolds/blank/.cursor/skills/feeef-filterator/SKILL.md +15 -0
- package/scaffolds/blank/.cursor/skills/feeef-theme/SKILL.md +56 -0
- package/scaffolds/blank/.cursor/skills/feeef-theme/reference.md +73 -0
- package/scaffolds/blank/.vscode/extensions.json +3 -0
- package/scaffolds/blank/.vscode/settings.json +13 -0
- package/scaffolds/blank/AGENTS.md +104 -0
- package/scaffolds/blank/README.md +21 -0
- package/scaffolds/blank/docs/00-INDEX.md +33 -0
- package/scaffolds/blank/docs/03-CUSTOM-COMPONENTS.md +116 -0
- package/scaffolds/blank/docs/04-WORKFLOW.md +129 -0
- package/scaffolds/blank/docs/05-ANTI-PATTERNS.md +80 -0
- package/scaffolds/blank/docs/07-SHARED-COMPONENTS.md +144 -0
- package/scaffolds/blank/docs/08-ORDER-FORM.md +376 -0
- package/scaffolds/blank/docs/09-THEME-PORTING.md +211 -0
- package/scaffolds/blank/docs/10-SCHEMA-LIBRARY.md +127 -0
- package/scaffolds/blank/docs/11-PAGES-CHECKOUT-THANKS-PRODUCT.md +81 -0
- package/scaffolds/blank/docs/12-DARK-LIGHT-THEME.md +164 -0
- package/scaffolds/blank/docs/13-TEMPLATE-I18N.md +320 -0
- package/scaffolds/blank/docs/14-FILTERATOR.md +433 -0
- package/scaffolds/blank/docs/16-AUTHORING-TS-IMPORTS.md +73 -0
- package/scaffolds/blank/feeef.template.json +7 -0
- package/scaffolds/blank/locales/README.md +4 -0
- package/scaffolds/blank/locales/ar.json +10 -0
- package/scaffolds/blank/locales/en.json +10 -0
- package/scaffolds/blank/package-lock.json +1975 -0
- package/scaffolds/blank/package.json +23 -0
- package/scaffolds/blank/pages/home/components/hero.tsx +34 -0
- package/scaffolds/blank/pages/home/page.json +3 -0
- package/scaffolds/blank/props.json +6 -0
- package/scaffolds/blank/schema.ts +49 -0
- package/scaffolds/blank/tsconfig.json +24 -0
- package/scaffolds/blank/types/feeef-live-scope.d.ts +162 -0
- package/scaffolds/blank/types/feeef-template-schema.d.ts +84 -0
- package/vendor/template-kit/README.md +64 -0
- package/vendor/template-kit/bin/dev.mjs +61 -0
- package/vendor/template-kit/bin/feeef-template.mjs +212 -0
- package/vendor/template-kit/data/lithium-schema.json +4733 -0
- package/vendor/template-kit/lib/build.mjs +377 -0
- package/vendor/template-kit/lib/compile-component.mjs +188 -0
- package/vendor/template-kit/lib/component-meta.mjs +481 -0
- package/vendor/template-kit/lib/library.mjs +168 -0
- package/vendor/template-kit/lib/locales.mjs +68 -0
- package/vendor/template-kit/lib/paths.mjs +84 -0
- package/vendor/template-kit/lib/shared.mjs +268 -0
- package/vendor/template-kit/lib/theme-schema.mjs +300 -0
- package/vendor/template-kit/lib/unpack.mjs +324 -0
- package/vendor/template-kit/lib/validate.mjs +207 -0
- package/vendor/template-kit/schemas/component.schema.json +55 -0
- package/vendor/template-kit/schemas/template.schema.json +18 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Theme Porting Playbook — Lessons from Dawn
|
|
2
|
+
|
|
3
|
+
> Theme package copy — paths assume this theme folder is the project root.
|
|
4
|
+
|
|
5
|
+
Use this when porting a reference storefront theme (Shopify Dawn, etc.) into Feeef Lithium custom components, or when starting a **new** theme after Dawn.
|
|
6
|
+
|
|
7
|
+
Companion: [`AGENTS.md`](../AGENTS.md), [`05-ANTI-PATTERNS.md`](./05-ANTI-PATTERNS.md), [`08-ORDER-FORM.md`](./08-ORDER-FORM.md), [`07-SHARED-COMPONENTS.md`](./07-SHARED-COMPONENTS.md).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 0. Goal
|
|
12
|
+
|
|
13
|
+
Match **craft and behavior**, not pixel-perfect Shopify Liquid. Feeef has different APIs (products list, cart, COD). Document intentional gaps (mega-menus, Color metafields, newsletter).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Workflow (new theme or major port)
|
|
18
|
+
|
|
19
|
+
1. Unpack / scaffold source under `templates/<theme>-src/` (Dawn: ``).
|
|
20
|
+
2. Promote chrome to **`shared/components/`** on day one: header-bar, footer, search, cart drawer — pages only `$ref`.
|
|
21
|
+
3. Pixel-compare reference vs local at **same viewport** (e.g. 1440px) with browser tools.
|
|
22
|
+
4. Wire Feeef APIs early (list, search, cart, shipping) — UI polish without data looks “broken”.
|
|
23
|
+
5. After every batch: `npm run build` (or `template:dev`).
|
|
24
|
+
6. Hard-refresh the preview store (`FEEEF_TEMPLATE_DATA_PATH`).
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm run dev
|
|
28
|
+
# store: http://<slug>.localhost.feeef.org:3000/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Shared chrome (mandatory)
|
|
34
|
+
|
|
35
|
+
| Block | Must be shared | Notes |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| Announcement + header + drawers | `shared.dawn-header-bar` (or theme equivalent) | Search + cart live here |
|
|
38
|
+
| Footer | `shared.dawn-footer` | |
|
|
39
|
+
| COD / order shell | Prefer native `product_order_form` or full `08` contract | Simplified stubs are incomplete |
|
|
40
|
+
|
|
41
|
+
**Do not** paste header/footer JSX into home + products + product shells. Duplication caused search/cart drift during Dawn port.
|
|
42
|
+
|
|
43
|
+
Open cart from PDP after add:
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
window.dispatchEvent(new CustomEvent('dawn:open-cart'));
|
|
47
|
+
// Header listens once:
|
|
48
|
+
// window.addEventListener('dawn:open-cart', () => setIsCartOpen(true));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 3. Craft / visual rules (Dawn-class)
|
|
54
|
+
|
|
55
|
+
- Restraint: white chrome, thin rules, airy type — not purple gradients / card spam.
|
|
56
|
+
- Prefer **Assistant** (or theme font) loaded once via `<link>` id guard.
|
|
57
|
+
- Brand colors: `hsl(var(--primary))`. Chrome black/white OK only for rare craft accents; prefer tokens so dark/light works — see `docs/12-DARK-LIGHT-THEME.md`.
|
|
58
|
+
- Arabic / RTL stores: **multilang themes** use `useFeeefLocale()` + `dir={locale === 'ar' ? 'rtl' : 'ltr'}` and `t()` (see [13-TEMPLATE-I18N.md](./13-TEMPLATE-I18N.md)). Force **`dir="ltr"`** only when intentionally matching an LTR-only reference craft on an RTL store.
|
|
59
|
+
- Never wrap the whole page shell in `min-height: 100vh` in a way that pushes PDP content down — header chrome should be height-of-content.
|
|
60
|
+
|
|
61
|
+
### Product cards
|
|
62
|
+
|
|
63
|
+
- Square media (`aspect-ratio: 1 / 1`).
|
|
64
|
+
- Hover secondary image when `media` has a **second distinct** URL:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
const raw = [product.photoUrl].concat(product.media || []).filter(Boolean);
|
|
68
|
+
const seen = {};
|
|
69
|
+
const images = raw.filter((src) => {
|
|
70
|
+
if (seen[src]) return false;
|
|
71
|
+
seen[src] = true;
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
const secondary = images[1] || null;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `product.media` is **`string[]`**, not `{ url }[]`.
|
|
78
|
+
- Enable `showSecondaryImage` by default on PLP / featured.
|
|
79
|
+
|
|
80
|
+
### Search
|
|
81
|
+
|
|
82
|
+
- Feeef list search param: **`params.search`** (also send `q` for compatibility).
|
|
83
|
+
- URL query stays `?q=` for the storefront.
|
|
84
|
+
- Prefer **predictive search** panel (live results + “Search for …” → `/products?q=`), not a bare full-screen input.
|
|
85
|
+
|
|
86
|
+
### Footer
|
|
87
|
+
|
|
88
|
+
- Shopify “Subscribe to our emails” is **unsupported** on Feeef — replace with **About / store bio** (`store.description` stripped of HTML, or props fallback).
|
|
89
|
+
- Keep Quick links / Info / mission columns; social from `store.contacts`.
|
|
90
|
+
|
|
91
|
+
### PLP filters
|
|
92
|
+
|
|
93
|
+
- Map reference facets to Feeef reality: Availability + Price + **Category** (not Shopify Color metafields unless you build metafield support).
|
|
94
|
+
- Empty search (`?q=product` with no matches) is valid — compare filled `/products` to a collection page.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 4. Cart & PDP (high bug rate)
|
|
99
|
+
|
|
100
|
+
### Add to cart
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
// WRONG — does not add a line
|
|
104
|
+
cart.updateCurrentItem({});
|
|
105
|
+
document.querySelector('[aria-label="Cart"]')?.click();
|
|
106
|
+
|
|
107
|
+
// RIGHT
|
|
108
|
+
if (!cart.getCurrentItem() || cart.getCurrentItem().product.id !== product.id) {
|
|
109
|
+
cart.setCurrentItem({ product, quantity: 1 });
|
|
110
|
+
}
|
|
111
|
+
cart.addCurrentItemToCart(); // or cart.add({ product, quantity, variantPath })
|
|
112
|
+
window.dispatchEvent(new CustomEvent('dawn:open-cart'));
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Bootstrap current item on PDP mount
|
|
116
|
+
|
|
117
|
+
Qty steppers and add-to-cart need `cart.getCurrentItem()`. Set it when product loads. Variants must `setCurrentItem` / `updateCurrentItem` safely.
|
|
118
|
+
|
|
119
|
+
### Shipping bootstrap — **once per product**
|
|
120
|
+
|
|
121
|
+
Without native `OrderForm`, custom PDPs must wire shipping (see `lib/bootstrap-product-cart-shipping.ts` + scope API). Inline in react-live (no imports):
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
cart.setShippingMethod(product.shippingMethod || store, false);
|
|
125
|
+
cart.setShippingAddress({ type: 'home', state: null, city: null, country: 'dz', ... }, false);
|
|
126
|
+
// then ff.shippingPrices.find → cart.setShippingPrice(sp, false)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Critical bug (Dawn):** if the bootstrap `useEffect` depends on **`cartCtx`** (object identity), every `updateShippingAddress` recreates context → effect re-runs → `setShippingAddress({ state: null })` **wipes** state/city/delivery types.
|
|
130
|
+
|
|
131
|
+
**Correct pattern:**
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
const bootKey = React.useRef('');
|
|
135
|
+
const cartReady = !!cartCtx?.cart;
|
|
136
|
+
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
if (!product || !cartReady || !store || !cartCtx?.cart) return;
|
|
139
|
+
const cart = cartCtx.cart;
|
|
140
|
+
// setCurrentItem if needed…
|
|
141
|
+
const key = product.id + ':' + store.id;
|
|
142
|
+
if (bootKey.current === key) return; // already bootstrapped
|
|
143
|
+
bootKey.current = key;
|
|
144
|
+
// setShippingMethod + setShippingAddress PRESERVING existing.state/city
|
|
145
|
+
// load shippingPrices once
|
|
146
|
+
}, [product?.id, store?.id, cartReady, ff]);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- Depend on **`cartReady` (boolean)**, not `cartCtx`.
|
|
150
|
+
- Preserve `existing.state` / `existing.city` / `existing.type` when writing address.
|
|
151
|
+
- `getAvailableShippingTypes()` is **empty until state (wilaya) is set** — show a hint, then full-width delivery cards.
|
|
152
|
+
|
|
153
|
+
### Floating labels on `<select>`
|
|
154
|
+
|
|
155
|
+
Empty selects that use a first option labeled `"State"` **overlap** the floating label.
|
|
156
|
+
|
|
157
|
+
- First option: `<option value="" disabled hidden />` (no text).
|
|
158
|
+
- Always float the label for selects (`.dn-field--select .dn-field__label` at top).
|
|
159
|
+
- Reserve `padding-top` on the select even when empty.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 5. Order / COD
|
|
164
|
+
|
|
165
|
+
Follow **[`08-ORDER-FORM.md`](./08-ORDER-FORM.md)** for production behavior:
|
|
166
|
+
|
|
167
|
+
- Submit via `POST /api/orders/create` (not bare `ff.orders.send` in production).
|
|
168
|
+
- Draft + pending, geo cascade, security.
|
|
169
|
+
|
|
170
|
+
Dawn’s shared `dawn-order-form` and early PDP COD stubs are **visual** until they match that contract.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 6. Intentional gaps vs Shopify Dawn
|
|
175
|
+
|
|
176
|
+
| Shopify Dawn | Feeef approach |
|
|
177
|
+
|---|---|
|
|
178
|
+
| Mega-menu Bags/Shoes | Flat links or custom later |
|
|
179
|
+
| Color metafield filters | Category (or skip) |
|
|
180
|
+
| Newsletter signup | Store bio / About |
|
|
181
|
+
| Best-selling sort | Map to Feeef `sort` values you support |
|
|
182
|
+
| Country/currency selector | Optional / store config |
|
|
183
|
+
| Predictive collections/pages | Products-only is enough |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 7. New-theme checklist
|
|
188
|
+
|
|
189
|
+
- [ ] `shared/` header (search predictive + cart drawer + `dawn:open-cart`)
|
|
190
|
+
- [ ] `shared/` footer (bio, not fake newsletter)
|
|
191
|
+
- [ ] Home / products / product / contact use `$ref` for chrome
|
|
192
|
+
- [ ] PDP: `setCurrentItem` + shipping bootstrap **once** + real add-to-cart
|
|
193
|
+
- [ ] PLP: `search` param, secondary image hover, Dawn-like filter bar
|
|
194
|
+
- [ ] Selects: no label overlap
|
|
195
|
+
- [ ] Delivery cards: expanded, appear after state
|
|
196
|
+
- [ ] `dir="ltr"` on chrome if matching LTR reference on RTL stores
|
|
197
|
+
- [ ] `template:build` + browser verify vs reference
|
|
198
|
+
- [ ] Order form path follows docs/08 (or registry form)
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 8. File map (Dawn)
|
|
203
|
+
|
|
204
|
+
| Area | Path |
|
|
205
|
+
|---|---|
|
|
206
|
+
| Shared header | `shared/components/dawn-header-bar/` |
|
|
207
|
+
| Shared footer | `shared/components/dawn-footer/` |
|
|
208
|
+
| Home shell | `pages/home/.../custom-dawn-header-home/` |
|
|
209
|
+
| Products PLP | `.../dawn-collection-banner/` + `dawn-collection-grid/` |
|
|
210
|
+
| PDP | `pages/product/.../custom-1777344732828270/` |
|
|
211
|
+
| Reference Liquid | `templates/_references/dawn/` (read-only) |
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Schema Library (`schema.library.json`)
|
|
2
|
+
|
|
3
|
+
Theme-bundled **drag-drop catalog** for the merchant template editor.
|
|
4
|
+
|
|
5
|
+
Components listed here can be **unused in `data.json`**. That is intentional: the theme ships extras the merchant can add later from the UI.
|
|
6
|
+
|
|
7
|
+
## Three planes (do not confuse)
|
|
8
|
+
|
|
9
|
+
| Plane | Theme source | Built output | Role |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| **Schema** | `schema.ts` (overlay) | `dist/schema.json` | Editor chrome: pages, layouts, root `propsSchema`, Lithium built-ins + overrides |
|
|
12
|
+
| **Library** | `library/components/` | `schema.library` on schema | Optional drag-drop extras (inline copy) |
|
|
13
|
+
| **Data** | `pages/`, `props.json` | `dist/data.json` | Live instances / merchant content |
|
|
14
|
+
|
|
15
|
+
| Catalog entry | Storage | Insert mode | Audience |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| **Built-in** | Lithium `components` (merged into schema) | Registry `type` + defaults | Stock blocks |
|
|
18
|
+
| **Theme library** | `schema.library.json` / `schema.library` | **Inline copy** | Theme author extras |
|
|
19
|
+
| **My library / Marketplace** | DB `template_components` | `type: "reference"` + `refId` | Merchant / public reuse |
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
flowchart LR
|
|
23
|
+
schemaTs["schema.ts overlay"]
|
|
24
|
+
lithium["Lithium built-ins"]
|
|
25
|
+
disk["library/components/id/"]
|
|
26
|
+
build["template:build"]
|
|
27
|
+
schema["dist/schema.json"]
|
|
28
|
+
editor["Flutter Theme tab"]
|
|
29
|
+
data["templateData placement"]
|
|
30
|
+
schemaTs --> build
|
|
31
|
+
lithium --> build
|
|
32
|
+
disk --> build
|
|
33
|
+
build --> schema
|
|
34
|
+
schema --> editor
|
|
35
|
+
editor -->|"copy-on-drop"| data
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Authoring (template-kit)
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
templates/dawn-src/
|
|
42
|
+
library/components/<id>/
|
|
43
|
+
component.json # type, code meta, props, schemas + catalog fields
|
|
44
|
+
component.jsx # optional
|
|
45
|
+
slots/… children/…
|
|
46
|
+
dist/
|
|
47
|
+
schema.library.json
|
|
48
|
+
data.json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Catalog fields on `component.json`
|
|
52
|
+
|
|
53
|
+
| Field | Role |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `libraryId` / folder name | Stable `id` in the library list |
|
|
56
|
+
| `title`, `subtitle`, `category`, `tags`, `imageUrl` | Editor card |
|
|
57
|
+
| `type` | Usually `custom`; may be a built-in registry type |
|
|
58
|
+
| `props` | Becomes `propsDefault` on drop |
|
|
59
|
+
| `code` / `component.jsx` | Custom JSX |
|
|
60
|
+
| `propsSchema`, `slotsSchema`, `slotsLayout`, `slots`, `children` | Seeded on drop |
|
|
61
|
+
|
|
62
|
+
Build:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cd storefront && npm run template:build
|
|
66
|
+
# → dist/schema.library.json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Wire shape
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"version": "1.0",
|
|
74
|
+
"components": [
|
|
75
|
+
{
|
|
76
|
+
"id": "dawn-promo-banner",
|
|
77
|
+
"title": "Promo Banner",
|
|
78
|
+
"subtitle": null,
|
|
79
|
+
"category": "marketing",
|
|
80
|
+
"tags": ["banner"],
|
|
81
|
+
"imageUrl": null,
|
|
82
|
+
"type": "custom",
|
|
83
|
+
"code": "function App() { return <div>{props.text}</div>; }",
|
|
84
|
+
"propsSchema": { "text": { "type": "string" } },
|
|
85
|
+
"propsDefault": { "text": "Sale" },
|
|
86
|
+
"slotsSchema": null,
|
|
87
|
+
"slotsDefault": null,
|
|
88
|
+
"slotsLayout": null,
|
|
89
|
+
"children": null
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Serving
|
|
96
|
+
|
|
97
|
+
| Endpoint | Behavior |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `GET /api/template/schema` | Lithium `schema.json` **plus** `library: [...]` from `template/schema.library.json` |
|
|
100
|
+
| `GET /api/template/library` | Standalone library document |
|
|
101
|
+
| Dev | `FEEEF_TEMPLATE_LIBRARY_PATH` → kit `dist/schema.library.json` |
|
|
102
|
+
|
|
103
|
+
`StoreTemplate.schema` should include the same `library` array when publishing a theme so `TemplateFetchResult.fromStoreTemplate` exposes it via `rawSchema['library']`.
|
|
104
|
+
|
|
105
|
+
## Editor behavior
|
|
106
|
+
|
|
107
|
+
- Tab **Theme** lists `controller.themeLibraryEntries` (`rawSchema['library']`).
|
|
108
|
+
- Drop / tap → **inline copy** into `templateData` (`buildThemeLibraryComponent`).
|
|
109
|
+
- Not a live link to the theme file: later theme updates do **not** rewrite existing placements (unlike DB `reference`).
|
|
110
|
+
- Prefer `shared/` + `$ref` for compile-time consistency across pages you ship in `data.json`; use **library** for optional blocks merchants may add.
|
|
111
|
+
|
|
112
|
+
## vs `shared/`
|
|
113
|
+
|
|
114
|
+
| | `shared/components` | `library/components` |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| Purpose | DRY placements already in the theme tree | Optional catalog for the editor |
|
|
117
|
+
| Build | Inlined into `data.json` via `$ref` | Emitted as `schema.library.json` |
|
|
118
|
+
| Unused OK? | No (only if referenced) | **Yes** |
|
|
119
|
+
|
|
120
|
+
A component can exist in **both** (shared for shipped pages + library for drag-drop of the same block elsewhere).
|
|
121
|
+
|
|
122
|
+
## Rules for AI
|
|
123
|
+
|
|
124
|
+
1. New optional theme UI → add under `library/components/<id>/`, rebuild.
|
|
125
|
+
2. Do not put library-only blocks into `data.json` unless the blank theme should include them.
|
|
126
|
+
3. When packing a `StoreTemplate`, merge `library` into `schema`.
|
|
127
|
+
4. Flutter insert = copy-on-drop, not `type: "reference"` (no DB id).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Template Pages — checkout, thanks, product PDP
|
|
2
|
+
|
|
3
|
+
> Theme package copy — paths assume this theme folder is the project root.
|
|
4
|
+
|
|
5
|
+
Canonical routes and the **single-`main`-section** pattern.
|
|
6
|
+
|
|
7
|
+
## Page matrix
|
|
8
|
+
|
|
9
|
+
| Page id | Path | Sections | Notes |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `product` | `/p/:id` **and** `/products/:id` | **`main` only** | Both URLs render the same PDP. Prefer `/p/` for links; `/products/:id` is an alias. |
|
|
12
|
+
| `checkout` | `/checkout` | **`main` only** | Cart checkout. Prefer registry `cart_order_form` in a form slot. |
|
|
13
|
+
| `thank_you` | `/thanks` | **`main` only** | Optional post-order page. Query params carry order context. Legacy `/thank-you` redirects. |
|
|
14
|
+
| `not_found` | Next.js not-found | **`main` only** | Editable 404 (`type: "not_found"` or custom). |
|
|
15
|
+
| `products` | `/products` | multi | Collection / PLP (unchanged). |
|
|
16
|
+
| `home` / `contact` / … | … | multi | Unchanged. |
|
|
17
|
+
|
|
18
|
+
## Pattern: one section, layout in slots
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
pages.<id>.sections.main.components[0] ← page shell (custom)
|
|
22
|
+
slots.header[]
|
|
23
|
+
slots.body[] | slots.form[]
|
|
24
|
+
slots.footer[]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Next.js page components only render `sections.main`. They do **not** invent header/footer section keys.
|
|
28
|
+
|
|
29
|
+
Use a single `main` section; put chrome in slots.
|
|
30
|
+
|
|
31
|
+
### Product runtime fallback
|
|
32
|
+
|
|
33
|
+
`ProductPageContent` prefers `main`. If `main` is empty, it still renders the legacy multi-section tree (`header` / `top` / `start` / `end` / `bottom` / `footer`) for older store blobs.
|
|
34
|
+
|
|
35
|
+
New themes (Dawn) ship `main` + slots only.
|
|
36
|
+
|
|
37
|
+
## `/checkout`
|
|
38
|
+
|
|
39
|
+
- Route: `app/checkout/page.tsx`
|
|
40
|
+
- Dawn: `pages/checkout/…/dawn-checkout-shell` with slots `header` | `form` | `footer`
|
|
41
|
+
- Form slot: `type: "cart_order_form"` (multi-item cart). `product_order_form` also works if the cart’s current item is enough.
|
|
42
|
+
- Empty cart: shell shows empty state + link to `/products`.
|
|
43
|
+
- On success, `CartOrderFormWrapper` redirects to `/thanks?order=&name=&phone=&total=`.
|
|
44
|
+
|
|
45
|
+
## `/thanks`
|
|
46
|
+
|
|
47
|
+
- Route: `app/thanks/page.tsx` → template key **`thank_you`**
|
|
48
|
+
- Query params (all optional):
|
|
49
|
+
|
|
50
|
+
| Param | Meaning |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `order` / `id` | Order id |
|
|
53
|
+
| `name` / `customerName` | Customer name |
|
|
54
|
+
| `total` / `amount` | Display total |
|
|
55
|
+
| `phone` | Phone |
|
|
56
|
+
|
|
57
|
+
- Built-in registry type: `thank_you` (reads those params).
|
|
58
|
+
- Dawn: `dawn-thanks-shell` (`$ref` shared) with slots `header` | `body` | `footer`; body hosts the thank-you JSX (reads those params).
|
|
59
|
+
- After a successful COD submit, redirect e.g. `/thanks?order={id}&name={encodeURIComponent(name)}`.
|
|
60
|
+
- Cart drawer “Check out” must link to **`/checkout`**, not `/cart`.
|
|
61
|
+
|
|
62
|
+
## `/p/[...id]` and `/products/[productId]`
|
|
63
|
+
|
|
64
|
+
- Routes: `app/p/[...id]/page.tsx` **and** `app/products/[productId]/page.tsx` (shared loader — **both fully work**, no redirect)
|
|
65
|
+
- Loads product by slug; sets current cart item + shipping bootstrap
|
|
66
|
+
- Dawn: `dawn-product-shell` with slots `header` | `body` | `footer`
|
|
67
|
+
- Prefer `/p/` for links (Dawn, OG, schema); `/products/:id` stays as a working alias
|
|
68
|
+
|
|
69
|
+
## Authoring checklist
|
|
70
|
+
|
|
71
|
+
1. Add / edit under `pages/<page>/sections/main/…`
|
|
72
|
+
2. Keep a single root (or a short stack) in `main`; put chrome in slots
|
|
73
|
+
3. `npm run build`
|
|
74
|
+
4. Preview with `npm run dev`
|
|
75
|
+
5. Do **not** reintroduce multi-section keys on these three pages in new themes
|
|
76
|
+
|
|
77
|
+
## Related
|
|
78
|
+
|
|
79
|
+
- Order / COD contract: [08-ORDER-FORM.md](./08-ORDER-FORM.md)
|
|
80
|
+
- Architecture pages list: [01-ARCHITECTURE.md](./01-ARCHITECTURE.md)
|
|
81
|
+
- Schema library (optional drag-drop blocks): [10-SCHEMA-LIBRARY.md](./10-SCHEMA-LIBRARY.md)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Dark / Light Theme (on top of native Lithium theme)
|
|
2
|
+
|
|
3
|
+
> Theme package copy — paths assume this theme folder is the project root.
|
|
4
|
+
|
|
5
|
+
How Feeef storefront themes support **light**, **dark**, and **system** modes — and how custom / Dawn JSX must ride that stack instead of inventing a parallel palette.
|
|
6
|
+
|
|
7
|
+
## Mental model (three layers)
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
1. Template default template.data.props.theme.mode → "light" | "dark" | "system"
|
|
11
|
+
2. Runtime class next-themes → <html class="dark"> (or not)
|
|
12
|
+
3. CSS tokens :root / .dark variables → hsl(var(--…))
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Layer | Who sets it | Effect |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Template `props.theme.mode` | Merchant / theme author | Default mode when the store loads (`StyleWrapper` calls `setTheme`) |
|
|
18
|
+
| `useTheme()` / toggle | Visitor or custom JSX | Overrides to light/dark/system for the session |
|
|
19
|
+
| CSS variables | Lithium + Tailwind theme | Actual colors; **`.dark` swaps surfaces + brand** |
|
|
20
|
+
|
|
21
|
+
Native registry components (order form, navbar, shadcn UI) already follow these tokens. Custom JSX must do the same.
|
|
22
|
+
|
|
23
|
+
## Sources of truth
|
|
24
|
+
|
|
25
|
+
## Template default mode
|
|
26
|
+
|
|
27
|
+
In template root props (kit: `props.json` or `template.data.props`):
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"theme": {
|
|
32
|
+
"mode": "light",
|
|
33
|
+
"rounded": 100,
|
|
34
|
+
"font": "expoArabic"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Options: `"light"` | `"dark"` | `"system"`.
|
|
40
|
+
|
|
41
|
+
`StyleWrapper` reads `template.data.props.theme.mode` and, when it is not `"light"`, calls `setTheme(themeMode)` so the store opens in the authored default.
|
|
42
|
+
|
|
43
|
+
## What happens in dark mode
|
|
44
|
+
|
|
45
|
+
1. `next-themes` puts `class="dark"` on the document root (`attribute="class"`).
|
|
46
|
+
2. **Surfaces** flip via `globals.css` `.dark { --background: …; --foreground: …; … }`.
|
|
47
|
+
3. **Brand** flips via `StyleWrapper`: store `decoration.primary` / `secondary` (and on*) are converted to HSL, then `hslToDark(...)` writes `.dark { --primary: … }` etc.
|
|
48
|
+
|
|
49
|
+
So in custom CSS you never hardcode “the brand hex for dark”. You write:
|
|
50
|
+
|
|
51
|
+
```css
|
|
52
|
+
color: hsl(var(--foreground));
|
|
53
|
+
background: hsl(var(--background));
|
|
54
|
+
border-color: hsl(var(--border));
|
|
55
|
+
background: hsl(var(--primary));
|
|
56
|
+
color: hsl(var(--primary-foreground));
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
When the visitor (or template) switches mode, tokens update automatically.
|
|
60
|
+
|
|
61
|
+
## Token cheat-sheet (prefer these)
|
|
62
|
+
|
|
63
|
+
| Token | Use |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `--background` / `--foreground` | Page / section surfaces and body text |
|
|
66
|
+
| `--card` / `--card-foreground` | Cards, panels |
|
|
67
|
+
| `--muted` / `--muted-foreground` | Secondary text, soft fills |
|
|
68
|
+
| `--border` / `--input` / `--ring` | Borders, inputs, focus |
|
|
69
|
+
| `--primary` / `--primary-foreground` | Brand CTAs (store decoration) |
|
|
70
|
+
| `--secondary` / `--secondary-foreground` | Secondary brand |
|
|
71
|
+
| `--destructive` / `--destructive-foreground` | Errors / danger |
|
|
72
|
+
| `--corners-card`, `--corners-fields`, `--corners-buttons-*` | Radii (mode-independent) |
|
|
73
|
+
|
|
74
|
+
Always wrap color channels with `hsl(...)`:
|
|
75
|
+
|
|
76
|
+
```jsx
|
|
77
|
+
style={{ background: "hsl(var(--background))", color: "hsl(var(--foreground))" }}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
.my-block {
|
|
82
|
+
background: hsl(var(--card));
|
|
83
|
+
color: hsl(var(--card-foreground));
|
|
84
|
+
border: 1px solid hsl(var(--border));
|
|
85
|
+
border-radius: var(--corners-card);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Optional softer borders that still track mode:
|
|
90
|
+
|
|
91
|
+
```css
|
|
92
|
+
border: 1px solid color-mix(in srgb, hsl(var(--foreground)) 12%, transparent);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Custom JSX: light/dark toggle
|
|
96
|
+
|
|
97
|
+
`useTheme()` is in the react-live scope (no import):
|
|
98
|
+
|
|
99
|
+
```jsx
|
|
100
|
+
function App() {
|
|
101
|
+
const { resolvedTheme, setTheme } = useTheme();
|
|
102
|
+
const isDark = resolvedTheme === "dark";
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
aria-label="Toggle theme"
|
|
108
|
+
onClick={() => setTheme(isDark ? "light" : "dark")}
|
|
109
|
+
style={{
|
|
110
|
+
color: "hsl(var(--foreground))",
|
|
111
|
+
background: "hsl(var(--muted))",
|
|
112
|
+
borderRadius: "var(--corners-buttons-medium)",
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
{isDark ? "Light" : "Dark"}
|
|
116
|
+
</button>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Field | Meaning |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `theme` | Stored preference: `light` / `dark` / `system` |
|
|
124
|
+
| `resolvedTheme` | What is actually painted: `light` or `dark` |
|
|
125
|
+
| `setTheme(mode)` | Set preference |
|
|
126
|
+
| `systemTheme` | OS preference when `theme === "system"` |
|
|
127
|
+
|
|
128
|
+
Prefer **`resolvedTheme`** for conditional UI (icons, “is dark?”). Prefer **CSS variables** over branching colors when possible.
|
|
129
|
+
|
|
130
|
+
## Tailwind / native components
|
|
131
|
+
|
|
132
|
+
Registry / shadcn components use semantic classes (`bg-background`, `text-foreground`, `border-border`, `bg-primary`) which map to the same CSS variables. `dark:` variants work because of the `dark` class on `<html>`.
|
|
133
|
+
|
|
134
|
+
In **custom react-live JSX**, Tailwind `dark:` utilities are unreliable (string CSS / limited class pipeline). Prefer CSS variables or a small `<style>` block that uses `hsl(var(--…))`.
|
|
135
|
+
|
|
136
|
+
## Anti-patterns
|
|
137
|
+
|
|
138
|
+
| Don’t | Do |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `color: store.decoration.primary` (Flutter int) | `hsl(var(--primary))` |
|
|
141
|
+
| Hardcoded `#fff` / `#000` for body chrome | `hsl(var(--background))` / `hsl(var(--foreground))` |
|
|
142
|
+
| Duplicate “Dawn dark palette” hex tables | Ride Lithium tokens |
|
|
143
|
+
| `if (dark) color = '#eee'` for brand CTAs | Keep `--primary`; only branch when an asset needs it |
|
|
144
|
+
| Assume template mode alone updates mid-session | Use `useTheme().setTheme` for toggles |
|
|
145
|
+
| Put mode under a page/component `props` | Root `template.data.props.theme.mode` |
|
|
146
|
+
|
|
147
|
+
Hardcoded black/white is OK only for **reference craft** accents that intentionally ignore brand (rare). Prefer tokens for anything that should follow the store theme.
|
|
148
|
+
|
|
149
|
+
## Authoring checklist
|
|
150
|
+
|
|
151
|
+
- [ ] Surfaces and text use `--background` / `--foreground` (or card/muted)
|
|
152
|
+
- [ ] Brand CTAs use `--primary` / `--primary-foreground`
|
|
153
|
+
- [ ] Borders use `--border` or `color-mix` on `--foreground`
|
|
154
|
+
- [ ] Radii use `--corners-*`
|
|
155
|
+
- [ ] Toggle (if any) uses `useTheme()` + `resolvedTheme`
|
|
156
|
+
- [ ] Template default set via `props.theme.mode` when the theme should open dark/system
|
|
157
|
+
- [ ] Preview both modes: set mode in editor / `setTheme('dark')` and hard-refresh `template:dev`
|
|
158
|
+
|
|
159
|
+
## Related
|
|
160
|
+
|
|
161
|
+
- Custom styling rules: [03-CUSTOM-COMPONENTS.md](./03-CUSTOM-COMPONENTS.md)
|
|
162
|
+
- Scope API (`useTheme`, CSS vars): `docs/03-CUSTOM-COMPONENTS.md`
|
|
163
|
+
- Architecture theme note: [01-ARCHITECTURE.md](./01-ARCHITECTURE.md)
|
|
164
|
+
- Anti-patterns: [05-ANTI-PATTERNS.md](./05-ANTI-PATTERNS.md)
|