@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/dist/index.d.cts CHANGED
@@ -1103,6 +1103,12 @@ interface UseOrdersOptions {
1103
1103
  orderStatuses?: string[];
1104
1104
  /** Fields the free-text `term` is matched against. */
1105
1105
  termFields?: OrderSearchFields[];
1106
+ /**
1107
+ * Seed the search/filter form on mount — e.g. rehydrated from the URL query
1108
+ * so a bookmarked/shared filtered view restores. The consumer is responsible
1109
+ * for the fetch: pass this and the first fetch uses the seeded criteria.
1110
+ */
1111
+ initialSearchForm?: OrderSearchForm;
1106
1112
  /** Portal configuration: image search/variant filters. */
1107
1113
  configuration?: {
1108
1114
  imageSearchFiltersGrid?: MediaImageProductSearchInput;
@@ -4727,6 +4733,19 @@ interface OrderListProps {
4727
4733
  enableSearch?: boolean;
4728
4734
  /** Fields enabled for searching (UI inputs) */
4729
4735
  searchFields?: string[];
4736
+ /**
4737
+ * Seed the filter form on mount — typically rehydrated from the URL query so
4738
+ * a bookmarked/shared filtered view restores on reload. Pair with
4739
+ * `onSearchApply` so the page can keep the URL in sync when filters change.
4740
+ */
4741
+ initialSearchForm?: OrderSearchForm;
4742
+ /**
4743
+ * Fires when the user applies or clears filters (the "Search"/"Clear"
4744
+ * buttons). Receives the active filter form. Use it to persist the filters
4745
+ * to the URL — the component owns no router, so the page decides how (and
4746
+ * whether) to reflect them in `location`.
4747
+ */
4748
+ onSearchApply?: (form: OrderSearchForm) => void;
4730
4749
  /** Term fields configuration (backend) */
4731
4750
  termFields?: any[];
4732
4751
  /** Override company ID for order filtering (respects company switcher) */
@@ -6591,6 +6610,35 @@ interface SearchBarProps {
6591
6610
  onResultClick?: (result: SearchBarResult) => void;
6592
6611
  /** Fires when "View all results" is clicked. Receives the search term. */
6593
6612
  onViewAllClick?: (term: string) => void;
6613
+ /**
6614
+ * Build the destination URL for a result item. When provided, each result
6615
+ * renders as a real `<a href>` (middle-clickable, new-tab, crawlable) while
6616
+ * `onResultClick` still fires for SPA navigation. Omit to keep the
6617
+ * div-based fallback.
6618
+ */
6619
+ getResultHref?: (result: SearchBarResult) => string;
6620
+ /**
6621
+ * Build the destination URL for the "View all results" CTA. When provided,
6622
+ * the CTA renders as a real `<a href>` (middle-clickable, new-tab, crawlable,
6623
+ * keyboard-focusable) while `onViewAllClick` still fires for SPA navigation.
6624
+ * Omit to keep the div-based fallback.
6625
+ */
6626
+ getViewAllHref?: (term: string) => string;
6627
+ /**
6628
+ * Show the price column on each autosuggest result. Defaults to `true`.
6629
+ * Set `false` to hide prices entirely in the dropdown — e.g. a B2B
6630
+ * contract-catalogue context where prices are quote-only and shouldn't
6631
+ * appear in the live preview. Ignored when {@link renderPrice} is provided.
6632
+ */
6633
+ showPrice?: boolean;
6634
+ /**
6635
+ * Replace the price cell of each result with custom content. When provided,
6636
+ * this fully overrides the default price rendering (and `showPrice`) — return
6637
+ * any node (e.g. a "Price by quotation" label for a contract catalogue), or
6638
+ * `null` to render nothing for that result. Keeps price/quotation wording
6639
+ * app-specific rather than baked into the package.
6640
+ */
6641
+ renderPrice?: (result: SearchBarResult) => React$1.ReactNode;
6594
6642
  /** Custom price formatting function */
6595
6643
  formatPrice?: (price: number) => string;
6596
6644
  /** Currency symbol for prices. Resolved from PropellerProvider when omitted; defaults to '€'. */
package/dist/index.d.ts CHANGED
@@ -1103,6 +1103,12 @@ interface UseOrdersOptions {
1103
1103
  orderStatuses?: string[];
1104
1104
  /** Fields the free-text `term` is matched against. */
1105
1105
  termFields?: OrderSearchFields[];
1106
+ /**
1107
+ * Seed the search/filter form on mount — e.g. rehydrated from the URL query
1108
+ * so a bookmarked/shared filtered view restores. The consumer is responsible
1109
+ * for the fetch: pass this and the first fetch uses the seeded criteria.
1110
+ */
1111
+ initialSearchForm?: OrderSearchForm;
1106
1112
  /** Portal configuration: image search/variant filters. */
1107
1113
  configuration?: {
1108
1114
  imageSearchFiltersGrid?: MediaImageProductSearchInput;
@@ -4727,6 +4733,19 @@ interface OrderListProps {
4727
4733
  enableSearch?: boolean;
4728
4734
  /** Fields enabled for searching (UI inputs) */
4729
4735
  searchFields?: string[];
4736
+ /**
4737
+ * Seed the filter form on mount — typically rehydrated from the URL query so
4738
+ * a bookmarked/shared filtered view restores on reload. Pair with
4739
+ * `onSearchApply` so the page can keep the URL in sync when filters change.
4740
+ */
4741
+ initialSearchForm?: OrderSearchForm;
4742
+ /**
4743
+ * Fires when the user applies or clears filters (the "Search"/"Clear"
4744
+ * buttons). Receives the active filter form. Use it to persist the filters
4745
+ * to the URL — the component owns no router, so the page decides how (and
4746
+ * whether) to reflect them in `location`.
4747
+ */
4748
+ onSearchApply?: (form: OrderSearchForm) => void;
4730
4749
  /** Term fields configuration (backend) */
4731
4750
  termFields?: any[];
4732
4751
  /** Override company ID for order filtering (respects company switcher) */
@@ -6591,6 +6610,35 @@ interface SearchBarProps {
6591
6610
  onResultClick?: (result: SearchBarResult) => void;
6592
6611
  /** Fires when "View all results" is clicked. Receives the search term. */
6593
6612
  onViewAllClick?: (term: string) => void;
6613
+ /**
6614
+ * Build the destination URL for a result item. When provided, each result
6615
+ * renders as a real `<a href>` (middle-clickable, new-tab, crawlable) while
6616
+ * `onResultClick` still fires for SPA navigation. Omit to keep the
6617
+ * div-based fallback.
6618
+ */
6619
+ getResultHref?: (result: SearchBarResult) => string;
6620
+ /**
6621
+ * Build the destination URL for the "View all results" CTA. When provided,
6622
+ * the CTA renders as a real `<a href>` (middle-clickable, new-tab, crawlable,
6623
+ * keyboard-focusable) while `onViewAllClick` still fires for SPA navigation.
6624
+ * Omit to keep the div-based fallback.
6625
+ */
6626
+ getViewAllHref?: (term: string) => string;
6627
+ /**
6628
+ * Show the price column on each autosuggest result. Defaults to `true`.
6629
+ * Set `false` to hide prices entirely in the dropdown — e.g. a B2B
6630
+ * contract-catalogue context where prices are quote-only and shouldn't
6631
+ * appear in the live preview. Ignored when {@link renderPrice} is provided.
6632
+ */
6633
+ showPrice?: boolean;
6634
+ /**
6635
+ * Replace the price cell of each result with custom content. When provided,
6636
+ * this fully overrides the default price rendering (and `showPrice`) — return
6637
+ * any node (e.g. a "Price by quotation" label for a contract catalogue), or
6638
+ * `null` to render nothing for that result. Keeps price/quotation wording
6639
+ * app-specific rather than baked into the package.
6640
+ */
6641
+ renderPrice?: (result: SearchBarResult) => React$1.ReactNode;
6594
6642
  /** Custom price formatting function */
6595
6643
  formatPrice?: (price: number) => string;
6596
6644
  /** Currency symbol for prices. Resolved from PropellerProvider when omitted; defaults to '€'. */
package/dist/index.js CHANGED
@@ -1698,7 +1698,9 @@ function useOrders(options) {
1698
1698
  const [orders, setOrders] = useState([]);
1699
1699
  const [loading, setLoading] = useState(false);
1700
1700
  const [error, setError] = useState(null);
1701
- const [searchForm, setSearchForm] = useState({});
1701
+ const [searchForm, setSearchForm] = useState(
1702
+ options.initialSearchForm ?? {}
1703
+ );
1702
1704
  const pagination = usePagination(options.itemsPerPage ?? 10);
1703
1705
  const fetchOrders = useCallback(
1704
1706
  async (page = 1) => {
@@ -2343,6 +2345,10 @@ function useProductSearch(options) {
2343
2345
  setSearchLoading(true);
2344
2346
  try {
2345
2347
  const service = createServices(graphqlClient).product;
2348
+ const orderlistScope = options.orderlistIds && options.orderlistIds.length > 0 ? {
2349
+ applyOrderlists: options.applyOrderlists !== false,
2350
+ orderlistIds: options.orderlistIds
2351
+ } : { applyOrderlists: false };
2346
2352
  const input = {
2347
2353
  term,
2348
2354
  language,
@@ -2354,6 +2360,7 @@ function useProductSearch(options) {
2354
2360
  ProductStatus.T,
2355
2361
  ProductStatus.S
2356
2362
  ],
2363
+ ...orderlistScope,
2357
2364
  sortInputs: [{ field: ProductSortField.RELEVANCE, order: SortOrder.DESC }],
2358
2365
  searchFields: [
2359
2366
  {
@@ -2400,7 +2407,8 @@ function useProductSearch(options) {
2400
2407
  }
2401
2408
  }, 300);
2402
2409
  },
2403
- [graphqlClient, language, configuration, userKey]
2410
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2411
+ [graphqlClient, language, configuration, userKey, JSON.stringify(options.orderlistIds ?? []), options.applyOrderlists]
2404
2412
  );
2405
2413
  useEffect(() => {
2406
2414
  if (!isControlled) fetchProducts();
@@ -3555,6 +3563,13 @@ function AccountIconAndMenu(rawProps) {
3555
3563
  setMenuOpen(false);
3556
3564
  if (props.onMenuItemClick) props.onMenuItemClick(href);
3557
3565
  }
3566
+ function handleMenuLinkClick(event, href) {
3567
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
3568
+ return;
3569
+ }
3570
+ event.preventDefault();
3571
+ handleMenuItemClick(href);
3572
+ }
3558
3573
  function handleLogoutClick() {
3559
3574
  setMenuOpen(false);
3560
3575
  if (props.onLogoutClick) props.onLogoutClick();
@@ -3603,10 +3618,10 @@ function AccountIconAndMenu(rawProps) {
3603
3618
  /* @__PURE__ */ jsx("p", { className: "propeller-account-menu__user-name font-medium text-foreground truncate", children: getUserName() })
3604
3619
  ] }),
3605
3620
  /* @__PURE__ */ jsx("nav", { className: "propeller-account-menu__nav py-2", children: /* @__PURE__ */ jsx("ul", { className: "propeller-account-menu__list space-y-0.5", children: getMenuLinks()?.map((link) => /* @__PURE__ */ jsx("li", { className: "propeller-account-menu__item", children: /* @__PURE__ */ jsx(
3606
- "button",
3621
+ "a",
3607
3622
  {
3608
- type: "button",
3609
- onClick: (event) => handleMenuItemClick(link.href),
3623
+ href: link.href,
3624
+ onClick: (event) => handleMenuLinkClick(event, link.href),
3610
3625
  "data-active": isActiveLink(link.href) ? "true" : "false",
3611
3626
  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"}`,
3612
3627
  children: link.label
@@ -3669,11 +3684,11 @@ function AccountIconAndMenu(rawProps) {
3669
3684
  /* @__PURE__ */ jsx("p", { className: "propeller-account-menu__user-name font-medium text-foreground truncate", children: getUserName() })
3670
3685
  ] }),
3671
3686
  /* @__PURE__ */ jsx("nav", { className: "propeller-account-menu__nav", children: /* @__PURE__ */ jsx("ul", { className: "propeller-account-menu__list space-y-0.5", children: getMenuLinks()?.map((link) => /* @__PURE__ */ jsx("li", { className: "propeller-account-menu__item", children: /* @__PURE__ */ jsx(
3672
- "button",
3687
+ "a",
3673
3688
  {
3674
- type: "button",
3689
+ href: link.href,
3675
3690
  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",
3676
- onClick: (event) => handleMenuItemClick(link.href),
3691
+ onClick: (event) => handleMenuLinkClick(event, link.href),
3677
3692
  children: link.label
3678
3693
  }
3679
3694
  ) }, link.href)) }) }),
@@ -11830,7 +11845,8 @@ function OrderList(rawProps) {
11830
11845
  orderStatuses: props.orderStatus,
11831
11846
  termFields: props.termFields,
11832
11847
  itemsPerPage: props.initialItemsPerPage || 10,
11833
- channelIds: props.channelIds
11848
+ channelIds: props.channelIds,
11849
+ initialSearchForm: props.initialSearchForm
11834
11850
  });
11835
11851
  function handlePageChange(newPage) {
11836
11852
  if (newPage >= 1 && newPage <= totalPages) {
@@ -12143,6 +12159,7 @@ function OrderList(rawProps) {
12143
12159
  onClick: (event) => {
12144
12160
  setSearchForm({});
12145
12161
  fetchOrders(1);
12162
+ props.onSearchApply?.({});
12146
12163
  },
12147
12164
  children: getLabel(props.labels, "clearButton", "Clear")
12148
12165
  }
@@ -12151,7 +12168,10 @@ function OrderList(rawProps) {
12151
12168
  "button",
12152
12169
  {
12153
12170
  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",
12154
- onClick: (event) => fetchOrders(1),
12171
+ onClick: (event) => {
12172
+ fetchOrders(1);
12173
+ props.onSearchApply?.(searchForm);
12174
+ },
12155
12175
  children: getLabel(props.labels, "searchButton", "Search")
12156
12176
  }
12157
12177
  )
@@ -12171,7 +12191,7 @@ function OrderList(rawProps) {
12171
12191
  /* @__PURE__ */ jsx("tbody", { className: "propeller-order-list__tbody bg-card divide-y divide-border", children: orders?.map((order) => /* @__PURE__ */ jsx(
12172
12192
  "tr",
12173
12193
  {
12174
- className: "propeller-order-list__row hover:bg-surface-hover",
12194
+ className: `propeller-order-list__row hover:bg-surface-hover ${rowsClickable ? "cursor-pointer" : ""}`,
12175
12195
  "data-clickable": rowsClickable ? "true" : "false",
12176
12196
  onClick: (event) => rowsClickable && props.onOrderClick(order.id),
12177
12197
  children: columns?.map((col) => /* @__PURE__ */ jsxs(
@@ -17261,6 +17281,9 @@ function SearchBar(rawProps) {
17261
17281
  }
17262
17282
  setShowDropdown(false);
17263
17283
  }
17284
+ function isModifiedClick(event) {
17285
+ return event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
17286
+ }
17264
17287
  useEffect(() => {
17265
17288
  const listener = (e) => {
17266
17289
  if (containerRef.current && !containerRef.current.contains(e.target)) {
@@ -17314,39 +17337,68 @@ function SearchBar(rawProps) {
17314
17337
  ] }) }),
17315
17338
  showDropdown ? /* @__PURE__ */ 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: [
17316
17339
  results.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
17317
- /* @__PURE__ */ jsx("div", { className: "propeller-search-bar__results flex-1 overflow-y-auto", children: results?.map((result, index) => /* @__PURE__ */ jsxs(
17318
- "div",
17340
+ /* @__PURE__ */ jsx("div", { className: "propeller-search-bar__results flex-1 overflow-y-auto", children: results?.map((result, index) => {
17341
+ const ResultTag = props.getResultHref ? "a" : "div";
17342
+ const resultProps = props.getResultHref ? {
17343
+ href: props.getResultHref(result),
17344
+ onClick: (event) => {
17345
+ if (isModifiedClick(event)) return;
17346
+ event.preventDefault();
17347
+ handleResultClick(result);
17348
+ }
17349
+ } : { onClick: (_event) => handleResultClick(result) };
17350
+ return /* @__PURE__ */ jsxs(
17351
+ ResultTag,
17352
+ {
17353
+ 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",
17354
+ ...resultProps,
17355
+ children: [
17356
+ result.imageUrl || noImageUrl() ? /* @__PURE__ */ jsx("div", { className: "propeller-search-bar__result-media relative w-16 h-16 flex-shrink-0", children: /* @__PURE__ */ jsx(
17357
+ "img",
17358
+ {
17359
+ className: "propeller-search-bar__result-image w-full h-full object-contain",
17360
+ src: result.imageUrl || noImageUrl(),
17361
+ alt: result.name
17362
+ }
17363
+ ) }) : null,
17364
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
17365
+ /* @__PURE__ */ jsx("div", { className: "propeller-search-bar__result-name font-semibold truncate", children: result.name }),
17366
+ result.sku ? /* @__PURE__ */ jsxs("div", { className: "propeller-search-bar__result-sku text-sm text-muted-foreground", children: [
17367
+ "SKU: ",
17368
+ result.sku
17369
+ ] }) : null
17370
+ ] }),
17371
+ props.renderPrice ? props.renderPrice(result) : props.showPrice !== false && result.price !== void 0 && result.price !== null ? /* @__PURE__ */ jsxs("div", { className: "propeller-search-bar__result-price text-sm font-semibold text-foreground flex-shrink-0 text-right", children: [
17372
+ /* @__PURE__ */ jsx("span", { className: "propeller-search-bar__result-price-value", children: formatItemPrice(leadingPrice(result)) }),
17373
+ /* @__PURE__ */ jsx("span", { className: "propeller-search-bar__result-price-label block text-xs font-normal text-muted-foreground", children: priceTaxLabel() })
17374
+ ] }) : null
17375
+ ]
17376
+ },
17377
+ result.id + "-" + index
17378
+ );
17379
+ }) }),
17380
+ itemsFound > results.length ? props.getViewAllHref ? /* @__PURE__ */ jsxs(
17381
+ "a",
17319
17382
  {
17320
- 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",
17321
- onClick: (event) => handleResultClick(result),
17383
+ href: props.getViewAllHref(localTerm),
17384
+ 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",
17385
+ onClick: (event) => {
17386
+ if (isModifiedClick(event)) return;
17387
+ event.preventDefault();
17388
+ handleViewAllClick();
17389
+ },
17322
17390
  children: [
17323
- result.imageUrl || noImageUrl() ? /* @__PURE__ */ jsx("div", { className: "propeller-search-bar__result-media relative w-16 h-16 flex-shrink-0", children: /* @__PURE__ */ jsx(
17324
- "img",
17325
- {
17326
- className: "propeller-search-bar__result-image w-full h-full object-contain",
17327
- src: result.imageUrl || noImageUrl(),
17328
- alt: result.name
17329
- }
17330
- ) }) : null,
17331
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
17332
- /* @__PURE__ */ jsx("div", { className: "propeller-search-bar__result-name font-semibold truncate", children: result.name }),
17333
- result.sku ? /* @__PURE__ */ jsxs("div", { className: "propeller-search-bar__result-sku text-sm text-muted-foreground", children: [
17334
- "SKU: ",
17335
- result.sku
17336
- ] }) : null
17337
- ] }),
17338
- result.price !== void 0 && result.price !== null ? /* @__PURE__ */ jsxs("div", { className: "propeller-search-bar__result-price text-sm font-semibold text-foreground flex-shrink-0 text-right", children: [
17339
- /* @__PURE__ */ jsx("span", { className: "propeller-search-bar__result-price-value", children: formatItemPrice(leadingPrice(result)) }),
17340
- /* @__PURE__ */ jsx("span", { className: "propeller-search-bar__result-price-label block text-xs font-normal text-muted-foreground", children: priceTaxLabel() })
17341
- ] }) : null
17391
+ getLabel(props.labels, "viewAll", "View all results"),
17392
+ " (",
17393
+ itemsFound,
17394
+ ")"
17342
17395
  ]
17343
- },
17344
- result.id + "-" + index
17345
- )) }),
17346
- itemsFound > results.length ? /* @__PURE__ */ jsxs(
17347
- "div",
17396
+ }
17397
+ ) : /* @__PURE__ */ jsxs(
17398
+ "button",
17348
17399
  {
17349
- 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",
17400
+ type: "button",
17401
+ 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",
17350
17402
  onClick: (event) => handleViewAllClick(),
17351
17403
  children: [
17352
17404
  getLabel(props.labels, "viewAll", "View all results"),