11/*
2- * Copyright (c) 2021, salesforce.com, inc .
2+ * Copyright (c) 2025, Salesforce, Inc .
33 * All rights reserved.
44 * SPDX-License-Identifier: BSD-3-Clause
55 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66 */
77import React , { useMemo } from 'react'
8+ import { Helmet } from 'react-helmet'
89import PropTypes from 'prop-types'
910import { 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'
1218
1319/**
14- * Quickly create a responsive image using your Dynamic Imaging Service
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.
1524 * @example
1625 * // Widths without a unit are interpreted as px values
1726 * <DynamicImage src="http://example.com/image.jpg[?sw={width}&q=60]" widths={[100, 360, 720]} />
1827 * <DynamicImage src="http://example.com/image.jpg[?sw={width}&q=60]" widths={{base: 100, sm: 360, md: 720}} />
1928 * // You can also use units of px or vw
2029 * <DynamicImage src="http://example.com/image.jpg[?sw={width}&q=60]" widths={['50vw', '100vw', '500px']} />
30+ * @see {@link https://web.dev/learn/design/responsive-images }
31+ * @see {@link https://web.dev/learn/design/picture-element }
32+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/picture }
33+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images }
2134 * @see {@link https://help.salesforce.com/s/articleView?id=cc.b2c_image_transformation_service.htm&type=5 }
2235 */
2336const DynamicImage = ( { src, widths, densities, imageProps, as, ...rest } ) => {
24- const Component = as ? as : Image
37+ const Component = as ? as : Img
2538 const theme = useTheme ( )
2639
27- const responsiveImageProps = useMemo (
28- ( ) =>
29- getResponsiveImageAttributes ( { src, widths, densities, breakpoints : theme . breakpoints } ) ,
30- [ src , widths , densities , theme . breakpoints ]
31- )
40+ const [ responsiveImageProps , l , effectiveImageProps , responsiveLinks ] = useMemo ( ( ) => {
41+ const responsiveImageProps = getResponsivePictureAttributes ( {
42+ src,
43+ widths,
44+ densities,
45+ breakpoints : theme . breakpoints
46+ } )
47+ const l = responsiveImageProps . sources . length
48+ const effectiveImageProps = getImageAttributes ( imageProps )
49+ const fetchPriority = effectiveImageProps . fetchPriority
50+ const responsiveLinks =
51+ ! responsiveImageProps . links . length && fetchPriority === 'high'
52+ ? [
53+ getImageLinkAttributes ( {
54+ ...effectiveImageProps ,
55+ fetchPriority, // React <18 vs. >=19 issue
56+ src : responsiveImageProps . src
57+ } )
58+ ]
59+ : responsiveImageProps . links . reduce ( ( acc , link ) => {
60+ const linkProps = getImageLinkAttributes ( {
61+ ...effectiveImageProps ,
62+ ...link ,
63+ fetchPriority, // React <18 vs. >=19 issue
64+ src : responsiveImageProps . src
65+ } )
66+ if ( linkProps ) {
67+ acc . push ( linkProps )
68+ }
69+ return acc
70+ } , [ ] )
71+ return [ responsiveImageProps , l , effectiveImageProps , responsiveLinks ]
72+ } , [ src , widths , densities , theme . breakpoints ] )
3273
3374 return (
3475 < Box { ...rest } >
35- < Component { ...responsiveImageProps } { ...imageProps } />
76+ { l > 0 ? (
77+ < picture >
78+ { responsiveImageProps . sources . map ( ( { srcSet, sizes, media, src} , idx ) => {
79+ if ( idx < l - 1 ) {
80+ return < source key = { idx } media = { media } sizes = { sizes } srcSet = { srcSet } />
81+ }
82+ return (
83+ < Component
84+ key = { idx }
85+ { ...effectiveImageProps }
86+ sizes = { sizes }
87+ srcSet = { srcSet }
88+ src = { responsiveImageProps . src }
89+ />
90+ )
91+ } ) }
92+ </ picture >
93+ ) : (
94+ < Component { ...effectiveImageProps } src = { responsiveImageProps . src } />
95+ ) }
96+
97+ { isServer ( ) && responsiveLinks . length > 0 && (
98+ < Helmet >
99+ { responsiveLinks . map ( ( responsiveLinkProps , idx ) => {
100+ const { href, ...rest } = responsiveLinkProps
101+ return < link key = { idx } { ...rest } href = { href } />
102+ } ) }
103+ </ Helmet >
104+ ) }
36105 </ Box >
37106 )
38107}
@@ -49,7 +118,7 @@ DynamicImage.propTypes = {
49118 /**
50119 * Image density factors to apply relative to the breakpoints. Will be mapped to the corresponding `srcSet`.
51120 */
52- densities : PropTypes . array ,
121+ densities : PropTypes . oneOfType ( [ PropTypes . array , PropTypes . object ] ) ,
53122 /**
54123 * Props to pass to the inner image component
55124 */
0 commit comments