Skip to content

Instantly share code, notes, and snippets.

@masimplo
Last active June 13, 2017 14:07
Show Gist options
  • Select an option

  • Save masimplo/8527b8b0017debb291654a7c6f124bc2 to your computer and use it in GitHub Desktop.

Select an option

Save masimplo/8527b8b0017debb291654a7c6f124bc2 to your computer and use it in GitHub Desktop.
Accordion component for Ionic
import { AccordionTabComponent } from './accordion-tab.component';
import { AccordionComponent } from './accordion.component';
describe('AccordionTab::Component', function () {
let accordionComponent: AccordionComponent;
let sut: AccordionTabComponent;
beforeEach(function () {
accordionComponent = new AccordionComponent();
sut = new AccordionTabComponent(accordionComponent);
});
it('can be instantiated', function () {
expect(sut).toBeDefined();
});
it('adds itself to the parent component', () => {
expect(sut.accordion.tabCount).toEqual(1);
});
it('emits selected event when select is called', (done) => {
sut.selectedChanged
.subscribe(({selected, index}) => {
expect(selected).toEqual(true);
expect(index).toEqual(0);
done();
});
sut.select(new Event('click'));
});
it('does not emit selected event when select is called but control is disabled', () => {
spyOn(sut.selectedChanged, 'emit');
sut.disabled = true;
sut.select(new Event('click'));
expect(sut.selectedChanged.emit).not.toHaveBeenCalled();
});
it('does not emit selected event when select is called but control is already selected', () => {
spyOn(sut.selectedChanged, 'emit');
sut.selected = true;
sut.select(new Event('click'));
expect(sut.selectedChanged.emit).not.toHaveBeenCalled();
});
});
import { Input, EventEmitter, Output, Component, HostBinding } from '@angular/core';
import { AccordionComponent } from './accordion.component';
@Component({
selector: 'cv-accordion-tab',
templateUrl: 'accordion-tab.html'
})
export class AccordionTabComponent {
@HostBinding('class.selected')
@Input()
selected: boolean;
@Input() disabled: boolean;
@Output() selectedChanged: EventEmitter<any> = new EventEmitter();
public tabIndex = -1;
constructor(public accordion?: AccordionComponent) {
if (accordion) {
this.tabIndex = this.accordion.addTab(this);
}
}
public select(event: Event) {
if (this.disabled || this.selected) return;
this.selected = true;
this.selectedChanged.emit({ selected: this.selected, index: this.tabIndex });
event.preventDefault();
}
}
<div class="tab-header" (click)="select($event)" [class.selected]="selected">
<ng-content select="[tab-header]"></ng-content>
<ion-icon class="icon-dropdown-mini"></ion-icon>
</div>
<div class="container" [class.selected]="selected">
<div *ngIf="selected" class="content">
<ng-content select="[tab-content]"></ng-content>
</div>
</div>
.ios, .md {
cv-accordion-tab {
display: flex;
flex-direction: column;
flex-shrink: 0;
.tab-header {
display:flex;
min-height: 40px;
border-bottom: solid 1px #ddd;
background-color: #eee;
justify-content: center;
align-items: center;
h2{
margin: 0;
}
}
.content {
background-color: #fff;
}
.container {
background-color: #fff;
.list{
margin:0;
}
}
.container.selected {
overflow-y: scroll;
border-bottom: solid 1px #ddd;
flex-grow: 1;
flex-basis: 0;
}
ion-icon.icon-dropdown-mini {
height: 17px;
margin: 5px 10px;
transition: all 0.2s ease-in-out;
color: #999;
}
.selected ion-icon.icon-dropdown-mini {
transition: all 0.2s ease-in-out;
transform: rotateX(-180deg);
}
}
}
import { AccordionComponent } from './accordion.component';
import { AccordionTabComponent } from './accordion-tab.component';
describe('Accordion::Component', function () {
let sut: AccordionComponent;
beforeEach(function () {
sut = new AccordionComponent();
});
it('can be instantiated', function () {
expect(sut).toBeDefined();
});
it('holds added accordion tabs', function () {
expect(sut.tabCount).toEqual(0);
// tslint:disable-next-line:no-unused-expression
new AccordionTabComponent(sut);
expect(sut.tabCount).toEqual(1);
});
it('closes all other tabs when a tab emits selected event', function () {
const tab1 = new AccordionTabComponent(sut);
const tab2 = new AccordionTabComponent(sut);
const tab3 = new AccordionTabComponent(sut);
tab2.select(new Event('click'));
expect(tab1.selected).toEqual(false);
expect(tab2.selected).toEqual(true);
expect(tab3.selected).toEqual(false);
tab1.select(new Event('click'));
expect(tab1.selected).toEqual(true);
expect(tab2.selected).toEqual(false);
expect(tab3.selected).toEqual(false);
});
it('clears event emitter and tabs on destroy', function () {
sut.ngOnDestroy();
expect(sut.onOpen).toBe(null);
expect(() => sut.tabCount).toThrow();
});
});
import { Component, Output, EventEmitter, OnDestroy } from '@angular/core';
import { AccordionTabComponent } from './accordion-tab.component';
@Component({
selector: 'cv-accordion',
templateUrl: 'accordion.html'
})
export class AccordionComponent implements OnDestroy {
@Output() public onOpen: EventEmitter<{ index: number }> = new EventEmitter();
private _tabs: AccordionTabComponent[] = [];
public get tabCount(): number {
return this._tabs.length;
}
public addTab(tab: AccordionTabComponent) {
this._tabs.push(tab);
tab.selectedChanged
.subscribe(({ index }) => {
this._tabs
.filter(t => t.tabIndex !== index)
.forEach(t => { t.selected = false; });
this.onOpen.emit({ index });
});
return this._tabs.length - 1;
}
public ngOnDestroy() {
this._tabs = null;
this.onOpen = null;
}
}
<div>
<ng-content></ng-content>
</div>
.md, .ios {
cv-accordion {
height: 100%;
>div {
display: flex;
flex-direction: column;
height: 100%;
cv-accordion-tab.selected{
transition: flex-grow 0.2s linear;
flex-grow: 1;
}
cv-accordion-tab{
transition: flex-grow 0.2s linear;
}
}
}
}
<cv-accordion>
<cv-accordion-tab selected="true">
<h2 tab-header class="text-uppercase">Title</h2>
<ion-list tab-content>
<div>My content</div>
</ion-list>
</cv-accordion-tab>
<cv-accordion-tab">
<h2 tab-header class="text-uppercase">Another Title</h2>
<ion-list tab-content>
<div>My content 2</div>
</ion-list>
</cv-accordion-tab>
</cv-accordion>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment