diff --git a/config/styleguide.config.js b/config/styleguide.config.js
index 5eac994008..536d3430ee 100644
--- a/config/styleguide.config.js
+++ b/config/styleguide.config.js
@@ -314,6 +314,7 @@ module.exports = {
return [
path.resolve('packages/Card/Card.jsx'),
path.resolve('packages/Image/Image.jsx'),
+ path.resolve('packages/ResponsiveImage/ResponsiveImage.jsx'),
path.resolve('packages/Video/Video.jsx'),
path.resolve('packages/WebVideo/WebVideo.jsx'),
path.resolve('packages/A11yContent/A11yContent.jsx'),
@@ -552,6 +553,7 @@ module.exports = {
PriceLockup: path.resolve('packages/PriceLockup'),
Radio: path.resolve('packages/Radio'),
Responsive: path.resolve('packages/Responsive'),
+ ResponsiveImage: path.resolve('packages/ResponsiveImage'),
Select: path.resolve('packages/Select'),
Small: path.resolve('packages/Small'),
Spinner: path.resolve('packages/Spinner'),
diff --git a/docs/assets/responsive-image/mountains_desktop.jpg b/docs/assets/responsive-image/mountains_desktop.jpg
new file mode 100644
index 0000000000..775a4495f2
Binary files /dev/null and b/docs/assets/responsive-image/mountains_desktop.jpg differ
diff --git a/docs/assets/responsive-image/mountains_mobile.jpg b/docs/assets/responsive-image/mountains_mobile.jpg
new file mode 100644
index 0000000000..5778516fdd
Binary files /dev/null and b/docs/assets/responsive-image/mountains_mobile.jpg differ
diff --git a/docs/assets/responsive-image/mountains_tablet.jpg b/docs/assets/responsive-image/mountains_tablet.jpg
new file mode 100644
index 0000000000..0c9fcbc1e3
Binary files /dev/null and b/docs/assets/responsive-image/mountains_tablet.jpg differ
diff --git a/e2e/output/components/ResponsiveImage/baseline/chrome_78.png b/e2e/output/components/ResponsiveImage/baseline/chrome_78.png
new file mode 100644
index 0000000000..dfbfe94e33
Binary files /dev/null and b/e2e/output/components/ResponsiveImage/baseline/chrome_78.png differ
diff --git a/e2e/output/components/ResponsiveImage/baseline/firefox_80.png b/e2e/output/components/ResponsiveImage/baseline/firefox_80.png
new file mode 100644
index 0000000000..8efc49fc6f
Binary files /dev/null and b/e2e/output/components/ResponsiveImage/baseline/firefox_80.png differ
diff --git a/e2e/output/components/ResponsiveImage/baseline/internet explorer_headless.png b/e2e/output/components/ResponsiveImage/baseline/internet explorer_headless.png
new file mode 100644
index 0000000000..05b8b237a7
Binary files /dev/null and b/e2e/output/components/ResponsiveImage/baseline/internet explorer_headless.png differ
diff --git a/e2e/output/components/ResponsiveImage/baseline/microsoftedge_18.png b/e2e/output/components/ResponsiveImage/baseline/microsoftedge_18.png
new file mode 100644
index 0000000000..9cfb7bee45
Binary files /dev/null and b/e2e/output/components/ResponsiveImage/baseline/microsoftedge_18.png differ
diff --git a/e2e/output/components/ResponsiveImage/baseline/safari_12.png b/e2e/output/components/ResponsiveImage/baseline/safari_12.png
new file mode 100644
index 0000000000..6df0fbf0fe
Binary files /dev/null and b/e2e/output/components/ResponsiveImage/baseline/safari_12.png differ
diff --git a/e2e/visual/components/CartesianResponsiveImage.jsx b/e2e/visual/components/CartesianResponsiveImage.jsx
new file mode 100644
index 0000000000..51f41ea909
--- /dev/null
+++ b/e2e/visual/components/CartesianResponsiveImage.jsx
@@ -0,0 +1,64 @@
+/* eslint-disable react/no-children-prop */
+import React from 'react'
+import { renderToString } from 'react-dom/server'
+import { Cartesian } from '@compositor/kit'
+
+import ResponsiveImage from '../../../packages/ResponsiveImage'
+
+const viewports = [
+ { width: 320, height: 568 },
+ { width: 640, height: 360 },
+ { width: 768, height: 1024 },
+ { width: 1024, height: 543 },
+ { width: 1440, height: 880 },
+]
+
+const ResponsiveImageContainer = props => {
+ // "Responsive" component from "@compositor/kit" does not take in a title attribute for
+ // iframe, failing the accessibility scan. iframe used to test different window sizes
+ const html = innerHtml => `
+
+
+
+
+
+
+ Document
+
+
+ ${innerHtml}
+
+
+ `
+
+ const CartesianResponsiveImage = renderToString(
+
+ )
+
+ return (
+ <>
+ {viewports.map((viewport, i) => (
+
+ ))}
+ >
+ )
+}
+
+export default { name: 'ResponsiveImage', Component: ResponsiveImageContainer }
diff --git a/packages/ResponsiveImage/README.md b/packages/ResponsiveImage/README.md
new file mode 100644
index 0000000000..f9f295f682
--- /dev/null
+++ b/packages/ResponsiveImage/README.md
@@ -0,0 +1 @@
+# TDS Core: ResponsiveImage
diff --git a/packages/ResponsiveImage/ResponsiveImage.jsx b/packages/ResponsiveImage/ResponsiveImage.jsx
new file mode 100644
index 0000000000..4910f77973
--- /dev/null
+++ b/packages/ResponsiveImage/ResponsiveImage.jsx
@@ -0,0 +1,59 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { breakpoints } from '@tds/core-responsive'
+import { safeRest } from '@tds/util-helpers'
+
+/**
+ * Provide different image sources for different screen sizes.
+ *
+ * @version ./package.json
+ */
+const ResponsiveImage = ({ xsSrc, smSrc, mdSrc, lgSrc, xlSrc, fallbackSrc, alt, ...rest }) => (
+
+)
+
+ResponsiveImage.propTypes = {
+ /**
+ * The src attribute used for screen widths up to 575px
+ */
+ xsSrc: PropTypes.string.isRequired,
+ /**
+ * The src attribute used for screen widths greater than 576px
+ */
+ smSrc: PropTypes.string.isRequired,
+ /**
+ * The src attribute used for screen widths greater than 768px
+ */
+ mdSrc: PropTypes.string,
+ /**
+ * The src attribute used for screen widths greater than 992px
+ */
+ lgSrc: PropTypes.string,
+ /**
+ * The src attribute used for screen widths greater than 1200px
+ */
+ xlSrc: PropTypes.string,
+ /**
+ * The src attribute used for browsers that don't support responsive images (InternetExplorer)
+ */
+ fallbackSrc: PropTypes.string.isRequired,
+ /**
+ * The alt attribute for the HTML img element. Setting this attribute to an empty string (alt="") indicates that this image is not a key part of the content, and that non-visual browsers may omit it from rendering.
+ */
+ alt: PropTypes.string.isRequired,
+}
+
+ResponsiveImage.defaultProps = {
+ mdSrc: undefined,
+ lgSrc: undefined,
+ xlSrc: undefined,
+}
+
+export default ResponsiveImage
diff --git a/packages/ResponsiveImage/ResponsiveImage.md b/packages/ResponsiveImage/ResponsiveImage.md
new file mode 100644
index 0000000000..aeea79c072
--- /dev/null
+++ b/packages/ResponsiveImage/ResponsiveImage.md
@@ -0,0 +1,37 @@
+### Usage criteria
+
+The `ResponsiveImage` component allows variation in "art direction" when wanting to change the image displayed to
+suit different screen sizes. This can also be used to provide compressed files or alternate formats based on device to help reduce
+download size.
+
+Similarly to the Image component:
+
+- We recommend not to resize or crop images in the browser
+- Image optimization should be done BEFORE rendering
+- You can use the `contentful API` to request an image of a certain size
+
+Specific to ResponsiveImage:
+
+- The image will fit to 100% of its parent container
+- The image source will change based on `window width`, not parent container size
+- Uses the following breakpoints:
+ - xl: min-width 1200px
+ - lg: min-width 992px
+ - md: min-width 768px
+ - sm: min-width 576px
+ - xs: max-width 575px
+- Provide a source for the sm and xs breakpoints as a minimum
+- Internet Explorer is not supported and will use the fallback src provided
+
+Change the window width to see the images change.
+
+```jsx { "props": { "className": "docs_full-width-playground" }}
+
+```
diff --git a/packages/ResponsiveImage/__tests__/ResponsiveImage.spec.jsx b/packages/ResponsiveImage/__tests__/ResponsiveImage.spec.jsx
new file mode 100644
index 0000000000..66eff5034c
--- /dev/null
+++ b/packages/ResponsiveImage/__tests__/ResponsiveImage.spec.jsx
@@ -0,0 +1,41 @@
+import React from 'react'
+import { shallow, render } from 'enzyme'
+
+import ResponsiveImage from '../ResponsiveImage'
+
+describe('ResponsiveImage', () => {
+ const testProps = {
+ xlSrc: 'responsive-image/mountains_desktop.jpg',
+ lgSrc: 'responsive-image/mountains_desktop.jpg',
+ mdSrc: 'responsive-image/mountains_tablet.jpg',
+ smSrc: 'responsive-image/mountains_mobile.jpg',
+ xsSrc: 'responsive-image/mountains_mobile.jpg',
+ fallbackSrc: 'responsive-image/mountains_desktop.jpg',
+ alt: 'mountain background',
+ }
+
+ const doShallow = (newProps = {}) => shallow()
+
+ it('renders', () => {
+ const responsiveImage = render()
+
+ expect(responsiveImage).toMatchSnapshot()
+ })
+
+ it('passes additional attributes to the element', () => {
+ const responsiveImage = doShallow({ id: 'the-id', 'data-some-attr': 'some value' })
+
+ expect(responsiveImage).toHaveProp('id', 'the-id')
+ expect(responsiveImage).toHaveProp('data-some-attr', 'some value')
+ })
+
+ it('does not allow custom CSS', () => {
+ const responsiveImage = doShallow({
+ className: 'my-custom-class',
+ style: { color: 'hotpink' },
+ })
+
+ expect(responsiveImage).not.toHaveProp('className', 'my-custom-class')
+ expect(responsiveImage).not.toHaveProp('style')
+ })
+})
diff --git a/packages/ResponsiveImage/__tests__/__snapshots__/ResponsiveImage.spec.jsx.snap b/packages/ResponsiveImage/__tests__/__snapshots__/ResponsiveImage.spec.jsx.snap
new file mode 100644
index 0000000000..d47025b35f
--- /dev/null
+++ b/packages/ResponsiveImage/__tests__/__snapshots__/ResponsiveImage.spec.jsx.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ResponsiveImage renders 1`] = `
+
+`;
diff --git a/packages/ResponsiveImage/index.cjs.js b/packages/ResponsiveImage/index.cjs.js
new file mode 100644
index 0000000000..42aac959b3
--- /dev/null
+++ b/packages/ResponsiveImage/index.cjs.js
@@ -0,0 +1,3 @@
+const ResponsiveImage = require('./dist/index.cjs')
+
+module.exports = ResponsiveImage
diff --git a/packages/ResponsiveImage/index.es.js b/packages/ResponsiveImage/index.es.js
new file mode 100644
index 0000000000..b89e65e54e
--- /dev/null
+++ b/packages/ResponsiveImage/index.es.js
@@ -0,0 +1,3 @@
+import ResponsiveImage from './dist/index.es'
+
+export default ResponsiveImage
diff --git a/packages/ResponsiveImage/package.json b/packages/ResponsiveImage/package.json
new file mode 100644
index 0000000000..ccede9bd04
--- /dev/null
+++ b/packages/ResponsiveImage/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@tds/core-responsive-image",
+ "version": "1.0.0-0",
+ "description": "",
+ "main": "index.cjs.js",
+ "module": "index.es.js",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/telus/tds-core.git"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "author": "TELUS digital",
+ "engines": {
+ "node": ">=8"
+ },
+ "bugs": {
+ "url": "https://github.com/telus/tds-core/issues"
+ },
+ "homepage": "http://tds.telus.com",
+ "peerDependencies": {
+ "react": "^16.8.2",
+ "react-dom": "^16.8.2",
+ "styled-components": "^4.1.3 || ^5.1.0"
+ },
+ "dependencies": {
+ "@tds/core-responsive": "^1.3.4",
+ "@tds/util-helpers": "^1.4.0",
+ "prop-types": "^15.5.10"
+ }
+}
diff --git a/packages/ResponsiveImage/rollup.config.js b/packages/ResponsiveImage/rollup.config.js
new file mode 100644
index 0000000000..c849b91a5b
--- /dev/null
+++ b/packages/ResponsiveImage/rollup.config.js
@@ -0,0 +1,7 @@
+import configure from '../../config/rollup.config'
+import { dependencies } from './package.json'
+
+export default configure({
+ input: './ResponsiveImage.jsx',
+ dependencies,
+})