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

EMULSIF-304: Banner component. #152

Open
wants to merge 5 commits into
base: convert-components-to-sdc
Choose a base branch
from
Open
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
Binary file added assets/audio/video-placeholder.mp4
Binary file not shown.
Binary file added assets/images/example/banner-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions images/icons/pause.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions images/icons/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
115 changes: 115 additions & 0 deletions src/components/banner/_banner.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
@use '../../foundation/typography/typography' as *;

.banner {
min-height: 16rem;
position: relative;
}

.banner--has-image {
background-size: 100%;
background-position: center;
}

.banner__video-container {
width: 100%;
height: 100%;
max-width: 100%;
position: absolute;
}

.banner--has-image,
.banner__video-container {
&::before {
content: '';
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
opacity: 0.75;
display: block;
position: absolute;
pointer-events: none;
mix-blend-mode: multiply;
background-color: var(--color-primary-dark);
}
}

.banner__video {
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
}

.banner__video-controls {
z-index: 3;
position: absolute;
bottom: var(--spacing-xl);
right: var(--spacing-xl);
}

.banner__toggle {
cursor: pointer;
border-radius: 50%;
width: var(--spacing-xl);
height: var(--spacing-xl);
border: 3px solid var(--color-white);
background-color: var(--color-primary-lighter);

svg {
width: 100%;
height: 100%;
}
}

.banner__content {
z-index: 2;
width: 100%;
display: flex;
position: relative;
flex-direction: column;
color: var(--color-white);
padding: var(--spacing-2xl) var(--spacing-xl);

.heading {
@include h2;

margin: 0 0 calc(var(--spacing-lg) * 1.5);
}

p {
margin: 0 0 var(--spacing-lg);
font-size: var(--font-size-h5);

&:is(p:last-child) {
margin-bottom: 0;
}
}
}

.banner__action {
margin: var(--spacing-xl) 0 0;

.button,
.button:hover,
.button:focus {
color: var(--color-black);
font-size: var(--font-size-small);
border-color: var(--color-primary-lighter);
background-color: var(--color-primary-lighter);
}
}

.banner-list {
display: flex;
flex-direction: column;
row-gap: var(--spacing-xl);
}

/* Variants -- Content centered */
.banner__content--center {
align-items: center;
}
74 changes: 74 additions & 0 deletions src/components/banner/banner.component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
$schema: https://git.drupalcode.org/project/drupal/-/raw/10.1.x/core/modules/sdc/src/metadata.schema.json

name: Banner
group: Components
status: stable
props:
type: object
required:
- banner__title
- banner__content
- banner__button_text
- banner__button_url
properties:
banner__title:
type: string
title: Title
description: 'Specifies the title of the banner'
data: 'This is the banner title'
banner__content:
type: string
title: Content
description: 'Specifies the main content of the banner'
data: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
banner__button_text:
type: string
title: Button Text
description: 'Specifies the text displayed on the button'
data: 'Learn More'
banner__button_url:
type: string
title: Button URL
description: 'Specifies the URL the button will link to'
data: '#'
banner__background_image:
type: string
title: Background Image
description: 'Specifies the URL of the background image for the banner. Either this or banner__video should be provided.'
data: 'https://example.com/path/to/background-image.jpg'
banner__video:
type: string
title: Video URL
description: 'Specifies the URL of the video to be displayed. Either this or banner__background_image should be provided.'
data: ''
banner__alignment:
type: string
title: Alignment
description: 'Specifies the alignment of the content within the banner. Options include: left, center, right.'
enum:
- left
- center
data: 'center'
example:
- banner__title: 'THIS IS A BANNER HEADING'
banner__content: '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>'
banner__button_text: 'this is a link'
banner__button_url: '#'
banner__background_image: ''
- banner__title: 'THIS IS A BANNER HEADING'
banner__content: '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>'
banner__button_text: 'this is a link'
banner__button_url: '#'
banner__video: ''
- banner__title: 'THIS IS A BANNER HEADING'
banner__content: '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>'
banner__button_text: 'this is a link'
banner__button_url: '#'
banner__background_image: ''
banner__alignment: 'center'
- banner__title: 'THIS IS A BANNER HEADING'
banner__content: '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>'
banner__button_text: 'this is a link'
banner__button_url: '#'
banner__video: ''
banner__alignment: 'center'
109 changes: 109 additions & 0 deletions src/components/banner/banner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
Drupal.behaviors.banners = {
attach(context) {
const banners = context.querySelectorAll('.banner');

/**
* getBannerReferences
*
* @description Returns references to banner elements.
* @param {HTMLElement} banner Banner element.
* @return {Object} References to label, button, video, pause, and play elements.
*/
function getBannerReferences(banner) {
return {
label: banner.querySelector('.banner__toggle_label'),
button: banner.querySelector('.banner__toggle'),
video: banner.querySelector('.banner__video'),
pause: banner.querySelector('.banner__pause'),
play: banner.querySelector('.banner__play'),
};
}

/**
* playVideo
*
* @description Starts video playback and updates UI to reflect the playing state.
* @param {Object} refs Object containing element references.
* @return {Promise} Resolves when the video starts playing or rejects with an error.
*/
function playVideo(refs) {
return new Promise((resolve, reject) => {
refs.video
.play()
.then(() => {
refs.pause.classList.remove('visually-hidden');
refs.play.classList.add('visually-hidden');
refs.label.textContent = 'Pause video';
resolve();
})
.catch((error) => {
reject(error);
});
});
}

/**
* pauseVideo
*
* @description Pauses video playback and updates UI to reflect the paused state.
* @param {Object} refs Object containing element references.
*/
function pauseVideo(refs) {
refs.play.classList.remove('visually-hidden');
refs.pause.classList.add('visually-hidden');
refs.label.textContent = 'Play video';
refs.video.pause();
}

/**
* toggleVideo
*
* @description Toggles video playback state and updates UI accordingly.
* @param {Object} refs Object containing element references.
*/
function toggleVideo(refs) {
if (refs.video.paused) {
playVideo(refs);
} else {
pauseVideo(refs);
}
}

/**
* setInitialState
*
* @description Sets the initial state for a video element and performs associated actions.
* @param {Object} refs Object containing element references.
* @throws {DOMException} - If an error occurs while attempting to play the video.
*/
async function setInitialState(refs) {
const reduceMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)',
);

// Check if the user disabled 'prefer reduced motion' on their system.
if (!reduceMotion.matches) {
// Check if the user's browser allows video autoplay.
try {
await playVideo(refs);
refs.actions[0].classList.add('hidden');
} catch (err) {
if (err.name === 'NotAllowedError') {
pauseVideo(refs);
}
}
} else {
pauseVideo(refs);
}
}

banners?.forEach((banner) => {
const refs = getBannerReferences(banner);

if (refs.button && refs.video) {
refs.button.addEventListener('click', () => toggleVideo(refs));
setInitialState(refs);
}
});
},
};
29 changes: 29 additions & 0 deletions src/components/banner/banner.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import bannerTwig from './banner.twig';
import { props } from './banner.component.yml';
import bannerVideo from '../../../assets/audio/video-placeholder.mp4';
import bannerImage from '../../../assets/images/example/banner-image.jpg';
import './banner';

export default {
title: 'Components/Banner',
decorators: [
(story) =>
`<div style="max-width: 890px; margin: 0 auto;">${story()}</div>`,
],
};

function getBannerData(data) {
const newData = Object.assign({}, data);
if (data && typeof data === 'object' && 'banner__video' in data) {
newData.banner__video = bannerVideo;
}
if (data && typeof data === 'object' && 'banner__background_image' in data) {
newData.banner__background_image = bannerImage;
}
return newData;
}

export const Banner = () =>
`<div class="banner-list">${props.example
.map((data) => bannerTwig(getBannerData(data)))
.join('')}</div>`;
Loading
Loading