Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/responsive image #1528

Merged
merged 11 commits into from
Mar 2, 2021
2 changes: 2 additions & 0 deletions config/styleguide.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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'),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/responsive-image/mountains_mobile.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 64 additions & 0 deletions e2e/visual/components/CartesianResponsiveImage.jsx
Original file line number Diff line number Diff line change
@@ -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 => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Document</title>
</head>
<body>
${innerHtml}
</body>
</html>
`

const CartesianResponsiveImage = renderToString(
<Cartesian
{...props}
component={ResponsiveImage}
lgSrc="https://tds.telus.com/components/responsive-image/mountains_desktop.jpg"
mdSrc="https://tds.telus.com/components/responsive-image/mountains_tablet.jpg"
smSrc="https://tds.telus.com/components/responsive-image/mountains_mobile.jpg"
xsSrc="https://tds.telus.com/components/responsive-image/mountains_mobile.jpg"
fallbackSrc="https://tds.telus.com/components/responsive-image/mountains_desktop.jpg"
alt="mountains background"
/>
)

return (
<>
{viewports.map((viewport, i) => (
<iframe
key={JSON.stringify(viewport[i])}
title={`test-width: ${viewport.width}`}
style={{
width: viewport.width,
height: viewport.height,
}}
srcDoc={html(CartesianResponsiveImage)}
/>
))}
</>
)
}

export default { name: 'ResponsiveImage', Component: ResponsiveImageContainer }
1 change: 1 addition & 0 deletions packages/ResponsiveImage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TDS Core: ResponsiveImage
59 changes: 59 additions & 0 deletions packages/ResponsiveImage/ResponsiveImage.jsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<picture {...safeRest(rest)}>
<source srcSet={xlSrc} media={`(min-width: ${breakpoints.xl}px)`} />
<source srcSet={lgSrc} media={`(min-width: ${breakpoints.lg}px)`} />
<source srcSet={mdSrc} media={`(min-width: ${breakpoints.md}px)`} />
<source srcSet={smSrc} media={`(min-width: ${breakpoints.sm}px)`} />
<source srcSet={xsSrc} media={`(max-width: ${breakpoints.sm - 1}px)`} />
<img src={fallbackSrc} alt={alt} style={{ width: '100%' }} />
</picture>
)

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
37 changes: 37 additions & 0 deletions packages/ResponsiveImage/ResponsiveImage.md
Original file line number Diff line number Diff line change
@@ -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" }}
<ResponsiveImage
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"
/>
```
41 changes: 41 additions & 0 deletions packages/ResponsiveImage/__tests__/ResponsiveImage.spec.jsx
Original file line number Diff line number Diff line change
@@ -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(<ResponsiveImage {...testProps} {...newProps} />)

it('renders', () => {
const responsiveImage = render(<ResponsiveImage {...testProps} />)

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')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ResponsiveImage renders 1`] = `
<picture>
<source
media="(min-width: 1200px)"
srcset="responsive-image/mountains_desktop.jpg"
/>
<source
media="(min-width: 992px)"
srcset="responsive-image/mountains_desktop.jpg"
/>
<source
media="(min-width: 768px)"
srcset="responsive-image/mountains_tablet.jpg"
/>
<source
media="(min-width: 576px)"
srcset="responsive-image/mountains_mobile.jpg"
/>
<source
media="(max-width: 575px)"
srcset="responsive-image/mountains_mobile.jpg"
/>
<img
alt="mountain background"
src="responsive-image/mountains_desktop.jpg"
style="width:100%"
/>
</picture>
`;
3 changes: 3 additions & 0 deletions packages/ResponsiveImage/index.cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const ResponsiveImage = require('./dist/index.cjs')

module.exports = ResponsiveImage
3 changes: 3 additions & 0 deletions packages/ResponsiveImage/index.es.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ResponsiveImage from './dist/index.es'

export default ResponsiveImage
33 changes: 33 additions & 0 deletions packages/ResponsiveImage/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
7 changes: 7 additions & 0 deletions packages/ResponsiveImage/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import configure from '../../config/rollup.config'
import { dependencies } from './package.json'

export default configure({
input: './ResponsiveImage.jsx',
dependencies,
})