Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/template-retail-react-app/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Provide support for partial hydration [#2696](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2696)
- Show Automatic Bonus Products on Cart Page [#2704](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2704)
- Support Standard Products [2697](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2697)
- Use `<picture>` element for responsive images [#2724](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2724)


## v6.1.0 (May 22, 2025)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,112 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* Copyright (c) 2025, Salesforce, Inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React, {useMemo} from 'react'
import {Helmet} from 'react-helmet'
import PropTypes from 'prop-types'
import {Box, useTheme} from '@salesforce/retail-react-app/app/components/shared/ui'
import Image from '@salesforce/retail-react-app/app/components/image'
import {getResponsiveImageAttributes} from '@salesforce/retail-react-app/app/utils/responsive-image'
import {Img} from '@salesforce/retail-react-app/app/components/shared/ui'
import {getResponsivePictureAttributes} from '@salesforce/retail-react-app/app/utils/responsive-image'
import {
getImageAttributes,
getImageLinkAttributes
} from '@salesforce/retail-react-app/app/utils/image'
import {isServer} from '@salesforce/retail-react-app/app/components/image/utils'

/**
* Quickly create a responsive image using your Dynamic Imaging Service
* @example
* // Widths without a unit are interpreted as px values
* <DynamicImage src="http://example.com/image.jpg[?sw={width}&q=60]" widths={[100, 360, 720]} />
* <DynamicImage src="http://example.com/image.jpg[?sw={width}&q=60]" widths={{base: 100, sm: 360, md: 720}} />
* // You can also use units of px or vw
* <DynamicImage src="http://example.com/image.jpg[?sw={width}&q=60]" widths={['50vw', '100vw', '500px']} />
* Responsive image component optimized to work with the Dynamic Imaging Service.
* Via this component it's easy to create a `<picture>` element with related
* theme-aware `<source>` elements and responsive preloading for high-priority
* images.
* @example Widths without a unit defined as array (interpreted as px values)
* <DynamicImage
* src="http://example.com/image.jpg[?sw={width}&q=60]"
* widths={[100, 360, 720]} />
* @example Widths without a unit defined as object (interpreted as px values)
* <DynamicImage
* src="http://example.com/image.jpg[?sw={width}&q=60]"
* widths={{base: 100, sm: 360, md: 720}} />
* @example Widths with mixed px and vw units defined as array
* <DynamicImage
* src="http://example.com/image.jpg[?sw={width}&q=60]"
* widths={['50vw', '100vw', '500px']} />
* @example Eagerly load image with high priority and responsive preloading
* <DynamicImage
* src="http://example.com/image.jpg[?sw={width}&q=60]"
* widths={['50vw', '50vw', '20vw', '20vw', '25vw']}
* imageProps={{loading: 'eager'}}
* />
* @see {@link https://web.dev/learn/design/responsive-images}
* @see {@link https://web.dev/learn/design/picture-element}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/picture}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Responsive_images}
* @see {@link https://help.salesforce.com/s/articleView?id=cc.b2c_image_transformation_service.htm&type=5}
*/
const DynamicImage = ({src, widths, imageProps, as, ...rest}) => {
const Component = as ? as : Image
const Component = as ? as : Img
const theme = useTheme()

const responsiveImageProps = useMemo(
() => getResponsiveImageAttributes({src, widths, breakpoints: theme.breakpoints}),
[src, widths, theme.breakpoints]
)
const [responsiveImageProps, numSources, effectiveImageProps, responsiveLinks] = useMemo(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad to see this is memoized

const responsiveImageProps = getResponsivePictureAttributes({
src,
widths,
breakpoints: theme.breakpoints
})
const effectiveImageProps = getImageAttributes(imageProps)
const fetchPriority = effectiveImageProps.fetchPriority
const responsiveLinks =
!responsiveImageProps.links.length && fetchPriority === 'high'
? [
getImageLinkAttributes({
...effectiveImageProps,
fetchPriority, // React <18 vs. >=19 issue
src: responsiveImageProps.src
})
]
: responsiveImageProps.links.reduce((acc, link) => {
const linkProps = getImageLinkAttributes({
...effectiveImageProps,
...link,
fetchPriority, // React <18 vs. >=19 issue
src: responsiveImageProps.src
})
if (linkProps) {
acc.push(linkProps)
}
return acc
}, [])
return [
responsiveImageProps,
responsiveImageProps.sources.length,
effectiveImageProps,
responsiveLinks
]
}, [src, widths, theme.breakpoints])

return (
<Box {...rest}>
<Component {...responsiveImageProps} {...imageProps} />
{numSources > 0 ? (
<picture>
{responsiveImageProps.sources.map(({srcSet, sizes, media}, idx) => (
<source key={idx} media={media} sizes={sizes} srcSet={srcSet} />
))}
<Component {...effectiveImageProps} src={responsiveImageProps.src} />
</picture>
) : (
<Component {...effectiveImageProps} src={responsiveImageProps.src} />
)}

{isServer() && responsiveLinks.length > 0 && (
<Helmet>
{responsiveLinks.map((responsiveLinkProps, idx) => {
const {href, ...rest} = responsiveLinkProps
return <link key={idx} {...rest} href={href} />
})}
</Helmet>
)}
</Box>
)
}
Expand Down
Loading
Loading