@shopgate/engage 7.32.0-beta.5 → 7.32.0-beta.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.
- package/back-in-stock/components/Subscriptions/components/Subscription/index.js +2 -5
- package/cart/components/CartItem/CartItemProductLayout.js +1 -1
- package/cart/components/CartItem/CartItemProductLayoutWide.js +2 -2
- package/category/components/CategoryImage/index.js +11 -15
- package/components/constants.d.ts +6 -0
- package/components/constants.d.ts.map +1 -0
- package/core/helpers/getFullImageSource.d.ts +5 -1
- package/core/helpers/getFullImageSource.d.ts.map +1 -1
- package/core/helpers/getFullImageSource.js +30 -14
- package/core/helpers/i18n.d.ts +4 -0
- package/core/helpers/i18n.d.ts.map +1 -1
- package/core/helpers/i18n.js +3 -0
- package/core/hooks/html/useLoadImage.d.ts +3 -3
- package/core/hooks/html/useLoadImage.d.ts.map +1 -1
- package/core/hooks/html/useLoadImage.js +2 -2
- package/favorites/components/Item/Item.d.ts.map +1 -1
- package/favorites/components/Item/Item.js +2 -5
- package/favorites/components/List/List.d.ts +1 -1
- package/locations/components/StoreList/StoreListProduct.js +2 -3
- package/package.json +8 -8
- package/page/widgets/ProductSlider/ProductSlider.js +1 -15
- package/product/components/Media/FeaturedMedia.js +7 -1
- package/product/components/Media/MediaImage.js +24 -23
- package/product/components/Media/MediaPlaceholder.js +4 -7
- package/product/components/MediaSlider/components/MediaImage/index.js +6 -2
- package/product/components/MediaSlider/constants.js +1 -12
- package/product/components/ProductCard/index.js +3 -10
- package/product/components/ProductGrid/components/Item/components/ItemImage/index.js +2 -5
- package/product/components/ProductGrid/hooks.d.ts +0 -7
- package/product/components/ProductGrid/hooks.d.ts.map +1 -1
- package/product/components/ProductGrid/hooks.js +9 -10
- package/product/components/ProductImage/ProductImage.d.ts +42 -0
- package/product/components/ProductImage/ProductImage.d.ts.map +1 -0
- package/product/components/ProductImage/ProductImage.js +153 -0
- package/product/components/ProductImage/ProductImagePlaceholder.d.ts +23 -0
- package/product/components/ProductImage/ProductImagePlaceholder.d.ts.map +1 -0
- package/product/components/ProductImage/ProductImagePlaceholder.js +9 -14
- package/product/components/ProductImage/constants.d.ts +7 -0
- package/product/components/ProductImage/constants.d.ts.map +1 -0
- package/product/components/ProductImage/constants.js +6 -0
- package/product/components/ProductImage/hooks.d.ts +8 -0
- package/product/components/ProductImage/hooks.d.ts.map +1 -0
- package/product/components/ProductImage/hooks.js +32 -0
- package/product/components/ProductImage/index.d.ts +3 -0
- package/product/components/ProductImage/index.d.ts.map +1 -0
- package/product/components/ProductImage/index.js +1 -201
- package/product/components/ProductImage/spec.js +115 -60
- package/product/components/ProductList/components/Item/index.js +5 -3
- package/product/components/ProductRatingStars/ProductRatingStars.js +2 -4
- package/product/components/ProductSlider/constants.d.ts +3 -0
- package/product/components/ProductSlider/constants.d.ts.map +1 -0
- package/product/components/ProductSlider/constants.js +2 -0
- package/product/components/ProductSlider/hooks.d.ts +7 -0
- package/product/components/ProductSlider/hooks.d.ts.map +1 -0
- package/product/components/ProductSlider/hooks.js +40 -0
- package/product/components/ProductSlider/index.js +4 -6
- package/product/components/ProductSlider/spec.js +2 -2
- package/product/components/Rating/index.js +2 -4
- package/product/helpers/index.js +29 -11
- package/product/hooks/index.js +3 -1
- package/product/hooks/useLoadProductImage.js +4 -2
- package/product/hooks/useShowEmptyRatingStars.d.ts +7 -0
- package/product/hooks/useShowEmptyRatingStars.d.ts.map +1 -0
- package/product/hooks/useShowEmptyRatingStars.js +21 -0
- package/product/index.js +0 -1
- package/settings/constants/appSettings.d.ts +3 -0
- package/settings/constants/appSettings.d.ts.map +1 -1
- package/settings/constants/appSettings.js +6 -1
- package/settings/constants/imageSettings.d.ts +52 -0
- package/settings/constants/imageSettings.d.ts.map +1 -0
- package/settings/constants/imageSettings.js +69 -0
- package/settings/helpers/index.d.ts +3 -0
- package/settings/helpers/index.d.ts.map +1 -0
- package/settings/helpers/index.js +1 -0
- package/settings/helpers/resolveImageSettings.d.ts +70 -0
- package/settings/helpers/resolveImageSettings.d.ts.map +1 -0
- package/settings/helpers/resolveImageSettings.js +171 -0
- package/settings/helpers/toImageQuality.d.ts +7 -0
- package/settings/helpers/toImageQuality.d.ts.map +1 -0
- package/settings/helpers/toImageQuality.js +44 -0
- package/settings/helpers/toThumborColor.d.ts +15 -0
- package/settings/helpers/toThumborColor.d.ts.map +1 -0
- package/settings/helpers/toThumborColor.js +51 -0
- package/settings/hooks/index.d.ts +2 -0
- package/settings/hooks/index.d.ts.map +1 -0
- package/settings/hooks/index.js +1 -0
- package/settings/hooks/useImageSettings.d.ts +28 -0
- package/settings/hooks/useImageSettings.d.ts.map +1 -0
- package/settings/hooks/useImageSettings.js +39 -0
- package/settings/reducers/appSettings.d.ts.map +1 -1
- package/settings/reducers/appSettings.js +61 -7
- package/settings/reducers/index.d.ts +2 -2
- package/settings/selectors/appSettings.d.ts +39 -1
- package/settings/selectors/appSettings.d.ts.map +1 -1
- package/settings/selectors/appSettings.js +21 -1
- package/settings/types/appSettings.d.ts +144 -9
- package/settings/types/appSettings.d.ts.map +1 -1
- package/styles/reset/root.js +23 -4
- package/styles/reset/typographyCustomProps.d.ts +12 -0
- package/styles/reset/typographyCustomProps.d.ts.map +1 -0
- package/styles/reset/typographyCustomProps.js +11 -0
- package/styles/theme/createTheme/createTypography.js +1 -0
- package/styles/theme/createTheme/helpers/cssVarsParser.d.ts.map +1 -1
- package/styles/theme/createTheme/helpers/cssVarsParser.js +10 -3
- package/product/collections/index.js +0 -1
- package/product/components/EffectivityDates/constants.d.ts +0 -3
- package/product/components/EffectivityDates/constants.d.ts.map +0 -1
- package/product/components/EffectivityDates/helpers.d.ts +0 -6
- package/product/components/EffectivityDates/helpers.d.ts.map +0 -1
- package/product/components/Media/helpers.d.ts +0 -2
- package/product/components/Media/helpers.d.ts.map +0 -1
- package/product/components/ProductImage/connector.js +0 -12
- package/product/helpers/index.d.ts +0 -13
- package/product/helpers/index.d.ts.map +0 -1
- package/product/helpers/redirects.d.ts +0 -2
- package/product/helpers/redirects.d.ts.map +0 -1
- package/product/selectors/helpers/filterProperties.d.ts +0 -8
- package/product/selectors/helpers/filterProperties.d.ts.map +0 -1
- package/product/selectors/helpers/index.d.ts +0 -3
- package/product/selectors/helpers/index.d.ts.map +0 -1
- package/product/selectors/helpers/wrapMemoizedSelector.d.ts +0 -2
- package/product/selectors/helpers/wrapMemoizedSelector.d.ts.map +0 -1
- package/product/selectors/product.d.ts +0 -112
- package/product/selectors/product.d.ts.map +0 -1
|
@@ -2,12 +2,9 @@ import React, { useMemo, memo } from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { SurroundPortals } from '@shopgate/engage/components';
|
|
4
4
|
import { PRODUCT_ITEM_IMAGE } from '@shopgate/engage/category/constants';
|
|
5
|
-
import {
|
|
5
|
+
import { ProductImage } from '@shopgate/engage/product';
|
|
6
6
|
import { makeStyles } from '@shopgate/engage/styles';
|
|
7
7
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
-
const {
|
|
9
|
-
ListImage: gridResolutions
|
|
10
|
-
} = getProductImageSettings();
|
|
11
8
|
const useStyles = makeStyles()(theme => ({
|
|
12
9
|
image: {
|
|
13
10
|
background: theme.palette.background.surface
|
|
@@ -37,7 +34,7 @@ const ItemImage = ({
|
|
|
37
34
|
className: classes.image,
|
|
38
35
|
alt: name,
|
|
39
36
|
src: imageUrl,
|
|
40
|
-
|
|
37
|
+
context: "list",
|
|
41
38
|
itemProp: "image"
|
|
42
39
|
})
|
|
43
40
|
});
|
|
@@ -5,13 +5,6 @@
|
|
|
5
5
|
* Precedence:
|
|
6
6
|
* - Once the app settings are hydrated from a source (admin sync / jsonp), they
|
|
7
7
|
* are authoritative everywhere.
|
|
8
|
-
* - Before hydration, when the grid is rendered outside a page-builder widget
|
|
9
|
-
* (Category / Search / legacy Products widget), the single legacy
|
|
10
|
-
* `useWidgetSettings` scalar is honored flat across all breakpoints. This
|
|
11
|
-
* reproduces the old theme grid and preserves the merchant's configured value.
|
|
12
|
-
* - Otherwise (inside a widget, pre-hydration) the built-in responsive default
|
|
13
|
-
* from the app settings is used ({ xs: 2, md: 4 }), so the inherited legacy
|
|
14
|
-
* `columns: 2` default no longer pins tablet to 2.
|
|
15
8
|
* @returns The resolved number of columns for the active breakpoint.
|
|
16
9
|
*/
|
|
17
10
|
export declare const useProductGridColumns: () => number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../../product/components/ProductGrid/hooks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../../product/components/ProductGrid/hooks.ts"],"names":[],"mappings":"AAiBA;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,QAAO,MAmCxC,CAAC"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import "core-js/modules/es.array.reduce.js";
|
|
1
2
|
import { useContext, useMemo } from 'react';
|
|
2
3
|
import { useSelector } from 'react-redux';
|
|
3
4
|
import { WidgetContext } from '@shopgate/engage/page/components/Widgets/WidgetContext';
|
|
4
5
|
import { useWidgetSettings } from '@shopgate/engage/core/hooks';
|
|
5
6
|
import { useResponsiveValue } from '@shopgate/engage/styles';
|
|
6
7
|
import { getAreAppSettingsHydrated, getProductGridColumns } from '@shopgate/engage/settings/selectors/appSettings';
|
|
8
|
+
import { SCREEN_SIZE_BREAKPOINTS } from '@shopgate/engage/settings/constants/appSettings';
|
|
7
9
|
import { WIDGET_ID } from "./constants";
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -13,13 +15,6 @@ import { WIDGET_ID } from "./constants";
|
|
|
13
15
|
* Precedence:
|
|
14
16
|
* - Once the app settings are hydrated from a source (admin sync / jsonp), they
|
|
15
17
|
* are authoritative everywhere.
|
|
16
|
-
* - Before hydration, when the grid is rendered outside a page-builder widget
|
|
17
|
-
* (Category / Search / legacy Products widget), the single legacy
|
|
18
|
-
* `useWidgetSettings` scalar is honored flat across all breakpoints. This
|
|
19
|
-
* reproduces the old theme grid and preserves the merchant's configured value.
|
|
20
|
-
* - Otherwise (inside a widget, pre-hydration) the built-in responsive default
|
|
21
|
-
* from the app settings is used ({ xs: 2, md: 4 }), so the inherited legacy
|
|
22
|
-
* `columns: 2` default no longer pins tablet to 2.
|
|
23
18
|
* @returns The resolved number of columns for the active breakpoint.
|
|
24
19
|
*/
|
|
25
20
|
export const useProductGridColumns = () => {
|
|
@@ -30,20 +25,24 @@ export const useProductGridColumns = () => {
|
|
|
30
25
|
} = useContext(WidgetContext);
|
|
31
26
|
const isInsideWidget = Boolean(widgetCode);
|
|
32
27
|
const areAppSettingsHydrated = useSelector(getAreAppSettingsHydrated);
|
|
33
|
-
// The built-in default {
|
|
28
|
+
// The built-in default { small: 2, large: 4 } until hydrated, the source value after.
|
|
34
29
|
const appSettingsColumns = useSelector(getProductGridColumns);
|
|
35
30
|
const {
|
|
36
31
|
columns: legacyColumns
|
|
37
32
|
} = useWidgetSettings(WIDGET_ID) || {};
|
|
38
|
-
const
|
|
33
|
+
const sizes = useMemo(() => {
|
|
39
34
|
// Only special case: pre-hydration, outside a widget, honor the legacy
|
|
40
35
|
// scalar flat across every breakpoint.
|
|
41
36
|
if (!areAppSettingsHydrated && !isInsideWidget && typeof legacyColumns === 'number') {
|
|
42
37
|
return {
|
|
43
|
-
|
|
38
|
+
small: legacyColumns
|
|
44
39
|
};
|
|
45
40
|
}
|
|
46
41
|
return appSettingsColumns;
|
|
47
42
|
}, [areAppSettingsHydrated, isInsideWidget, legacyColumns, appSettingsColumns]);
|
|
43
|
+
const breakpoints = useMemo(() => Object.entries(sizes).reduce((acc, [size, value]) => {
|
|
44
|
+
acc[SCREEN_SIZE_BREAKPOINTS[size]] = value;
|
|
45
|
+
return acc;
|
|
46
|
+
}, {}), [sizes]);
|
|
48
47
|
return useResponsiveValue(breakpoints);
|
|
49
48
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ImageProps } from '@shopgate/pwa-common/components/Image';
|
|
2
|
+
import type { ImageResolution, ProductImageContext } from '@shopgate/engage/settings/types/appSettings';
|
|
3
|
+
/**
|
|
4
|
+
* Everything the underlying Image accepts is forwarded, minus the two this component owns:
|
|
5
|
+
* `backgroundColor`, derived from `noBackground`, and `placeholder`.
|
|
6
|
+
*/
|
|
7
|
+
export interface ProductImageProps extends Omit<ImageProps, 'backgroundColor' | 'placeholder'> {
|
|
8
|
+
/**
|
|
9
|
+
* The surface this image is rendered on. Determines the resolutions and aspect ratio, which are
|
|
10
|
+
* configured app wide. Ignored when an explicit `resolutions` prop is passed.
|
|
11
|
+
*/
|
|
12
|
+
context?: ProductImageContext | null;
|
|
13
|
+
/**
|
|
14
|
+
* Drops the background behind the image.
|
|
15
|
+
*/
|
|
16
|
+
noBackground?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Image to show instead of the placeholder icon. The shop wide setting wins over this.
|
|
19
|
+
*/
|
|
20
|
+
placeholderSrc?: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Pass a `context` instead, so the image follows the configured aspect ratio.
|
|
23
|
+
*/
|
|
24
|
+
ratio?: number[] | null;
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated Pass a `context` instead, so the image follows the configured aspect ratio.
|
|
27
|
+
* Still honored, and still takes precedence over `context`.
|
|
28
|
+
*/
|
|
29
|
+
resolutions?: ImageResolution[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The product image component.
|
|
33
|
+
*
|
|
34
|
+
* Behaves like the core Image component, with a product placeholder for a missing or failed image.
|
|
35
|
+
* @param props The component props.
|
|
36
|
+
* @returns The rendered component.
|
|
37
|
+
*/
|
|
38
|
+
declare const ProductImage: (props: ProductImageProps) => import("react").JSX.Element;
|
|
39
|
+
export { ProductImage as UnwrappedProductImage };
|
|
40
|
+
declare const _default: import("react").MemoExoticComponent<(props: ProductImageProps) => import("react").JSX.Element>;
|
|
41
|
+
export default _default;
|
|
42
|
+
//# sourceMappingURL=ProductImage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductImage.d.ts","sourceRoot":"","sources":["../../../../product/components/ProductImage/ProductImage.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAMxE,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACpB,MAAM,6CAA6C,CAAC;AA0BrD;;;GAGG;AACH,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,UAAU,EAAE,iBAAiB,GAAG,aAAa,CAAC;IAC5F;;;OAGG;IACH,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;CACjC;AAmCD;;;;;;GAMG;AACH,QAAA,MAAM,YAAY,GAAI,OAAO,iBAAiB,gCA0E7C,CAAC;AAEF,OAAO,EAAE,YAAY,IAAI,qBAAqB,EAAE,CAAC;oEAOX,iBAAiB;AAOvD,wBAA2C"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useLayoutEffect, useMemo, memo } from 'react';
|
|
2
|
+
import { useSelector } from 'react-redux';
|
|
3
|
+
import { logger } from '@shopgate/pwa-core';
|
|
4
|
+
import Image from '@shopgate/pwa-common/components/Image';
|
|
5
|
+
import PlaceholderIcon from '@shopgate/pwa-ui-shared/icons/PlaceholderIcon';
|
|
6
|
+
import SurroundPortals from '@shopgate/pwa-common/components/SurroundPortals';
|
|
7
|
+
import { makeStyles, useTheme } from '@shopgate/engage/styles';
|
|
8
|
+
import { useProductImageSettings } from '@shopgate/engage/settings/hooks';
|
|
9
|
+
import { getProductImagePlaceholder } from '@shopgate/engage/settings/selectors/shopSettings';
|
|
10
|
+
import { PORTAL_PRODUCT_IMAGE } from "../../../components/constants";
|
|
11
|
+
import ProductImagePlaceholder from "./ProductImagePlaceholder";
|
|
12
|
+
import { useProductImageShadow } from "./hooks";
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
const placeholderIconScale = 0.65;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The context applied when a caller passes neither a context nor explicit resolutions.
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_PRODUCT_IMAGE_CONTEXT = 'list';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The resolutions and aspect ratio the image is rendered with.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Everything the underlying Image accepts is forwarded, minus the two this component owns:
|
|
27
|
+
* `backgroundColor`, derived from `noBackground`, and `placeholder`.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const useStyles = makeStyles()(theme => ({
|
|
31
|
+
placeholderContent: {
|
|
32
|
+
position: 'absolute',
|
|
33
|
+
width: '100%',
|
|
34
|
+
height: '100%',
|
|
35
|
+
top: 0,
|
|
36
|
+
textAlign: 'center'
|
|
37
|
+
},
|
|
38
|
+
placeholder: {
|
|
39
|
+
position: 'absolute',
|
|
40
|
+
width: `${placeholderIconScale * 100}% !important`,
|
|
41
|
+
height: `${placeholderIconScale * 100}% !important`,
|
|
42
|
+
top: `${(1.0 - placeholderIconScale) * 50}%`,
|
|
43
|
+
left: `${(1.0 - placeholderIconScale) * 50}%`,
|
|
44
|
+
color: theme.palette.background.emphasized
|
|
45
|
+
},
|
|
46
|
+
innerShadow: {
|
|
47
|
+
position: 'relative',
|
|
48
|
+
overflow: 'hidden',
|
|
49
|
+
':after': {
|
|
50
|
+
display: 'block',
|
|
51
|
+
content: '""',
|
|
52
|
+
position: 'absolute',
|
|
53
|
+
top: 0,
|
|
54
|
+
right: 0,
|
|
55
|
+
bottom: 0,
|
|
56
|
+
left: 0,
|
|
57
|
+
boxShadow: 'inset 0 0 20px rgba(0, 0, 0, .05)',
|
|
58
|
+
pointerEvents: 'none'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The product image component.
|
|
65
|
+
*
|
|
66
|
+
* Behaves like the core Image component, with a product placeholder for a missing or failed image.
|
|
67
|
+
* @param props The component props.
|
|
68
|
+
* @returns The rendered component.
|
|
69
|
+
*/
|
|
70
|
+
const ProductImage = props => {
|
|
71
|
+
const {
|
|
72
|
+
alt = null,
|
|
73
|
+
className = null,
|
|
74
|
+
context = null,
|
|
75
|
+
noBackground = false,
|
|
76
|
+
placeholderSrc = null,
|
|
77
|
+
ratio = null,
|
|
78
|
+
resolutions = null,
|
|
79
|
+
src = null
|
|
80
|
+
} = props;
|
|
81
|
+
const productImageSettings = useProductImageSettings();
|
|
82
|
+
|
|
83
|
+
// An explicit resolutions prop wins over the context, which is only consulted without one.
|
|
84
|
+
const resolved = useMemo(() => {
|
|
85
|
+
if (resolutions) {
|
|
86
|
+
return {
|
|
87
|
+
resolutions,
|
|
88
|
+
ratio
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return productImageSettings[context || DEFAULT_PRODUCT_IMAGE_CONTEXT] ?? productImageSettings[DEFAULT_PRODUCT_IMAGE_CONTEXT];
|
|
92
|
+
}, [context, productImageSettings, ratio, resolutions]);
|
|
93
|
+
useLayoutEffect(() => {
|
|
94
|
+
logger.assert(!(resolutions && context), 'ProductImage received both "resolutions" and "context". The explicit resolutions win - drop one of them.');
|
|
95
|
+
/* eslint-disable-next-line react-hooks/exhaustive-deps -- legacy: assert once at mount */
|
|
96
|
+
}, []);
|
|
97
|
+
const {
|
|
98
|
+
classes,
|
|
99
|
+
cx
|
|
100
|
+
} = useStyles();
|
|
101
|
+
const theme = useTheme();
|
|
102
|
+
const showInnerShadow = useProductImageShadow();
|
|
103
|
+
|
|
104
|
+
// Image decides when to show this.
|
|
105
|
+
const placeholder = placeholderSrc ? /*#__PURE__*/_jsx(ProductImagePlaceholder, {
|
|
106
|
+
src: placeholderSrc,
|
|
107
|
+
showInnerShadow: showInnerShadow,
|
|
108
|
+
noBackground: noBackground
|
|
109
|
+
}) : /*#__PURE__*/_jsx("div", {
|
|
110
|
+
"aria-hidden": true,
|
|
111
|
+
className: classes.placeholderContent,
|
|
112
|
+
"data-test-id": "placeHolder",
|
|
113
|
+
children: /*#__PURE__*/_jsx(PlaceholderIcon, {
|
|
114
|
+
className: classes.placeholder
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
return /*#__PURE__*/_jsx(SurroundPortals, {
|
|
118
|
+
portalName: PORTAL_PRODUCT_IMAGE,
|
|
119
|
+
portalProps: src ? {
|
|
120
|
+
src,
|
|
121
|
+
resolutions: resolved.resolutions,
|
|
122
|
+
ratio: resolved.ratio
|
|
123
|
+
} : undefined,
|
|
124
|
+
children: /*#__PURE__*/_jsx("div", {
|
|
125
|
+
className: cx(className, 'engage__product__product-image'),
|
|
126
|
+
children: /*#__PURE__*/_jsx(Image, {
|
|
127
|
+
...props,
|
|
128
|
+
resolutions: resolved.resolutions,
|
|
129
|
+
ratio: resolved.ratio,
|
|
130
|
+
placeholder: placeholder,
|
|
131
|
+
className: showInnerShadow ? classes.innerShadow : '',
|
|
132
|
+
backgroundColor: noBackground ? 'transparent' : theme.palette.background.surface,
|
|
133
|
+
"aria-hidden": !alt
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
export { ProductImage as UnwrappedProductImage };
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Supplies the shop wide placeholder image.
|
|
142
|
+
* @param props The component props.
|
|
143
|
+
* @returns The rendered component.
|
|
144
|
+
*/
|
|
145
|
+
const ConnectedProductImage = props => {
|
|
146
|
+
// The shop wide placeholder takes precedence over a passed one.
|
|
147
|
+
const placeholderSrc = useSelector(getProductImagePlaceholder) ?? props.placeholderSrc ?? null;
|
|
148
|
+
return /*#__PURE__*/_jsx(ProductImage, {
|
|
149
|
+
...props,
|
|
150
|
+
placeholderSrc: placeholderSrc
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
export default /*#__PURE__*/memo(ConnectedProductImage);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ProductImagePlaceholderProps {
|
|
2
|
+
/**
|
|
3
|
+
* Drops the background behind the placeholder.
|
|
4
|
+
*/
|
|
5
|
+
noBackground?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Whether the placeholder gets an inset shadow, matching the image it stands in for.
|
|
8
|
+
*/
|
|
9
|
+
showInnerShadow?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The merchant configured placeholder image. While it loads, and when it is absent, the
|
|
12
|
+
* built-in icon is shown instead.
|
|
13
|
+
*/
|
|
14
|
+
src?: string | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The ProductImagePlaceholder component.
|
|
18
|
+
* @param props The components props.
|
|
19
|
+
* @returns The rendered component.
|
|
20
|
+
*/
|
|
21
|
+
declare const ProductImagePlaceholder: (props: ProductImagePlaceholderProps) => import("react").JSX.Element;
|
|
22
|
+
export default ProductImagePlaceholder;
|
|
23
|
+
//# sourceMappingURL=ProductImagePlaceholder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductImagePlaceholder.d.ts","sourceRoot":"","sources":["../../../../product/components/ProductImage/ProductImagePlaceholder.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,4BAA4B;IAC3C;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAqCD;;;;GAIG;AACH,QAAA,MAAM,uBAAuB,GAAI,OAAO,4BAA4B,gCAwCnE,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
2
|
import PlaceholderIcon from '@shopgate/pwa-ui-shared/icons/PlaceholderIcon';
|
|
4
3
|
import { makeStyles, useTheme } from '@shopgate/engage/styles';
|
|
5
4
|
import { useLoadImage } from '@shopgate/engage/core';
|
|
@@ -40,14 +39,15 @@ const useStyles = makeStyles()(theme => ({
|
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
41
|
* The ProductImagePlaceholder component.
|
|
43
|
-
* @param
|
|
44
|
-
* @returns
|
|
42
|
+
* @param props The components props.
|
|
43
|
+
* @returns The rendered component.
|
|
45
44
|
*/
|
|
46
|
-
const ProductImagePlaceholder =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
const ProductImagePlaceholder = props => {
|
|
46
|
+
const {
|
|
47
|
+
src = null,
|
|
48
|
+
showInnerShadow = false,
|
|
49
|
+
noBackground = false
|
|
50
|
+
} = props;
|
|
51
51
|
const {
|
|
52
52
|
classes,
|
|
53
53
|
cx
|
|
@@ -85,9 +85,4 @@ const ProductImagePlaceholder = ({
|
|
|
85
85
|
}), !showPlaceholder && ' ']
|
|
86
86
|
});
|
|
87
87
|
};
|
|
88
|
-
ProductImagePlaceholder.defaultProps = {
|
|
89
|
-
src: null,
|
|
90
|
-
noBackground: false,
|
|
91
|
-
showInnerShadow: false
|
|
92
|
-
};
|
|
93
88
|
export default ProductImagePlaceholder;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The widget id the legacy inner shadow setting is configured under.
|
|
3
|
+
*
|
|
4
|
+
* Extracted so the resolution hook can read it without importing the component.
|
|
5
|
+
*/
|
|
6
|
+
export declare const WIDGET_ID = "@shopgate/engage/product/ProductImage";
|
|
7
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../product/components/ProductImage/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,SAAS,0CAA0C,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves whether product images render with an inset shadow.
|
|
3
|
+
* @param widgetId The widget id the legacy setting is configured under, for the product image
|
|
4
|
+
* components that have their own.
|
|
5
|
+
* @returns Whether to render the shadow.
|
|
6
|
+
*/
|
|
7
|
+
export declare const useProductImageShadow: (widgetId?: string) => boolean;
|
|
8
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../../product/components/ProductImage/hooks.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAU,MAAkB,KAAG,OAiBpE,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useSelector } from 'react-redux';
|
|
3
|
+
// Imported via its module path rather than the "core/hooks" barrel, to keep this out of the
|
|
4
|
+
// import cycle the barrel pulls in.
|
|
5
|
+
import { useWidgetSettings } from '@shopgate/engage/core/hooks/useWidgetSettings';
|
|
6
|
+
import { getAreAppSettingsHydrated, getImageSettings } from '@shopgate/engage/settings/selectors/appSettings';
|
|
7
|
+
import { DEFAULT_SHOW_INNER_SHADOW } from '@shopgate/engage/settings/constants/imageSettings';
|
|
8
|
+
import { WIDGET_ID } from "./constants";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves whether product images render with an inset shadow.
|
|
12
|
+
* @param widgetId The widget id the legacy setting is configured under, for the product image
|
|
13
|
+
* components that have their own.
|
|
14
|
+
* @returns Whether to render the shadow.
|
|
15
|
+
*/
|
|
16
|
+
export const useProductImageShadow = (widgetId = WIDGET_ID) => {
|
|
17
|
+
const areAppSettingsHydrated = useSelector(getAreAppSettingsHydrated);
|
|
18
|
+
const configured = useSelector(getImageSettings)?.product?.showInnerShadow;
|
|
19
|
+
const {
|
|
20
|
+
showInnerShadow: legacy
|
|
21
|
+
} = useWidgetSettings(widgetId) || {};
|
|
22
|
+
return useMemo(() => {
|
|
23
|
+
if (areAppSettingsHydrated) {
|
|
24
|
+
return configured ?? DEFAULT_SHOW_INNER_SHADOW;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Until a source hydrates the app settings the legacy widget configuration wins, and failing
|
|
28
|
+
// that the built-in default, which is derived from the legacy app config flag so that each
|
|
29
|
+
// theme keeps its own behavior.
|
|
30
|
+
return legacy ?? DEFAULT_SHOW_INNER_SHADOW;
|
|
31
|
+
}, [areAppSettingsHydrated, configured, legacy]);
|
|
32
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../product/components/ProductImage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -1,201 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import { logger } from '@shopgate/pwa-core';
|
|
4
|
-
import appConfig from '@shopgate/pwa-common/helpers/config';
|
|
5
|
-
import Image from '@shopgate/pwa-common/components/Image';
|
|
6
|
-
import PlaceholderIcon from '@shopgate/pwa-ui-shared/icons/PlaceholderIcon';
|
|
7
|
-
import SurroundPortals from '@shopgate/pwa-common/components/SurroundPortals';
|
|
8
|
-
import { makeStyles, useTheme } from '@shopgate/engage/styles';
|
|
9
|
-
import { withWidgetSettings } from "../../../core/hocs/withWidgetSettings";
|
|
10
|
-
import { PORTAL_PRODUCT_IMAGE } from "../../../components/constants";
|
|
11
|
-
import ProductImagePlaceholder from "./ProductImagePlaceholder";
|
|
12
|
-
import connect from "./connector";
|
|
13
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
-
const placeholderIconScale = 0.65;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Derives the image aspect ratio from explicit ratio props or the largest resolution.
|
|
18
|
-
* @param {Object} props The component props.
|
|
19
|
-
* @param {number[]} [props.ratio] Optional width/height ratio tuple.
|
|
20
|
-
* @param {Array<{ width: number, height: number }>} props.resolutions Available image resolutions.
|
|
21
|
-
* @returns {string} The computed ratio as a fixed decimal string.
|
|
22
|
-
*/
|
|
23
|
-
const getImageRatio = ({
|
|
24
|
-
ratio,
|
|
25
|
-
resolutions
|
|
26
|
-
} = {}) => {
|
|
27
|
-
if (Array.isArray(ratio) && ratio.length === 2 && ratio[0]) {
|
|
28
|
-
const [x, y] = ratio;
|
|
29
|
-
return (y / x).toFixed(3);
|
|
30
|
-
}
|
|
31
|
-
if (Array.isArray(resolutions) && resolutions.length > 0) {
|
|
32
|
-
const {
|
|
33
|
-
width,
|
|
34
|
-
height
|
|
35
|
-
} = resolutions[resolutions.length - 1];
|
|
36
|
-
if (width && height) {
|
|
37
|
-
return (height / width).toFixed(3);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return '1.000';
|
|
41
|
-
};
|
|
42
|
-
const useStyles = makeStyles()((theme, {
|
|
43
|
-
ratio
|
|
44
|
-
}) => ({
|
|
45
|
-
placeholderContainer: {
|
|
46
|
-
position: 'relative',
|
|
47
|
-
width: '100%',
|
|
48
|
-
':before': {
|
|
49
|
-
display: 'block',
|
|
50
|
-
content: '""',
|
|
51
|
-
width: '100%',
|
|
52
|
-
paddingTop: `${100 * ratio}%`
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
placeholderContent: {
|
|
56
|
-
position: 'absolute',
|
|
57
|
-
width: '100%',
|
|
58
|
-
height: '100%',
|
|
59
|
-
top: 0,
|
|
60
|
-
textAlign: 'center'
|
|
61
|
-
},
|
|
62
|
-
placeholder: {
|
|
63
|
-
position: 'absolute',
|
|
64
|
-
width: `${placeholderIconScale * 100}% !important`,
|
|
65
|
-
height: `${placeholderIconScale * 100}% !important`,
|
|
66
|
-
top: `${(1.0 - placeholderIconScale) * 50}%`,
|
|
67
|
-
left: `${(1.0 - placeholderIconScale) * 50}%`,
|
|
68
|
-
color: theme.palette.background.emphasized
|
|
69
|
-
},
|
|
70
|
-
innerShadow: {
|
|
71
|
-
position: 'relative',
|
|
72
|
-
overflow: 'hidden',
|
|
73
|
-
':after': {
|
|
74
|
-
display: 'block',
|
|
75
|
-
content: '""',
|
|
76
|
-
position: 'absolute',
|
|
77
|
-
top: 0,
|
|
78
|
-
right: 0,
|
|
79
|
-
bottom: 0,
|
|
80
|
-
left: 0,
|
|
81
|
-
boxShadow: 'inset 0 0 20px rgba(0, 0, 0, .05)',
|
|
82
|
-
pointerEvents: 'none'
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}));
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* The product image component.
|
|
89
|
-
* This component will behave like the core Image component with the additional
|
|
90
|
-
* feature of showing a placeholder in case no src property has been passed
|
|
91
|
-
* or the given source image cannot be loaded.
|
|
92
|
-
* @param {Object} props Props.
|
|
93
|
-
* @returns {JSX.Element}
|
|
94
|
-
*/
|
|
95
|
-
const ProductImage = props => {
|
|
96
|
-
const {
|
|
97
|
-
alt,
|
|
98
|
-
className,
|
|
99
|
-
noBackground,
|
|
100
|
-
placeholderSrc,
|
|
101
|
-
ratio,
|
|
102
|
-
resolutions,
|
|
103
|
-
src,
|
|
104
|
-
srcmap,
|
|
105
|
-
widgetSettings
|
|
106
|
-
} = props;
|
|
107
|
-
const ratioValue = Number.parseFloat(getImageRatio({
|
|
108
|
-
ratio,
|
|
109
|
-
resolutions
|
|
110
|
-
})) || 1;
|
|
111
|
-
const {
|
|
112
|
-
classes,
|
|
113
|
-
cx
|
|
114
|
-
} = useStyles({
|
|
115
|
-
ratio: ratioValue
|
|
116
|
-
});
|
|
117
|
-
const theme = useTheme();
|
|
118
|
-
const [showPlaceholder, setShowPlaceholder] = useState(!src && (srcmap === null || srcmap.length === 0));
|
|
119
|
-
const [imageLoadingFailed, setImageLoadingFailed] = useState(false);
|
|
120
|
-
useLayoutEffect(() => {
|
|
121
|
-
logger.assert(!srcmap, 'Use of srcmap prop is deprecated. Use resolutions instead');
|
|
122
|
-
/* eslint-disable-next-line react-hooks/exhaustive-deps -- legacy: assert once at mount */
|
|
123
|
-
}, []);
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
const nextShowPlaceholder = !src && (!srcmap || srcmap.length === 0);
|
|
126
|
-
setShowPlaceholder(nextShowPlaceholder);
|
|
127
|
-
}, [src, srcmap]);
|
|
128
|
-
const imageLoadingFailedHandler = useCallback(() => {
|
|
129
|
-
setShowPlaceholder(true);
|
|
130
|
-
setImageLoadingFailed(true);
|
|
131
|
-
}, []);
|
|
132
|
-
let {
|
|
133
|
-
showInnerShadow
|
|
134
|
-
} = widgetSettings;
|
|
135
|
-
if (typeof showInnerShadow === 'undefined') {
|
|
136
|
-
showInnerShadow = !appConfig.hideProductImageShadow;
|
|
137
|
-
}
|
|
138
|
-
if (imageLoadingFailed || showPlaceholder) {
|
|
139
|
-
return /*#__PURE__*/_jsx(SurroundPortals, {
|
|
140
|
-
portalName: PORTAL_PRODUCT_IMAGE,
|
|
141
|
-
children: /*#__PURE__*/_jsx("div", {
|
|
142
|
-
className: cx(classes.placeholderContainer, {
|
|
143
|
-
[classes.innerShadow]: showInnerShadow,
|
|
144
|
-
[className]: !!className
|
|
145
|
-
}),
|
|
146
|
-
children: placeholderSrc ? /*#__PURE__*/_jsx(ProductImagePlaceholder, {
|
|
147
|
-
src: placeholderSrc,
|
|
148
|
-
showInnerShadow: showInnerShadow,
|
|
149
|
-
noBackground: noBackground
|
|
150
|
-
}) : /*#__PURE__*/_jsx("div", {
|
|
151
|
-
"aria-hidden": true,
|
|
152
|
-
className: classes.placeholderContent,
|
|
153
|
-
"data-test-id": "placeHolder",
|
|
154
|
-
children: /*#__PURE__*/_jsx(PlaceholderIcon, {
|
|
155
|
-
className: classes.placeholder
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
return /*#__PURE__*/_jsx(SurroundPortals, {
|
|
162
|
-
portalName: PORTAL_PRODUCT_IMAGE,
|
|
163
|
-
portalProps: {
|
|
164
|
-
src,
|
|
165
|
-
resolutions
|
|
166
|
-
},
|
|
167
|
-
children: /*#__PURE__*/_jsx("div", {
|
|
168
|
-
className: cx(className, 'engage__product__product-image'),
|
|
169
|
-
children: /*#__PURE__*/_jsx(Image, {
|
|
170
|
-
...props,
|
|
171
|
-
className: showInnerShadow ? classes.innerShadow : '',
|
|
172
|
-
backgroundColor: noBackground ? 'transparent' : theme.palette.background.surface,
|
|
173
|
-
onError: imageLoadingFailedHandler,
|
|
174
|
-
"aria-hidden": !alt
|
|
175
|
-
})
|
|
176
|
-
})
|
|
177
|
-
});
|
|
178
|
-
};
|
|
179
|
-
ProductImage.defaultProps = {
|
|
180
|
-
alt: null,
|
|
181
|
-
animating: true,
|
|
182
|
-
className: null,
|
|
183
|
-
forcePlaceholder: false,
|
|
184
|
-
highestResolutionLoaded: () => {},
|
|
185
|
-
noBackground: false,
|
|
186
|
-
ratio: null,
|
|
187
|
-
resolutions: [{
|
|
188
|
-
width: 50,
|
|
189
|
-
height: 50,
|
|
190
|
-
blur: 2
|
|
191
|
-
}, {
|
|
192
|
-
width: 440,
|
|
193
|
-
height: 440
|
|
194
|
-
}],
|
|
195
|
-
src: null,
|
|
196
|
-
srcmap: null,
|
|
197
|
-
placeholderSrc: null,
|
|
198
|
-
widgetSettings: {}
|
|
199
|
-
};
|
|
200
|
-
export { ProductImage as UnwrappedProductImage };
|
|
201
|
-
export default connect(withWidgetSettings(/*#__PURE__*/memo(ProductImage), '@shopgate/engage/product/ProductImage'));
|
|
1
|
+
export { default } from "./ProductImage";
|