diff --git a/projects/showcase/src/app/components/accordion/showcase-accordion.component.html b/projects/showcase/src/app/components/accordion/showcase-accordion.component.html new file mode 100644 index 000000000..86daa8918 --- /dev/null +++ b/projects/showcase/src/app/components/accordion/showcase-accordion.component.html @@ -0,0 +1,5 @@ +
+ + + +
\ No newline at end of file diff --git a/projects/showcase/src/app/components/accordion/showcase-accordion.component.ts b/projects/showcase/src/app/components/accordion/showcase-accordion.component.ts new file mode 100644 index 000000000..56cb906b0 --- /dev/null +++ b/projects/showcase/src/app/components/accordion/showcase-accordion.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'showcase-accordion', + templateUrl: 'showcase-accordion.component.html' +}) +export class ShowcaseAccordion { + constructor() { + } +} \ No newline at end of file diff --git a/projects/showcase/src/app/components/showcase-components.component.html b/projects/showcase/src/app/components/showcase-components.component.html index 19195c2cf..0eb018d9d 100644 --- a/projects/showcase/src/app/components/showcase-components.component.html +++ b/projects/showcase/src/app/components/showcase-components.component.html @@ -107,6 +107,9 @@ Progress bar + Accordion + + Percentage Circle diff --git a/projects/showcase/src/app/showcase.module.ts b/projects/showcase/src/app/showcase.module.ts index 449732c0e..4689d6cba 100644 --- a/projects/showcase/src/app/showcase.module.ts +++ b/projects/showcase/src/app/showcase.module.ts @@ -115,6 +115,7 @@ import { } from './components/progress-bars/progressbar-with-text-dialog/showcase-progressbar-with-text-dialog.component'; import { ShowcaseImageViewerComponent } from './components/image-viewer/showcase-image-viewer.component'; import { CdkTreeModule } from '@angular/cdk/tree'; +import { ShowcaseAccordion } from './components/accordion/showcase-accordion.component'; @NgModule({ declarations: [ ShowcaseComponent, @@ -204,6 +205,7 @@ import { CdkTreeModule } from '@angular/cdk/tree'; ShowcaseInteractiveComponent, ShowcaseProgressBarWithTextDialog, ShowcaseImageViewerComponent, + ShowcaseAccordion ], bootstrap: [ShowcaseComponent], imports: [ diff --git a/projects/systelab-components/package.json b/projects/systelab-components/package.json index 95b492b14..3560eaf49 100644 --- a/projects/systelab-components/package.json +++ b/projects/systelab-components/package.json @@ -1,6 +1,6 @@ { "name": "systelab-components", - "version": "18.0.4", + "version": "18.1.0", "license": "MIT", "keywords": [ "Angular", diff --git a/projects/systelab-components/src/lib/accordion/README.md b/projects/systelab-components/src/lib/accordion/README.md new file mode 100644 index 000000000..d45a88d55 --- /dev/null +++ b/projects/systelab-components/src/lib/accordion/README.md @@ -0,0 +1,32 @@ +# systelab-accordion + +Component to create accordion with internal content. + +## Using the template + +```html + + + +``` +The accordion component wraps the internal component, adding accordion functionality that allows us to expand or collapse its content. + +If you want the defaults the template will look like: + +```html + + + +``` + + +## Properties + +| Name | Type | Default | Description | +| ---- |:------:|:---------:|-------------------------------------------------------------------| +| headerTitle | string | '' | Sets the header title of the accordion.| +| preferenceName | string | undefined | The preference name where the isCollapsed state will be stored. | +| contentMaxHeight | number | 300 | The maximum height of the accordion content. | +| withOverflow | number | false | When set to true, enables vertical scrolling within the container. | +| headerColor | number | undefined | The background color of the accordion header. | +| iconColor | number | undefined | The color of the expand/collapse icon. | diff --git a/projects/systelab-components/src/lib/accordion/accordion.component.html b/projects/systelab-components/src/lib/accordion/accordion.component.html new file mode 100644 index 000000000..43b3f7083 --- /dev/null +++ b/projects/systelab-components/src/lib/accordion/accordion.component.html @@ -0,0 +1,10 @@ +
+
+ + {{ headerTitle }} +
+
+ +
+
\ No newline at end of file diff --git a/projects/systelab-components/src/lib/accordion/accordion.component.scss b/projects/systelab-components/src/lib/accordion/accordion.component.scss new file mode 100644 index 000000000..40eadead0 --- /dev/null +++ b/projects/systelab-components/src/lib/accordion/accordion.component.scss @@ -0,0 +1,32 @@ +.accordion-header { + background-color: lightgrey; + border-radius: 4px 4px 0 0; + height: 40px; + i { + &:after { + content: ''; + background-color: white; + width: 15px; + height: 15px; + z-index: 1; + position: absolute; + } + &:before { + z-index: 2; + } + font-size: 25px; + &:hover { + cursor: pointer; + } + } +} + +.collapsible-content { + transition: max-height 0.3s ease-out, opacity 0.3s ease-out; + opacity: 1; + border: 1px solid var(--slab_table_border_color) !important; +} + +.collapsed { + opacity: 0; +} \ No newline at end of file diff --git a/projects/systelab-components/src/lib/accordion/accordion.component.spec.ts b/projects/systelab-components/src/lib/accordion/accordion.component.spec.ts new file mode 100644 index 000000000..b2d3b36da --- /dev/null +++ b/projects/systelab-components/src/lib/accordion/accordion.component.spec.ts @@ -0,0 +1,83 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowserModule, By } from '@angular/platform-browser'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { Accordion } from './accordion.component'; +import { PreferencesService } from 'systelab-preferences'; + +describe('Systelab Accordion', () => { + let fixture: ComponentFixture; + let component: Accordion; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [], + imports: [BrowserModule], + providers: [ + PreferencesService, + provideHttpClient(withInterceptorsFromDi()) + ] + }).compileComponents(); + + }); + + beforeEach(() => { + fixture = TestBed.createComponent(Accordion); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be instantiate', () => { + expect(fixture.componentInstance).toBeDefined(); + }); + + it('should be height 0 when isCollapse is true', async () => { + component.isCollapsed = true; + fixture.detectChanges(); + await fixture.whenStable(); + const collapsibleContent = fixture.debugElement.query(By.css('.collapsible-content')); + const maxHeight = parseInt(getComputedStyle(collapsibleContent.nativeElement).maxHeight, 10); + expect(maxHeight).toEqual(0); + }); + + it('should be height contentMaxHeight when isCollapse is false', async () => { + component.isCollapsed = false; + fixture.detectChanges(); + await fixture.whenStable(); + const collapsibleContent = fixture.debugElement.query(By.css('.collapsible-content')); + const maxHeight = parseInt(getComputedStyle(collapsibleContent.nativeElement).maxHeight, 10); + expect(maxHeight).toEqual(component.contentMaxHeight); + }); + + it('should be collapsed if is expanded and clicks icon button', async () => { + component.isCollapsed = false; + const collapseExpandIcon = fixture.debugElement.query(By.css('.accordion-header')); + collapseExpandIcon.triggerEventHandler('click', null); + fixture.detectChanges(); + await fixture.whenStable(); + expect(component.isCollapsed).toBeTrue(); + }); + + it('should be expanded if is collapsed and clicks icon button', async () => { + component.isCollapsed = true; + const collapseExpandIcon = fixture.debugElement.query(By.css('.accordion-header')); + collapseExpandIcon.triggerEventHandler('click', null); + fixture.detectChanges(); + await fixture.whenStable(); + expect(component.isCollapsed).toBeFalse(); + }); + + it('should be get isCollapsed value from preferences id flag preferenceName exists', async () => { + const valueReturned: boolean = true; + const initialPreferenceName = 'testName'; + component.isCollapsed = false; + component.preferenceName = initialPreferenceName; + const preferenceServiceGetSpy = spyOn(component['preferenceService'], 'get').and.returnValue(valueReturned); + const preferenceServicePutSpy = spyOn(component['preferenceService'], 'put').and.callThrough(); + component.ngOnInit(); + await fixture.whenStable(); + expect(component.preferenceName).toEqual(`${initialPreferenceName}.${component['preferenceSuffix']}`); + expect(preferenceServiceGetSpy).toHaveBeenCalledWith(component.preferenceName, false); + expect(component.isCollapsed).toEqual(valueReturned); + expect(preferenceServicePutSpy).toHaveBeenCalledWith(component.preferenceName, valueReturned); + }); +}); \ No newline at end of file diff --git a/projects/systelab-components/src/lib/accordion/accordion.component.ts b/projects/systelab-components/src/lib/accordion/accordion.component.ts new file mode 100644 index 000000000..66f99f36e --- /dev/null +++ b/projects/systelab-components/src/lib/accordion/accordion.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { PreferencesService } from 'systelab-preferences'; + +@Component({ + selector: 'systelab-accordion', + templateUrl: './accordion.component.html', + styleUrls: ['./accordion.component.scss'] +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class Accordion implements OnInit { + @Input() headerTitle: string = ''; + @Input() preferenceName: string; + @Input() contentMaxHeight: number = 300; + @Input() withOverflow: boolean = false; + @Input() headerColor: string; + @Input() iconColor: string; + public isCollapsed: boolean = false; + private preferenceSuffix: string = 'accordionStatus'; + + constructor(private readonly preferenceService: PreferencesService) { + } + + public ngOnInit() { + if(this.preferenceName) { + this.preferenceName = `${this.preferenceName}.${this.preferenceSuffix}`; + this.isCollapsed = this.preferenceService.get(this.preferenceName, false); + this.preferenceService.put(this.preferenceName, this.isCollapsed); + } + } +} \ No newline at end of file diff --git a/projects/systelab-components/src/lib/systelab-components.module.ts b/projects/systelab-components/src/lib/systelab-components.module.ts index 0fc543cbd..ce6b2f652 100644 --- a/projects/systelab-components/src/lib/systelab-components.module.ts +++ b/projects/systelab-components/src/lib/systelab-components.module.ts @@ -104,6 +104,7 @@ import { PositiveIntegerInputCellEditorComponent } from './grid/custom-cells/positive-integer/positive-integer-input-cell-editor.component'; import { TestIdDirective } from './directives/test-id.directive'; +import { Accordion } from './accordion/accordion.component'; export const factory = () => { const systelabComponentsModuleCreated = (factory as any)._systelabComponentsModuleCreated || false; @@ -223,6 +224,7 @@ const providers = [ PositiveIntegerInputCellEditorComponent, NumpadDecimalNumericDirective, TestIdDirective, + Accordion, ], exports: [ SliderComponent, @@ -310,7 +312,8 @@ const providers = [ NumpadDecimalNumericDirective, TestIdDirective, NumpadDecimalNumericDirective, - PositiveIntegerInputCellEditorComponent + PositiveIntegerInputCellEditorComponent, + Accordion ], }) export class SystelabComponentsModule { diff --git a/projects/systelab-components/src/public-api.ts b/projects/systelab-components/src/public-api.ts index 41d22a734..97bc1c5ab 100644 --- a/projects/systelab-components/src/public-api.ts +++ b/projects/systelab-components/src/public-api.ts @@ -121,6 +121,7 @@ export * from './lib/listbox/abstract-listbox.component'; export * from './lib/listbox/abstract-api-listbox.component'; export * from './lib/listbox/abstract-api-tree-listbox.component'; export * from './lib/sortable-list/abstract-sortable-list.component'; +export * from './lib/accordion/accordion.component'; export * from './lib/add-remove-list/abstract-add-remove-list.component'; export * from './lib/tree/tree-node'; export * from './lib/tree/abstract-tree.component';