@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/dist/index.d.cts CHANGED
@@ -219,6 +219,13 @@ interface ProductGridConfig {
219
219
  clusterCardComponent?: ComponentType<{
220
220
  cluster: Cluster;
221
221
  } & Record<string, unknown>>;
222
+ /**
223
+ * Render arbitrary content directly below each card's product name. Receives
224
+ * the product; return `null`/`undefined` to render nothing. Lets hosts surface
225
+ * extra per-product info (e.g. package descriptions) across the whole grid
226
+ * without swapping the entire card.
227
+ */
228
+ belowName?: (product: Product) => ReactNode;
222
229
  }
223
230
  /**
224
231
  * Provider — ProductGrid wraps its card subtree with this so descendant
@@ -5193,6 +5200,14 @@ interface ProductCardProps {
5193
5200
  badgesComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.BadgesComponentProps>;
5194
5201
  /** Replaces the default favorite-toggle. Receives `FavoriteComponentProps`. */
5195
5202
  favoriteComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.FavoriteComponentProps>;
5203
+ /**
5204
+ * Render arbitrary content directly below the product name (and above the
5205
+ * short description / price), in both the grid and row layouts. Receives the
5206
+ * product so hosts can surface extra info — e.g. package descriptions, custom
5207
+ * badges — without forking the card. Return `null`/`undefined` to render
5208
+ * nothing for a given product.
5209
+ */
5210
+ belowName?: (product: Product) => React$1.ReactNode;
5196
5211
  }
5197
5212
  /**
5198
5213
  * Root of the `<ProductCard>` compound component. Renders either the
@@ -5270,6 +5285,10 @@ declare function ProductCardStock(props: {
5270
5285
  /** Show only the availability indicator. Defaults to context `showAvailability`. */
5271
5286
  showAvailability?: boolean;
5272
5287
  }): React$1.JSX.Element | null;
5288
+ /** Renders host-supplied content directly below the name, via the `belowName`
5289
+ * render prop (explicit or from grid context). Renders nothing when unset or
5290
+ * when the render prop returns a nullish value. */
5291
+ declare function ProductCardBelowName(): React$1.JSX.Element | null;
5273
5292
  /** Renders the product price via the embedded `ProductPrice` component, or
5274
5293
  * the injected `priceComponent` from context when set. */
5275
5294
  declare function ProductCardPrice(props: {
@@ -5317,6 +5336,7 @@ type ProductCardComponent = typeof MemoizedRoot & {
5317
5336
  Favorite: typeof ProductCardFavorite;
5318
5337
  Name: typeof ProductCardName;
5319
5338
  Sku: typeof ProductCardSku;
5339
+ BelowName: typeof ProductCardBelowName;
5320
5340
  ShortDescription: typeof ProductCardShortDescription;
5321
5341
  Manufacturer: typeof ProductCardManufacturer;
5322
5342
  TextLabels: typeof ProductCardTextLabels;
@@ -5583,6 +5603,14 @@ interface ProductGridProps {
5583
5603
  favoriteComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.FavoriteComponentProps>;
5584
5604
  productCardComponent?: React$1.ComponentType<ProductCardProps>;
5585
5605
  clusterCardComponent?: React$1.ComponentType<ClusterCardProps>;
5606
+ /**
5607
+ * Render arbitrary content directly below each card's product name (and above
5608
+ * the short description / price). Receives the product; return `null` to
5609
+ * render nothing. Cascades to every ProductCard via ProductGridConfig — lets
5610
+ * hosts surface extra per-product info (e.g. package descriptions) without
5611
+ * swapping the whole card.
5612
+ */
5613
+ belowName?: (product: Product) => React$1.ReactNode;
5586
5614
  beforeItem?: (item: Product | Cluster, index: number) => React$1.ReactNode;
5587
5615
  afterItem?: (item: Product | Cluster, index: number) => React$1.ReactNode;
5588
5616
  }
@@ -6049,6 +6077,27 @@ interface ProductSpecificationsProps {
6049
6077
  * rendered above the attribute table. Omitted when empty.
6050
6078
  */
6051
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;
6052
6101
  /** Extra CSS class applied to the root element. */
6053
6102
  className?: string;
6054
6103
  }
@@ -6152,6 +6201,22 @@ interface ProductTabsProps {
6152
6201
  * ProductSpecifications.
6153
6202
  */
6154
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;
6155
6220
  /**
6156
6221
  * Override UI strings for the Downloads section.
6157
6222
  * Available keys: title, download
package/dist/index.d.ts CHANGED
@@ -219,6 +219,13 @@ interface ProductGridConfig {
219
219
  clusterCardComponent?: ComponentType<{
220
220
  cluster: Cluster;
221
221
  } & Record<string, unknown>>;
222
+ /**
223
+ * Render arbitrary content directly below each card's product name. Receives
224
+ * the product; return `null`/`undefined` to render nothing. Lets hosts surface
225
+ * extra per-product info (e.g. package descriptions) across the whole grid
226
+ * without swapping the entire card.
227
+ */
228
+ belowName?: (product: Product) => ReactNode;
222
229
  }
223
230
  /**
224
231
  * Provider — ProductGrid wraps its card subtree with this so descendant
@@ -5193,6 +5200,14 @@ interface ProductCardProps {
5193
5200
  badgesComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.BadgesComponentProps>;
5194
5201
  /** Replaces the default favorite-toggle. Receives `FavoriteComponentProps`. */
5195
5202
  favoriteComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.FavoriteComponentProps>;
5203
+ /**
5204
+ * Render arbitrary content directly below the product name (and above the
5205
+ * short description / price), in both the grid and row layouts. Receives the
5206
+ * product so hosts can surface extra info — e.g. package descriptions, custom
5207
+ * badges — without forking the card. Return `null`/`undefined` to render
5208
+ * nothing for a given product.
5209
+ */
5210
+ belowName?: (product: Product) => React$1.ReactNode;
5196
5211
  }
5197
5212
  /**
5198
5213
  * Root of the `<ProductCard>` compound component. Renders either the
@@ -5270,6 +5285,10 @@ declare function ProductCardStock(props: {
5270
5285
  /** Show only the availability indicator. Defaults to context `showAvailability`. */
5271
5286
  showAvailability?: boolean;
5272
5287
  }): React$1.JSX.Element | null;
5288
+ /** Renders host-supplied content directly below the name, via the `belowName`
5289
+ * render prop (explicit or from grid context). Renders nothing when unset or
5290
+ * when the render prop returns a nullish value. */
5291
+ declare function ProductCardBelowName(): React$1.JSX.Element | null;
5273
5292
  /** Renders the product price via the embedded `ProductPrice` component, or
5274
5293
  * the injected `priceComponent` from context when set. */
5275
5294
  declare function ProductCardPrice(props: {
@@ -5317,6 +5336,7 @@ type ProductCardComponent = typeof MemoizedRoot & {
5317
5336
  Favorite: typeof ProductCardFavorite;
5318
5337
  Name: typeof ProductCardName;
5319
5338
  Sku: typeof ProductCardSku;
5339
+ BelowName: typeof ProductCardBelowName;
5320
5340
  ShortDescription: typeof ProductCardShortDescription;
5321
5341
  Manufacturer: typeof ProductCardManufacturer;
5322
5342
  TextLabels: typeof ProductCardTextLabels;
@@ -5583,6 +5603,14 @@ interface ProductGridProps {
5583
5603
  favoriteComponent?: React$1.ComponentType<_propeller_commerce_propeller_v2_core_ui.FavoriteComponentProps>;
5584
5604
  productCardComponent?: React$1.ComponentType<ProductCardProps>;
5585
5605
  clusterCardComponent?: React$1.ComponentType<ClusterCardProps>;
5606
+ /**
5607
+ * Render arbitrary content directly below each card's product name (and above
5608
+ * the short description / price). Receives the product; return `null` to
5609
+ * render nothing. Cascades to every ProductCard via ProductGridConfig — lets
5610
+ * hosts surface extra per-product info (e.g. package descriptions) without
5611
+ * swapping the whole card.
5612
+ */
5613
+ belowName?: (product: Product) => React$1.ReactNode;
5586
5614
  beforeItem?: (item: Product | Cluster, index: number) => React$1.ReactNode;
5587
5615
  afterItem?: (item: Product | Cluster, index: number) => React$1.ReactNode;
5588
5616
  }
@@ -6049,6 +6077,27 @@ interface ProductSpecificationsProps {
6049
6077
  * rendered above the attribute table. Omitted when empty.
6050
6078
  */
6051
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;
6052
6101
  /** Extra CSS class applied to the root element. */
6053
6102
  className?: string;
6054
6103
  }
@@ -6152,6 +6201,22 @@ interface ProductTabsProps {
6152
6201
  * ProductSpecifications.
6153
6202
  */
6154
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;
6155
6220
  /**
6156
6221
  * Override UI strings for the Downloads section.
6157
6222
  * Available keys: title, download
package/dist/index.js CHANGED
@@ -13302,7 +13302,8 @@ var RESOLVE_SPEC2 = {
13302
13302
  addToCartComponent: { grid: "addToCartComponent" },
13303
13303
  imageComponent: { grid: "imageComponent" },
13304
13304
  badgesComponent: { grid: "badgesComponent" },
13305
- favoriteComponent: { grid: "favoriteComponent" }
13305
+ favoriteComponent: { grid: "favoriteComponent" },
13306
+ belowName: { grid: "belowName" }
13306
13307
  };
13307
13308
  var ProductCardContext = createContext(null);
13308
13309
  function useProductCard() {
@@ -13383,6 +13384,7 @@ function ProductCardRoot(rawProps) {
13383
13384
  /* @__PURE__ */ 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__ */ jsxs("div", { className: "flex flex-col gap-0.5 flex-1 min-w-0", children: [
13384
13385
  props.showSku !== false && sku ? /* @__PURE__ */ jsx(ProductCardSku, {}) : null,
13385
13386
  props.showName !== false ? /* @__PURE__ */ jsx(ProductCardName, { linkable: true }) : null,
13387
+ /* @__PURE__ */ jsx(ProductCardBelowName, {}),
13386
13388
  textLabelValues.length > 0 ? /* @__PURE__ */ jsx(ProductCardTextLabels, {}) : null,
13387
13389
  props.showManufacturer && manufacturer ? /* @__PURE__ */ jsx(ProductCardManufacturer, {}) : null,
13388
13390
  props.showShortDescription && shortDescription ? /* @__PURE__ */ jsx(ProductCardShortDescription, {}) : null
@@ -13398,6 +13400,7 @@ function ProductCardRoot(rawProps) {
13398
13400
  /* @__PURE__ */ jsxs("div", { className: "propeller-product-card__body flex flex-1 flex-col gap-1.5 p-3 sm:gap-2 sm:p-4", children: [
13399
13401
  props.showSku !== false && sku ? /* @__PURE__ */ jsx(ProductCardSku, {}) : null,
13400
13402
  props.showName !== false ? /* @__PURE__ */ jsx(ProductCardName, { linkable: true }) : null,
13403
+ /* @__PURE__ */ jsx(ProductCardBelowName, {}),
13401
13404
  textLabelValues.length > 0 ? /* @__PURE__ */ jsx(ProductCardTextLabels, {}) : null,
13402
13405
  props.showStock && !!product.inventory ? /* @__PURE__ */ jsx("div", { className: "hidden md:block", children: /* @__PURE__ */ jsx(ProductCardStock, {}) }) : null,
13403
13406
  props.showManufacturer && manufacturer ? /* @__PURE__ */ jsx(ProductCardManufacturer, {}) : null,
@@ -13580,6 +13583,13 @@ function ProductCardStock(props) {
13580
13583
  }
13581
13584
  );
13582
13585
  }
13586
+ function ProductCardBelowName() {
13587
+ const { product, resolved } = useProductCard();
13588
+ if (!resolved.belowName) return null;
13589
+ const content = resolved.belowName(product);
13590
+ if (content == null || content === false) return null;
13591
+ return /* @__PURE__ */ jsx("div", { className: "propeller-product-card__below-name", children: content });
13592
+ }
13583
13593
  function ProductCardPrice(props) {
13584
13594
  const { product, derived, resolved } = useProductCard();
13585
13595
  if (!product?.price) return null;
@@ -13658,6 +13668,7 @@ ProductCard.Badges = ProductCardBadges;
13658
13668
  ProductCard.Favorite = ProductCardFavorite;
13659
13669
  ProductCard.Name = ProductCardName;
13660
13670
  ProductCard.Sku = ProductCardSku;
13671
+ ProductCard.BelowName = ProductCardBelowName;
13661
13672
  ProductCard.ShortDescription = ProductCardShortDescription;
13662
13673
  ProductCard.Manufacturer = ProductCardManufacturer;
13663
13674
  ProductCard.TextLabels = ProductCardTextLabels;
@@ -14040,7 +14051,8 @@ function ProductGrid(rawProps) {
14040
14051
  badgesComponent: props.badgesComponent,
14041
14052
  favoriteComponent: props.favoriteComponent,
14042
14053
  productCardComponent: props.productCardComponent,
14043
- clusterCardComponent: props.clusterCardComponent
14054
+ clusterCardComponent: props.clusterCardComponent,
14055
+ belowName: props.belowName
14044
14056
  };
14045
14057
  if (rawProps.children !== void 0) {
14046
14058
  const compoundCtx = {
@@ -15083,7 +15095,11 @@ function ProductSpecifications(rawProps) {
15083
15095
  if (!props.productId || !props.graphqlClient) return;
15084
15096
  fetchSpecs(props.productId);
15085
15097
  }, [props.productId]);
15086
- 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(
15087
15103
  "div",
15088
15104
  {
15089
15105
  className: `propeller-product-specifications ${props.className || ""}`,
@@ -15092,26 +15108,38 @@ function ProductSpecifications(rawProps) {
15092
15108
  children: [
15093
15109
  props.packageDescription ? /* @__PURE__ */ jsx("p", { className: "propeller-product-specifications__package-description mb-4 text-sm text-muted-foreground", children: props.packageDescription }) : null,
15094
15110
  !props.grouping ? /* @__PURE__ */ jsxs(Fragment, { children: [
15095
- 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: [
15096
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15097
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15098
- ] }, i)) }) }) }) : null,
15099
- 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: [
15100
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15101
- /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15102
- ] }, 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
15103
15127
  ] }) : null,
15104
- !!props.grouping ? /* @__PURE__ */ jsx(Fragment, { children: getGroups()?.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
15105
- !!group ? /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-2", children: group }) : null,
15106
- 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: [
15107
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 font-medium text-foreground w-1/2", children: getAttributeLabel(attr) }),
15108
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground", children: getAttributeValue(attr) })
15109
- ] }, i)) }) }) }) : null,
15110
- 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: [
15111
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: getAttributeLabel(attr) }),
15112
- /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: getAttributeValue(attr) })
15113
- ] }, i)) }) : null
15114
- ] }, 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
15115
15143
  ]
15116
15144
  }
15117
15145
  ) }) : null });
@@ -15320,7 +15348,9 @@ function ProductTabs(rawProps) {
15320
15348
  language: props.language,
15321
15349
  layout: props.specificationsLayout,
15322
15350
  grouping: props.specificationsGrouping,
15323
- packageDescription: props.specificationsPackageDescription
15351
+ packageDescription: props.specificationsPackageDescription,
15352
+ beforeSpecs: props.specificationsBeforeSpecs,
15353
+ afterSpecs: props.specificationsAfterSpecs
15324
15354
  }
15325
15355
  ) }) : null,
15326
15356
  isActive("downloads") && isTabVisible("downloads") ? /* @__PURE__ */ jsx(