@orderlyshop/web-components 0.1.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/AGENTS.md +110 -0
- package/README.md +685 -0
- package/bin/orderly-build-category-pages.mjs +160 -0
- package/bin/orderly-generate-category-pages.mjs +308 -0
- package/bin/orderly-hydrate-static-pages.mjs +595 -0
- package/bin/orderly-init-navigation.mjs +327 -0
- package/bin/orderly-init-shop.mjs +876 -0
- package/bin/orderly-init-taxonomy.mjs +2 -0
- package/bin/orderly-publish-site.mjs +342 -0
- package/custom-elements.json +495 -0
- package/dist/browser/orderly-web-components.define.global.js +3085 -0
- package/dist/browser/orderly-web-components.define.global.js.map +1 -0
- package/dist/browser/orderly-web-components.global.js +3085 -0
- package/dist/browser/orderly-web-components.global.js.map +1 -0
- package/dist/default-shop-DWdB_MRd.d.ts +220 -0
- package/dist/default-shop.d.ts +6 -0
- package/dist/default-shop.js +762 -0
- package/dist/default-shop.js.map +1 -0
- package/dist/define-IAQk8OmQ.d.ts +9 -0
- package/dist/define.d.ts +2 -0
- package/dist/define.js +10266 -0
- package/dist/define.js.map +1 -0
- package/dist/index.d.ts +683 -0
- package/dist/index.js +10589 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation.d.ts +51 -0
- package/dist/navigation.js +818 -0
- package/dist/navigation.js.map +1 -0
- package/dist/query.d.ts +31 -0
- package/dist/query.js +115 -0
- package/dist/query.js.map +1 -0
- package/dist/registry-CPDecU3g.d.ts +6 -0
- package/dist/shop-BnT1C6kG.d.ts +173 -0
- package/dist/shop-query.d.ts +8 -0
- package/dist/shop-query.js +100 -0
- package/dist/shop-query.js.map +1 -0
- package/dist/shop.d.ts +8 -0
- package/dist/shop.js +10359 -0
- package/dist/shop.js.map +1 -0
- package/dist/stores.d.ts +46 -0
- package/dist/stores.js +145 -0
- package/dist/stores.js.map +1 -0
- package/dist/taxonomy.d.ts +35 -0
- package/dist/taxonomy.js +247 -0
- package/dist/taxonomy.js.map +1 -0
- package/dist/types-CCQDd6Nd.d.ts +95 -0
- package/docs/components/README.md +610 -0
- package/docs/components/product-grid.md +176 -0
- package/docs/components/product-rail.md +174 -0
- package/examples/shop/README.md +71 -0
- package/examples/shop/package.json +28 -0
- package/examples/shop/src/category.html +20 -0
- package/examples/shop/src/checkout.html +21 -0
- package/examples/shop/src/forretningsbetingelser.html +80 -0
- package/examples/shop/src/includes/body-end.html +1 -0
- package/examples/shop/src/includes/body-start.html +2 -0
- package/examples/shop/src/includes/head.html +32 -0
- package/examples/shop/src/index.html +25 -0
- package/examples/shop/src/navigation.ts +154 -0
- package/examples/shop/src/product.html +24 -0
- package/examples/shop/src/templates/shop-footer.html +76 -0
- package/examples/shop/tsconfig.json +32 -0
- package/examples/shop/vite.config.mjs +184 -0
- package/html-custom-data.json +262 -0
- package/package.json +118 -0
- package/server/README.md +111 -0
- package/server/apache/.htaccess +18 -0
- package/server/nginx/orderly-products.conf +24 -0
- package/server/node/product-snapshot-server.mjs +133 -0
- package/server/php/orderly-product.php +204 -0
package/README.md
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
# `@orderlyshop/web-components`
|
|
2
|
+
|
|
3
|
+
Headless native web components for Orderly storefronts.
|
|
4
|
+
|
|
5
|
+
The package depends on `@orderlyshop/core-client` and wraps Core contracts such as `SearchObject`, `SearchQuery`, and `DraftOrder` in reusable browser components. Components render practical light-DOM defaults and expose typed properties, events, controllers, stores, and template overrides so host shops keep full control over CSS, layout, routing, and copy.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @orderlyshop/core-client @orderlyshop/web-components
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Create A Shop
|
|
14
|
+
|
|
15
|
+
For a new vanilla storefront, run the scaffold command from the shop project:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npx orderly-init-shop
|
|
19
|
+
npm install
|
|
20
|
+
npm run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Agents and developers should use `npx orderly-init-shop` before hand-writing shop files. It creates `src/index.html`, `src/category.html`, `src/product.html`, `src/checkout.html`, `src/includes/head.html`, `src/navigation.ts`, `src/shop-query.ts`, `src/templates/*.html`, `src/style.css`, `vite.config.mjs`, and package scripts. Pass an account id when the shop-wide default query should be tenant-scoped from the start:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npx orderly-init-shop --account-id 00000000-0000-0000-0000-000000000000
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The scaffold uses Vite for local development and static builds, but Vite is added to the generated shop's `devDependencies`. Local development is configured for `http://localhost:61677` because that is the backend CORS development origin. `@orderlyshop/web-components` does not depend on Vite at runtime.
|
|
30
|
+
|
|
31
|
+
## Browser Standalone Bundle
|
|
32
|
+
|
|
33
|
+
The package build also emits browser-ready standalone bundles with `@orderlyshop/core-client`, Connect, and protobuf runtime code included. These files are shipped in the npm package and are what the `unpkg` and `jsdelivr` package fields point at.
|
|
34
|
+
|
|
35
|
+
Use the configurable bundle when a vanilla HTML site needs to set shop options before registering elements:
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<script src="/node_modules/@orderlyshop/web-components/dist/browser/orderly-web-components.global.js"></script>
|
|
39
|
+
<script>
|
|
40
|
+
const { configureShop } = OrderlyWebComponents;
|
|
41
|
+
|
|
42
|
+
configureShop({
|
|
43
|
+
uiLanguage: "DA",
|
|
44
|
+
defaultShop: {
|
|
45
|
+
baseUrl: "https://service.orderly.shop",
|
|
46
|
+
navigationDefinitions: [
|
|
47
|
+
{
|
|
48
|
+
label: "Sko",
|
|
49
|
+
slug: "sko",
|
|
50
|
+
query: { query: "sko sneakers" },
|
|
51
|
+
children: [
|
|
52
|
+
{ label: "Dame sko", slug: "dame-sko", query: { query: "dame sko sneakers" } }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
</script>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Use the auto-register bundle for the built-in default shop or pages that configure everything declaratively:
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<script src="/node_modules/@orderlyshop/web-components/dist/browser/orderly-web-components.define.global.js"></script>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The normal package entrypoints remain ESM for bundlers. The standalone files are classic browser scripts and expose `window.OrderlyWebComponents`.
|
|
68
|
+
|
|
69
|
+
`configureShop()` is the recommended one-call setup for HTML head scripts. Its parameter is strongly typed as `ShopConfig`, so VS Code/TypeScript can provide completions directly on the object literal. All fields are optional: `uiLanguage`, `defaultShop`, `pageLayout`, `responsiveTemplates`, `shopFooter`, `storedImageUrls`, and `components`. It applies configuration first and then registers custom elements. Pass `components: false` only when elements are registered elsewhere.
|
|
70
|
+
|
|
71
|
+
Built-in component copy is Danish by default (`uiLanguage: "DA"`). Set `uiLanguage: "EN"` to switch built-in labels, empty states, checkout copy, navigation controls, and page defaults to English. Shop-specific attributes, templates, and `defaultShop` label fields still take precedence over language defaults.
|
|
72
|
+
|
|
73
|
+
## API Discovery
|
|
74
|
+
|
|
75
|
+
Developers and coding agents should start with these package-owned docs:
|
|
76
|
+
|
|
77
|
+
- [`docs/components/README.md`](./docs/components/README.md) lists every custom element, common attributes, typed properties, events, and template hooks.
|
|
78
|
+
- [`docs/components/product-grid.md`](./docs/components/product-grid.md) documents search loading, sorting, paging, default query merging, and declarative query markup.
|
|
79
|
+
- [`docs/components/product-rail.md`](./docs/components/product-rail.md) documents homepage/category rails and their query configuration.
|
|
80
|
+
- [`AGENTS.md`](./AGENTS.md) gives coding agents the conventions for building shops with this package.
|
|
81
|
+
- [`custom-elements.json`](./custom-elements.json) is the Custom Elements Manifest for tools that understand web component metadata.
|
|
82
|
+
- [`html-custom-data.json`](./html-custom-data.json) is VS Code compatible custom data for HTML autocomplete and hover docs.
|
|
83
|
+
|
|
84
|
+
Enable HTML editor hints in a consuming project by adding the package custom data file to `.vscode/settings.json`:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"html.customData": [
|
|
89
|
+
"./node_modules/@orderlyshop/web-components/html-custom-data.json"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The most important convention is that Core contracts use typed JavaScript properties, while simple search use cases can be declared in HTML. For example:
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<orderly-product-rail
|
|
98
|
+
title="Sko"
|
|
99
|
+
cta-label="Se alle"
|
|
100
|
+
cta-href="/categories/sko/"
|
|
101
|
+
keywords="H&M"
|
|
102
|
+
tags="børnetøj,bluser"
|
|
103
|
+
order-by="CreatedTime desc">
|
|
104
|
+
</orderly-product-rail>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Default Shop
|
|
108
|
+
|
|
109
|
+
The package can render a working default storefront with the built-in backend, navigation, sorting, basket, product detail, and footer defaults:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import "@orderlyshop/web-components/define";
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<orderly-category-page></orderly-category-page>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The default backend is `https://service.orderly.shop`. Category links use real `/categories/<category-path>/` URLs by default so production builds can ship server-rendered category HTML. Use `categoryUrlMode: "hash"` only when a shop explicitly wants the older `/category.html#id=<category>` routing.
|
|
120
|
+
|
|
121
|
+
Customize the default once per shop from the HTML head:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { create } from "@bufbuild/protobuf";
|
|
125
|
+
import { AccountIdSchema, SearchQuerySchema, UUIDSchema } from "@orderlyshop/core-client";
|
|
126
|
+
import { configureShop } from "@orderlyshop/web-components";
|
|
127
|
+
import { navigationDefinitions } from "./navigation";
|
|
128
|
+
|
|
129
|
+
const defaultQuery = create(SearchQuerySchema, {
|
|
130
|
+
SourceAccountId: create(AccountIdSchema, {
|
|
131
|
+
Id: create(UUIDSchema, { Value: "00000000-0000-0000-0000-000000000000" })
|
|
132
|
+
}),
|
|
133
|
+
HiddenQuery: "published"
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
configureShop({
|
|
137
|
+
uiLanguage: "DA",
|
|
138
|
+
defaultShop: {
|
|
139
|
+
baseUrl: "https://service.orderly.shop",
|
|
140
|
+
defaultQuery,
|
|
141
|
+
brandLabel: "Example Shop",
|
|
142
|
+
homeHref: "/",
|
|
143
|
+
productHref: "/product.html",
|
|
144
|
+
navigationDefinitions,
|
|
145
|
+
sortOptions: [
|
|
146
|
+
{ label: "Newest", orderBy: ["CreatedTime desc"] },
|
|
147
|
+
{ label: "Price: low to high", orderBy: ["Price asc"] }
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
pageLayout: {
|
|
151
|
+
logoSrc: "https://orderly.shop/home/App_Icon.svg",
|
|
152
|
+
logoAlt: "Example Shop",
|
|
153
|
+
logoHref: "/"
|
|
154
|
+
},
|
|
155
|
+
responsiveTemplates: {
|
|
156
|
+
mobileMediaQuery: "(max-width: 860px)"
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Use English built-in copy by changing only the top-level language:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
configureShop({ uiLanguage: "EN" });
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`defaultQuery` is a full Core `SearchQuery` and is merged into every `orderly-product-grid` request, including grids inside `orderly-category-page`, `orderly-collection-page`, and `orderly-product-rail`. A tenant account id is just one possible field on that query. Advanced shops can also assign `defaultQuery` directly on a specific component from JavaScript. User-facing category and search text stays in `SearchQuery.Query`; any text from the default query is moved to `HiddenQuery` so it still narrows results without replacing the visible search term.
|
|
168
|
+
|
|
169
|
+
`configureShop()` validates `navigationDefinitions`. Every category needs an explicit globally unique `slug` using lowercase letters, numbers, and hyphens. If navigation is invalid, category pages render a visible configuration error that points at the broken item.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
categoryPage.defaultQuery = defaultQuery;
|
|
173
|
+
productGrid.defaultQuery = defaultQuery;
|
|
174
|
+
productRail.defaultQuery = defaultQuery;
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
After that, category pages can stay declarative:
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<orderly-category-page slug="shoes/women"></orderly-category-page>
|
|
181
|
+
<orderly-category-page slug="women"></orderly-category-page>
|
|
182
|
+
<orderly-category-page></orderly-category-page>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
When no category attribute is set, `orderly-category-page` resolves the category from the current URL. It supports `/category.html#id=shoes%2Fwomen` and generated path pages such as `/categories/shoes/women/`. Hash ids are matched against both the generated category id and each navigation item's slug.
|
|
186
|
+
|
|
187
|
+
## Example Shop
|
|
188
|
+
|
|
189
|
+
A complete vanilla HTML storefront example with strongly typed TypeScript navigation data is included in the npm package source at `examples/shop`.
|
|
190
|
+
|
|
191
|
+
After installing from the repository workspace, run it with:
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
npm run dev --workspace @orderlyshop/example-shop
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Inside an installed package, inspect the same reference implementation at:
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
node_modules/@orderlyshop/web-components/examples/shop
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The example demonstrates how to customize the package default with Danish navigation, shop copy, and declarative home/category/product/checkout pages while exercising the base component renderers directly. It intentionally avoids local CSS and templates while the package default look and feel is developed. Shared component configuration lives in `src/includes/head.html`; the strongly typed navigation data lives in `src/navigation.ts`. It uses the package-level `orderly-generate-category-pages` and `orderly-build-category-pages` commands for simple real-URL category pages. It is included as readable source code and is not exported as part of the runtime API.
|
|
204
|
+
|
|
205
|
+
## Navigation Setup
|
|
206
|
+
|
|
207
|
+
Installing `@orderlyshop/web-components` installs explicit shop and navigation helper tools. They do not run automatically during `npm install`.
|
|
208
|
+
|
|
209
|
+
For a new shop, start with the full scaffold:
|
|
210
|
+
|
|
211
|
+
```sh
|
|
212
|
+
npx orderly-init-shop
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Create only a default strongly typed `navigation.ts` in the current project when the shop scaffold already exists:
|
|
216
|
+
|
|
217
|
+
```sh
|
|
218
|
+
npx orderly-init-navigation
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Create the file somewhere else:
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
npx orderly-init-navigation --path src/navigation.ts
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Agent workflow for a new shop:
|
|
228
|
+
|
|
229
|
+
1. Ask the user for the new shop's Orderly account id.
|
|
230
|
+
2. Run the navigation helper to dump sampled product information:
|
|
231
|
+
|
|
232
|
+
```sh
|
|
233
|
+
npx orderly-init-navigation --suggest --account-id <account-id>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
3. Read the generated `navigation-products.json`. The helper does not infer categories. It writes sampled product records with `title`, `brand`, and `tags` only, plus sampling metadata.
|
|
237
|
+
4. Create `navigation.ts` manually from the dump. Export `navigationDefinitions: NavigationDefinition[]`. Each entry must have `label` and a stable, globally unique `slug`, and can also have optional `heroImage`, optional `description`, optional `query`, and optional recursive `children`. Use plain `SearchQueryInput` objects for `query`, such as `{ query: "sko sneakers", tags: ["sko"] }`.
|
|
238
|
+
5. Keep navigation to at most two category levels. Categories should be reasonably granular, but avoid empty categories or categories backed by only a few products.
|
|
239
|
+
6. Configure the shop scope with a Core `SearchQuery` and pass it to `configureShop({ defaultShop: { defaultQuery } })`.
|
|
240
|
+
|
|
241
|
+
The helper is intentionally an explicit tool for developers and coding agents. It is not a postinstall action. Agents should keep user-owned navigation edits, ask before overwriting files, and treat the product dump as source material for human-reviewable navigation.
|
|
242
|
+
|
|
243
|
+
Show tool help:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
npx orderly-init-navigation --help
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Optional flags:
|
|
250
|
+
|
|
251
|
+
```sh
|
|
252
|
+
npx orderly-init-navigation --suggest --account-id <account-id> --base-url https://service.orderly.shop --max-results 1000 --product-dump-path src/navigation-products.json
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Use TypeScript output paths when the shop source is TypeScript:
|
|
256
|
+
|
|
257
|
+
```sh
|
|
258
|
+
npx orderly-init-navigation --suggest --account-id <account-id> --path src/navigation.ts --product-dump-path src/navigation-products.json
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The product dump is sampled so large search indexes are not copied wholesale. It starts with every second product, then every third, fifth, eighth, and thirteenth product as the source result set grows. `--max-results` caps the number of product records written and defaults to `1000`.
|
|
262
|
+
|
|
263
|
+
Navigation data has three layers:
|
|
264
|
+
|
|
265
|
+
- `NavigationDefinition` is the authored data in `navigation.ts`. It is the only type most shops need to write. Every item needs an explicit stable `slug`; labels are display text and can change without changing URLs.
|
|
266
|
+
- `ResolvedNavigationItem` is created by `createCategoryNavigation()` and consumed by default page components. It adds stable ids, hrefs, normalized Core `SearchQuery` values, and derived metadata.
|
|
267
|
+
- `NavigationMetadata` contains those derived page values: slug, parent id, path segments, normalized query, search text, description, hero image, and page root.
|
|
268
|
+
|
|
269
|
+
The older `CategoryDefinition`, `CategoryNavigationItem`, and `CategoryMetadata` names remain as compatibility aliases only.
|
|
270
|
+
|
|
271
|
+
## Real Category URLs
|
|
272
|
+
|
|
273
|
+
Installing `@orderlyshop/web-components` also installs generic category page tools. Real path category URLs are the default because they are the best fit for SEO and static/server-rendered category pages.
|
|
274
|
+
|
|
275
|
+
Generate pages from the current shop's `src/navigation.ts` or `src/navigation.js` and category page template. If `src/category.html` exists, the generator uses it and writes generated source pages under `src/categories`; otherwise it falls back to `category.html` and `categories`:
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
npx orderly-generate-category-pages
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Preview what would be generated:
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
npx orderly-generate-category-pages --dry-run
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Remove generated pages:
|
|
288
|
+
|
|
289
|
+
```sh
|
|
290
|
+
npx orderly-generate-category-pages --clean
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Build with generated category pages and then clean the generated source files. Scaffolded shops use this flow from `npm run build` by default:
|
|
294
|
+
|
|
295
|
+
```sh
|
|
296
|
+
npx orderly-build-category-pages
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`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 a small product snapshot 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 hide the fallback when their client-side search data has loaded, so users avoid a blank category page while Google receives meaningful initial HTML.
|
|
300
|
+
|
|
301
|
+
Hydrate an already-built site manually, for example in a nightly job:
|
|
302
|
+
|
|
303
|
+
```sh
|
|
304
|
+
npx orderly-hydrate-static-pages --base-url https://service.orderly.shop --products-per-category 12
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
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.
|
|
308
|
+
|
|
309
|
+
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.
|
|
310
|
+
|
|
311
|
+
## Publish Static Sites
|
|
312
|
+
|
|
313
|
+
Installing `@orderlyshop/web-components` also installs a publish command for the consuming shop project. Run it from the project directory. The command generates category pages, builds, hydrates the built homepage/category pages, and publishes the resulting `dist` folder as vanilla HTML/JS/CSS.
|
|
314
|
+
|
|
315
|
+
Publish the current project to a local folder:
|
|
316
|
+
|
|
317
|
+
```sh
|
|
318
|
+
npx orderly-publish-site --target local --dir published-site
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Opt out of generated category pages when a project has its own server-side category routing:
|
|
322
|
+
|
|
323
|
+
```sh
|
|
324
|
+
npx orderly-publish-site --target local --dir published-site --no-category-pages
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Publish the current project over FTP:
|
|
328
|
+
|
|
329
|
+
```sh
|
|
330
|
+
npx orderly-publish-site --target ftp
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
The FTP target prompts for server URL, remote folder, username, and password when values are missing. Values are saved in `.orderly-publish.local.json` in the current project directory and reused on the next publish. Keep that file out of git; it is written with owner-only file permissions where the OS supports it.
|
|
334
|
+
|
|
335
|
+
Re-enter saved FTP settings:
|
|
336
|
+
|
|
337
|
+
```sh
|
|
338
|
+
npx orderly-publish-site --target ftp --configure
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Server Side Product URLs
|
|
342
|
+
|
|
343
|
+
Category pages can be generated and hydrated statically. Product pages usually need real URLs without generating one HTML file per product. The package ships server helper files under `server/` for this pattern:
|
|
344
|
+
|
|
345
|
+
- `server/apache/.htaccess` rewrites unknown product-like `.html` URLs to PHP.
|
|
346
|
+
- `server/php/orderly-product.php` reuses the built `product.html`, injects declarative SSR fallback markup into `<orderly-product-detail-page>`, and preserves built JS/CSS references.
|
|
347
|
+
- `server/nginx/orderly-products.conf` provides the equivalent Nginx rewrite for PHP-FPM.
|
|
348
|
+
- `server/node/product-snapshot-server.mjs` is an optional Node snapshot endpoint that calls `SearchService.Search` through `@orderlyshop/core-client` and returns product title, brand, price, image, and description as JSON.
|
|
349
|
+
|
|
350
|
+
Apache deployment example:
|
|
351
|
+
|
|
352
|
+
```sh
|
|
353
|
+
npm run build
|
|
354
|
+
cp node_modules/@orderlyshop/web-components/server/apache/.htaccess dist/.htaccess
|
|
355
|
+
cp node_modules/@orderlyshop/web-components/server/php/orderly-product.php dist/orderly-product.php
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
If PHP should SSR real product fields, configure a server-side snapshot endpoint:
|
|
359
|
+
|
|
360
|
+
```sh
|
|
361
|
+
ORDERLY_BACKEND_URL=https://service.orderly.shop \
|
|
362
|
+
node node_modules/@orderlyshop/web-components/server/node/product-snapshot-server.mjs
|
|
363
|
+
|
|
364
|
+
export ORDERLY_PRODUCT_SNAPSHOT_ENDPOINT=http://127.0.0.1:4107/orderly-product-snapshot
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
The PHP renderer emits a small declarative fallback with `data-orderly-ssr-fallback`; `orderly-product-detail-page` hides that fallback when the browser has loaded the live `SearchObject`. See [`server/README.md`](./server/README.md) for Apache, Nginx, PHP, Node, caching, and SEO notes.
|
|
368
|
+
|
|
369
|
+
## Define Elements
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
import { configureShop } from "@orderlyshop/web-components";
|
|
373
|
+
|
|
374
|
+
configureShop({
|
|
375
|
+
responsiveTemplates: { mobileMediaQuery: "(max-width: 860px)" },
|
|
376
|
+
storedImageUrls: { baseUrl: "https://static.example-cdn.com/" }
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Use a custom prefix when a shop already owns the default tag names:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
configureShop({ components: { prefix: "shop" } });
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
The side-effect entrypoint registers the default `orderly-*` tags:
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
import "@orderlyshop/web-components/define";
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
The package installs baseline storefront CSS and structural lazy-load CSS when components are registered. The baseline CSS gives the default light-DOM components usable layout, spacing, responsive behavior, and product presentation without requiring a shop-owned stylesheet. Host shops can override it with normal CSS variables and `orderly-*` class selectors.
|
|
393
|
+
|
|
394
|
+
The lazy-load CSS can also be included as early as possible in the document head when pages contain slotted light-DOM content and need to avoid pre-hydration flashes:
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
import { ORDERLY_LAZY_LOAD_CSS } from "@orderlyshop/web-components";
|
|
398
|
+
|
|
399
|
+
const style = document.createElement("style");
|
|
400
|
+
style.textContent = ORDERLY_LAZY_LOAD_CSS;
|
|
401
|
+
document.head.append(style);
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
`defineOrderlyWebComponents()` installs the same lazy-load CSS at runtime as a fallback, but head inclusion is what prevents raw slotted links from flashing before JavaScript has loaded. The default component CSS is exported as `ORDERLY_DEFAULT_CSS` and installed automatically by `defineOrderlyWebComponents()`.
|
|
405
|
+
|
|
406
|
+
## Global Templates
|
|
407
|
+
|
|
408
|
+
Templates can live next to a specific element or in a shared page-level registry. Local templates still win, but components also look for document-level templates with `data-orderly-for`, `data-orderly-component`, or `data-orderly-template-scope`.
|
|
409
|
+
|
|
410
|
+
```html
|
|
411
|
+
<template data-orderly-template="product" data-orderly-for="product-tile">
|
|
412
|
+
<article class="product-card">
|
|
413
|
+
<h3 data-orderly-bind="title"></h3>
|
|
414
|
+
<orderly-stored-image data-orderly-bind="image" fit="contain"></orderly-stored-image>
|
|
415
|
+
<p data-orderly-bind="price"></p>
|
|
416
|
+
<button type="button" data-orderly-action="add-to-basket">
|
|
417
|
+
<span data-orderly-bind="basket-action-icon"></span>
|
|
418
|
+
</button>
|
|
419
|
+
</article>
|
|
420
|
+
</template>
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
This is useful for vanilla shops that include shared snippets such as `body-start.html`; all shop-specific templates can be included there instead of being repeated on every category page. A shop can keep each template in `src/templates/*.html` and use build-time comments such as `<!-- orderly-include-template: ../templates/product-tile.html -->` from `body-start.html`. `data-orderly-for` can target a full tag name such as `orderly-product-tile`, a prefix-independent component name such as `product-tile`, or `*`. Responsive global templates support the same `data-orderly-viewport` and `data-orderly-media` attributes as local templates.
|
|
424
|
+
|
|
425
|
+
JavaScript can also register templates:
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
import { registerGlobalTemplate } from "@orderlyshop/web-components";
|
|
429
|
+
|
|
430
|
+
registerGlobalTemplate({
|
|
431
|
+
name: "product",
|
|
432
|
+
for: "product-tile",
|
|
433
|
+
template: `<article><h3 data-orderly-bind="title"></h3></article>`
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Collection Page
|
|
438
|
+
|
|
439
|
+
```html
|
|
440
|
+
<orderly-collection-page
|
|
441
|
+
id="collection"
|
|
442
|
+
title="Summer collection"
|
|
443
|
+
description="Light layers and one-off pieces"
|
|
444
|
+
hero-image="/images/summer.jpg">
|
|
445
|
+
<template data-orderly-template="product">
|
|
446
|
+
<article class="product-card">
|
|
447
|
+
<orderly-product-tile></orderly-product-tile>
|
|
448
|
+
</article>
|
|
449
|
+
</template>
|
|
450
|
+
</orderly-collection-page>
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
import { create } from "@bufbuild/protobuf";
|
|
455
|
+
import { SearchQuerySchema } from "@orderlyshop/core-client";
|
|
456
|
+
import { createOrderlyWebClient } from "@orderlyshop/core-client/web";
|
|
457
|
+
|
|
458
|
+
const client = createOrderlyWebClient({ baseUrl: "https://api.orderly.example" });
|
|
459
|
+
const searchQuery = create(SearchQuerySchema, {
|
|
460
|
+
Term: "summer"
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
collection.client = client;
|
|
464
|
+
collection.query = searchQuery;
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Components
|
|
468
|
+
|
|
469
|
+
- `orderly-page-layout` provides reusable page regions for header, header actions, 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`.
|
|
470
|
+
- `orderly-home-page` composes page layout, side navigation, one product rail per top-level category, basket icon, basket drawer, product detail dialog, and footer. Configure it with `configureShop({ defaultShop })` plus attributes such as `title`, `eyebrow`, `rail-cta-label`, and `product-href`. The product dialog includes an expand icon link to `product-href#url=<encoded-share-url>`; `https://orderly.shop/` is stripped from Orderly product URLs before encoding.
|
|
471
|
+
- `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. The product dialog includes an expand icon link built from `SearchObject.ShareURL.URL`, using compact hashes for `https://orderly.shop/...` product URLs.
|
|
472
|
+
- `orderly-checkout-page` composes page layout, side 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. Configure copy through `configureShop({ defaultShop: { checkoutPageTitle, checkoutPageDescription, checkoutOrderTitle, checkoutTermsHref, checkoutLabels, basketLabels } })`, or assign `client`, `basketController`, `navigationItems`, `checkoutLabels`, and `basketLabels` from JavaScript.
|
|
473
|
+
- `orderly-product-detail-page` composes page layout, navigation, product detail, basket drawer, 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`.
|
|
474
|
+
- `orderly-stored-image` accepts `image: StoredImage`, resolves image URLs from the configured prefix, and applies `RotationDeg`, crop, and full-size metadata in the browser.
|
|
475
|
+
- `orderly-credit` accepts `credit: Credit` or declarative `amount` and `currency` attributes, then renders stylable money markup. DKK values are formatted as `kr. <pris>`, for example `kr. 100,00`.
|
|
476
|
+
- `orderly-product-tile` accepts `product: SearchObject` and emits `orderly-product-selected`, `orderly-add-to-basket`, and `orderly-remove-from-basket`. Templates can bind the product share URL with `data-orderly-bind="share-url"`; anchors receive `href` and other elements receive `share-url`. If an anchor already has `href="/product.html"`, the bound link becomes `/product.html#url=<encoded-share-url>`, using the compact path for `https://orderly.shop/...` product URLs. Default tiles and statically hydrated category tiles include `schema.org/Product` and `schema.org/Offer` microdata for name, image, URL, description, SKU, brand, price, currency, and in-stock availability when those fields are present. When a `BasketController` is assigned through `basketController`, the default action button toggles between add and remove, using an icon-only button next to the price.
|
|
477
|
+
- `orderly-product-page` accepts `product: SearchObject`, renders all product image thumbnails, switches the selected image on thumbnail click, and exposes product details plus add-to-basket behavior.
|
|
478
|
+
- `orderly-search-box` binds to a target `orderly-product-grid`. Use `mode="textbox"` for an inline search input or `mode="icon"` for a header icon that opens a full-page search overlay. Icon mode searches automatically with a 500ms debounce and renders matching product tiles below the search field. Default home and category pages use icon mode next to the basket icon.
|
|
479
|
+
- `orderly-product-grid` accepts `query: SearchQuery`, merges the configured shop query scope, calls `SearchService.Search`, renders its own sort control from `sortOptions`, supports opaque `ContinuationToken` paging, manual paging, dynamic/infinite scroll, and default loading placeholders while the first page is pending.
|
|
480
|
+
- `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.
|
|
481
|
+
- `orderly-collection-page` accepts `query: SearchQuery`, title, description, and hero image, then delegates fetching to `orderly-product-grid`.
|
|
482
|
+
- `orderly-shop-footer` renders configurable logo, about text, address, contact information, opening hours, and information links.
|
|
483
|
+
- `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` renders an item overview, verifies changed drafts 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`.
|
|
484
|
+
- `orderly-checkout` persists checkout profile fields, 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`.
|
|
485
|
+
- `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.
|
|
486
|
+
- `orderly-search-box`, `orderly-sort-select`, `orderly-filter-panel`, and `orderly-load-more` bind to `orderly-product-grid`.
|
|
487
|
+
|
|
488
|
+
## Rendering And Styling
|
|
489
|
+
|
|
490
|
+
The package ships baseline default CSS and uses light DOM. Default markup uses stable `orderly-*` class names and CSS variables such as `--orderly-color-accent`, `--orderly-color-page`, `--orderly-color-surface`, `--orderly-color-text`, `--orderly-color-muted`, and `--orderly-content-max-width`, so host CSS can style or replace the default look directly.
|
|
491
|
+
|
|
492
|
+
Rendering can be replaced with templates:
|
|
493
|
+
|
|
494
|
+
```html
|
|
495
|
+
<orderly-page-layout>
|
|
496
|
+
<template data-orderly-template="layout">
|
|
497
|
+
<section class="shop-layout">
|
|
498
|
+
<header data-orderly-slot="header"></header>
|
|
499
|
+
<aside data-orderly-slot="left"></aside>
|
|
500
|
+
<main data-orderly-slot="content"></main>
|
|
501
|
+
<footer data-orderly-slot="footer"></footer>
|
|
502
|
+
</section>
|
|
503
|
+
</template>
|
|
504
|
+
|
|
505
|
+
<a slot="header" href="/">Shop</a>
|
|
506
|
+
<shop-navigation slot="left"></shop-navigation>
|
|
507
|
+
<orderly-collection-page slot="content"></orderly-collection-page>
|
|
508
|
+
</orderly-page-layout>
|
|
509
|
+
|
|
510
|
+
<orderly-page-layout
|
|
511
|
+
logo-src="/brand/logo.svg"
|
|
512
|
+
logo-alt="Example shop"
|
|
513
|
+
logo-href="/">
|
|
514
|
+
<a slot="header" href="/">Example shop</a>
|
|
515
|
+
<orderly-shop-footer slot="footer"></orderly-shop-footer>
|
|
516
|
+
</orderly-page-layout>
|
|
517
|
+
|
|
518
|
+
<orderly-product-grid id="grid" paging="dynamic">
|
|
519
|
+
<template data-orderly-template="layout">
|
|
520
|
+
<section class="catalog">
|
|
521
|
+
<div data-orderly-slot="sort"></div>
|
|
522
|
+
<p data-orderly-bind="status"></p>
|
|
523
|
+
<ol data-orderly-slot="items"></ol>
|
|
524
|
+
<span data-orderly-sentinel></span>
|
|
525
|
+
</section>
|
|
526
|
+
</template>
|
|
527
|
+
<template data-orderly-template="empty">
|
|
528
|
+
<p class="empty-state">No matches.</p>
|
|
529
|
+
</template>
|
|
530
|
+
<template data-orderly-template="product">
|
|
531
|
+
<li class="catalog-item">
|
|
532
|
+
<orderly-product-tile></orderly-product-tile>
|
|
533
|
+
</li>
|
|
534
|
+
</template>
|
|
535
|
+
</orderly-product-grid>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
For dynamic paging, `orderly-product-grid` keeps the returned `PageResult.Continuation` as an opaque `ContinuationToken` and sends it as `SearchQuery.Continuation` when the sentinel enters the viewport. Dynamic and infinite paging do not render a load-more button. Use `paging="button"` only when a visible load-more button is desired, `paging="manual"` when another control calls `loadNextPage()`, and `paging="dynamic"` or `paging="infinite"` for automatic loading.
|
|
539
|
+
|
|
540
|
+
Every component has a default light-DOM implementation and matching template hooks. Use `data-orderly-bind` for values, `data-orderly-action` for behavior, and `data-orderly-slot` for repeated or projected content. Product tile templates can bind `basket-action-icon`, `basket-action-label`, and `basket-action-state` and use `data-orderly-action="add-to-basket"` for the add/remove toggle. Product page galleries use `data-orderly-slot="thumbnails"` and `data-orderly-action="select-image"` for custom thumbnail markup. Product rail templates can replace the outer `layout`, inner `grid`, product item, loading state, and empty state. The main template names are `layout`, `grid`, `footer`, `product`, `thumbnail`, `image`, `basket`, `basket-icon`, `line`, `checkout`, `navigation`, `item`, `search-box`, `sort`, `sort-select`, `filter-panel`, `load-more`, `address-line`, `contact-item`, `opening-hour`, and `information-link`.
|
|
541
|
+
|
|
542
|
+
Product sorting belongs to `orderly-product-grid`: assign `grid.sortOptions = [{ label, orderBy }]` or pass sort options through `orderly-collection-page.sortOptions`. The default grid layout renders the sort select above the products. Custom grid layouts should include `data-orderly-slot="sort"` where the control should appear, and custom sort templates can use `data-orderly-template="sort"` with a `<select data-orderly-action="sort">`.
|
|
543
|
+
|
|
544
|
+
Simple product queries can be declared in markup on `orderly-product-grid`, `orderly-product-rail`, and `orderly-collection-page`:
|
|
545
|
+
|
|
546
|
+
```html
|
|
547
|
+
<orderly-product-rail
|
|
548
|
+
title="Sko"
|
|
549
|
+
cta-label="Se alle"
|
|
550
|
+
cta-href="/categories/sko/"
|
|
551
|
+
keywords="H&M"
|
|
552
|
+
tags="børnetøj,bluser"
|
|
553
|
+
order-by="CreatedTime desc">
|
|
554
|
+
</orderly-product-rail>
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
The same query can be grouped as child markup:
|
|
558
|
+
|
|
559
|
+
```html
|
|
560
|
+
<orderly-product-rail
|
|
561
|
+
title="Sko"
|
|
562
|
+
cta-label="Se alle"
|
|
563
|
+
cta-href="/categories/sko/">
|
|
564
|
+
<query>
|
|
565
|
+
<tags>børnetøj,bluser</tags>
|
|
566
|
+
<keywords>H&M</keywords>
|
|
567
|
+
<order-by>CreatedTime desc</order-by>
|
|
568
|
+
</query>
|
|
569
|
+
</orderly-product-rail>
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
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.
|
|
573
|
+
|
|
574
|
+
Templates can be viewport-specific on every component:
|
|
575
|
+
|
|
576
|
+
```html
|
|
577
|
+
<orderly-product-page>
|
|
578
|
+
<template data-orderly-template="product" data-orderly-viewport="desktop">
|
|
579
|
+
<section class="product-detail product-detail--desktop">
|
|
580
|
+
<div data-orderly-slot="thumbnails"></div>
|
|
581
|
+
<orderly-stored-image data-orderly-bind="image" variant="object"></orderly-stored-image>
|
|
582
|
+
</section>
|
|
583
|
+
</template>
|
|
584
|
+
<template data-orderly-template="product" data-orderly-viewport="mobile">
|
|
585
|
+
<section class="product-detail product-detail--mobile">
|
|
586
|
+
<orderly-stored-image data-orderly-bind="image" variant="object"></orderly-stored-image>
|
|
587
|
+
<div data-orderly-slot="thumbnails"></div>
|
|
588
|
+
</section>
|
|
589
|
+
</template>
|
|
590
|
+
<template data-orderly-template="thumbnail">
|
|
591
|
+
<button type="button" data-orderly-action="select-image">
|
|
592
|
+
<orderly-stored-image data-orderly-bind="thumbnail"></orderly-stored-image>
|
|
593
|
+
</button>
|
|
594
|
+
</template>
|
|
595
|
+
</orderly-product-page>
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Use `data-orderly-viewport="mobile"` or `data-orderly-viewport="desktop"` for the package breakpoint, or `data-orderly-media="(max-width: 540px)"` for a component-specific media query. Components rerender when the viewport crosses the configured breakpoint. The default mobile breakpoint is `767px` and can be changed once per app:
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
import { configureShop } from "@orderlyshop/web-components";
|
|
602
|
+
|
|
603
|
+
configureShop({ responsiveTemplates: { mobileMediaQuery: "(max-width: 860px)" } });
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
`orderly-page-layout` can also be changed once per shop without touching every category page:
|
|
607
|
+
|
|
608
|
+
```ts
|
|
609
|
+
import { configureShop } from "@orderlyshop/web-components";
|
|
610
|
+
|
|
611
|
+
configureShop({
|
|
612
|
+
pageLayout: {
|
|
613
|
+
template: `
|
|
614
|
+
<section class="custom-shop-layout">
|
|
615
|
+
<header data-orderly-slot="header"></header>
|
|
616
|
+
<main data-orderly-slot="content"></main>
|
|
617
|
+
<footer>
|
|
618
|
+
<orderly-shop-footer></orderly-shop-footer>
|
|
619
|
+
<div data-orderly-slot="footer"></div>
|
|
620
|
+
</footer>
|
|
621
|
+
<aside data-orderly-slot="right"></aside>
|
|
622
|
+
<div data-orderly-slot="overlay"></div>
|
|
623
|
+
</section>
|
|
624
|
+
`
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Shop-specific footer content is usually clearer as a global template in shared HTML, for example `body-start.html`:
|
|
630
|
+
|
|
631
|
+
```html
|
|
632
|
+
<template data-orderly-template="footer" data-orderly-for="shop-footer">
|
|
633
|
+
<div class="orderly-shop-footer">
|
|
634
|
+
<div class="orderly-shop-footer__brand">
|
|
635
|
+
<a class="orderly-shop-footer__logo" href="/" aria-label="Example shop">
|
|
636
|
+
<img class="orderly-shop-footer__logo-image" src="/brand/logo.svg" alt="Example shop">
|
|
637
|
+
</a>
|
|
638
|
+
<p class="orderly-shop-footer__about">Independent shop copy.</p>
|
|
639
|
+
</div>
|
|
640
|
+
<section class="orderly-shop-footer__section" aria-labelledby="footerInfo">
|
|
641
|
+
<h2 id="footerInfo" class="orderly-shop-footer__heading">Information</h2>
|
|
642
|
+
<ul class="orderly-shop-footer__list">
|
|
643
|
+
<li class="orderly-shop-footer__link-item">
|
|
644
|
+
<a class="orderly-shop-footer__link" href="/delivery/">Delivery</a>
|
|
645
|
+
</li>
|
|
646
|
+
</ul>
|
|
647
|
+
</section>
|
|
648
|
+
</div>
|
|
649
|
+
</template>
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Typed JavaScript properties are used for Core contracts and clients. Attributes are limited to primitive configuration such as `base-url`, `paging`, `title`, `description`, `hero-image`, `logo-src`, `logo-alt`, `logo-href`, `fit`, `variant`, and `for`.
|
|
653
|
+
|
|
654
|
+
Basket checkout navigation is configured in markup:
|
|
655
|
+
|
|
656
|
+
```html
|
|
657
|
+
<orderly-basket checkout-href="/checkout.html" checkout-label="Go to checkout"></orderly-basket>
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Stored images can be rendered directly:
|
|
661
|
+
|
|
662
|
+
```html
|
|
663
|
+
<orderly-stored-image id="productImage" fit="cover" variant="thumbnail"></orderly-stored-image>
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
```ts
|
|
667
|
+
productImage.image = searchObject.Images[0];
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
`orderly-stored-image` uses `DEFAULT_IMAGE_BASE_URL`, currently `https://orderlyproduction.azureedge.net/`, when no custom image URL configuration is supplied. `configureStoredImageUrls({ baseUrl })` maps normal product images to `baseUrl/thumbnails/<StoredImage.Name>` and full/object variants to `baseUrl/objects/<StoredImage.Name>`. The component also supports `ImageDataBase64`, absolute URLs, and a custom resolver through `configureStoredImageUrls({ resolver })`.
|
|
671
|
+
|
|
672
|
+
## State And Security
|
|
673
|
+
|
|
674
|
+
Basket drafts and checkout profile fields are persisted to localStorage by default. Checkout profile data can include PII such as name, address, email, and phone. Provide custom stores to change or disable that behavior. This package never stores tokens, API keys, OAuth responses, or cookie values.
|
|
675
|
+
|
|
676
|
+
Basket persistence uses `LocalStorageBasketStore`. Checkout profile persistence uses `LocalStorageCheckoutProfileStore`. Both can be replaced:
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
import { createBasketController } from "@orderlyshop/web-components";
|
|
680
|
+
|
|
681
|
+
basket.basketController = createBasketController({ store: myBasketStore });
|
|
682
|
+
checkout.profileStore = myCheckoutProfileStore;
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
Authentication remains owned by `@orderlyshop/core-client`. Browser cookie-backed sessions work through the core browser client and credentialed gRPC-web requests.
|