|
1 | 1 | /* |
2 | | - * Copyright (c) 2021, salesforce.com, inc. |
| 2 | + * Copyright (c) 2025, Salesforce, Inc. |
3 | 3 | * All rights reserved. |
4 | 4 | * SPDX-License-Identifier: BSD-3-Clause |
5 | 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause |
6 | 6 | */ |
7 | 7 | import React, {useMemo} from 'react' |
| 8 | +import {Helmet} from 'react-helmet' |
8 | 9 | import PropTypes from 'prop-types' |
9 | 10 | import {Box, useTheme} from '@salesforce/retail-react-app/app/components/shared/ui' |
10 | | -import Image from '@salesforce/retail-react-app/app/components/image' |
11 | | -import {getResponsiveImageAttributes} from '@salesforce/retail-react-app/app/utils/responsive-image' |
| 11 | +import {Img} from '@salesforce/retail-react-app/app/components/shared/ui' |
| 12 | +import {getResponsivePictureAttributes} from '@salesforce/retail-react-app/app/utils/responsive-image' |
| 13 | +import { |
| 14 | + getImageAttributes, |
| 15 | + getImageLinkAttributes |
| 16 | +} from '@salesforce/retail-react-app/app/utils/image' |
| 17 | +import {isServer} from '@salesforce/retail-react-app/app/components/image/utils' |
12 | 18 |
|
13 | 19 | /** |
14 | | - * Quickly create a responsive image using your Dynamic Imaging Service |
15 | | - * @example |
16 | | - * // Widths without a unit are interpreted as px values |
17 | | - * <DynamicImage src="http://example.com/image.jpg[?sw={width}&q=60]" widths={[100, 360, 720]} /> |
18 | | - * <DynamicImage src="http://example.com/image.jpg[?sw={width}&q=60]" widths={{base: 100, sm: 360, md: 720}} /> |
19 | | - * // You can also use units of px or vw |
20 | | - * <DynamicImage src="http://example.com/image.jpg[?sw={width}&q=60]" widths={['50vw', '100vw', '500px']} /> |
| 20 | + * Responsive image component optimized to work with the Dynamic Imaging Service. |
| 21 | + * Via this component it's easy to create a `<picture>` element with related |
| 22 | + * theme-aware `<source>` elements and responsive preloading for high-priority |
| 23 | + * images. |
| 24 | + * @example Widths without a unit defined as array (interpreted as px values) |
| 25 | + * <DynamicImage |
| 26 | + * src="http://example.com/image.jpg[?sw={width}&q=60]" |
| 27 | + * widths={[100, 360, 720]} /> |
| 28 | + * @example Widths without a unit defined as object (interpreted as px values) |
| 29 | + * <DynamicImage |
| 30 | + * src="http://example.com/image.jpg[?sw={width}&q=60]" |
| 31 | + * widths={{base: 100, sm: 360, md: 720}} /> |
| 32 | + * @example Widths with mixed px and vw units defined as array |
| 33 | + * <DynamicImage |
| 34 | + * src="http://example.com/image.jpg[?sw={width}&q=60]" |
| 35 | + * widths={['50vw', '100vw', '500px']} /> |
| 36 | + * @example Eagerly load image with high priority and responsive preloading |
| 37 | + * <DynamicImage |
| 38 | + * src="http://example.com/image.jpg[?sw={width}&q=60]" |
| 39 | + * widths={['50vw', '50vw', '20vw', '20vw', '25vw']} |
| 40 | + * imageProps={{loading: 'eager'}} |
| 41 | + * /> |
| 42 | + * @see {@link https://web.dev/learn/design/responsive-images} |
| 43 | + * @see {@link https://web.dev/learn/design/picture-element} |
| 44 | + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/picture} |
| 45 | + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images} |
21 | 46 | * @see {@link https://help.salesforce.com/s/articleView?id=cc.b2c_image_transformation_service.htm&type=5} |
22 | 47 | */ |
23 | 48 | const DynamicImage = ({src, widths, imageProps, as, ...rest}) => { |
24 | | - const Component = as ? as : Image |
| 49 | + const Component = as ? as : Img |
25 | 50 | const theme = useTheme() |
26 | 51 |
|
27 | | - const responsiveImageProps = useMemo( |
28 | | - () => getResponsiveImageAttributes({src, widths, breakpoints: theme.breakpoints}), |
29 | | - [src, widths, theme.breakpoints] |
30 | | - ) |
| 52 | + const [responsiveImageProps, numSources, effectiveImageProps, responsiveLinks] = useMemo(() => { |
| 53 | + const responsiveImageProps = getResponsivePictureAttributes({ |
| 54 | + src, |
| 55 | + widths, |
| 56 | + breakpoints: theme.breakpoints |
| 57 | + }) |
| 58 | + const effectiveImageProps = getImageAttributes(imageProps) |
| 59 | + const fetchPriority = effectiveImageProps.fetchPriority |
| 60 | + const responsiveLinks = |
| 61 | + !responsiveImageProps.links.length && fetchPriority === 'high' |
| 62 | + ? [ |
| 63 | + getImageLinkAttributes({ |
| 64 | + ...effectiveImageProps, |
| 65 | + fetchPriority, // React <18 vs. >=19 issue |
| 66 | + src: responsiveImageProps.src |
| 67 | + }) |
| 68 | + ] |
| 69 | + : responsiveImageProps.links.reduce((acc, link) => { |
| 70 | + const linkProps = getImageLinkAttributes({ |
| 71 | + ...effectiveImageProps, |
| 72 | + ...link, |
| 73 | + fetchPriority, // React <18 vs. >=19 issue |
| 74 | + src: responsiveImageProps.src |
| 75 | + }) |
| 76 | + if (linkProps) { |
| 77 | + acc.push(linkProps) |
| 78 | + } |
| 79 | + return acc |
| 80 | + }, []) |
| 81 | + return [ |
| 82 | + responsiveImageProps, |
| 83 | + responsiveImageProps.sources.length, |
| 84 | + effectiveImageProps, |
| 85 | + responsiveLinks |
| 86 | + ] |
| 87 | + }, [src, widths, theme.breakpoints]) |
31 | 88 |
|
32 | 89 | return ( |
33 | 90 | <Box {...rest}> |
34 | | - <Component {...responsiveImageProps} {...imageProps} /> |
| 91 | + {numSources > 0 ? ( |
| 92 | + <picture> |
| 93 | + {responsiveImageProps.sources.map(({srcSet, sizes, media}, idx) => ( |
| 94 | + <source key={idx} media={media} sizes={sizes} srcSet={srcSet} /> |
| 95 | + ))} |
| 96 | + <Component {...effectiveImageProps} src={responsiveImageProps.src} /> |
| 97 | + </picture> |
| 98 | + ) : ( |
| 99 | + <Component {...effectiveImageProps} src={responsiveImageProps.src} /> |
| 100 | + )} |
| 101 | + |
| 102 | + {isServer() && responsiveLinks.length > 0 && ( |
| 103 | + <Helmet> |
| 104 | + {responsiveLinks.map((responsiveLinkProps, idx) => { |
| 105 | + const {href, ...rest} = responsiveLinkProps |
| 106 | + return <link key={idx} {...rest} href={href} /> |
| 107 | + })} |
| 108 | + </Helmet> |
| 109 | + )} |
35 | 110 | </Box> |
36 | 111 | ) |
37 | 112 | } |
|
0 commit comments