@propeller-commerce/propeller-v2-react-ui 0.4.16 → 0.4.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,32 @@ once it reaches 1.0. Until then (the `0.x` line) the public API may change
8
8
  between minor versions; breaking changes are called out below and in
9
9
  [MIGRATION.md](./MIGRATION.md).
10
10
 
11
+ ## [0.4.18] - 2026-07-10
12
+
13
+ ### Added
14
+
15
+ - `ProductSpecifications`: new `beforeSpecs?` / `afterSpecs?` render-prop slots.
16
+ Each receives `{ layout: 'table' | 'list' }` and returns arbitrary content
17
+ injected at the start / end of the specifications — inside `<tbody>` for the
18
+ table layout (return a `<tr>`, e.g. a labelled "Unit of measure" row) or in
19
+ the list stack for the list layout (return a block). In grouped mode they
20
+ render once, above the first group / below the last. General replacement for
21
+ hardcoding extra rows; the existing `packageDescription` prop is unchanged.
22
+ - `ProductTabs`: `specificationsBeforeSpecs?` / `specificationsAfterSpecs?`
23
+ passthrough props that forward to the specifications section's new slots.
24
+
25
+ ## [0.4.17] - 2026-07-09
26
+
27
+ ### Added
28
+
29
+ - `ProductCard` / `ProductGrid`: new `belowName?: (product: Product) => ReactNode`
30
+ render prop. Renders arbitrary host-supplied content directly below the
31
+ product name (and above the short description / price) in both the grid and
32
+ row layouts, without forking the card — e.g. package descriptions or custom
33
+ badges. On `ProductGrid` it cascades to every card via `ProductGridConfig`;
34
+ on `ProductCard` it can also be set per-card (explicit prop wins over grid
35
+ context). Also exposed as the `<ProductCard.BelowName>` compound subcomponent.
36
+
11
37
  ## [0.4.16] - 2026-07-09
12
38
 
13
39
  ### Added
package/dist/index.cjs CHANGED
@@ -13322,7 +13322,8 @@ var RESOLVE_SPEC2 = {
13322
13322
  addToCartComponent: { grid: "addToCartComponent" },
13323
13323
  imageComponent: { grid: "imageComponent" },
13324
13324
  badgesComponent: { grid: "badgesComponent" },
13325
- favoriteComponent: { grid: "favoriteComponent" }
13325
+ favoriteComponent: { grid: "favoriteComponent" },
13326
+ belowName: { grid: "belowName" }
13326
13327
  };
13327
13328
  var ProductCardContext = React6.createContext(null);
13328
13329
  function useProductCard() {
@@ -13403,6 +13404,7 @@ function ProductCardRoot(rawProps) {
13403
13404
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-product-card__body flex flex-1 flex-row items-center gap-4 px-4 py-2 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5 flex-1 min-w-0", children: [
13404
13405
  props.showSku !== false && sku ? /* @__PURE__ */ jsxRuntime.jsx(ProductCardSku, {}) : null,
13405
13406
  props.showName !== false ? /* @__PURE__ */ jsxRuntime.jsx(ProductCardName, { linkable: true }) : null,
13407
+ /* @__PURE__ */ jsxRuntime.jsx(ProductCardBelowName, {}),
13406
13408
  textLabelValues.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ProductCardTextLabels, {}) : null,
13407
13409
  props.showManufacturer && manufacturer ? /* @__PURE__ */ jsxRuntime.jsx(ProductCardManufacturer, {}) : null,
13408
13410
  props.showShortDescription && shortDescription ? /* @__PURE__ */ jsxRuntime.jsx(ProductCardShortDescription, {}) : null
@@ -13418,6 +13420,7 @@ function ProductCardRoot(rawProps) {
13418
13420
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "propeller-product-card__body flex flex-1 flex-col gap-1.5 p-3 sm:gap-2 sm:p-4", children: [
13419
13421
  props.showSku !== false && sku ? /* @__PURE__ */ jsxRuntime.jsx(ProductCardSku, {}) : null,
13420
13422
  props.showName !== false ? /* @__PURE__ */ jsxRuntime.jsx(ProductCardName, { linkable: true }) : null,
13423
+ /* @__PURE__ */ jsxRuntime.jsx(ProductCardBelowName, {}),
13421
13424
  textLabelValues.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ProductCardTextLabels, {}) : null,
13422
13425
  props.showStock && !!product.inventory ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden md:block", children: /* @__PURE__ */ jsxRuntime.jsx(ProductCardStock, {}) }) : null,
13423
13426
  props.showManufacturer && manufacturer ? /* @__PURE__ */ jsxRuntime.jsx(ProductCardManufacturer, {}) : null,
@@ -13600,6 +13603,13 @@ function ProductCardStock(props) {
13600
13603
  }
13601
13604
  );
13602
13605
  }
13606
+ function ProductCardBelowName() {
13607
+ const { product, resolved } = useProductCard();
13608
+ if (!resolved.belowName) return null;
13609
+ const content = resolved.belowName(product);
13610
+ if (content == null || content === false) return null;
13611
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "propeller-product-card__below-name", children: content });
13612
+ }
13603
13613
  function ProductCardPrice(props) {
13604
13614
  const { product, derived, resolved } = useProductCard();
13605
13615
  if (!product?.price) return null;
@@ -13678,6 +13688,7 @@ ProductCard.Badges = ProductCardBadges;
13678
13688
  ProductCard.Favorite = ProductCardFavorite;
13679
13689
  ProductCard.Name = ProductCardName;
13680
13690
  ProductCard.Sku = ProductCardSku;
13691
+ ProductCard.BelowName = ProductCardBelowName;
13681
13692
  ProductCard.ShortDescription = ProductCardShortDescription;
13682
13693
  ProductCard.Manufacturer = ProductCardManufacturer;
13683
13694
  ProductCard.TextLabels = ProductCardTextLabels;
@@ -14060,7 +14071,8 @@ function ProductGrid(rawProps) {
14060
14071
  badgesComponent: props.badgesComponent,
14061
14072
  favoriteComponent: props.favoriteComponent,
14062
14073
  productCardComponent: props.productCardComponent,
14063
- clusterCardComponent: props.clusterCardComponent
14074
+ clusterCardComponent: props.clusterCardComponent,
14075
+ belowName: props.belowName
14064
14076
  };
14065
14077
  if (rawProps.children !== void 0) {
14066
14078
  const compoundCtx = {
@@ -15103,7 +15115,11 @@ function ProductSpecifications(rawProps) {
15103
15115
  if (!props.productId || !props.graphqlClient) return;
15104
15116
  fetchSpecs(props.productId);
15105
15117
  }, [props.productId]);
15106
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: !loading && (hasPublicAttributes() || props.packageDescription) ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs(
15118
+ const layoutMode = props.layout === "list" ? "list" : "table";
15119
+ const before = props.beforeSpecs?.({ layout: layoutMode });
15120
+ const after = props.afterSpecs?.({ layout: layoutMode });
15121
+ const hasSlotContent = before != null || after != null;
15122
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: !loading && (hasPublicAttributes() || props.packageDescription || hasSlotContent) ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs(
15107
15123
  "div",
15108
15124
  {
15109
15125
  className: `propeller-product-specifications ${props.className || ""}`,
@@ -15112,26 +15128,38 @@ function ProductSpecifications(rawProps) {
15112
15128
  children: [
15113
15129
  props.packageDescription ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "propeller-product-specifications__package-description mb-4 text-sm text-muted-foreground", children: props.packageDescription }) : null,
15114
15130
  !props.grouping ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
15115
- props.layout !== "list" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-border", children: getAttributes()?.map((attr, i) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "propeller-product-specifications__row odd:bg-card even:bg-muted/20", children: [
15116
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15117
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15118
- ] }, i)) }) }) }) : null,
15119
- props.layout === "list" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: getAttributes()?.map((attr, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
15120
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15121
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15122
- ] }, i)) }) : null
15131
+ props.layout !== "list" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxRuntime.jsxs("tbody", { className: "divide-y divide-border", children: [
15132
+ before,
15133
+ getAttributes()?.map((attr, i) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "propeller-product-specifications__row odd:bg-card even:bg-muted/20", children: [
15134
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15135
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15136
+ ] }, i)),
15137
+ after
15138
+ ] }) }) }) : null,
15139
+ props.layout === "list" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
15140
+ before,
15141
+ getAttributes()?.map((attr, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
15142
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15143
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15144
+ ] }, i)),
15145
+ after
15146
+ ] }) : null
15123
15147
  ] }) : null,
15124
- !!props.grouping ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: getGroups()?.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
15125
- !!group ? /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-2", children: group }) : null,
15126
- props.layout !== "list" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-border", children: getAttributesByGroup(group)?.map((attr, i) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "propeller-product-specifications__row odd:bg-card even:bg-muted/20", children: [
15127
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15128
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15129
- ] }, i)) }) }) }) : null,
15130
- props.layout === "list" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: getAttributesByGroup(group)?.map((attr, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
15131
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15132
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15133
- ] }, i)) }) : null
15134
- ] }, group)) }) : null
15148
+ !!props.grouping ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
15149
+ before ? layoutMode === "table" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-border", children: before }) }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 space-y-3", children: before }) : null,
15150
+ getGroups()?.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
15151
+ !!group ? /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-2", children: group }) : null,
15152
+ props.layout !== "list" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-border", children: getAttributesByGroup(group)?.map((attr, i) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "propeller-product-specifications__row odd:bg-card even:bg-muted/20", children: [
15153
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15154
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15155
+ ] }, i)) }) }) }) : null,
15156
+ props.layout === "list" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: getAttributesByGroup(group)?.map((attr, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
15157
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15158
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15159
+ ] }, i)) }) : null
15160
+ ] }, group)),
15161
+ after ? layoutMode === "table" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-border", children: after }) }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: after }) : null
15162
+ ] }) : null
15135
15163
  ]
15136
15164
  }
15137
15165
  ) }) : null });
@@ -15340,7 +15368,9 @@ function ProductTabs(rawProps) {
15340
15368
  language: props.language,
15341
15369
  layout: props.specificationsLayout,
15342
15370
  grouping: props.specificationsGrouping,
15343
- packageDescription: props.specificationsPackageDescription
15371
+ packageDescription: props.specificationsPackageDescription,
15372
+ beforeSpecs: props.specificationsBeforeSpecs,
15373
+ afterSpecs: props.specificationsAfterSpecs
15344
15374
  }
15345
15375
  ) }) : null,
15346
15376
  isActive("downloads") && isTabVisible("downloads") ? /* @__PURE__ */ jsxRuntime.jsx(