@propeller-commerce/propeller-v2-react-ui 0.4.17 → 0.4.19

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
@@ -6077,6 +6077,27 @@ interface ProductSpecificationsProps {
6077
6077
  * rendered above the attribute table. Omitted when empty.
6078
6078
  */
6079
6079
  packageDescription?: string;
6080
+ /**
6081
+ * Render arbitrary content at the START of the specifications, before the
6082
+ * fetched attribute rows. Receives the active `layout` so the consumer can
6083
+ * return markup that fits the container:
6084
+ * - `'table'` → return one or more `<tr>` (injected inside `<tbody>`), e.g.
6085
+ * a labelled "Unit of measure" row.
6086
+ * - `'list'` → return block element(s) (injected inside the list stack).
6087
+ * In grouped mode it renders once, above the first group. Return
6088
+ * `null`/`undefined` to render nothing.
6089
+ */
6090
+ beforeSpecs?: (ctx: {
6091
+ layout: 'table' | 'list';
6092
+ }) => React$1.ReactNode;
6093
+ /**
6094
+ * Render arbitrary content at the END of the specifications, after the
6095
+ * fetched attribute rows. Same `layout` contract as {@link beforeSpecs}.
6096
+ * In grouped mode it renders once, below the last group.
6097
+ */
6098
+ afterSpecs?: (ctx: {
6099
+ layout: 'table' | 'list';
6100
+ }) => React$1.ReactNode;
6080
6101
  /** Extra CSS class applied to the root element. */
6081
6102
  className?: string;
6082
6103
  }
@@ -6180,6 +6201,22 @@ interface ProductTabsProps {
6180
6201
  * ProductSpecifications.
6181
6202
  */
6182
6203
  specificationsPackageDescription?: string;
6204
+ /**
6205
+ * Render arbitrary content at the start of the specifications section (before
6206
+ * the attribute rows) — e.g. a labelled "Unit of measure" row. Passed as
6207
+ * `beforeSpecs` to ProductSpecifications; receives the active `layout` so the
6208
+ * consumer can return a `<tr>` (table) or block (list).
6209
+ */
6210
+ specificationsBeforeSpecs?: (ctx: {
6211
+ layout: 'table' | 'list';
6212
+ }) => React$1.ReactNode;
6213
+ /**
6214
+ * Render arbitrary content at the end of the specifications section (after the
6215
+ * attribute rows). Passed as `afterSpecs` to ProductSpecifications.
6216
+ */
6217
+ specificationsAfterSpecs?: (ctx: {
6218
+ layout: 'table' | 'list';
6219
+ }) => React$1.ReactNode;
6183
6220
  /**
6184
6221
  * Override UI strings for the Downloads section.
6185
6222
  * Available keys: title, download
package/dist/index.d.ts CHANGED
@@ -6077,6 +6077,27 @@ interface ProductSpecificationsProps {
6077
6077
  * rendered above the attribute table. Omitted when empty.
6078
6078
  */
6079
6079
  packageDescription?: string;
6080
+ /**
6081
+ * Render arbitrary content at the START of the specifications, before the
6082
+ * fetched attribute rows. Receives the active `layout` so the consumer can
6083
+ * return markup that fits the container:
6084
+ * - `'table'` → return one or more `<tr>` (injected inside `<tbody>`), e.g.
6085
+ * a labelled "Unit of measure" row.
6086
+ * - `'list'` → return block element(s) (injected inside the list stack).
6087
+ * In grouped mode it renders once, above the first group. Return
6088
+ * `null`/`undefined` to render nothing.
6089
+ */
6090
+ beforeSpecs?: (ctx: {
6091
+ layout: 'table' | 'list';
6092
+ }) => React$1.ReactNode;
6093
+ /**
6094
+ * Render arbitrary content at the END of the specifications, after the
6095
+ * fetched attribute rows. Same `layout` contract as {@link beforeSpecs}.
6096
+ * In grouped mode it renders once, below the last group.
6097
+ */
6098
+ afterSpecs?: (ctx: {
6099
+ layout: 'table' | 'list';
6100
+ }) => React$1.ReactNode;
6080
6101
  /** Extra CSS class applied to the root element. */
6081
6102
  className?: string;
6082
6103
  }
@@ -6180,6 +6201,22 @@ interface ProductTabsProps {
6180
6201
  * ProductSpecifications.
6181
6202
  */
6182
6203
  specificationsPackageDescription?: string;
6204
+ /**
6205
+ * Render arbitrary content at the start of the specifications section (before
6206
+ * the attribute rows) — e.g. a labelled "Unit of measure" row. Passed as
6207
+ * `beforeSpecs` to ProductSpecifications; receives the active `layout` so the
6208
+ * consumer can return a `<tr>` (table) or block (list).
6209
+ */
6210
+ specificationsBeforeSpecs?: (ctx: {
6211
+ layout: 'table' | 'list';
6212
+ }) => React$1.ReactNode;
6213
+ /**
6214
+ * Render arbitrary content at the end of the specifications section (after the
6215
+ * attribute rows). Passed as `afterSpecs` to ProductSpecifications.
6216
+ */
6217
+ specificationsAfterSpecs?: (ctx: {
6218
+ layout: 'table' | 'list';
6219
+ }) => React$1.ReactNode;
6183
6220
  /**
6184
6221
  * Override UI strings for the Downloads section.
6185
6222
  * Available keys: title, download
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { getLabel, formatPrice, getClusterSku, getClusterImageUrl, getProductSku, getProductImageUrl, createServices, err, ok, getLocalizedValue, formatSurcharge, deriveUserMode, isContact, isCustomer, getAddresses, isContentHidden, attributeNameMatches, getAttributeDisplayName, filterProductsBySelections, extractAttributeValues, getLanguageString, collectAttributeValues, getCountryName } from '@propeller-commerce/propeller-v2-core-ui';
2
+ import { getLabel, getLocalizedValue, formatPrice, getClusterSku, getClusterImageUrl, getProductSku, getProductImageUrl, createServices, err, ok, formatSurcharge, deriveUserMode, isContact, isCustomer, getAddresses, isContentHidden, attributeNameMatches, getAttributeDisplayName, filterProductsBySelections, extractAttributeValues, 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';
@@ -3934,7 +3934,7 @@ function AddToCart(rawProps) {
3934
3934
  }
3935
3935
  function getModalName() {
3936
3936
  if (addedCartItem) {
3937
- return addedCartItem.product?.names?.[0]?.value || getLocalizedValue(props.product?.names, props.language, "Product");
3937
+ return getLocalizedValue(addedCartItem.product?.names, props.language, "") || getLocalizedValue(props.product?.names, props.language, "Product");
3938
3938
  }
3939
3939
  return getLocalizedValue(props.product?.names, props.language, "Product");
3940
3940
  }
@@ -4287,7 +4287,7 @@ function AddToCart(rawProps) {
4287
4287
  {
4288
4288
  className: "propeller-add-to-cart__modal-child flex justify-between items-center text-xs text-muted-foreground",
4289
4289
  children: [
4290
- /* @__PURE__ */ jsx("span", { className: "line-clamp-1", children: child.product?.names?.[0]?.value || "Option" }),
4290
+ /* @__PURE__ */ jsx("span", { className: "line-clamp-1", children: getLocalizedValue(child.product?.names, props.language, "Option") }),
4291
4291
  /* @__PURE__ */ jsx("span", { className: "text-foreground-subtle whitespace-nowrap ml-2", children: formatPrice(
4292
4292
  ((props.includeTax !== void 0 ? !!props.includeTax : includeTax) ? child.totalSumNet : child.totalSum) ?? 0,
4293
4293
  { symbol: props.currency ?? "\u20AC" }
@@ -6040,7 +6040,7 @@ function CartItem(rawProps) {
6040
6040
  const minQuantity = getMinQuantity(props.cartItem.product);
6041
6041
  const step = getStep(props.cartItem.product);
6042
6042
  function getProductName5() {
6043
- return props.cartItem.product?.names?.[0]?.value || "Product";
6043
+ return getLocalizedValue(props.cartItem.product?.names, props.language, "Product");
6044
6044
  }
6045
6045
  function getProductUrl2() {
6046
6046
  if (props.configuration?.urls && props.cartItem.product) {
@@ -6091,7 +6091,7 @@ function CartItem(rawProps) {
6091
6091
  if (!items) return "";
6092
6092
  const leader = items.find((bi) => bi.isLeader === YesNo.Y);
6093
6093
  if (!leader) return "";
6094
- return leader.product.names?.[0]?.value || "Product";
6094
+ return getLocalizedValue(leader.product.names, props.language, "Product");
6095
6095
  }
6096
6096
  function getBundleLeaderPrice() {
6097
6097
  const items = props.cartItem.bundle?.items;
@@ -6108,7 +6108,7 @@ function CartItem(rawProps) {
6108
6108
  return items.filter((bi) => bi.isLeader !== YesNo.Y);
6109
6109
  }
6110
6110
  function getBundleItemName(bundleItem) {
6111
- return bundleItem.product.names?.[0]?.value || "Product";
6111
+ return getLocalizedValue(bundleItem.product.names, props.language, "Product");
6112
6112
  }
6113
6113
  function getBundleItemPrice(bundleItem) {
6114
6114
  const price = bundleItem.price?.net;
@@ -6117,7 +6117,7 @@ function CartItem(rawProps) {
6117
6117
  }
6118
6118
  function getCrossupsellName(item) {
6119
6119
  const product = item?.productTo || item?.clusterTo;
6120
- return product?.names?.[0]?.value || "Product";
6120
+ return getLocalizedValue(product?.names, props.language, "Product");
6121
6121
  }
6122
6122
  function getCrossupsellImageUrl(item) {
6123
6123
  const product = item?.productTo || item?.clusterTo;
@@ -6442,7 +6442,7 @@ function CartItemChildItems(props = {}) {
6442
6442
  return /* @__PURE__ */ jsxs("div", { className: props.className ?? "propeller-cart-item__options mt-3 space-y-1.5 border-l-2 border-border pl-3", children: [
6443
6443
  /* @__PURE__ */ jsx("p", { className: "propeller-cart-item__options-label text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1", children: getLabel(resolved.labels, "includedOptions", "Included Options:") }),
6444
6444
  (cartItem.childItems || []).map((child, idx) => /* @__PURE__ */ jsxs("div", { className: "propeller-cart-item__option flex flex-wrap gap-x-2 text-sm text-muted-foreground", children: [
6445
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: child.product.names?.[0]?.value || "Option" }),
6445
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: getLocalizedValue(child.product.names, resolved.language, "Option") }),
6446
6446
  /* @__PURE__ */ jsx("span", { className: "text-foreground-subtle hidden sm:inline", children: "-" }),
6447
6447
  /* @__PURE__ */ jsx("span", { className: "text-foreground-subtle text-xs self-center", children: child.product.sku }),
6448
6448
  /* @__PURE__ */ jsx("div", { className: "flex-1 border-b border-dotted border-border mx-1 mb-1" }),
@@ -15095,7 +15095,11 @@ function ProductSpecifications(rawProps) {
15095
15095
  if (!props.productId || !props.graphqlClient) return;
15096
15096
  fetchSpecs(props.productId);
15097
15097
  }, [props.productId]);
15098
- return /* @__PURE__ */ jsx(Fragment, { children: !loading && (hasPublicAttributes() || props.packageDescription) ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(
15098
+ const layoutMode = props.layout === "list" ? "list" : "table";
15099
+ const before = props.beforeSpecs?.({ layout: layoutMode });
15100
+ const after = props.afterSpecs?.({ layout: layoutMode });
15101
+ const hasSlotContent = before != null || after != null;
15102
+ return /* @__PURE__ */ jsx(Fragment, { children: !loading && (hasPublicAttributes() || props.packageDescription || hasSlotContent) ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(
15099
15103
  "div",
15100
15104
  {
15101
15105
  className: `propeller-product-specifications ${props.className || ""}`,
@@ -15104,26 +15108,38 @@ function ProductSpecifications(rawProps) {
15104
15108
  children: [
15105
15109
  props.packageDescription ? /* @__PURE__ */ jsx("p", { className: "propeller-product-specifications__package-description mb-4 text-sm text-muted-foreground", children: props.packageDescription }) : null,
15106
15110
  !props.grouping ? /* @__PURE__ */ jsxs(Fragment, { children: [
15107
- props.layout !== "list" ? /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-border", children: getAttributes()?.map((attr, i) => /* @__PURE__ */ jsxs("tr", { className: "propeller-product-specifications__row odd:bg-card even:bg-muted/20", children: [
15108
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15109
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15110
- ] }, i)) }) }) }) : null,
15111
- props.layout === "list" ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: getAttributes()?.map((attr, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
15112
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15113
- /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15114
- ] }, i)) }) : null
15111
+ props.layout !== "list" ? /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsxs("tbody", { className: "divide-y divide-border", children: [
15112
+ before,
15113
+ getAttributes()?.map((attr, i) => /* @__PURE__ */ jsxs("tr", { className: "propeller-product-specifications__row odd:bg-card even:bg-muted/20", children: [
15114
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15115
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15116
+ ] }, i)),
15117
+ after
15118
+ ] }) }) }) : null,
15119
+ props.layout === "list" ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
15120
+ before,
15121
+ getAttributes()?.map((attr, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
15122
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15123
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15124
+ ] }, i)),
15125
+ after
15126
+ ] }) : null
15115
15127
  ] }) : null,
15116
- !!props.grouping ? /* @__PURE__ */ jsx(Fragment, { children: getGroups()?.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
15117
- !!group ? /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-2", children: group }) : null,
15118
- props.layout !== "list" ? /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-border", children: getAttributesByGroup(group)?.map((attr, i) => /* @__PURE__ */ jsxs("tr", { className: "propeller-product-specifications__row odd:bg-card even:bg-muted/20", children: [
15119
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15120
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15121
- ] }, i)) }) }) }) : null,
15122
- props.layout === "list" ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: getAttributesByGroup(group)?.map((attr, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
15123
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15124
- /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15125
- ] }, i)) }) : null
15126
- ] }, group)) }) : null
15128
+ !!props.grouping ? /* @__PURE__ */ jsxs(Fragment, { children: [
15129
+ before ? layoutMode === "table" ? /* @__PURE__ */ jsx("div", { className: "mb-6 overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-border", children: before }) }) }) : /* @__PURE__ */ jsx("div", { className: "mb-6 space-y-3", children: before }) : null,
15130
+ getGroups()?.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
15131
+ !!group ? /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-2", children: group }) : null,
15132
+ props.layout !== "list" ? /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-border", children: getAttributesByGroup(group)?.map((attr, i) => /* @__PURE__ */ jsxs("tr", { className: "propeller-product-specifications__row odd:bg-card even:bg-muted/20", children: [
15133
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15134
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15135
+ ] }, i)) }) }) }) : null,
15136
+ props.layout === "list" ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: getAttributesByGroup(group)?.map((attr, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
15137
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15138
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15139
+ ] }, i)) }) : null
15140
+ ] }, group)),
15141
+ after ? layoutMode === "table" ? /* @__PURE__ */ jsx("div", { className: "mb-6 overflow-hidden rounded-lg border border-border", children: /* @__PURE__ */ jsx("table", { className: "w-full text-sm", children: /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-border", children: after }) }) }) : /* @__PURE__ */ jsx("div", { className: "space-y-3", children: after }) : null
15142
+ ] }) : null
15127
15143
  ]
15128
15144
  }
15129
15145
  ) }) : null });
@@ -15332,7 +15348,9 @@ function ProductTabs(rawProps) {
15332
15348
  language: props.language,
15333
15349
  layout: props.specificationsLayout,
15334
15350
  grouping: props.specificationsGrouping,
15335
- packageDescription: props.specificationsPackageDescription
15351
+ packageDescription: props.specificationsPackageDescription,
15352
+ beforeSpecs: props.specificationsBeforeSpecs,
15353
+ afterSpecs: props.specificationsAfterSpecs
15336
15354
  }
15337
15355
  ) }) : null,
15338
15356
  isActive("downloads") && isTabVisible("downloads") ? /* @__PURE__ */ jsx(