Each variation will be presented in the following sections.
The component represents a simple accordion with transitions
and max-height
.
Accordions are elements used to expand and collapse content that is broken into logical sections, much like tabs.
The accordion is based on the blueprint of Veams-Components and is a wrap-with component to support flexible content with predefined surrounded markup.
The accordion is jQuery-free (we use Veams-Query) and contains some accessiblity functionality.
Veams >= v5.0.0
- Veams Framework.veams install vc accordion
bower install veams-component-accordion --save
c-accordion.hbs
String
} [default] - Context class of component.String
} - Modifier classes for component.Object
} - JavaScript options which gets stringified.c-accordion__item.hbs
Boolean
} - Hide wrapper div .accordion__item
.String
} - Id of the accordion item.String
} - Button text for accordion item.The module gives you the possibility to override default options:
String
} [‘is-active’] - Define the active class for active elements.String
} [’[data-js-item=‘accordion-btn’]’] - Define the element for accordion buttons.String
} [’[data-js-item=“accordion-content”]’] - Define the element for accordion content items.String
} [‘is-calculating’] - Define the calculating class for the initial calculation cycle.String
} [‘click’] - Define a click handler for the buttons.String
} [‘is-closed’] - Define the closing class for accordion content items.String
} [‘data-js-height’] - Define the attribute in which the calculated height is saved.Boolean
} [false] - If set to true, all panels stays open on render.Boolean
} [false] - If set to true, panel can be opened by url hash referencing the id of the panel.Boolean
} [‘is-open’] - Define the opening class for accordion content items.Number
} [null] - Index of panel to be opened on init (zero based).Array
} [ [‘desktop’, ‘tablet-large’, ‘tablet-small’] ] - Viewports on which the openIndex panel is opened on init.Boolean
} [false] - If set to true, only one panel can be opened at the same time.Boolean
} [false] - If set to true, the accordion behaves like a tab module (click on active button will not close corresponding panel).String
} [‘is-unresolved’] - Define the unresolved class for the whole accordion which will be deleted after initialize()
and render()
is finished.There are multiple global variables which you can change:
300ms !default
] - Speed of toggling.ease !default
] - Transition method of toggle effect.#666 !default
] - + icon color.30px !default
] - + icon width.2px !default
] - + icon height.[$accordion-icon-color !default
] - Accordion button color.rgba(255, 255, 255, 0.5) !default
] - Background color of the accordion button.1rem !default
] - Default padding which will be used in the accordion.{
"variations": {
"simple": {
"docs": {
"variationName": "Simple Accordion"
},
"settings": {
"accContextClass": "default",
"accClasses": false
},
"content": {
"items": [
{
"itemId": "test-1",
"itemBtnText": "Item 1"
},
{
"itemId": "test-2",
"itemBtnText": "Item 2"
}
]
}
},
"singleOpen": {
"docs": {
"variationName": "Single Open Accordion with Default Openings"
},
"settings": {
"accContextClass": "default",
"accClasses": "is-some-modifier",
"accJsOptions": {
"openIndex": 0,
"singleOpen": true
}
},
"content": {
"items": [
{
"itemId": "test-2-1",
"itemBtnText": "Item 1"
},
{
"itemId": "test-2-2",
"itemBtnText": "Item 2"
}
]
}
}
}
}
{{! WrapWith START: Accordion }}
{{#wrapWith "c-accordion" settings=this.settings}}
{{! WrapWith START: Item }}
{{#each content.items}}
{{#wrapWith "c-accordion__item" accItemId=this.itemId accButton=itemBtnText}}
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aliquid assumenda, ducimus facilis inventore iste labore laborum libero nam necessitatibus neque nulla numquam perspiciatis rem, repudiandae sed soluta veniam vero.
{{/wrapWith}}
{{/each}}
{{! WrapWith END: Item }}
{{/wrapWith}}
{{! WrapWith END: Accordion }}
<div class="c-accordion{{#if options.settings.accContextClass}}--{{options.settings.accContextClass}}{{else}}--default{{/if}}{{#if options.settings.accClasses}} {{options.settings.accClasses}}{{/if}}"
data-css="c-accordion"
data-js-module="accordion"{{#if options.settings.accJsOptions}}
data-js-options='{{stringify options.settings.accJsOptions}}'{{/if}} role="tablist">
{{{yield}}}
</div>
{{#unless options.settings.accNoWrapper}}
<div class="accordion__item">
{{/unless}}
<div class="accordion__header">
<a href="#{{options.accItemId}}"
class="accordion__btn"
data-js-item="accordion-btn" role="tab"
aria-controls="{{options.accItemId}}">
<span class="accordion__btn-inner">{{options.accButton}}</span>
</a>
</div>
<div class="accordion__content"
id="{{options.accItemId}}"
data-js-item="accordion-content" role="tabpanel"
aria-labelledby="{{options.accItemId}}">
<div class="accordion__content-inner">
{{{yield}}}
</div>
</div>
{{#unless options.settings.accNoWrapper}}
</div>
{{/unless}}
/* ===================================================
Accordion
=================================================== */
/* ---------------------------------------------------
Global Variables
--------------------------------------------------- */
$accordion-toggle-duration: 300ms !default;
$accordion-transition-method: ease !default;
$accordion-icon-color: #666 !default;
$accordion-icon-width: 30px !default;
$accordion-icon-height: 2px !default;
$accordion-btn-color: $accordion-icon-color !default;
$accordion-btn-bg-color: rgba(255, 255, 255, 0.5) !default;
$accordion-padding: 1rem !default;
/* ---------------------------------------------------
Global Styles
--------------------------------------------------- */
[data-css="c-accordion"] {
position: relative;
/*
Unresolved
----------------------- */
&.is-unresolved {
}
/*
Wrapper Item
----------------------- */
.accordion__item {
}
/*
Header/ Toggle Button
----------------------- */
.accordion__header {
}
.accordion__btn {
}
.accordion__icon {
}
/*
Content/ Toggle Content
----------------------- */
.accordion__content {
}
.accordion__content-inner {
}
/*
Modifiers
----------------------- */
.is-closed {
height: 0;
transition: height $accordion-transition-method $accordion-toggle-duration;
overflow: hidden;
}
.is-open {
transition: height $accordion-transition-method $accordion-toggle-duration;
overflow: hidden;
}
// !IMPORTANT for calculation
.is-calculating {
position: absolute !important;
visibility: hidden !important;
display: block !important;
height: auto !important;
}
}
/* ---------------------------------------------------
Context: Default
--------------------------------------------------- */
.c-accordion--default {
/*
Header/ Toggle Button
----------------------- */
.accordion__btn {
color: $accordion-btn-color;
display: block;
font-size: $accordion-padding * 2;
padding: ($accordion-padding * 2.4) $accordion-padding ($accordion-padding * 2.2) $accordion-padding;
position: relative;
text-decoration: none;
text-transform: uppercase;
background-color: $accordion-btn-bg-color;
margin-bottom: 2px;
&::before,
&::after {
@extend %pseudo;
transition: bottom $accordion-toggle-duration linear $accordion-toggle-duration, transform $accordion-toggle-duration linear;
background-color: $accordion-icon-color;
height: $accordion-icon-height;
right: $accordion-padding;
top: 50%;
width: $accordion-icon-width;
}
&::after {
transform: rotate(90deg);
}
&.is-active {
&::after {
transform: rotate(0deg);
}
}
}
/*
Icon
----------------------- */
.accordion__icon {
position: relative;
}
/*
Content
----------------------- */
.accordion__content-inner {
padding: $accordion-padding 0;
}
}
/**
* Represents a simple accordion with transitions and max-height.
*
* @module Accordion
* @version v3.0.2
*
* @author Sebastian Fitzner
* @author Andy Gutsche
*/
/**
* Requirements
*/
import { Veams } from 'app';
import VeamsComponent from 'veams/src/js/common/component';
const $ = Veams.$;
const Helpers = Veams.helpers;
/**
* Class Accordion
*/
class Accordion extends VeamsComponent {
constructor(obj) {
let options = {
activeClass: 'is-active',
accordionBtn: '[data-js-item="accordion-btn"]',
accordionContent: '[data-js-item="accordion-content"]',
calculatingClass: 'is-calculating',
clickHandler: 'click',
closeClass: 'is-closed',
dataMaxAttr: 'data-js-height',
openAllOnInit: false,
openByHash: false,
openClass: 'is-open',
openIndex: null,
openOnViewports: [
'tablet-small',
'tablet-large',
'desktop'
], // array: viewport names - eg.: ['mobile', 'tablet', 'desktop-small', 'desktop']
removeStyles: false, // TODO
singleOpen: false,
tabMode: false,
unresolvedClass: 'is-unresolved'
};
super(obj, options);
}
/** =================================================
* GETTER & SETTER
* ================================================ */
/**
* Get module information
*/
static get info() {
return {
name: 'Accordion',
version: '3.0.2',
vc: true,
mod: false // set to true if source was modified in project
};
}
set $accordionContents(items) {
this._$accordionContents = items;
}
get $accordionContents() {
return this._$accordionContents;
}
set $accordionBtns(items) {
this._$accordionBtns = items;
}
get $accordionBtns() {
return this._$accordionBtns;
}
set $target(item) {
this._$target = item;
}
get $target() {
return this._$target;
}
set $btn(item) {
this._$btn = item;
}
get $btn() {
return this._$btn;
}
/** =================================================
* EVENTS
* ================================================ */
get events() {
return {
'{{this.options.clickHandler}} {{this.options.accordionBtn}}': 'handleClick'
}
}
get subscribe() {
return {
'{{Veams.EVENTS.resize}}': 'render',
'{{Veams.EVENTS.accordion.closeAll}}': 'closeAll',
'{{Veams.EVENTS.accordion.openAll}}': 'openAll'
}
}
/** =================================================
* STANDARD METHODS
* ================================================= */
/**
* Init method to save all necessary references.
*/
initialize() {
this.$accordionContents = $(this.options.accordionContent, this.$el);
this.$accordionBtns = $(this.options.accordionBtn, this.$el);
this.$target = null;
this.$btn = null;
this.openIndex = this.options.openIndex;
if (this.options.openByHash) {
let idx = this.getIndexByHash();
this.openIndex = typeof idx === 'number' ? idx : this.options.openIndex;
}
else if (this.options.tabMode && !this.options.openIndex) {
this.openIndex = 0;
}
}
/**
* Bind all events
*/
bindEvents() {
let fnOnHashChange = this.onHashChange.bind(this);
// Global events
if (this.options.openByHash) {
$(window).on(Veams.EVENTS.hashchange, fnOnHashChange);
}
}
render() {
if (!Veams.currentMedia) {
console.warn('Accordion: Veams.currentMedia is necessary to support the slider module!');
return;
}
this.removeStyles();
this.saveHeights(this.$accordionContents);
this.closeAll();
if (this.options.openAllOnInit) {
this.openAll();
}
// Open on index if set in options
if (typeof this.openIndex === 'number') {
if (this.options.tabMode || this.options.openOnViewports.indexOf(Veams.currentMedia) !== -1) {
this.activateBtn(this.$accordionBtns.eq(this.openIndex));
this.slideDown(this.$accordionContents.eq(this.openIndex));
}
}
if (this.$el.hasClass(this.options.unresolvedClass)) {
this.$el.removeClass(this.options.unresolvedClass);
}
}
/** =================================================
* CUSTOM ACCORDION METHODS
* ================================================= */
/**
* Get index of accordion content referenced by hash
*
* @return {number|boolean} - index of element or false if no match
*/
getIndexByHash() {
let hash = document.location.hash.split('#');
let retVal = false;
let i = 0;
if (hash < 2) {
return false;
}
for (i; i < this.$accordionContents.length; i++) {
if (this.$accordionContents[i].id === hash[1]) {
retVal = i;
break;
}
}
return retVal;
}
/**
* Open accordion content referenced by hash
*
* @param {object} e - event object
*/
onHashChange(e) {
let idx = this.getIndexByHash();
if (typeof idx === 'number') {
if (this.options.singleOpen) {
this.closeAll();
}
this.activateBtn(this.$accordionBtns.eq(idx));
this.slideDown(this.$accordionContents.eq(idx));
}
}
/**
* Save heights of all accordion contents.
*
* @param {Array} items - array of items
*/
saveHeights(items) {
Helpers.forEach(items, (idx, item) => {
this.saveHeight(item);
});
}
/**
* Save the height of the node item.
*
* @param {Object} item - item to calculate the height
*/
saveHeight(item) {
let $el = $(item);
// the el is hidden so:
// making the el block so we can measure its height but still be hidden
$el.addClass(this.options.calculatingClass);
let wantedHeight = $el.outerHeight();
// reverting to the original values
$el.removeClass(this.options.calculatingClass);
// save height in data attribute
$el.attr(this.options.dataMaxAttr, wantedHeight);
}
/**
* Handle the click,
* get the id of the clicked button and
* execute the toggleContent method.
*
* @param {Object} e - event object
* @param {object} currentTarget - Target to which listener was attached.
*/
handleClick(e, currentTarget) {
this.$btn = currentTarget ? $(currentTarget) : $(e.currentTarget);
let targetId = this.$btn.attr('href');
e.preventDefault();
if (this.options.tabMode && this.$btn.hasClass(this.options.activeClass)) {
return;
}
this.toggleContent(targetId);
}
/**
* Toggle the accordion content by using the id of the accordion button.
*
* @param {String} id - id of the target
*
* @public
*/
toggleContent(id) {
this.$target = this.$el.find(id);
if (this.$target.hasClass(this.options.openClass)) {
this.slideUp(this.$target);
this.deactivateBtn(this.$btn);
} else {
if (this.options.singleOpen || this.options.tabMode) {
this.closeAll();
}
this.activateBtn(this.$btn);
this.slideDown(this.$target);
}
}
/**
* Mimics the slideUp functionality of jQuery by using height and transition.
*
* @param {Object} $item - jQuery object of item
*/
slideUp($item) {
$item
.css('height', 0)
.removeAttr('style')
.attr('aria-expanded', 'false')
.removeClass(this.options.openClass)
.addClass(this.options.closeClass);
}
/**
* Mimics the slideDown functionality of jQuery by using height and transition.
*
* @param {Object} $item - jQuery object of item
*/
slideDown($item) {
$item
.css('height', $item.attr('data-js-height') + 'px')
.attr('aria-expanded', 'true')
.removeClass(this.options.closeClass)
.addClass(this.options.openClass);
}
/**
* Adds active class to the clicked button.
*
* @param {Object} $item - jQuery object of button
*/
activateBtn($item) {
$item.addClass(this.options.activeClass);
}
/**
* Removes active class from the button.
*
* @param {Object} $item - jQuery object of button
*/
deactivateBtn($item) {
$item.removeClass(this.options.activeClass);
}
/**
* Remove all styles of the accordion content elements
*/
removeStyles() {
this.$accordionContents.removeAttr('style');
}
/**
* Close all accordion contents and active buttons
*
* @public
*/
closeAll() {
Helpers.forEach(this.$accordionContents, (idx, item) => {
this.slideUp($(item));
});
Helpers.forEach(this.$accordionBtns, (idx, item) => {
this.deactivateBtn($(item));
});
}
/**
* Close all accordion contents and active buttons
*
* @public
*/
openAll() {
Helpers.forEach(this.$accordionContents, (idx, item) => {
this.slideDown($(item));
});
Helpers.forEach(this.$accordionBtns, (idx, item) => {
this.activateBtn($(item));
});
}
}
// Returns constructor
export default Accordion;
<div class="c-accordion--default" data-css="c-accordion" data-js-module="accordion" role="tablist">
<div class="accordion__item">
<div class="accordion__header">
<a href="#test-1" class="accordion__btn" data-js-item="accordion-btn" role="tab" aria-controls="test-1">
<span class="accordion__btn-inner">Item 1</span>
</a>
</div>
<div class="accordion__content" id="test-1" data-js-item="accordion-content" role="tabpanel" aria-labelledby="test-1">
<div class="accordion__content-inner">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aliquid assumenda, ducimus facilis inventore iste labore laborum libero nam necessitatibus neque nulla numquam perspiciatis rem, repudiandae sed soluta veniam vero.
</div>
</div>
</div>
<div class="accordion__item">
<div class="accordion__header">
<a href="#test-2" class="accordion__btn" data-js-item="accordion-btn" role="tab" aria-controls="test-2">
<span class="accordion__btn-inner">Item 2</span>
</a>
</div>
<div class="accordion__content" id="test-2" data-js-item="accordion-content" role="tabpanel" aria-labelledby="test-2">
<div class="accordion__content-inner">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aliquid assumenda, ducimus facilis inventore iste labore laborum libero nam necessitatibus neque nulla numquam perspiciatis rem, repudiandae sed soluta veniam vero.
</div>
</div>
</div>
</div>
<div class="c-accordion--default is-some-modifier" data-css="c-accordion" data-js-module="accordion" data-js-options='{"openIndex":0,"singleOpen":true}' role="tablist">
<div class="accordion__item">
<div class="accordion__header">
<a href="#test-2-1" class="accordion__btn" data-js-item="accordion-btn" role="tab" aria-controls="test-2-1">
<span class="accordion__btn-inner">Item 1</span>
</a>
</div>
<div class="accordion__content" id="test-2-1" data-js-item="accordion-content" role="tabpanel" aria-labelledby="test-2-1">
<div class="accordion__content-inner">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aliquid assumenda, ducimus facilis inventore iste labore laborum libero nam necessitatibus neque nulla numquam perspiciatis rem, repudiandae sed soluta veniam vero.
</div>
</div>
</div>
<div class="accordion__item">
<div class="accordion__header">
<a href="#test-2-2" class="accordion__btn" data-js-item="accordion-btn" role="tab" aria-controls="test-2-2">
<span class="accordion__btn-inner">Item 2</span>
</a>
</div>
<div class="accordion__content" id="test-2-2" data-js-item="accordion-content" role="tabpanel" aria-labelledby="test-2-2">
<div class="accordion__content-inner">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab aliquid assumenda, ducimus facilis inventore iste labore laborum libero nam necessitatibus neque nulla numquam perspiciatis rem, repudiandae sed soluta veniam vero.
</div>
</div>
</div>
</div>