@propeller-commerce/propeller-v2-react-ui 0.4.25 → 0.4.27

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,62 @@ 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.4.27] - 2026-07-21
12
+
13
+ ### Fixed
14
+
15
+ - **`SearchBar` autosuggest now respects orderlist (contract) scoping.** The
16
+ debounced live-search query built a plain `ProductSearchInput` that never
17
+ passed `orderlistIds`/`applyOrderlists`, so the dropdown previewed the full
18
+ catalogue even inside a B2B contract catalogue (the submitted results were
19
+ already correctly scoped — only the preview leaked). It now threads the same
20
+ orderlist scope as the grid fetch, so previews and results agree and a PDP
21
+ outside the contract is no longer reachable from the preview.
22
+
23
+ ### Added
24
+
25
+ - **`SearchBar`: `renderPrice` + `showPrice` props.** `renderPrice(result)`
26
+ replaces the price cell of each result with custom content (return any node,
27
+ or `null` to render nothing) — e.g. a "Price by quotation" label for a
28
+ contract catalogue where prices are quote-only; it fully overrides the
29
+ default price rendering. `showPrice` (default `true`) is a simpler boolean to
30
+ hide the price column outright. Both default to the previous behaviour, so
31
+ the change is backward-compatible.
32
+
33
+ ## [0.4.26] - 2026-07-21
34
+
35
+ ### Added
36
+
37
+ - **`SearchBar`: real anchor links for navigation.** New optional
38
+ `getResultHref(result)` and `getViewAllHref(term)` props. When provided, the
39
+ autosuggest result rows and the "View all results" CTA render as real
40
+ `<a href>` elements — middle-clickable, open-in-new-tab, hover-preview,
41
+ crawlable — while the existing `onResultClick`/`onViewAllClick` callbacks
42
+ still fire for SPA navigation (modified clicks fall through to the browser).
43
+ Omit the props to keep the previous behaviour.
44
+ - **`OrderList`: URL-persistable filters.** New optional `initialSearchForm`
45
+ (seed the filter form on mount, e.g. rehydrated from the URL) and
46
+ `onSearchApply(form)` (fires when the user applies/clears filters) props, so
47
+ the consuming page can keep the filter state in the URL — bookmarkable,
48
+ shareable, back-button-friendly. `useOrders` gained the matching
49
+ `initialSearchForm` option.
50
+
51
+ ### Changed
52
+
53
+ - **`AccountIconAndMenu`: account-menu items are now `<a href>` instead of
54
+ `<button>`** (both the sidebar and dropdown variants). They use each link's
55
+ existing `href`, so they are middle-clickable / new-tab-able / crawlable;
56
+ `onMenuItemClick` still handles plain-click SPA navigation.
57
+ - **`SearchBar`: the "View all results" fallback is now a `<button>`** (was a
58
+ non-focusable `<div>`), so it is keyboard-accessible even without the new
59
+ `getViewAllHref` prop.
60
+
61
+ ### Fixed
62
+
63
+ - **`OrderList`: clickable rows now show `cursor: pointer`.** When
64
+ `rowsClickable` is set, the row cursor matches its behaviour (was `auto`,
65
+ inconsistent with the favourites cards).
66
+
11
67
  ## [0.4.25] - 2026-07-20
12
68
 
13
69
  ### Fixed
package/dist/index.cjs CHANGED
@@ -1718,7 +1718,9 @@ function useOrders(options) {
1718
1718
  const [orders, setOrders] = React6.useState([]);
1719
1719
  const [loading, setLoading] = React6.useState(false);
1720
1720
  const [error, setError] = React6.useState(null);
1721
- const [searchForm, setSearchForm] = React6.useState({});
1721
+ const [searchForm, setSearchForm] = React6.useState(
1722
+ options.initialSearchForm ?? {}
1723
+ );
1722
1724
  const pagination = usePagination(options.itemsPerPage ?? 10);
1723
1725
  const fetchOrders = React6.useCallback(
1724
1726
  async (page = 1) => {
@@ -2363,6 +2365,10 @@ function useProductSearch(options) {
2363
2365
  setSearchLoading(true);
2364
2366
  try {
2365
2367
  const service = propellerV2CoreUi.createServices(graphqlClient).product;
2368
+ const orderlistScope = options.orderlistIds && options.orderlistIds.length > 0 ? {
2369
+ applyOrderlists: options.applyOrderlists !== false,
2370
+ orderlistIds: options.orderlistIds
2371
+ } : { applyOrderlists: false };
2366
2372
  const input = {
2367
2373
  term,
2368
2374
  language,
@@ -2374,6 +2380,7 @@ function useProductSearch(options) {
2374
2380
  propellerSdkV2.ProductStatus.T,
2375
2381
  propellerSdkV2.ProductStatus.S
2376
2382
  ],
2383
+ ...orderlistScope,
2377
2384
  sortInputs: [{ field: propellerSdkV2.ProductSortField.RELEVANCE, order: propellerSdkV2.SortOrder.DESC }],
2378
2385
  searchFields: [
2379
2386
  {
@@ -2420,7 +2427,8 @@ function useProductSearch(options) {
2420
2427
  }
2421
2428
  }, 300);
2422
2429
  },
2423
- [graphqlClient, language, configuration, userKey]
2430
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2431
+ [graphqlClient, language, configuration, userKey, JSON.stringify(options.orderlistIds ?? []), options.applyOrderlists]
2424
2432
  );
2425
2433
  React6.useEffect(() => {
2426
2434
  if (!isControlled) fetchProducts();
@@ -3575,6 +3583,13 @@ function AccountIconAndMenu(rawProps) {
3575
3583
  setMenuOpen(false);
3576
3584
  if (props.onMenuItemClick) props.onMenuItemClick(href);
3577
3585
  }
3586
+ function handleMenuLinkClick(event, href) {
3587
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
3588
+ return;
3589
+ }
3590
+ event.preventDefault();
3591
+ handleMenuItemClick(href);
3592
+ }
3578
3593
  function handleLogoutClick() {
3579
3594
  setMenuOpen(false);
3580
3595
  if (props.onLogoutClick) props.onLogoutClick();
@@ -3623,10 +3638,10 @@ function AccountIconAndMenu(rawProps) {
3623
3638
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "propeller-account-menu__user-name font-medium text-foreground truncate", children: getUserName() })
3624
3639
  ] }),
3625
3640
  /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "propeller-account-menu__nav py-2", children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "propeller-account-menu__list space-y-0.5", children: getMenuLinks()?.map((link) => /* @__PURE__ */ jsxRuntime.jsx("li", { className: "propeller-account-menu__item", children: /* @__PURE__ */ jsxRuntime.jsx(
3626
- "button",
3641
+ "a",
3627
3642
  {
3628
- type: "button",
3629
- onClick: (event) => handleMenuItemClick(link.href),
3643
+ href: link.href,
3644
+ onClick: (event) => handleMenuLinkClick(event, link.href),
3630
3645
  "data-active": isActiveLink(link.href) ? "true" : "false",
3631
3646
  className: `propeller-account-menu__link flex w-full items-center gap-3 px-4 py-2.5 text-sm font-medium transition-colors ${isActiveLink(link.href) ? "bg-secondary/5 text-secondary border-l-2 border-secondary" : "text-muted-foreground hover:bg-surface-hover hover:text-foreground"}`,
3632
3647
  children: link.label
@@ -3689,11 +3704,11 @@ function AccountIconAndMenu(rawProps) {
3689
3704
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "propeller-account-menu__user-name font-medium text-foreground truncate", children: getUserName() })
3690
3705
  ] }),
3691
3706
  /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "propeller-account-menu__nav", children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "propeller-account-menu__list space-y-0.5", children: getMenuLinks()?.map((link) => /* @__PURE__ */ jsxRuntime.jsx("li", { className: "propeller-account-menu__item", children: /* @__PURE__ */ jsxRuntime.jsx(
3692
- "button",
3707
+ "a",
3693
3708
  {
3694
- type: "button",
3709
+ href: link.href,
3695
3710
  className: "propeller-account-menu__link flex w-full items-center gap-3 px-3 py-2 text-sm font-medium rounded-control text-muted-foreground hover:bg-surface-hover hover:text-foreground transition-colors",
3696
- onClick: (event) => handleMenuItemClick(link.href),
3711
+ onClick: (event) => handleMenuLinkClick(event, link.href),
3697
3712
  children: link.label
3698
3713
  }
3699
3714
  ) }, link.href)) }) }),
@@ -11850,7 +11865,8 @@ function OrderList(rawProps) {
11850
11865
  orderStatuses: props.orderStatus,
11851
11866
  termFields: props.termFields,
11852
11867
  itemsPerPage: props.initialItemsPerPage || 10,
11853
- channelIds: props.channelIds
11868
+ channelIds: props.channelIds,
11869
+ initialSearchForm: props.initialSearchForm
11854
11870
  });
11855
11871
  function handlePageChange(newPage) {
11856
11872
  if (newPage >= 1 && newPage <= totalPages) {
@@ -12163,6 +12179,7 @@ function OrderList(rawProps) {
12163
12179
  onClick: (event) => {
12164
12180
  setSearchForm({});
12165
12181
  fetchOrders(1);
12182
+ props.onSearchApply?.({});
12166
12183
  },
12167
12184
  children: propellerV2CoreUi.getLabel(props.labels, "clearButton", "Clear")
12168
12185
  }
@@ -12171,7 +12188,10 @@ function OrderList(rawProps) {
12171
12188
  "button",
12172
12189
  {
12173
12190
  className: "propeller-order-list__search-btn inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-control shadow-sm text-primary-foreground bg-primary hover:bg-primary/80 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary",
12174
- onClick: (event) => fetchOrders(1),
12191
+ onClick: (event) => {
12192
+ fetchOrders(1);
12193
+ props.onSearchApply?.(searchForm);
12194
+ },
12175
12195
  children: propellerV2CoreUi.getLabel(props.labels, "searchButton", "Search")
12176
12196
  }
12177
12197
  )
@@ -12191,7 +12211,7 @@ function OrderList(rawProps) {
12191
12211
  /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "propeller-order-list__tbody bg-card divide-y divide-border", children: orders?.map((order) => /* @__PURE__ */ jsxRuntime.jsx(
12192
12212
  "tr",
12193
12213
  {
12194
- className: "propeller-order-list__row hover:bg-surface-hover",
12214
+ className: `propeller-order-list__row hover:bg-surface-hover ${rowsClickable ? "cursor-pointer" : ""}`,
12195
12215
  "data-clickable": rowsClickable ? "true" : "false",
12196
12216
  onClick: (event) => rowsClickable && props.onOrderClick(order.id),
12197
12217
  children: columns?.map((col) => /* @__PURE__ */ jsxRuntime.jsxs(
@@ -17281,6 +17301,9 @@ function SearchBar(rawProps) {
17281
17301
  }
17282
17302
  setShowDropdown(false);
17283
17303
  }
17304
+ function isModifiedClick(event) {
17305
+ return event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
17306
+ }
17284
17307
  React6.useEffect(() => {
17285
17308
  const listener = (e) => {
17286
17309
  if (containerRef.current && !containerRef.current.contains(e.target)) {
@@ -17334,39 +17357,68 @@ function SearchBar(rawProps) {
17334
17357
  ] }) }),
17335
17358
  showDropdown ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-search-bar__dropdown absolute top-full left-0 right-0 mt-2 bg-card rounded-container shadow-xl border border-border z-50 flex flex-col max-h-96", children: [
17336
17359
  results.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17337
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-search-bar__results flex-1 overflow-y-auto", children: results?.map((result, index) => /* @__PURE__ */ jsxRuntime.jsxs(
17338
- "div",
17360
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-search-bar__results flex-1 overflow-y-auto", children: results?.map((result, index) => {
17361
+ const ResultTag = props.getResultHref ? "a" : "div";
17362
+ const resultProps = props.getResultHref ? {
17363
+ href: props.getResultHref(result),
17364
+ onClick: (event) => {
17365
+ if (isModifiedClick(event)) return;
17366
+ event.preventDefault();
17367
+ handleResultClick(result);
17368
+ }
17369
+ } : { onClick: (_event) => handleResultClick(result) };
17370
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17371
+ ResultTag,
17372
+ {
17373
+ className: "propeller-search-bar__result flex items-center gap-4 p-3 hover:bg-surface-hover cursor-pointer border-b border-border-subtle last:border-b-0",
17374
+ ...resultProps,
17375
+ children: [
17376
+ result.imageUrl || noImageUrl() ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-search-bar__result-media relative w-16 h-16 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
17377
+ "img",
17378
+ {
17379
+ className: "propeller-search-bar__result-image w-full h-full object-contain",
17380
+ src: result.imageUrl || noImageUrl(),
17381
+ alt: result.name
17382
+ }
17383
+ ) }) : null,
17384
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
17385
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-search-bar__result-name font-semibold truncate", children: result.name }),
17386
+ result.sku ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-search-bar__result-sku text-sm text-muted-foreground", children: [
17387
+ "SKU: ",
17388
+ result.sku
17389
+ ] }) : null
17390
+ ] }),
17391
+ props.renderPrice ? props.renderPrice(result) : props.showPrice !== false && result.price !== void 0 && result.price !== null ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-search-bar__result-price text-sm font-semibold text-foreground flex-shrink-0 text-right", children: [
17392
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-search-bar__result-price-value", children: formatItemPrice(leadingPrice(result)) }),
17393
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-search-bar__result-price-label block text-xs font-normal text-muted-foreground", children: priceTaxLabel() })
17394
+ ] }) : null
17395
+ ]
17396
+ },
17397
+ result.id + "-" + index
17398
+ );
17399
+ }) }),
17400
+ itemsFound > results.length ? props.getViewAllHref ? /* @__PURE__ */ jsxRuntime.jsxs(
17401
+ "a",
17339
17402
  {
17340
- className: "propeller-search-bar__result flex items-center gap-4 p-3 hover:bg-surface-hover cursor-pointer border-b border-border-subtle last:border-b-0",
17341
- onClick: (event) => handleResultClick(result),
17403
+ href: props.getViewAllHref(localTerm),
17404
+ className: "propeller-search-bar__view-all block flex-shrink-0 p-3 text-center text-primary hover:bg-primary/5 cursor-pointer font-semibold border-t border-border bg-card rounded-b-container",
17405
+ onClick: (event) => {
17406
+ if (isModifiedClick(event)) return;
17407
+ event.preventDefault();
17408
+ handleViewAllClick();
17409
+ },
17342
17410
  children: [
17343
- result.imageUrl || noImageUrl() ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-search-bar__result-media relative w-16 h-16 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
17344
- "img",
17345
- {
17346
- className: "propeller-search-bar__result-image w-full h-full object-contain",
17347
- src: result.imageUrl || noImageUrl(),
17348
- alt: result.name
17349
- }
17350
- ) }) : null,
17351
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
17352
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-search-bar__result-name font-semibold truncate", children: result.name }),
17353
- result.sku ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-search-bar__result-sku text-sm text-muted-foreground", children: [
17354
- "SKU: ",
17355
- result.sku
17356
- ] }) : null
17357
- ] }),
17358
- result.price !== void 0 && result.price !== null ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-search-bar__result-price text-sm font-semibold text-foreground flex-shrink-0 text-right", children: [
17359
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-search-bar__result-price-value", children: formatItemPrice(leadingPrice(result)) }),
17360
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "propeller-search-bar__result-price-label block text-xs font-normal text-muted-foreground", children: priceTaxLabel() })
17361
- ] }) : null
17411
+ propellerV2CoreUi.getLabel(props.labels, "viewAll", "View all results"),
17412
+ " (",
17413
+ itemsFound,
17414
+ ")"
17362
17415
  ]
17363
- },
17364
- result.id + "-" + index
17365
- )) }),
17366
- itemsFound > results.length ? /* @__PURE__ */ jsxRuntime.jsxs(
17367
- "div",
17416
+ }
17417
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
17418
+ "button",
17368
17419
  {
17369
- className: "propeller-search-bar__view-all flex-shrink-0 p-3 text-center text-primary hover:bg-primary/5 cursor-pointer font-semibold border-t border-border bg-card rounded-b-container",
17420
+ type: "button",
17421
+ className: "propeller-search-bar__view-all block w-full flex-shrink-0 p-3 text-center text-primary hover:bg-primary/5 cursor-pointer font-semibold border-t border-border bg-card rounded-b-container",
17370
17422
  onClick: (event) => handleViewAllClick(),
17371
17423
  children: [
17372
17424
  propellerV2CoreUi.getLabel(props.labels, "viewAll", "View all results"),