Skip to content

Instantly share code, notes, and snippets.

@vasilionjea
Last active February 2, 2018 05:37
Show Gist options
  • Save vasilionjea/60e1c98f7440233e8c3fe211909a8a10 to your computer and use it in GitHub Desktop.
Save vasilionjea/60e1c98f7440233e8c3fe211909a8a10 to your computer and use it in GitHub Desktop.
Ember Accordion
import Ember from 'ember';
const { Component, computed } = Ember;
export default Component.extend({
tagName: 'button',
type: 'button',
classNames: ['accordion-item__header'],
attributeBindings: ['type', 'aria-expanded', 'aria-controls'],
'aria-expanded': computed('isExpanded', function() {
return this.get('isExpanded') ? 'true' : 'false';
}),
didInsertElement() {
const panel = this.element.nextElementSibling;
this.set('aria-controls', panel.getAttribute('id'));
},
click() {
this.get('expand')();
},
});
import Ember from 'ember';
const { Component, Object: EmberObject } = Ember;
export default Component.extend({
tagName: 'li',
classNames: ['accordion-item'],
classNameBindings: ['state.isExpanded:accordion-item--is-expanded'],
expandOnInit: false,
registerState() {
const header = this.element.firstElementChild;
const state = EmberObject.create({
id: this.elementId,
element: this.element,
isExpanded: this.get('expandOnInit'),
openHeight: this.element.getBoundingClientRect().height,
closedHeight: header.getBoundingClientRect().height,
});
this.set('state', state);
this.get('register')(state);
},
didInsertElement() {
this.registerState();
if (this.get('expandOnInit')) {
this.element.style.maxHeight = `${this.get('state.openHeight')}px`;
} else {
this.element.style.maxHeight = `${this.get('state.closedHeight')}px`;
}
},
});
import Ember from 'ember';
import KeyboardAccessMixin from 'twiddle/mixins/accordion-keyboard-access';
const { Component, A } = Ember;
export default Component.extend(KeyboardAccessMixin, {
tagName: 'ul',
classNames: ['accordion-list'],
_items: A(),
actions: {
registerItem(item) {
item && this.get('_items').pushObject(item);
},
expandItem(id) {
if (!id) {
return;
}
this.get('_items').forEach(item => {
if (item.get('id') === id) {
// Show this item
item.setProperties({
isExpanded: true,
'element.style.maxHeight': `${item.get('openHeight')}px`
});
} else {
// Hide the rest
item.setProperties({
isExpanded: false,
'element.style.maxHeight': `${item.get('closedHeight')}px`
});
}
});
},
}
});
import Ember from 'ember';
const { Component, computed } = Ember;
export default Component.extend({
tagName: 'section',
classNames: ['accordion-item__panel'],
attributeBindings: ['aria-hidden'],
'aria-hidden': computed('isExpanded', function() {
return this.get('isExpanded') ? 'false' : 'true';
}),
});
import Ember from 'ember';
const { Controller } = Ember;
export default Controller.extend({
appName: 'Ember Accordion',
author: {
name: 'Billy Onjea',
link: 'https://gist.github.com/vasilionjea/60e1c98f7440233e8c3fe211909a8a10'
}
});
const { Mixin } = Ember;
const KEYCODES = {
ARROW_UP: 38,
ARROW_DOWN: 40,
};
export default Mixin.create({
_onArrowDown(item) {
const next = item.nextElementSibling || this.element.firstElementChild;
next && next.firstElementChild.focus();
},
_onArrowUp(item) {
const prev = item.previousElementSibling || this.element.lastElementChild;
prev && prev.firstElementChild.focus();
},
keyDown({ keyCode, target }) {
this._super(...arguments);
if (!target.classList.contains('accordion-item__header')) {
return;
}
const item = target.parentElement;
switch(keyCode) {
case KEYCODES.ARROW_DOWN:
this._onArrowDown(item);
break;
case KEYCODES.ARROW_UP:
this._onArrowUp(item);
break;
}
},
});
body {
font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;
line-height: 1.5;
padding: 0 20px;
}
h4 { margin: 0; }
ul, li {
margin: 0;
padding: 0;
list-style: none;
}
/**
* Accordion Styles
*/
.accordion-list {
box-sizing: border-box;
border: 1px solid #bfbfbf;
box-shadow: 0 1px 3px #bfbfbf;
}
.accordion-item {
overflow: hidden;
transition: max-height 400ms ease-in-out;
}
.accordion-item__header {
position: relative;
display: block;
width: 100%;
border: none;
border-top: 1px solid #bfbfbf;
margin: 0;
padding: 1em 1.25em;
cursor: pointer;
font-size: .925rem;
font-weight: normal;
text-align: left;
background: none;
border-radius: 0;
color: #424242;
}
.accordion-item:first-child .accordion-item__header {
border-top: none;
}
.accordion-item__header::after {
content: '';
display: block;
position: absolute;
left: 0;
bottom: -1px;
width: 100%;
border-bottom: 1px solid #bfbfbf;
}
.accordion-item--is-expanded .accordion-item__header,
.accordion-item__header:hover,
.accordion-item__header:focus {
background: #f7f7f7;
box-shadow: inset 0 0 3px 2px #f2f2f2;
}
.accordion-item__panel {
visibility: hidden;
opacity: 0;
transition: visibility 400ms linear, opacity 400ms linear;
padding: 1.5em;
}
.accordion-item--is-expanded .accordion-item__panel {
visibility: visible;
opacity: 1;
}
<h1>{{appName}}</h1>
<hr><br>
{{#accordion-list classNames='my-accordion' as |accordion|}}
{{#accordion.item expandOnInit=true as |item|}}
{{#item.header}}Lorem Ipsum{{/item.header}}
{{#item.panel}}
<h4>Lorem ipsum</h4>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur <a href="#">numquam</a> optio quidem. Officia rem libero dicta ratione eum ut nisi dolorem!</p>
<ul>
<li>≠ Aspernatur</li>
<li>≠ numquam</li>
<li>≠ optio</li>
</ul>
{{/item.panel}}
{{/accordion.item}}
{{#accordion.item as |item|}}
{{#item.header}}Dolor Sit{{/item.header}}
{{#item.panel}}
<h4>Dolor Sit</h4>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur <a href="#">numquam</a> optio quidem impedit tempora perspiciatis, aliquam modi quaerat, porro quasi in, officia rem libero dicta ratione eum ut nisi dolorem!</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur numquam optio quidem!</p>
{{/item.panel}}
{{/accordion.item}}
{{#accordion.item as |item|}}
{{#item.header}}Amet{{/item.header}}
{{#item.panel}}
<h4>Amet</h4>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur <a href="#">numquam</a> optio quidem impedit tempora perspiciatis, aliquam modi quaerat, porro quasi in, officia rem libero dicta ratione eum ut nisi dolorem!</p>
{{/item.panel}}
{{/accordion.item}}
{{#accordion.item as |item|}}
{{#item.header}}Consectetur{{/item.header}}
{{#item.panel}}
<h4>Consectetur</h4>
<p>Aspernatur <a href="#">numquam</a> optio quidem impedit tempora perspiciatis, aliquam modi quaerat, porro quasi in, officia rem libero dicta ratione eum ut nisi dolorem!</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
{{/item.panel}}
{{/accordion.item}}
{{/accordion-list}}
<p style="text-align:center;color:#888">
<small>Created by: <a href="{{author.link}}" target="_blank">{{author.name}}</a></small>
</p>
{{yield (hash
isExpanded=state.isExpanded
header=(component 'accordion-header'
isExpanded=state.isExpanded
expand=(action expand state.id)
)
panel=(component 'accordion-panel'
isExpanded=state.isExpanded
)
)}}
{{yield (hash
item=(component 'accordion-item'
register=(action 'registerItem')
expand=(action 'expandItem')
)
)}}
{
"version": "0.13.0",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.16.2",
"ember-template-compiler": "2.16.2",
"ember-testing": "2.16.2"
},
"addons": {
"ember-data": "2.16.3"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment