-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #40 from BKWLD/contentful-visual
Implement Contentful Visual
- Loading branch information
Showing
22 changed files
with
657 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
# @react-visual/contentful [![react-visual](https://img.shields.io/endpoint?url=https://cloud.cypress.io/badge/simple/fn6c7w&style=flat&logo=cypress)](https://cloud.cypress.io/projects/fn6c7w/runs) | ||
|
||
Renders Contentful images and videos into a container. Features: | ||
|
||
- Automatically defines a loader functions for generating srcsets | ||
- Supports responsive image and video assets | ||
|
||
## Install | ||
|
||
```sh | ||
yarn add @react-visual/contentful | ||
``` | ||
|
||
## Usage | ||
|
||
### Asset fields | ||
|
||
```jsx | ||
import Visual from '@react-visual/contentful' | ||
|
||
export default function Example() { | ||
return ( | ||
<Visual | ||
image={ entry.image } | ||
video={ entry.video } | ||
sizes='100vw'/> | ||
) | ||
} | ||
``` | ||
|
||
Where `image` and `video` are asset fields defined by these GQL fragments: | ||
|
||
```gql | ||
fragment image on Asset { | ||
title | ||
description | ||
fileName | ||
width | ||
height | ||
url | ||
} | ||
|
||
fragment video on Asset { | ||
title | ||
description | ||
fileName | ||
url | ||
} | ||
``` | ||
|
||
### Visual entryType reference | ||
|
||
This is the expected pattern for rendering responsive images and videos. | ||
|
||
```jsx | ||
import Visual from '@react-visual/contentful' | ||
|
||
export default function Example() { | ||
return ( | ||
<Visual | ||
src={ entry.background } | ||
sizes='100vw'/> | ||
) | ||
} | ||
``` | ||
|
||
Where `background` is defined by this GQL fragment (this consumes the previous fragments): | ||
|
||
```gql | ||
fragment visual on Visual { | ||
image { ...image } | ||
portraitImage { ...image } | ||
video { ...video } | ||
portraitVideo { ...video } | ||
alt | ||
} | ||
``` | ||
|
||
For more examples, read [the Cypress component tests](./cypress/component). | ||
|
||
## Props | ||
|
||
### Sources | ||
|
||
| Prop | Type | Description | ||
| -- | -- | -- | ||
| `image` | `object` | A Contentful image Asset. | ||
| `video` | `object` | A Contentful video Asset. | ||
| `src` | `object` | An object with keys of responsive keys. See examples above. | ||
|
||
### Layout | ||
|
||
| Prop | Type | Description | ||
| -- | -- | -- | ||
| `expand` | `boolean` | Make the Visual fill it's container via CSS using absolute positioning. | ||
| `aspect` | `number` | Force the Visual to a specific aspect ratio. If empty, this will be set using width and height fields from Contentful queries. | ||
| `width` | `number`, `string` | A CSS dimension value or a px number. | ||
| `height` | `number`, `string` | A CSS dimension value or a px number. | ||
| `fit` | `string` | An [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) value that is applied to the assets. Defaults to `cover`. | ||
| `position` | `string` | An [`object-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) value. | ||
|
||
### Loading | ||
|
||
| Prop | Type | Description | ||
| -- | -- | -- | ||
| `priority` | `boolean` | Sets [`next/image`'s `priority`](https://nextjs.org/docs/pages/api-reference/components/image#priority) and videos to not lazy load. | ||
| `sizes` | `string` | Sets [`next/image`'s `sizes`](https://nextjs.org/docs/pages/api-reference/components/image#sizes) prop. | ||
| `imageLoader` | `Function` | This is passed through [to `next/image`'s `loader` prop](https://nextjs.org/docs/app/api-reference/components/image#loader). | ||
|
||
### Video | ||
|
||
| Prop | Type | Description | ||
| -- | -- | -- | ||
| `paused` | `boolean` | Disables autoplay of videos. This prop is reactive, unlike the `paused` property of the html `<video>` tag. You can set it to `true` to pause a playing video or set it to `false` to play a paused video. | ||
|
||
|
||
### Accessibility | ||
|
||
| Prop | Type | Description | ||
| -- | -- | -- | ||
| `alt` | `string` | Sets the alt attribute or aria-label value, depending on asset type. | ||
|
||
### Theming | ||
|
||
| Prop | Type | Description | ||
| -- | -- | -- | ||
| `className` | `string` | Add a custom CSS class. | ||
| `style` | `CSSProperties` | Add additional styles. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { defineConfig } from "cypress"; | ||
|
||
export default defineConfig({ | ||
viewportWidth: 500, | ||
viewportHeight: 500, | ||
component: { | ||
specPattern: "cypress/component/**/*.cy.{js,jsx,ts,tsx}", | ||
devServer: { | ||
framework: "next", | ||
bundler: "webpack", | ||
}, | ||
}, | ||
}); |
116 changes: 116 additions & 0 deletions
116
packages/contentful/cypress/component/ContentfulVisual.cy.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import ContentfulVisual from '../../src' | ||
import { | ||
imageAsset, | ||
portraitImageAsset, | ||
videoAsset, | ||
portraitVideoAsset | ||
} from '../fixtures/assets' | ||
import { visualEntry } from '../fixtures/entries' | ||
|
||
// Dimensions | ||
const VW = Cypress.config('viewportWidth'), | ||
VH = Cypress.config('viewportHeight'), | ||
landscapeAspect = imageAsset.width / imageAsset.height | ||
|
||
describe('no asset', () => { | ||
|
||
it('renders nothing', () => { | ||
cy.mount(<ContentfulVisual | ||
width={300} | ||
height={200} | ||
alt='' | ||
data-cy='next-visual' />) | ||
cy.get('[data-cy=next-visual]').should('not.exist') | ||
}) | ||
|
||
}) | ||
|
||
describe('contentful asset props', () => { | ||
|
||
it('renders image', () => { | ||
cy.mount(<ContentfulVisual image={ imageAsset } />) | ||
cy.get('img') | ||
.hasDimensions(VW, VW / landscapeAspect) | ||
.invoke('attr', 'alt').should('eq', imageAsset.title) | ||
cy.get('img').its('[0].currentSrc').should('contain', 'w=640') // srcset | ||
}) | ||
|
||
it('can override inferred props', () => { | ||
cy.mount(<ContentfulVisual | ||
image={ imageAsset } | ||
aspect={ 1 } | ||
alt='Override' />) | ||
cy.get('img') | ||
.hasDimensions(VW, VW) | ||
.invoke('attr', 'alt').should('eq', 'Override') | ||
}) | ||
|
||
it('renders video', () => { | ||
cy.mount(<ContentfulVisual video={ videoAsset } aspect={ 16/9 } />) | ||
cy.get('video') | ||
.hasDimensions(VW, VW / (16/9) ) | ||
.invoke('attr', 'aria-label').should('eq', videoAsset.description) | ||
}) | ||
|
||
}) | ||
|
||
describe('contentful visual entry props', () => { | ||
|
||
it('renders responsive images', () => { | ||
cy.mount(<ContentfulVisual src={{ | ||
...visualEntry, | ||
video: null, | ||
portraitVideo: null, | ||
}} />) | ||
|
||
// Portrait asset | ||
cy.get('img').hasDimensions(VW, VW) | ||
cy.get('img').its('[0].currentSrc') | ||
.should('contain', 'w=640') | ||
.should('contain', portraitImageAsset.url) | ||
|
||
// Landscape asset | ||
cy.viewport(500, 400) | ||
cy.get('img').hasDimensions(VW, VW / landscapeAspect) | ||
cy.get('img').its('[0].currentSrc') | ||
.should('contain', 'w=640') | ||
.should('contain', imageAsset.url) | ||
}) | ||
|
||
it('renders responsive videos', () => { | ||
cy.mount(<ContentfulVisual expand src={{ | ||
...visualEntry, | ||
image: null, | ||
portraitImage: null, | ||
}} />) | ||
|
||
// Portrait asset | ||
cy.get('video').its('[0].currentSrc') | ||
.should('contain', portraitVideoAsset.url) | ||
|
||
// Landscape asset | ||
cy.viewport(500, 400) | ||
cy.get('video').its('[0].currentSrc') | ||
.should('contain', videoAsset.url) | ||
}) | ||
|
||
it('renders full visual entry', () => { | ||
cy.mount(<ContentfulVisual src={visualEntry} />) | ||
|
||
// Portrait asset | ||
cy.get('img').hasDimensions(VW, VW) | ||
cy.get('img').its('[0].currentSrc') | ||
.should('contain', portraitImageAsset.url) | ||
cy.get('video').its('[0].currentSrc') | ||
.should('contain', portraitVideoAsset.url) | ||
|
||
// Landscape asset | ||
cy.viewport(500, 400) | ||
cy.get('img').hasDimensions(VW, VW / landscapeAspect) | ||
cy.get('img').its('[0].currentSrc') | ||
.should('contain', imageAsset.url) | ||
cy.get('video').its('[0].currentSrc') | ||
.should('contain', videoAsset.url) | ||
}) | ||
|
||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
export const imageAsset = { | ||
title: "Landscape gradient", | ||
description: "", | ||
fileName: "landscape.png", | ||
width: 1280, | ||
height: 720, | ||
url: "https://images.ctfassets.net/x9fe3hhqauxm/6WN8Zz47zmXelBLDenfteO/7e87ed7484738fd97fd86ac67f6feed5/tumblr_n8xc9t24W31tf8vylo1_1280.png" | ||
} | ||
|
||
export const portraitImageAsset = { | ||
title: "Square gradient", | ||
description: null, | ||
fileName: "square.png", | ||
width: 1280, | ||
height: 1280, | ||
url: "https://images.ctfassets.net/x9fe3hhqauxm/1lsKAmirOYNDVu0bfCfQ2H/32da927183924f22c535a74a10512817/tumblr_n8z97bAEMU1tf8vylo1_1280.png" | ||
} | ||
|
||
export const videoAsset = { | ||
title: "Background Loop", | ||
description: "Background loop description", | ||
fileName: "Backround_Loop.mp4", | ||
url: "https://videos.ctfassets.net/x9fe3hhqauxm/3TMXSh7C5nR1lUOeUygMou/15eccf4eaddcf1289c5b2b9ca83c90c4/PANDORA_ANIMATION_5.mp4" | ||
} | ||
|
||
export const portraitVideoAsset = { | ||
title: "Portrait Loop", | ||
description: "", | ||
fileName: "Portait_Loop.mp4", | ||
url: "https://videos.ctfassets.net/x9fe3hhqauxm/2mveMy2NaxbwfIp6ABl8xc/641f890eb10a329766d52d4805d404b4/PANDORA_ANIMATION_4.mp4" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { | ||
imageAsset, | ||
portraitImageAsset, | ||
videoAsset, | ||
portraitVideoAsset | ||
} from './assets' | ||
|
||
export const visualEntry = { | ||
image: imageAsset, | ||
portraitImage: portraitImageAsset, | ||
video: videoAsset, | ||
portraitVideo: portraitVideoAsset, | ||
alt: 'Description', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/// <reference types="cypress" /> | ||
|
||
// Asset that el has dimensions | ||
Cypress.Commands.add('hasDimensions', | ||
{ prevSubject: true }, | ||
(subject, width, height) => { | ||
cy.wrap(subject).invoke('width').should('equal', width) | ||
cy.wrap(subject).invoke('height').should('equal', height) | ||
cy.wrap(subject) | ||
}) | ||
|
||
// Check that a video is playing | ||
// https://glebbahmutov.com/blog/test-video-play/ | ||
Cypress.Commands.add('isPlaying', | ||
{ prevSubject: true }, | ||
(subject) => { | ||
cy.wrap(subject).should('have.prop', 'paused', false) | ||
cy.wrap(subject) | ||
}) | ||
|
||
// Add Typescript support for custom commaands | ||
// https://docs.cypress.io/guides/tooling/typescript-support#Types-for-Custom-Commands | ||
export {}; | ||
declare global { | ||
namespace Cypress { | ||
interface Chainable { | ||
|
||
hasDimensions( | ||
width: number, | ||
height: number | ||
): Chainable<void> | ||
|
||
isPlaying(): Chainable<void> | ||
} | ||
} | ||
} | ||
|
||
|
Oops, something went wrong.