@propeller-commerce/propeller-v2-react-ui 0.20.1 → 0.22.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,6 +8,107 @@ 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.22.0] - 2026-09-24
12
+
13
+ ### Fixed
14
+
15
+ - **Media language is matched case-insensitively.** The API lowercases
16
+ `documents[].language` while leaving `alt[].language` as uploaded, so
17
+ `ProductDownloads` matched the label but never the file and served
18
+ `documents[0]` — the Dutch PDF under an English label. `ProductVideos` had
19
+ the identical comparison and worked only by data luck. Also fixed in
20
+ `useProductSearch.filterByLanguage`, where a case mismatch dropped **every**
21
+ product from the grid, and in `DefaultProductImage`. (PWP-984)
22
+ - **`checkoutAllowed` is usable, and fails closed.** It read the raw `companyId`
23
+ option rather than the resolved one, so a contact acting for their own company
24
+ matched no authorization config and every cart reported as within limit; and it
25
+ evaluated its own cart state, which stays null when the consumer only seeds a
26
+ `cartId`. The hook now hydrates a seeded cart, and reports `false` while that
27
+ is in flight rather than `true`. (PWP-988)
28
+ - **The place-order button is gated on the authorization limit.** An over-limit
29
+ purchaser opening checkout directly got an ungated button and a backend
30
+ rejection; `CartOverview` now shows an `authorizationRequired` message
31
+ instead, using the same predicate as the cart summary. (PWP-988)
32
+ - **Semi-closed surfaces no longer flash their logged-out state** for a signed-in
33
+ visitor while the profile loads. Every `isContentHidden` call site passes the
34
+ provider’s `isAuthenticated` (core-ui 0.9.0+). (PWP-989)
35
+ - **The cart sidebar slides again.** The overflow fix named only `transform` in
36
+ its transition, but Tailwind v4 emits `translate-x-*` as the `translate`
37
+ property, so the panel jumped. (PWP-1002)
38
+ - **Header triggers inherit their colour.** `AccountIconAndMenu` and
39
+ `CompanySwitcher` hardcoded `text-white`, which broke light headers;
40
+ `text-foreground` then broke dark ones. Both now use `text-inherit`, with the
41
+ hover tint derived from `currentColor`. (PWP-1005)
42
+
43
+ ### Added
44
+
45
+ - `CompanySwitcher` accepts `triggerClassName`; it had no class prop at all.
46
+ - `ProductInfo` accepts `cartId`, `createCart` and `onCartCreated`, and forwards
47
+ them to both the injected and the default add-to-cart. It declared none of
48
+ them, so the PDP’s add-to-cart always failed for a visitor without a cart.
49
+ `ProductCard` forwards the same to an injected component. (PWP-986)
50
+ - `CartOverview` resolves `user` and `companyId` from `PropellerProvider`.
51
+
52
+ ## [0.21.0] - 2026-09-23
53
+
54
+ `MachineGrid` stops losing rows, and becomes usable in a translated or
55
+ customised storefront. One project had given up on it and rebuilt the surface
56
+ by hand (~450 lines), losing the breadcrumb, module grid and filter wiring.
57
+
58
+ ### Fixed
59
+
60
+ - **A machine with no slug in the tree language is no longer dropped from the
61
+ list.** `childSlugHref` returned `null` and the card was filtered out, so a
62
+ customer saw three of four machines with nothing saying so — and the same
63
+ helper guards the child-module grid, so a module could vanish from a machine
64
+ the same way. Two changes: `ROOT_MACHINE_FIELDS` no longer passes
65
+ `(language: $language)` to `name` / `description` / `slug`, so the
66
+ cross-language fallback in `getLocalizedValue` has entries to fall back to;
67
+ and a machine with no slug in *any* language now renders as an unlinked card
68
+ rather than vanishing. Needs SDK ≥ 0.17.0 for the child-machine half, which
69
+ comes through `getMachine`. (PWP-993)
70
+
71
+ The asymmetry that made this hard to spot: the **name** falls back across
72
+ languages, the **slug** did not — so a half-translated tree rendered names in
73
+ the wrong language rather than failing, but lost whole rows when the slug was
74
+ the missing half.
75
+
76
+ ### Added
77
+
78
+ - **`MachineGrid` forwards label overrides to the parts list.** It rendered its
79
+ parts through `ProductGrid` but forwarded none of `productCardLabels`,
80
+ `addToCartLabels`, `stockLabels`, `priceLabels` or `labels`, and there was no
81
+ way to reach them from outside — so "In stock", "Add" and "Search parts…"
82
+ appeared in English in the middle of translated copy. A monolingual shop
83
+ never noticed. (PWP-995a)
84
+ - **`MachineGrid.productCardComponent`** — the `ProductGrid` prop of the same
85
+ name, forwarded. Without it a quote-only product rendered with the host's
86
+ custom card on a normal listing and with the stock card inside a machine, so
87
+ the same product looked different depending on the page it was reached from.
88
+ (PWP-995c)
89
+ - **`useSpareParts({ page })`** — a controlled page, mirroring the existing
90
+ controlled `parts`. Every other listing input (`term`, `textFilters`,
91
+ `sortField`, `pageSize`) was already an option, so the absence of `page` read
92
+ as "paging is internal" — and it was not: driving the hook from URL state and
93
+ rendering `<GridPagination>` changed the URL and re-rendered the same first
94
+ twelve parts. The only way in was a `useEffect(() => goToPage(page))` that
95
+ `MachineGrid` did internally and nothing documented. `goToPage` still works
96
+ for uncontrolled hosts; omit `page` for the previous behaviour. (PWP-995b)
97
+
98
+ ### Changed
99
+
100
+ - `MachineCard.href` is now optional — a machine with no URL renders as a
101
+ `<div>` instead of an `<a>`.
102
+ - **`MachineGrid` drives `useSpareParts` with `page` directly** instead of the
103
+ internal `goToPage` effect.
104
+
105
+ ### Documentation
106
+
107
+ - **`MachineCardProps.labels` documented one key while `MachineGrid` read
108
+ three.** The doc named only `viewMachine`; `MachineGrid` also reads `loading`
109
+ and `noMachines` off the same object, so anyone following the doc left those
110
+ two in English. Both sides now say so. (PWP-995d)
111
+
11
112
  ## [0.20.1] - 2026-09-23
12
113
 
13
114
  ### Fixed
package/dist/index.cjs CHANGED
@@ -767,10 +767,29 @@ function useCart(options) {
767
767
  const [loading, setLoading] = React6.useState(false);
768
768
  const [error, setError] = React6.useState(null);
769
769
  const notesTimers = React6.useRef({});
770
- const checkoutAllowed = React6.useMemo(
771
- () => propellerV2CoreUi.isCheckoutAllowed(user, companyId, cart),
772
- [user, companyId, cart]
773
- );
770
+ React6.useEffect(() => {
771
+ if (!graphqlClient || !cartId || cart) return;
772
+ let cancelled = false;
773
+ (async () => {
774
+ try {
775
+ const fetched = await propellerV2CoreUi.createServices(graphqlClient).cart.getCart({
776
+ cartId,
777
+ imageSearchFilters: imageSearchFilters(),
778
+ imageVariantFilters: imageVariantFilters(),
779
+ language
780
+ });
781
+ if (!cancelled && fetched) setCart(fetched);
782
+ } catch {
783
+ }
784
+ })();
785
+ return () => {
786
+ cancelled = true;
787
+ };
788
+ }, [graphqlClient, cartId, cart, language, imageSearchFilters, imageVariantFilters]);
789
+ const checkoutAllowed = React6.useMemo(() => {
790
+ if (cartId && !cart) return false;
791
+ return propellerV2CoreUi.isCheckoutAllowed(user, resolvedCompanyId, cart);
792
+ }, [user, resolvedCompanyId, cart, cartId]);
774
793
  function getMinQuantity(product) {
775
794
  const min = product?.minimumQuantity;
776
795
  return min && min > 0 ? min : 1;
@@ -1591,7 +1610,8 @@ var INFRA_KEYS = [
1591
1610
  "includeTax",
1592
1611
  "currency",
1593
1612
  "configuration",
1594
- "portalMode"
1613
+ "portalMode",
1614
+ "isAuthenticated"
1595
1615
  ];
1596
1616
  function useInfraProps(props) {
1597
1617
  const ctx = usePropellerContext();
@@ -2268,10 +2288,11 @@ function useProductSearch(options) {
2268
2288
  const isLoading = !isControlled && internalLoading;
2269
2289
  function filterByLanguage(products, lang) {
2270
2290
  if (!lang) return products;
2291
+ const target = lang.toUpperCase();
2271
2292
  return products.filter((p) => {
2272
2293
  const names = p.names || p.names || [];
2273
2294
  if (!names || names.length === 0) return true;
2274
- return names.some((n) => n.language === lang);
2295
+ return names.some((n) => (n.language || "").toUpperCase() === target);
2275
2296
  });
2276
2297
  }
2277
2298
  const fetchProducts = React6.useCallback(async () => {
@@ -2776,7 +2797,8 @@ function useSpareParts(options) {
2776
2797
  const [childMachines, setChildMachines] = React6.useState([]);
2777
2798
  const [itemsFound, setItemsFound] = React6.useState(0);
2778
2799
  const [internalLoading, setInternalLoading] = React6.useState(false);
2779
- const [currentPage, setCurrentPage] = React6.useState(1);
2800
+ const [internalPage, setInternalPage] = React6.useState(1);
2801
+ const currentPage = options.page ?? internalPage;
2780
2802
  const [totalPages, setTotalPages] = React6.useState(1);
2781
2803
  const fetchIdRef = React6.useRef(0);
2782
2804
  const displayParts = React6.useMemo(
@@ -2925,7 +2947,7 @@ function useSpareParts(options) {
2925
2947
  userKey
2926
2948
  ]);
2927
2949
  const goToPage = React6.useCallback((page) => {
2928
- setCurrentPage((prev) => {
2950
+ setInternalPage((prev) => {
2929
2951
  if (page >= 1 && (totalPages <= 1 || page <= totalPages)) return page;
2930
2952
  return prev;
2931
2953
  });
@@ -2944,9 +2966,9 @@ function useSpareParts(options) {
2944
2966
  var inflightFetches4 = /* @__PURE__ */ new Set();
2945
2967
  var ROOT_MACHINE_FIELDS = `
2946
2968
  id
2947
- name(language: $language) { language value }
2948
- description(language: $language) { language value }
2949
- slug(language: $language) { language value }
2969
+ name { language value }
2970
+ description { language value }
2971
+ slug { language value }
2950
2972
  media {
2951
2973
  images {
2952
2974
  items { imageVariants(input: $imageVariantFilters) { url } }
@@ -4227,7 +4249,7 @@ function AccountIconAndMenu(rawProps) {
4227
4249
  onClick: (event) => handleIconClick(),
4228
4250
  "aria-label": propellerV2CoreUi.getLabel(props.labels, "accountLabel", "Account"),
4229
4251
  "data-open": menuOpen ? "true" : "false",
4230
- className: cn(`propeller-account-menu__trigger inline-flex items-center gap-2 px-3 py-2 rounded-control text-sm font-medium transition-colors text-foreground hover:bg-surface-hover${props.iconClassName ? " " + props.iconClassName : ""}`),
4252
+ className: cn(`propeller-account-menu__trigger inline-flex items-center gap-2 px-3 py-2 rounded-control text-sm font-medium transition-colors text-inherit${props.iconClassName ? " " + props.iconClassName : ""}`),
4231
4253
  children: [
4232
4254
  /* @__PURE__ */ jsxRuntime.jsx(
4233
4255
  "svg",
@@ -7639,7 +7661,8 @@ function CartIconAndSidebar(rawProps) {
7639
7661
  ] });
7640
7662
  }
7641
7663
  var CartIconAndSidebar_default = CartIconAndSidebar;
7642
- function CartOverview(props) {
7664
+ function CartOverview(rawProps) {
7665
+ const props = useInfraProps(rawProps);
7643
7666
  const [reference, setReference] = React6.useState("");
7644
7667
  const [notes, setNotes] = React6.useState("");
7645
7668
  const [termsAccepted, setTermsAccepted] = React6.useState(false);
@@ -7649,6 +7672,7 @@ function CartOverview(props) {
7649
7672
  const showReference = props.showReference !== false;
7650
7673
  const showTermsAndConditions = props.showTermsAndConditions !== false;
7651
7674
  const showPurchaseButton = props.showPurchaseButton !== false;
7675
+ const overLimit = propellerV2CoreUi.isOverAuthorizationLimit(props.user, props.companyId, props.cart);
7652
7676
  const invoiceAddress = props.cart?.invoiceAddress;
7653
7677
  const deliveryAddress = props.cart?.deliveryAddress;
7654
7678
  const getCountryName = (code) => propellerV2CoreUi.getCountryName(code, props.countries);
@@ -7788,7 +7812,12 @@ function CartOverview(props) {
7788
7812
  ] });
7789
7813
  })() })
7790
7814
  ] }) : null,
7791
- showPurchaseButton ? /* @__PURE__ */ jsxRuntime.jsxs(
7815
+ showPurchaseButton && overLimit ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "propeller-cart-overview__authorization-required text-sm text-muted-foreground mt-2", children: propellerV2CoreUi.getLabel(
7816
+ props.labels,
7817
+ "authorizationRequired",
7818
+ "This order exceeds your authorization limit. Request authorization from the cart to continue."
7819
+ ) }) : null,
7820
+ showPurchaseButton && !overLimit ? /* @__PURE__ */ jsxRuntime.jsxs(
7792
7821
  "button",
7793
7822
  {
7794
7823
  type: "button",
@@ -8163,6 +8192,7 @@ var RESOLVE_SPEC = {
8163
8192
  currency: { infra: "currency", default: "\u20AC" },
8164
8193
  user: { infra: "user", default: null },
8165
8194
  portalMode: { infra: "portalMode" },
8195
+ isAuthenticated: { infra: "isAuthenticated" },
8166
8196
  columns: { grid: "columns", default: 3 },
8167
8197
  showPrice: { grid: "showPrice" },
8168
8198
  showStock: { grid: "showStock" },
@@ -8206,7 +8236,7 @@ function ClusterCard(rawProps) {
8206
8236
  const textLabelValues = resolveAttributeValues(cluster, props.textLabels).map((value) => ({
8207
8237
  value
8208
8238
  }));
8209
- const contentHidden = propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
8239
+ const contentHidden = propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
8210
8240
  const defaultProductInventory = contentHidden ? void 0 : cluster?.defaultProduct?.inventory;
8211
8241
  const useTax = props.includeTax !== void 0 ? !!props.includeTax : false;
8212
8242
  const priceObj = contentHidden ? void 0 : cluster?.defaultProduct?.price;
@@ -8549,6 +8579,7 @@ function getMachineImageUrl(machine) {
8549
8579
  }
8550
8580
  function MachineCard(props) {
8551
8581
  const { machine, href, language } = props;
8582
+ const Wrapper = href ? "a" : "div";
8552
8583
  const showImage = props.showImage ?? true;
8553
8584
  const showDescription = props.showDescription ?? false;
8554
8585
  const name = propellerV2CoreUi.getLocalizedValue(machine.name, language);
@@ -8560,10 +8591,9 @@ function MachineCard(props) {
8560
8591
  {
8561
8592
  className: cn(`propeller-machine-card group relative flex h-full flex-col overflow-hidden rounded-lg border border-border bg-background ${props.className ?? ""}`),
8562
8593
  children: /* @__PURE__ */ jsxRuntime.jsxs(
8563
- "a",
8594
+ Wrapper,
8564
8595
  {
8565
- href,
8566
- onClick: handleClick,
8596
+ ...href ? { href, onClick: handleClick } : {},
8567
8597
  className: "flex h-full flex-col focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
8568
8598
  children: [
8569
8599
  showImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-machine-card__image aspect-square w-full overflow-hidden bg-background-subtle", children: imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -8633,7 +8663,7 @@ function LoginToOrderButton(props) {
8633
8663
  var LoginToOrderButton_default = LoginToOrderButton;
8634
8664
  function ProductPriceDisplay(props) {
8635
8665
  function isHidden2() {
8636
- return propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
8666
+ return propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
8637
8667
  }
8638
8668
  function formatPrice17(value) {
8639
8669
  if (value === null || value === void 0) return "";
@@ -8725,6 +8755,7 @@ var RESOLVE_SPEC2 = {
8725
8755
  language: { infra: "language", default: "NL" },
8726
8756
  includeTax: { infra: "includeTax" },
8727
8757
  portalMode: { infra: "portalMode" },
8758
+ isAuthenticated: { infra: "isAuthenticated" },
8728
8759
  configuration: { infra: "configuration" },
8729
8760
  // Same entry ClusterCard has always had. Without it a shop priced in
8730
8761
  // anything but euros had no route to the cards at all: the grid resolved the
@@ -8783,7 +8814,7 @@ function ProductCardRoot(rawProps) {
8783
8814
  const language = props.language || "NL";
8784
8815
  const product = props.product;
8785
8816
  const isRow = props.columns === 1;
8786
- const contentHidden = propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
8817
+ const contentHidden = propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
8787
8818
  const showStock = !!props.showStock && !contentHidden;
8788
8819
  const showCta = props.allowAddToCart !== false || contentHidden;
8789
8820
  const name = getProductName(product, language);
@@ -9080,6 +9111,7 @@ function ProductCardPrice(props) {
9080
9111
  currency: resolved.currency,
9081
9112
  language: resolved.language,
9082
9113
  portalMode: resolved.portalMode,
9114
+ isAuthenticated: resolved.isAuthenticated,
9083
9115
  user: resolved.user,
9084
9116
  showLoginPrompt: false
9085
9117
  }
@@ -9087,7 +9119,7 @@ function ProductCardPrice(props) {
9087
9119
  }
9088
9120
  function ProductCardAddToCart(props) {
9089
9121
  const { product, resolved } = useProductCard();
9090
- if (propellerV2CoreUi.isContentHidden(resolved.portalMode, resolved.user)) {
9122
+ if (propellerV2CoreUi.isContentHidden(resolved.portalMode, resolved.user, resolved.isAuthenticated)) {
9091
9123
  return /* @__PURE__ */ jsxRuntime.jsx(
9092
9124
  LoginToOrderButton_default,
9093
9125
  {
@@ -9108,9 +9140,13 @@ function ProductCardAddToCart(props) {
9108
9140
  allowIncrDecr: resolved.allowIncrDecr,
9109
9141
  showModal: resolved.showModal,
9110
9142
  enableStockValidation: resolved.enableStockValidation,
9143
+ onAddToCart: resolved.onAddToCart,
9111
9144
  afterAddToCart: resolved.afterAddToCart,
9112
9145
  onProceedToCheckout: resolved.onProceedToCheckout,
9113
9146
  onRequestQuoteClick: resolved.onRequestQuoteClick,
9147
+ createCart: resolved.createCart,
9148
+ onCartCreated: resolved.onCartCreated,
9149
+ includeTax: resolved.includeTax,
9114
9150
  labels: resolved.addToCartLabels
9115
9151
  }
9116
9152
  );
@@ -9233,7 +9269,7 @@ function ProductGrid(rawProps) {
9233
9269
  }
9234
9270
  function showAddToCart() {
9235
9271
  const allow = props.allowAddToCart !== false;
9236
- return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user) && allow;
9272
+ return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated) && allow;
9237
9273
  }
9238
9274
  function getSkeletonItems() {
9239
9275
  const cols = props.columns || 3;
@@ -9511,7 +9547,7 @@ function GridFilters(rawProps) {
9511
9547
  }
9512
9548
  function showAvailability() {
9513
9549
  if (!props.showAvailabilityFilter) return false;
9514
- return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
9550
+ return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
9515
9551
  }
9516
9552
  function getFilterName(filter) {
9517
9553
  return filter?.attributeDescription?.name || "";
@@ -10066,7 +10102,7 @@ function GridToolbar(rawProps) {
10066
10102
  return badges;
10067
10103
  }
10068
10104
  function isPriceSortDisabled() {
10069
- return propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
10105
+ return propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
10070
10106
  }
10071
10107
  function getAvailabilityLabel() {
10072
10108
  const qty = props.minStock;
@@ -10468,7 +10504,7 @@ function MachineGrid(rawProps) {
10468
10504
  // node query threads through useSpareParts.
10469
10505
  imageVariantFilters: configuration?.imageVariantFiltersMedium
10470
10506
  });
10471
- const { displayParts, childMachines, isLoading: partsLoading, currentPage, totalPages, goToPage } = useSpareParts({
10507
+ const { displayParts, childMachines, isLoading: partsLoading, currentPage, totalPages } = useSpareParts({
10472
10508
  graphqlClient: props.graphqlClient,
10473
10509
  // Idle at the root (no slug).
10474
10510
  slug: isRoot ? void 0 : currentSlug,
@@ -10484,6 +10520,10 @@ function MachineGrid(rawProps) {
10484
10520
  sortField,
10485
10521
  sortOrder,
10486
10522
  pageSize: offset,
10523
+ // The hook takes the controlled page directly now. It used to own its own
10524
+ // counter, so the only way to drive it from URL state was the effect
10525
+ // below — undiscoverable unless you read this file (PWP-995b).
10526
+ page: listing.page,
10487
10527
  configuration: rawProps.configuration ?? props.configuration,
10488
10528
  onFiltersChange: setGridFilters,
10489
10529
  onPriceBoundsChange: (min, max) => {
@@ -10493,9 +10533,6 @@ function MachineGrid(rawProps) {
10493
10533
  onItemsFoundChange: setItemsFound,
10494
10534
  onMachineChange: setMachine
10495
10535
  });
10496
- React6.useEffect(() => {
10497
- goToPage(listing.page);
10498
- }, [listing.page, goToPage]);
10499
10536
  const machineName = machine ? propellerV2CoreUi.getLocalizedValue(machine.name, machineLanguage) : slugToLabel(currentSlug);
10500
10537
  const partProducts = React6.useMemo(
10501
10538
  () => displayParts.map((p) => p.product).filter(Boolean),
@@ -10552,11 +10589,10 @@ function MachineGrid(rawProps) {
10552
10589
  );
10553
10590
  const childSlugHref = (child) => {
10554
10591
  const slug = propellerV2CoreUi.getLocalizedValue(child.slug, machineLanguage);
10555
- if (!slug) return null;
10556
- return `${currentPath}/${slug}`;
10592
+ return slug ? `${currentPath}/${slug}` : void 0;
10557
10593
  };
10558
10594
  if (isRoot) {
10559
- const cards = rootMachines.map((m) => ({ machine: m, href: childSlugHref(m) })).filter((c) => c.href !== null);
10595
+ const cards = rootMachines.map((m) => ({ machine: m, href: childSlugHref(m) }));
10560
10596
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rawProps.className, children: [
10561
10597
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "propeller-grid-title mb-6 text-2xl font-semibold", children: rawProps.rootTitle ?? "Machines" }),
10562
10598
  rootLoading ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-12 text-center text-foreground-subtle", children: propellerV2CoreUi.getLabel(rawProps.machineCardLabels, "loading", "Loading\u2026") }) : cards.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-12 text-center text-foreground-subtle", children: propellerV2CoreUi.getLabel(rawProps.machineCardLabels, "noMachines", "No machines found.") }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: cards.map(({ machine: m, href }) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -10588,7 +10624,6 @@ function MachineGrid(rawProps) {
10588
10624
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "propeller-grid-title mb-6 text-2xl font-semibold", children: machineName }),
10589
10625
  childMachines.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-machine-children mb-8 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: childMachines.map((child) => {
10590
10626
  const href = childSlugHref(child);
10591
- if (!href) return null;
10592
10627
  return /* @__PURE__ */ jsxRuntime.jsx(
10593
10628
  MachineCard,
10594
10629
  {
@@ -10666,6 +10701,12 @@ function MachineGrid(rawProps) {
10666
10701
  columns: viewMode === "list" ? 1 : 3,
10667
10702
  showAvailability: rawProps.showAvailability ?? false,
10668
10703
  showStock: rawProps.showStock ?? true,
10704
+ productCardComponent: rawProps.productCardComponent,
10705
+ productCardLabels: rawProps.productCardLabels,
10706
+ addToCartLabels: rawProps.addToCartLabels,
10707
+ stockLabels: rawProps.stockLabels,
10708
+ priceLabels: rawProps.priceLabels,
10709
+ labels: rawProps.labels,
10669
10710
  belowName: (product) => {
10670
10711
  const qty = quantityBySku.get(product.sku);
10671
10712
  if (!qty) return null;
@@ -10914,7 +10955,7 @@ function ClusterOptions(rawProps) {
10914
10955
  const language = props.language;
10915
10956
  const options = props.options || [];
10916
10957
  const sel = selectedProductIds;
10917
- const hidePrices = propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
10958
+ const hidePrices = propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
10918
10959
  return options.filter((option) => option.hidden !== propellerSdkV2.YesNo.Y).map((option) => {
10919
10960
  const idStr = option.id.toString();
10920
10961
  const selectedProductId = sel[idStr] || "";
@@ -11098,7 +11139,11 @@ function CompanySwitcher(props) {
11098
11139
  type: "button",
11099
11140
  "aria-haspopup": "listbox",
11100
11141
  "aria-label": propellerV2CoreUi.getLabel(props.labels, "switchCompanyAriaLabel", "Switch company"),
11101
- className: "propeller-company-switcher__trigger flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium transition-colors hover:bg-white/10",
11142
+ className: cn(
11143
+ // `text-inherit`: this sits in the host's header, which may be dark or
11144
+ // light. See the note on AccountIconAndMenu's trigger.
11145
+ `propeller-company-switcher__trigger flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium transition-colors text-inherit${props.triggerClassName ? " " + props.triggerClassName : ""}`
11146
+ ),
11102
11147
  onClick: (event) => toggleDropdown(),
11103
11148
  "aria-expanded": isOpen,
11104
11149
  children: [
@@ -15073,7 +15118,7 @@ function PriceToggle(props) {
15073
15118
  var PriceToggle_default = PriceToggle;
15074
15119
  function ProductBulkPrices(props) {
15075
15120
  const includeTax = !!props.includeTax;
15076
- const isHidden2 = propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
15121
+ const isHidden2 = propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
15077
15122
  function getTierQuantity(tier) {
15078
15123
  const discount = tier.discount;
15079
15124
  return discount?.quantityFrom ?? tier.quantity ?? null;
@@ -15620,14 +15665,10 @@ function ProductDownloads(props) {
15620
15665
  const hasItems = items.length > 0;
15621
15666
  const lang = props.language || "NL";
15622
15667
  function getDocumentUrl(doc) {
15623
- const docs = doc.documents || [];
15624
- const match = docs.find((d) => d.language === lang);
15625
- return match?.originalUrl || docs?.[0]?.originalUrl || "";
15668
+ return propellerV2CoreUi.resolveLanguageEntry(doc.documents, lang)?.originalUrl || "";
15626
15669
  }
15627
15670
  function getDocumentName(doc) {
15628
- const alts = doc.alt || [];
15629
- const match = alts.find((a) => a.language === lang);
15630
- return match?.value || alts?.[0]?.value || "Download";
15671
+ return propellerV2CoreUi.getLanguageString(doc.alt, lang, "Download");
15631
15672
  }
15632
15673
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(`propeller-product-downloads ${props.className || ""}`), children: [
15633
15674
  hasItems ? /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "propeller-product-downloads__title text-base font-semibold text-foreground mb-3", children: propellerV2CoreUi.getLabel(props.labels, "title", "Downloads") }) : null,
@@ -15833,12 +15874,14 @@ function ProductGallery(props) {
15833
15874
  var ProductGallery_default = ProductGallery;
15834
15875
  function pickImageUrl(mediaItems, target) {
15835
15876
  if (!mediaItems || mediaItems.length === 0) return null;
15877
+ const targetUpper = (target || "").toUpperCase();
15878
+ const langMatches = (value) => (value || "").toUpperCase() === targetUpper;
15836
15879
  for (const m of mediaItems) {
15837
- const match = m.imageVariants?.find((v) => v.language === target && v.url);
15880
+ const match = m.imageVariants?.find((v) => langMatches(v.language) && v.url);
15838
15881
  if (match?.url) return match.url;
15839
15882
  }
15840
15883
  for (const m of mediaItems) {
15841
- const match = m.images?.find((i) => i.language === target && i.originalUrl);
15884
+ const match = m.images?.find((i) => langMatches(i.language) && i.originalUrl);
15842
15885
  if (match?.originalUrl) return match.originalUrl;
15843
15886
  }
15844
15887
  for (const m of mediaItems) {
@@ -16166,6 +16209,7 @@ function ProductInfoPrice(props = {}) {
16166
16209
  currency: derived.currency,
16167
16210
  taxZone: derived.taxZone,
16168
16211
  portalMode: derived.portalMode,
16212
+ isAuthenticated: resolved.isAuthenticated,
16169
16213
  user: derived.user,
16170
16214
  labels: derived.labels,
16171
16215
  className: props.className
@@ -16175,7 +16219,7 @@ function ProductInfoPrice(props = {}) {
16175
16219
  function ProductInfoStock(props = {}) {
16176
16220
  const { product, resolved, derived } = useProductInfoContext();
16177
16221
  if (!product.inventory) return null;
16178
- if (propellerV2CoreUi.isContentHidden(derived.portalMode, resolved.user)) return null;
16222
+ if (propellerV2CoreUi.isContentHidden(derived.portalMode, resolved.user, resolved.isAuthenticated)) return null;
16179
16223
  const Injected = resolved.stockComponent;
16180
16224
  if (Injected) {
16181
16225
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -16202,7 +16246,7 @@ function ProductInfoStock(props = {}) {
16202
16246
  }
16203
16247
  function ProductInfoAddToCart(props = {}) {
16204
16248
  const { product, resolved, derived } = useProductInfoContext();
16205
- if (propellerV2CoreUi.isContentHidden(derived.portalMode, resolved.user)) {
16249
+ if (propellerV2CoreUi.isContentHidden(derived.portalMode, resolved.user, resolved.isAuthenticated)) {
16206
16250
  return /* @__PURE__ */ jsxRuntime.jsx(
16207
16251
  LoginToOrderButton_default,
16208
16252
  {
@@ -16214,7 +16258,18 @@ function ProductInfoAddToCart(props = {}) {
16214
16258
  }
16215
16259
  const Injected = resolved.addToCartComponent;
16216
16260
  if (Injected) {
16217
- return /* @__PURE__ */ jsxRuntime.jsx(Injected, { product, labels: derived.labels, className: props.className });
16261
+ return /* @__PURE__ */ jsxRuntime.jsx(
16262
+ Injected,
16263
+ {
16264
+ product,
16265
+ cartId: resolved.cartId,
16266
+ createCart: resolved.createCart,
16267
+ onCartCreated: resolved.onCartCreated,
16268
+ includeTax: derived.useTax,
16269
+ labels: derived.labels,
16270
+ className: props.className
16271
+ }
16272
+ );
16218
16273
  }
16219
16274
  return /* @__PURE__ */ jsxRuntime.jsx(
16220
16275
  AddToCart_default,
@@ -16223,6 +16278,9 @@ function ProductInfoAddToCart(props = {}) {
16223
16278
  user: derived.user,
16224
16279
  currency: derived.currency,
16225
16280
  product,
16281
+ cartId: resolved.cartId,
16282
+ createCart: resolved.createCart,
16283
+ onCartCreated: resolved.onCartCreated,
16226
16284
  configuration: resolved.configuration,
16227
16285
  companyId: resolved.companyId,
16228
16286
  includeTax: derived.useTax,
@@ -16276,6 +16334,7 @@ function ProductInfoBulkPrices(props = {}) {
16276
16334
  bulkPrices: product.bulkPrices,
16277
16335
  includeTax: derived.useTax,
16278
16336
  portalMode: derived.portalMode,
16337
+ isAuthenticated: resolved.isAuthenticated,
16279
16338
  user: derived.user,
16280
16339
  taxZone: derived.taxZone,
16281
16340
  currency: derived.currency,
@@ -16438,7 +16497,7 @@ function ProductSlider(rawProps) {
16438
16497
  }
16439
16498
  function showAddToCart() {
16440
16499
  const allow = props.allowAddToCart !== false;
16441
- return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user) && allow;
16500
+ return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated) && allow;
16442
16501
  }
16443
16502
  function isCluster(item) {
16444
16503
  return "clusterId" in item && !("productId" in item);
@@ -16742,14 +16801,10 @@ function ProductVideos(props) {
16742
16801
  const hasItems = items.length > 0;
16743
16802
  const lang = props.language || "NL";
16744
16803
  function getVideoUri(video) {
16745
- const vids = video.videos || [];
16746
- const match = vids.find((v) => v.language === lang);
16747
- return match?.uri || vids?.[0]?.uri || "";
16804
+ return propellerV2CoreUi.getLanguageUri(video.videos, lang, "");
16748
16805
  }
16749
16806
  function getVideoTitle(video) {
16750
- const alts = video.alt || [];
16751
- const match = alts.find((a) => a.language === lang);
16752
- return match?.value || alts?.[0]?.value || "Video";
16807
+ return propellerV2CoreUi.getLanguageString(video.alt, lang, "Video");
16753
16808
  }
16754
16809
  function isEmbeddable(uri) {
16755
16810
  return uri.includes("youtube.com") || uri.includes("youtu.be") || uri.includes("vimeo.com");