@propeller-commerce/propeller-v2-react-ui 0.11.4 → 0.13.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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _propeller_commerce_propeller_v2_core_ui from '@propeller-commerce/propeller-v2-core-ui';
2
- import { Services, ShopMode, UserMode, PriceComponentProps, StockComponentProps, AddToCartComponentProps, ImageComponentProps, BadgesComponentProps, FavoriteComponentProps, ProductBundlesComponentProps, ProductBulkPricesComponentProps, ProductSurchargesComponentProps, AnyUser, Result } from '@propeller-commerce/propeller-v2-core-ui';
2
+ import { Services, ShopMode, UserMode, PriceComponentProps, StockComponentProps, AddToCartComponentProps, ImageComponentProps, BadgesComponentProps, FavoriteComponentProps, ProductBundlesComponentProps, ProductBulkPricesComponentProps, ProductSurchargesComponentProps, AnyUser, Result, Availability } from '@propeller-commerce/propeller-v2-core-ui';
3
3
  export * from '@propeller-commerce/propeller-v2-core-ui';
4
4
  import * as React$1 from 'react';
5
5
  import React__default, { ReactNode, ComponentType } from 'react';
@@ -1383,12 +1383,24 @@ interface UseProductSearchOptions {
1383
1383
  * without a contract still sees the full catalogue.
1384
1384
  */
1385
1385
  applyOrderlists?: boolean;
1386
+ /**
1387
+ * Attribute names to request per product, e.g. `['MPN']`. Unset returns the
1388
+ * first page of ALL attributes (12 per product), so products with more than
1389
+ * 12 silently lose the rest — name what you render.
1390
+ */
1391
+ productTrackAttributes?: string[];
1386
1392
  /** Attribute text filters applied to the category search. */
1387
1393
  textFilters?: ProductTextFilterInput[];
1388
1394
  /** Lower price bound filter. */
1389
1395
  priceFilterMin?: number;
1390
1396
  /** Upper price bound filter. */
1391
1397
  priceFilterMax?: number;
1398
+ /**
1399
+ * Stock buckets to filter by. Empty, unset, or both buckets sends no stock
1400
+ * filter. Filtering happens server-side, so `itemsFound` and the page count
1401
+ * describe the filtered set.
1402
+ */
1403
+ availability?: Availability[];
1392
1404
  /** Sort field; overrides the internal sort state when set. */
1393
1405
  sortField?: string;
1394
1406
  /** Sort direction (`'ASC'` / `'DESC'`); overrides internal state when set. */
@@ -4612,6 +4624,23 @@ interface GridFiltersProps {
4612
4624
  activePriceMin?: number;
4613
4625
  /** Currently active price filter max from URL state. */
4614
4626
  activePriceMax?: number;
4627
+ /**
4628
+ * Show the availability (stock) section. Defaults to false so hosts opt in
4629
+ * rather than gaining a new filter section on upgrade.
4630
+ *
4631
+ * The host should pass its own "show stock" setting here — a stock filter
4632
+ * is meaningless when no stock is displayed anywhere in the grid. When
4633
+ * truthy the section is still subject to the same semi-closed-portal
4634
+ * visibility rule as the price filter (hidden for anonymous users).
4635
+ */
4636
+ showAvailabilityFilter?: boolean;
4637
+ /**
4638
+ * Currently active availability buckets (URL-driven). Seeds the checkboxes
4639
+ * on the first render so a restored filtered URL renders them ticked.
4640
+ */
4641
+ activeAvailability?: Availability[];
4642
+ /** Called whenever an availability checkbox is toggled. */
4643
+ onAvailabilityChange?: (sel: Availability[]) => void;
4615
4644
  /**
4616
4645
  * When true, all checkboxes and price inputs are disabled.
4617
4646
  * Wire to ProductGrid's `onLoadingChange` to block rapid re-clicks while a fetch is in flight.
@@ -4796,6 +4825,16 @@ interface GridToolbarProps {
4796
4825
  * Called when the price filter badge × is clicked.
4797
4826
  */
4798
4827
  onPriceFilterRemove?: () => void;
4828
+ /**
4829
+ * Currently active availability (stock) filter selections.
4830
+ * Renders one removable chip per selected bucket.
4831
+ */
4832
+ availability?: Availability[];
4833
+ /**
4834
+ * Called when an availability filter badge × is clicked.
4835
+ * Receives the specific bucket to remove.
4836
+ */
4837
+ onAvailabilityFilterRemove?: (value: Availability) => void;
4799
4838
  /**
4800
4839
  * Called when "Clear All" is clicked.
4801
4840
  */
@@ -6146,6 +6185,13 @@ interface ProductGridProps {
6146
6185
  * still sees the full catalogue.
6147
6186
  */
6148
6187
  applyOrderlists?: boolean;
6188
+ /**
6189
+ * Attribute names to request per product, e.g. `['MPN']` — makes
6190
+ * `product.attributes` usable in card slots like `belowName`. Unset returns
6191
+ * the first page of ALL attributes (12 per product), so products with more
6192
+ * than 12 silently lose the rest.
6193
+ */
6194
+ productTrackAttributes?: string[];
6149
6195
  /** Number of columns in the grid. Accepts 2, 3, 4, 5, or 6. Defaults to 3. */
6150
6196
  columns?: number;
6151
6197
  /**
@@ -6182,6 +6228,11 @@ interface ProductGridProps {
6182
6228
  /** * Active text filters to apply — built by the parent from FiltersSidebar * `onFilterChange` callbacks. Each entry maps to a `textFilters` input * row in the CategoryService query. * When this prop changes the grid automatically re-fetches (page resets to 1). */ textFilters?: ProductTextFilterInput[];
6183
6229
  /** * Active price range lower bound from the FiltersSidebar `onPriceChange`. * Triggers a re-fetch when changed. */ priceFilterMin?: number;
6184
6230
  /** * Active price range upper bound from the FiltersSidebar `onPriceChange`. * Triggers a re-fetch when changed. */ priceFilterMax?: number;
6231
+ /**
6232
+ * Active stock buckets from the filters sidebar. Triggers a re-fetch when
6233
+ * changed. Empty or both buckets means no stock filter.
6234
+ */
6235
+ availability?: Availability[];
6185
6236
  /** * Called when sort state changes internally (for syncing a sibling toolbar). */ onSortChange?: (field: string, order: string) => void;
6186
6237
  /** * Called after each internal data fetch with the min/max price of the * current product set — use to populate a price range slider in the parent. */ onPriceBoundsChange?: (min: number, max: number) => void;
6187
6238
  /** * Called after each fetch with the total number of products found — * use to display a result count in the parent toolbar. */ onItemsFoundChange?: (count: number) => void;
@@ -7300,6 +7351,8 @@ interface SearchBarProps {
7300
7351
  * still sees the full catalogue.
7301
7352
  */
7302
7353
  applyOrderlists?: boolean;
7354
+ /** Attribute names to request per product, e.g. `['MPN']`. See ProductGrid. */
7355
+ productTrackAttributes?: string[];
7303
7356
  /**
7304
7357
  * Bump this counter to clear the search input from outside (e.g. on route
7305
7358
  * change). Each unique value triggers a one-time reset of the local term.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _propeller_commerce_propeller_v2_core_ui from '@propeller-commerce/propeller-v2-core-ui';
2
- import { Services, ShopMode, UserMode, PriceComponentProps, StockComponentProps, AddToCartComponentProps, ImageComponentProps, BadgesComponentProps, FavoriteComponentProps, ProductBundlesComponentProps, ProductBulkPricesComponentProps, ProductSurchargesComponentProps, AnyUser, Result } from '@propeller-commerce/propeller-v2-core-ui';
2
+ import { Services, ShopMode, UserMode, PriceComponentProps, StockComponentProps, AddToCartComponentProps, ImageComponentProps, BadgesComponentProps, FavoriteComponentProps, ProductBundlesComponentProps, ProductBulkPricesComponentProps, ProductSurchargesComponentProps, AnyUser, Result, Availability } from '@propeller-commerce/propeller-v2-core-ui';
3
3
  export * from '@propeller-commerce/propeller-v2-core-ui';
4
4
  import * as React$1 from 'react';
5
5
  import React__default, { ReactNode, ComponentType } from 'react';
@@ -1383,12 +1383,24 @@ interface UseProductSearchOptions {
1383
1383
  * without a contract still sees the full catalogue.
1384
1384
  */
1385
1385
  applyOrderlists?: boolean;
1386
+ /**
1387
+ * Attribute names to request per product, e.g. `['MPN']`. Unset returns the
1388
+ * first page of ALL attributes (12 per product), so products with more than
1389
+ * 12 silently lose the rest — name what you render.
1390
+ */
1391
+ productTrackAttributes?: string[];
1386
1392
  /** Attribute text filters applied to the category search. */
1387
1393
  textFilters?: ProductTextFilterInput[];
1388
1394
  /** Lower price bound filter. */
1389
1395
  priceFilterMin?: number;
1390
1396
  /** Upper price bound filter. */
1391
1397
  priceFilterMax?: number;
1398
+ /**
1399
+ * Stock buckets to filter by. Empty, unset, or both buckets sends no stock
1400
+ * filter. Filtering happens server-side, so `itemsFound` and the page count
1401
+ * describe the filtered set.
1402
+ */
1403
+ availability?: Availability[];
1392
1404
  /** Sort field; overrides the internal sort state when set. */
1393
1405
  sortField?: string;
1394
1406
  /** Sort direction (`'ASC'` / `'DESC'`); overrides internal state when set. */
@@ -4612,6 +4624,23 @@ interface GridFiltersProps {
4612
4624
  activePriceMin?: number;
4613
4625
  /** Currently active price filter max from URL state. */
4614
4626
  activePriceMax?: number;
4627
+ /**
4628
+ * Show the availability (stock) section. Defaults to false so hosts opt in
4629
+ * rather than gaining a new filter section on upgrade.
4630
+ *
4631
+ * The host should pass its own "show stock" setting here — a stock filter
4632
+ * is meaningless when no stock is displayed anywhere in the grid. When
4633
+ * truthy the section is still subject to the same semi-closed-portal
4634
+ * visibility rule as the price filter (hidden for anonymous users).
4635
+ */
4636
+ showAvailabilityFilter?: boolean;
4637
+ /**
4638
+ * Currently active availability buckets (URL-driven). Seeds the checkboxes
4639
+ * on the first render so a restored filtered URL renders them ticked.
4640
+ */
4641
+ activeAvailability?: Availability[];
4642
+ /** Called whenever an availability checkbox is toggled. */
4643
+ onAvailabilityChange?: (sel: Availability[]) => void;
4615
4644
  /**
4616
4645
  * When true, all checkboxes and price inputs are disabled.
4617
4646
  * Wire to ProductGrid's `onLoadingChange` to block rapid re-clicks while a fetch is in flight.
@@ -4796,6 +4825,16 @@ interface GridToolbarProps {
4796
4825
  * Called when the price filter badge × is clicked.
4797
4826
  */
4798
4827
  onPriceFilterRemove?: () => void;
4828
+ /**
4829
+ * Currently active availability (stock) filter selections.
4830
+ * Renders one removable chip per selected bucket.
4831
+ */
4832
+ availability?: Availability[];
4833
+ /**
4834
+ * Called when an availability filter badge × is clicked.
4835
+ * Receives the specific bucket to remove.
4836
+ */
4837
+ onAvailabilityFilterRemove?: (value: Availability) => void;
4799
4838
  /**
4800
4839
  * Called when "Clear All" is clicked.
4801
4840
  */
@@ -6146,6 +6185,13 @@ interface ProductGridProps {
6146
6185
  * still sees the full catalogue.
6147
6186
  */
6148
6187
  applyOrderlists?: boolean;
6188
+ /**
6189
+ * Attribute names to request per product, e.g. `['MPN']` — makes
6190
+ * `product.attributes` usable in card slots like `belowName`. Unset returns
6191
+ * the first page of ALL attributes (12 per product), so products with more
6192
+ * than 12 silently lose the rest.
6193
+ */
6194
+ productTrackAttributes?: string[];
6149
6195
  /** Number of columns in the grid. Accepts 2, 3, 4, 5, or 6. Defaults to 3. */
6150
6196
  columns?: number;
6151
6197
  /**
@@ -6182,6 +6228,11 @@ interface ProductGridProps {
6182
6228
  /** * Active text filters to apply — built by the parent from FiltersSidebar * `onFilterChange` callbacks. Each entry maps to a `textFilters` input * row in the CategoryService query. * When this prop changes the grid automatically re-fetches (page resets to 1). */ textFilters?: ProductTextFilterInput[];
6183
6229
  /** * Active price range lower bound from the FiltersSidebar `onPriceChange`. * Triggers a re-fetch when changed. */ priceFilterMin?: number;
6184
6230
  /** * Active price range upper bound from the FiltersSidebar `onPriceChange`. * Triggers a re-fetch when changed. */ priceFilterMax?: number;
6231
+ /**
6232
+ * Active stock buckets from the filters sidebar. Triggers a re-fetch when
6233
+ * changed. Empty or both buckets means no stock filter.
6234
+ */
6235
+ availability?: Availability[];
6185
6236
  /** * Called when sort state changes internally (for syncing a sibling toolbar). */ onSortChange?: (field: string, order: string) => void;
6186
6237
  /** * Called after each internal data fetch with the min/max price of the * current product set — use to populate a price range slider in the parent. */ onPriceBoundsChange?: (min: number, max: number) => void;
6187
6238
  /** * Called after each fetch with the total number of products found — * use to display a result count in the parent toolbar. */ onItemsFoundChange?: (count: number) => void;
@@ -7300,6 +7351,8 @@ interface SearchBarProps {
7300
7351
  * still sees the full catalogue.
7301
7352
  */
7302
7353
  applyOrderlists?: boolean;
7354
+ /** Attribute names to request per product, e.g. `['MPN']`. See ProductGrid. */
7355
+ productTrackAttributes?: string[];
7303
7356
  /**
7304
7357
  * Bump this counter to clear the search input from outside (e.g. on route
7305
7358
  * change). Each unique value triggers a one-time reset of the local term.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { getLabel, getLocalizedValue, formatPrice, getClusterSku, getClusterImageUrl, isContentHidden, getProductSku, getProductImageUrl, createServices, err, ok, formatSurcharge, deriveUserMode, isContact, isCustomer, getAddresses, attributeNameMatches, getAttributeDisplayName, filterProductsBySelections, extractAttributeValues, getLanguageString, collectAttributeValues, getCountryName } from '@propeller-commerce/propeller-v2-core-ui';
2
+ import { getLabel, getLocalizedValue, formatPrice, getClusterSku, getClusterImageUrl, isContentHidden, getProductSku, getProductImageUrl, createServices, err, ok, formatSurcharge, deriveUserMode, isContact, isCustomer, getAddresses, attributeNameMatches, getAttributeDisplayName, filterProductsBySelections, extractAttributeValues, buildInventoryFilter, getLanguageString, collectAttributeValues, getCountryName } from '@propeller-commerce/propeller-v2-core-ui';
3
3
  export * from '@propeller-commerce/propeller-v2-core-ui';
4
4
  import * as React6 from 'react';
5
5
  import { createContext, useState, useRef, useMemo, useCallback, useContext, useEffect } from 'react';
@@ -2070,7 +2070,7 @@ function useProductInfo(options) {
2070
2070
  if (options.user && "customerId" in options.user) input.customerId = options.user.customerId;
2071
2071
  return input;
2072
2072
  }
2073
- function buildAttributeInput() {
2073
+ function buildAttributeInput2() {
2074
2074
  const names = options.productTrackAttributes;
2075
2075
  if (!names || names.length === 0) return void 0;
2076
2076
  return { attributeDescription: { names } };
@@ -2093,7 +2093,7 @@ function useProductInfo(options) {
2093
2093
  orderlistIds = (orderlists?.items ?? []).map((ol) => ol.id);
2094
2094
  }
2095
2095
  const service = createServices(graphqlClient).product;
2096
- const attributeInput = buildAttributeInput();
2096
+ const attributeInput = buildAttributeInput2();
2097
2097
  const variables = {
2098
2098
  productId,
2099
2099
  language,
@@ -2183,6 +2183,10 @@ function useProductInfo(options) {
2183
2183
  };
2184
2184
  }
2185
2185
  var inflightFetches2 = /* @__PURE__ */ new Set();
2186
+ function buildAttributeInput(names) {
2187
+ if (!names || names.length === 0) return void 0;
2188
+ return { attributeDescription: { names } };
2189
+ }
2186
2190
  function useProductSearch(options) {
2187
2191
  const { graphqlClient, configuration } = options;
2188
2192
  const isControlled = options.products !== void 0;
@@ -2237,7 +2241,8 @@ function useProductSearch(options) {
2237
2241
  userKey,
2238
2242
  JSON.stringify(options.textFilters ?? []),
2239
2243
  options.priceFilterMin ?? "",
2240
- options.priceFilterMax ?? ""
2244
+ options.priceFilterMax ?? "",
2245
+ JSON.stringify(options.availability ?? [])
2241
2246
  ].join("|");
2242
2247
  if (inflightFetches2.has(inflightKey)) {
2243
2248
  fetchIdRef.current--;
@@ -2279,6 +2284,7 @@ function useProductSearch(options) {
2279
2284
  }
2280
2285
  ] : [];
2281
2286
  const priceFilter = options.priceFilterMin !== void 0 || options.priceFilterMax !== void 0 ? { from: options.priceFilterMin ?? 0, to: options.priceFilterMax ?? 999999 } : void 0;
2287
+ const inventoryFilter = buildInventoryFilter(options.availability);
2282
2288
  const userId = options.user && "contactId" in options.user ? options.user.contactId : options.user && "customerId" in options.user ? options.user.customerId : void 0;
2283
2289
  const contactId = options.user && "contactId" in options.user ? options.user.contactId : void 0;
2284
2290
  const customerId = options.user && "customerId" in options.user ? options.user.customerId : void 0;
@@ -2301,6 +2307,7 @@ function useProductSearch(options) {
2301
2307
  ...options.brand && { manufacturers: [options.brand] },
2302
2308
  ...options.textFilters?.length && { textFilters: options.textFilters },
2303
2309
  ...priceFilter && { price: priceFilter },
2310
+ ...inventoryFilter && { inventory: inventoryFilter },
2304
2311
  ...sortInputs.length && { sortInputs },
2305
2312
  ...options.companyId && { companyId: options.companyId },
2306
2313
  ...userId !== void 0 && { userId },
@@ -2315,6 +2322,7 @@ function useProductSearch(options) {
2315
2322
  const filterAvailableAttributeInput = {
2316
2323
  isSearchable: true
2317
2324
  };
2325
+ const attributeInput = buildAttributeInput(options.productTrackAttributes);
2318
2326
  const variables = {
2319
2327
  categoryId: catId,
2320
2328
  language: lang,
@@ -2322,7 +2330,8 @@ function useProductSearch(options) {
2322
2330
  priceCalculateProductInput,
2323
2331
  filterAvailableAttributeInput,
2324
2332
  imageSearchFilters: configuration?.imageSearchFiltersGrid,
2325
- imageVariantFilters: configuration?.imageVariantFiltersMedium
2333
+ imageVariantFilters: configuration?.imageVariantFiltersMedium,
2334
+ ...attributeInput && { attributeResultSearchInput: attributeInput }
2326
2335
  };
2327
2336
  const response = await service.getCategory(variables);
2328
2337
  if (thisId !== fetchIdRef.current) return;
@@ -2375,6 +2384,7 @@ function useProductSearch(options) {
2375
2384
  options.textFilters,
2376
2385
  options.priceFilterMin,
2377
2386
  options.priceFilterMax,
2387
+ options.availability,
2378
2388
  options.sortField,
2379
2389
  options.sortOrder,
2380
2390
  options.companyId,
@@ -2383,7 +2393,9 @@ function useProductSearch(options) {
2383
2393
  taxZone,
2384
2394
  pageSize,
2385
2395
  pagination.currentPage,
2386
- configuration
2396
+ configuration,
2397
+ // Stringified: callers pass an array literal, so identity changes each render.
2398
+ JSON.stringify(options.productTrackAttributes ?? [])
2387
2399
  ]);
2388
2400
  const search = useCallback(
2389
2401
  (term) => {
@@ -2412,6 +2424,7 @@ function useProductSearch(options) {
2412
2424
  const userId = options.user && "contactId" in options.user ? options.user.contactId : options.user && "customerId" in options.user ? options.user.customerId : void 0;
2413
2425
  const contactId = options.user && "contactId" in options.user ? options.user.contactId : void 0;
2414
2426
  const customerId = options.user && "customerId" in options.user ? options.user.customerId : void 0;
2427
+ const inventoryFilter = buildInventoryFilter(options.availability);
2415
2428
  const categoryProductSearchInput = {
2416
2429
  language,
2417
2430
  page: 1,
@@ -2423,6 +2436,7 @@ function useProductSearch(options) {
2423
2436
  ProductStatus.S
2424
2437
  ],
2425
2438
  hidden: false,
2439
+ ...inventoryFilter && { inventory: inventoryFilter },
2426
2440
  term,
2427
2441
  searchFields: [
2428
2442
  {
@@ -2462,13 +2476,15 @@ function useProductSearch(options) {
2462
2476
  ...contactId !== void 0 && { contactId },
2463
2477
  ...customerId !== void 0 && { customerId }
2464
2478
  };
2479
+ const attributeInput = buildAttributeInput(options.productTrackAttributes);
2465
2480
  const variables = {
2466
2481
  categoryId: catId,
2467
2482
  language,
2468
2483
  categoryProductSearchInput,
2469
2484
  priceCalculateProductInput,
2470
2485
  imageSearchFilters: configuration?.imageSearchFiltersGrid,
2471
- imageVariantFilters: configuration?.imageVariantFiltersMedium
2486
+ imageVariantFilters: configuration?.imageVariantFiltersMedium,
2487
+ ...attributeInput && { attributeResultSearchInput: attributeInput }
2472
2488
  };
2473
2489
  const response = await service.getCategory(variables);
2474
2490
  const productsResponse = response?.products;
@@ -2487,7 +2503,7 @@ function useProductSearch(options) {
2487
2503
  }, 300);
2488
2504
  },
2489
2505
  // eslint-disable-next-line react-hooks/exhaustive-deps
2490
- [graphqlClient, language, configuration, userKey, JSON.stringify(options.orderlistIds ?? []), options.applyOrderlists]
2506
+ [graphqlClient, language, configuration, userKey, JSON.stringify(options.orderlistIds ?? []), options.applyOrderlists, JSON.stringify(options.productTrackAttributes ?? [])]
2491
2507
  );
2492
2508
  useEffect(() => {
2493
2509
  if (!isControlled) fetchProducts();
@@ -2500,6 +2516,7 @@ function useProductSearch(options) {
2500
2516
  JSON.stringify(options.textFilters),
2501
2517
  options.priceFilterMin,
2502
2518
  options.priceFilterMax,
2519
+ options.availability,
2503
2520
  options.sortField,
2504
2521
  options.sortOrder,
2505
2522
  pageSize,
@@ -9014,6 +9031,7 @@ function ProductGrid(rawProps) {
9014
9031
  brand: props.brand,
9015
9032
  orderlistIds: props.orderlistIds,
9016
9033
  applyOrderlists: props.applyOrderlists,
9034
+ productTrackAttributes: props.productTrackAttributes,
9017
9035
  language: props.language,
9018
9036
  taxZone: props.taxZone,
9019
9037
  user: props.user,
@@ -9021,6 +9039,7 @@ function ProductGrid(rawProps) {
9021
9039
  textFilters: props.textFilters,
9022
9040
  priceFilterMin: props.priceFilterMin,
9023
9041
  priceFilterMax: props.priceFilterMax,
9042
+ availability: props.availability,
9024
9043
  sortField: props.sortField,
9025
9044
  sortOrder: props.sortOrder,
9026
9045
  pageSize: props.pageSize,
@@ -9323,11 +9342,18 @@ function GridFilters(rawProps) {
9323
9342
  const [isPending, setIsPending] = useState(() => false);
9324
9343
  const [appliedMin, setAppliedMin] = useState(() => void 0);
9325
9344
  const [appliedMax, setAppliedMax] = useState(() => void 0);
9345
+ const [availability, setAvailability] = useState(
9346
+ () => [...props.activeAvailability || []]
9347
+ );
9326
9348
  function showPriceFilter() {
9327
9349
  const mode = props.portalMode || "open";
9328
9350
  if (mode === "open") return true;
9329
9351
  return !!props.user;
9330
9352
  }
9353
+ function showAvailability() {
9354
+ if (!props.showAvailabilityFilter) return false;
9355
+ return !isContentHidden(props.portalMode, props.user);
9356
+ }
9331
9357
  function getFilterName(filter) {
9332
9358
  return filter?.attributeDescription?.name || "";
9333
9359
  }
@@ -9372,6 +9398,13 @@ function GridFilters(rawProps) {
9372
9398
  props.onFilterChange(filter, value);
9373
9399
  if (props.getSelectedFilters) props.getSelectedFilters();
9374
9400
  }
9401
+ function handleAvailabilityToggle(value, checked) {
9402
+ const next = checked ? [...availability, value] : availability.filter((v) => v !== value);
9403
+ setAvailability(next);
9404
+ setIsPending(true);
9405
+ if (props.onAvailabilityChange) props.onAvailabilityChange(next);
9406
+ if (props.getSelectedFilters) props.getSelectedFilters();
9407
+ }
9375
9408
  function handleMinChange(value) {
9376
9409
  const n = value > currentMax ? currentMax : value;
9377
9410
  setCurrentMin(n);
@@ -9445,11 +9478,16 @@ function GridFilters(rawProps) {
9445
9478
  setCurrentMin(props.priceMin || 0);
9446
9479
  setCurrentMax(props.priceMax || 9999);
9447
9480
  setExpandedFilters({});
9481
+ setAvailability([]);
9448
9482
  }, [props.clearSignal, props.priceMin, props.priceMax]);
9449
9483
  useEffect(() => {
9450
9484
  if (!props.activeTextFilters) return;
9451
9485
  setSelectedFilters(props.activeTextFilters);
9452
9486
  }, [props.activeTextFilters]);
9487
+ useEffect(() => {
9488
+ if (!props.activeAvailability) return;
9489
+ setAvailability([...props.activeAvailability]);
9490
+ }, [props.activeAvailability]);
9453
9491
  useEffect(() => {
9454
9492
  if (props.activePriceMin === void 0 && props.activePriceMax === void 0) {
9455
9493
  setCurrentMin(props.priceMin || 0);
@@ -9549,6 +9587,40 @@ function GridFilters(rawProps) {
9549
9587
  " ",
9550
9588
  /* @__PURE__ */ jsx("div", { className: "propeller-grid-filters__divider h-px bg-border-subtle" })
9551
9589
  ] }) : null,
9590
+ showAvailability() ? /* @__PURE__ */ jsxs(Fragment, { children: [
9591
+ /* @__PURE__ */ jsxs("div", { className: "propeller-grid-filters__availability space-y-3", children: [
9592
+ /* @__PURE__ */ jsx("h3", { className: "propeller-grid-filters__availability-title text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: getLabel(props.labels, "availability", "Availability") }),
9593
+ /* @__PURE__ */ jsxs("div", { className: "propeller-grid-filters__availability-options space-y-1.5", children: [
9594
+ /* @__PURE__ */ jsxs("label", { className: "propeller-grid-filters__option flex items-center gap-2 cursor-pointer group", children: [
9595
+ /* @__PURE__ */ jsx(
9596
+ "input",
9597
+ {
9598
+ type: "checkbox",
9599
+ className: "propeller-grid-filters__checkbox h-4 w-4 rounded border-input text-secondary focus:ring-secondary cursor-pointer flex-shrink-0",
9600
+ checked: availability.includes("in-stock"),
9601
+ disabled: !!props.isLoading,
9602
+ onChange: (e) => handleAvailabilityToggle("in-stock", e.target.checked)
9603
+ }
9604
+ ),
9605
+ /* @__PURE__ */ jsx("span", { className: "propeller-grid-filters__option-label flex-1 text-sm text-muted-foreground leading-none select-none group-hover:text-foreground", children: getLabel(props.labels, "inStock", "In stock") })
9606
+ ] }),
9607
+ /* @__PURE__ */ jsxs("label", { className: "propeller-grid-filters__option flex items-center gap-2 cursor-pointer group", children: [
9608
+ /* @__PURE__ */ jsx(
9609
+ "input",
9610
+ {
9611
+ type: "checkbox",
9612
+ className: "propeller-grid-filters__checkbox h-4 w-4 rounded border-input text-secondary focus:ring-secondary cursor-pointer flex-shrink-0",
9613
+ checked: availability.includes("out-of-stock"),
9614
+ disabled: !!props.isLoading,
9615
+ onChange: (e) => handleAvailabilityToggle("out-of-stock", e.target.checked)
9616
+ }
9617
+ ),
9618
+ /* @__PURE__ */ jsx("span", { className: "propeller-grid-filters__option-label flex-1 text-sm text-muted-foreground leading-none select-none group-hover:text-foreground", children: getLabel(props.labels, "outOfStock", "Out of stock") })
9619
+ ] })
9620
+ ] })
9621
+ ] }),
9622
+ /* @__PURE__ */ jsx("div", { className: "propeller-grid-filters__divider h-px bg-border-subtle" })
9623
+ ] }) : null,
9552
9624
  props.filters.length === 0 ? /* @__PURE__ */ jsx("p", { className: "propeller-grid-filters__empty text-sm text-foreground-subtle italic", children: getLabel(props.labels, "noFiltersAvailable", "No filters available") }) : null,
9553
9625
  getFilteredFilters()?.map((filter) => /* @__PURE__ */ jsxs("div", { className: "propeller-grid-filters__group border-b border-border-subtle pb-3 last:border-b-0", "data-expanded": isExpanded(getFilterName(filter)) ? "true" : "false", children: [
9554
9626
  /* @__PURE__ */ jsxs(
@@ -9724,6 +9796,8 @@ var DEFAULT_LABELS = {
9724
9796
  results: "results",
9725
9797
  perPage: " per page",
9726
9798
  price: "Price",
9799
+ inStock: "In stock",
9800
+ outOfStock: "Out of stock",
9727
9801
  switchToList: "Switch to list view",
9728
9802
  switchToGrid: "Switch to grid view"
9729
9803
  };
@@ -9752,7 +9826,8 @@ function GridToolbar(rawProps) {
9752
9826
  const text = props.activeTextFilters || {};
9753
9827
  const hasText = Object.keys(text).some((k) => (text[k] || []).length > 0);
9754
9828
  const hasPrice = props.priceFilterMin !== void 0 || props.priceFilterMax !== void 0;
9755
- return hasText || hasPrice;
9829
+ const hasAvailability = (props.availability || []).length > 0;
9830
+ return hasText || hasPrice || hasAvailability;
9756
9831
  }
9757
9832
  function getActiveFilterBadges() {
9758
9833
  const text = props.activeTextFilters || {};
@@ -9770,6 +9845,9 @@ function GridToolbar(rawProps) {
9770
9845
  function isPriceSortDisabled() {
9771
9846
  return isContentHidden(props.portalMode, props.user);
9772
9847
  }
9848
+ function getAvailabilityLabel(value) {
9849
+ return value === "in-stock" ? getLabel56("inStock") : getLabel56("outOfStock");
9850
+ }
9773
9851
  function handleSortFieldChange(field) {
9774
9852
  setCurrentSortField(field);
9775
9853
  if (props.onSortChange) props.onSortChange(field, currentSortOrder);
@@ -9943,6 +10021,20 @@ function GridToolbar(rawProps) {
9943
10021
  ]
9944
10022
  }
9945
10023
  ) : null,
10024
+ (props.availability || []).map((value) => /* @__PURE__ */ jsxs(
10025
+ "span",
10026
+ {
10027
+ className: "propeller-grid-toolbar__filter-badge propeller-grid-toolbar__filter-badge--availability inline-flex items-center gap-1 cursor-pointer px-2.5 py-0.5 rounded-full text-xs font-semibold border border-input bg-background hover:bg-primary hover:text-primary-foreground hover:border-primary transition-colors",
10028
+ onClick: (event) => {
10029
+ if (props.onAvailabilityFilterRemove) props.onAvailabilityFilterRemove(value);
10030
+ },
10031
+ children: [
10032
+ getAvailabilityLabel(value),
10033
+ /* @__PURE__ */ jsx("span", { className: "propeller-grid-toolbar__filter-badge-remove", children: "\xD7" })
10034
+ ]
10035
+ },
10036
+ value
10037
+ )),
9946
10038
  getActiveFilterBadges()?.map((badge) => /* @__PURE__ */ jsxs(
9947
10039
  "span",
9948
10040
  {
@@ -18376,7 +18468,8 @@ function SearchBar(rawProps) {
18376
18468
  user: props.user,
18377
18469
  companyId: props.companyId,
18378
18470
  orderlistIds: props.orderlistIds,
18379
- applyOrderlists: props.applyOrderlists
18471
+ applyOrderlists: props.applyOrderlists,
18472
+ productTrackAttributes: props.productTrackAttributes
18380
18473
  });
18381
18474
  const results = searchResults.slice(0, maxResults).map((item) => mapToSearchBarResult(item, props.language));
18382
18475
  const itemsFound = searchItemsFound;