Skip to content

Commit 2709050

Browse files
JosueObbytesantiagosemantic-release-bot
authored
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 <[email protected]> Co-authored-by: semantic-release-bot <[email protected]>
1 parent 0125ac6 commit 2709050

File tree

11 files changed

+273
-99
lines changed

11 files changed

+273
-99
lines changed

src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import { TechnologyReportComponent } from './modules/technology-report/pages/tec
8686
import { CalendarComponent } from './modules/time-entries/components/calendar/calendar.component';
8787
import { DropdownComponent } from './modules/shared/components/dropdown/dropdown.component';
8888
import { NgSelectModule } from '@ng-select/ng-select';
89+
import { DarkModeComponent } from './modules/shared/components/dark-mode/dark-mode.component';
8990

9091
const maskConfig: Partial<IConfig> = {
9192
validation: false,
@@ -140,6 +141,7 @@ const maskConfig: Partial<IConfig> = {
140141
TechnologyReportTableComponent,
141142
CalendarComponent,
142143
DropdownComponent,
144+
DarkModeComponent,
143145
],
144146
imports: [
145147
NgxMaskModule.forRoot(maskConfig),
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<button
2+
type="button"
3+
class="p-2 transition duration-150 ease-in-out bg-whiteTW rounded-md hover:bg-grayTW-lightest dark:bg-grayTW-dark dark:hover:bg-grayTW-darker dark:hover:bg-gray-700 hover:text-gray-700 focus:outline-none focus:bg-grayTW-lightest dark:focus:bg-grayTW-darker"
4+
(click)="changeToDarkOrLightTheme()"
5+
*ngIf="isFeatureToggleDarkModeActive"
6+
>
7+
<div *ngIf="isDarkTheme(); then sunIcon; else moonIcon"></div>
8+
<ng-template #moonIcon><img class="w-5 h-5" src="assets/icons/moon.svg" alt="moon icon" /></ng-template>
9+
<ng-template #sunIcon><img class="w-5 h-5" src="assets/icons/sun.svg" alt="sun icon" /></ng-template>
10+
</button>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
2+
import { of } from 'rxjs';
3+
import { delay } from 'rxjs/operators';
4+
import { FeatureToggleGeneralService } from '../../feature-toggles/feature-toggle-general/feature-toggle-general.service';
5+
import { FeatureToggleModel } from '../../feature-toggles/feature-toggle.model';
6+
import { FeatureFilterModel } from '../../feature-toggles/filters/feature-filter.model';
7+
import { DarkModeComponent } from './dark-mode.component';
8+
9+
describe('DarkModeComponent', () => {
10+
let component: DarkModeComponent;
11+
let fixture: ComponentFixture<DarkModeComponent>;
12+
let html: HTMLElement;
13+
let featureToggleGeneralService: FeatureToggleGeneralService;
14+
15+
beforeEach(async () => {
16+
await TestBed.configureTestingModule({
17+
declarations: [DarkModeComponent],
18+
}).compileComponents();
19+
});
20+
21+
beforeEach(() => {
22+
fixture = TestBed.createComponent(DarkModeComponent);
23+
component = fixture.componentInstance;
24+
html = document.documentElement;
25+
featureToggleGeneralService = TestBed.inject(FeatureToggleGeneralService);
26+
fixture.detectChanges();
27+
});
28+
29+
afterEach(() => {
30+
localStorage.removeItem('theme');
31+
});
32+
33+
it('should be created', () => {
34+
expect(component).toBeTruthy();
35+
});
36+
37+
it('should be light the default theme', () => {
38+
expect(component.theme).toEqual('light');
39+
});
40+
41+
it('should change the value of the theme property if it exists in the local storage', () => {
42+
localStorage.setItem('theme', 'dark');
43+
component.checkThemeInLocalStorage();
44+
expect(component.theme).toEqual('dark');
45+
});
46+
47+
it('should be light theme if it does not exist in local storage', () => {
48+
component.checkThemeInLocalStorage();
49+
expect(component.theme).toEqual('light');
50+
});
51+
52+
it('should be light mode if property ‘theme’ does not have a value in local storage', () => {
53+
localStorage.setItem('theme', '');
54+
component.checkThemeInLocalStorage();
55+
expect(component.theme).toEqual('light');
56+
});
57+
58+
it('should switch to dark theme if the theme property is light and vice versa', () => {
59+
component.theme = component.setTheme();
60+
expect(component.theme).toEqual('dark');
61+
component.theme = component.setTheme();
62+
expect(component.theme).toEqual('light');
63+
});
64+
65+
it('should add the dark class in the html tag to apply dark mode', () => {
66+
component.theme = 'dark';
67+
component.addOrRemoveDarkMode();
68+
fixture.detectChanges();
69+
expect(html.classList.contains('dark')).toBe(true);
70+
});
71+
72+
it('should not have dark class in the html tag when theme is light', () => {
73+
component.addOrRemoveDarkMode();
74+
fixture.detectChanges();
75+
expect(component.theme).toEqual('light');
76+
expect(html.classList.contains('dark')).toBe(false);
77+
});
78+
79+
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', () => {
80+
component.changeToDarkOrLightTheme();
81+
fixture.detectChanges();
82+
expect(component.theme).toEqual('dark');
83+
expect(localStorage.getItem('theme')).toEqual('dark');
84+
expect(html.classList.contains('dark')).toBe(true);
85+
});
86+
87+
it('should be true the isFeatureToggleDarkModeActive property when the user has the dark-mode feature toggle enabled', fakeAsync(() => {
88+
const filters: FeatureFilterModel[] = [];
89+
const featureToggle: FeatureToggleModel = {name: 'dark-mode', enabled: true, filters};
90+
spyOn(featureToggleGeneralService, 'getActivated').and.returnValue(of([featureToggle]).pipe(delay(1)));
91+
component.ngOnInit();
92+
tick(1);
93+
expect(component.isFeatureToggleDarkModeActive).toBeTruthy();
94+
}));
95+
96+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { FeatureToggle } from 'src/environments/enum';
3+
import { FeatureToggleGeneralService } from '../../feature-toggles/feature-toggle-general/feature-toggle-general.service';
4+
5+
@Component({
6+
selector: 'app-dark-mode',
7+
templateUrl: './dark-mode.component.html',
8+
})
9+
export class DarkModeComponent implements OnInit {
10+
public theme = 'light';
11+
public isFeatureToggleDarkModeActive: boolean;
12+
13+
constructor(
14+
private featureToggleGeneralService: FeatureToggleGeneralService
15+
) {}
16+
17+
ngOnInit() {
18+
this.featureToggleGeneralService.getActivated().subscribe((featuresToggles) => {
19+
const darkModeToggle = featuresToggles.find( (item) => item.name === FeatureToggle.DARK_MODE);
20+
this.isFeatureToggleDarkModeActive = darkModeToggle.enabled;
21+
if (this.isFeatureToggleDarkModeActive) {
22+
this.checkThemeInLocalStorage();
23+
this.addOrRemoveDarkMode();
24+
}
25+
});
26+
}
27+
28+
getLocalStorageTheme(): string {
29+
return localStorage.getItem('theme') || 'light';
30+
}
31+
32+
setLocalStorageTheme(theme: string): void {
33+
localStorage.setItem('theme', theme);
34+
}
35+
36+
checkThemeInLocalStorage(): void {
37+
if ('theme' in localStorage) {
38+
this.theme = this.getLocalStorageTheme();
39+
} else {
40+
this.setLocalStorageTheme(this.theme);
41+
}
42+
}
43+
44+
isDarkTheme(): boolean {
45+
return this.theme === 'dark' ? true : false;
46+
}
47+
48+
setTheme(): string {
49+
return this.isDarkTheme() ? 'light' : 'dark';
50+
}
51+
52+
addOrRemoveDarkMode(): void {
53+
if (this.isDarkTheme()) {
54+
document.documentElement.classList.add('dark');
55+
} else {
56+
document.documentElement.classList.remove('dark');
57+
}
58+
}
59+
60+
changeToDarkOrLightTheme(): void {
61+
this.theme = this.setTheme();
62+
this.setLocalStorageTheme(this.theme);
63+
this.addOrRemoveDarkMode();
64+
}
65+
}
Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
1-
<div class="d-flex" id="wrapper">
2-
<div class="bg-light border-right" id="sidebar-wrapper">
1+
<div class="d-flex bg-grayTW-lightest dark:bg-grayTW-darker dark:text-whiteTW" id="wrapper">
2+
<div class="border-right bg-whiteTW dark:bg-grayTW-dark" id="sidebar-wrapper">
33
<div class="sidebar-heading" style="text-align: center">
44
<img src="assets/img/ioet.png" width="90" height="auto" style="padding-top: 2rem; padding-bottom: 2rem;" alt="logo" />
55
</div>
6-
<div class="list-group list-group-flush">
6+
<div class="list-group list-group-flush bg-whiteTW dark:bg-grayTW-darker">
77
<a
88
*ngFor="let item of itemsSidebar"
99
[routerLink]="item.route"
1010
routerLinkActive=""
11-
class="list-group-item list-group-item-action bg-light"
11+
class="list-group-item list-group-item-action bg-whiteTW dark:bg-grayTW-dark dark:text-whiteTW"
1212
[ngClass]="{active: item.active}"
1313
>
1414
<i class="{{ item.icon }}"></i> {{ item.text }}
1515
</a>
1616
</div>
1717
</div>
1818
<div id="page-content-wrapper">
19-
<nav class="navbar navbar-expand-lg navbar-light border-bottom">
20-
<button class="btn btn-primary" id="menu-toggle">
19+
<nav class="navbar navbar-expand-lg navbar-light border-bottom bg-whiteTW dark:bg-grayTW-dark">
20+
<button class="btn bg-primaryTW hover:bg-primaryTW-dark text-whiteTW hover:text-whiteTW" id="menu-toggle">
2121
Toggle Menu
2222
</button>
23+
<div class="dark-mode-toggle">
24+
<app-dark-mode></app-dark-mode>
25+
</div>
2326
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
2427
<span class="navbar-toggler-icon"></span>
2528
</button>
@@ -30,9 +33,11 @@
3033
</div>
3134
</nav>
3235
<div class="container-fluid px-0 full-height">
33-
<div class="content_app h-100">
36+
<div class="content_app h-100">
37+
<div class="m-1 p-5 rounded-md bg-whiteTW dark:bg-grayTW-dark">
3438
<router-outlet></router-outlet>
3539
</div>
36-
</div>
40+
</div>
41+
</div>
3742
</div>
3843
</div>

src/app/modules/shared/components/sidebar/sidebar.component.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ body {
5353
font-weight: bold;
5454
text-decoration: underline;
5555
border-color: $primary;
56+
background-color: transparent;
5657
}
5758

5859
.full-height {
@@ -62,3 +63,9 @@ body {
6263
height: -o-calc(100vh - 12vh);
6364
height: calc(100vh - 12vh);
6465
}
66+
67+
.dark-mode-toggle {
68+
position: absolute;
69+
left: 50%;
70+
transform: translateX(-50%);
71+
}

src/assets/icons/moon.svg

Lines changed: 8 additions & 0 deletions
Loading

src/assets/icons/sun.svg

Lines changed: 7 additions & 0 deletions
Loading

src/environments/enum.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export enum FeatureToggle {
2-
SWITCH_GROUP = 'switch-group',
3-
TIME_TRACKER_CALENDAR = 'time-tracker-calendar'
2+
SWITCH_GROUP = 'switch-group',
3+
TIME_TRACKER_CALENDAR = 'time-tracker-calendar',
4+
DARK_MODE = 'dark-mode',
45
}

src/styles.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
@import './styles/colors.scss';
66
@import '../node_modules/angular-calendar/css/angular-calendar.css';
77

8+
@import "tailwindcss/base";
9+
@import "tailwindcss/components";
10+
@import "tailwindcss/utilities";
11+
812
html,
913
body {
1014
font-family: 'Roboto', sans-serif;

0 commit comments

Comments
 (0)