@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/dist/index.d.ts CHANGED
@@ -40,6 +40,14 @@ interface PropellerDeps {
40
40
  interface PropellerScope {
41
41
  /** The authenticated user, or `null` when browsing anonymously. */
42
42
  user: Contact | Customer | null;
43
+ /**
44
+ * Whether a session exists, independent of whether `user` has loaded yet.
45
+ * Hosts paint from a cached hint before the profile arrives, so `user` is
46
+ * null for an authenticated visitor for the first frames; without this,
47
+ * semi-closed surfaces flash their logged-out state. Optional — omitting it
48
+ * keeps the previous behaviour.
49
+ */
50
+ isAuthenticated?: boolean;
43
51
  /** Active company ID for the current session; `undefined` for non-company users. */
44
52
  companyId: number | undefined;
45
53
  /** Active language code (e.g. `'NL'`). */
@@ -588,7 +596,11 @@ interface UseCartReturn {
588
596
  loading: boolean;
589
597
  /** Last error message, or `null`. */
590
598
  error: string | null;
591
- /** `false` when a B2B purchaser's authorization limit is exceeded by the cart total. */
599
+ /**
600
+ * `false` when a B2B purchaser's authorization limit is exceeded by the cart
601
+ * total, and while a seeded `cartId` is still being hydrated — it fails
602
+ * closed, so gating a checkout button on it holds rather than opens.
603
+ */
592
604
  checkoutAllowed: boolean;
593
605
  /** Resolves an existing cart or creates one via the shared `initCart` flow. */
594
606
  resolveCart: () => Promise<Cart>;
@@ -1687,6 +1699,20 @@ interface UseSparePartsOptions {
1687
1699
  sortOrder?: SortOrder | string;
1688
1700
  /** Items per page. Defaults to 12. */
1689
1701
  pageSize?: number;
1702
+ /**
1703
+ * Controlled page. When provided the hook renders (and fetches) this page and
1704
+ * `goToPage` becomes advisory - the host owns the number, typically from the
1705
+ * URL.
1706
+ *
1707
+ * Every other listing input (term, textFilters, sortField, pageSize) was
1708
+ * already an option, so its absence read as "paging is internal" and it was
1709
+ * not: driving the hook from URL state and rendering GridPagination changed
1710
+ * the URL but re-rendered the same first page, because the only way in was a
1711
+ * `useEffect(() => goToPage(page))` that MachineGrid does internally and
1712
+ * nothing documented (PWP-995b). Omit it to keep the previous uncontrolled
1713
+ * behaviour.
1714
+ */
1715
+ page?: number;
1690
1716
  /** Image filter config, mirroring `useProductSearch`'s `configuration`. */
1691
1717
  configuration?: {
1692
1718
  /** The channel's anonymous user, seeded by the host. Scopes logged-out
@@ -2167,8 +2193,13 @@ interface ResolveSpecEntry {
2167
2193
  */
2168
2194
  transform?: (gridValue: NonNullable<ProductGridConfig[GridKey]>) => unknown;
2169
2195
  }
2170
- /** Per-prop resolution spec: maps each prop key to its {@link ResolveSpecEntry}. */
2171
- type ResolveSpec<P> = Partial<Record<keyof P, ResolveSpecEntry>>;
2196
+ /**
2197
+ * Per-prop resolution spec: maps each prop key to its {@link ResolveSpecEntry}.
2198
+ *
2199
+ * Infra keys are spec'able too, so a component can pull a provider value it
2200
+ * only reads internally without having to publish it as its own prop.
2201
+ */
2202
+ type ResolveSpec<P> = Partial<Record<keyof P | keyof PropellerInfra, ResolveSpecEntry>>;
2172
2203
  /**
2173
2204
  * useResolvedProps — resolves props through the two-tier precedence
2174
2205
  * `explicit prop > ProductGrid context > Propeller infra > default`.
@@ -2177,7 +2208,7 @@ type ResolveSpec<P> = Partial<Record<keyof P, ResolveSpecEntry>>;
2177
2208
  * @param spec - per-key resolution rules; keys absent from the spec pass through unchanged.
2178
2209
  * @returns the props with each spec'd key resolved to its highest-precedence value.
2179
2210
  */
2180
- declare function useResolvedProps<P extends object>(rawProps: P, spec: ResolveSpec<P>): P;
2211
+ declare function useResolvedProps<P extends object>(rawProps: P, spec: ResolveSpec<P>): P & Partial<PropellerInfra>;
2181
2212
 
2182
2213
  /**
2183
2214
  * Read the SDK services bundle from `<PropellerDepsProvider>`.
@@ -3482,13 +3513,17 @@ interface CartOverviewProps {
3482
3513
  code: string;
3483
3514
  name: string;
3484
3515
  }[];
3516
+ /** Logged-in user — used for the purchase-authorization check. Resolved from PropellerProvider when omitted. */
3517
+ user?: Contact | Customer | null;
3518
+ /** Active company ID — used for the purchase-authorization check. Resolved from PropellerProvider when omitted. */
3519
+ companyId?: number;
3485
3520
  }
3486
3521
  /**
3487
3522
  * Final cart review panel: shows invoice/delivery addresses, payment, carrier
3488
3523
  * and delivery date, with optional reference, notes, terms acceptance and a
3489
3524
  * place-order button.
3490
3525
  */
3491
- declare function CartOverview(props: CartOverviewProps): React$1.JSX.Element;
3526
+ declare function CartOverview(rawProps: CartOverviewProps): React$1.JSX.Element;
3492
3527
 
3493
3528
  /**
3494
3529
  * @rsc-blocked — Client-only component: interactive state (useState/useReducer).
@@ -3898,14 +3933,22 @@ interface MachineCardProps {
3898
3933
  * URL is its ancestor path (`/machines/a/b/c`), which only the host route
3899
3934
  * knows; the machine object carries just its own slug.
3900
3935
  */
3901
- href: string;
3936
+ href?: string;
3902
3937
  /** Show the machine image. Defaults to true. */
3903
3938
  showImage?: boolean;
3904
3939
  /** Show the machine description under the name. Defaults to false. */
3905
3940
  showDescription?: boolean;
3906
3941
  /** Language used to resolve the localized name/description. */
3907
3942
  language?: string;
3908
- /** UI label overrides. Supported key: `viewMachine`. */
3943
+ /**
3944
+ * UI label overrides. Key read here: `viewMachine`.
3945
+ *
3946
+ * `MachineGrid` passes its own `machineCardLabels` straight through to this
3947
+ * prop and additionally reads `loading` and `noMachines` from it for its own
3948
+ * empty/loading states, so the object a grid host supplies carries all three.
3949
+ * This doc used to name only `viewMachine`, which left the other two in
3950
+ * English for anyone who followed it (PWP-995d).
3951
+ */
3909
3952
  labels?: Record<string, string>;
3910
3953
  /** Extra classes on the card root. */
3911
3954
  className?: string;
@@ -3923,216 +3966,514 @@ interface MachineCardProps {
3923
3966
  declare function MachineCard(props: MachineCardProps): React$1.JSX.Element;
3924
3967
 
3925
3968
  /**
3926
- * @rsc-blocked — Client-only component: hooks + effects (via useSpareParts /
3927
- * useMachines) and event handlers. Render inside a Client boundary.
3928
- */
3929
- /**
3930
- * MachineGrid — the spare-parts machine tree, as one self-contained grid.
3931
- *
3932
- * The machine-tree sibling of `ProductGrid`. Driven by the current URL path
3933
- * (`segments`), it renders one of two modes:
3934
- *
3935
- * - **Root** (`segments` empty): resolves the company's installations in ONE
3936
- * concatenated request via `useMachines(source, sourceIds)` and renders them
3937
- * as `MachineCard`s.
3938
- * - **Node** (`segments` non-empty): fetches that machine by its leaf slug via
3939
- * `useSpareParts`, and renders its child machines (`MachineCard`s) above a
3940
- * category-style spare-parts listing (facets + toolbar + a permanently
3941
- * controlled `ProductGrid` + pagination, with the qty-in-machine `belowName`).
3942
- *
3943
- * Navigation between levels is via `MachineCard`'s `href` (built from `basePath`
3944
- * + segments + child slug) — the package owns no router. The parts listing is
3945
- * **controlled**: the current state comes in via `listing`, and every filter /
3946
- * sort / page / search interaction emits the next state via `onListingChange`.
3947
- * The host route maps that to the URL (so a filtered view is shareable), exactly
3948
- * as `ProductGrid` + the category island already do.
3949
- */
3950
-
3951
- /**
3952
- * The controlled listing state for the spare-parts view — mirrors what the host
3953
- * derives from the URL (`page`/`offset`/`sort`/attribute-`filters`/`price`/`term`).
3954
- * Primitives + SDK enums only, so no app type leaks into the package.
3969
+ * Props for the root `<ProductCard>` (and the back-compat monolithic
3970
+ * variant). Most fields are display toggles preserved for back-compat; in
3971
+ * the new compound API, consumers control rendering by composition instead
3972
+ * — see the Subcomponents section below.
3955
3973
  */
3956
- interface MachineListingState {
3957
- page: number;
3958
- offset: number;
3959
- sortField: ProductSortField | string;
3960
- sortOrder: SortOrder | string;
3961
- /** Attribute name → selected facet values. */
3962
- filters: Record<string, string[]>;
3963
- minPrice?: number;
3964
- maxPrice?: number;
3965
- term: string;
3966
- }
3967
- interface MachineGridProps {
3968
- /** Current URL path under the machines root, e.g. `['mixer','frame']`. `[]` = root. */
3969
- segments: string[];
3970
- /** Localized machines base path (e.g. `/nl/machines`) — used to build hrefs. */
3971
- basePath: string;
3972
- /** External system the installation ids belong to (root mode). */
3973
- source?: string;
3974
- /** Installation ids from `MY_INSTALLATIONS` (root mode). */
3975
- sourceIds?: string[];
3976
- /** Title for the root list. Defaults to `'Machines'`. */
3977
- rootTitle?: string;
3978
- /** Language the machine tree is authored in (usually EN). Defaults to `'EN'`. */
3979
- machineLanguage?: string;
3980
- listing: MachineListingState;
3981
- onListingChange: (next: MachineListingState) => void;
3982
- graphqlClient?: GraphQLClient;
3983
- user?: Contact | Customer | null;
3984
- companyId?: number;
3985
- /** Storefront language (parts). */
3986
- language?: string;
3987
- taxZone?: string;
3988
- configuration?: {
3989
- imageSearchFiltersGrid?: unknown;
3990
- imageVariantFiltersMedium?: unknown;
3991
- };
3992
- portalMode?: string;
3993
- cartId?: string;
3994
- createCart?: boolean;
3995
- onCartCreated?: (cart: Cart) => void;
3974
+ interface ProductCardProps {
3975
+ /** The product object to display */
3976
+ product: Product;
3996
3977
  /**
3997
- * Fired after every successful add-to-cart (adds into an EXISTING cart too, not
3998
- * just the first create). Forward this to `CartContext.saveCart` or the cart
3999
- * icon/sidebar/page won't reflect parts added from the grid — `onCartCreated`
4000
- * alone only fires when a brand-new cart is created.
3978
+ * Compound API: provide subcomponents as children. When omitted, the
3979
+ * monolithic legacy layout renders based on the show*/ children?: React$1.ReactNode;
3980
+ /** Show the product name. Defaults to true. */
3981
+ showName?: boolean;
3982
+ /** Show the product image. Defaults to true. */
3983
+ showImage?: boolean;
3984
+ /** Show the product short description. Defaults to false. */
3985
+ showShortDescription?: boolean;
3986
+ /** Show the product SKU. Defaults to true. */
3987
+ showSku?: boolean;
3988
+ /** Show the product manufacturer. Defaults to false. */
3989
+ showManufacturer?: boolean;
3990
+ /**
3991
+ * Show the stock / availability widget below the product name.
3992
+ * Uses the embedded `ItemStock` component driven by `product.inventory`.
3993
+ * Defaults to false.
4001
3994
  */
4002
- afterAddToCart?: (cart: Cart, item?: CartMainItem) => void;
4003
- allowAddToCart?: boolean;
4004
- showPrice?: boolean;
4005
3995
  showStock?: boolean;
4006
- showAvailability?: boolean;
4007
- onProductClick?: (product: Product) => void;
4008
- paginationLabels?: Record<string, string>;
4009
- filtersLabels?: Record<string, string>;
4010
- toolbarLabels?: Record<string, string>;
4011
- machineCardLabels?: Record<string, string>;
4012
- className?: string;
4013
- }
4014
- declare function MachineGrid(rawProps: MachineGridProps): React$1.JSX.Element;
4015
-
4016
- /**
4017
- * @rsc-blocked — Client-only component: side effects (useEffect).
4018
- * Must be rendered inside (or below) a Client Component boundary; cannot be
4019
- * imported directly into a React Server Component. The 'use client' header
4020
- * above marks this boundary to Next.js.
4021
- */
4022
-
4023
- /**
4024
- * A computed object containing a cluster config setting enriched with
4025
- * its current UI state: available values for drilldown, the currently
4026
- * selected value, and whether the selector should be disabled.
4027
- */
4028
- interface ClusterConfiguratorProps {
4029
- /** The cluster ID this configurator belongs to. Required. */
4030
- clusterId: number;
4031
3996
  /**
4032
- * All products that belong to the cluster. Required.
4033
- * Used to derive available values per attribute and to match
4034
- * the configured product when all selections are made.
3997
+ * Show only the availability indicator (Available / Not available) inside ItemStock.
3998
+ * Only relevant when `showStock` is true.
3999
+ * Defaults to true.
4035
4000
  */
4036
- products: Product[];
4001
+ showAvailability?: boolean;
4037
4002
  /**
4038
- * Cluster configuration object (`cluster.config`). Required.
4039
- * Provides the ordered list of attribute settings.
4003
+ * Show the price below the product name.
4004
+ * Defaults to true.
4040
4005
  */
4041
- config: ClusterConfig;
4006
+ showPrice?: boolean;
4042
4007
  /**
4043
- * Fired whenever the user completes a set of attribute selections
4044
- * that uniquely identifies a cluster product.
4045
- * Also fired whenever any selection changes and a matching product
4046
- * can already be determined (e.g. only one setting exists).
4008
+ * Show the AddToCart component.
4009
+ * Defaults to true.
4047
4010
  */
4048
- onConfigurationChange?: (product: Product) => void;
4049
- /** Default product to pre-populate the attribute selections on mount. */
4050
- defaultProduct?: Product;
4051
- /** Override any UI string. Available keys: selectOption */
4052
- labels?: Record<string, string>;
4053
- /** Extra CSS class applied to the root element. */
4054
- className?: string;
4055
- }
4056
- /**
4057
- * Renders attribute selectors (dropdown, radio, colour or image swatches) for
4058
- * configuring a cluster, narrowing available values as the user selects and
4059
- * emitting the matching product.
4060
- *
4061
- * @remarks Uses {@link useClusterConfigurator} to derive selectable values and
4062
- * resolve the configured product.
4063
- */
4064
- declare function ClusterConfigurator(props: ClusterConfiguratorProps): React$1.JSX.Element;
4065
-
4066
- /**
4067
- * @rsc-blocked — Client-only component: side effects (useEffect).
4068
- * Must be rendered inside (or below) a Client Component boundary; cannot be
4069
- * imported directly into a React Server Component. The 'use client' header
4070
- * above marks this boundary to Next.js.
4071
- */
4072
-
4073
- interface ClusterInfoProps {
4074
- /** The authenticated user (Contact or Customer). Resolved from PropellerProvider when omitted. */
4075
- user?: Contact | Customer | null;
4011
+ allowAddToCart?: boolean;
4076
4012
  /**
4077
- * Pre-fetched cluster object to display.
4078
- * When provided the component skips internal fetching.
4013
+ * Label overrides forwarded to the embedded ItemStock component.
4014
+ * Keys: inStock, outOfStock, lowStock, available, notAvailable, pieces
4079
4015
  */
4080
- cluster?: Cluster;
4016
+ stockLabels?: Record<string, string>;
4081
4017
  /**
4082
- * Cluster ID to fetch data for when no `cluster` prop is provided.
4083
- * Requires `graphqlClient` to be set.
4018
+ * Attribute codes/names to look up and display as badge overlays on the product image.
4019
+ * Example: ['new', 'sale']
4084
4020
  */
4085
- clusterId?: number;
4021
+ imageLabels?: string[];
4086
4022
  /**
4087
- * Initialised Propeller SDK GraphQL client.
4088
- * Required when `clusterId` is provided for internal data fetching.
4023
+ * Attribute codes/names to look up and display as extra text rows below the product name.
4024
+ * Example: ['brand', 'color']
4089
4025
  */
4090
- graphqlClient?: GraphQLClient;
4026
+ textLabels?: string[];
4027
+ /** Override any UI string. Keys: addToFavorites, removeFromFavorites */
4028
+ labels?: Record<string, string>;
4029
+ /** Renders a heart-icon toggle button on the product image. Defaults to false. */
4030
+ enableAddFavorite?: boolean;
4031
+ /** Called whenever the favourite state is toggled. */
4032
+ onToggleFavorite?: (product: Product, isFavorite: boolean) => void;
4091
4033
  /**
4092
- * Called once the cluster data is loaded — either immediately (when
4093
- * `cluster` prop is supplied) or after the internal fetch completes.
4094
- * Use this to hydrate sibling components (configurator, price, gallery, etc.).
4034
+ * Called when the product name or image is clicked.
4035
+ * When provided, the default `<a>` navigation is prevented.
4095
4036
  */
4096
- onClusterLoaded?: (cluster: Cluster) => void;
4097
- /** Show the cluster name. Defaults to true. */
4098
- showTitle?: boolean;
4099
- /** Show the cluster SKU. Defaults to true. */
4100
- showSku?: boolean;
4101
- /** Language code used to resolve localised names. Defaults to 'NL'. */
4102
- language?: string;
4037
+ onProductClick?: (product: Product) => void;
4038
+ /** When true, tax-inclusive (net) price is the leading price. Defaults to false. */
4039
+ includeTax?: boolean;
4040
+ /** Number of grid columns — when 1 the card renders as a compact horizontal row. */
4041
+ columns?: number;
4103
4042
  /** Extra CSS class applied to the root element. */
4104
4043
  className?: string;
4105
4044
  /**
4106
- * Tax zone to use for price calculation.
4107
- */
4108
- taxZone?: string;
4109
- /**
4110
- * Image search filter passed to ProductService.getProduct().
4111
- * Controls how many image items are returned.
4112
- * Example: { page: 1, offset: 20 }
4045
+ * URL pattern controlling which segments appear in product links.
4046
+ * Tokens: page → 'product', id → productId, slug → slug value.
4047
+ * Defaults to 'page/id/slug'.
4113
4048
  */
4114
- imageSearchFilters?: any;
4049
+ urlPattern?: string;
4050
+ /** GraphQL client for the embedded AddToCart. Resolved from PropellerProvider when omitted. */
4051
+ graphqlClient?: GraphQLClient;
4052
+ /** Authenticated user. Resolved from PropellerProvider when omitted. */
4053
+ user?: Contact | Customer | null;
4115
4054
  /**
4116
- * Image variant transformation filter passed to ProductService.getProduct().
4117
- * Controls image size/format variants returned with the product.
4118
- * Example: imageVariantFiltersLarge from @/data/defaults
4119
- * Defaults to { transformations: [] } when omitted.
4055
+ * Portal access mode — `'open'` / `'semi-closed'` / `'closed'`. Resolved from
4056
+ * PropellerProvider when omitted. In `'semi-closed'` the card hides price and
4057
+ * stock from anonymous visitors and offers a log-in action in place of
4058
+ * add-to-cart; signed-in users are unaffected.
4120
4059
  */
4121
- imageVariantFilters?: any;
4060
+ portalMode?: string;
4122
4061
  /**
4123
- * Config object providing imageSearchFiltersGrid and imageVariantFiltersSmall.
4062
+ * Invoked when an anonymous visitor clicks the log-in action that replaces
4063
+ * add-to-cart in a semi-closed portal. The host owns navigation.
4124
4064
  */
4065
+ onLoginClick?: () => void;
4066
+ /** ID of the cart that add-to-cart targets. */
4067
+ cartId?: string;
4068
+ /** Configuration object (URL builders, image filters, etc.). */
4125
4069
  configuration?: any;
4126
- /**
4127
- * Attribute codes/names to look up and display as badge overlays on the product image.
4128
- * Each code is resolved against `product.attributes.items[].attributeDescription.code`
4129
- * (or `.name`). Attributes with no matching value are silently omitted.
4130
- * Example: ['new', 'sale']
4131
- */
4132
- imageLabels?: string[];
4133
- /**
4134
- * Attribute codes/names to look up and display as extra text rows below the product name.
4135
- * Resolved the same way as `imageLabels`.
4070
+ /** Cluster ID when this card represents a variant of a product cluster. */
4071
+ clusterId?: number;
4072
+ /** Pre-selected child item product IDs added alongside the product. */
4073
+ childItems?: number[];
4074
+ /** Order-line notes forwarded with the add-to-cart request. */
4075
+ notes?: string;
4076
+ /** Custom price override forwarded with the add-to-cart request. */
4077
+ price?: number;
4078
+ /** When true, a new cart is created if no `cartId` is set. */
4079
+ createCart?: boolean;
4080
+ /** Fires when add-to-cart creates a new cart. */
4081
+ onCartCreated?: (cart: Cart) => void;
4082
+ /** Delegation handler — when provided, the parent performs the add-to-cart itself. */
4083
+ onAddToCart?: (product: Product, clusterId?: number, quantity?: number, childItems?: CartChildItemInput[], notes?: string, price?: number, showModal?: boolean) => Cart;
4084
+ /** Fires after an item has been added to the cart. */
4085
+ afterAddToCart?: (cart: Cart, item?: CartMainItem) => void;
4086
+ /** When true, show a confirmation modal after add-to-cart instead of a toast. */
4087
+ showModal?: boolean;
4088
+ /** When true, the AddToCart shows quantity increment/decrement controls. */
4089
+ allowIncrDecr?: boolean;
4090
+ /** When true, stock availability is validated before adding to cart. */
4091
+ enableStockValidation?: boolean;
4092
+ /** Language code for localised content. Resolved from PropellerProvider when omitted. */
4093
+ language?: string;
4094
+ /** Active company ID from the company switcher. */
4095
+ companyId?: number;
4096
+ /** Fires when the "Proceed to checkout" modal button is clicked. */
4097
+ onProceedToCheckout?: () => void;
4098
+ /** Fires when the user requests a quote from the AddToCart modal. */
4099
+ onRequestQuoteClick?: (cart: Cart) => void;
4100
+ /** Label overrides forwarded to the embedded AddToCart component. */
4101
+ addToCartLabels?: Record<string, string>;
4102
+ /** Translated labels forwarded to the embedded `<ProductPrice>` display.
4103
+ * See `ProductPriceProps.labels` for slugs (inclTax, exclTax, loginToSeePrices). */
4104
+ priceLabels?: Record<string, string>;
4105
+ /** Replaces the default price block. Receives `PriceComponentProps`. */
4106
+ /** Currency symbol used when formatting the price. Falls back to PropellerInfra.currency, then `'€'`. */
4107
+ currency?: string;
4108
+ priceComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.PriceComponentProps>;
4109
+ /** Replaces the default stock/availability block. Receives `StockComponentProps`. */
4110
+ stockComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.StockComponentProps>;
4111
+ /** Replaces the default add-to-cart control. Receives `AddToCartComponentProps`. */
4112
+ addToCartComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.AddToCartComponentProps>;
4113
+ /** Replaces the default product image block. Receives `ImageComponentProps`. */
4114
+ imageComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.ImageComponentProps>;
4115
+ /** Replaces the default product badges block. Receives `BadgesComponentProps`. */
4116
+ badgesComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.BadgesComponentProps>;
4117
+ /** Replaces the default favorite-toggle. Receives `FavoriteComponentProps`. */
4118
+ favoriteComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.FavoriteComponentProps>;
4119
+ /**
4120
+ * Render arbitrary content directly below the product name (and above the
4121
+ * short description / price), in both the grid and row layouts. Receives the
4122
+ * product so hosts can surface extra info — e.g. package descriptions, custom
4123
+ * badges — without forking the card. Return `null`/`undefined` to render
4124
+ * nothing for a given product.
4125
+ */
4126
+ belowName?: (product: Product) => React$1.ReactNode;
4127
+ }
4128
+ /**
4129
+ * Root of the `<ProductCard>` compound component. Renders either the
4130
+ * consumer-composed children (compound mode) or the legacy monolithic layout
4131
+ * driven by `show*`/`allow*` toggles, and provides the shared card context.
4132
+ */
4133
+ declare function ProductCardRoot(rawProps: ProductCardProps): React$1.JSX.Element;
4134
+ /**
4135
+ * Product image with optional badge overlays and favourite button.
4136
+ * Reads `derived.imageUrl`, `derived.imageLabelValues`, `derived.productUrl`,
4137
+ * and the `favorite` state from context.
4138
+ *
4139
+ * Trade-off: when `imageComponent` is injected, the consumer takes over the
4140
+ * ENTIRE image area — including the badge / favorite overlay layer.
4141
+ * The default markup wraps the `<img>` in a link with absolute-positioned
4142
+ * badges and a favorite button; an injected component receives only the
4143
+ * `ImageComponentProps` contract and is free to re-render those overlays
4144
+ * itself (e.g. by composing `<DefaultProductBadges>` / `<DefaultAddToFavorite>`)
4145
+ * or omit them entirely.
4146
+ */
4147
+ declare function ProductCardImage(props: {
4148
+ /** Override the wrapper class. */
4149
+ className?: string;
4150
+ /** Render the favourite heart button. Defaults to context `enableAddFavorite`. */
4151
+ showFavorite?: boolean;
4152
+ }): React$1.JSX.Element;
4153
+ /** Image-overlay badge list (e.g. "new", "sale" attributes), or the
4154
+ * injected `badgesComponent` from context when set. */
4155
+ declare function ProductCardBadges(props: {
4156
+ /** Override the badge container class. */
4157
+ className?: string;
4158
+ }): React$1.JSX.Element | null;
4159
+ /** Favourite (heart) toggle button — typically placed inside `<ProductCard.Image>`,
4160
+ * or the injected `favoriteComponent` from context when set.
4161
+ *
4162
+ * The default render is the inline self-contained heart button (local state
4163
+ * only, fires `onToggleFavorite`); the injected variant receives the full
4164
+ * `FavoriteComponentProps` contract (product, user, callback) so consumers
4165
+ * can plug in real favorite-list mutations. The unused import of
4166
+ * `DefaultAddToFavoriteImpl` is kept so consumers can wrap-and-call it. */
4167
+ declare function ProductCardFavorite(props: {
4168
+ /** Override the button class. */
4169
+ className?: string;
4170
+ }): React$1.JSX.Element;
4171
+ /** Product name. `linkable` (default true) wraps it in an `<a>` to the PDP. */
4172
+ declare function ProductCardName(props: {
4173
+ /** When true (default), wraps the name in a link to the product page. */
4174
+ linkable?: boolean;
4175
+ /** Override the name element class. */
4176
+ className?: string;
4177
+ }): React$1.JSX.Element;
4178
+ /** Renders the product SKU. */
4179
+ declare function ProductCardSku(props: {
4180
+ /** Override the SKU element class. */
4181
+ className?: string;
4182
+ }): React$1.JSX.Element | null;
4183
+ /** Renders the product's short description. */
4184
+ declare function ProductCardShortDescription(props: {
4185
+ /** Override the description element class. */
4186
+ className?: string;
4187
+ }): React$1.JSX.Element | null;
4188
+ /** Renders the product manufacturer name. */
4189
+ declare function ProductCardManufacturer(props: {
4190
+ /** Override the manufacturer element class. */
4191
+ className?: string;
4192
+ }): React$1.JSX.Element | null;
4193
+ /** Renders the resolved `textLabels` attribute values as text rows. */
4194
+ declare function ProductCardTextLabels(props: {
4195
+ /** Override the label container class. */
4196
+ className?: string;
4197
+ }): React$1.JSX.Element | null;
4198
+ /** Renders the embedded stock/availability widget for the product, or the
4199
+ * injected `stockComponent` from context when set. */
4200
+ declare function ProductCardStock(props: {
4201
+ /** Show only the availability indicator. Defaults to context `showAvailability`. */
4202
+ showAvailability?: boolean;
4203
+ }): React$1.JSX.Element | null;
4204
+ /** Renders host-supplied content directly below the name, via the `belowName`
4205
+ * render prop (explicit or from grid context). Renders nothing when unset or
4206
+ * when the render prop returns a nullish value. */
4207
+ declare function ProductCardBelowName(): React$1.JSX.Element | null;
4208
+ /** Renders the product price via the embedded `ProductPrice` component, or
4209
+ * the injected `priceComponent` from context when set. */
4210
+ declare function ProductCardPrice(props: {
4211
+ /** Tailwind text-size class for the price. Defaults to `'text-base sm:text-lg'`. */
4212
+ priceSize?: string;
4213
+ /** Override the price wrapper class. */
4214
+ className?: string;
4215
+ }): React$1.JSX.Element | null;
4216
+ /** Renders the embedded `AddToCart` control with all pass-through props from
4217
+ * context, or the injected `addToCartComponent` when set.
4218
+ *
4219
+ * Note: the default `AddToCartImpl` accepts a richer prop set than the public
4220
+ * `AddToCartComponentProps` contract. Injected components receive the whole
4221
+ * contract surface; what stays default-only is infra (graphqlClient, user,
4222
+ * configuration, …), which an injected component resolves from
4223
+ * `<PropellerProvider>` itself. */
4224
+ declare function ProductCardAddToCart(props: {
4225
+ /** Override the AddToCart wrapper class. */
4226
+ className?: string;
4227
+ }): React$1.JSX.Element;
4228
+ declare const MemoizedRoot: React$1.MemoExoticComponent<typeof ProductCardRoot>;
4229
+ /**
4230
+ * Compound component. Use either:
4231
+ *
4232
+ * **Compound mode (new — preferred):**
4233
+ * ```tsx
4234
+ * <ProductCard product={p}>
4235
+ * <ProductCard.Image variant="grid" />
4236
+ * <ProductCard.Name linkable />
4237
+ * <ProductCard.Sku />
4238
+ * <ProductCard.Price />
4239
+ * <ProductCard.AddToCart />
4240
+ * </ProductCard>
4241
+ * ```
4242
+ *
4243
+ * **Legacy mode (deprecated — kept for back-compat):**
4244
+ * ```tsx
4245
+ * <ProductCard product={p} showName showPrice allowAddToCart />
4246
+ * ```
4247
+ * All the show* / allow* toggles still work and render the canonical layout
4248
+ * using the compound subcomponents internally.
4249
+ */
4250
+ type ProductCardComponent = typeof MemoizedRoot & {
4251
+ Image: typeof ProductCardImage;
4252
+ Badges: typeof ProductCardBadges;
4253
+ Favorite: typeof ProductCardFavorite;
4254
+ Name: typeof ProductCardName;
4255
+ Sku: typeof ProductCardSku;
4256
+ BelowName: typeof ProductCardBelowName;
4257
+ ShortDescription: typeof ProductCardShortDescription;
4258
+ Manufacturer: typeof ProductCardManufacturer;
4259
+ TextLabels: typeof ProductCardTextLabels;
4260
+ Stock: typeof ProductCardStock;
4261
+ Price: typeof ProductCardPrice;
4262
+ AddToCart: typeof ProductCardAddToCart;
4263
+ };
4264
+ declare const ProductCard: ProductCardComponent;
4265
+
4266
+ /**
4267
+ * The controlled listing state for the spare-parts view — mirrors what the host
4268
+ * derives from the URL (`page`/`offset`/`sort`/attribute-`filters`/`price`/`term`).
4269
+ * Primitives + SDK enums only, so no app type leaks into the package.
4270
+ */
4271
+ interface MachineListingState {
4272
+ page: number;
4273
+ offset: number;
4274
+ sortField: ProductSortField | string;
4275
+ sortOrder: SortOrder | string;
4276
+ /** Attribute name → selected facet values. */
4277
+ filters: Record<string, string[]>;
4278
+ minPrice?: number;
4279
+ maxPrice?: number;
4280
+ term: string;
4281
+ }
4282
+ interface MachineGridProps {
4283
+ /** Current URL path under the machines root, e.g. `['mixer','frame']`. `[]` = root. */
4284
+ segments: string[];
4285
+ /** Localized machines base path (e.g. `/nl/machines`) — used to build hrefs. */
4286
+ basePath: string;
4287
+ /** External system the installation ids belong to (root mode). */
4288
+ source?: string;
4289
+ /** Installation ids from `MY_INSTALLATIONS` (root mode). */
4290
+ sourceIds?: string[];
4291
+ /** Title for the root list. Defaults to `'Machines'`. */
4292
+ rootTitle?: string;
4293
+ /** Language the machine tree is authored in (usually EN). Defaults to `'EN'`. */
4294
+ machineLanguage?: string;
4295
+ listing: MachineListingState;
4296
+ onListingChange: (next: MachineListingState) => void;
4297
+ graphqlClient?: GraphQLClient;
4298
+ user?: Contact | Customer | null;
4299
+ companyId?: number;
4300
+ /** Storefront language (parts). */
4301
+ language?: string;
4302
+ taxZone?: string;
4303
+ configuration?: {
4304
+ imageSearchFiltersGrid?: unknown;
4305
+ imageVariantFiltersMedium?: unknown;
4306
+ };
4307
+ portalMode?: string;
4308
+ cartId?: string;
4309
+ createCart?: boolean;
4310
+ onCartCreated?: (cart: Cart) => void;
4311
+ /**
4312
+ * Fired after every successful add-to-cart (adds into an EXISTING cart too, not
4313
+ * just the first create). Forward this to `CartContext.saveCart` or the cart
4314
+ * icon/sidebar/page won't reflect parts added from the grid — `onCartCreated`
4315
+ * alone only fires when a brand-new cart is created.
4316
+ */
4317
+ afterAddToCart?: (cart: Cart, item?: CartMainItem) => void;
4318
+ allowAddToCart?: boolean;
4319
+ showPrice?: boolean;
4320
+ showStock?: boolean;
4321
+ showAvailability?: boolean;
4322
+ onProductClick?: (product: Product) => void;
4323
+ /**
4324
+ * Custom card for the PARTS list, forwarded to the inner ProductGrid. Without
4325
+ * it a product rendered with the host's custom card on a normal listing and
4326
+ * with the stock card here, so the same product looked different depending on
4327
+ * the page it was reached from (PWP-995c).
4328
+ */
4329
+ productCardComponent?: React$1.ComponentType<ProductCardProps>;
4330
+ paginationLabels?: Record<string, string>;
4331
+ filtersLabels?: Record<string, string>;
4332
+ toolbarLabels?: Record<string, string>;
4333
+ /**
4334
+ * Labels for the machine side of the grid. Keys read here: `loading` and
4335
+ * `noMachines`. The same object is passed to each `MachineCard` as its
4336
+ * `labels`, which reads `viewMachine` — so all three keys belong in it.
4337
+ */
4338
+ machineCardLabels?: Record<string, string>;
4339
+ /**
4340
+ * Labels for the PARTS list, forwarded verbatim to the inner ProductGrid and
4341
+ * the components it embeds.
4342
+ *
4343
+ * MachineGrid used to forward none of these and expose no way to reach them,
4344
+ * so a translated storefront rendered "In stock", "Add" and "Search parts…"
4345
+ * in English in the middle of its own copy. A monolingual shop never noticed
4346
+ * (PWP-995a).
4347
+ */
4348
+ productCardLabels?: Record<string, string>;
4349
+ addToCartLabels?: Record<string, string>;
4350
+ stockLabels?: Record<string, string>;
4351
+ priceLabels?: Record<string, string>;
4352
+ labels?: Record<string, string>;
4353
+ className?: string;
4354
+ }
4355
+ declare function MachineGrid(rawProps: MachineGridProps): React$1.JSX.Element;
4356
+
4357
+ /**
4358
+ * @rsc-blocked — Client-only component: side effects (useEffect).
4359
+ * Must be rendered inside (or below) a Client Component boundary; cannot be
4360
+ * imported directly into a React Server Component. The 'use client' header
4361
+ * above marks this boundary to Next.js.
4362
+ */
4363
+
4364
+ /**
4365
+ * A computed object containing a cluster config setting enriched with
4366
+ * its current UI state: available values for drilldown, the currently
4367
+ * selected value, and whether the selector should be disabled.
4368
+ */
4369
+ interface ClusterConfiguratorProps {
4370
+ /** The cluster ID this configurator belongs to. Required. */
4371
+ clusterId: number;
4372
+ /**
4373
+ * All products that belong to the cluster. Required.
4374
+ * Used to derive available values per attribute and to match
4375
+ * the configured product when all selections are made.
4376
+ */
4377
+ products: Product[];
4378
+ /**
4379
+ * Cluster configuration object (`cluster.config`). Required.
4380
+ * Provides the ordered list of attribute settings.
4381
+ */
4382
+ config: ClusterConfig;
4383
+ /**
4384
+ * Fired whenever the user completes a set of attribute selections
4385
+ * that uniquely identifies a cluster product.
4386
+ * Also fired whenever any selection changes and a matching product
4387
+ * can already be determined (e.g. only one setting exists).
4388
+ */
4389
+ onConfigurationChange?: (product: Product) => void;
4390
+ /** Default product to pre-populate the attribute selections on mount. */
4391
+ defaultProduct?: Product;
4392
+ /** Override any UI string. Available keys: selectOption */
4393
+ labels?: Record<string, string>;
4394
+ /** Extra CSS class applied to the root element. */
4395
+ className?: string;
4396
+ }
4397
+ /**
4398
+ * Renders attribute selectors (dropdown, radio, colour or image swatches) for
4399
+ * configuring a cluster, narrowing available values as the user selects and
4400
+ * emitting the matching product.
4401
+ *
4402
+ * @remarks Uses {@link useClusterConfigurator} to derive selectable values and
4403
+ * resolve the configured product.
4404
+ */
4405
+ declare function ClusterConfigurator(props: ClusterConfiguratorProps): React$1.JSX.Element;
4406
+
4407
+ /**
4408
+ * @rsc-blocked — Client-only component: side effects (useEffect).
4409
+ * Must be rendered inside (or below) a Client Component boundary; cannot be
4410
+ * imported directly into a React Server Component. The 'use client' header
4411
+ * above marks this boundary to Next.js.
4412
+ */
4413
+
4414
+ interface ClusterInfoProps {
4415
+ /** The authenticated user (Contact or Customer). Resolved from PropellerProvider when omitted. */
4416
+ user?: Contact | Customer | null;
4417
+ /**
4418
+ * Pre-fetched cluster object to display.
4419
+ * When provided the component skips internal fetching.
4420
+ */
4421
+ cluster?: Cluster;
4422
+ /**
4423
+ * Cluster ID to fetch data for when no `cluster` prop is provided.
4424
+ * Requires `graphqlClient` to be set.
4425
+ */
4426
+ clusterId?: number;
4427
+ /**
4428
+ * Initialised Propeller SDK GraphQL client.
4429
+ * Required when `clusterId` is provided for internal data fetching.
4430
+ */
4431
+ graphqlClient?: GraphQLClient;
4432
+ /**
4433
+ * Called once the cluster data is loaded — either immediately (when
4434
+ * `cluster` prop is supplied) or after the internal fetch completes.
4435
+ * Use this to hydrate sibling components (configurator, price, gallery, etc.).
4436
+ */
4437
+ onClusterLoaded?: (cluster: Cluster) => void;
4438
+ /** Show the cluster name. Defaults to true. */
4439
+ showTitle?: boolean;
4440
+ /** Show the cluster SKU. Defaults to true. */
4441
+ showSku?: boolean;
4442
+ /** Language code used to resolve localised names. Defaults to 'NL'. */
4443
+ language?: string;
4444
+ /** Extra CSS class applied to the root element. */
4445
+ className?: string;
4446
+ /**
4447
+ * Tax zone to use for price calculation.
4448
+ */
4449
+ taxZone?: string;
4450
+ /**
4451
+ * Image search filter passed to ProductService.getProduct().
4452
+ * Controls how many image items are returned.
4453
+ * Example: { page: 1, offset: 20 }
4454
+ */
4455
+ imageSearchFilters?: any;
4456
+ /**
4457
+ * Image variant transformation filter passed to ProductService.getProduct().
4458
+ * Controls image size/format variants returned with the product.
4459
+ * Example: imageVariantFiltersLarge from @/data/defaults
4460
+ * Defaults to { transformations: [] } when omitted.
4461
+ */
4462
+ imageVariantFilters?: any;
4463
+ /**
4464
+ * Config object providing imageSearchFiltersGrid and imageVariantFiltersSmall.
4465
+ */
4466
+ configuration?: any;
4467
+ /**
4468
+ * Attribute codes/names to look up and display as badge overlays on the product image.
4469
+ * Each code is resolved against `product.attributes.items[].attributeDescription.code`
4470
+ * (or `.name`). Attributes with no matching value are silently omitted.
4471
+ * Example: ['new', 'sale']
4472
+ */
4473
+ imageLabels?: string[];
4474
+ /**
4475
+ * Attribute codes/names to look up and display as extra text rows below the product name.
4476
+ * Resolved the same way as `imageLabels`.
4136
4477
  * Example: ['brand', 'color']
4137
4478
  */
4138
4479
  textLabels?: string[];
@@ -4230,6 +4571,8 @@ interface CompanySwitcherProps {
4230
4571
  /** Translated labels keyed by the slugs used inside the component (see
4231
4572
  * `getLabel` calls). Missing keys fall back to the English defaults. */
4232
4573
  labels?: Record<string, string>;
4574
+ /** Additional class name for the switcher's trigger button. */
4575
+ triggerClassName?: string;
4233
4576
  }
4234
4577
  /**
4235
4578
  * Dropdown for switching the active company of a multi-company contact.
@@ -5836,6 +6179,12 @@ interface ProductBulkPricesProps {
5836
6179
  * Defaults to 'open'.
5837
6180
  */
5838
6181
  portalMode?: string;
6182
+ /**
6183
+ * Whether a session exists, independent of whether `user` has loaded yet.
6184
+ * Passed down by the parent alongside `portalMode`; closes the hydration
6185
+ * window in which an authenticated visitor still has a null `user`.
6186
+ */
6187
+ isAuthenticated?: boolean;
5839
6188
  /** Authenticated user — used for semi-closed visibility. */
5840
6189
  user?: Contact | Customer | null;
5841
6190
  /** Tax zone code. Defaults to 'NL'. */
@@ -5913,356 +6262,59 @@ interface ProductBundlesProps {
5913
6262
  /** When true, stock availability is validated before adding to cart. */
5914
6263
  stockValidation?: boolean;
5915
6264
  /**
5916
- * When true, the individual bundle items are listed inside each bundle card.
5917
- * Defaults to true.
5918
- */
5919
- showIndividualItems?: boolean;
5920
- /** Additional configuration object passed through to the component. */
5921
- configuration?: any;
5922
- /**
5923
- * Layout variant for the bundle display.
5924
- * - 'vertical' — stacked layout
5925
- * - 'horizontal' — side-by-side (default)
5926
- * - 'compact' — condensed, hides individual items
5927
- */
5928
- layout?: 'vertical' | 'horizontal' | 'compact';
5929
- /**
5930
- * Override any UI string.
5931
- * Available keys: title, condition_ALL, condition_EP, leaderItem,
5932
- * youSave, adding, addToCart, loginToSeePrices, addedToCart,
5933
- * modalTitle, continueShopping, proceedToCheckout, noCartId
5934
- */
5935
- labels?: Record<string, string>;
5936
- /**
5937
- * Show the "log in to see prices" prompt when prices are hidden.
5938
- * Defaults to true; pass false to render nothing in its place.
5939
- */
5940
- showLoginPrompt?: boolean;
5941
- /**
5942
- * When true a modal popup is shown after a successful add-to-cart
5943
- * with buttons to continue shopping or proceed to checkout.
5944
- * Defaults to false (only a brief inline toast is shown).
5945
- */
5946
- showModal?: boolean;
5947
- /** Callback fired when the "Proceed to checkout" modal button is clicked */
5948
- onProceedToCheckout?: () => void;
5949
- /**
5950
- * Callback triggered before adding the bundle to cart.
5951
- */
5952
- beforeBundleAddToCart?: (bundleId: string, quantity: number) => boolean;
5953
- /** Called when the user clicks "Add bundle to cart". Receives bundleId and quantity (always 1). */
5954
- onAddBundleToCart?: (bundleId: string, quantity: number) => void;
5955
- /**
5956
- * Callback triggered after adding the bundle to cart.
5957
- */
5958
- afterBundleAddToCart?: (cart: Cart, bundle?: Bundle) => void;
5959
- /** Extra CSS class applied to the root wrapper element. */
5960
- className?: string;
5961
- }
5962
- /**
5963
- * Renders combo-deal bundles for a product, each with its constituent items,
5964
- * pricing and savings, plus add-to-cart with toast or modal feedback.
5965
- *
5966
- * @remarks Uses {@link useProductBundles} to fetch bundles and add them to a cart.
5967
- */
5968
- declare function ProductBundles(rawProps: ProductBundlesProps): React$1.JSX.Element;
5969
-
5970
- /**
5971
- * Props for the root `<ProductCard>` (and the back-compat monolithic
5972
- * variant). Most fields are display toggles preserved for back-compat; in
5973
- * the new compound API, consumers control rendering by composition instead
5974
- * — see the Subcomponents section below.
5975
- */
5976
- interface ProductCardProps {
5977
- /** The product object to display */
5978
- product: Product;
5979
- /**
5980
- * Compound API: provide subcomponents as children. When omitted, the
5981
- * monolithic legacy layout renders based on the show*/ children?: React$1.ReactNode;
5982
- /** Show the product name. Defaults to true. */
5983
- showName?: boolean;
5984
- /** Show the product image. Defaults to true. */
5985
- showImage?: boolean;
5986
- /** Show the product short description. Defaults to false. */
5987
- showShortDescription?: boolean;
5988
- /** Show the product SKU. Defaults to true. */
5989
- showSku?: boolean;
5990
- /** Show the product manufacturer. Defaults to false. */
5991
- showManufacturer?: boolean;
5992
- /**
5993
- * Show the stock / availability widget below the product name.
5994
- * Uses the embedded `ItemStock` component driven by `product.inventory`.
5995
- * Defaults to false.
5996
- */
5997
- showStock?: boolean;
5998
- /**
5999
- * Show only the availability indicator (Available / Not available) inside ItemStock.
6000
- * Only relevant when `showStock` is true.
6001
- * Defaults to true.
6002
- */
6003
- showAvailability?: boolean;
6004
- /**
6005
- * Show the price below the product name.
6006
- * Defaults to true.
6007
- */
6008
- showPrice?: boolean;
6009
- /**
6010
- * Show the AddToCart component.
6011
- * Defaults to true.
6012
- */
6013
- allowAddToCart?: boolean;
6014
- /**
6015
- * Label overrides forwarded to the embedded ItemStock component.
6016
- * Keys: inStock, outOfStock, lowStock, available, notAvailable, pieces
6017
- */
6018
- stockLabels?: Record<string, string>;
6019
- /**
6020
- * Attribute codes/names to look up and display as badge overlays on the product image.
6021
- * Example: ['new', 'sale']
6022
- */
6023
- imageLabels?: string[];
6024
- /**
6025
- * Attribute codes/names to look up and display as extra text rows below the product name.
6026
- * Example: ['brand', 'color']
6027
- */
6028
- textLabels?: string[];
6029
- /** Override any UI string. Keys: addToFavorites, removeFromFavorites */
6030
- labels?: Record<string, string>;
6031
- /** Renders a heart-icon toggle button on the product image. Defaults to false. */
6032
- enableAddFavorite?: boolean;
6033
- /** Called whenever the favourite state is toggled. */
6034
- onToggleFavorite?: (product: Product, isFavorite: boolean) => void;
6035
- /**
6036
- * Called when the product name or image is clicked.
6037
- * When provided, the default `<a>` navigation is prevented.
6265
+ * When true, the individual bundle items are listed inside each bundle card.
6266
+ * Defaults to true.
6038
6267
  */
6039
- onProductClick?: (product: Product) => void;
6040
- /** When true, tax-inclusive (net) price is the leading price. Defaults to false. */
6041
- includeTax?: boolean;
6042
- /** Number of grid columns — when 1 the card renders as a compact horizontal row. */
6043
- columns?: number;
6044
- /** Extra CSS class applied to the root element. */
6045
- className?: string;
6268
+ showIndividualItems?: boolean;
6269
+ /** Additional configuration object passed through to the component. */
6270
+ configuration?: any;
6046
6271
  /**
6047
- * URL pattern controlling which segments appear in product links.
6048
- * Tokens: page → 'product', id → productId, slug → slug value.
6049
- * Defaults to 'page/id/slug'.
6272
+ * Layout variant for the bundle display.
6273
+ * - 'vertical' — stacked layout
6274
+ * - 'horizontal' — side-by-side (default)
6275
+ * - 'compact' — condensed, hides individual items
6050
6276
  */
6051
- urlPattern?: string;
6052
- /** GraphQL client for the embedded AddToCart. Resolved from PropellerProvider when omitted. */
6053
- graphqlClient?: GraphQLClient;
6054
- /** Authenticated user. Resolved from PropellerProvider when omitted. */
6055
- user?: Contact | Customer | null;
6277
+ layout?: 'vertical' | 'horizontal' | 'compact';
6056
6278
  /**
6057
- * Portal access mode — `'open'` / `'semi-closed'` / `'closed'`. Resolved from
6058
- * PropellerProvider when omitted. In `'semi-closed'` the card hides price and
6059
- * stock from anonymous visitors and offers a log-in action in place of
6060
- * add-to-cart; signed-in users are unaffected.
6279
+ * Override any UI string.
6280
+ * Available keys: title, condition_ALL, condition_EP, leaderItem,
6281
+ * youSave, adding, addToCart, loginToSeePrices, addedToCart,
6282
+ * modalTitle, continueShopping, proceedToCheckout, noCartId
6061
6283
  */
6062
- portalMode?: string;
6284
+ labels?: Record<string, string>;
6063
6285
  /**
6064
- * Invoked when an anonymous visitor clicks the log-in action that replaces
6065
- * add-to-cart in a semi-closed portal. The host owns navigation.
6286
+ * Show the "log in to see prices" prompt when prices are hidden.
6287
+ * Defaults to true; pass false to render nothing in its place.
6288
+ */
6289
+ showLoginPrompt?: boolean;
6290
+ /**
6291
+ * When true a modal popup is shown after a successful add-to-cart
6292
+ * with buttons to continue shopping or proceed to checkout.
6293
+ * Defaults to false (only a brief inline toast is shown).
6066
6294
  */
6067
- onLoginClick?: () => void;
6068
- /** ID of the cart that add-to-cart targets. */
6069
- cartId?: string;
6070
- /** Configuration object (URL builders, image filters, etc.). */
6071
- configuration?: any;
6072
- /** Cluster ID when this card represents a variant of a product cluster. */
6073
- clusterId?: number;
6074
- /** Pre-selected child item product IDs added alongside the product. */
6075
- childItems?: number[];
6076
- /** Order-line notes forwarded with the add-to-cart request. */
6077
- notes?: string;
6078
- /** Custom price override forwarded with the add-to-cart request. */
6079
- price?: number;
6080
- /** When true, a new cart is created if no `cartId` is set. */
6081
- createCart?: boolean;
6082
- /** Fires when add-to-cart creates a new cart. */
6083
- onCartCreated?: (cart: Cart) => void;
6084
- /** Delegation handler — when provided, the parent performs the add-to-cart itself. */
6085
- onAddToCart?: (product: Product, clusterId?: number, quantity?: number, childItems?: CartChildItemInput[], notes?: string, price?: number, showModal?: boolean) => Cart;
6086
- /** Fires after an item has been added to the cart. */
6087
- afterAddToCart?: (cart: Cart, item?: CartMainItem) => void;
6088
- /** When true, show a confirmation modal after add-to-cart instead of a toast. */
6089
6295
  showModal?: boolean;
6090
- /** When true, the AddToCart shows quantity increment/decrement controls. */
6091
- allowIncrDecr?: boolean;
6092
- /** When true, stock availability is validated before adding to cart. */
6093
- enableStockValidation?: boolean;
6094
- /** Language code for localised content. Resolved from PropellerProvider when omitted. */
6095
- language?: string;
6096
- /** Active company ID from the company switcher. */
6097
- companyId?: number;
6098
- /** Fires when the "Proceed to checkout" modal button is clicked. */
6296
+ /** Callback fired when the "Proceed to checkout" modal button is clicked */
6099
6297
  onProceedToCheckout?: () => void;
6100
- /** Fires when the user requests a quote from the AddToCart modal. */
6101
- onRequestQuoteClick?: (cart: Cart) => void;
6102
- /** Label overrides forwarded to the embedded AddToCart component. */
6103
- addToCartLabels?: Record<string, string>;
6104
- /** Translated labels forwarded to the embedded `<ProductPrice>` display.
6105
- * See `ProductPriceProps.labels` for slugs (inclTax, exclTax, loginToSeePrices). */
6106
- priceLabels?: Record<string, string>;
6107
- /** Replaces the default price block. Receives `PriceComponentProps`. */
6108
- /** Currency symbol used when formatting the price. Falls back to PropellerInfra.currency, then `'€'`. */
6109
- currency?: string;
6110
- priceComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.PriceComponentProps>;
6111
- /** Replaces the default stock/availability block. Receives `StockComponentProps`. */
6112
- stockComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.StockComponentProps>;
6113
- /** Replaces the default add-to-cart control. Receives `AddToCartComponentProps`. */
6114
- addToCartComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.AddToCartComponentProps>;
6115
- /** Replaces the default product image block. Receives `ImageComponentProps`. */
6116
- imageComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.ImageComponentProps>;
6117
- /** Replaces the default product badges block. Receives `BadgesComponentProps`. */
6118
- badgesComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.BadgesComponentProps>;
6119
- /** Replaces the default favorite-toggle. Receives `FavoriteComponentProps`. */
6120
- favoriteComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.FavoriteComponentProps>;
6121
6298
  /**
6122
- * Render arbitrary content directly below the product name (and above the
6123
- * short description / price), in both the grid and row layouts. Receives the
6124
- * product so hosts can surface extra info — e.g. package descriptions, custom
6125
- * badges — without forking the card. Return `null`/`undefined` to render
6126
- * nothing for a given product.
6299
+ * Callback triggered before adding the bundle to cart.
6127
6300
  */
6128
- belowName?: (product: Product) => React$1.ReactNode;
6129
- }
6130
- /**
6131
- * Root of the `<ProductCard>` compound component. Renders either the
6132
- * consumer-composed children (compound mode) or the legacy monolithic layout
6133
- * driven by `show*`/`allow*` toggles, and provides the shared card context.
6134
- */
6135
- declare function ProductCardRoot(rawProps: ProductCardProps): React$1.JSX.Element;
6136
- /**
6137
- * Product image with optional badge overlays and favourite button.
6138
- * Reads `derived.imageUrl`, `derived.imageLabelValues`, `derived.productUrl`,
6139
- * and the `favorite` state from context.
6140
- *
6141
- * Trade-off: when `imageComponent` is injected, the consumer takes over the
6142
- * ENTIRE image area — including the badge / favorite overlay layer.
6143
- * The default markup wraps the `<img>` in a link with absolute-positioned
6144
- * badges and a favorite button; an injected component receives only the
6145
- * `ImageComponentProps` contract and is free to re-render those overlays
6146
- * itself (e.g. by composing `<DefaultProductBadges>` / `<DefaultAddToFavorite>`)
6147
- * or omit them entirely.
6148
- */
6149
- declare function ProductCardImage(props: {
6150
- /** Override the wrapper class. */
6151
- className?: string;
6152
- /** Render the favourite heart button. Defaults to context `enableAddFavorite`. */
6153
- showFavorite?: boolean;
6154
- }): React$1.JSX.Element;
6155
- /** Image-overlay badge list (e.g. "new", "sale" attributes), or the
6156
- * injected `badgesComponent` from context when set. */
6157
- declare function ProductCardBadges(props: {
6158
- /** Override the badge container class. */
6159
- className?: string;
6160
- }): React$1.JSX.Element | null;
6161
- /** Favourite (heart) toggle button — typically placed inside `<ProductCard.Image>`,
6162
- * or the injected `favoriteComponent` from context when set.
6163
- *
6164
- * The default render is the inline self-contained heart button (local state
6165
- * only, fires `onToggleFavorite`); the injected variant receives the full
6166
- * `FavoriteComponentProps` contract (product, user, callback) so consumers
6167
- * can plug in real favorite-list mutations. The unused import of
6168
- * `DefaultAddToFavoriteImpl` is kept so consumers can wrap-and-call it. */
6169
- declare function ProductCardFavorite(props: {
6170
- /** Override the button class. */
6171
- className?: string;
6172
- }): React$1.JSX.Element;
6173
- /** Product name. `linkable` (default true) wraps it in an `<a>` to the PDP. */
6174
- declare function ProductCardName(props: {
6175
- /** When true (default), wraps the name in a link to the product page. */
6176
- linkable?: boolean;
6177
- /** Override the name element class. */
6178
- className?: string;
6179
- }): React$1.JSX.Element;
6180
- /** Renders the product SKU. */
6181
- declare function ProductCardSku(props: {
6182
- /** Override the SKU element class. */
6183
- className?: string;
6184
- }): React$1.JSX.Element | null;
6185
- /** Renders the product's short description. */
6186
- declare function ProductCardShortDescription(props: {
6187
- /** Override the description element class. */
6188
- className?: string;
6189
- }): React$1.JSX.Element | null;
6190
- /** Renders the product manufacturer name. */
6191
- declare function ProductCardManufacturer(props: {
6192
- /** Override the manufacturer element class. */
6193
- className?: string;
6194
- }): React$1.JSX.Element | null;
6195
- /** Renders the resolved `textLabels` attribute values as text rows. */
6196
- declare function ProductCardTextLabels(props: {
6197
- /** Override the label container class. */
6198
- className?: string;
6199
- }): React$1.JSX.Element | null;
6200
- /** Renders the embedded stock/availability widget for the product, or the
6201
- * injected `stockComponent` from context when set. */
6202
- declare function ProductCardStock(props: {
6203
- /** Show only the availability indicator. Defaults to context `showAvailability`. */
6204
- showAvailability?: boolean;
6205
- }): React$1.JSX.Element | null;
6206
- /** Renders host-supplied content directly below the name, via the `belowName`
6207
- * render prop (explicit or from grid context). Renders nothing when unset or
6208
- * when the render prop returns a nullish value. */
6209
- declare function ProductCardBelowName(): React$1.JSX.Element | null;
6210
- /** Renders the product price via the embedded `ProductPrice` component, or
6211
- * the injected `priceComponent` from context when set. */
6212
- declare function ProductCardPrice(props: {
6213
- /** Tailwind text-size class for the price. Defaults to `'text-base sm:text-lg'`. */
6214
- priceSize?: string;
6215
- /** Override the price wrapper class. */
6216
- className?: string;
6217
- }): React$1.JSX.Element | null;
6218
- /** Renders the embedded `AddToCart` control with all pass-through props from
6219
- * context, or the injected `addToCartComponent` when set.
6220
- *
6221
- * Note: the default `AddToCartImpl` accepts a richer prop set (graphqlClient,
6222
- * configuration, etc.) than the public `AddToCartComponentProps` contract.
6223
- * Injected components only receive the contract surface; the extra infra
6224
- * props stay default-only. */
6225
- declare function ProductCardAddToCart(props: {
6226
- /** Override the AddToCart wrapper class. */
6301
+ beforeBundleAddToCart?: (bundleId: string, quantity: number) => boolean;
6302
+ /** Called when the user clicks "Add bundle to cart". Receives bundleId and quantity (always 1). */
6303
+ onAddBundleToCart?: (bundleId: string, quantity: number) => void;
6304
+ /**
6305
+ * Callback triggered after adding the bundle to cart.
6306
+ */
6307
+ afterBundleAddToCart?: (cart: Cart, bundle?: Bundle) => void;
6308
+ /** Extra CSS class applied to the root wrapper element. */
6227
6309
  className?: string;
6228
- }): React$1.JSX.Element;
6229
- declare const MemoizedRoot: React$1.MemoExoticComponent<typeof ProductCardRoot>;
6310
+ }
6230
6311
  /**
6231
- * Compound component. Use either:
6232
- *
6233
- * **Compound mode (new — preferred):**
6234
- * ```tsx
6235
- * <ProductCard product={p}>
6236
- * <ProductCard.Image variant="grid" />
6237
- * <ProductCard.Name linkable />
6238
- * <ProductCard.Sku />
6239
- * <ProductCard.Price />
6240
- * <ProductCard.AddToCart />
6241
- * </ProductCard>
6242
- * ```
6312
+ * Renders combo-deal bundles for a product, each with its constituent items,
6313
+ * pricing and savings, plus add-to-cart with toast or modal feedback.
6243
6314
  *
6244
- * **Legacy mode (deprecated — kept for back-compat):**
6245
- * ```tsx
6246
- * <ProductCard product={p} showName showPrice allowAddToCart />
6247
- * ```
6248
- * All the show* / allow* toggles still work and render the canonical layout
6249
- * using the compound subcomponents internally.
6315
+ * @remarks Uses {@link useProductBundles} to fetch bundles and add them to a cart.
6250
6316
  */
6251
- type ProductCardComponent = typeof MemoizedRoot & {
6252
- Image: typeof ProductCardImage;
6253
- Badges: typeof ProductCardBadges;
6254
- Favorite: typeof ProductCardFavorite;
6255
- Name: typeof ProductCardName;
6256
- Sku: typeof ProductCardSku;
6257
- BelowName: typeof ProductCardBelowName;
6258
- ShortDescription: typeof ProductCardShortDescription;
6259
- Manufacturer: typeof ProductCardManufacturer;
6260
- TextLabels: typeof ProductCardTextLabels;
6261
- Stock: typeof ProductCardStock;
6262
- Price: typeof ProductCardPrice;
6263
- AddToCart: typeof ProductCardAddToCart;
6264
- };
6265
- declare const ProductCard: ProductCardComponent;
6317
+ declare function ProductBundles(rawProps: ProductBundlesProps): React$1.JSX.Element;
6266
6318
 
6267
6319
  /**
6268
6320
  * @rsc-blocked — Client-only component: interactive state (useState/useReducer).
@@ -6672,6 +6724,15 @@ interface ProductInfoProps {
6672
6724
  * Example: ['brand', 'color']
6673
6725
  */
6674
6726
  textLabels?: string[];
6727
+ /** Cart to add into. Omit and pass `createCart` to start one on first add. */
6728
+ cartId?: string;
6729
+ /** If true a new cart is created when no `cartId` is available. */
6730
+ createCart?: boolean;
6731
+ /**
6732
+ * Called when a new cart is created, so the host can persist `cart.cartId`.
6733
+ * WARNING: without it a new cart is created on every add.
6734
+ */
6735
+ onCartCreated?: (cart: Cart) => void;
6675
6736
  showImage?: boolean;
6676
6737
  showBadges?: boolean;
6677
6738
  showFavorite?: boolean;
@@ -6815,6 +6876,12 @@ interface ProductPriceProps {
6815
6876
  * Defaults to 'open'.
6816
6877
  */
6817
6878
  portalMode?: string;
6879
+ /**
6880
+ * Whether a session exists, independent of whether `user` has loaded yet.
6881
+ * Passed down by the parent alongside `portalMode`; closes the hydration
6882
+ * window in which an authenticated visitor still has a null `user`.
6883
+ */
6884
+ isAuthenticated?: boolean;
6818
6885
  /** Authenticated user — used for semi-closed visibility. */
6819
6886
  user?: Contact | Customer | null;
6820
6887
  /**