@orderlyshop/web-components 0.1.0-build.7068 → 0.1.0-build.7075
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/AGENTS.md +12 -10
- package/DESIGN.md +224 -0
- package/README.md +28 -21
- package/bin/orderly-build-category-pages.mjs +3 -1
- package/bin/orderly-hydrate-static-pages.mjs +24 -0
- package/bin/orderly-init-navigation.mjs +187 -19
- package/bin/orderly-init-shop.mjs +25 -17
- package/custom-elements.json +2 -1
- package/dist/browser/orderly-web-components.define.global.js +134 -80
- package/dist/browser/orderly-web-components.define.global.js.br +0 -0
- package/dist/browser/orderly-web-components.define.global.js.gz +0 -0
- package/dist/browser/orderly-web-components.define.global.js.map +1 -1
- package/dist/browser/orderly-web-components.global.js +134 -80
- package/dist/browser/orderly-web-components.global.js.br +0 -0
- package/dist/browser/orderly-web-components.global.js.gz +0 -0
- package/dist/browser/orderly-web-components.global.js.map +1 -1
- package/dist/{default-shop-CQuz1DKx.d.ts → default-shop-CtdjFj4B.d.ts} +8 -1
- package/dist/default-shop.d.ts +1 -1
- package/dist/default-shop.js +1 -1
- package/dist/default-shop.js.br +0 -0
- package/dist/default-shop.js.gz +0 -0
- package/dist/default-shop.js.map +1 -1
- package/dist/define-RfHbS0-w.d.ts +10 -0
- package/dist/define.d.ts +1 -1
- package/dist/define.js +126 -72
- package/dist/define.js.br +0 -0
- package/dist/define.js.gz +0 -0
- package/dist/define.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +132 -78
- package/dist/index.js.br +0 -0
- package/dist/index.js.gz +0 -0
- package/dist/index.js.map +1 -1
- package/dist/{shop-CYr-svkP.d.ts → shop-DDkMlBLC.d.ts} +1 -1
- package/dist/shop.d.ts +2 -2
- package/dist/shop.js +132 -78
- package/dist/shop.js.br +0 -0
- package/dist/shop.js.gz +0 -0
- package/dist/shop.js.map +1 -1
- package/docs/components/README.md +14 -7
- package/docs/components/product-grid.md +5 -3
- package/docs/components/product-rail.md +8 -7
- package/docs/search-syntax.md +38 -0
- package/examples/shop/src/includes/head.html +2 -0
- package/html-custom-data.json +1 -1
- package/package.json +3 -2
- package/dist/define-DThgfT4n.d.ts +0 -10
package/AGENTS.md
CHANGED
|
@@ -13,7 +13,8 @@ Use this package as a headless storefront component library. Prefer native custo
|
|
|
13
13
|
|
|
14
14
|
- Read `README.md`, `DESIGN.md`, `docs/shop-best-practices.md`, `docs/components/README.md`, then `custom-elements.json` or `html-custom-data.json` before shop-specific edits. Read `docs/components/product-rail.md` and `docs/components/product-grid.md` before product listing work.
|
|
15
15
|
- Use `docs/shop-best-practices.md` as the completion checklist for branded shop work. A finished shop must not look like the default scaffold with minor token changes.
|
|
16
|
-
- For new shops, run `npx orderly-init-shop` first. Ask for the Orderly account id and a reference website URL; if no reference exists, ask for a short style description.
|
|
16
|
+
- For new shops, run `npx orderly-init-shop` first. Ask for the Orderly account id, legal webshop owner name, legal owner CVR, and a reference website URL; if no reference exists, ask for a short style description.
|
|
17
|
+
- Set `defaultShop.legalOwnerName` and `defaultShop.legalOwnerCvr`. `orderly-page-layout` renders the required ownership notice after footer content, and shop templates must not replace it.
|
|
17
18
|
- Use the account id for tenant-scoped `src/shop-query.ts`. Use the reference, catalog inspection, and online research as design direction for `src/style.css`; never copy proprietary assets, text, logos, or brand marks without user confirmation.
|
|
18
19
|
- Build the first version around catalog-driven content: homepage sections, category introductions, product context, useful navigation, and a visual system based on `DESIGN.md` tokens plus scoped CSS.
|
|
19
20
|
- Aim for current premium frontend craft: responsive art direction, strong imagery, clear interaction states, appropriate motion, performance, and accessibility.
|
|
@@ -23,6 +24,7 @@ Use this package as a headless storefront component library. Prefer native custo
|
|
|
23
24
|
|
|
24
25
|
- Use `configureShop({ defaultShop: { defaultQuery } })` for shop-wide query scope. `defaultQuery` is a full Core `SearchQuery`.
|
|
25
26
|
- Do not create package-specific account id shortcuts. Tenant scoping is just `SearchQuery.SourceAccountId`.
|
|
27
|
+
- Read `docs/search-syntax.md` before writing category or rail queries. Whitespace in `SearchQuery.Query` is narrowing; use `|` for OR alternatives such as `lego | matchbox | playmobil`. Multiple `SearchQuery.Tags` are narrowing AND-style filters, not broad alternatives.
|
|
26
28
|
- For simple markup-driven searches, use declarative query attributes:
|
|
27
29
|
- `keywords` maps to `SearchQuery.Query`.
|
|
28
30
|
- `tags` maps to `SearchQuery.Tags`.
|
|
@@ -51,14 +53,14 @@ Use this package as a headless storefront component library. Prefer native custo
|
|
|
51
53
|
- Product images should default to `fit="contain"`, white background, and square aspect ratio in product tiles.
|
|
52
54
|
- Use `orderly-stored-image` for package/product images when possible and set the appropriate `image-role` (`product-tile`, `product-detail`, `thumbnail`, `basket-line`, `checkout-line`, or `fullsize`) so image URLs reuse configured CDN dimensions.
|
|
53
55
|
- Product grids should support continuation-token based dynamic paging without requiring a visible load-more button.
|
|
54
|
-
- Use `orderly-home-page` for default storefront homepages instead of generating product rails in app code. Set `basket-mode="inline-desktop"` when the design needs a package-owned desktop basket rail instead of custom portal glue.
|
|
56
|
+
- Use `orderly-home-page` for default storefront homepages instead of generating product rails in app code. Use its `hero` slot/template for a custom homepage hero before replacing the full layout. Set `basket-mode="inline-desktop"` when the design needs a package-owned desktop basket rail instead of custom portal glue.
|
|
55
57
|
- Use `orderly-category-page` for category routes. Default category links should use `/categories/<category-path>/` so builds can generate and hydrate server-rendered category pages. Use `/category.html#id=<encoded-category-path-or-slug>` only for shops that explicitly opt into hash routing. Prefer navigation `heroImage`, `heroLayout: "covers"`, `heroCoverCount`, and category `description` before replacing the category hero template.
|
|
56
58
|
- Keep `orderly-navigation` sticky by default. Use `sticky="false"` only when a shop intentionally wants navigation to scroll away. Use `persist-state state-key="main-menu"` for vertical desktop menus that should remember open branches, and use `slot="menu-before"`/`slot="menu-after"` for store finder, campaign, volunteer, or other menu promo links.
|
|
57
59
|
- Use package-owned basket and checkout surfaces first: `<orderly-basket-icon href="/checkout.html" placement="mobile-fixed" hide-when-empty>`, `basket-mode="inline-desktop"`, and `orderly-checkout-page`.
|
|
58
60
|
- Use `orderly-product-detail-page` for full product routes. Set `share-url` to the product `SearchObject.ShareURL.URL`; the component loads the product through `SearchQuery.ShareUrl`.
|
|
59
|
-
- Keep category page generation in the normal shop build. Use `orderly-build-category-pages` without `--no-hydrate` so homepage/category pages get initial HTML for SEO and less visible loading shift. Pass `--base-url` or set `VITE_ORDERLY_BASE_URL` to override the default `https://service.orderly.shop` snapshot backend.
|
|
61
|
+
- Keep category page generation in the normal shop build. Use `orderly-build-category-pages` without `--no-hydrate` so homepage/category pages get initial HTML for SEO and less visible loading shift. Pass `--base-url` or set `VITE_ORDERLY_BASE_URL` to override the default `https://service.orderly.shop` snapshot backend. Treat warnings about category pages or homepage rails matching 0 products as blockers; use `--strict-empty-categories` in CI when empty categories should fail the build.
|
|
60
62
|
- For real product URLs on traditional hosting, use the package-owned helpers in `server/`: Apache `.htaccess`, Nginx rewrite config, PHP product renderer, and optional Node product snapshot endpoint. Do not copy this logic into example-shop.
|
|
61
|
-
- Use `navigation.ts` for category/navigation structure. Run `npx orderly-init-navigation --suggest --account-id <account-id>` when sampling helps, then
|
|
63
|
+
- Use `navigation.ts` for category/navigation structure. Run `npx orderly-init-navigation --suggest --account-id <account-id>` when sampling helps, then read `querySyntax` and `navigationSuggestions` in `navigation-products.json` before exporting `navigationDefinitions: NavigationDefinition[]` with stable unique slugs, at most two levels, optional hero/content fields, and plain `SearchQueryInput` values such as `query: { query: "sko | sneakers" }` or `query: { query: "kjole | sommerkjole", tags: ["dametøj"] }`.
|
|
62
64
|
|
|
63
65
|
## Common Patterns
|
|
64
66
|
|
|
@@ -69,8 +71,8 @@ Declarative product rail:
|
|
|
69
71
|
title="Sko"
|
|
70
72
|
cta-label="Se alle"
|
|
71
73
|
cta-href="/categories/sko/"
|
|
72
|
-
keywords="
|
|
73
|
-
tags="
|
|
74
|
+
keywords="sneakers | støvler | sandaler"
|
|
75
|
+
tags="sko">
|
|
74
76
|
</orderly-product-rail>
|
|
75
77
|
```
|
|
76
78
|
|
|
@@ -79,8 +81,8 @@ Structured query markup:
|
|
|
79
81
|
```html
|
|
80
82
|
<orderly-product-rail title="Sko">
|
|
81
83
|
<query>
|
|
82
|
-
<tags>
|
|
83
|
-
<keywords>
|
|
84
|
+
<tags>sko</tags>
|
|
85
|
+
<keywords>sneakers | støvler | sandaler</keywords>
|
|
84
86
|
<order-by>CreatedTime desc</order-by>
|
|
85
87
|
</query>
|
|
86
88
|
</orderly-product-rail>
|
|
@@ -93,7 +95,7 @@ import { create } from "@bufbuild/protobuf";
|
|
|
93
95
|
import { SearchQuerySchema } from "@orderlyshop/core-client";
|
|
94
96
|
|
|
95
97
|
productRail.query = create(SearchQuerySchema, {
|
|
96
|
-
Query: "
|
|
98
|
+
Query: "sneakers | støvler | sandaler",
|
|
97
99
|
OrderBy: ["CreatedTime desc"]
|
|
98
100
|
});
|
|
99
101
|
```
|
|
@@ -108,7 +110,7 @@ Standalone browser setup:
|
|
|
108
110
|
uiLanguage: "DA",
|
|
109
111
|
defaultShop: {
|
|
110
112
|
navigationDefinitions: [
|
|
111
|
-
{ label: "Sko", slug: "sko", query: { query: "sko sneakers" } }
|
|
113
|
+
{ label: "Sko", slug: "sko", query: { query: "sko | sneakers" } }
|
|
112
114
|
]
|
|
113
115
|
}
|
|
114
116
|
});
|
package/DESIGN.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Design Contract
|
|
2
|
+
|
|
3
|
+
This file is the source of truth for theming `@orderlyshop/web-components`.
|
|
4
|
+
|
|
5
|
+
If a shop, coding agent, or LLM needs to restyle the package, start here before touching component selectors.
|
|
6
|
+
|
|
7
|
+
## Rules For Agents And LLMs
|
|
8
|
+
|
|
9
|
+
1. Keep design work outcome-first: produce a coherent shop theme with the smallest token and selector changes that satisfy the requested visual behavior.
|
|
10
|
+
2. Set semantic design tokens first. Override them on `:root`, `html`, `body`, `orderly-page-layout`, or another shop shell element.
|
|
11
|
+
3. Do not start by hunting through `.orderly-*` selectors. Component selectors are for deliberate exceptions, not for the base theme.
|
|
12
|
+
4. Treat `--orderly-color-accent` and `--orderly-color-accent-soft` as legacy compatibility aliases. New themes should set `--orderly-color-primary`, `--orderly-color-primary-soft`, and `--orderly-color-primary-contrast`.
|
|
13
|
+
5. If a color change should apply to buttons, badges, selection states, navigation hovers, and CTA links, change the semantic tokens below instead of patching each component separately.
|
|
14
|
+
6. If shape changes should apply to tiles, panels, inputs, buttons, and icon buttons, change the radius tokens below instead of hardcoding `border-radius` on component selectors.
|
|
15
|
+
7. Use component-specific selectors only when the token system cannot express the requested exception. Keep that exception scoped to the component and avoid redesigning unrelated surfaces.
|
|
16
|
+
|
|
17
|
+
Package defaults are intentionally declared with `:where(:root)` so they have zero selector specificity. A shop-level `:root { --orderly-color-primary: ... }` is therefore authoritative even if the package CSS is injected later by `defineOrderlyWebComponents()`. Do not repeat token overrides on `body` just to win the cascade; use `body` or a shell element only when you deliberately want a narrower theme scope.
|
|
18
|
+
|
|
19
|
+
## Reference Website Workflow
|
|
20
|
+
|
|
21
|
+
When an agent or LLM builds a new shop, ask for a reference website URL before choosing the visual style. Inspect the reference for reusable design direction:
|
|
22
|
+
|
|
23
|
+
- foundation palette and contrast
|
|
24
|
+
- action, badge, link, and selection colors
|
|
25
|
+
- shape language and radius scale
|
|
26
|
+
- spacing density and page rhythm
|
|
27
|
+
- header, navigation, card, product tile, and checkout surface patterns
|
|
28
|
+
|
|
29
|
+
Translate those observations into this package's semantic tokens and the shop stylesheet. Keep the result recognizably aligned with the reference, but do not copy proprietary assets, copy, logos, or brand marks unless the user explicitly confirms they own or may use them.
|
|
30
|
+
|
|
31
|
+
## Token Layers
|
|
32
|
+
|
|
33
|
+
### Foundation Tokens
|
|
34
|
+
|
|
35
|
+
Use these for the base palette:
|
|
36
|
+
|
|
37
|
+
- `--orderly-color-primary`
|
|
38
|
+
- `--orderly-color-primary-soft`
|
|
39
|
+
- `--orderly-color-primary-contrast`
|
|
40
|
+
- `--orderly-color-text`
|
|
41
|
+
- `--orderly-color-muted`
|
|
42
|
+
- `--orderly-color-border`
|
|
43
|
+
- `--orderly-color-surface`
|
|
44
|
+
- `--orderly-color-surface-muted`
|
|
45
|
+
- `--orderly-color-page`
|
|
46
|
+
- `--orderly-color-danger`
|
|
47
|
+
|
|
48
|
+
`--orderly-color-accent` and `--orderly-color-accent-soft` still exist, but they now default to the primary tokens and should not be the first place new themes start.
|
|
49
|
+
|
|
50
|
+
### Semantic Interaction Tokens
|
|
51
|
+
|
|
52
|
+
Use these when the shop wants a coherent interactive theme:
|
|
53
|
+
|
|
54
|
+
- `--orderly-link-accent-color`
|
|
55
|
+
- `--orderly-selection-border`
|
|
56
|
+
- `--orderly-selection-background`
|
|
57
|
+
- `--orderly-badge-background`
|
|
58
|
+
- `--orderly-badge-color`
|
|
59
|
+
- `--orderly-action-primary-background`
|
|
60
|
+
- `--orderly-action-primary-border`
|
|
61
|
+
- `--orderly-action-primary-color`
|
|
62
|
+
- `--orderly-action-primary-hover-background`
|
|
63
|
+
- `--orderly-action-primary-hover-border`
|
|
64
|
+
- `--orderly-action-primary-hover-color`
|
|
65
|
+
- `--orderly-action-primary-hover-shadow`
|
|
66
|
+
- `--orderly-action-secondary-background`
|
|
67
|
+
- `--orderly-action-secondary-border`
|
|
68
|
+
- `--orderly-action-secondary-color`
|
|
69
|
+
- `--orderly-action-secondary-hover-background`
|
|
70
|
+
- `--orderly-action-secondary-hover-border`
|
|
71
|
+
- `--orderly-action-secondary-hover-color`
|
|
72
|
+
- `--orderly-action-secondary-hover-shadow`
|
|
73
|
+
- `--orderly-focus-ring`
|
|
74
|
+
- `--orderly-focus-outline`
|
|
75
|
+
|
|
76
|
+
These tokens drive the default styles for:
|
|
77
|
+
|
|
78
|
+
- primary actions such as product tile add-to-basket, product page add-to-basket, checkout submit, and basket checkout
|
|
79
|
+
- secondary actions such as close buttons, expand buttons, and outlined remove/toggle actions
|
|
80
|
+
- navigation hover states, inline CTA links, hero eyebrows, and footer links
|
|
81
|
+
- basket count badges and other count/status pills
|
|
82
|
+
- selected thumbnails, selected checkout options, and form/radio focus states
|
|
83
|
+
|
|
84
|
+
### Radius Tokens
|
|
85
|
+
|
|
86
|
+
Use the radius scale to define the shop's shape language:
|
|
87
|
+
|
|
88
|
+
- `--orderly-radius-none`
|
|
89
|
+
- `--orderly-radius-xs`
|
|
90
|
+
- `--orderly-radius-sm`
|
|
91
|
+
- `--orderly-radius-md`
|
|
92
|
+
- `--orderly-radius-lg`
|
|
93
|
+
- `--orderly-radius-pill`
|
|
94
|
+
|
|
95
|
+
Use semantic radius tokens for components. This keeps related UI aligned while still allowing smaller elements to use smaller radii than large panels:
|
|
96
|
+
|
|
97
|
+
- `--orderly-radius-card`: product tiles, basket/order lines, checkout option cards, and other card-like rows
|
|
98
|
+
- `--orderly-radius-panel`: checkout cards, payment/result cards, dialogs, empty states, and larger panels
|
|
99
|
+
- `--orderly-radius-control`: default control radius foundation
|
|
100
|
+
- `--orderly-radius-control-sm`: compact skeletons and small control details
|
|
101
|
+
- `--orderly-radius-button`: primary and secondary buttons, CTA links, and add-to-basket controls
|
|
102
|
+
- `--orderly-radius-icon-button`: square icon buttons such as search, basket, burger menu, and close controls
|
|
103
|
+
- `--orderly-radius-input`: text inputs, selects, search inputs, and quantity controls
|
|
104
|
+
- `--orderly-radius-image`: small image frames inside basket/order summaries
|
|
105
|
+
- `--orderly-radius-indicator`: active navigation indicators and other pill indicators
|
|
106
|
+
|
|
107
|
+
Compatibility aliases still exist:
|
|
108
|
+
|
|
109
|
+
- `--orderly-radius` defaults to `--orderly-radius-sm`
|
|
110
|
+
- `--orderly-icon-button-radius` defaults to `--orderly-radius-icon-button`
|
|
111
|
+
|
|
112
|
+
### Layout Tokens
|
|
113
|
+
|
|
114
|
+
These are not color tokens, but they are part of the default design contract and should be preferred over ad hoc layout overrides:
|
|
115
|
+
|
|
116
|
+
- `--orderly-content-max-width`
|
|
117
|
+
- `--orderly-section-gap`
|
|
118
|
+
- `--orderly-item-gap`
|
|
119
|
+
- `--orderly-control-height`
|
|
120
|
+
- `--orderly-icon-button-size`
|
|
121
|
+
- `--orderly-product-grid-gap`
|
|
122
|
+
- `--orderly-product-grid-item-min-width`
|
|
123
|
+
- `--orderly-product-tile-border`
|
|
124
|
+
- `--orderly-product-tile-radius`
|
|
125
|
+
- `--orderly-product-tile-background`
|
|
126
|
+
- `--orderly-product-tile-shadow`
|
|
127
|
+
- `--orderly-product-tile-hover-border-color`
|
|
128
|
+
- `--orderly-product-tile-image-background`
|
|
129
|
+
- `--orderly-product-tile-body-padding`
|
|
130
|
+
- `--orderly-product-tile-title-color`
|
|
131
|
+
- `--orderly-product-tile-title-font-weight`
|
|
132
|
+
- `--orderly-product-tile-price-color`
|
|
133
|
+
- `--orderly-product-price-color`
|
|
134
|
+
- `--orderly-product-rail-header-padding`
|
|
135
|
+
- `--orderly-product-rail-title-font-size`
|
|
136
|
+
- `--orderly-product-rail-cta-color`
|
|
137
|
+
- `--orderly-collection-hero-height`
|
|
138
|
+
- `--orderly-collection-hero-background`
|
|
139
|
+
- `--orderly-collection-hero-color`
|
|
140
|
+
- `--orderly-collection-hero-image-opacity`
|
|
141
|
+
- `--orderly-collection-hero-cover-shadow`
|
|
142
|
+
- `--orderly-checkout-card-border`
|
|
143
|
+
- `--orderly-checkout-card-radius`
|
|
144
|
+
- `--orderly-checkout-card-background`
|
|
145
|
+
- `--orderly-checkout-card-shadow`
|
|
146
|
+
- `--orderly-checkout-option-border`
|
|
147
|
+
- `--orderly-checkout-option-background`
|
|
148
|
+
- `--orderly-checkout-option-selected-border`
|
|
149
|
+
- `--orderly-checkout-option-selected-background`
|
|
150
|
+
- `--orderly-checkout-option-selected-shadow`
|
|
151
|
+
|
|
152
|
+
## Recommended Styling Workflow
|
|
153
|
+
|
|
154
|
+
When building a new shop or retheming an existing one:
|
|
155
|
+
|
|
156
|
+
1. Set the foundation palette.
|
|
157
|
+
2. Set the semantic interaction tokens so buttons, badges, links, and selection states stay aligned.
|
|
158
|
+
3. Set the radius scale and semantic radius tokens so tiles, checkout surfaces, buttons, inputs, and icon buttons share one shape system.
|
|
159
|
+
4. Review the shop.
|
|
160
|
+
5. Only then use component-specific hooks such as navigation or product-grid variables for intentional deviations.
|
|
161
|
+
|
|
162
|
+
## Example: Green Theme
|
|
163
|
+
|
|
164
|
+
```css
|
|
165
|
+
:root {
|
|
166
|
+
--orderly-color-primary: #1f7a43;
|
|
167
|
+
--orderly-color-primary-soft: #e7f4eb;
|
|
168
|
+
--orderly-color-primary-contrast: #ffffff;
|
|
169
|
+
|
|
170
|
+
--orderly-link-accent-color: var(--orderly-color-primary);
|
|
171
|
+
--orderly-selection-border: var(--orderly-color-primary);
|
|
172
|
+
--orderly-selection-background: var(--orderly-color-primary-soft);
|
|
173
|
+
--orderly-badge-background: var(--orderly-color-primary);
|
|
174
|
+
--orderly-badge-color: var(--orderly-color-primary-contrast);
|
|
175
|
+
|
|
176
|
+
--orderly-action-primary-background: var(--orderly-color-primary);
|
|
177
|
+
--orderly-action-primary-border: var(--orderly-color-primary);
|
|
178
|
+
--orderly-action-primary-color: var(--orderly-color-primary-contrast);
|
|
179
|
+
--orderly-action-primary-hover-background: #166338;
|
|
180
|
+
--orderly-action-primary-hover-border: #166338;
|
|
181
|
+
--orderly-action-primary-hover-color: #ffffff;
|
|
182
|
+
--orderly-action-primary-hover-shadow: inset 0 -3px 0 rgba(0, 0, 0, 0.22);
|
|
183
|
+
|
|
184
|
+
--orderly-action-secondary-background: #ffffff;
|
|
185
|
+
--orderly-action-secondary-border: var(--orderly-color-primary);
|
|
186
|
+
--orderly-action-secondary-color: var(--orderly-color-primary);
|
|
187
|
+
--orderly-action-secondary-hover-background: #dff0e5;
|
|
188
|
+
--orderly-action-secondary-hover-border: var(--orderly-color-primary);
|
|
189
|
+
--orderly-action-secondary-hover-color: var(--orderly-color-primary);
|
|
190
|
+
--orderly-action-secondary-hover-shadow: inset 0 -3px 0 rgba(31, 122, 67, 0.14);
|
|
191
|
+
|
|
192
|
+
--orderly-focus-ring: 0 0 0 3px rgba(31, 122, 67, 0.24);
|
|
193
|
+
--orderly-focus-outline: 3px solid rgba(31, 122, 67, 0.34);
|
|
194
|
+
|
|
195
|
+
--orderly-radius-sm: 4px;
|
|
196
|
+
--orderly-radius-md: 10px;
|
|
197
|
+
--orderly-radius-lg: 14px;
|
|
198
|
+
--orderly-radius-card: var(--orderly-radius-md);
|
|
199
|
+
--orderly-radius-panel: var(--orderly-radius-lg);
|
|
200
|
+
--orderly-radius-control: var(--orderly-radius-md);
|
|
201
|
+
--orderly-radius-button: var(--orderly-radius-control);
|
|
202
|
+
--orderly-radius-icon-button: var(--orderly-radius-control);
|
|
203
|
+
--orderly-radius-input: var(--orderly-radius-control);
|
|
204
|
+
--orderly-radius-image: var(--orderly-radius-sm);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
That one block is enough to restyle color and shape consistently without editing component markup or package CSS.
|
|
209
|
+
|
|
210
|
+
## Component-Specific Hooks
|
|
211
|
+
|
|
212
|
+
Component-specific variables still exist and are useful, but they are a second step.
|
|
213
|
+
|
|
214
|
+
Examples:
|
|
215
|
+
|
|
216
|
+
- navigation active states: `--orderly-navigation-indicator-color`, `--orderly-navigation-link-active-background`, `--orderly-navigation-link-active-color`, `--orderly-navigation-link-padding`
|
|
217
|
+
- product tile surfaces: `--orderly-product-tile-background`, `--orderly-product-tile-border`, `--orderly-product-tile-radius`, `--orderly-product-tile-shadow`, `--orderly-product-tile-image-background`
|
|
218
|
+
- product rail headers: `--orderly-product-rail-header-padding`, `--orderly-product-rail-title-font-size`, `--orderly-product-rail-cta-color`
|
|
219
|
+
- category hero covers: `--orderly-collection-hero-background`, `--orderly-collection-hero-color`, `--orderly-collection-hero-image-opacity`, `--orderly-collection-hero-cover-shadow`
|
|
220
|
+
- checkout cards/options: `--orderly-checkout-card-*`, `--orderly-checkout-option-*`
|
|
221
|
+
- product grid: `--orderly-product-grid-gap`, `--orderly-product-grid-item-min-width`
|
|
222
|
+
- page layout: `--orderly-content-max-width`
|
|
223
|
+
|
|
224
|
+
Use these only when a specific component should differ from the shared design system.
|
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ Inputs:
|
|
|
15
15
|
- Project directory: ASK
|
|
16
16
|
- Shop name: ASK
|
|
17
17
|
- Orderly account id: ASK
|
|
18
|
+
- Legal webshop owner name: ASK
|
|
19
|
+
- Legal webshop owner CVR: ASK
|
|
18
20
|
- Visual style reference: ASK
|
|
19
21
|
- Deployment target: ASK
|
|
20
22
|
- Google Tag Manager container id: NONE
|
|
@@ -26,8 +28,8 @@ Use any provided input above without asking again. Ask only for values that are
|
|
|
26
28
|
|
|
27
29
|
Workflow:
|
|
28
30
|
1. Install @orderlyshop/core-client and @orderlyshop/web-components if needed. Run npx orderly-init-shop for a new shop, pass --account-id when available, and preserve existing files unless I approve overwrite.
|
|
29
|
-
2. Read node_modules/@orderlyshop/web-components/AGENTS.md, DESIGN.md, README.md, docs/components/README.md, and docs/shop-best-practices.md before changing shop files. Treat docs/shop-best-practices.md as a completion checklist, not optional inspiration.
|
|
30
|
-
3. Scope the shop with the Orderly account id in src/shop-query.ts. When product sampling is possible, run npx orderly-init-navigation --suggest --account-id <account-id>, then build src/navigation.ts manually with stable unique slugs, at most two category levels, and plain SearchQueryInput objects.
|
|
31
|
+
2. Read node_modules/@orderlyshop/web-components/AGENTS.md, DESIGN.md, README.md, docs/components/README.md, docs/search-syntax.md, and docs/shop-best-practices.md before changing shop files. Treat docs/shop-best-practices.md as a completion checklist, not optional inspiration.
|
|
32
|
+
3. Scope the shop with the Orderly account id in src/shop-query.ts and set `defaultShop.legalOwnerName` plus `defaultShop.legalOwnerCvr` from the legal owner inputs. When product sampling is possible, run npx orderly-init-navigation --suggest --account-id <account-id>, read querySyntax and navigationSuggestions, then build src/navigation.ts manually with stable unique slugs, at most two category levels, and plain SearchQueryInput objects.
|
|
31
33
|
4. Use package-owned components and configuration first: orderly-home-page, orderly-category-page, orderly-product-detail-page, orderly-checkout-page, configureShop(...), templates, generated Vite setup, generated category URLs, and package SSR helpers.
|
|
32
34
|
5. Use the visual style reference as design direction, not as copy material. Start src/style.css from DESIGN.md semantic tokens, then add scoped component selectors only where tokens are insufficient.
|
|
33
35
|
6. Do not ship the package default look and feel with minor color changes. Inspect the catalog, research the reference site and comparable modern shops, and create homepage/category/product context that is visually and editorially specific to this shop.
|
|
@@ -59,11 +61,11 @@ Agents and developers should use `npx orderly-init-shop` before hand-writing sho
|
|
|
59
61
|
npx orderly-init-shop --account-id 00000000-0000-0000-0000-000000000000
|
|
60
62
|
```
|
|
61
63
|
|
|
62
|
-
For agent-assisted shop creation, ask for
|
|
64
|
+
For agent-assisted shop creation, ask for the Orderly account id, legal webshop owner name, legal owner CVR, and a reference website URL for visual style before customizing the scaffold. Use the account id for `src/shop-query.ts`, use the legal owner values for `defaultShop.legalOwnerName` and `defaultShop.legalOwnerCvr`, use the reference website and catalog research to shape `src/style.css`, and follow [`docs/shop-best-practices.md`](./docs/shop-best-practices.md) as the completion checklist. The first pass should already feel visually distinct, editorially useful, and technically polished; it should not look like the default package scaffold with small token changes.
|
|
63
65
|
|
|
64
66
|
Re-running `npx orderly-init-shop` in an existing shop is non-destructive by default. Existing scaffold files are left unchanged with warnings, while missing new scaffold files are added. Use `--force` only when you explicitly want scaffold files overwritten.
|
|
65
67
|
|
|
66
|
-
The scaffold uses Vite for local development and static builds, but Vite is added to the generated shop's `devDependencies`. Dev mode SSR for product/category URLs is enabled by default, so View Source on local category/product URLs shows semantic server-rendered markup. Consumers should test locally from `http://localhost:61677` or `https://localhost:61677` because those are the expected local development origins for backend CORS. The generated Vite setup defaults to `http://localhost:61677`; if a local reverse proxy or TLS terminator is added, keep the same `localhost:61677` origin over HTTPS. `@orderlyshop/web-components` does not depend on Vite at runtime.
|
|
68
|
+
The scaffold uses Vite for local development and static builds, but Vite is added to the generated shop's `devDependencies`. Dev mode SSR for product/category URLs is enabled by default, so View Source on local category/product URLs shows semantic server-rendered markup. Consumers should test locally from `http://localhost:61677` or `https://localhost:61677` because those are the expected local development origins for backend CORS. Do not switch to `127.0.0.1`, `0.0.0.0`, or a random Vite fallback port when debugging API calls; close the conflicting process and keep the origin on `localhost:61677`. The generated Vite setup defaults to `http://localhost:61677`; if a local reverse proxy or TLS terminator is added, keep the same `localhost:61677` origin over HTTPS. `@orderlyshop/web-components` does not depend on Vite at runtime.
|
|
67
69
|
|
|
68
70
|
## Browser Standalone Bundle
|
|
69
71
|
|
|
@@ -84,9 +86,9 @@ Use the configurable bundle when a vanilla HTML site needs to set shop options b
|
|
|
84
86
|
{
|
|
85
87
|
label: "Sko",
|
|
86
88
|
slug: "sko",
|
|
87
|
-
query: { query: "sko sneakers" },
|
|
89
|
+
query: { query: "sko | sneakers" },
|
|
88
90
|
children: [
|
|
89
|
-
{ label: "Dame sko", slug: "dame-sko", query: { query: "
|
|
91
|
+
{ label: "Dame sko", slug: "dame-sko", query: { query: "damesko | sneakers" } }
|
|
90
92
|
]
|
|
91
93
|
}
|
|
92
94
|
]
|
|
@@ -176,6 +178,7 @@ Developers and coding agents should start with these package-owned docs:
|
|
|
176
178
|
- [`DESIGN.md`](./DESIGN.md) defines the package-wide design tokens and the recommended theming workflow for consistent styling across all components.
|
|
177
179
|
- [`docs/components/README.md`](./docs/components/README.md) lists every custom element, common attributes, typed properties, events, and template hooks.
|
|
178
180
|
- [`docs/shop-best-practices.md`](./docs/shop-best-practices.md) is the practical checklist for turning the package scaffold into a branded, editorial storefront without replacing package-owned component behavior.
|
|
181
|
+
- [`docs/search-syntax.md`](./docs/search-syntax.md) explains `SearchQuery.Query`, `|` OR queries, tag narrowing, and the recommended category query workflow.
|
|
179
182
|
- [`docs/components/product-grid.md`](./docs/components/product-grid.md) documents search loading, sorting, paging, default query merging, and declarative query markup.
|
|
180
183
|
- [`docs/components/product-rail.md`](./docs/components/product-rail.md) documents homepage/category rails and their query configuration.
|
|
181
184
|
- [`AGENTS.md`](./AGENTS.md) gives coding agents the conventions for building shops with this package.
|
|
@@ -201,12 +204,13 @@ The most important convention is that Core contracts use typed JavaScript proper
|
|
|
201
204
|
title="Sko"
|
|
202
205
|
cta-label="Se alle"
|
|
203
206
|
cta-href="/categories/sko/"
|
|
204
|
-
keywords="
|
|
205
|
-
tags="børnetøj,bluser"
|
|
207
|
+
keywords="sneakers | støvler | sandaler"
|
|
206
208
|
order-by="CreatedTime desc">
|
|
207
209
|
</orderly-product-rail>
|
|
208
210
|
```
|
|
209
211
|
|
|
212
|
+
`keywords` maps to `SearchQuery.Query`. Spaces narrow the text query; they are not OR. Use `|` for broad alternatives, for example `lego | matchbox | playmobil`. `tags="a,b"` maps to multiple `SearchQuery.Tags` and should be treated as an AND-style narrowing filter, not as category alternatives. For the full guidance, read [`docs/search-syntax.md`](./docs/search-syntax.md).
|
|
213
|
+
|
|
210
214
|
## Default Shop
|
|
211
215
|
|
|
212
216
|
The package can render a working default storefront with the built-in backend, navigation, sorting, basket, product detail, and footer defaults:
|
|
@@ -246,6 +250,8 @@ The simplest setup needs no router block:
|
|
|
246
250
|
configureShop({
|
|
247
251
|
defaultShop: {
|
|
248
252
|
homeHref: "/",
|
|
253
|
+
legalOwnerName: "Example Shop ApS",
|
|
254
|
+
legalOwnerCvr: "12345678",
|
|
249
255
|
productHref: "/products/",
|
|
250
256
|
productUrlMode: "path",
|
|
251
257
|
productPathRoot: "/products/",
|
|
@@ -373,7 +379,7 @@ After installing from the repository workspace, run it with:
|
|
|
373
379
|
npm run dev --workspace @orderlyshop/example-shop
|
|
374
380
|
```
|
|
375
381
|
|
|
376
|
-
Open the local shop from `http://localhost:61677` during normal development, or `https://localhost:61677` when a local TLS proxy is in front of the dev server. Consumers should keep that exact localhost origin because the backend CORS development policy expects it.
|
|
382
|
+
Open the local shop from `http://localhost:61677` during normal development, or `https://localhost:61677` when a local TLS proxy is in front of the dev server. Consumers should keep that exact localhost origin because the backend CORS development policy expects it. If live API requests work from one local URL but fail from another, use `localhost:61677` and stop any process that forced Vite onto a different port; avoid `127.0.0.1` for package shop development.
|
|
377
383
|
|
|
378
384
|
Inside an installed package, inspect the same reference implementation at:
|
|
379
385
|
|
|
@@ -414,7 +420,7 @@ Agent workflow for a new shop:
|
|
|
414
420
|
npx orderly-init-navigation --suggest --account-id <account-id>
|
|
415
421
|
```
|
|
416
422
|
|
|
417
|
-
3. Create `navigation.ts` manually from `navigation-products.json`. Export `navigationDefinitions: NavigationDefinition[]`; each item needs `label`, a globally unique `slug`, optional hero/content fields, optional `children`, and plain `SearchQueryInput` values such as `{ query: "sko sneakers", tags: ["
|
|
423
|
+
3. Create `navigation.ts` manually from `navigation-products.json`. Read `querySyntax` and `navigationSuggestions` first. Export `navigationDefinitions: NavigationDefinition[]`; each item needs `label`, a globally unique `slug`, optional hero/content fields, optional `children`, and plain `SearchQueryInput` values such as `{ query: "sko | sneakers" }` or `{ query: "kjole | sommerkjole", tags: ["dametøj"] }`. Keep navigation to at most two category levels and avoid empty or tiny categories.
|
|
418
424
|
4. Configure shop scope with a Core `SearchQuery` through `configureShop({ defaultShop: { defaultQuery } })`.
|
|
419
425
|
5. Read and follow `docs/shop-best-practices.md`, translate the reference into `DESIGN.md` tokens plus scoped CSS, and add homepage/category content from catalog and research.
|
|
420
426
|
6. Run the dev server when a browser is available and visually inspect homepage, category, product, basket, and checkout on desktop and mobile. Fix visible issues before reporting completion; visual QA is required for finished shop work.
|
|
@@ -477,7 +483,7 @@ Build with generated category pages and then clean the generated source files. S
|
|
|
477
483
|
npx orderly-build-category-pages
|
|
478
484
|
```
|
|
479
485
|
|
|
480
|
-
`orderly-build-category-pages` also hydrates the built `dist/index.html` and generated `dist/categories/**/index.html` pages with SEO-friendly fallback HTML. The hydration step embeds semantic product cards per category using `SearchService.Search`; it reads `--base-url`, `VITE_ORDERLY_BASE_URL`, or `ORDERLY_BASE_URL`, and otherwise uses the default `https://service.orderly.shop` backend. Live web components read the semantic fallback as initial visible state, hide the raw fallback markup, and then continue as normal client components. Product grids still run their first live `SearchService.Search()` after hydration when a client or backend URL is configured, so fallback products are replaced by current backend data and continuation-token paging starts from the live result set.
|
|
486
|
+
`orderly-build-category-pages` also hydrates the built `dist/index.html` and generated `dist/categories/**/index.html` pages with SEO-friendly fallback HTML. The hydration step embeds semantic product cards per category using `SearchService.Search`; it reads `--base-url`, `VITE_ORDERLY_BASE_URL`, or `ORDERLY_BASE_URL`, and otherwise uses the default `https://service.orderly.shop` backend. It warns loudly when a category page or homepage rail query matches 0 products, because that almost always means `src/navigation.ts` needs a broader OR query or fewer tag filters. Live web components read the semantic fallback as initial visible state, hide the raw fallback markup, and then continue as normal client components. Product grids still run their first live `SearchService.Search()` after hydration when a client or backend URL is configured, so fallback products are replaced by current backend data and continuation-token paging starts from the live result set.
|
|
481
487
|
|
|
482
488
|
Hydrate an already-built site manually, for example in a nightly job:
|
|
483
489
|
|
|
@@ -485,7 +491,7 @@ Hydrate an already-built site manually, for example in a nightly job:
|
|
|
485
491
|
npx orderly-hydrate-static-pages --base-url https://service.orderly.shop --products-per-category 12
|
|
486
492
|
```
|
|
487
493
|
|
|
488
|
-
Use `--skip-products` for metadata-only hydration, `--strict-products` when a nightly job should fail on backend search errors, and `--no-hydrate` on `orderly-build-category-pages` to keep the older unhydrated build behavior.
|
|
494
|
+
Use `--skip-products` for metadata-only hydration, `--strict-products` when a nightly job should fail on backend search errors, `--strict-empty-categories` when CI should fail on empty category pages/homepage rails, and `--no-hydrate` on `orderly-build-category-pages` to keep the older unhydrated build behavior.
|
|
489
495
|
|
|
490
496
|
The category generator expects `src/navigation.ts` or `src/navigation.js` to export `navigationDefinitions` and the template page to contain `<orderly-category-page></orderly-category-page>`. TypeScript navigation files use the current shop project's `typescript` dependency for one-off transpilation. Useful options include `--navigation`, `--template`, `--categories-dir`, `--site-title`, `--component-tag`, and `--export`. The older `--taxonomy` flag and `categoryDefinitions` export are still accepted as compatibility aliases.
|
|
491
497
|
|
|
@@ -693,8 +699,8 @@ For a fuller real-shop checklist, read [Shop Best Practices](./docs/shop-best-pr
|
|
|
693
699
|
|
|
694
700
|
## Components
|
|
695
701
|
|
|
696
|
-
- `orderly-page-layout` provides reusable page regions for header, header actions, responsive primary navigation placement, left/right sidebars, content, footer, and overlay content. It includes a default logo image using `https://orderly.shop/home/App_Icon.svg`; override it with `logo-src`, `logo-alt`, and `logo-href`. Use `primary-nav-mobile-placement="header"` for the common mobile header burger pattern without a duplicate mobile layout template.
|
|
697
|
-
- `orderly-home-page` composes page layout, responsive primary navigation, one product rail per top-level category, basket icon, basket drawer, optional inline desktop basket rail, product detail dialog, and footer. Configure it with `configureShop({ defaultShop })` plus attributes such as `title`, `eyebrow`, `rail-cta-label`, `product-href`, and `basket-mode="inline-desktop"`. The product dialog is fullscreen on mobile, keeps a bounded image column on desktop through `--orderly-product-dialog-image-size`, and pushes a same-URL history entry when opened, so browser back closes the overlay before leaving the homepage. The expand link follows the configured product URL mode, and compact `https://orderly.shop/...` product URLs are preserved when it builds hash or path routes.
|
|
702
|
+
- `orderly-page-layout` provides reusable page regions for header, header actions, responsive primary navigation placement, left/right sidebars, content, footer, and overlay content. It includes a default logo image using `https://orderly.shop/home/App_Icon.svg`; override it with `logo-src`, `logo-alt`, and `logo-href`. It always appends the package-owned legal ownership notice after footer content using `defaultShop.legalOwnerName` and `defaultShop.legalOwnerCvr`; shop templates cannot replace that notice. Use `primary-nav-mobile-placement="header"` for the common mobile header burger pattern without a duplicate mobile layout template.
|
|
703
|
+
- `orderly-home-page` composes page layout, responsive primary navigation, one product rail per top-level category, basket icon, basket drawer, optional inline desktop basket rail, product detail dialog, and footer. Configure it with `configureShop({ defaultShop })` plus attributes such as `title`, `eyebrow`, `rail-cta-label`, `product-href`, and `basket-mode="inline-desktop"`. Use the `hero` slot or `hero` template for a custom premium homepage hero while keeping package-owned rails, basket, product overlay, and generated URLs. The product dialog is fullscreen on mobile, keeps a bounded image column on desktop through `--orderly-product-dialog-image-size`, and pushes a same-URL history entry when opened, so browser back closes the overlay before leaving the homepage. The expand link follows the configured product URL mode, and compact `https://orderly.shop/...` product URLs are preserved when it builds hash or path routes.
|
|
698
704
|
- `orderly-category-page` composes page layout, navigation, search, product grid, product detail, basket icon, and basket around a navigation category. Without properties it uses the package default shop configuration. Override with `configureShop({ defaultShop })` once per shop, set `base-url` or `product-href` in markup, or assign `category`, `navigationItems`, `sortOptions`, `client`, `defaultQuery`, and `basketController` from JavaScript for advanced cases. Configure `defaultShop.navigationLayout` or top-level `navigationLayout` to switch the package-owned category page from desktop side navigation to top horizontal category navigation while keeping the mobile burger in the header. Set `basket-mode="inline-desktop"` when the desktop category page should show a right basket rail only after the basket has lines. The product dialog is fullscreen on mobile, keeps a bounded image column on desktop through `--orderly-product-dialog-image-size`, and pushes a same-URL history entry when opened, so browser back closes the overlay before leaving the category. The expand link uses the configured product URL mode and still supports explicit per-page `product-href` overrides when a shop wants a different physical product page URL.
|
|
699
705
|
- `orderly-checkout-page` composes page layout, responsive primary navigation, checkout form, basket order summary, footer, shared basket state, delivery loading, and order-created cleanup. Delivery methods render as radio cards after address entry, service points render as radio-card pickup choices when required, and delivery changes verify the draft so totals update. If a persisted draft already contains a complete address, delivery methods are restored on mount; if it also contains a delivery choice, the draft is verified so the basket summary can show backend shipping and total prices. Configure copy through `configureShop({ defaultShop: { checkoutPageTitle, checkoutPageDescription, checkoutOrderTitle, checkoutTermsHref, checkoutLabels, basketLabels } })`, or assign `client`, `basketController`, `navigationItems`, `checkoutLabels`, and `basketLabels` from JavaScript.
|
|
700
706
|
- `orderly-product-detail-page` composes page layout, navigation, product detail, basket drawer, optional inline desktop basket rail, and footer for a product route. Set `share-url`, the `shareUrl` property, or pass `#url=<encoded-share-url>` in the page URL; compact Orderly slugs are restored to `https://orderly.shop/...` before it calls `SearchService.Search` with `SearchQuery.ShareUrl` and renders the resolved `SearchObject`. It forwards `details-before`, `purchase-note`, `secondary-cta`, and `details-after` slots to the nested product page. Set `basket-mode="inline-desktop"` when a product page should keep the basket visible as a desktop right rail after add-to-basket. Shops can tune outer gutters with `--orderly-product-detail-page-padding`, `--orderly-product-detail-page-mobile-padding`, `--orderly-product-detail-page-inline-padding`, and `--orderly-product-detail-page-mobile-inline-padding`.
|
|
@@ -707,7 +713,7 @@ For a fuller real-shop checklist, read [Shop Best Practices](./docs/shop-best-pr
|
|
|
707
713
|
- `orderly-product-rail` accepts `query: SearchQuery`, title, CTA label, and CTA href, then renders the search as a horizontal scroll list by composing `orderly-product-grid`. It is useful for homepages and editorial rows where several category searches should be stacked vertically.
|
|
708
714
|
- `orderly-collection-page` accepts `query: SearchQuery`, title, description, hero image, `hero-layout`, and `hero-cover-count`, then delegates fetching to `orderly-product-grid`. Use `hero-layout="covers"` to show product-cover previews from the loaded category products without custom category header HTML.
|
|
709
715
|
- `orderly-shop-footer` renders configurable logo, about text, address, contact information, opening hours, and information links.
|
|
710
|
-
- `orderly-basket-icon` and `orderly-basket` share a `BasketController` backed by `DraftOrder` persistence. Basket state events such as `orderly-basket-open`, `orderly-basket-change`, and `orderly-basket-verified` use the current `DraftOrder` directly as `event.detail`, so consumers can derive counts and totals from Core contracts instead of an internal basket data shape. `orderly-basket-icon` can also render a checkout/cart link with `href`, `placement="mobile-fixed"`, and `hide-when-empty` for a package-owned mobile button with count. `orderly-basket-checkout` includes `{ draft, count, href }` when the checkout action is triggered. `orderly-basket` renders an item overview, verifies non-empty persisted drafts on load plus later basket mutations through `OrderService.VerifyDraft` when a client or `base-url` is configured, includes a configurable checkout link through `checkout-href` and `checkout-label`, and only exposes quantity selection for lines where `MaxQuantity > 1`. Summary rows now render only backend-provided `DraftOrder` values, never local subtotal/total math, and the component renders both `DraftOrder.Errors` and `DraftOrderLine.Errors`. Custom basket templates can project order-level errors through `data-orderly-slot="errors"`.
|
|
716
|
+
- `orderly-basket-icon` and `orderly-basket` share a `BasketController` backed by `DraftOrder` persistence. Basket state events such as `orderly-basket-open`, `orderly-basket-change`, and `orderly-basket-verified` use the current `DraftOrder` directly as `event.detail`, so consumers can derive counts and totals from Core contracts instead of an internal basket data shape. Product add/remove and in-basket UI state are matched only by `SearchObject.SKU.Value` and `DraftOrderLine.SKU.Value`; titles, barcodes, target ids, and share URLs are not basket identity. `orderly-basket-icon` can also render a checkout/cart link with `href`, `placement="mobile-fixed"`, and `hide-when-empty` for a package-owned mobile button with count. `orderly-basket-checkout` includes `{ draft, count, href }` when the checkout action is triggered. `orderly-basket` renders an item overview, verifies non-empty persisted drafts on load plus later basket mutations through `OrderService.VerifyDraft` when a client or `base-url` is configured, includes a configurable checkout link through `checkout-href` and `checkout-label`, and only exposes quantity selection for lines where `MaxQuantity > 1`. Summary rows now render only backend-provided `DraftOrder` values, never local subtotal/total math, and the component renders both `DraftOrder.Errors` and `DraftOrderLine.Errors`. Custom basket templates can project order-level errors through `data-orderly-slot="errors"`.
|
|
711
717
|
- `orderly-checkout` persists checkout contact and address fields directly on `DraftOrder.Transport`, defaults the phone country picker to `+45`, validates email and phone values with built-in component logic, looks up Danish city names from the entered postal code through Dataforsyningen's `https://api.dataforsyningen.dk/postnumre/{postnr}` endpoint, loads delivery methods and service points, verifies the draft, and calls `OrderService.Create`. On mount it restores delivery methods for a persisted complete address and verifies a persisted delivery/service-point choice once, so shared basket summaries receive backend shipping and total prices without waiting for another address edit.
|
|
712
718
|
- `orderly-navigation` uses `NavigationController` and can be subclassed with site-specific `NavigationItem[]`. It is sticky by default, including when placed in `left`, `right`, or `primary-nav`; set `sticky="false"` to opt out. Use `layout="vertical"` for disclosure side navigation, `layout="horizontal"` for the tiered desktop bar, or `layout="burgermenu"` for an icon trigger that expands into the same nested disclosure menu inside a floating panel. Use `layout-desktop="horizontal" layout-mobile="burgermenu"` with `orderly-page-layout primary-nav-mobile-placement="header"` for desktop horizontal nav plus inline mobile burger. Add `persist-state state-key="main-menu"` to remember expanded category branches across navigation, and use `slot="menu-before"`/`slot="menu-after"` for shop-owned links such as store finder, campaigns, or volunteer pages.
|
|
713
719
|
- `orderly-search-box`, `orderly-sort-select`, `orderly-filter-panel`, and `orderly-load-more` bind to `orderly-product-grid`.
|
|
@@ -761,6 +767,7 @@ Use the package basket primitives before writing custom state glue:
|
|
|
761
767
|
Basket count contract:
|
|
762
768
|
|
|
763
769
|
- `BasketController.count` is derived from `DraftOrder.OrderLines[].Quantity`.
|
|
770
|
+
- Product identity in the basket is `SKU.Value` only. Products that share titles, barcodes, target ids, or share URLs must still have distinct SKUs to avoid being treated as the same basket line.
|
|
764
771
|
- `orderly-basket-icon`, `orderly-basket`, product tiles/grids/rails, product pages, and checkout should share the same `BasketController` instance.
|
|
765
772
|
- `orderly-basket-change` and `orderly-basket-verified` expose the current `DraftOrder` as `event.detail`; custom UI can derive counts from that draft without copying package state.
|
|
766
773
|
- Verified and persisted drafts still use the same `DraftOrder`; backend-provided prices and errors are rendered by `orderly-basket`.
|
|
@@ -778,7 +785,7 @@ When customizing checkout layout, do not override the package mobile collapse ac
|
|
|
778
785
|
|
|
779
786
|
The package ships baseline default CSS and uses light DOM. Default markup uses stable `orderly-*` class names and CSS variables, so host CSS can style or replace the default look directly.
|
|
780
787
|
|
|
781
|
-
For consistent theming across the whole package, start with the semantic design contract in [`DESIGN.md`](./DESIGN.md). In practice that means overriding `--orderly-color-primary`, `--orderly-color-primary-soft`, `--orderly-color-primary-contrast`, the `--orderly-action-primary-*` and `--orderly-action-secondary-*` tokens, plus shared tokens such as `--orderly-link-accent-color`, `--orderly-selection-*`, and `--orderly-
|
|
788
|
+
For consistent theming across the whole package, start with the semantic design contract in [`DESIGN.md`](./DESIGN.md). In practice that means overriding `--orderly-color-primary`, `--orderly-color-primary-soft`, `--orderly-color-primary-contrast`, the `--orderly-action-primary-*` and `--orderly-action-secondary-*` tokens, plus shared tokens such as `--orderly-link-accent-color`, `--orderly-selection-*`, `--orderly-badge-*`, `--orderly-product-tile-*`, `--orderly-product-rail-*`, `--orderly-collection-hero-*`, and `--orderly-checkout-*`. `--orderly-color-accent` is kept as a compatibility alias, but new themes should treat `--orderly-color-primary` as the source of truth.
|
|
782
789
|
|
|
783
790
|
The package declares its fallback tokens with `:where(:root)`, which has zero selector specificity. If your shop sets tokens on `:root`, those values win even when `defineOrderlyWebComponents()` injects the package CSS after your stylesheet. You should not need to duplicate token overrides on `body` unless you intentionally want a scoped override.
|
|
784
791
|
|
|
@@ -873,8 +880,8 @@ Simple product queries can be declared in markup on `orderly-product-grid`, `ord
|
|
|
873
880
|
title="Sko"
|
|
874
881
|
cta-label="Se alle"
|
|
875
882
|
cta-href="/categories/sko/"
|
|
876
|
-
keywords="
|
|
877
|
-
tags="
|
|
883
|
+
keywords="sneakers | støvler | sandaler"
|
|
884
|
+
tags="sko"
|
|
878
885
|
order-by="CreatedTime desc">
|
|
879
886
|
</orderly-product-rail>
|
|
880
887
|
```
|
|
@@ -887,14 +894,14 @@ The same query can be grouped as child markup:
|
|
|
887
894
|
cta-label="Se alle"
|
|
888
895
|
cta-href="/categories/sko/">
|
|
889
896
|
<query>
|
|
890
|
-
<tags>
|
|
891
|
-
<keywords>
|
|
897
|
+
<tags>sko</tags>
|
|
898
|
+
<keywords>sneakers | støvler | sandaler</keywords>
|
|
892
899
|
<order-by>CreatedTime desc</order-by>
|
|
893
900
|
</query>
|
|
894
901
|
</orderly-product-rail>
|
|
895
902
|
```
|
|
896
903
|
|
|
897
|
-
Declarative query fields map to `SearchQuery.Query`, `SearchQuery.Tags`, `SearchQuery.HiddenQuery`, `SearchQuery.OrderBy`, `SearchQuery.StoreId`, and `SearchQuery.Featured`. For the full Core contract, assign the typed `query` property from JavaScript.
|
|
904
|
+
Declarative query fields map to `SearchQuery.Query`, `SearchQuery.Tags`, `SearchQuery.HiddenQuery`, `SearchQuery.OrderBy`, `SearchQuery.StoreId`, and `SearchQuery.Featured`. Use `|` for OR text and avoid multiple tags for alternatives. For the full Core contract, assign the typed `query` property from JavaScript.
|
|
898
905
|
|
|
899
906
|
Templates can be viewport-specific on every component:
|
|
900
907
|
|
|
@@ -43,6 +43,7 @@ const imageBaseUrl = args["image-base-url"] ?? process.env.VITE_ORDERLY_IMAGE_BA
|
|
|
43
43
|
const dryRun = Boolean(args["dry-run"]);
|
|
44
44
|
const skipProducts = Boolean(args["skip-products"]);
|
|
45
45
|
const strictProducts = Boolean(args["strict-products"]);
|
|
46
|
+
const strictEmptyCategories = Boolean(args["strict-empty-categories"] ?? args["fail-empty-categories"]);
|
|
46
47
|
|
|
47
48
|
if (!existsSync(distDir)) {
|
|
48
49
|
throw new Error(`Build output directory not found: ${relativePath(distDir)}`);
|
|
@@ -71,6 +72,8 @@ const client = skipProducts || !baseUrl
|
|
|
71
72
|
? undefined
|
|
72
73
|
: createOrderlyNodeClient({ baseUrl, protocol });
|
|
73
74
|
const productCache = new Map();
|
|
75
|
+
const emptyProductWarnings = [];
|
|
76
|
+
const failedProductSnapshots = new Set();
|
|
74
77
|
let updatedPages = 0;
|
|
75
78
|
let hydratedProducts = 0;
|
|
76
79
|
|
|
@@ -80,6 +83,7 @@ if (existsSync(homeFile)) {
|
|
|
80
83
|
const homeProducts = new Map();
|
|
81
84
|
for (const category of homeCategories) {
|
|
82
85
|
const products = await productsForCategory(category, homeProductsPerCategory);
|
|
86
|
+
reportEmptyProducts(category, products, "homepage rail", homeProductsPerCategory);
|
|
83
87
|
hydratedProducts += products.length;
|
|
84
88
|
homeProducts.set(category.id, products);
|
|
85
89
|
}
|
|
@@ -97,6 +101,7 @@ for (const category of categories) {
|
|
|
97
101
|
continue;
|
|
98
102
|
}
|
|
99
103
|
const products = await productsForCategory(category, productsPerCategory);
|
|
104
|
+
reportEmptyProducts(category, products, "category page", productsPerCategory);
|
|
100
105
|
hydratedProducts += products.length;
|
|
101
106
|
const html = readFileSync(file, "utf8");
|
|
102
107
|
const next = hydrateCategoryHtml(html, category, products);
|
|
@@ -110,6 +115,13 @@ const productSuffix = skipProducts
|
|
|
110
115
|
? "Product snapshots were skipped."
|
|
111
116
|
: `${hydratedProducts} product snapshot${hydratedProducts === 1 ? "" : "s"} embedded.`;
|
|
112
117
|
console.log(`Hydrated ${updatedPages} static page${updatedPages === 1 ? "" : "s"}. ${productSuffix}`);
|
|
118
|
+
if (emptyProductWarnings.length > 0) {
|
|
119
|
+
const summary = `${emptyProductWarnings.length} category/rail product snapshot quer${emptyProductWarnings.length === 1 ? "y" : "ies"} matched 0 products.`;
|
|
120
|
+
if (strictEmptyCategories) {
|
|
121
|
+
throw new Error(`${summary} Rework src/navigation.ts queries or remove --strict-empty-categories.`);
|
|
122
|
+
}
|
|
123
|
+
console.warn(`Warning: ${summary}`);
|
|
124
|
+
}
|
|
113
125
|
|
|
114
126
|
function writePage(file, html) {
|
|
115
127
|
if (dryRun) {
|
|
@@ -143,6 +155,7 @@ async function fetchProducts(category, limit) {
|
|
|
143
155
|
.slice(0, limit);
|
|
144
156
|
} catch (error) {
|
|
145
157
|
const message = error instanceof Error ? error.message : String(error);
|
|
158
|
+
failedProductSnapshots.add(category.id);
|
|
146
159
|
if (strictProducts) {
|
|
147
160
|
throw new Error(`Could not hydrate products for ${category.id}: ${message}`, { cause: error });
|
|
148
161
|
}
|
|
@@ -151,6 +164,16 @@ async function fetchProducts(category, limit) {
|
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
166
|
|
|
167
|
+
function reportEmptyProducts(category, products, surface, requestedLimit) {
|
|
168
|
+
if (skipProducts || !client || requestedLimit <= 0 || products.length > 0 || failedProductSnapshots.has(category.id)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const label = categoryLabelPath(category).join(" / ") || category.label || category.id;
|
|
172
|
+
const message = `${surface} "${label}" (${category.id}) matched 0 products. Check its SearchQuery.Query, SearchQuery.Tags, and the shop defaultQuery; use "|" for OR terms and avoid multiple tags for alternatives.`;
|
|
173
|
+
emptyProductWarnings.push(message);
|
|
174
|
+
console.warn(`Warning: ${message}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
154
177
|
function hydrateHomeHtml(html, categories, productsByCategory) {
|
|
155
178
|
const title = readAttribute(html, "orderly-home-page", "title") ?? siteTitle;
|
|
156
179
|
const eyebrow = readAttribute(html, "orderly-home-page", "eyebrow");
|
|
@@ -624,6 +647,7 @@ Options:
|
|
|
624
647
|
--image-base-url <url> StoredImage CDN prefix. Defaults to Orderly production CDN.
|
|
625
648
|
--skip-products Hydrate category/home text only, without backend product calls.
|
|
626
649
|
--strict-products Fail when a product snapshot call fails.
|
|
650
|
+
--strict-empty-categories Fail when a category page or homepage rail query returns 0 products.
|
|
627
651
|
--dry-run Print files that would change without writing.
|
|
628
652
|
--help, -h Show this help.
|
|
629
653
|
`);
|