Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -16,6 +16,7 @@
- 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)
- Fix passwordless race conditions in form submission [#2758](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2758)
- 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