Skip to content

Commit e340970

Browse files
authored
Migrate over collapse directive (#28)
1 parent f4d1934 commit e340970

7 files changed

Lines changed: 140 additions & 11 deletions

File tree

projects/angular-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@frankframework/angular-components",
3-
"version": "1.3.0",
3+
"version": "1.3.1",
44
"description": "A collection of reusable components designed for use in Frank!Framework projects",
55
"main": "",
66
"author": "Vivy Booman",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { CollapseDirective } from './collapse.directive';
2+
import { Component, DebugElement } from '@angular/core';
3+
import { ComponentFixture, TestBed } from '@angular/core/testing';
4+
import { By } from '@angular/platform-browser';
5+
6+
@Component({
7+
template: ``,
8+
imports: [
9+
/*CollapseDirective*/
10+
],
11+
})
12+
class TestComponent {}
13+
14+
describe('CollapseDirective', () => {
15+
let fixture: ComponentFixture<TestComponent>;
16+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
17+
let des: DebugElement[];
18+
let component: TestComponent;
19+
20+
beforeEach(() => {
21+
fixture = TestBed.configureTestingModule({
22+
imports: [CollapseDirective, TestComponent],
23+
}).createComponent(TestComponent);
24+
fixture.detectChanges(); // initial binding
25+
// all elements with an attached HighlightDirective
26+
des = fixture.debugElement.queryAll(By.directive(CollapseDirective));
27+
component = fixture.componentInstance;
28+
});
29+
30+
it('should create an instance', () => {
31+
expect(component).toBeTruthy();
32+
});
33+
});
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {
2+
Directive,
3+
HostListener,
4+
Input,
5+
booleanAttribute,
6+
Output,
7+
EventEmitter,
8+
AfterViewInit,
9+
inject,
10+
Renderer2,
11+
} from '@angular/core';
12+
13+
@Directive({
14+
selector: '[collapse]',
15+
standalone: true,
16+
})
17+
export class CollapseDirective implements AfterViewInit {
18+
@Input({ required: true }) collapse!: HTMLElement;
19+
@Input({ transform: booleanAttribute }) collapsed = false;
20+
@Input() animationSpeed = 300;
21+
@Output() collapsedChange = new EventEmitter<boolean>();
22+
23+
private collapseAnimation: Animation | null = null;
24+
private clientHeight = 0;
25+
private readonly renderer: Renderer2 = inject(Renderer2);
26+
27+
ngAfterViewInit(): void {
28+
this.setInitialState();
29+
}
30+
31+
@HostListener('click')
32+
onClick(): void {
33+
this.collapsed = !this.collapsed;
34+
this.collapsedChange.emit(this.collapsed);
35+
this.updateState();
36+
}
37+
38+
updateState(): void {
39+
if (this.collapseAnimation) {
40+
this.collapseAnimation.cancel();
41+
return;
42+
}
43+
44+
if (this.collapsed) {
45+
this.clientHeight = this.collapse.clientHeight;
46+
this.collapseElement();
47+
} else {
48+
this.expandElement();
49+
}
50+
}
51+
52+
private setInitialState(): void {
53+
if (this.collapsed) {
54+
this.clientHeight = this.collapse.clientHeight;
55+
this.renderer.addClass(this.collapse, 'collapsed')
56+
}
57+
}
58+
59+
private collapseElement(): void {
60+
this.collapse.classList.add('transforming');
61+
this.collapseAnimation = this.collapse.animate(
62+
{ height: [`${this.clientHeight}px`, '0px'] },
63+
{ duration: this.animationSpeed, easing: 'ease-in-out' },
64+
);
65+
this.collapseAnimation.finished
66+
.then(() => {
67+
this.renderer.addClass(this.collapse, 'collapsed')
68+
})
69+
.finally(() => {
70+
this.renderer.removeClass(this.collapse, 'transforming');
71+
this.collapseAnimation = null;
72+
});
73+
}
74+
75+
private expandElement(): void {
76+
this.collapseAnimation = this.collapse.animate(
77+
{ height: ['0px', `${this.clientHeight}px`] },
78+
{ duration: this.animationSpeed, easing: 'ease-in-out' },
79+
);
80+
this.collapseAnimation.finished
81+
.then(() => {
82+
this.renderer.removeClass(this.collapse, 'collapsed');
83+
})
84+
.finally(() => {
85+
this.collapseAnimation = null;
86+
});
87+
}
88+
}

projects/angular-components/src/lib/library.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { DatatableComponent } from './datatable/datatable.component';
77
import { CheckboxComponent } from './checkbox/checkbox.component';
88
import { DtContentDirective } from './datatable/dt-content.directive';
99
import { ThSortableDirective } from './th-sortable.directive';
10+
import { CollapseDirective } from './collapse.directive';
1011

1112
const components = [
1213
ButtonComponent,
@@ -16,6 +17,7 @@ const components = [
1617
DatatableComponent,
1718
CheckboxComponent,
1819
DtContentDirective,
20+
CollapseDirective,
1921
ThSortableDirective,
2022
];
2123

projects/angular-components/src/public_api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from './lib/chip/chip.component';
77
export * from './lib/datatable/datatable.component';
88
export * from './lib/checkbox/checkbox.component';
99

10+
export * from './lib/collapse.directive';
1011
export * from './lib/th-sortable.directive';
1112
export * from './lib/datatable/dt-content.directive';
1213

projects/angular-components/src/styles/_light_theme.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,8 @@ th[sortable] {
126126
right: 0;
127127
}
128128
}
129+
130+
.collapsed {
131+
height: 0;
132+
overflow: hidden;
133+
}

src/app/app.component.html

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<h1>FF Components</h1>
22
<ul class="components-list">
33
<li>
4-
<h2>Button</h2>
5-
<div class="components">
4+
<h2 [collapse]="buttons">Button</h2>
5+
<div #buttons class="components">
66
<ff-button>Click me!</ff-button>
77
<ff-button><app-svg-add-icon colour="#fff" [width]="16" [height]="16" /> Click me!</ff-button>
88
<ff-button active="true">Active</ff-button>
@@ -11,8 +11,8 @@ <h2>Button</h2>
1111
</div>
1212
</li>
1313
<li>
14-
<h2>Checkbox</h2>
15-
<div class="components">
14+
<h2 [collapse]="checkboxes">Checkbox</h2>
15+
<div #checkboxes class="components">
1616
<ff-checkbox [(ngModel)]="checked" />
1717
<ff-checkbox [(ngModel)]="checked2" />
1818
<ff-checkbox checked />
@@ -22,8 +22,8 @@ <h2>Checkbox</h2>
2222
<p>Checkbox 1: {{ checked }}, checkbox 2: {{ checked2 }}</p>
2323
</li>
2424
<li>
25-
<h2>Search</h2>
26-
<div class="components">
25+
<h2 [collapse]="search">Search</h2>
26+
<div #search class="components">
2727
<ff-search style="width: 100%" forceFocus autofocus [(ngModel)]="searchText" />
2828
<div class="separator">
2929
<ff-search placeholder="Test the focus key" focusKey="=" style="width: 100%" />
@@ -35,8 +35,8 @@ <h2>Search</h2>
3535
<ff-button (click)="changeSearchQuery()">Update search query</ff-button>
3636
</li>
3737
<li>
38-
<h2>Alert</h2>
39-
<div class="components">
38+
<h2 [collapse]="alerts">Alert</h2>
39+
<div #alerts class="components">
4040
<ff-alert type="info">Info</ff-alert>
4141
<ff-alert type="success">Success</ff-alert>
4242
<ff-alert type="warning">Warning</ff-alert>
@@ -64,8 +64,8 @@ <h2>Alert</h2>
6464
</div>
6565
</li>
6666
<li>
67-
<h2>Chip</h2>
68-
<div class="components">
67+
<h2 [collapse]="chips">Chip</h2>
68+
<div #chips class="components">
6969
<ff-chip rounded>L</ff-chip>
7070
<ff-chip>Label</ff-chip>
7171
<ff-chip colour="#345678" rounded>Colour!</ff-chip>

0 commit comments

Comments
 (0)