From 704ba0e9c00eda086f2b0944b3a196c3f134639e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Ricardo=20Cando=20Obaco?= Date: Thu, 9 Sep 2021 15:31:53 -0500 Subject: [PATCH 01/10] fix: TT-334 keep the sidebar item selected when the page is refreshed (#748) --- .../modules/shared/components/sidebar/sidebar.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.ts b/src/app/modules/shared/components/sidebar/sidebar.component.ts index 6d733d0fc..6e579bc72 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.ts +++ b/src/app/modules/shared/components/sidebar/sidebar.component.ts @@ -26,12 +26,13 @@ export class SidebarComponent implements OnInit, OnDestroy { ngOnInit(): void { this.toggleSideBar(); - this.sidebarItems$ = this.getSidebarItems().subscribe(); - this.highlightMenuOption(this.router.routerState.snapshot.url); + const currentRouting = this.router.routerState.snapshot.url; + this.sidebarItems$ = this.getSidebarItems().subscribe(() => this.highlightMenuOption(currentRouting)); this.navStart.subscribe((evt) => { this.highlightMenuOption(evt.url); }); } + ngOnDestroy(): void { this.sidebarItems$.unsubscribe(); } From 0125ac66c7343f48275df047e1cd6624d2f307e9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 9 Sep 2021 20:34:15 +0000 Subject: [PATCH 02/10] chore(release): 1.50.6 [skip ci]nn --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index ada2b395b..535cd77f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.50.5", + "version": "1.50.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 72af7e891..dbde1ba81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "time-tracker", - "version": "1.50.5", + "version": "1.50.6", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", From 27090505e78707cc4f20a9293d911c1302a26302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Ricardo=20Cando=20Obaco?= Date: Mon, 13 Sep 2021 10:53:19 -0500 Subject: [PATCH 03/10] feat: TT-332 Dark mode (#746) * style: TT-332 include Tailwind in global style file * feat: TT-332 dark mode implementation * style: TT-332 color palette * test: TT-332 unit test on the dark mode component * fix: TT-216 add tabIndex attribute to materialDatePicker and to Date divs (#747) * chore(release): 1.50.5 [skip ci]nn * refactor: TT-332 application of Azure toggle function to display dark mode * style: TT-332 TW prefix added to the color palette * refactor: TT-332 changes in sidebar and dark mode components * fix: TT-334 keep the sidebar item selected when the page is refreshed (#748) * chore(release): 1.50.6 [skip ci]nn * refactor: TT-332 changes to the button to change the page theme * test: TT-332 unit test to check if the user has the dark-mode feature toggle enabled * style: TT-332 include Tailwind in global style file * feat: TT-332 dark mode implementation * style: TT-332 color palette * test: TT-332 unit test on the dark mode component * refactor: TT-332 application of Azure toggle function to display dark mode * style: TT-332 TW prefix added to the color palette * refactor: TT-332 changes in sidebar and dark mode components * refactor: TT-332 changes to the button to change the page theme * test: TT-332 unit test to check if the user has the dark-mode feature toggle enabled * refactor: TT-332 changes in the unit test description of the dark mode component and style name of the sidebar component Co-authored-by: Santiago Pozo Ruiz <38196801+DrFreud1@users.noreply.github.com> Co-authored-by: semantic-release-bot --- src/app/app.module.ts | 2 + .../dark-mode/dark-mode.component.html | 10 ++ .../dark-mode/dark-mode.component.spec.ts | 96 ++++++++++++ .../dark-mode/dark-mode.component.ts | 65 ++++++++ .../components/sidebar/sidebar.component.html | 21 ++- .../components/sidebar/sidebar.component.scss | 7 + src/assets/icons/moon.svg | 8 + src/assets/icons/sun.svg | 7 + src/environments/enum.ts | 5 +- src/styles.scss | 4 + tailwind.config.js | 147 +++++++----------- 11 files changed, 273 insertions(+), 99 deletions(-) create mode 100644 src/app/modules/shared/components/dark-mode/dark-mode.component.html create mode 100644 src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts create mode 100644 src/app/modules/shared/components/dark-mode/dark-mode.component.ts create mode 100644 src/assets/icons/moon.svg create mode 100644 src/assets/icons/sun.svg diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2fb23cb82..4ac8a1693 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -86,6 +86,7 @@ import { TechnologyReportComponent } from './modules/technology-report/pages/tec import { CalendarComponent } from './modules/time-entries/components/calendar/calendar.component'; import { DropdownComponent } from './modules/shared/components/dropdown/dropdown.component'; import { NgSelectModule } from '@ng-select/ng-select'; +import { DarkModeComponent } from './modules/shared/components/dark-mode/dark-mode.component'; const maskConfig: Partial = { validation: false, @@ -140,6 +141,7 @@ const maskConfig: Partial = { TechnologyReportTableComponent, CalendarComponent, DropdownComponent, + DarkModeComponent, ], imports: [ NgxMaskModule.forRoot(maskConfig), diff --git a/src/app/modules/shared/components/dark-mode/dark-mode.component.html b/src/app/modules/shared/components/dark-mode/dark-mode.component.html new file mode 100644 index 000000000..df5a11423 --- /dev/null +++ b/src/app/modules/shared/components/dark-mode/dark-mode.component.html @@ -0,0 +1,10 @@ + diff --git a/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts b/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts new file mode 100644 index 000000000..900854ed9 --- /dev/null +++ b/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts @@ -0,0 +1,96 @@ +import { ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; +import { of } from 'rxjs'; +import { delay } from 'rxjs/operators'; +import { FeatureToggleGeneralService } from '../../feature-toggles/feature-toggle-general/feature-toggle-general.service'; +import { FeatureToggleModel } from '../../feature-toggles/feature-toggle.model'; +import { FeatureFilterModel } from '../../feature-toggles/filters/feature-filter.model'; +import { DarkModeComponent } from './dark-mode.component'; + +describe('DarkModeComponent', () => { + let component: DarkModeComponent; + let fixture: ComponentFixture; + let html: HTMLElement; + let featureToggleGeneralService: FeatureToggleGeneralService; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DarkModeComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DarkModeComponent); + component = fixture.componentInstance; + html = document.documentElement; + featureToggleGeneralService = TestBed.inject(FeatureToggleGeneralService); + fixture.detectChanges(); + }); + + afterEach(() => { + localStorage.removeItem('theme'); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); + + it('should be light the default theme', () => { + expect(component.theme).toEqual('light'); + }); + + it('should change the value of the theme property if it exists in the local storage', () => { + localStorage.setItem('theme', 'dark'); + component.checkThemeInLocalStorage(); + expect(component.theme).toEqual('dark'); + }); + + it('should be light theme if it does not exist in local storage', () => { + component.checkThemeInLocalStorage(); + expect(component.theme).toEqual('light'); + }); + + it('should be light mode if property ‘theme’ does not have a value in local storage', () => { + localStorage.setItem('theme', ''); + component.checkThemeInLocalStorage(); + expect(component.theme).toEqual('light'); + }); + + it('should switch to dark theme if the theme property is light and vice versa', () => { + component.theme = component.setTheme(); + expect(component.theme).toEqual('dark'); + component.theme = component.setTheme(); + expect(component.theme).toEqual('light'); + }); + + it('should add the dark class in the html tag to apply dark mode', () => { + component.theme = 'dark'; + component.addOrRemoveDarkMode(); + fixture.detectChanges(); + expect(html.classList.contains('dark')).toBe(true); + }); + + it('should not have dark class in the html tag when theme is light', () => { + component.addOrRemoveDarkMode(); + fixture.detectChanges(); + expect(component.theme).toEqual('light'); + expect(html.classList.contains('dark')).toBe(false); + }); + + it('should change the value of the theme property, save it in the local storage and add the dark class to the HTML tag to change the theme', () => { + component.changeToDarkOrLightTheme(); + fixture.detectChanges(); + expect(component.theme).toEqual('dark'); + expect(localStorage.getItem('theme')).toEqual('dark'); + expect(html.classList.contains('dark')).toBe(true); + }); + + it('should be true the isFeatureToggleDarkModeActive property when the user has the dark-mode feature toggle enabled', fakeAsync(() => { + const filters: FeatureFilterModel[] = []; + const featureToggle: FeatureToggleModel = {name: 'dark-mode', enabled: true, filters}; + spyOn(featureToggleGeneralService, 'getActivated').and.returnValue(of([featureToggle]).pipe(delay(1))); + component.ngOnInit(); + tick(1); + expect(component.isFeatureToggleDarkModeActive).toBeTruthy(); + })); + +}); diff --git a/src/app/modules/shared/components/dark-mode/dark-mode.component.ts b/src/app/modules/shared/components/dark-mode/dark-mode.component.ts new file mode 100644 index 000000000..58ff4691f --- /dev/null +++ b/src/app/modules/shared/components/dark-mode/dark-mode.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit } from '@angular/core'; +import { FeatureToggle } from 'src/environments/enum'; +import { FeatureToggleGeneralService } from '../../feature-toggles/feature-toggle-general/feature-toggle-general.service'; + +@Component({ + selector: 'app-dark-mode', + templateUrl: './dark-mode.component.html', +}) +export class DarkModeComponent implements OnInit { + public theme = 'light'; + public isFeatureToggleDarkModeActive: boolean; + + constructor( + private featureToggleGeneralService: FeatureToggleGeneralService + ) {} + + ngOnInit() { + this.featureToggleGeneralService.getActivated().subscribe((featuresToggles) => { + const darkModeToggle = featuresToggles.find( (item) => item.name === FeatureToggle.DARK_MODE); + this.isFeatureToggleDarkModeActive = darkModeToggle.enabled; + if (this.isFeatureToggleDarkModeActive) { + this.checkThemeInLocalStorage(); + this.addOrRemoveDarkMode(); + } + }); + } + + getLocalStorageTheme(): string { + return localStorage.getItem('theme') || 'light'; + } + + setLocalStorageTheme(theme: string): void { + localStorage.setItem('theme', theme); + } + + checkThemeInLocalStorage(): void { + if ('theme' in localStorage) { + this.theme = this.getLocalStorageTheme(); + } else { + this.setLocalStorageTheme(this.theme); + } + } + + isDarkTheme(): boolean { + return this.theme === 'dark' ? true : false; + } + + setTheme(): string { + return this.isDarkTheme() ? 'light' : 'dark'; + } + + addOrRemoveDarkMode(): void { + if (this.isDarkTheme()) { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + } + + changeToDarkOrLightTheme(): void { + this.theme = this.setTheme(); + this.setLocalStorageTheme(this.theme); + this.addOrRemoveDarkMode(); + } +} diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.html b/src/app/modules/shared/components/sidebar/sidebar.component.html index b18c05e2d..6ee9989cd 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.html +++ b/src/app/modules/shared/components/sidebar/sidebar.component.html @@ -1,14 +1,14 @@ -
-