Skip to content

Commit

Permalink
feat(emulsif-304): add banner component
Browse files Browse the repository at this point in the history
  • Loading branch information
robherba committed Aug 26, 2024
1 parent d4b545e commit 8b2672f
Show file tree
Hide file tree
Showing 5 changed files with 407 additions and 0 deletions.
113 changes: 113 additions & 0 deletions src/components/banner/_banner.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
.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 heading-large;

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.
* @returns {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.
* @returns {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 '../../media/video-placeholder.mp4';
import bannerImage from '../../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

0 comments on commit 8b2672f

Please sign in to comment.