@propeller-commerce/propeller-v2-react-ui 0.4.6
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 +359 -0
- package/LICENSE +21 -0
- package/MIGRATION.md +217 -0
- package/README.md +668 -0
- package/STYLING.md +145 -0
- package/dist/index.cjs +17345 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +6478 -0
- package/dist/index.d.ts +6478 -0
- package/dist/index.js +17218 -0
- package/dist/index.js.map +1 -0
- package/dist/pure.cjs +974 -0
- package/dist/pure.cjs.map +1 -0
- package/dist/pure.d.cts +556 -0
- package/dist/pure.d.ts +556 -0
- package/dist/pure.js +938 -0
- package/dist/pure.js.map +1 -0
- package/dist/shared.cjs +14 -0
- package/dist/shared.cjs.map +1 -0
- package/dist/shared.d.cts +24 -0
- package/dist/shared.d.ts +24 -0
- package/dist/shared.js +3 -0
- package/dist/shared.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +93 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `propeller-v2-react-ui` are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and the project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
|
7
|
+
once it reaches 1.0. Until then (the `0.x` line) the public API may change
|
|
8
|
+
between minor versions; breaking changes are called out below and in
|
|
9
|
+
[MIGRATION.md](./MIGRATION.md).
|
|
10
|
+
|
|
11
|
+
## [0.4.6] - 2026-06-09
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **`CartItem` cart actions silently no-op (quantity +/-, delete, notes).**
|
|
16
|
+
The compound-API refactor in 0.3.0 (commit 564f8d0) switched `CartItem`
|
|
17
|
+
from `useInfraProps(rawProps)` to
|
|
18
|
+
`useResolvedProps(rawProps, CART_ITEM_RESOLVE_SPEC)`, but the new spec
|
|
19
|
+
only listed the slot-injection keys (`priceComponent`, `stockComponent`,
|
|
20
|
+
`surchargesComponent`) — the Tier 1 infra keys (`graphqlClient`, `user`,
|
|
21
|
+
`language`, `currency`, `configuration`, `companyId`, `includeTax`)
|
|
22
|
+
weren't declared, so they never got filled in from `<PropellerProvider>`.
|
|
23
|
+
The internal `useCart()` call received `graphqlClient: undefined`, every
|
|
24
|
+
service mutation threw inside the SDK, and the consumer saw no spinner,
|
|
25
|
+
no toast, no change — just buttons that did nothing.
|
|
26
|
+
Added the missing infra entries to `CART_ITEM_RESOLVE_SPEC` so resolution
|
|
27
|
+
matches the precedence the rest of the surface uses
|
|
28
|
+
(`ClusterCard.RESOLVE_SPEC`, `ProductCard.RESOLVE_SPEC`).
|
|
29
|
+
|
|
30
|
+
## [0.4.5] - 2026-06-04
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **List-view / cart row collapse against a consumer's Tailwind cascade.**
|
|
35
|
+
`ProductCard` (row layout), `ClusterCard` (row layout), and `CartItem`
|
|
36
|
+
use responsive override pairs in the markup — `w-full md:w-auto`,
|
|
37
|
+
`border-t md:border-t-0`, `py-2 md:py-0`, `flex-wrap md:flex-nowrap`.
|
|
38
|
+
When a consumer also runs Tailwind v4, its generated stylesheet re-emits
|
|
39
|
+
the base utilities (`.w-full`, `.border-t`, …) but not the `md:` variants
|
|
40
|
+
that only appear inside this package's components. On equal specificity
|
|
41
|
+
the consumer sheet wins, the card footer goes full-width, and the body's
|
|
42
|
+
`flex-1 min-w-0` collapses to zero — title and SKU render at 0px width
|
|
43
|
+
while the placeholder image still occupies space (the symptom looks like
|
|
44
|
+
"missing product name" but the element is actually there).
|
|
45
|
+
Restated the desktop (≥768px) intent on the BEM hook classes scoped under
|
|
46
|
+
the card root in `styles.css` so the selector is specificity (0,2,0) —
|
|
47
|
+
it beats any single-class utility (0,1,0) regardless of sheet order.
|
|
48
|
+
This mirrors the `propeller-v2-vue-ui` fix from May (commit ce7a899).
|
|
49
|
+
|
|
50
|
+
## [0.4.4] - 2026-06-04
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **SDK dependency switched from GitHub tarball to npm.** Both the
|
|
55
|
+
`peerDependencies` entry and the `devDependencies` test pin now point
|
|
56
|
+
at `@propeller-commerce/propeller-sdk-v2@^0.11.1` instead of
|
|
57
|
+
`github:propeller-commerce/propeller-sdk-v2#master`. All 113 source +
|
|
58
|
+
test files renamed accordingly (`from 'propeller-sdk-v2'` →
|
|
59
|
+
`from '@propeller-commerce/propeller-sdk-v2'`).
|
|
60
|
+
|
|
61
|
+
### Dependencies
|
|
62
|
+
|
|
63
|
+
- Bumps `propeller-v2-core-ui` to 0.2.4 (its own SDK switch to npm).
|
|
64
|
+
|
|
65
|
+
### Why
|
|
66
|
+
|
|
67
|
+
The SDK is now published on npm as a properly scoped package. Pinning
|
|
68
|
+
via npm removes the GitLab→GitHub mirror dependency from the install
|
|
69
|
+
chain and gives consumers semver ranges instead of a moving master tip.
|
|
70
|
+
Behaviour is unchanged.
|
|
71
|
+
|
|
72
|
+
## [0.4.3] - 2026-06-04
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
|
|
76
|
+
- **`CartItem` now resolves product / bundle / crossupsell names via
|
|
77
|
+
`getLanguageString`** (from `propeller-v2-core-ui@0.2.3`), matching
|
|
78
|
+
the active language and walking the localised entries for the first
|
|
79
|
+
non-empty value before falling back to `'Product'`. Previously each
|
|
80
|
+
helper hard-coded `names?.[0]?.value || 'Product'`, which always
|
|
81
|
+
picked the first SDK entry regardless of language and rendered blank
|
|
82
|
+
when that first entry's `value` was empty. Cart pages now show the
|
|
83
|
+
correct localised name and never collapse to invisible rows on
|
|
84
|
+
datasets with sparse localisation.
|
|
85
|
+
|
|
86
|
+
### Dependencies
|
|
87
|
+
|
|
88
|
+
- Bumps `propeller-v2-core-ui` to 0.2.3 (the resolver fix that powers
|
|
89
|
+
the above).
|
|
90
|
+
|
|
91
|
+
## [0.4.0] - 2026-06-02
|
|
92
|
+
|
|
93
|
+
Finishes the prop-cascade cleanup started in 0.3.0. Every component on
|
|
94
|
+
the main entry now resolves Tier 1 + Tier 2 infra from
|
|
95
|
+
`<PropellerProvider>` when not passed explicitly — consumer islands can
|
|
96
|
+
drop redundant `graphqlClient` / `user` / `companyId` / `language` /
|
|
97
|
+
`includeTax` / `currency` / `configuration` / `portalMode` passes on
|
|
98
|
+
every component (not just the 31 retrofitted in 0.3.0).
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
|
|
102
|
+
- **6 client components retrofitted** to call `useInfraProps(rawProps)`:
|
|
103
|
+
`CategoryDescription`, `ProductDescription`, `GridFilters`,
|
|
104
|
+
`GridToolbar`, `AddressSelector`, `CartPaymethods`. Same mechanical
|
|
105
|
+
pattern as the 31 already retrofitted in 0.3.0.
|
|
106
|
+
|
|
107
|
+
### Added
|
|
108
|
+
|
|
109
|
+
- **Provider-aware client wrappers for the 6 `@rsc-safe` components**:
|
|
110
|
+
`Breadcrumbs`, `GridTitle`, `ProductPrice`, `ProductBulkPrices`,
|
|
111
|
+
`ProductShortDescription`, `CategoryShortDescription`. The pure RSC-safe
|
|
112
|
+
components still exist and are still exported from `/pure` for Server
|
|
113
|
+
Component use. The **main `/` entry now exports the wrapper** under the
|
|
114
|
+
original name (`Breadcrumbs`, etc.), so client islands automatically
|
|
115
|
+
pick up provider-aware versions with no consumer-side import change.
|
|
116
|
+
Server pages that need the pure component should import from
|
|
117
|
+
`propeller-v2-react-ui/pure` (this was already the canonical pattern).
|
|
118
|
+
|
|
119
|
+
### Migration
|
|
120
|
+
|
|
121
|
+
Additive. Existing call sites that pass explicit infra props keep
|
|
122
|
+
working unchanged. Client islands can now drop those passes. Server
|
|
123
|
+
pages importing from `/pure` are unaffected.
|
|
124
|
+
|
|
125
|
+
### Verification
|
|
126
|
+
|
|
127
|
+
- `tsc --noEmit` clean.
|
|
128
|
+
- 21 / 21 vitest pass.
|
|
129
|
+
- `tsup` build emits both client (with `"use client"` banner) and `/pure`
|
|
130
|
+
(without banner) shapes correctly; dts unchanged for the pure entry.
|
|
131
|
+
|
|
132
|
+
## [0.3.0] - 2026-06-02
|
|
133
|
+
|
|
134
|
+
Loosens 5 components' required infra props to optional, plus retrofits
|
|
135
|
+
`UserDetails` to consume `useInfraProps()` like the other 30 retrofitted
|
|
136
|
+
components. Consumers can now drop redundant `:user=` / `:language=`
|
|
137
|
+
passes on these components when `<PropellerProvider>` wraps the subtree.
|
|
138
|
+
|
|
139
|
+
### Changed
|
|
140
|
+
|
|
141
|
+
- **`UserDetails`** — `user: Contact | Customer` → `user?: Contact |
|
|
142
|
+
Customer | null`. Component now calls `useInfraProps(rawProps)` to
|
|
143
|
+
resolve `user` from the provider when omitted. Existing call sites
|
|
144
|
+
that pass `user` keep working unchanged.
|
|
145
|
+
- **`AddressSelector`** — `user: Contact | Customer | null` →
|
|
146
|
+
`user?: Contact | Customer | null`.
|
|
147
|
+
- **`CartPaymethods`** — `user: Contact | Customer | null` →
|
|
148
|
+
`user?: Contact | Customer | null`.
|
|
149
|
+
- **`GridTitle`** — `language: string` → `language?: string`.
|
|
150
|
+
- **`CategoryDescription`** — `language: string` → `language?: string`.
|
|
151
|
+
|
|
152
|
+
### Migration
|
|
153
|
+
|
|
154
|
+
Same shape as `propeller-v2-vue-ui@0.3.0`. Additive. Existing call sites
|
|
155
|
+
keep working. A follow-up cleanup in `propeller-next` will prune ~85
|
|
156
|
+
redundant prop-lines from the consumer islands.
|
|
157
|
+
|
|
158
|
+
### Verification
|
|
159
|
+
|
|
160
|
+
- `tsc --noEmit` clean.
|
|
161
|
+
- 21 / 21 vitest tests pass unchanged.
|
|
162
|
+
- `tsup` build emits identical dts shape.
|
|
163
|
+
|
|
164
|
+
## 0.2.3
|
|
165
|
+
|
|
166
|
+
### Fixed
|
|
167
|
+
|
|
168
|
+
- `AccountIconAndMenu` no longer forwards its own `labels` (which contains menu-UI slugs like `accountLabel`, `logoutLabel`) to the embedded `<LoginForm>` — that previously caused LoginForm's `email`, `password`, `forgotPassword` strings to fail translation. Use the new `loginFormLabels?` prop instead.
|
|
169
|
+
|
|
170
|
+
### Added
|
|
171
|
+
|
|
172
|
+
- `AccountIconAndMenu`: new `loginFormLabels?: Record<string, string>` prop, forwarded to the embedded `<LoginForm>`. Lets consumers pass the LoginForm namespace's translations through to the dropdown sign-in form.
|
|
173
|
+
|
|
174
|
+
## 0.2.2
|
|
175
|
+
|
|
176
|
+
### Added
|
|
177
|
+
|
|
178
|
+
- `ProductCard` / `ProductGrid` / `ProductSlider`: new `priceLabels?: Record<string, string>` prop, forwarded through to the embedded `<ProductPrice>` display inside each card. Lets consumers translate `inclTax` / `exclTax` / `loginToSeePrices` strings on grid/slider pages without per-card wiring. `ClusterCard` is intentionally unchanged — it builds its own price span and doesn't render `<ProductPrice>`.
|
|
179
|
+
|
|
180
|
+
## 0.2.1
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
|
|
184
|
+
- `ProductGrid` / `ProductSlider`: new props `productCardLabels?`, `clusterCardLabels?` for forwarding translations to embedded `<ProductCard>` / `<ClusterCard>`. Mirrors the existing `stockLabels?` / `addToCartLabels?` pattern.
|
|
185
|
+
- `PriceToggle`: new `labels?: Record<string, string>` prop with slugs `pricesLabel`, `inclVat`, `exclVat`. Previously these strings were hardcoded.
|
|
186
|
+
- `OrderList`: filter column field labels and sort-field dropdown options now route through the `labels` prop. Column labels use slug `col<Capitalized>` (e.g. `colTerm`, `colCreatedAt`); sort-field options use the enum value as the slug (e.g. `createdAt`, `price`); sort-order options (ASC/DESC) and order type options also route through `labels`. All fallbacks preserve existing English behavior, so the change is non-breaking for consumers that don't supply the new keys.
|
|
187
|
+
|
|
188
|
+
### Fixed
|
|
189
|
+
|
|
190
|
+
- `ProductSlider` no longer forwards its own `labels` (which contains slider-UI slugs such as `scrollLeft`, `noProducts`) to embedded `ClusterCard` — that previously caused card-level strings to fail translation. Use the new `clusterCardLabels?` prop instead.
|
|
191
|
+
|
|
192
|
+
## [0.2.0] - 2026-06-01
|
|
193
|
+
|
|
194
|
+
Adds shop-mode-aware user gating. Existing public API is unchanged; new
|
|
195
|
+
fields are optional and default to backward-compatible behaviour.
|
|
196
|
+
|
|
197
|
+
### Added
|
|
198
|
+
|
|
199
|
+
- **`shopMode?: ShopMode`** on `PropellerScope`. Declares whether the shop
|
|
200
|
+
is `'b2b'`, `'b2c'`, or `'hybrid'`. Defaults to `'hybrid'` when omitted
|
|
201
|
+
so existing call sites keep their current branching semantics (any
|
|
202
|
+
logged-in Contact treated as B2B).
|
|
203
|
+
- **Derived `userMode: UserMode`** on `PropellerInfra`. Computed via
|
|
204
|
+
`deriveUserMode(user, shopMode)` from `propeller-v2-core-ui`. Values:
|
|
205
|
+
`'anonymous' | 'b2b' | 'b2c'`. B2B-gated UI (company switcher, B2B
|
|
206
|
+
side-nav items, quote/authorization affordances) should read this
|
|
207
|
+
instead of re-deriving from `isContact(user)` ad hoc.
|
|
208
|
+
- **`useUserMode()` hook**. Direct read of `userMode` for components that
|
|
209
|
+
only need that one signal.
|
|
210
|
+
|
|
211
|
+
### Why this matters
|
|
212
|
+
|
|
213
|
+
Hybrid shops need a single, consistent gate that says "is the current
|
|
214
|
+
viewer behaving as B2B or B2C?" Previously every B2B-gated component
|
|
215
|
+
re-derived this from `isContact(user)` ad hoc, which was correct but
|
|
216
|
+
duplicated and ignored the shop's `mode` (a B2C shop that somehow had a
|
|
217
|
+
Contact session would still light up the B2B surface). Centralising the
|
|
218
|
+
derivation eliminates the duplication and makes the shop mode authoritative.
|
|
219
|
+
|
|
220
|
+
### Requires
|
|
221
|
+
|
|
222
|
+
- `propeller-v2-core-ui` ≥ 0.2.0 (for `deriveUserMode`, `ShopMode`,
|
|
223
|
+
`UserMode`).
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## [0.1.0] — Unreleased
|
|
228
|
+
|
|
229
|
+
First version of the package. Extracted from the `propeller-next`
|
|
230
|
+
boilerplate so the Propeller Commerce React surface can be consumed as a
|
|
231
|
+
standalone library. Not yet published to a registry — consumed via a
|
|
232
|
+
`file:` link during stabilization.
|
|
233
|
+
|
|
234
|
+
### Added
|
|
235
|
+
|
|
236
|
+
- **Initial extraction (Phase E).** 60 components, the React composables
|
|
237
|
+
(hooks), the runtime-agnostic `composables/shared/` layer (utilities and
|
|
238
|
+
domain types), and the two contexts (`PropellerContext`,
|
|
239
|
+
`ProductGridContext`) moved into this package from `propeller-next`.
|
|
240
|
+
- **Build pipeline.** `tsup` produces dual ESM + CJS bundles with `.d.ts`
|
|
241
|
+
declarations. The client bundle (`index`) gets a `"use client";` banner
|
|
242
|
+
prepended in a post-build hook; the `shared` and `pure` bundles are
|
|
243
|
+
runtime-agnostic with no banner.
|
|
244
|
+
- **Three code entry points.** `propeller-v2-react-ui` (components, hooks,
|
|
245
|
+
contexts, `createServices`, `toPlain`), `propeller-v2-react-ui/pure` (the
|
|
246
|
+
12 pure/presentational components, RSC-safe — see below), and
|
|
247
|
+
`propeller-v2-react-ui/shared` (pure TS — `createServices`, `toPlain`,
|
|
248
|
+
formatters, helpers, all domain types — safe to import from Server
|
|
249
|
+
Components).
|
|
250
|
+
- **`/pure` RSC-safe component entry.** A third `tsup` entry exporting the
|
|
251
|
+
12 pure/presentational components (`Breadcrumbs`, `ProductPrice`,
|
|
252
|
+
`ItemStock`, `OrderTotals`, `ProductBulkPrices`, `ProductShortDescription`,
|
|
253
|
+
`ProductDownloads`, `ProductVideos`, `OrderItemCard`, `OrderSummary`,
|
|
254
|
+
`GridTitle`, `CategoryShortDescription`). The `pure` bundle is built
|
|
255
|
+
**without** the `"use client"` banner, so a React Server Component can
|
|
256
|
+
import and render these components directly — server-rendering real
|
|
257
|
+
product/price/order markup — without drawing a client boundary or
|
|
258
|
+
shipping the client bundle. Each is verified to use no hooks, state,
|
|
259
|
+
effects, handlers, browser APIs or context reads. The same components
|
|
260
|
+
remain available from the main entry for use inside client boundaries.
|
|
261
|
+
- **Precompiled stylesheet** (`dist/styles.css`). The package's Tailwind v4
|
|
262
|
+
classes are compiled to vanilla CSS at build time and shipped. Consumers
|
|
263
|
+
import it once and do **not** need Tailwind in their own project.
|
|
264
|
+
- **Three styling override surfaces** — theme tokens (CSS variables), BEM
|
|
265
|
+
hooks (`.propeller-product-card__price`, …), and per-instance `className`.
|
|
266
|
+
Documented in [STYLING.md](./STYLING.md).
|
|
267
|
+
- **`PropellerProvider`** — the single integration point. Takes one value
|
|
268
|
+
object (`PropellerInfra`) carrying `graphqlClient`, `services`, `user`,
|
|
269
|
+
`companyId`, `language`, `includeTax`, `currency`, `portalMode`,
|
|
270
|
+
`configuration`. Imports zero host contexts.
|
|
271
|
+
- **`createServices(client)`** — factory that builds the typed `Services`
|
|
272
|
+
bundle (`product`, `cart`, `user`, `order`, …) keyed to a consumer-built
|
|
273
|
+
`GraphQLClient`. Memoized per client via `WeakMap`.
|
|
274
|
+
- **`toPlain(value)`** — recursively strips the SDK's underscore-prefixed
|
|
275
|
+
backing fields from class instances.
|
|
276
|
+
- **`useServices()`** — reads the `Services` bundle from `PropellerProvider`;
|
|
277
|
+
throws a clear error when used outside a provider.
|
|
278
|
+
- Public-grade documentation: [README.md](./README.md), [STYLING.md](./STYLING.md),
|
|
279
|
+
[TECH.md](./TECH.md), [CONTRIBUTING.md](./CONTRIBUTING.md),
|
|
280
|
+
[MIGRATION.md](./MIGRATION.md), [SECURITY.md](./SECURITY.md), and an MIT
|
|
281
|
+
`LICENSE`.
|
|
282
|
+
- **Unit tests.** Vitest suite covering the pure-logic surface — `src/lib/`
|
|
283
|
+
(`createServices`, `toPlain`) and the 11 framework-free shared utilities
|
|
284
|
+
(formatters, truncation, inventory/label/visibility helpers, attribute
|
|
285
|
+
extraction, country lookup, language resolution, product helpers, user
|
|
286
|
+
identity, video URL transforms). 183 tests, ~99% statement / 100%
|
|
287
|
+
function coverage of that surface. Scripts: `test`, `test:watch`,
|
|
288
|
+
`test:coverage`.
|
|
289
|
+
- **CI pipeline** (`.gitlab-ci.yml`). A `verify` stage (typecheck, unit
|
|
290
|
+
tests + coverage, build) and a `downstream` stage that builds the
|
|
291
|
+
package, installs it into a fresh checkout of propeller-next, and runs
|
|
292
|
+
that repo's full Playwright e2e suite — the package's component
|
|
293
|
+
regression gate. Components are intentionally not unit-tested against a
|
|
294
|
+
mock SDK; the consumer's real e2e suite is the verification layer.
|
|
295
|
+
Required GitLab CI/CD variables are documented in
|
|
296
|
+
[CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
297
|
+
- **Storybook** (`.storybook/`). A story per component (60 total),
|
|
298
|
+
rendering each in isolation against fixture data and a mock
|
|
299
|
+
`PropellerProvider`. The "Docs" tab auto-generates each component's
|
|
300
|
+
full `*Props` table from the TypeScript source via
|
|
301
|
+
`react-docgen-typescript`. Mock foundation in `src/__mocks__/`
|
|
302
|
+
(`fixtures.ts`, `mockServices.ts`, `decorators.tsx`). Scripts:
|
|
303
|
+
`storybook`, `build-storybook`.
|
|
304
|
+
- **Documentation site** (`docs/`). A self-contained Docusaurus 3 app —
|
|
305
|
+
its own `package.json` / lockfile, not published to npm, not part of
|
|
306
|
+
the package build. Eight guide pages (introduction, getting started,
|
|
307
|
+
the SDK seam, styling, server components, component reference,
|
|
308
|
+
Storybook, contributing) sourced from the package's own markdown.
|
|
309
|
+
The component prop reference is intentionally not duplicated here —
|
|
310
|
+
the site links out to Storybook's auto-generated prop tables.
|
|
311
|
+
|
|
312
|
+
- **`Menu`: optional pre-fetched `tree` prop.** When the host supplies
|
|
313
|
+
`tree: MenuCategory[]`, `Menu` skips its internal `useMenu` fetch
|
|
314
|
+
entirely and renders the tree directly — mirroring the
|
|
315
|
+
`ProductGrid.products` opt-in. Lets host apps fetch the category tree
|
|
316
|
+
server-side (e.g. in a Next.js layout) and have the menu HTML land in
|
|
317
|
+
the initial response. Omitting the prop preserves the legacy
|
|
318
|
+
client-side fetch behaviour — no breaking change.
|
|
319
|
+
|
|
320
|
+
### Changed
|
|
321
|
+
|
|
322
|
+
- **Decoupled the SDK seam from Next.js (breaking, pre-publish).** The
|
|
323
|
+
package no longer ships a module-level `graphqlClient` singleton, a
|
|
324
|
+
hardcoded `/api/graphql` endpoint, or `NEXT_PUBLIC_*` environment reads.
|
|
325
|
+
The consumer now constructs its own `GraphQLClient`, calls
|
|
326
|
+
`createServices(client)`, and passes both into `PropellerProvider`.
|
|
327
|
+
`getServices` (singleton-defaulting accessor) was replaced by
|
|
328
|
+
`createServices` (pure factory). See [MIGRATION.md](./MIGRATION.md).
|
|
329
|
+
- **Removed the `/server` entry.** The previous
|
|
330
|
+
`propeller-v2-react-ui/server` subpath (`createServerClient`,
|
|
331
|
+
`getServerInfra`, `fetchProduct`, `fetchCategory`) was deleted —
|
|
332
|
+
server-side GraphQL wiring (endpoint, API keys, cookie names, auth) is
|
|
333
|
+
application-specific. Consumers host their own server module; the package
|
|
334
|
+
exports `createServices` from `/shared` for server-side use.
|
|
335
|
+
- **Dropped the `next` peer dependency.** The package has zero `next/*`
|
|
336
|
+
imports. Next.js apps still work out of the box.
|
|
337
|
+
- `PropellerInfra` gained a required `services: Services` field.
|
|
338
|
+
|
|
339
|
+
### Fixed
|
|
340
|
+
|
|
341
|
+
- **`OrderActions` button layout.** Buttons no longer wrap mid-word when
|
|
342
|
+
`OrderActions` shares a flex row with `OrderTotals` — added
|
|
343
|
+
`flex-shrink-0` to the wrapper and `whitespace-nowrap` to the buttons.
|
|
344
|
+
- **`CartItem` alignment.** The product image and footer are top-aligned
|
|
345
|
+
with the title row instead of vertically centering against tall content
|
|
346
|
+
(cross-sells) — root changed from `items-center` to `items-start`.
|
|
347
|
+
- **`ProductCard` row layout.** Responsive utilities (`md:flex-nowrap`,
|
|
348
|
+
etc.) buried inside template-literal ternaries were missing from the
|
|
349
|
+
compiled stylesheet; force-included via `@source inline(...)` so list
|
|
350
|
+
view stays single-row at desktop widths.
|
|
351
|
+
- **`GridFilters` — active filters now visible on first render.** A filter
|
|
352
|
+
group with a selected value starts expanded even when `collapsed` is
|
|
353
|
+
`true`, and its checkboxes render checked, on the very first render
|
|
354
|
+
(including SSR — previously this depended on a client-only effect that
|
|
355
|
+
never ran server-side). Restoring a filtered URL no longer hides the
|
|
356
|
+
ticked checkboxes. A group the user explicitly toggles still wins, and a
|
|
357
|
+
user collapsing a group with an active filter is not fought.
|
|
358
|
+
|
|
359
|
+
[0.1.0]: https://github.com/propeller-commerce/propeller-v2-react-ui/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Propeller Commerce
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Migration Guide
|
|
2
|
+
|
|
3
|
+
Breaking changes between versions of `propeller-v2-react-ui`, with the steps
|
|
4
|
+
to upgrade. The package is pre-1.0, so breaking changes can land in `0.x`
|
|
5
|
+
minor versions — each one is documented here.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## `Menu`: optional pre-fetched `tree` prop (additive)
|
|
10
|
+
|
|
11
|
+
**When:** during `0.1.0` stabilization (pre-publish).
|
|
12
|
+
|
|
13
|
+
`Menu` now accepts an optional `tree?: MenuCategory[]` prop. When supplied,
|
|
14
|
+
the component skips its internal `useMenu` fetch and renders the tree
|
|
15
|
+
directly — mirroring the long-standing `ProductGrid.products` opt-in.
|
|
16
|
+
|
|
17
|
+
**No migration required.** Omitting the prop preserves the legacy
|
|
18
|
+
client-side fetch behaviour. This is purely opt-in for hosts that want to
|
|
19
|
+
move the category tree fetch into a Server Component (e.g. a Next.js
|
|
20
|
+
layout) so the menu HTML lands in the initial response and the host can
|
|
21
|
+
attach framework cache hints — see also `GraphQLFetchOptions` in
|
|
22
|
+
`propeller-sdk-v2` v0.11.0.
|
|
23
|
+
|
|
24
|
+
### Recommended pattern for Next.js consumers
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// app/layout.tsx — Server Component
|
|
28
|
+
import { fetchMenu, getAnonymousInfra } from '@/lib/server';
|
|
29
|
+
import Header from '@/components/layout/Header';
|
|
30
|
+
|
|
31
|
+
export default async function RootLayout({ children }) {
|
|
32
|
+
const menuTree = await fetchMenu(getAnonymousInfra(), BASE_CATEGORY_ID, lang);
|
|
33
|
+
return (
|
|
34
|
+
<html><body>
|
|
35
|
+
<Header menuTree={menuTree} />
|
|
36
|
+
{children}
|
|
37
|
+
</body></html>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// components/layout/Header.tsx — 'use client'
|
|
42
|
+
import { Menu } from 'propeller-v2-react-ui';
|
|
43
|
+
export default function Header({ menuTree }) {
|
|
44
|
+
return <Menu categoryId={BASE_CATEGORY_ID} tree={menuTree} onMenuItemClick={...} />;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The internal `useEffect` short-circuits when `tree` is present, so there is
|
|
49
|
+
no avoidable client-side round trip after hydration.
|
|
50
|
+
|
|
51
|
+
See [TECH.md §7 "Pre-fetched data prop pattern"](./TECH.md) for the broader
|
|
52
|
+
context and the same pattern as it applies to `ProductGrid`.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Decoupling the SDK seam from Next.js
|
|
57
|
+
|
|
58
|
+
**When:** during `0.1.0` stabilization (pre-publish).
|
|
59
|
+
|
|
60
|
+
The package used to ship a module-level GraphQL client singleton with a
|
|
61
|
+
hardcoded `/api/graphql` endpoint and `NEXT_PUBLIC_*` environment reads, plus
|
|
62
|
+
a `/server` entry for server-side fetching. Both baked a specific app shape
|
|
63
|
+
(a Next.js app proxying at exactly that path) into the library. They were
|
|
64
|
+
removed.
|
|
65
|
+
|
|
66
|
+
### What changed
|
|
67
|
+
|
|
68
|
+
| Before | After |
|
|
69
|
+
| ------ | ----- |
|
|
70
|
+
| `import { graphqlClient } from 'propeller-v2-react-ui'` | Consumer constructs its own `GraphQLClient` |
|
|
71
|
+
| `import { getServices } from 'propeller-v2-react-ui'` | `import { createServices } from 'propeller-v2-react-ui'` |
|
|
72
|
+
| `getServices()` (singleton default) | `createServices(client)` — explicit client, required |
|
|
73
|
+
| `propeller-v2-react-ui/server` entry | Removed — host your own server module |
|
|
74
|
+
| `next` peer dependency | Removed — the package has no `next/*` imports |
|
|
75
|
+
| `PropellerInfra` without `services` | `PropellerInfra` requires a `services` field |
|
|
76
|
+
|
|
77
|
+
### How to upgrade
|
|
78
|
+
|
|
79
|
+
#### 1. Own your GraphQL client
|
|
80
|
+
|
|
81
|
+
Create a small module in your app that constructs the client and the
|
|
82
|
+
services bundle:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// lib/api.ts — in YOUR app
|
|
86
|
+
import { GraphQLClient } from 'propeller-sdk-v2';
|
|
87
|
+
import { createServices } from 'propeller-v2-react-ui';
|
|
88
|
+
|
|
89
|
+
export const graphqlClient = new GraphQLClient({
|
|
90
|
+
endpoint: '/api/graphql', // your endpoint / proxy path
|
|
91
|
+
apiKey: '',
|
|
92
|
+
orderEditorApiKey: process.env.NEXT_PUBLIC_ORDER_EDITOR_API_KEY || '',
|
|
93
|
+
timeout: 30_000,
|
|
94
|
+
headers: {},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const services = createServices(graphqlClient);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The endpoint, env-var names, and timeout are now **your** decision — pick
|
|
101
|
+
whatever fits your app (a route-handler proxy, a direct upstream URL, a
|
|
102
|
+
custom rewrite).
|
|
103
|
+
|
|
104
|
+
#### 2. Pass `graphqlClient` and `services` into `PropellerProvider`
|
|
105
|
+
|
|
106
|
+
`PropellerInfra` now requires `services`:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { PropellerProvider } from 'propeller-v2-react-ui';
|
|
110
|
+
import { graphqlClient, services } from '@/lib/api';
|
|
111
|
+
|
|
112
|
+
<PropellerProvider value={{
|
|
113
|
+
graphqlClient,
|
|
114
|
+
services, // ← newly required
|
|
115
|
+
user,
|
|
116
|
+
companyId,
|
|
117
|
+
language: 'NL',
|
|
118
|
+
includeTax: false,
|
|
119
|
+
currency: '€',
|
|
120
|
+
portalMode: 'OPEN',
|
|
121
|
+
configuration: {},
|
|
122
|
+
}}>
|
|
123
|
+
{children}
|
|
124
|
+
</PropellerProvider>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### 3. Replace `graphqlClient` / `getServices` imports
|
|
128
|
+
|
|
129
|
+
Anywhere you imported these from the package, import from your own
|
|
130
|
+
`lib/api` instead:
|
|
131
|
+
|
|
132
|
+
```diff
|
|
133
|
+
- import { graphqlClient, getServices } from 'propeller-v2-react-ui';
|
|
134
|
+
+ import { graphqlClient, services } from '@/lib/api';
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Call sites collapse — `getServices(graphqlClient).cart` becomes
|
|
138
|
+
`services.cart`:
|
|
139
|
+
|
|
140
|
+
```diff
|
|
141
|
+
- await getServices(graphqlClient).cart.deleteCart({ id });
|
|
142
|
+
+ await services.cart.deleteCart({ id });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Components and composables rendered inside `PropellerProvider` can also use
|
|
146
|
+
the `useServices()` hook instead of importing `services` directly.
|
|
147
|
+
|
|
148
|
+
#### 4. Update the shared cart helpers
|
|
149
|
+
|
|
150
|
+
`initCart`, `fetchActiveCart`, and `mergeAnonymousCart` previously took a
|
|
151
|
+
`graphqlClient` field in their config object. They now take `services`:
|
|
152
|
+
|
|
153
|
+
```diff
|
|
154
|
+
await fetchActiveCart({
|
|
155
|
+
- graphqlClient,
|
|
156
|
+
+ services,
|
|
157
|
+
user,
|
|
158
|
+
language,
|
|
159
|
+
imageSearchFilters,
|
|
160
|
+
imageVariantFilters,
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### 5. Replace the `/server` entry
|
|
165
|
+
|
|
166
|
+
If you imported `createServerClient`, `getServerInfra`, `fetchProduct`, or
|
|
167
|
+
`fetchCategory` from `propeller-v2-react-ui/server`, that entry is gone.
|
|
168
|
+
Host a server module in your own app instead. The pattern:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// lib/server.ts — in YOUR app
|
|
172
|
+
import 'server-only';
|
|
173
|
+
import { cookies } from 'next/headers'; // or your framework's equivalent
|
|
174
|
+
import { GraphQLClient } from 'propeller-sdk-v2';
|
|
175
|
+
import { createServices, toPlain } from 'propeller-v2-react-ui/shared';
|
|
176
|
+
|
|
177
|
+
export function createServerClient() {
|
|
178
|
+
return new GraphQLClient({
|
|
179
|
+
endpoint: process.env.PROPELLER_GRAPHQL_ENDPOINT!, // your env name
|
|
180
|
+
apiKey: process.env.PROPELLER_API_KEY!,
|
|
181
|
+
securityMode: 'direct',
|
|
182
|
+
getAccessToken: async () => (await cookies()).get('access_token')?.value,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function fetchProduct(productId: number, language = 'NL') {
|
|
187
|
+
const services = createServices(createServerClient());
|
|
188
|
+
const result = await services.product.getProduct({
|
|
189
|
+
productId, language, imageSearchFilters: {},
|
|
190
|
+
imageVariantFilters: { transformations: [] },
|
|
191
|
+
});
|
|
192
|
+
return result ? toPlain(result) : null;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`createServices` is exported from `propeller-v2-react-ui/shared` precisely so
|
|
197
|
+
it can be used server-side without pulling the client bundle into the server
|
|
198
|
+
graph. The `propeller-next` repo's `lib/server.ts` is a complete reference
|
|
199
|
+
implementation — copy it and adjust the env-var names and cookie name.
|
|
200
|
+
|
|
201
|
+
#### 6. Drop `next` from your reasoning, not your app
|
|
202
|
+
|
|
203
|
+
The package no longer peer-depends on `next`. Your Next.js app obviously
|
|
204
|
+
still depends on Next — nothing changes there. The point is the package
|
|
205
|
+
itself is now framework-neutral; nothing to do on your side beyond noting it.
|
|
206
|
+
|
|
207
|
+
### Why this change
|
|
208
|
+
|
|
209
|
+
GraphQL transport — the endpoint URL, whether you proxy, how you resolve
|
|
210
|
+
auth tokens, which environment-variable convention you use — is
|
|
211
|
+
application-specific. A library that hardcodes `/api/graphql` and
|
|
212
|
+
`NEXT_PUBLIC_*` is usable only by an app shaped exactly like the one it was
|
|
213
|
+
extracted from. Moving the client construction into the consumer makes the
|
|
214
|
+
package usable by any React app (Next.js App Router or Pages Router, Vite,
|
|
215
|
+
Remix, CRA) and makes it testable with a mock client.
|
|
216
|
+
|
|
217
|
+
See [TECH.md](./TECH.md) §6 for the full rationale.
|