From cb4a8103b6a368d41189e81e1d6cf4ede08dfbee Mon Sep 17 00:00:00 2001 From: Francesco Pavone Date: Thu, 30 Aug 2018 18:19:28 +0200 Subject: [PATCH] feat(Tabs): implementa la componente dei Tab ref #47 --- e2e/src/tabs/tabs.e2e-spec.ts | 108 ++++ e2e/src/tabs/tabs.po.ts | 135 +++++ .../src/lib/design-angular-kit.module.ts | 6 + .../src/lib/tabs/tab-group.component.html | 29 + .../src/lib/tabs/tab-group.component.scss | 0 .../src/lib/tabs/tab-group.component.spec.ts | 548 ++++++++++++++++++ .../src/lib/tabs/tab-group.component.ts | 223 +++++++ .../src/lib/tabs/tab.component.html | 1 + .../src/lib/tabs/tab.component.scss | 0 .../src/lib/tabs/tab.component.ts | 90 +++ .../design-angular-kit/src/lib/util/util.ts | 9 + projects/design-angular-kit/src/public_api.ts | 1 + src/app/app-routing.module.ts | 1 + src/app/table-of-content.service.ts | 4 + .../tabs-dynamic-example.component.html | 35 ++ .../tabs-dynamic-example.component.scss | 0 .../tabs-dynamic-example.component.ts | 26 + .../tabs-example/tabs-example.component.html | 29 + .../tabs-example/tabs-example.component.scss | 0 .../tabs-example/tabs-example.component.ts | 33 ++ .../tabs-examples.component.scss | 0 .../tabs-examples/tabs-examples.component.tpl | 25 + .../tabs-examples/tabs-examples.component.ts | 15 + .../tabs/tabs-index/tabs-index.component.html | 82 +++ .../tabs/tabs-index/tabs-index.component.scss | 0 .../tabs/tabs-index/tabs-index.component.ts | 19 + src/app/tabs/tabs-routing.module.ts | 13 + src/app/tabs/tabs.module.ts | 27 + 28 files changed, 1459 insertions(+) create mode 100644 e2e/src/tabs/tabs.e2e-spec.ts create mode 100644 e2e/src/tabs/tabs.po.ts create mode 100644 projects/design-angular-kit/src/lib/tabs/tab-group.component.html create mode 100644 projects/design-angular-kit/src/lib/tabs/tab-group.component.scss create mode 100644 projects/design-angular-kit/src/lib/tabs/tab-group.component.spec.ts create mode 100644 projects/design-angular-kit/src/lib/tabs/tab-group.component.ts create mode 100644 projects/design-angular-kit/src/lib/tabs/tab.component.html create mode 100644 projects/design-angular-kit/src/lib/tabs/tab.component.scss create mode 100644 projects/design-angular-kit/src/lib/tabs/tab.component.ts create mode 100644 src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.html create mode 100644 src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.scss create mode 100644 src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.ts create mode 100644 src/app/tabs/tabs-example/tabs-example.component.html create mode 100644 src/app/tabs/tabs-example/tabs-example.component.scss create mode 100644 src/app/tabs/tabs-example/tabs-example.component.ts create mode 100644 src/app/tabs/tabs-examples/tabs-examples.component.scss create mode 100644 src/app/tabs/tabs-examples/tabs-examples.component.tpl create mode 100644 src/app/tabs/tabs-examples/tabs-examples.component.ts create mode 100644 src/app/tabs/tabs-index/tabs-index.component.html create mode 100644 src/app/tabs/tabs-index/tabs-index.component.scss create mode 100644 src/app/tabs/tabs-index/tabs-index.component.ts create mode 100644 src/app/tabs/tabs-routing.module.ts create mode 100644 src/app/tabs/tabs.module.ts diff --git a/e2e/src/tabs/tabs.e2e-spec.ts b/e2e/src/tabs/tabs.e2e-spec.ts new file mode 100644 index 00000000..3a577269 --- /dev/null +++ b/e2e/src/tabs/tabs.e2e-spec.ts @@ -0,0 +1,108 @@ +import { ButtonPage } from './tabs.po'; + +describe('Button', () => { + let page: ButtonPage; + + beforeEach(async() => { + page = new ButtonPage(); + await page.go(); + }); + + it('dovrebbe essere visualizzato il primo tab', async () => { + const firstTabShown = await page.isNthTabContentShown(1); + const secondTabShown = await page.isNthTabContentShown(2); + const thirdTabShown = await page.isNthTabContentShown(3); + const fourthTabShown = await page.isNthTabContentShown(4); + + expect(firstTabShown).toBeTruthy(); + expect(secondTabShown).toBeFalsy(); + expect(thirdTabShown).toBeFalsy(); + expect(fourthTabShown).toBeFalsy(); + }); + + it('dovrebbe cambiare il contenuto visualizzato al click su un altro tab', async () => { + await page.clickNthTab(3); + + const firstTabShown = await page.isNthTabContentShown(1); + const secondTabShown = await page.isNthTabContentShown(2); + const thirdTabShown = await page.isNthTabContentShown(3); + const fourthTabShown = await page.isNthTabContentShown(4); + + expect(firstTabShown).toBeFalsy(); + expect(secondTabShown).toBeFalsy(); + expect(thirdTabShown).toBeTruthy(); + expect(fourthTabShown).toBeFalsy(); + }); + + it('non dovrebbe cambiare il contenuto se si clicca su un tab disabilitato', async () => { + + let firstTabShown = await page.isNthTabContentShown(1); + let fourthTabShown = await page.isNthTabContentShown(4); + + expect(firstTabShown).toBeTruthy(); + expect(fourthTabShown).toBeFalsy(); + + await page.clickDisabledCheckbox(); + await page.clickNthTab(4); + + firstTabShown = await page.isNthTabContentShown(1); + fourthTabShown = await page.isNthTabContentShown(4); + + expect(firstTabShown).toBeTruthy(); + expect(fourthTabShown).toBeFalsy(); + }); + + it('dovrebbe selezionare un nuovo tab aggiunto solo se specificato esplicitamente', async () => { + expect(page.getDynamicTabGroupNumberOfTabs()).toBe(3); + + let firstTabShown = await page.isNthDynamicTabContentShown(1); + + expect(firstTabShown).toBeTruthy(); + + // faccio click per aggiungere un tab senza che questo venga selezionato + await page.clickAddButton(); + + expect(page.getDynamicTabGroupNumberOfTabs()).toBe(4); + + firstTabShown = await page.isNthDynamicTabContentShown(1); + let fourthTabShown = await page.isNthDynamicTabContentShown(4); + + expect(firstTabShown).toBeTruthy(); + expect(fourthTabShown).toBeFalsy(); + + // faccio click per aggiungere un tab e selezionarlo + await page.clickSelectAfterAddingCheckbox(); + await page.clickAddButton(); + + expect(page.getDynamicTabGroupNumberOfTabs()).toBe(5); + + fourthTabShown = await page.isNthDynamicTabContentShown(4); + const fifthTabShown = await page.isNthDynamicTabContentShown(5); + + expect(fourthTabShown).toBeFalsy(); + expect(fifthTabShown).toBeTruthy(); + }); + + it('dovrebbe cancellare correttamente un tab e selezionare quello successivo se presente, altrimenti quello precedente', async () => { + expect(page.getDynamicTabGroupNumberOfTabs()).toBe(3); + + await page.clickNthDynamicTab(2); + await page.clickNthDynamicTabRemoveButton(2); + + expect(page.getDynamicTabGroupNumberOfTabs()).toBe(2); + + const secondTabShown = await page.isNthDynamicTabContentShown(2); + + expect(secondTabShown).toBeTruthy(); + + await page.clickNthDynamicTab(2); + await page.clickNthDynamicTabRemoveButton(2); + + expect(page.getDynamicTabGroupNumberOfTabs()).toBe(1); + + const firstTabShown = await page.isNthDynamicTabContentShown(1); + + expect(firstTabShown).toBeTruthy(); + }); + +}); diff --git a/e2e/src/tabs/tabs.po.ts b/e2e/src/tabs/tabs.po.ts new file mode 100644 index 00000000..98cb02a7 --- /dev/null +++ b/e2e/src/tabs/tabs.po.ts @@ -0,0 +1,135 @@ +import { browser, by, element } from 'protractor'; + +export class ButtonPage { + private readonly BUTTON_URL = '/#/componenti/tabs'; + private readonly ID_EXAMPLE_TAB = 'tabs-examples-tab'; + + private readonly ID_CHECKBOX_DARKTHEME = this.getLabelForAttribute('checkbox-0'); + private readonly ID_CHECKBOX_DISABLE = this.getLabelForAttribute('checkbox-1'); + private readonly ID_CHECKBOX_PILL = this.getLabelForAttribute('checkbox-2'); + private readonly ID_CHECKBOX_SELECT_AFTER_ADDING = this.getLabelForAttribute('checkbox-3'); + + private readonly ID_TAB_CONTENT_1 = 'it-tab-content-0-0'; + private readonly ID_TAB_CONTENT_2 = 'it-tab-content-0-1'; + private readonly ID_TAB_CONTENT_3 = 'it-tab-content-0-2'; + private readonly ID_TAB_CONTENT_4 = 'it-tab-content-0-3'; + + private readonly ID_TAB_1 = 'it-tab-label-0-0'; + private readonly ID_TAB_2 = 'it-tab-label-0-1'; + private readonly ID_TAB_3 = 'it-tab-label-0-2'; + private readonly ID_TAB_4 = 'it-tab-label-0-3'; + + private readonly ID_ADD_BUTTON = 'button-0'; + + private getLabelForAttribute(attr: string) { + return `[for="${attr}"]`; + } + + private getTabIdByNumber(n: number) { + switch (n) { + case 1: + return this.ID_TAB_1; + case 2: + return this.ID_TAB_2; + case 3: + return this.ID_TAB_3; + case 4: + return this.ID_TAB_4; + default: + return null; + } + } + + private getTabContentIdByNumber(n: number) { + switch (n) { + case 1: + return this.ID_TAB_CONTENT_1; + case 2: + return this.ID_TAB_CONTENT_2; + case 3: + return this.ID_TAB_CONTENT_3; + case 4: + return this.ID_TAB_CONTENT_4; + default: + return null; + } + } + + private async getDynamicTabIdByNumber(n: number) { + const index = n - 1; + const tabs = await element.all(by.css('.dynamic-tab-group .nav-link')); + return tabs[index].getAttribute('id'); + } + + private async getDynamicTabContentIdByNumber(n: number) { + const index = n - 1; + const tabs = await element.all(by.css('.dynamic-tab-group .tab-pane')); + return tabs[index].getAttribute('id'); + } + + async go() { + await browser.get(this.BUTTON_URL); + await element(by.id(this.ID_EXAMPLE_TAB)).click(); + return await browser.sleep(500); + } + + async clickElement(id: string) { + await element(by.css(id)).click(); + } + + async clickDarkThemeCheckbox() { + await this.clickElement(this.ID_CHECKBOX_DARKTHEME); + } + + async clickDisabledCheckbox() { + await this.clickElement(this.ID_CHECKBOX_DISABLE); + } + + async clickPillCheckbox() { + await this.clickElement(this.ID_CHECKBOX_PILL); + } + + async clickSelectAfterAddingCheckbox() { + await this.clickElement(this.ID_CHECKBOX_SELECT_AFTER_ADDING); + } + + async clickAddButton() { + await this.clickElement(`#${this.ID_ADD_BUTTON}`); + } + + async isNthTabContentShown(n: number) { + const idTab = this.getTabContentIdByNumber(n); + const classes = await this.getElementClasses(idTab); + return classes.includes('show') && classes.includes('active'); + } + + async isNthDynamicTabContentShown(n: number) { + const idTab = await this.getDynamicTabContentIdByNumber(n); + const classes = await this.getElementClasses(idTab); + return classes.includes('show') && classes.includes('active'); + } + + async clickNthTab(n: number) { + await this.clickElement('#' + this.getTabIdByNumber(n)); + } + + async getDynamicTabGroupNumberOfTabs() { + const tabs = await element.all(by.css('.dynamic-tab-group .nav-item')); + return tabs.length; + } + + async clickNthDynamicTab(n: number) { + const idTab = await this.getDynamicTabIdByNumber(n); + await this.clickElement(`#${idTab}`); + } + + async clickNthDynamicTabRemoveButton(n: number) { + const idTab = await this.getDynamicTabContentIdByNumber(n); + await this.clickElement(`#${idTab} button`); + } + + private async getElementClasses(id: string) { + const classAttribute = await element(by.id(id)).getAttribute('class'); + return classAttribute.split(' '); + } +} diff --git a/projects/design-angular-kit/src/lib/design-angular-kit.module.ts b/projects/design-angular-kit/src/lib/design-angular-kit.module.ts index 0535ee79..d3e31e06 100644 --- a/projects/design-angular-kit/src/lib/design-angular-kit.module.ts +++ b/projects/design-angular-kit/src/lib/design-angular-kit.module.ts @@ -10,6 +10,8 @@ import { TooltipDirective } from './tooltip/tooltip.directive'; import { TooltipComponent } from './tooltip/tooltip.component'; import { ButtonComponent } from './button/button.component'; import { BadgeDirective } from './badge/badge.directive'; +import { TabGroupComponent } from './tabs/tab-group.component'; +import { TabComponent } from './tabs/tab.component'; @NgModule({ imports: [ @@ -22,6 +24,8 @@ import { BadgeDirective } from './badge/badge.directive'; RadioGroupDirective, RadioButtonComponent, BadgeDirective, + TabGroupComponent, + TabComponent, ProgressBarComponent, ButtonComponent, TooltipDirective, @@ -33,6 +37,8 @@ import { BadgeDirective } from './badge/badge.directive'; RadioGroupDirective, RadioButtonComponent, BadgeDirective, + TabGroupComponent, + TabComponent, ProgressBarComponent, ButtonComponent, TooltipDirective diff --git a/projects/design-angular-kit/src/lib/tabs/tab-group.component.html b/projects/design-angular-kit/src/lib/tabs/tab-group.component.html new file mode 100644 index 00000000..9545b3e4 --- /dev/null +++ b/projects/design-angular-kit/src/lib/tabs/tab-group.component.html @@ -0,0 +1,29 @@ + +
+ +
+ +
+
+
diff --git a/projects/design-angular-kit/src/lib/tabs/tab-group.component.scss b/projects/design-angular-kit/src/lib/tabs/tab-group.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/projects/design-angular-kit/src/lib/tabs/tab-group.component.spec.ts b/projects/design-angular-kit/src/lib/tabs/tab-group.component.spec.ts new file mode 100644 index 00000000..182a966f --- /dev/null +++ b/projects/design-angular-kit/src/lib/tabs/tab-group.component.spec.ts @@ -0,0 +1,548 @@ +import { ComponentFixture, TestBed, fakeAsync, tick, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { Component, QueryList, ViewChildren, OnInit, ViewChild } from '@angular/core'; + +import { TabGroupComponent } from './tab-group.component'; +import { TabComponent } from './tab.component'; +import { Observable } from 'rxjs'; + + +@Component({ + template: ` + + + Tab one content + + + Tab twocontent + + + Tab three content + + + ` +}) +class SimpleTabsComponent { + @ViewChildren(TabComponent) tabs: QueryList; + selectedIndex = 1; + focusEvent: any; + selectEvent: any; + handleSelection(event: any) { + this.selectEvent = event; + } +} + +@Component({ + template: ` + + + + ` +}) +class TabGroupWithAriaInputsComponent { + ariaLabel: string; + ariaLabelledby: string; +} + +@Component({ + template: ` + + + Tab one content + + + Tab two content + + + Tab three content + + + `, +}) +class DisabledTabsComponent { + @ViewChildren(TabComponent) tabs: QueryList; + isDisabled = false; +} + +@Component({ + template: ` + + + {{tab.content}} + + + ` +}) +class DynamicTabsComponent { + tabs = [ + {label: 'Label 1', content: 'Content 1'}, + {label: 'Label 2', content: 'Content 2'}, + {label: 'Label 3', content: 'Content 3'}, + ]; + selectedIndex = 1; + selectEvent: any; + handleSelection(event: any) { + this.selectEvent = event; + } +} + +@Component({ + template: ` + + + {{ tab.content }} + + + ` +}) +class AsyncTabsComponent implements OnInit { + private _tabs = [ + { label: 'one', content: 'one' }, + { label: 'two', content: 'two' } + ]; + + tabs: Observable; + + ngOnInit() { + this.tabs = Observable.create((observer: any) => { + setTimeout(() => observer.next(this._tabs)); + }); + } +} + +@Component({ + template: ` + + Pizza, fries + Broccoli, spinach + {{otherContent}} +

Peanuts

+
+ ` +}) +class TabGroupWithSimpleApiComponent { + otherLabel = 'Fruit'; + otherContent = 'Apples, grapes'; + @ViewChild('legumes') legumes: any; +} + +describe('TabGroupComponent', () => { + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + declarations: [ + SimpleTabsComponent, + TabGroupWithAriaInputsComponent, + DisabledTabsComponent, + DynamicTabsComponent, + AsyncTabsComponent, + // BindedTabsTestApp, + TabGroupWithSimpleApiComponent, + TabGroupComponent, + TabComponent + ], + }); + + TestBed.compileComponents(); + })); + + describe('comportamenti di base', () => { + let fixture: ComponentFixture; + let element: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(SimpleTabsComponent); + element = fixture.nativeElement; + }); + + it('dovrebbe selezionare di la seconda tab all\'inizio', () => { + checkSelectedIndex(1, fixture); + }); + + it('dovrebbe caricare correttamente il contenuto dopo la prima change detection', () => { + fixture.detectChanges(); + expect(element.querySelectorAll('.tab-pane')[1].querySelectorAll('span').length).toBe(3); + }); + + it('dovrebbe cambiare l\'indice selezionato al click', () => { + const component = fixture.debugElement.componentInstance; + component.selectedIndex = 0; + checkSelectedIndex(0, fixture); + + // seleziona il secondo tab + let tabLabel = fixture.debugElement.queryAll(By.css('.nav-link'))[1]; + tabLabel.nativeElement.click(); + checkSelectedIndex(1, fixture); + + // seleziona il terzo tab + tabLabel = fixture.debugElement.queryAll(By.css('.nav-link'))[2]; + tabLabel.nativeElement.click(); + checkSelectedIndex(2, fixture); + }); + + it('dovrebbe supportare il binding bidirezionale per selectedIndex', fakeAsync(() => { + const component = fixture.componentInstance; + component.selectedIndex = 0; + + fixture.detectChanges(); + + const tabLabel = fixture.debugElement.queryAll(By.css('.nav-link'))[1]; + tabLabel.nativeElement.click(); + fixture.detectChanges(); + tick(); + + expect(component.selectedIndex).toBe(1); + })); + + it('dovrebbe selezionare la giusta tab anche se il cambiamento è molto veloce', async(() => { + const component = fixture.componentInstance; + component.selectedIndex = 0; + fixture.detectChanges(); + + setTimeout(() => { + component.selectedIndex = 1; + fixture.detectChanges(); + + setTimeout(() => { + component.selectedIndex = 0; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(component.selectedIndex).toBe(0); + }); + }, 1); + }, 1); + })); + + it('dovrebbe cambiare il tab basandosi sul selectedIndex', fakeAsync(() => { + const component = fixture.componentInstance; + const tabComponent = fixture.debugElement.query(By.css('it-tab-group')).componentInstance; + + spyOn(component, 'handleSelection').and.callThrough(); + + checkSelectedIndex(1, fixture); + + tabComponent.selectedIndex = 2; + + checkSelectedIndex(2, fixture); + tick(); + + expect(component.handleSelection).toHaveBeenCalledTimes(1); + expect(component.selectEvent.index).toBe(2); + })); + + it('dovrebbe aggiornare la posizione del tab quando l\'indice selezionato cambia', () => { + fixture.detectChanges(); + const component: TabGroupComponent = + fixture.debugElement.query(By.css('it-tab-group')).componentInstance; + const tabs: TabComponent[] = component._tabs.toArray(); + + expect(tabs[0].position).toBeLessThan(0); + expect(tabs[1].position).toBe(0); + expect(tabs[2].position).toBeGreaterThan(0); + + // spostati sulla terza tab + component.selectedIndex = 2; + fixture.detectChanges(); + expect(tabs[0].position).toBeLessThan(0); + expect(tabs[1].position).toBeLessThan(0); + expect(tabs[2].position).toBe(0); + + // spostati sulla terza tab + component.selectedIndex = 0; + fixture.detectChanges(); + expect(tabs[0].position).toBe(0); + expect(tabs[1].position).toBeGreaterThan(0); + expect(tabs[2].position).toBeGreaterThan(0); + }); + + it('dovrebbe bloccare l\'indice selezionato al numero di tab', () => { + fixture.detectChanges(); + const component: TabGroupComponent = + fixture.debugElement.query(By.css('it-tab-group')).componentInstance; + + // Imposto l'indice ad un valore negativo, mi aspetto il primo tab selezionato + fixture.componentInstance.selectedIndex = -1; + fixture.detectChanges(); + expect(component.selectedIndex).toBe(0); + + // Imposto l'indice ad un valore superiore al numero di tab, mi aspetto l'ultimo tab selezionato + fixture.componentInstance.selectedIndex = 3; + fixture.detectChanges(); + expect(component.selectedIndex).toBe(2); + }); + + it('non dovrebbe crashare se l\'indice selezionato assume il valore di NaN', () => { + const component = fixture.debugElement.componentInstance; + + expect(() => { + component.selectedIndex = NaN; + fixture.detectChanges(); + }).not.toThrow(); + }); + + it('dovrebbe impostare la proprietà isActive correttamente su ognuno dei tab', () => { + fixture.detectChanges(); + + const tabs = fixture.componentInstance.tabs.toArray(); + + expect(tabs[0].isActive).toBe(false); + expect(tabs[1].isActive).toBe(true); + expect(tabs[2].isActive).toBe(false); + + fixture.componentInstance.selectedIndex = 2; + fixture.detectChanges(); + + expect(tabs[0].isActive).toBe(false); + expect(tabs[1].isActive).toBe(false); + expect(tabs[2].isActive).toBe(true); + }); + }); + + describe('aria label', () => { + let fixture: ComponentFixture; + let tab: HTMLElement; + + beforeEach(fakeAsync(() => { + fixture = TestBed.createComponent(TabGroupWithAriaInputsComponent); + fixture.detectChanges(); + tick(); + tab = fixture.nativeElement.querySelector('.nav-link'); + })); + + it('non dovrebbe impostare gli attributi aria-label o aria-labelledby se non sono passati', () => { + expect(tab.hasAttribute('aria-label')).toBe(false); + expect(tab.hasAttribute('aria-labelledby')).toBe(false); + }); + + it('dovrebbe impostare l\'attributo aria-label', () => { + fixture.componentInstance.ariaLabel = 'Fruit'; + fixture.detectChanges(); + + expect(tab.getAttribute('aria-label')).toBe('Fruit'); + }); + + it('dovrebbe impostare l\'attributo aria-labelledby', () => { + fixture.componentInstance.ariaLabelledby = 'fruit-label'; + fixture.detectChanges(); + + expect(tab.getAttribute('aria-labelledby')).toBe('fruit-label'); + }); + + it('non dovrebbe impostare entrambi aria-label e aria-labelledby', () => { + fixture.componentInstance.ariaLabel = 'Fruit'; + fixture.componentInstance.ariaLabelledby = 'fruit-label'; + fixture.detectChanges(); + + expect(tab.getAttribute('aria-label')).toBe('Fruit'); + expect(tab.hasAttribute('aria-labelledby')).toBe(false); + }); + }); + + describe('tab disabilitati', () => { + let fixture: ComponentFixture; + beforeEach(() => { + fixture = TestBed.createComponent(DisabledTabsComponent); + }); + + it('dovrebbe avere un tab disabilitato', () => { + fixture.detectChanges(); + const labels = fixture.debugElement.queryAll(By.css('.disabled')); + expect(labels.length).toBe(1); + }); + + it('dovrebbe disabilitare la tab', () => { + fixture.detectChanges(); + + const tabs = fixture.componentInstance.tabs.toArray(); + let labels = fixture.debugElement.queryAll(By.css('.disabled')); + expect(tabs[2].disabled).toBe(false); + expect(labels.length).toBe(1); + + fixture.componentInstance.isDisabled = true; + fixture.detectChanges(); + + expect(tabs[2].disabled).toBe(true); + labels = fixture.debugElement.queryAll(By.css('.disabled')); + expect(labels.length).toBe(2); + }); + }); + + describe('tab bindate dinamicamente', () => { + let fixture: ComponentFixture; + + beforeEach(fakeAsync(() => { + fixture = TestBed.createComponent(DynamicTabsComponent); + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + })); + + it('dovrebbe aggiornare l\'indice selezionato se l\'ultima tab viene rimossa mentre è selezionata', fakeAsync(() => { + const component: TabGroupComponent = + fixture.debugElement.query(By.css('it-tab-group')).componentInstance; + + const numberOfTabs = component._tabs.length; + fixture.componentInstance.selectedIndex = numberOfTabs - 1; + fixture.detectChanges(); + tick(); + + // Rimuovi l'ultimo tab mentre è selezionato, mi aspetto che sia selezionato quello immediatamente precedente + fixture.componentInstance.tabs.pop(); + fixture.detectChanges(); + tick(); + + expect(component.selectedIndex).toBe(numberOfTabs - 2); + })); + + it('dovrebbe mantenere il tab selezionato se un uovo tab viene aggiunto', () => { + fixture.detectChanges(); + const component: TabGroupComponent = + fixture.debugElement.query(By.css('it-tab-group')).componentInstance; + + fixture.componentInstance.selectedIndex = 1; + fixture.detectChanges(); + + // aggiungi un nuovo tab all'inizio. + fixture.componentInstance.tabs.unshift({label: 'Nuovo tab', content: 'all\'inizio'}); + fixture.detectChanges(); + + expect(component.selectedIndex).toBe(2); + expect(component._tabs.toArray()[2].isActive).toBe(true); + }); + + it('dovrebbe mantenere il tab selezionato se un tab viene rimosso', () => { + // Seleziona il secondo tab. + fixture.componentInstance.selectedIndex = 1; + fixture.detectChanges(); + + const component: TabGroupComponent = + fixture.debugElement.query(By.css('it-tab-group')).componentInstance; + + // Rimuovi il primo tab che si trova a destra di quello selezionato. + fixture.componentInstance.tabs.splice(0, 1); + fixture.detectChanges(); + + // Dal momento che il primo tab è stato rimosso e il secondo era stato precedentemente selezionato, + // il tab selezionato si sarà spostato di una posizione a destra, essendo ora il primo tab. + expect(component.selectedIndex).toBe(0); + expect(component._tabs.toArray()[0].isActive).toBe(true); + }); + + it('dovrebbe poter selezionare un nuovo tab dopo la sua creazione', () => { + fixture.detectChanges(); + const component: TabGroupComponent = + fixture.debugElement.query(By.css('it-tab-group')).componentInstance; + + fixture.componentInstance.tabs.push({label: 'Ultimo tab', content: 'alla fine'}); + fixture.componentInstance.selectedIndex = 3; + + fixture.detectChanges(); + + expect(component.selectedIndex).toBe(3); + expect(component._tabs.toArray()[3].isActive).toBe(true); + }); + + it('non dovrebbe emettere l\'evento `selectedTabChange` quando il numero di tab cambia', fakeAsync(() => { + fixture.detectChanges(); + fixture.componentInstance.selectedIndex = 1; + fixture.detectChanges(); + + // Aggiungi un nuovo tabb all'inizio. + spyOn(fixture.componentInstance, 'handleSelection'); + fixture.componentInstance.tabs.unshift({label: 'Nuovo tab', content: 'all\'inizio'}); + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + + expect(fixture.componentInstance.handleSelection).not.toHaveBeenCalled(); + })); + }); + + describe('async tabs', () => { + let fixture: ComponentFixture; + + it('dovrebbe mostrare i tab quando questi sono disponibili', fakeAsync(() => { + fixture = TestBed.createComponent(AsyncTabsComponent); + + expect(fixture.debugElement.queryAll(By.css('.nav-link')).length).toBe(0); + + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + tick(); + + expect(fixture.debugElement.queryAll(By.css('.nav-link')).length).toBe(2); + })); + }); + + describe('Tab API', () => { + let fixture: ComponentFixture; + let tabGroup: TabGroupComponent; + + beforeEach(fakeAsync(() => { + fixture = TestBed.createComponent(TabGroupWithSimpleApiComponent); + fixture.detectChanges(); + tick(); + + tabGroup = + fixture.debugElement.query(By.directive(TabGroupComponent)).componentInstance as TabGroupComponent; + })); + + it('i tab dovrebbero avere i giusti contenuti', fakeAsync(() => { + expect(getSelectedLabel(fixture).textContent).toMatch('Junk food'); + expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries'); + + tabGroup.selectedIndex = 2; + fixture.detectChanges(); + tick(); + + expect(getSelectedLabel(fixture).textContent).toMatch('Fruit'); + expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes'); + + fixture.componentInstance.otherLabel = 'Chips'; + fixture.componentInstance.otherContent = 'Salt, vinegar'; + fixture.detectChanges(); + + expect(getSelectedLabel(fixture).textContent).toMatch('Chips'); + expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar'); + })); + + it('dovrebbe supportare @ViewChild nel contenuto dei tab', () => { + expect(fixture.componentInstance.legumes).toBeTruthy(); + }); + }); + + /** + * controlla che `selectedIndex` è stato aggiornato; controlla che il label e il corpo hanno + * le rispettive classi `active` + */ + function checkSelectedIndex(expectedIndex: number, fixture: ComponentFixture) { + fixture.detectChanges(); + + const tabComponent: TabGroupComponent = fixture.debugElement + .query(By.css('it-tab-group')).componentInstance; + + expect(tabComponent.selectedIndex).toBe(expectedIndex); + + const tabLabelElement = fixture.debugElement + .query(By.css(`.nav-item:nth-of-type(${expectedIndex + 1}) .nav-link`)).nativeElement; + expect(tabLabelElement.classList.contains('active')).toBe(true); + + const tabContentElement = fixture.debugElement + .query(By.css(`.tab-pane:nth-of-type(${expectedIndex + 1})`)).nativeElement; + expect(tabContentElement.classList.contains('active')).toBe(true); + } + + function getSelectedLabel(fixture: ComponentFixture): HTMLElement { + return fixture.nativeElement.querySelector('.nav-link.active'); + } + + function getSelectedContent(fixture: ComponentFixture): HTMLElement { + return fixture.nativeElement.querySelector('.tab-pane.active'); + } + +}); diff --git a/projects/design-angular-kit/src/lib/tabs/tab-group.component.ts b/projects/design-angular-kit/src/lib/tabs/tab-group.component.ts new file mode 100644 index 00000000..26f21ed9 --- /dev/null +++ b/projects/design-angular-kit/src/lib/tabs/tab-group.component.ts @@ -0,0 +1,223 @@ +import { + Component, + AfterContentInit, + ContentChildren, + QueryList, + ViewEncapsulation, + ChangeDetectionStrategy, + Input, + Output, + EventEmitter, + ChangeDetectorRef, + AfterContentChecked, + OnDestroy +} from '@angular/core'; +import { Subscription, merge } from 'rxjs'; +import { Util } from '../util/util'; +import { TabComponent } from './tab.component'; + +/** Usato per generare ID univoci per ogni componente tab */ +let nextId = 0; + +/** Un change event emesso ai cambi di selezione. */ +export class TabChangeEvent { + /** Indice del tab selezionato. */ + index: number; + /** Riferimento al tab selezionato. */ + tab: TabComponent; +} + + /** + * Un componente tab-group con design bootstrap italia. Utilizzabile con il tag ``. + * + * Supporta al suo interno tab di base `` con una label e un contenuto. + */ +@Component({ + selector: 'it-tab-group', + exportAs: 'itTabGroup', + templateUrl: './tab-group.component.html', + styleUrls: ['./tab-group.component.scss'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TabGroupComponent implements AfterContentInit, AfterContentChecked, OnDestroy { + + @ContentChildren(TabComponent) _tabs: QueryList; + + /** L'indice del tab che dovrebbe essere selezionato dopo che il contenuto e' stato controllato */ + private _indexToSelect: number | null = 0; + + /** Subscription all'aggiunta e rimozione di tab. */ + private _tabsSubscription = Subscription.EMPTY; + + /** Subscription ai cambiamenti alle label dei tab. */ + private _tabLabelSubscription = Subscription.EMPTY; + + /** Se le tab sono formattate come pill. */ + @Input() + get pill(): boolean { return this._isPill; } + set pill(value) { + this._isPill = Util.coerceBooleanProperty(value); + } + private _isPill = false; + + + /** L'indice della tab attiva. */ + @Input() + get selectedIndex(): number | null { return this._selectedIndex; } + set selectedIndex(value: number | null) { + this._indexToSelect = Util.coerceNumberProperty(value, null); + } + private _selectedIndex: number | null = null; + + /** + * Se il tab-group ha un tema scuro + */ + @Input() + get dark(): boolean { return this._dark; } + set dark(value) { + this._dark = Util.coerceBooleanProperty(value); + } + private _dark = false; + + /** abilita il supporto al data-binding bidirezionale `[(selectedIndex)]` */ + @Output() readonly selectedIndexChange: EventEmitter = new EventEmitter(); + + /** emesso quando la selezione del tab cambia. */ + @Output() readonly selectedTabChange: EventEmitter = + new EventEmitter(true); + + private _groupId: number; + + constructor(private _changeDetectorRef: ChangeDetectorRef) { + this._groupId = nextId++; + } + + /** + * Dopo che il contenuto è controllato, il componente conosce i tab che sono stati definiti + * e qual è l'indice del tab selezionato. + */ + ngAfterContentChecked(): void { + this.changeTab(this._indexToSelect); + } + + changeTab(newIndex: number): void { + + // Non fissare `indexToSelect` immediatamente nel setter perchè può accadere che + // il numero di tab cambi prima che avvenga la change detection. + const indexToSelect = this._indexToSelect = this._clampTabIndex(newIndex); + + // Se il nuovo tab è disabilitato, non fare niente + if (this._tabs && this._tabs.length > 0 && this._tabs.toArray()[indexToSelect].disabled) { + return; + } + + if (this._selectedIndex !== indexToSelect && this._selectedIndex != null) { + const tabChangeEvent = this._createChangeEvent(indexToSelect); + this.selectedTabChange.emit(tabChangeEvent); + // Emetto questo valore dopo che è partita la change detection + // dal momento che il contenuto controllato potrebbe contenere questa variabile + Promise.resolve().then(() => this.selectedIndexChange.emit(indexToSelect)); + } + + // Setta la posizione per ogni tab. + this._tabs.forEach((tab: TabComponent, index: number) => { + tab.position = index - indexToSelect; + tab.isActive = index === indexToSelect; + }); + + if (this._selectedIndex !== indexToSelect) { + this._selectedIndex = indexToSelect; + this._changeDetectorRef.markForCheck(); + } + } + + ngAfterContentInit(): void { + this._subscribeToTabLabels(); + + // Sottoscrivi al cambiamento nel numero di tab, così da + // poter ri-renderizzare il contenuto quando nuove tab vengono aggiunte o rimosse. + this._tabsSubscription = this._tabs.changes.subscribe(() => { + const indexToSelect = this._clampTabIndex(this._indexToSelect); + + // Mantieni il tab selezionato precedentemente se un nuovo tab è aggiunto o rimosso e non ci sono + // cambiamenti espliciti che selezionino un tab differente. + if (indexToSelect === this._selectedIndex) { + const tabs = this._tabs.toArray(); + + for (let i = 0; i < tabs.length; i++) { + if (tabs[i].isActive) { + // Assegna `_indexToSelect` e `_selectedIndex` in modo da non emettere un change event + // per evitare al consumer loop infiniti in alcuni casi limite come ad esempio + // se si aggiunge un tab all'interno dell'evento `selectedIndexChange`. + this._indexToSelect = this._selectedIndex = i; + break; + } + } + } + + this._subscribeToTabLabels(); + this._changeDetectorRef.markForCheck(); + }); + } + + ngOnDestroy(): void { + this._tabsSubscription.unsubscribe(); + this._tabLabelSubscription.unsubscribe(); + } + + private _createChangeEvent(index: number): TabChangeEvent { + const event = new TabChangeEvent; + event.index = index; + if (this._tabs && this._tabs.length) { + event.tab = this._tabs.toArray()[index]; + } + return event; + } + + /** + * Sottoscrivi a cambiamenti nelle label dei tab. Necessario perchè l'input per la label è sul TabComponent + * mentre il data binding è all'interno di TabGroupComponent. Per fare in modo che il binding sia aggiornato + * bisogna sottoscriversi ai cambiamenti e azionare la change detection in maniera manuale. + */ + private _subscribeToTabLabels() { + if (this._tabLabelSubscription) { + this._tabLabelSubscription.unsubscribe(); + } + + this._tabLabelSubscription = merge( + ...this._tabs.map(tab => tab._disableChange), + ...this._tabs.map(tab => tab._labelChange)).subscribe(() => { + this._changeDetectorRef.markForCheck(); + }); + } + + /** fissa l'indice tra 0 e la dimensione dei tab. */ + private _clampTabIndex(index: number | null): number { + return Math.min(this._tabs.length - 1, Math.max(index || 0, 0)); + } + + /** ritorna un id univoco per ogni label di tab */ + _getTabLabelId(i: number): string { + return `it-tab-label-${this._groupId}-${i}`; + } + + /** ritorna un id univoco per ogni elemento di contenuto del tab */ + _getTabContentId(i: number): string { + return `it-tab-content-${this._groupId}-${i}`; + } + + /** restituisce il tabIndex del tab. */ + _getTabIndex(tab: TabComponent, idx: number): number | null { + if (tab.disabled) { + return null; + } + return this.selectedIndex === idx ? 0 : -1; + } + + _handleClick($event: Event, index: number): void { + $event.preventDefault(); + this.changeTab(index); + } + +} diff --git a/projects/design-angular-kit/src/lib/tabs/tab.component.html b/projects/design-angular-kit/src/lib/tabs/tab.component.html new file mode 100644 index 00000000..cd48c06b --- /dev/null +++ b/projects/design-angular-kit/src/lib/tabs/tab.component.html @@ -0,0 +1 @@ + diff --git a/projects/design-angular-kit/src/lib/tabs/tab.component.scss b/projects/design-angular-kit/src/lib/tabs/tab.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/projects/design-angular-kit/src/lib/tabs/tab.component.ts b/projects/design-angular-kit/src/lib/tabs/tab.component.ts new file mode 100644 index 00000000..9013bf90 --- /dev/null +++ b/projects/design-angular-kit/src/lib/tabs/tab.component.ts @@ -0,0 +1,90 @@ +import { + Component, + OnDestroy, + OnChanges, + Input, + ChangeDetectionStrategy, + ViewEncapsulation, + SimpleChanges, + TemplateRef, + ViewChild +} from '@angular/core'; +import { Subject } from 'rxjs'; +import { Util } from '../util/util'; + +/** + * Un componente tab con design bootstrap italia. Indica la singola tab di un insieme di tab. + * Utilizzabile con il tag `` all'interno di un tag ``. + */ +@Component({ + selector: 'it-tab', + exportAs: 'itTab', + templateUrl: './tab.component.html', + styleUrls: ['./tab.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, +}) +export class TabComponent implements OnChanges, OnDestroy { + + /** Testo della tab. */ + @Input() label: string = ''; // tslint:disable-line + + /** Aria label del tab. */ + @Input('aria-label') ariaLabel: string; // tslint:disable-line + + /** + * Riferimento all'elemento dal quale il tab è etichettato. + * Viene resettato se `aria-label` è impostato. + */ + @Input('aria-labelledby') ariaLabelledby: string; // tslint:disable-line + + /** Se la tab è disabilitata. */ + @Input() + get disabled(): boolean { return this._disabled; } + set disabled(value) { + this._disabled = Util.coerceBooleanProperty(value); + } + private _disabled = false; + + /** + * La stringa rappresentante l'icona da utilizzare nel titolo della tab. Es. `it-file` + */ + @Input() icon: string | null = null; + + /** Emette un evento ogni volta che la label cambia. */ + readonly _labelChange = new Subject(); + + /** Emette un evento ogni volta che l'attributo disabled cambia */ + readonly _disableChange = new Subject(); + + /** + * La posizione relativa della tab dove 0 rappresenta il centro, i negativi sono a sinistra + * e i positivi sono a destra. + */ + position: number | null = null; + + /** + * Se il tab è attivo. + */ + isActive = false; + + @ViewChild(TemplateRef) _implicitContent: TemplateRef; + + ngOnDestroy(): void { + this._disableChange.complete(); + this._labelChange.complete(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.hasOwnProperty('label') + || changes.hasOwnProperty('ariaLabel') + || changes.hasOwnProperty('ariaLabelledby')) { + this._labelChange.next(); + } + + if (changes.hasOwnProperty('disabled')) { + this._disableChange.next(); + } + } + +} diff --git a/projects/design-angular-kit/src/lib/util/util.ts b/projects/design-angular-kit/src/lib/util/util.ts index cc53d481..70389396 100644 --- a/projects/design-angular-kit/src/lib/util/util.ts +++ b/projects/design-angular-kit/src/lib/util/util.ts @@ -2,4 +2,13 @@ export class Util { static coerceBooleanProperty(value: any): boolean { return value != null && `${value}` !== 'false'; } + + static coerceNumberProperty(value: any, fallbackValue = 0): number { + return Util._isNumberValue(value) ? Number(value) : fallbackValue; + } + + static _isNumberValue(value: any): boolean { + return !isNaN(parseFloat(value as any)) && !isNaN(Number(value)); + + } } diff --git a/projects/design-angular-kit/src/public_api.ts b/projects/design-angular-kit/src/public_api.ts index 84896be3..22a27674 100644 --- a/projects/design-angular-kit/src/public_api.ts +++ b/projects/design-angular-kit/src/public_api.ts @@ -10,4 +10,5 @@ export * from './lib/tooltip/tooltip.directive'; export * from './lib/tooltip/tooltip.config'; export * from './lib/button/button.component'; export * from './lib/badge/badge.directive'; +export * from './lib/tabs/tab-group.component'; export * from './lib/design-angular-kit.module'; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 3e958fbd..9f8d81ea 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -14,6 +14,7 @@ const routes: Routes = [ { path: 'progress-bar', loadChildren: 'src/app/progress-bar/progress-bar.module#ProgressBarModule' }, { path: 'toggle', loadChildren: 'src/app/toggle/toggle.module#ToggleModule' }, { path: 'radio', loadChildren: 'src/app/radio/radio.module#RadioModule' }, + { path: 'tabs', loadChildren: 'src/app/tabs/tabs.module#TabsModule' }, { path: 'tooltip', loadChildren: 'src/app/tooltip/tooltip.module#TooltipModule' }, { path: 'button', loadChildren: 'src/app/button/button.module#ButtonModule' }, { path: 'badge', loadChildren: 'src/app/badge/badge.module#BadgeModule' } diff --git a/src/app/table-of-content.service.ts b/src/app/table-of-content.service.ts index 8d21c45d..f133a9da 100644 --- a/src/app/table-of-content.service.ts +++ b/src/app/table-of-content.service.ts @@ -51,6 +51,10 @@ export class TableOfContentService { { label: 'Badge', link: '/componenti/badge', + }, + { + label: 'Tabs', + link: '/componenti/tabs', } ] } diff --git a/src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.html b/src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.html new file mode 100644 index 00000000..4b6a126c --- /dev/null +++ b/src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.html @@ -0,0 +1,35 @@ +
+
+

Configurazione tabs

+
+ indice del tab selezionato: + +
+
+ + Aggiungi un nuovo tab + + +
+
+
+ +
+
+

Risultato tabs

+ + + contenuto del {{tab}} tab + + + Cancella tab + + + +
+
diff --git a/src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.scss b/src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.ts b/src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.ts new file mode 100644 index 00000000..75ab1c09 --- /dev/null +++ b/src/app/tabs/tabs-dynamic-example/tabs-dynamic-example.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; +import { FormControl } from '@angular/forms'; + +@Component({ + selector: 'it-tabs-dynamic-example', + templateUrl: './tabs-dynamic-example.component.html', + styleUrls: ['./tabs-dynamic-example.component.scss'] +}) +export class TabsDynamicExampleComponent { + + tabs = ['Primo', 'Secondo', 'Terzo']; + selected = new FormControl(0); + selectAfterAdding = false; + + addTab(selectAfterAdding: boolean) { + this.tabs.push('Nuovo'); + + if (selectAfterAdding) { + this.selected.setValue(this.tabs.length - 1); + } + } + + removeTab(index: number) { + this.tabs.splice(index, 1); + } +} diff --git a/src/app/tabs/tabs-example/tabs-example.component.html b/src/app/tabs/tabs-example/tabs-example.component.html new file mode 100644 index 00000000..675cdd6d --- /dev/null +++ b/src/app/tabs/tabs-example/tabs-example.component.html @@ -0,0 +1,29 @@ +
+
+

Configurazione tabs

+

+ +

+

+ +

+

+ +

+
+
+ +
+
+

Risultato tabs

+ + + {{tab.content}} + + +

Titolo

+

Contenuto complesso

+
+
+
+
diff --git a/src/app/tabs/tabs-example/tabs-example.component.scss b/src/app/tabs/tabs-example/tabs-example.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/tabs/tabs-example/tabs-example.component.ts b/src/app/tabs/tabs-example/tabs-example.component.ts new file mode 100644 index 00000000..0bae27e2 --- /dev/null +++ b/src/app/tabs/tabs-example/tabs-example.component.ts @@ -0,0 +1,33 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'it-tabs-example', + templateUrl: './tabs-example.component.html', + styleUrls: ['./tabs-example.component.scss'] +}) +export class TabsExampleComponent { + + isDarkTheme = false; + + isDisabled = false; + + isPill = false; + + tabs = [ + { + label: 'tab1', + content: 'content1', + icon: 'it-file' + }, + { + label: 'tab2', + content: 'content2', + icon: 'it-calendar' + }, + { + label: 'tab3', + content: 'content3', + icon: 'it-comment' + } + ]; +} diff --git a/src/app/tabs/tabs-examples/tabs-examples.component.scss b/src/app/tabs/tabs-examples/tabs-examples.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/tabs/tabs-examples/tabs-examples.component.tpl b/src/app/tabs/tabs-examples/tabs-examples.component.tpl new file mode 100644 index 00000000..85d8bfa5 --- /dev/null +++ b/src/app/tabs/tabs-examples/tabs-examples.component.tpl @@ -0,0 +1,25 @@ +{% set html %} + {% include "../tabs-example/tabs-example.component.html" %} +{% endset %} + +{% set typescript %} + {% include "../tabs-example/tabs-example.component.ts" %} +{% endset %} + +{% set htmlDynamic %} + {% include "../tabs-dynamic-example/tabs-dynamic-example.component.html" %} +{% endset %} + +{% set typescriptDynamic %} + {% include "../tabs-dynamic-example/tabs-dynamic-example.component.ts" %} +{% endset %} + + + + + + + + + + diff --git a/src/app/tabs/tabs-examples/tabs-examples.component.ts b/src/app/tabs/tabs-examples/tabs-examples.component.ts new file mode 100644 index 00000000..02472f55 --- /dev/null +++ b/src/app/tabs/tabs-examples/tabs-examples.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'it-tabs-examples', + templateUrl: './tabs-examples.component.html', + styleUrls: ['./tabs-examples.component.scss'] +}) +export class TabsExamplesComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/tabs/tabs-index/tabs-index.component.html b/src/app/tabs/tabs-index/tabs-index.component.html new file mode 100644 index 00000000..a02e036e --- /dev/null +++ b/src/app/tabs/tabs-index/tabs-index.component.html @@ -0,0 +1,82 @@ +

Tabs

+
Il componente Tabs
+ + +
+
+

Tab Group

+
+

Tab

+
+
+
+

Tab Group

+
+

Input

+
+ + + + + + + + + + +
+ {{input.name}} + +

+ Tipo: + {{input.type}} +

+
+
+
+
+

Output

+
+ +

Tab

+
+

Input

+
+ + + + + + + + + + +
+ {{input.name}} + +

+ Tipo: + {{input.type}} +

+
+
+
+
+

Output

+
+
+
+ +
+
diff --git a/src/app/tabs/tabs-index/tabs-index.component.scss b/src/app/tabs/tabs-index/tabs-index.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/tabs/tabs-index/tabs-index.component.ts b/src/app/tabs/tabs-index/tabs-index.component.ts new file mode 100644 index 00000000..8f8a63e0 --- /dev/null +++ b/src/app/tabs/tabs-index/tabs-index.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit } from '@angular/core'; +import * as Documentation from '../../../assets/documentation.json'; + +@Component({ + selector: 'it-tabs-index', + templateUrl: './tabs-index.component.html', + styleUrls: ['./tabs-index.component.scss'] +}) +export class TabsIndexComponent { + + tabGroupComponent: any; + tabComponent: any; + + constructor() { + this.tabGroupComponent = (Documentation).components.find(component => component.name === 'TabGroupComponent'); + this.tabComponent = (Documentation).components.find(component => component.name === 'TabComponent'); + } + +} diff --git a/src/app/tabs/tabs-routing.module.ts b/src/app/tabs/tabs-routing.module.ts new file mode 100644 index 00000000..96b43121 --- /dev/null +++ b/src/app/tabs/tabs-routing.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { TabsIndexComponent } from './tabs-index/tabs-index.component'; + +const routes: Routes = [ + { path: '', component: TabsIndexComponent} +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class TabsRoutingModule { } diff --git a/src/app/tabs/tabs.module.ts b/src/app/tabs/tabs.module.ts new file mode 100644 index 00000000..7fe3a227 --- /dev/null +++ b/src/app/tabs/tabs.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; + +import { DesignAngularKitModule } from 'projects/design-angular-kit/src/public_api'; + +import { SharedModule } from '../shared/shared.module'; + +import { TabsRoutingModule } from './tabs-routing.module'; +import { TabsExampleComponent } from './tabs-example/tabs-example.component'; +import { TabsExamplesComponent } from './tabs-examples/tabs-examples.component'; +import { TabsIndexComponent } from './tabs-index/tabs-index.component'; +import { TabsDynamicExampleComponent } from './tabs-dynamic-example/tabs-dynamic-example.component'; + + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + DesignAngularKitModule, + SharedModule, + TabsRoutingModule + ], + declarations: [TabsExampleComponent, TabsExamplesComponent, TabsIndexComponent, TabsDynamicExampleComponent] +}) +export class TabsModule { }