Created
March 25, 2020 18:13
-
-
Save DaveBitter/c7a4342a8a7058b75ffebf1829c99654 to your computer and use it in GitHub Desktop.
Example component for Mirabeau Boilerplate (https://github.com/mirabeau-nl/frontend-boilerplate)
This file contains hidden or 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
.carousel { | |
.has-js & { | |
max-width: 25em; | |
} | |
&__pages { | |
display: flex; | |
flex-wrap: wrap; | |
padding: 0; | |
list-style-type: none; | |
} | |
&__page { | |
&[aria-hidden='true'] { | |
display: none; | |
} | |
.has-js & { | |
width: 100%; | |
} | |
picture { | |
display: block; | |
img { | |
width: 100%; | |
} | |
} | |
} | |
&__actions { | |
display: none; | |
justify-content: space-between; | |
.has-js & { | |
display: flex; | |
} | |
} | |
&__action { | |
min-width: 7.5rem; | |
border: 1px solid $black; | |
background-color: $white; | |
} | |
} |
This file contains hidden or 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
/** | |
* @module Carousel | |
*/ | |
class Carousel { | |
/** | |
* @classdesc - This module handles all the logic required for the Carousel component | |
* @param {HTMLElement} element - The HTMLElement this module is constructed upon | |
* @param {Object} options - The data attributes on the HTMLElement this module is constructed upon | |
*/ | |
constructor(element, options) { | |
this._element = element | |
this._options = { ...Carousel._defaultOptions, ...options } | |
this._goToPrevPage = this._goToPrevPage.bind(this) | |
this._goToNextPage = this._goToNextPage.bind(this) | |
this._init() | |
} | |
static _defaultOptions = { | |
attributes: { | |
DATA_PAGE: 'data-page', | |
ARIA_HIDDEN: 'aria-hidden', | |
DATA_BUTTON_PREV: 'data-button-prev', | |
DATA_BUTTON_NEXT: 'data-button-next', | |
DATA_DIRECTION: 'data-direction' | |
} | |
} | |
_state = { | |
currIndex: 0 | |
} | |
/** | |
* Remove all event listeners that were needed | |
*/ | |
unload() { | |
this._buttonPrev.removeEventListener('click', this._goToPrevPage) | |
this._buttonNext.removeEventListener('click', this._goToNextPage) | |
} | |
/** | |
* @param {Number} index - Index of page to show | |
*/ | |
_showPage(index) { | |
this._pages[index].setAttribute( | |
this._options.attributes.ARIA_HIDDEN, | |
'false' | |
) | |
} | |
/** | |
* @param {Number} index - Index of page to hide | |
*/ | |
_hidePage(index) { | |
this._pages[index].setAttribute( | |
this._options.attributes.ARIA_HIDDEN, | |
'true' | |
) | |
} | |
/** | |
* @param {Number} index - index of page | |
*/ | |
_goToPage(index) { | |
this._hidePage(this._state.currIndex) | |
this._state.currIndex = index | |
this._showPage(this._state.currIndex) | |
} | |
/** | |
* @param {Event} e - Handle click event emitted by the prev button | |
*/ | |
_goToPrevPage(e) { | |
let nextIndex = this._state.currIndex - 1 | |
// Set nextIndex to last page if required page is negative | |
if (nextIndex < 0) { | |
nextIndex = this._pages.length - 1 | |
} | |
this._goToPage(nextIndex) | |
e.preventDefault() | |
} | |
/** | |
* @param {Event} e - Handle click event emitted by the next button | |
*/ | |
_goToNextPage(e) { | |
let nextIndex = this._state.currIndex + 1 | |
// Set nextIndex to first page if exceeding amount of pages | |
if (nextIndex > this._pages.length - 1) { | |
nextIndex = 0 | |
} | |
this._goToPage(nextIndex) | |
e.preventDefault() | |
} | |
/** | |
* Add all event listeners that are needed | |
*/ | |
_addEventListeners() { | |
this._buttonPrev.addEventListener('click', this._goToPrevPage) | |
this._buttonNext.addEventListener('click', this._goToNextPage) | |
} | |
/** | |
* Set the defaults that are needed | |
*/ | |
_setDefaults() { | |
this._pages.forEach((page, index) => { | |
// Bail out if first page | |
if (index === 0) { | |
return | |
} | |
this._hidePage(index) | |
}) | |
} | |
/** | |
* Cache all selectors that are needed | |
*/ | |
_cacheSelectors() { | |
this._pages = [ | |
...this._element.querySelectorAll( | |
`[${this._options.attributes.DATA_PAGE}]` | |
) | |
] | |
this._buttonPrev = this._element.querySelector( | |
`[${this._options.attributes.DATA_BUTTON_PREV}]` | |
) | |
this._buttonNext = this._element.querySelector( | |
`[${this._options.attributes.DATA_BUTTON_NEXT}]` | |
) | |
} | |
/** | |
* Inititialize module | |
*/ | |
_init() { | |
this._cacheSelectors() | |
this._setDefaults() | |
this._addEventListeners() | |
} | |
} | |
export default Carousel |
This file contains hidden or 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
{ | |
"pages": [ | |
{ | |
"heading": "Page one", | |
"copy": "This is the first carousel page", | |
"image": { | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"sources": [ | |
{ | |
"src": "/mock/250x150.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px)" | |
}, | |
{ | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px) and (max-width: 1000px)" | |
}, | |
{ | |
"src": "/mock/1000x600.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(min-width: 1000px)" | |
} | |
] | |
} | |
}, | |
{ | |
"heading": "Page two", | |
"copy": "This is the second carousel page", | |
"image": { | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"sources": [ | |
{ | |
"src": "/mock/250x150.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px)" | |
}, | |
{ | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px) and (max-width: 1000px)" | |
}, | |
{ | |
"src": "/mock/1000x600.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(min-width: 1000px)" | |
} | |
] | |
} | |
}, | |
{ | |
"heading": "Page three", | |
"copy": "This is the third carousel page", | |
"image": { | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"sources": [ | |
{ | |
"src": "/mock/250x150.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px)" | |
}, | |
{ | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px) and (max-width: 1000px)" | |
}, | |
{ | |
"src": "/mock/1000x600.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(min-width: 1000px)" | |
} | |
] | |
} | |
}, | |
{ | |
"heading": "Page four", | |
"copy": "This is the fourth carousel page", | |
"image": { | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"sources": [ | |
{ | |
"src": "/mock/250x150.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px)" | |
}, | |
{ | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px) and (max-width: 1000px)" | |
}, | |
{ | |
"src": "/mock/1000x600.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(min-width: 1000px)" | |
} | |
] | |
} | |
}, | |
{ | |
"heading": "Page five", | |
"copy": "This is the fifth carousel page", | |
"image": { | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"sources": [ | |
{ | |
"src": "/mock/250x150.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px)" | |
}, | |
{ | |
"src": "/mock/500x300.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(max-width: 500px) and (max-width: 1000px)" | |
}, | |
{ | |
"src": "/mock/1000x600.png", | |
"alt": "This is a placeholder image for demo purposes", | |
"media": "(min-width: 1000px)" | |
} | |
] | |
} | |
} | |
], | |
"actions": { | |
"previous": "previous", | |
"next": "next" | |
} | |
} |
This file contains hidden or 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
<div class="carousel" data-module="carousel/Carousel"> | |
<ul class="carousel__pages"> | |
{% for page in pages %} | |
<li class="carousel__page" data-page> | |
<h3>{{ page.heading }}</h3> | |
<p>{{ page.copy }}</p> | |
<picture> | |
{% for source in page.image.sources %} | |
<source srcset="{{ source.src }}" media="{{ source.media }}" alt="{{ source.alt }}" /> | |
{% endfor %} | |
<img src="{{ page.image.src }}" alt="{{ page.image.alt }}" /> | |
</picture> | |
</li> | |
{% endfor %} | |
</ul> | |
<div class="carousel__actions"> | |
<button class="carousel__action" data-button-prev>{{ actions.previous }}</button> | |
<button class="carousel__action" data-button-next>{{ actions.next }}</button> | |
</div> | |
</div> |
This file contains hidden or 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
import assert from 'assert' | |
import Carousel from './Carousel' | |
// Mocks | |
const mockEvent = { preventDefault: () => {} } | |
const mockFunc = () => {} | |
// Setup | |
const amountOfPages = 10 | |
const context = { | |
_state: { | |
currIndex: 0 | |
}, | |
_pages: [...Array(amountOfPages).keys()], | |
_showPage: mockFunc, | |
_hidePage: mockFunc, | |
_goToPage: index => Carousel.prototype._goToPage.apply(context, [index]) | |
} | |
describe('Carousel', () => { | |
describe('_goToPage', () => { | |
it('should update the current active index in state to the passed index', () => { | |
// Act | |
const newActiveIndex = 1 | |
Carousel.prototype._goToPage.apply(context, [newActiveIndex]) | |
// Assert | |
assert.strictEqual(context._state.currIndex, newActiveIndex) | |
}) | |
}) | |
describe('_goToNextPage', () => { | |
it('should increase the current active index in state by 1', () => { | |
// Act | |
const startIndex = 0 | |
Carousel.prototype._goToNextPage.apply( | |
{ ...context, _state: { currIndex: startIndex } }, | |
[mockEvent] | |
) | |
// Assert | |
assert.strictEqual(context._state.currIndex, startIndex + 1) | |
}) | |
it('should reset the current active index to 0 if the passed index is bigger then length of pages', () => { | |
// Act | |
const startIndex = amountOfPages | |
Carousel.prototype._goToNextPage.apply( | |
{ ...context, _state: { currIndex: startIndex } }, | |
[mockEvent] | |
) | |
// Assert | |
assert.strictEqual(context._state.currIndex, 0) | |
}) | |
}) | |
describe('_goToPrevPage', () => { | |
it('should decrease the current active index in state by 1', () => { | |
// Act | |
const startIndex = 2 | |
Carousel.prototype._goToPrevPage.apply( | |
{ ...context, _state: { currIndex: startIndex } }, | |
[{ preventDefault: () => {} }] | |
) | |
// Assert | |
assert.strictEqual(context._state.currIndex, startIndex - 1) | |
}) | |
it('should reset the current active index to the last page if the passed index is lower then 0', () => { | |
// Act | |
const startIndex = amountOfPages | |
Carousel.prototype._goToNextPage.apply( | |
{ ...context, _state: { currIndex: startIndex } }, | |
[mockEvent] | |
) | |
// Assert | |
assert.strictEqual(context._state.currIndex, 0) | |
}) | |
}) | |
}) |
This file contains hidden or 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
title: Carousel | |
description: Carousel component for demonstrational purposes |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment