@constructor-io/constructorio-ui-plp 1.6.4 → 1.6.6

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.
@@ -30,7 +30,10 @@ function ProductCard(props) {
30
30
  const onAddToCart = (0, callbacks_1.useOnAddToCart)(client, state.callbacks.onAddToCart);
31
31
  const { formatPrice } = state.formatters;
32
32
  const onClick = (0, callbacks_1.useOnProductCardClick)(client, state.callbacks.onProductCardClick);
33
- const cnstrcData = (0, utils_1.getProductCardCnstrcDataAttributes)(productInfo);
33
+ const cnstrcDataAttrs = (0, utils_1.getProductCardCnstrcDataAttributes)(productInfo, {
34
+ labels: item.labels,
35
+ });
36
+ const addToCartBtnAttrs = (0, utils_1.getConversionButtonCnstrcDataAttributes)('add_to_cart');
34
37
  const handleRolloverImageState = (isShown) => {
35
38
  var _a;
36
39
  setIsRolloverImageShown(isShown);
@@ -63,9 +66,9 @@ function ProductCard(props) {
63
66
  onMouseEnter,
64
67
  onMouseLeave,
65
68
  isRolloverImageShown,
66
- productCardCnstrcDataAttributes: cnstrcData,
67
- }, override: children, htmlOverride: (_a = state.renderOverrides.productCard) === null || _a === void 0 ? void 0 : _a.renderHtml, topLevelAttributes: Object.assign(Object.assign({}, cnstrcData), { className: 'cio-product-card' }) },
68
- react_1.default.createElement("a", Object.assign({}, cnstrcData, { className: 'cio-product-card', href: itemUrl, ref: cardRef, onClick: (e) => { var _a; return onClick(e, item, (_a = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _a === void 0 ? void 0 : _a.variationId); } }),
69
+ productCardCnstrcDataAttributes: cnstrcDataAttrs,
70
+ }, override: children, htmlOverride: (_a = state.renderOverrides.productCard) === null || _a === void 0 ? void 0 : _a.renderHtml, topLevelAttributes: Object.assign(Object.assign({}, cnstrcDataAttrs), { className: 'cio-product-card' }) },
71
+ react_1.default.createElement("a", Object.assign({}, cnstrcDataAttrs, { className: 'cio-product-card', href: itemUrl, ref: cardRef, onClick: (e) => { var _a; return onClick(e, item, (_a = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _a === void 0 ? void 0 : _a.variationId); } }),
69
72
  react_1.default.createElement("div", { className: 'cio-image-container', onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave },
70
73
  react_1.default.createElement("img", { alt: itemName, src: itemImageUrl, className: 'cio-image' }),
71
74
  rolloverImage && (react_1.default.createElement("img", { alt: `${itemName} rollover`, src: rolloverImage, loading: 'lazy', className: (0, utils_1.concatStyles)('cio-image cio-rollover-image', isRolloverImageShown && 'is-active') }))),
@@ -75,6 +78,6 @@ function ProductCard(props) {
75
78
  Number(itemPrice) >= 0 && (react_1.default.createElement("div", { className: (0, utils_1.concatStyles)('cio-item-price', hasSalePrice && 'cio-item-price-strikethrough') }, formatPrice(itemPrice)))),
76
79
  react_1.default.createElement("div", { className: 'cio-item-name' }, itemName),
77
80
  productSwatch && react_1.default.createElement(ProductSwatch_1.default, { swatchObject: productSwatch })),
78
- react_1.default.createElement("button", { className: 'cio-add-to-cart-button', type: 'button', onClick: (e) => { var _a; return onAddToCart(e, item, itemPrice, (_a = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _a === void 0 ? void 0 : _a.variationId); } }, "Add to Cart"))));
81
+ react_1.default.createElement("button", Object.assign({}, addToCartBtnAttrs, { className: 'cio-add-to-cart-button', type: 'button', onClick: (e) => { var _a; return onAddToCart(e, item, itemPrice, (_a = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _a === void 0 ? void 0 : _a.variationId); } }), "Add to Cart"))));
79
82
  }
80
83
  exports.default = ProductCard;
@@ -34,9 +34,12 @@ function ProductSwatch(props) {
34
34
  const color = (0, utils_1.isHexColor)(swatch === null || swatch === void 0 ? void 0 : swatch.swatchPreview)
35
35
  ? swatch === null || swatch === void 0 ? void 0 : swatch.swatchPreview
36
36
  : `url(${swatch === null || swatch === void 0 ? void 0 : swatch.swatchPreview})`;
37
- return (react_1.default.createElement("button", { type: 'button', key: swatch.variationId, "data-cnstrc-variation-id": swatch.variationId, "data-testid": `cio-swatch-${swatch.variationId}`, className: 'cio-swatch-button cio-swatch-item', onClick: (e) => swatchClickHandler(e, swatch), style: {
37
+ const variationIdAttr = {
38
+ [utils_1.cnstrcDataAttrs.common.variationId]: swatch.variationId,
39
+ };
40
+ return (react_1.default.createElement("button", Object.assign({ type: 'button', key: swatch.variationId, "data-testid": `cio-swatch-${swatch.variationId}`, className: 'cio-swatch-button cio-swatch-item', onClick: (e) => swatchClickHandler(e, swatch), style: {
38
41
  background: color,
39
- } }, isSelected && (react_1.default.createElement("div", { "data-cnstrc-variation-id": swatch.variationId, className: 'cio-swatch-selected', style: { outline: `3px solid ${color}` } }))));
42
+ } }, variationIdAttr), isSelected && (react_1.default.createElement("div", Object.assign({ className: 'cio-swatch-selected', style: { outline: `3px solid ${color}` } }, variationIdAttr)))));
40
43
  }))))));
41
44
  }
42
45
  exports.default = ProductSwatch;
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EMITTED_EVENTS = exports.DEMO_API_KEY = void 0;
4
- // eslint-disable-next-line import/prefer-default-export
5
4
  exports.DEMO_API_KEY = 'key_M57QS8SMPdLdLx4x';
6
5
  exports.EMITTED_EVENTS = {
7
6
  PRODUCT_CARD_IMAGE_ROLLOVER: 'cio.ui-plp.productCardImageRollover',
@@ -69,14 +69,15 @@ function useCioPlp(props = {}) {
69
69
  const pagination = (0, usePagination_1.default)(Object.assign({ totalNumResults }, paginationConfigs));
70
70
  const groups = (0, useGroups_1.default)(Object.assign({ groups: rawGroups }, groupsConfigs));
71
71
  const data = isSearchPage ? search.data : browse.data;
72
- const plpContainerCnstrcDataAttributes = (0, utils_1.getPlpContainerCnstrcDataAttributes)(data, requestConfigs);
72
+ const status = isSearchPage ? search.status : browse.status;
73
+ const plpContainerCnstrcDataAttributes = (0, utils_1.getPlpContainerCnstrcDataAttributes)(data, requestConfigs, status === 'fetching');
73
74
  return {
74
75
  isSearchPage,
75
76
  isBrowsePage,
76
77
  searchQuery: search.query,
77
78
  browseFilterName: browse.filterName,
78
79
  browseFilterValue: browse.filterValue,
79
- status: isSearchPage ? search.status : browse.status,
80
+ status,
80
81
  data,
81
82
  refetch,
82
83
  filters,
@@ -6,7 +6,7 @@ const useCioPlpContext_1 = require("./useCioPlpContext");
6
6
  const utils_1 = require("../utils");
7
7
  const itemFieldGetters_1 = require("../utils/itemFieldGetters");
8
8
  const useProductInfo = ({ item }) => {
9
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
9
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
10
10
  const state = (0, useCioPlpContext_1.useCioPlpContext)();
11
11
  const productSwatch = (0, useProductSwatch_1.default)({ item });
12
12
  if (!item.data || !item.itemId || !item.itemName) {
@@ -15,18 +15,22 @@ const useProductInfo = ({ item }) => {
15
15
  const getPrice = (0, utils_1.tryCatchify)(((_a = state === null || state === void 0 ? void 0 : state.itemFieldGetters) === null || _a === void 0 ? void 0 : _a.getPrice) || itemFieldGetters_1.getPrice);
16
16
  const getSalePrice = (0, utils_1.tryCatchify)(((_b = state === null || state === void 0 ? void 0 : state.itemFieldGetters) === null || _b === void 0 ? void 0 : _b.getSalePrice) || itemFieldGetters_1.getSalePrice);
17
17
  const getRolloverImage = (0, utils_1.tryCatchify)(((_c = state === null || state === void 0 ? void 0 : state.itemFieldGetters) === null || _c === void 0 ? void 0 : _c.getRolloverImage) || itemFieldGetters_1.getRolloverImage);
18
- const itemName = ((_d = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _d === void 0 ? void 0 : _d.itemName) || item.itemName;
19
- const itemPrice = ((_e = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _e === void 0 ? void 0 : _e.price) || getPrice(item);
20
- const itemImageUrl = ((_f = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _f === void 0 ? void 0 : _f.imageUrl) || item.imageUrl;
21
- const itemUrl = ((_g = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _g === void 0 ? void 0 : _g.url) || item.url;
22
- const variationId = (_h = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _h === void 0 ? void 0 : _h.variationId;
23
- let rolloverImage = (_j = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _j === void 0 ? void 0 : _j.rolloverImage;
18
+ const getItemUrl = (0, utils_1.tryCatchify)(((_d = state === null || state === void 0 ? void 0 : state.itemFieldGetters) === null || _d === void 0 ? void 0 : _d.getItemUrl) || itemFieldGetters_1.getItemUrl);
19
+ const itemName = ((_e = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _e === void 0 ? void 0 : _e.itemName) || item.itemName;
20
+ const itemPrice = ((_f = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _f === void 0 ? void 0 : _f.price) || getPrice(item);
21
+ const itemImageUrl = ((_g = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _g === void 0 ? void 0 : _g.imageUrl) || item.imageUrl;
22
+ // Get href - merge variation URL into item if variation is selected
23
+ const itemWithVariationUrl = (productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation)
24
+ ? Object.assign(Object.assign({}, item), { url: productSwatch.selectedVariation.url }) : item;
25
+ const itemUrl = getItemUrl(itemWithVariationUrl) || ((_h = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _h === void 0 ? void 0 : _h.url);
26
+ const variationId = (_j = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _j === void 0 ? void 0 : _j.variationId;
27
+ let rolloverImage = (_k = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _k === void 0 ? void 0 : _k.rolloverImage;
24
28
  // Fallback to item's rollover image if all variations don't have a rollover image
25
- if (!rolloverImage && ((_k = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.swatchList) === null || _k === void 0 ? void 0 : _k.every((swatch) => !swatch.rolloverImage))) {
29
+ if (!rolloverImage && ((_l = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.swatchList) === null || _l === void 0 ? void 0 : _l.every((swatch) => !swatch.rolloverImage))) {
26
30
  rolloverImage = getRolloverImage(item);
27
31
  }
28
32
  const { itemId } = item;
29
- let salePrice = ((_l = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _l === void 0 ? void 0 : _l.salePrice) || getSalePrice(item);
33
+ let salePrice = ((_m = productSwatch === null || productSwatch === void 0 ? void 0 : productSwatch.selectedVariation) === null || _m === void 0 ? void 0 : _m.salePrice) || getSalePrice(item);
30
34
  let hasSalePrice = true;
31
35
  if (!(0, utils_1.isValidSalePrice)(salePrice, itemPrice)) {
32
36
  salePrice = undefined;
@@ -1,42 +1,85 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getPlpContainerCnstrcDataAttributes = exports.getProductCardCnstrcDataAttributes = void 0;
3
+ exports.getConversionButtonCnstrcDataAttributes = exports.getPlpContainerCnstrcDataAttributes = exports.getProductCardCnstrcDataAttributes = exports.cnstrcDataAttrs = void 0;
4
4
  const typeHelpers_1 = require("./typeHelpers");
5
5
  const requestConfigsHelpers_1 = require("./requestConfigsHelpers");
6
- function getProductCardCnstrcDataAttributes(productInfo) {
7
- let dataCnstrc = {};
6
+ exports.cnstrcDataAttrs = {
7
+ common: {
8
+ itemId: 'data-cnstrc-item-id',
9
+ itemName: 'data-cnstrc-item-name',
10
+ itemPrice: 'data-cnstrc-item-price',
11
+ variationId: 'data-cnstrc-item-variation-id',
12
+ numResults: 'data-cnstrc-num-results',
13
+ conversionBtn: 'data-cnstrc-btn',
14
+ resultId: 'data-cnstrc-result-id',
15
+ section: 'data-cnstrc-section',
16
+ zeroResults: 'data-cnstrc-zero-result',
17
+ slCampaignId: 'data-cnstrc-sl-campaign-id',
18
+ slCampaignOwner: 'data-cnstrc-sl-campaign-owner',
19
+ },
20
+ search: {
21
+ searchContainer: 'data-cnstrc-search',
22
+ searchTerm: 'data-cnstrc-search-term',
23
+ },
24
+ browse: {
25
+ browseContainer: 'data-cnstrc-browse',
26
+ filterName: 'data-cnstrc-filter-name',
27
+ filterValue: 'data-cnstrc-filter-value',
28
+ },
29
+ };
30
+ function getProductCardCnstrcDataAttributes(productInfo, options) {
31
+ var _a, _b;
8
32
  const { itemId, itemPrice, itemName, variationId } = productInfo;
9
- dataCnstrc = {
10
- 'data-cnstrc-item-id': itemId,
11
- 'data-cnstrc-item-name': itemName,
12
- 'data-cnstrc-item-price': itemPrice,
13
- 'data-cnstrc-item-variation-id': variationId,
33
+ const dataCnstrc = {
34
+ [exports.cnstrcDataAttrs.common.itemId]: itemId,
35
+ [exports.cnstrcDataAttrs.common.itemName]: itemName,
14
36
  };
37
+ // Only include variation ID if it exists
38
+ if (variationId) {
39
+ dataCnstrc[exports.cnstrcDataAttrs.common.variationId] = variationId;
40
+ }
41
+ // Only include price if it exists
42
+ if (itemPrice !== undefined && itemPrice !== null) {
43
+ dataCnstrc[exports.cnstrcDataAttrs.common.itemPrice] = itemPrice;
44
+ }
45
+ // Add sponsored listing data if available
46
+ if ((_a = options === null || options === void 0 ? void 0 : options.labels) === null || _a === void 0 ? void 0 : _a.sl_campaign_id) {
47
+ dataCnstrc[exports.cnstrcDataAttrs.common.slCampaignId] = String(options.labels.sl_campaign_id);
48
+ }
49
+ if ((_b = options === null || options === void 0 ? void 0 : options.labels) === null || _b === void 0 ? void 0 : _b.sl_campaign_owner) {
50
+ dataCnstrc[exports.cnstrcDataAttrs.common.slCampaignOwner] = String(options.labels.sl_campaign_owner);
51
+ }
15
52
  return dataCnstrc;
16
53
  }
17
54
  exports.getProductCardCnstrcDataAttributes = getProductCardCnstrcDataAttributes;
18
- function getPlpContainerCnstrcDataAttributes(data, requestConfigs) {
55
+ function getPlpContainerCnstrcDataAttributes(data, requestConfigs, isLoading = false) {
56
+ var _a, _b;
19
57
  if (!data || (!(0, typeHelpers_1.isPlpSearchDataResults)(data) && !(0, typeHelpers_1.isPlpBrowseDataResults)(data)))
20
58
  return {};
21
59
  const { filterName, filterValue } = requestConfigs;
22
60
  const pageType = (0, requestConfigsHelpers_1.getPageType)(requestConfigs);
61
+ const isZeroResults = data.response.totalNumResults === 0;
23
62
  let dataCnstrc = {};
24
63
  switch (pageType) {
25
64
  case 'browse':
26
65
  dataCnstrc = {
27
- 'data-cnstrc-browse': true,
28
- 'data-cnstrc-num-results': data.response.totalNumResults,
29
- 'data-cnstrc-result-id': data.resultId,
30
- 'data-cnstrc-filter-name': filterName,
31
- 'data-cnstrc-filter-value': filterValue,
66
+ [exports.cnstrcDataAttrs.browse.browseContainer]: true,
67
+ [exports.cnstrcDataAttrs.common.numResults]: data.response.totalNumResults,
68
+ [exports.cnstrcDataAttrs.common.resultId]: data.resultId,
69
+ [exports.cnstrcDataAttrs.browse.filterName]: filterName,
70
+ [exports.cnstrcDataAttrs.browse.filterValue]: filterValue,
32
71
  };
33
72
  break;
34
73
  case 'search':
35
74
  dataCnstrc = {
36
- 'data-cnstrc-search': true,
37
- 'data-cnstrc-result-id': data.resultId,
38
- 'data-cnstrc-num-results': data.response.totalNumResults,
75
+ [exports.cnstrcDataAttrs.search.searchContainer]: true,
76
+ [exports.cnstrcDataAttrs.common.resultId]: data.resultId,
77
+ [exports.cnstrcDataAttrs.common.numResults]: data.response.totalNumResults,
39
78
  };
79
+ // Add search term
80
+ if ((_a = data.request) === null || _a === void 0 ? void 0 : _a.term) {
81
+ dataCnstrc[exports.cnstrcDataAttrs.search.searchTerm] = data.request.term;
82
+ }
40
83
  break;
41
84
  case 'unknown':
42
85
  dataCnstrc = {};
@@ -44,6 +87,23 @@ function getPlpContainerCnstrcDataAttributes(data, requestConfigs) {
44
87
  default:
45
88
  break;
46
89
  }
90
+ // Add common conditional attributes (applies to both search and browse)
91
+ if (pageType === 'search' || pageType === 'browse') {
92
+ // Add zero-result attribute only if not loading and there are no results
93
+ if (isZeroResults && !isLoading) {
94
+ dataCnstrc[exports.cnstrcDataAttrs.common.zeroResults] = true;
95
+ }
96
+ // Add section if it's not "Products"
97
+ if (((_b = data.request) === null || _b === void 0 ? void 0 : _b.section) && data.request.section !== 'Products') {
98
+ dataCnstrc[exports.cnstrcDataAttrs.common.section] = data.request.section;
99
+ }
100
+ }
47
101
  return dataCnstrc;
48
102
  }
49
103
  exports.getPlpContainerCnstrcDataAttributes = getPlpContainerCnstrcDataAttributes;
104
+ function getConversionButtonCnstrcDataAttributes(conversionType) {
105
+ return {
106
+ [exports.cnstrcDataAttrs.common.conversionBtn]: conversionType,
107
+ };
108
+ }
109
+ exports.getConversionButtonCnstrcDataAttributes = getConversionButtonCnstrcDataAttributes;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getIsHiddenGroupField = exports.getSwatchPreview = exports.getSwatches = exports.getRolloverImage = exports.getSalePrice = exports.getPrice = void 0;
3
+ exports.getItemUrl = exports.getIsHiddenGroupField = exports.getSwatchPreview = exports.getSwatches = exports.getRolloverImage = exports.getSalePrice = exports.getPrice = void 0;
4
4
  // eslint-disable-next-line import/prefer-default-export
5
5
  function getPrice(item) {
6
6
  return item.data.price;
@@ -45,3 +45,7 @@ function getIsHiddenGroupField(group) {
45
45
  return (_a = group === null || group === void 0 ? void 0 : group.data) === null || _a === void 0 ? void 0 : _a.cio_plp_hidden;
46
46
  }
47
47
  exports.getIsHiddenGroupField = getIsHiddenGroupField;
48
+ function getItemUrl(item) {
49
+ return item === null || item === void 0 ? void 0 : item.url;
50
+ }
51
+ exports.getItemUrl = getItemUrl;
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = '1.6.4';
3
+ exports.default = '1.6.6';
@@ -3,7 +3,7 @@ import { useCioPlpContext } from '../../hooks/useCioPlpContext';
3
3
  import { useOnAddToCart, useOnProductCardClick } from '../../hooks/callbacks';
4
4
  import ProductSwatch from '../ProductSwatch';
5
5
  import useProductInfo from '../../hooks/useProduct';
6
- import { concatStyles, getProductCardCnstrcDataAttributes } from '../../utils';
6
+ import { concatStyles, getProductCardCnstrcDataAttributes, getConversionButtonCnstrcDataAttributes, } from '../../utils';
7
7
  import RenderPropsWrapper from '../RenderPropsWrapper/RenderPropsWrapper';
8
8
  import { EMITTED_EVENTS } from '../../constants';
9
9
  /**
@@ -26,7 +26,10 @@ export default function ProductCard(props) {
26
26
  const onAddToCart = useOnAddToCart(client, state.callbacks.onAddToCart);
27
27
  const { formatPrice } = state.formatters;
28
28
  const onClick = useOnProductCardClick(client, state.callbacks.onProductCardClick);
29
- const cnstrcData = getProductCardCnstrcDataAttributes(productInfo);
29
+ const cnstrcDataAttrs = getProductCardCnstrcDataAttributes(productInfo, {
30
+ labels: item.labels,
31
+ });
32
+ const addToCartBtnAttrs = getConversionButtonCnstrcDataAttributes('add_to_cart');
30
33
  const handleRolloverImageState = (isShown) => {
31
34
  setIsRolloverImageShown(isShown);
32
35
  if (isShown && rolloverImage) {
@@ -58,9 +61,9 @@ export default function ProductCard(props) {
58
61
  onMouseEnter,
59
62
  onMouseLeave,
60
63
  isRolloverImageShown,
61
- productCardCnstrcDataAttributes: cnstrcData,
62
- }, override: children, htmlOverride: state.renderOverrides.productCard?.renderHtml, topLevelAttributes: { ...cnstrcData, className: 'cio-product-card' } },
63
- React.createElement("a", { ...cnstrcData, className: 'cio-product-card', href: itemUrl, ref: cardRef, onClick: (e) => onClick(e, item, productSwatch?.selectedVariation?.variationId) },
64
+ productCardCnstrcDataAttributes: cnstrcDataAttrs,
65
+ }, override: children, htmlOverride: state.renderOverrides.productCard?.renderHtml, topLevelAttributes: { ...cnstrcDataAttrs, className: 'cio-product-card' } },
66
+ React.createElement("a", { ...cnstrcDataAttrs, className: 'cio-product-card', href: itemUrl, ref: cardRef, onClick: (e) => onClick(e, item, productSwatch?.selectedVariation?.variationId) },
64
67
  React.createElement("div", { className: 'cio-image-container', onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave },
65
68
  React.createElement("img", { alt: itemName, src: itemImageUrl, className: 'cio-image' }),
66
69
  rolloverImage && (React.createElement("img", { alt: `${itemName} rollover`, src: rolloverImage, loading: 'lazy', className: concatStyles('cio-image cio-rollover-image', isRolloverImageShown && 'is-active') }))),
@@ -70,5 +73,5 @@ export default function ProductCard(props) {
70
73
  Number(itemPrice) >= 0 && (React.createElement("div", { className: concatStyles('cio-item-price', hasSalePrice && 'cio-item-price-strikethrough') }, formatPrice(itemPrice)))),
71
74
  React.createElement("div", { className: 'cio-item-name' }, itemName),
72
75
  productSwatch && React.createElement(ProductSwatch, { swatchObject: productSwatch })),
73
- React.createElement("button", { className: 'cio-add-to-cart-button', type: 'button', onClick: (e) => onAddToCart(e, item, itemPrice, productSwatch?.selectedVariation?.variationId) }, "Add to Cart"))));
76
+ React.createElement("button", { ...addToCartBtnAttrs, className: 'cio-add-to-cart-button', type: 'button', onClick: (e) => onAddToCart(e, item, itemPrice, productSwatch?.selectedVariation?.variationId) }, "Add to Cart"))));
74
77
  }
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable jsx-a11y/click-events-have-key-events */
2
2
  import React from 'react';
3
3
  import { useCioPlpContext } from '../../hooks/useCioPlpContext';
4
- import { isHexColor } from '../../utils';
4
+ import { isHexColor, cnstrcDataAttrs } from '../../utils';
5
5
  export default function ProductSwatch(props) {
6
6
  const context = useCioPlpContext();
7
7
  const { swatchObject, children } = props;
@@ -31,8 +31,11 @@ export default function ProductSwatch(props) {
31
31
  const color = isHexColor(swatch?.swatchPreview)
32
32
  ? swatch?.swatchPreview
33
33
  : `url(${swatch?.swatchPreview})`;
34
- return (React.createElement("button", { type: 'button', key: swatch.variationId, "data-cnstrc-variation-id": swatch.variationId, "data-testid": `cio-swatch-${swatch.variationId}`, className: 'cio-swatch-button cio-swatch-item', onClick: (e) => swatchClickHandler(e, swatch), style: {
34
+ const variationIdAttr = {
35
+ [cnstrcDataAttrs.common.variationId]: swatch.variationId,
36
+ };
37
+ return (React.createElement("button", { type: 'button', key: swatch.variationId, "data-testid": `cio-swatch-${swatch.variationId}`, className: 'cio-swatch-button cio-swatch-item', onClick: (e) => swatchClickHandler(e, swatch), style: {
35
38
  background: color,
36
- } }, isSelected && (React.createElement("div", { "data-cnstrc-variation-id": swatch.variationId, className: 'cio-swatch-selected', style: { outline: `3px solid ${color}` } }))));
39
+ }, ...variationIdAttr }, isSelected && (React.createElement("div", { className: 'cio-swatch-selected', style: { outline: `3px solid ${color}` }, ...variationIdAttr }))));
37
40
  }))))));
38
41
  }
@@ -1,4 +1,3 @@
1
- // eslint-disable-next-line import/prefer-default-export
2
1
  export const DEMO_API_KEY = 'key_M57QS8SMPdLdLx4x';
3
2
  export const EMITTED_EVENTS = {
4
3
  PRODUCT_CARD_IMAGE_ROLLOVER: 'cio.ui-plp.productCardImageRollover',
@@ -65,14 +65,15 @@ export default function useCioPlp(props = {}) {
65
65
  const pagination = usePagination({ totalNumResults, ...paginationConfigs });
66
66
  const groups = useGroups({ groups: rawGroups, ...groupsConfigs });
67
67
  const data = isSearchPage ? search.data : browse.data;
68
- const plpContainerCnstrcDataAttributes = getPlpContainerCnstrcDataAttributes(data, requestConfigs);
68
+ const status = isSearchPage ? search.status : browse.status;
69
+ const plpContainerCnstrcDataAttributes = getPlpContainerCnstrcDataAttributes(data, requestConfigs, status === 'fetching');
69
70
  return {
70
71
  isSearchPage,
71
72
  isBrowsePage,
72
73
  searchQuery: search.query,
73
74
  browseFilterName: browse.filterName,
74
75
  browseFilterValue: browse.filterValue,
75
- status: isSearchPage ? search.status : browse.status,
76
+ status,
76
77
  data,
77
78
  refetch,
78
79
  filters,
@@ -1,7 +1,7 @@
1
1
  import useProductSwatch from './useProductSwatch';
2
2
  import { useCioPlpContext } from './useCioPlpContext';
3
3
  import { tryCatchify, isValidSalePrice } from '../utils';
4
- import { getPrice as defaultGetPrice, getSalePrice as defaultGetSalePrice, getRolloverImage as defaultGetRolloverImage, } from '../utils/itemFieldGetters';
4
+ import { getPrice as defaultGetPrice, getSalePrice as defaultGetSalePrice, getRolloverImage as defaultGetRolloverImage, getItemUrl as defaultGetItemUrl, } from '../utils/itemFieldGetters';
5
5
  const useProductInfo = ({ item }) => {
6
6
  const state = useCioPlpContext();
7
7
  const productSwatch = useProductSwatch({ item });
@@ -11,10 +11,15 @@ const useProductInfo = ({ item }) => {
11
11
  const getPrice = tryCatchify(state?.itemFieldGetters?.getPrice || defaultGetPrice);
12
12
  const getSalePrice = tryCatchify(state?.itemFieldGetters?.getSalePrice || defaultGetSalePrice);
13
13
  const getRolloverImage = tryCatchify(state?.itemFieldGetters?.getRolloverImage || defaultGetRolloverImage);
14
+ const getItemUrl = tryCatchify(state?.itemFieldGetters?.getItemUrl || defaultGetItemUrl);
14
15
  const itemName = productSwatch?.selectedVariation?.itemName || item.itemName;
15
16
  const itemPrice = productSwatch?.selectedVariation?.price || getPrice(item);
16
17
  const itemImageUrl = productSwatch?.selectedVariation?.imageUrl || item.imageUrl;
17
- const itemUrl = productSwatch?.selectedVariation?.url || item.url;
18
+ // Get href - merge variation URL into item if variation is selected
19
+ const itemWithVariationUrl = productSwatch?.selectedVariation
20
+ ? { ...item, url: productSwatch.selectedVariation.url }
21
+ : item;
22
+ const itemUrl = getItemUrl(itemWithVariationUrl) || productSwatch?.selectedVariation?.url;
18
23
  const variationId = productSwatch?.selectedVariation?.variationId;
19
24
  let rolloverImage = productSwatch?.selectedVariation?.rolloverImage;
20
25
  // Fallback to item's rollover image if all variations don't have a rollover image
@@ -1,38 +1,79 @@
1
1
  import { isPlpBrowseDataResults, isPlpSearchDataResults } from './typeHelpers';
2
2
  import { getPageType } from './requestConfigsHelpers';
3
- export function getProductCardCnstrcDataAttributes(productInfo) {
4
- let dataCnstrc = {};
3
+ export const cnstrcDataAttrs = {
4
+ common: {
5
+ itemId: 'data-cnstrc-item-id',
6
+ itemName: 'data-cnstrc-item-name',
7
+ itemPrice: 'data-cnstrc-item-price',
8
+ variationId: 'data-cnstrc-item-variation-id',
9
+ numResults: 'data-cnstrc-num-results',
10
+ conversionBtn: 'data-cnstrc-btn',
11
+ resultId: 'data-cnstrc-result-id',
12
+ section: 'data-cnstrc-section',
13
+ zeroResults: 'data-cnstrc-zero-result',
14
+ slCampaignId: 'data-cnstrc-sl-campaign-id',
15
+ slCampaignOwner: 'data-cnstrc-sl-campaign-owner',
16
+ },
17
+ search: {
18
+ searchContainer: 'data-cnstrc-search',
19
+ searchTerm: 'data-cnstrc-search-term',
20
+ },
21
+ browse: {
22
+ browseContainer: 'data-cnstrc-browse',
23
+ filterName: 'data-cnstrc-filter-name',
24
+ filterValue: 'data-cnstrc-filter-value',
25
+ },
26
+ };
27
+ export function getProductCardCnstrcDataAttributes(productInfo, options) {
5
28
  const { itemId, itemPrice, itemName, variationId } = productInfo;
6
- dataCnstrc = {
7
- 'data-cnstrc-item-id': itemId,
8
- 'data-cnstrc-item-name': itemName,
9
- 'data-cnstrc-item-price': itemPrice,
10
- 'data-cnstrc-item-variation-id': variationId,
29
+ const dataCnstrc = {
30
+ [cnstrcDataAttrs.common.itemId]: itemId,
31
+ [cnstrcDataAttrs.common.itemName]: itemName,
11
32
  };
33
+ // Only include variation ID if it exists
34
+ if (variationId) {
35
+ dataCnstrc[cnstrcDataAttrs.common.variationId] = variationId;
36
+ }
37
+ // Only include price if it exists
38
+ if (itemPrice !== undefined && itemPrice !== null) {
39
+ dataCnstrc[cnstrcDataAttrs.common.itemPrice] = itemPrice;
40
+ }
41
+ // Add sponsored listing data if available
42
+ if (options?.labels?.sl_campaign_id) {
43
+ dataCnstrc[cnstrcDataAttrs.common.slCampaignId] = String(options.labels.sl_campaign_id);
44
+ }
45
+ if (options?.labels?.sl_campaign_owner) {
46
+ dataCnstrc[cnstrcDataAttrs.common.slCampaignOwner] = String(options.labels.sl_campaign_owner);
47
+ }
12
48
  return dataCnstrc;
13
49
  }
14
- export function getPlpContainerCnstrcDataAttributes(data, requestConfigs) {
50
+ export function getPlpContainerCnstrcDataAttributes(data, requestConfigs, isLoading = false) {
15
51
  if (!data || (!isPlpSearchDataResults(data) && !isPlpBrowseDataResults(data)))
16
52
  return {};
17
53
  const { filterName, filterValue } = requestConfigs;
18
54
  const pageType = getPageType(requestConfigs);
55
+ const isZeroResults = data.response.totalNumResults === 0;
19
56
  let dataCnstrc = {};
20
57
  switch (pageType) {
21
58
  case 'browse':
22
59
  dataCnstrc = {
23
- 'data-cnstrc-browse': true,
24
- 'data-cnstrc-num-results': data.response.totalNumResults,
25
- 'data-cnstrc-result-id': data.resultId,
26
- 'data-cnstrc-filter-name': filterName,
27
- 'data-cnstrc-filter-value': filterValue,
60
+ [cnstrcDataAttrs.browse.browseContainer]: true,
61
+ [cnstrcDataAttrs.common.numResults]: data.response.totalNumResults,
62
+ [cnstrcDataAttrs.common.resultId]: data.resultId,
63
+ [cnstrcDataAttrs.browse.filterName]: filterName,
64
+ [cnstrcDataAttrs.browse.filterValue]: filterValue,
28
65
  };
29
66
  break;
30
67
  case 'search':
31
68
  dataCnstrc = {
32
- 'data-cnstrc-search': true,
33
- 'data-cnstrc-result-id': data.resultId,
34
- 'data-cnstrc-num-results': data.response.totalNumResults,
69
+ [cnstrcDataAttrs.search.searchContainer]: true,
70
+ [cnstrcDataAttrs.common.resultId]: data.resultId,
71
+ [cnstrcDataAttrs.common.numResults]: data.response.totalNumResults,
35
72
  };
73
+ // Add search term
74
+ if (data.request?.term) {
75
+ dataCnstrc[cnstrcDataAttrs.search.searchTerm] = data.request.term;
76
+ }
36
77
  break;
37
78
  case 'unknown':
38
79
  dataCnstrc = {};
@@ -40,5 +81,21 @@ export function getPlpContainerCnstrcDataAttributes(data, requestConfigs) {
40
81
  default:
41
82
  break;
42
83
  }
84
+ // Add common conditional attributes (applies to both search and browse)
85
+ if (pageType === 'search' || pageType === 'browse') {
86
+ // Add zero-result attribute only if not loading and there are no results
87
+ if (isZeroResults && !isLoading) {
88
+ dataCnstrc[cnstrcDataAttrs.common.zeroResults] = true;
89
+ }
90
+ // Add section if it's not "Products"
91
+ if (data.request?.section && data.request.section !== 'Products') {
92
+ dataCnstrc[cnstrcDataAttrs.common.section] = data.request.section;
93
+ }
94
+ }
43
95
  return dataCnstrc;
44
96
  }
97
+ export function getConversionButtonCnstrcDataAttributes(conversionType) {
98
+ return {
99
+ [cnstrcDataAttrs.common.conversionBtn]: conversionType,
100
+ };
101
+ }
@@ -33,3 +33,6 @@ export function getSwatchPreview(variation) {
33
33
  export function getIsHiddenGroupField(group) {
34
34
  return group?.data?.cio_plp_hidden;
35
35
  }
36
+ export function getItemUrl(item) {
37
+ return item?.url;
38
+ }
@@ -1 +1 @@
1
- export default '1.6.4';
1
+ export default '1.6.6';
@@ -59,5 +59,5 @@ export default function useCioPlp(props?: UseCioPlpProps): {
59
59
  optionsToRender: PlpItemGroup[];
60
60
  setOptionsToRender: import("react").Dispatch<import("react").SetStateAction<PlpItemGroup[]>>;
61
61
  };
62
- plpContainerCnstrcDataAttributes: Record<`data-cnstrc-${string}`, string | number | boolean>;
62
+ plpContainerCnstrcDataAttributes: import("../types").CnstrcDataAttrs;
63
63
  };
@@ -17,6 +17,7 @@ export interface ItemFieldGetters {
17
17
  getSwatchPreview: (variation: Variation) => string;
18
18
  getSwatches: (item: Item, retrievePrice: ItemFieldGetters['getPrice'], retrieveSwatchPreview: ItemFieldGetters['getSwatchPreview'], retrieveSalePrice: ItemFieldGetters['getSalePrice'], retrieveRolloverImage: ItemFieldGetters['getRolloverImage']) => SwatchItem[] | undefined;
19
19
  getIsHiddenGroupField: (group: PlpItemGroup) => boolean | undefined;
20
+ getItemUrl: (item: Item) => string | undefined;
20
21
  }
21
22
  export interface Formatters {
22
23
  formatPrice: (price?: number) => string;
@@ -75,7 +76,7 @@ export interface ProductCardRenderProps extends ProductCardProps {
75
76
  /**
76
77
  * Data Attributes to surface on parent div of product card.
77
78
  */
78
- productCardCnstrcDataAttributes: CnstrcData;
79
+ productCardCnstrcDataAttributes: CnstrcDataAttrs;
79
80
  }
80
81
  export type ProductCardProps = IncludeRenderProps<ProductCardBaseProps, ProductCardRenderProps>;
81
82
  export interface RenderOverrides {
@@ -295,7 +296,7 @@ export interface PlpItemGroup {
295
296
  children: Array<PlpItemGroup>;
296
297
  parents: Pick<PlpItemGroup, 'groupId' | 'displayName'>[];
297
298
  }
298
- export type CnstrcData = Record<`data-cnstrc-${string}`, string | number | boolean>;
299
+ export type CnstrcDataAttrs = Record<`data-cnstrc-${string}` | string, string | number | boolean>;
299
300
  export type PropsWithChildren<P> = P & {
300
301
  children?: ReactNode;
301
302
  };
@@ -1,3 +1,37 @@
1
- import { RequestConfigs, PlpSearchDataResults, PlpSearchDataRedirect, PlpBrowseData, CnstrcData, ProductInfoObject } from '../types';
2
- export declare function getProductCardCnstrcDataAttributes(productInfo: ProductInfoObject): CnstrcData;
3
- export declare function getPlpContainerCnstrcDataAttributes(data: PlpSearchDataResults | PlpSearchDataRedirect | PlpBrowseData | null, requestConfigs: RequestConfigs): Record<`data-cnstrc-${string}`, string | number | boolean>;
1
+ import { RequestConfigs, PlpSearchDataResults, PlpSearchDataRedirect, PlpBrowseData, CnstrcDataAttrs, ProductInfoObject } from '../types';
2
+ export interface ProductCardDataAttributeOptions {
3
+ labels?: {
4
+ sl_campaign_id?: string | number;
5
+ sl_campaign_owner?: string | number;
6
+ };
7
+ }
8
+ export declare const cnstrcDataAttrs: {
9
+ common: {
10
+ itemId: string;
11
+ itemName: string;
12
+ itemPrice: string;
13
+ variationId: string;
14
+ numResults: string;
15
+ conversionBtn: string;
16
+ resultId: string;
17
+ section: string;
18
+ zeroResults: string;
19
+ slCampaignId: string;
20
+ slCampaignOwner: string;
21
+ };
22
+ search: {
23
+ searchContainer: string;
24
+ searchTerm: string;
25
+ };
26
+ browse: {
27
+ browseContainer: string;
28
+ filterName: string;
29
+ filterValue: string;
30
+ };
31
+ };
32
+ export declare function getProductCardCnstrcDataAttributes(productInfo: ProductInfoObject, options?: ProductCardDataAttributeOptions): CnstrcDataAttrs;
33
+ export declare function getPlpContainerCnstrcDataAttributes(data: PlpSearchDataResults | PlpSearchDataRedirect | PlpBrowseData | null, requestConfigs: RequestConfigs, isLoading?: boolean): CnstrcDataAttrs;
34
+ export type ConversionType = 'add_to_cart' | 'add_to_wishlist' | 'like' | 'message' | 'make_offer' | 'read' | string;
35
+ export declare function getConversionButtonCnstrcDataAttributes(conversionType: ConversionType): {
36
+ [x: string]: string;
37
+ };
@@ -5,3 +5,4 @@ export declare function getRolloverImage(item: Item | Variation): string | undef
5
5
  export declare function getSwatches(item: Item, retrievePrice: ItemFieldGetters['getPrice'], retrieveSwatchPreview: ItemFieldGetters['getSwatchPreview'], retrieveSalePrice: ItemFieldGetters['getSalePrice'], retrieveRolloverImage: ItemFieldGetters['getRolloverImage']): SwatchItem[] | undefined;
6
6
  export declare function getSwatchPreview(variation: Variation): string;
7
7
  export declare function getIsHiddenGroupField(group: PlpItemGroup): any;
8
+ export declare function getItemUrl(item: Item): string | undefined;
@@ -1,2 +1,2 @@
1
- declare const _default: "1.6.4";
1
+ declare const _default: "1.6.6";
2
2
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructor-io/constructorio-ui-plp",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "Constructor PLP UI library for web applications",
5
5
  "author": "Constructor.io Corporation",
6
6
  "license": "MIT",