@numueg/theme-sdk 0.2.2 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -4,7 +4,35 @@ All notable changes to `@numueg/theme-sdk` are documented here. The format is ba
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
- ## [0.1.0] - 2026-05-11
7
+ ## [0.3.1] - 2026-06-17
8
+
9
+ ### Added
10
+
11
+ - **Size charts** — `useProductSizeChart()` hook + `resolveSizeChart()` pure
12
+ resolver. Resolves the per-product chart (`product.attributes.size_chart`)
13
+ against the store-wide default (`store.settings.size_chart`) using the same
14
+ precedence as the merchant hub + backend validator (`mode`:
15
+ `custom` → `default` → `off`, with a legacy no-mode fallback). New types
16
+ `SizeChart` / `SizeChartMode`.
17
+ - `Product.attributes` and `Store.settings` are now typed (optional
18
+ `Record<string, unknown>`) — the storefront already forwards these JSONB
19
+ blobs (also used by `useFieldTranslation`); they were previously untyped.
20
+
21
+ ## [0.3.0] - 2026-06-10
22
+
23
+ ### Added
24
+
25
+ - **`defineThemeEntry`** — one-call theme entry that returns both `mount`
26
+ (client, hydration-aware via `hydrateRoot`) and `createApp` (server
27
+ `renderToString`), wiring `NuMuProvider` + page/product/collection context +
28
+ catalog forwarding + global style tokens + the customizer's live-preview
29
+ draft cycle. This is the SSR contract for federated themes.
30
+
31
+ ### Changed
32
+
33
+ - `mount()` adopts host-server-rendered HTML instead of re-rendering when the
34
+ host passes `hydrate: true`; pure, browser-free global-style-token compute so
35
+ the server render is deterministic.
8
36
 
9
37
  First public release. Full surface documented at [numueg.app/docs/sdk/overview](https://numueg.app/docs/sdk/overview).
10
38
 
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![npm](https://img.shields.io/npm/v/@numueg/theme-sdk.svg)](https://www.npmjs.com/package/@numueg/theme-sdk)
6
6
  [![license](https://img.shields.io/npm/l/@numueg/theme-sdk.svg)](./LICENSE)
7
7
 
8
- The SDK every NUMU theme consumes. 25+ hooks (`useCart`, `useProduct`, `useCheckout`, `useVariantSelection`, `useGiftCardBalance`, …), 15+ components (`AddToCartButton`, `ProductCard`, `Money`, `Section`, `NuMuProvider`, …), and helpers for variant resolution, asset URLs, and federation singletons.
8
+ The SDK every NUMU theme consumes. The `mountTheme()` runtime helper, 27+ hooks (`useCart`, `useProduct`, `useCheckout`, `useVariantSelection`, `useCurrentTemplate`, `useResolvedSettings`, …), 15+ components (`AddToCartButton`, `ProductCard`, `Money`, `Section`, `EditableText`/`EditableImage`, `NuMuProvider`, …), and helpers for variant resolution, focal image crops (`focalSrc`), global style tokens, asset URLs, and federation singletons.
9
9
 
10
10
  Themes import via the bare specifier `@numueg/theme-sdk` — at runtime the storefront's import map resolves it to the host-loaded singleton so every theme on the platform shares one React identity.
11
11
 
@@ -31,23 +31,23 @@ The `@numueg/theme-plugin` Vite plugin does this for you automatically.
31
31
 
32
32
  ## Usage
33
33
 
34
+ The host storefront calls your bundle's `mount(el, ctx)` where `ctx = { storeData, page, themeSettings, locale, … }`. Use `mountTheme()` — it wires catalog data, global style tokens, navigation, and live-edit updates for you and returns the `{ unmount, update }` contract the host expects:
35
+
34
36
  ```tsx
35
- import { createRoot } from "react-dom/client";
36
- import { NuMuProvider, useCart, AddToCartButton } from "@numueg/theme-sdk";
37
- import type { MountContext } from "@numueg/theme-sdk";
37
+ import { mountTheme, useCart } from "@numueg/theme-sdk";
38
38
 
39
39
  function App() {
40
40
  const { cart } = useCart();
41
41
  return <p>Items in cart: {cart.item_count}</p>;
42
42
  }
43
43
 
44
- export function mount(ctx: MountContext) {
45
- const root = createRoot(document.getElementById("numu-root")!);
46
- root.render(<NuMuProvider {...ctx}><App /></NuMuProvider>);
47
- return () => root.unmount();
44
+ export function mount(el: HTMLElement, ctx: unknown) {
45
+ return mountTheme(el, ctx, () => <App />);
48
46
  }
49
47
  ```
50
48
 
49
+ > The authoritative `ctx` shape is defined by the host's `ByotThemeBoundary` (numu-storefront), not by SDK types — accept it as opaque and let `mountTheme`/`NuMuProvider` normalize it.
50
+
51
51
  ## Docs
52
52
 
53
53
  - [SDK Overview](https://numueg.app/docs/sdk/overview)
@@ -11,6 +11,14 @@ interface Store {
11
11
  default_language: string;
12
12
  use_nextjs_storefront: boolean;
13
13
  social_links?: Record<string, string>;
14
+ /**
15
+ * Store-level JSONB settings blob. Holds merchant-wide configuration the
16
+ * storefront forwards to themes — e.g. the store-wide default
17
+ * `size_chart` (see {@link SizeChart}) used when a product opts into
18
+ * `mode: "default"`. Untyped here because the shape grows independently
19
+ * of the SDK; narrow it at the read site.
20
+ */
21
+ settings?: Record<string, unknown>;
14
22
  }
15
23
  /** Product entity */
16
24
  interface Product {
@@ -30,6 +38,42 @@ interface Product {
30
38
  in_stock: boolean;
31
39
  seo_title?: string;
32
40
  seo_description?: string;
41
+ /**
42
+ * Per-product JSONB attribute blob the storefront forwards verbatim.
43
+ * Holds translated fields (`name_ar`, … — see `useFieldTranslation`) and
44
+ * the per-product `size_chart` ({@link SizeChart}). Untyped because the
45
+ * shape is open-ended; `useProductSizeChart` narrows the size-chart slot.
46
+ */
47
+ attributes?: Record<string, unknown>;
48
+ }
49
+ /**
50
+ * Size-chart resolution mode (mirrors the merchant hub's editor).
51
+ *
52
+ * "custom" → use the product's own chart
53
+ * "default" → fall back to the store-wide chart (`store.settings.size_chart`)
54
+ * "off" → never show, even if a store default exists
55
+ */
56
+ type SizeChartMode = "default" | "custom" | "off";
57
+ /**
58
+ * A size / measurement chart, stored per-product at
59
+ * `product.attributes.size_chart` and store-wide at
60
+ * `store.settings.size_chart`. Resolve the two with {@link useProductSizeChart}
61
+ * instead of reading the raw blobs.
62
+ */
63
+ interface SizeChart {
64
+ /** Legacy boolean kept for charts written before `mode` existed. */
65
+ enabled?: boolean;
66
+ mode?: SizeChartMode;
67
+ /** Column labels, e.g. ["Chest", "Waist", "Hip"]. */
68
+ column_headers: string[];
69
+ /** One row per size; `values` aligns to `column_headers`. */
70
+ rows: Array<{
71
+ size: string;
72
+ values: string[];
73
+ }>;
74
+ unit?: "cm" | "in" | "kg";
75
+ notes?: string;
76
+ image_url?: string;
33
77
  }
34
78
  interface ProductImage {
35
79
  id: string;
@@ -138,6 +182,25 @@ interface Order {
138
182
  items: OrderItem[];
139
183
  created_at: string;
140
184
  shipping_address?: Address;
185
+ /**
186
+ * Discount code the customer applied at checkout, if any. Sourced from
187
+ * the backend order-detail (`GET /storefront/me/orders/{id}`). Themes
188
+ * render this on the order-confirmation / order-detail page.
189
+ */
190
+ coupon_code?: string | null;
191
+ /**
192
+ * Automatic offers (offers-v2 promotions) applied to the order. Each
193
+ * entry carries the localized title and the discount it contributed.
194
+ * `amount` is in integer cents — divide by 100 (or pass through
195
+ * `<Money>`) before display, consistent with the rest of the order's
196
+ * money fields.
197
+ */
198
+ applied_promotions?: {
199
+ id: string;
200
+ title: string;
201
+ title_ar?: string;
202
+ amount: number;
203
+ }[];
141
204
  }
142
205
  interface OrderItem {
143
206
  product_id: string;
@@ -162,4 +225,4 @@ interface Page {
162
225
  data?: Record<string, any>;
163
226
  }
164
227
 
165
- export type { Address as A, Cart as C, Order as O, Page as P, Store as S, CartItem as a, Collection as b, Customer as c, OrderItem as d, Product as e, ProductImage as f, ProductVariant as g, ProductOption as h };
228
+ export type { Address as A, Cart as C, Order as O, Page as P, SizeChart as S, CartItem as a, Collection as b, Customer as c, OrderItem as d, Product as e, ProductImage as f, ProductVariant as g, SizeChartMode as h, Store as i, ProductOption as j };
@@ -11,6 +11,14 @@ interface Store {
11
11
  default_language: string;
12
12
  use_nextjs_storefront: boolean;
13
13
  social_links?: Record<string, string>;
14
+ /**
15
+ * Store-level JSONB settings blob. Holds merchant-wide configuration the
16
+ * storefront forwards to themes — e.g. the store-wide default
17
+ * `size_chart` (see {@link SizeChart}) used when a product opts into
18
+ * `mode: "default"`. Untyped here because the shape grows independently
19
+ * of the SDK; narrow it at the read site.
20
+ */
21
+ settings?: Record<string, unknown>;
14
22
  }
15
23
  /** Product entity */
16
24
  interface Product {
@@ -30,6 +38,42 @@ interface Product {
30
38
  in_stock: boolean;
31
39
  seo_title?: string;
32
40
  seo_description?: string;
41
+ /**
42
+ * Per-product JSONB attribute blob the storefront forwards verbatim.
43
+ * Holds translated fields (`name_ar`, … — see `useFieldTranslation`) and
44
+ * the per-product `size_chart` ({@link SizeChart}). Untyped because the
45
+ * shape is open-ended; `useProductSizeChart` narrows the size-chart slot.
46
+ */
47
+ attributes?: Record<string, unknown>;
48
+ }
49
+ /**
50
+ * Size-chart resolution mode (mirrors the merchant hub's editor).
51
+ *
52
+ * "custom" → use the product's own chart
53
+ * "default" → fall back to the store-wide chart (`store.settings.size_chart`)
54
+ * "off" → never show, even if a store default exists
55
+ */
56
+ type SizeChartMode = "default" | "custom" | "off";
57
+ /**
58
+ * A size / measurement chart, stored per-product at
59
+ * `product.attributes.size_chart` and store-wide at
60
+ * `store.settings.size_chart`. Resolve the two with {@link useProductSizeChart}
61
+ * instead of reading the raw blobs.
62
+ */
63
+ interface SizeChart {
64
+ /** Legacy boolean kept for charts written before `mode` existed. */
65
+ enabled?: boolean;
66
+ mode?: SizeChartMode;
67
+ /** Column labels, e.g. ["Chest", "Waist", "Hip"]. */
68
+ column_headers: string[];
69
+ /** One row per size; `values` aligns to `column_headers`. */
70
+ rows: Array<{
71
+ size: string;
72
+ values: string[];
73
+ }>;
74
+ unit?: "cm" | "in" | "kg";
75
+ notes?: string;
76
+ image_url?: string;
33
77
  }
34
78
  interface ProductImage {
35
79
  id: string;
@@ -138,6 +182,25 @@ interface Order {
138
182
  items: OrderItem[];
139
183
  created_at: string;
140
184
  shipping_address?: Address;
185
+ /**
186
+ * Discount code the customer applied at checkout, if any. Sourced from
187
+ * the backend order-detail (`GET /storefront/me/orders/{id}`). Themes
188
+ * render this on the order-confirmation / order-detail page.
189
+ */
190
+ coupon_code?: string | null;
191
+ /**
192
+ * Automatic offers (offers-v2 promotions) applied to the order. Each
193
+ * entry carries the localized title and the discount it contributed.
194
+ * `amount` is in integer cents — divide by 100 (or pass through
195
+ * `<Money>`) before display, consistent with the rest of the order's
196
+ * money fields.
197
+ */
198
+ applied_promotions?: {
199
+ id: string;
200
+ title: string;
201
+ title_ar?: string;
202
+ amount: number;
203
+ }[];
141
204
  }
142
205
  interface OrderItem {
143
206
  product_id: string;
@@ -162,4 +225,4 @@ interface Page {
162
225
  data?: Record<string, any>;
163
226
  }
164
227
 
165
- export type { Address as A, Cart as C, Order as O, Page as P, Store as S, CartItem as a, Collection as b, Customer as c, OrderItem as d, Product as e, ProductImage as f, ProductVariant as g, ProductOption as h };
228
+ export type { Address as A, Cart as C, Order as O, Page as P, SizeChart as S, CartItem as a, Collection as b, Customer as c, OrderItem as d, Product as e, ProductImage as f, ProductVariant as g, SizeChartMode as h, Store as i, ProductOption as j };