@propeller-commerce/propeller-v2-react-ui 0.16.0 → 0.19.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/CHANGELOG.md CHANGED
@@ -8,18 +8,152 @@ once it reaches 1.0. Until then (the `0.x` line) the public API may change
8
8
  between minor versions; breaking changes are called out below and in
9
9
  [MIGRATION.md](./MIGRATION.md).
10
10
 
11
+ ## [0.19.0] - 2026-08-26
12
+
13
+ ### Added
14
+
15
+ - **`useCart().addItems(items)`** — sequential bulk add that threads each add's
16
+ resolved cart id into the next. "Add this whole set to the basket" is a normal
17
+ requirement (kits, re-order, recipe packs) and there was no bulk call, so every
18
+ consumer wrote the loop themselves and hit the bug below.
19
+ - **`currency` on `ProductCardProps` / `ProductGridProps`**, resolved from
20
+ `<PropellerProvider>` like `ClusterCard` already did.
21
+ - **`className` on `CartSummaryProps`** and **`inputClassName` on
22
+ `SearchBarProps`**, merged over the defaults through `tailwind-merge`.
23
+ - **`configuration` on `OrderItemCardProps`** — supply the host's URL builders
24
+ and the item link is generated the way every other link on the storefront is.
25
+ - **Bonus items in the add-to-cart modal.** A promotion granting a free product
26
+ said nothing at the moment it fired; the shopper only found it by opening the
27
+ cart later, which is after it can influence them. Only the items *this* add
28
+ earned are shown. `bonusItemsLabels` overrides the block's labels.
29
+
30
+ ### Fixed
31
+
32
+ - **`addItem` could not add more than one product per tick.** It resolved the
33
+ cart id from React state, which does not update until the next render, so a
34
+ loop sent the same stale id every iteration: the 2nd..nth add failed with
35
+ "No cart ID provided", or — with `createCart` — quietly started a NEW cart per
36
+ product so only the last one survived. Neither failure throws, so both looked
37
+ like the adds had simply vanished. The id now lives in a ref written the
38
+ moment a cart resolves.
39
+ - **Cart lines lost their images unless every add path passed `configuration`.**
40
+ The media arguments are optional and nothing warned; the mutation succeeded
41
+ and the returned items carried no `imageVariants` at all, so the shopper saw
42
+ empty tiles for products with perfectly good PIM images — two screens away
43
+ from the call site. The hook now defaults them; an explicit `configuration`
44
+ still wins.
45
+ - **A non-euro shop could not change the currency in the catalogue.**
46
+ `ProductGrid` resolved a currency and never forwarded it, so every card fell
47
+ through to the package's `€`. `GridFilters`' price inputs and the active
48
+ price-filter chip hardcoded the glyph too — the chip had no prop and no class,
49
+ so no userland fix existed.
50
+ - **Prices used Dutch separators in every language.** Number format now follows
51
+ the storefront language via core-ui's `localeForLanguage`, so an English shop
52
+ renders `£3.45` rather than `£ 3,45`. Components that format money take a
53
+ `language` prop, resolved from the provider where they already read infra.
54
+ - **Search autosuggest and order-item links dropped the locale prefix.** Both
55
+ built `/product/:id/:slug` from literals instead of the host's URL builders,
56
+ so on a prefixed storefront they sent the visitor to the default language.
57
+ They now use `configuration.urls` when supplied, with the literals as a bare-
58
+ mount fallback.
59
+ - **`ProductBulkPrices` ignored an explicit empty `title`.** The heading is
60
+ resolved with `getLabel`, which treats `''` as missing and substitutes its
61
+ English default — so the component's own "no title, no heading" branch was
62
+ dead code and the block rendered "Volume pricing" on a Dutch page.
63
+ - **The mini-cart labelled an excl-VAT figure "Total".** The cart page calls the
64
+ same number "Total excl. VAT" and reserves "Total" for the incl-VAT figure, so
65
+ the first number a shopper saw understated the price by one VAT amount. The
66
+ label now follows the same switch the figure does.
67
+ - **`CartSummary` painted a card background with no card.** No padding, radius
68
+ or shadow, directly above a properly styled `ActionCode` in the same column.
69
+ - **`SearchBar`'s input was styled for a dark header** (`bg-white/95
70
+ border-white/20`), rendering as a white-on-white ghost on a light one. It now
71
+ uses the themed surface and border tokens.
72
+ - **`ProductBundles`' add button read "In cart"**, a status rather than the
73
+ action it performs, where every other cart control in the package says
74
+ "Add to cart".
75
+ - **`OrderList` showed a bare line of text while loading**, collapsing the list
76
+ and snapping it back — most visible when switching language on the account
77
+ pages. It renders skeleton rows that hold the layout instead.
78
+
79
+ ### Changed
80
+
81
+ - `AddToCartProps.onAddToCart` may return a promise, and `useCart` awaits the
82
+ override. Matches the widened slot contract in core-ui 0.7.0.
83
+ - Requires `@propeller-commerce/propeller-v2-core-ui` `^0.7.0`.
84
+
85
+ ## [0.18.0] - 2026-08-20
86
+
87
+ ### Added
88
+
89
+ - **After-hooks now say WHAT happened, not just that something did.** Three
90
+ callbacks reported success with no argument, which made them unusable for
91
+ anything that has to distinguish the outcome — analytics, audit trails, or
92
+ optimistic UI. All three arguments are optional, so existing zero-argument
93
+ callbacks keep working unchanged.
94
+ - `AddToFavorite.onFavoriteChanged(change?)` — `{ action: 'added' | 'removed',
95
+ listId, productId, clusterId }`. It fired identically for an add and a
96
+ removal, so a host could not report "added to wishlist" without inventing
97
+ the direction, and a wishlist metric that also counts removals is worse
98
+ than no metric.
99
+ - `FavoriteLists.onListChanged(change?)` / `useFavorites` — `{ action:
100
+ 'created' | 'updated' | 'deleted', listId, name, isDefault }`. Note the
101
+ existing `onCreate` / `onEdit` / `onDelete` props are *overrides* that
102
+ replace the default mutation, so they were never usable as notifications.
103
+ - `QuickOrder.onTemplateDownload()` — the template link was a bare `<a>`, so
104
+ a buyer fetching the spreadsheet left no trace. Navigation is untouched.
105
+
106
+ ### Notes
107
+
108
+ - `QuoteActions` deliberately gains no `afterReject`: the component has no
109
+ reject action to hook — only accept. A rejection callback would be dead API.
110
+ - `PurchaseAuthorizationRequests` needs no new prop either; the existing
111
+ `afterDeleteRequest` already covers the manager rejecting a request, which is
112
+ what "delete" means in that UI.
113
+
114
+ ## [0.17.0] - 2026-08-20
115
+
116
+ ### Fixed
117
+
118
+ - **A host `className` override could not beat the component's own utility
119
+ .** Class lists were built by string-appending the override
120
+ after the package's defaults, which decides nothing — the cascade does, not
121
+ attribute order. `iconClassName="text-cocoa"` on `AccountIconAndMenu` (baked-in
122
+ `text-white`) produced an element carrying both and rendered white, while the
123
+ same override on `CartIconAndSidebar` appeared to work only because its
124
+ default happened to lose anyway. Every override site now merges through
125
+ `tailwind-merge`, so a conflicting utility is replaced rather than raced.
126
+ 59 sites across 47 components; BEM hooks and non-conflicting utilities are
127
+ untouched.
128
+
129
+ - **Anonymous catalog listings were scoped differently on the client than on
130
+ the server.** Listing hooks derived `userId` only from a
131
+ logged-in user and omitted the key entirely for guests, while a server-side
132
+ seed scopes anonymous queries to the channel's `anonymousUserId`. The two
133
+ therefore asked different questions, and the client refetch quietly replaced
134
+ a correctly-scoped product list with a differently-scoped one — assortment
135
+ rules, negative order lists in particular, are applied per user. All five
136
+ listing hooks (`useProductSearch` ×2, `useProductSlider`, `useQuickOrder`,
137
+ `useSpareParts`) now resolve the id through one shared helper. Hosts that
138
+ supply no `anonymousUserId` behave exactly as before.
139
+
140
+ ### Added
141
+
142
+ - **`configuration.anonymousUserId`** — the channel's guest account, resolved
143
+ server-side and handed to the package so client-side listings can scope to
144
+ it. Same route `baseCategoryId` takes; no module guesses it.
145
+
11
146
  ## [0.16.0] - 2026-08-12
12
147
 
13
148
  ### Fixed
14
149
 
15
- - **Localized names and slugs ignored the storefront language (PWP-936 /
16
- PWP-940).** A localized array carries one entry per authored language in
150
+ - **Localized names and slugs ignored the storefront language.** A localized array carries one entry per authored language in
17
151
  catalog order, so `names[0]` / `slugs[0]` is the catalog's DEFAULT language,
18
152
  not the storefront's. Thirteen sites read index 0 unconditionally while
19
153
  eleven others resolved by language correctly. Now all of them go through
20
154
  `getLocalizedValue`, which prefers the active language and falls back to any
21
155
  translation that has a value:
22
- - `ClusterOptions` — option group and option product names (PWP-936)
156
+ - `ClusterOptions` — option group and option product names
23
157
  - `FavoriteListItem`, `FavoriteListDetails` — product and cluster names
24
158
  - `ItemsOverview` — item, bundle-leader, bundle-item and child-option names
25
159
  - `ProductBundles` — bundle item names
@@ -29,8 +163,7 @@ between minor versions; breaking changes are called out below and in
29
163
 
30
164
  The three slug sites are the ones that actually broke: they emitted
31
165
  default-language URLs on every non-default locale.
32
- - **`useCart` never adopted a cart id that arrived after mount (PWP-938 /
33
- PWP-937).** The id was seeded once with `useState(options.cartId || '')` and
166
+ - **`useCart` never adopted a cart id that arrived after mount.** The id was seeded once with `useState(options.cartId || '')` and
34
167
  only reassigned from the hook's own cart-creation path, so a component that
35
168
  mounted before the cart resolved held `''` permanently. `CartIconAndSidebar`
36
169
  renders in the header on the first paint of every page, so its "Request
@@ -50,7 +183,7 @@ between minor versions; breaking changes are called out below and in
50
183
  `useInfraProps`, like the other catalog components. Passing it explicitly
51
184
  still wins.
52
185
  - The favorites list's add-product button default now reads "Add product to
53
- favorite list" instead of "Add product directly to this wishlist" (PWP-939).
186
+ favorite list" instead of "Add product directly to this wishlist".
54
187
  Hosts that translate `FavoriteListDetails.addProductDirectly` should update
55
188
  their own copy — "favorite list" is the agreed term, not "wishlist".
56
189
 
@@ -58,7 +191,7 @@ between minor versions; breaking changes are called out below and in
58
191
 
59
192
  ### Fixed
60
193
 
61
- - **Checkout step 3 opened with nothing selected on a fresh cart (PWP-934).**
194
+ - **Checkout step 3 opened with nothing selected on a fresh cart.**
62
195
  `CartPaymethods` and `CartCarriers` only ever adopted a value the cart already
63
196
  stored, so a cart that had never reached step 3 — every first order, and every
64
197
  order after the cart is recreated — rendered both grids blank and refused to
@@ -97,7 +230,7 @@ between minor versions; breaking changes are called out below and in
97
230
  ### Fixed
98
231
 
99
232
  - **The product specifications table emptied out in a language the catalogue is
100
- only partly translated into** (PWP-928). A localized list carries only the
233
+ only partly translated into**. A localized list carries only the
101
234
  languages someone authored, so asking for one nobody wrote returns nothing —
102
235
  it does not fall back. `ProductSpecifications` matched strictly on
103
236
  `language === props.language`, which failed twice over: the label fell through
@@ -203,7 +336,7 @@ between minor versions; breaking changes are called out below and in
203
336
  fall back to, and the row rendered with a blank label and an empty slug:
204
337
  invisible and unclickable. Switching a bilingual storefront to a language with
205
338
  partial translations collapsed the menu to whichever categories happened to be
206
- translated (PWP-927).
339
+ translated.
207
340
 
208
341
  `names` and `slugs` are now fetched unfiltered — every translation — and
209
342
  `mapCategory` picks the active language, falling back to whichever translation
package/README.md CHANGED
@@ -307,9 +307,17 @@ and pass it once.
307
307
  | `language` | `string` | Locale code used to resolve localized content (e.g. `'NL'`, `'EN'`). |
308
308
  | `includeTax` | `boolean` | Whether displayed prices include tax. |
309
309
  | `currency` | `string` | Currency symbol for price formatting. Default: `'€'`. |
310
- | `configuration` | `unknown` | Free-form config bag forwarded to components — stuff your own settings in. |
310
+ | `configuration` | `unknown` | Config bag forwarded to components. Recognized keys: `baseCategoryId`, `anonymousUserId` (see below), image filters — plus anything of your own. |
311
311
  | `portalMode` | `string` | Storefront mode (e.g. `'open'`, `'closed'`). |
312
312
 
313
+ **`configuration.anonymousUserId`** — the channel's guest account. Listing
314
+ hooks scope logged-out catalog queries to it, so the client asks the same
315
+ question your server-rendered seed did. Resolve it server-side (only the
316
+ server can reach the channel) and pass it in; when it's absent, anonymous
317
+ queries send no `userId` and the backend falls back to the api key's own
318
+ scope. Getting this wrong is invisible — the grid renders, just with the
319
+ wrong assortment.
320
+
313
321
  The `value` object is reactive — when your auth/company/language state
314
322
  changes, recompute it (memoize on those dependencies) and the provider
315
323
  propagates the new value. Components re-render with fresh infra; service
@@ -584,15 +592,19 @@ cascade order — no `!important` needed:
584
592
  .propeller-breadcrumbs__separator { display: none; }
585
593
  ```
586
594
 
587
- **3. Per-instance `className`** — every component appends `props.className`
588
- on its root, so a one-off override is a regular prop:
595
+ **3. Per-instance `className`** — every component merges `props.className`
596
+ into its root classes, so a one-off override is a regular prop:
589
597
 
590
598
  ```tsx
591
599
  <ProductCard product={p} className="ring-2 ring-yellow-400" />
592
600
  ```
593
601
 
594
- `className` **adds to** the base classes — it doesn't replace them. To strip
595
- a default, use a BEM hook.
602
+ The merge runs through [`tailwind-merge`](https://github.com/dcastil/tailwind-merge)
603
+ (since 0.17.0), so an override **replaces** the base utility it conflicts with
604
+ rather than racing it in the cascade — `iconClassName="text-cocoa"` on a
605
+ component whose default is `text-white` now renders cocoa. Non-conflicting
606
+ utilities and the BEM hooks are left untouched. To remove a default outright
607
+ (rather than override it), use a BEM hook.
596
608
 
597
609
  See [STYLING.md](./STYLING.md) for the full token list, the complete BEM
598
610
  hook catalog, and the cascade rationale.