theme
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 2s

This commit is contained in:
Gal Podlipnik 2025-06-04 02:09:16 +02:00
parent 459deb255d
commit 50056356f3
14 changed files with 274 additions and 14 deletions

View File

@ -32,5 +32,6 @@
"**/node_modules": true "**/node_modules": true
}, },
"search.useIgnoreFiles": false, "search.useIgnoreFiles": false,
"nxConsole.generateAiAgentRules": true "nxConsole.generateAiAgentRules": true,
"nxConsole.nxWorkspacePath": "frontend"
} }

View File

@ -0,0 +1,7 @@
# theme-toggle
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test theme-toggle` to execute the unit tests.

View File

@ -0,0 +1,34 @@
import nx from '@nx/eslint-plugin';
import baseConfig from '../../../eslint.base.config.mjs';
export default [
...baseConfig,
...nx.configs['flat/angular'],
...nx.configs['flat/angular-template'],
{
files: ['**/*.ts'],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'lib',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'lib',
style: 'kebab-case',
},
],
},
},
{
files: ['**/*.html'],
// Override or add rules here
rules: {},
},
];

View File

@ -0,0 +1,21 @@
export default {
displayName: 'theme-toggle',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/libs/shared/theme-toggle',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

View File

@ -0,0 +1,20 @@
{
"name": "theme-toggle",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/shared/theme-toggle/src",
"prefix": "lib",
"projectType": "library",
"tags": [],
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/shared/theme-toggle/jest.config.ts"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}

View File

@ -0,0 +1 @@
export * from './lib/theme-toggle.service';

View File

@ -0,0 +1,54 @@
import { OverlayContainer } from '@angular/cdk/overlay';
import { computed, DOCUMENT, effect, inject, Injectable } from '@angular/core';
import { stored } from '@mmstack/primitives';
@Injectable({
providedIn: 'root',
})
export class ThemeToggleService {
private readonly overlayCls =
inject(OverlayContainer).getContainerElement().classList;
private readonly dockument = inject(DOCUMENT);
private readonly theme = stored<'light' | 'dark'>('light', {
key: 'user-theme',
syncTabs: true,
});
public isDark = computed(() => this.theme() === 'dark');
constructor() {
const prefersDark =
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
if (prefersDark && this.theme() === 'light') {
this.theme.set('dark');
}
effect(() =>
this.isDark() ? this.addDarkThemeClass() : this.removeDarkThemeClass()
);
effect(() =>
this.dockument.body.setAttribute(
'style',
`color-scheme: ${this.isDark() ? 'dark' : 'light'}`
)
);
}
public toggleTheme() {
this.theme.set(this.isDark() ? 'light' : 'dark');
}
private addDarkThemeClass() {
this.overlayCls.add('dark-theme');
this.dockument.body.classList.add('dark-theme');
}
private removeDarkThemeClass() {
this.overlayCls.remove('dark-theme');
this.dockument.body.classList.remove('dark-theme');
}
}

View File

@ -0,0 +1,6 @@
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
setupZoneTestEnv({
errorOnUnknownElements: true,
errorOnUnknownProperties: true,
});

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es2022",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@ -0,0 +1,17 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": [
"src/**/*.spec.ts",
"src/test-setup.ts",
"jest.config.ts",
"src/**/*.test.ts"
],
"include": ["src/**/*.ts"]
}

View File

@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"target": "es2016",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -1,35 +1,74 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, OnInit, inject } from '@angular/core'; import { Component, OnInit, inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { StatsWsService } from '@frontend/shared/stats-ws'; import { StatsWsService } from '@frontend/shared/stats-ws';
import { ThemeToggleService } from '@frontend/shared/theme-toggle';
@Component({ @Component({
standalone: true, standalone: true,
imports: [RouterModule, CommonModule], imports: [
RouterModule,
MatButtonModule,
MatCardModule,
MatIconModule,
MatToolbarModule,
CommonModule,
],
selector: 'app-root', selector: 'app-root',
template: ` template: `
<div> <mat-toolbar>
<h1>Docker Containers</h1> <span>Docker Containers</span>
<span class="spacer"></span>
<button mat-icon-button (click)="switchTheme()">
<mat-icon>{{ isDarkTheme() ? 'light_mode' : 'dark_mode' }}</mat-icon>
</button>
</mat-toolbar>
<div class="content-container">
@for (container of containers(); track container.id) { @for (container of containers(); track container.id) {
<div class="container"> <mat-card class="container">
<h2>{{ container.name }}</h2> <mat-card-header>
<p>ID: {{ container.id }}</p> <mat-card-title>{{ container.name }}</mat-card-title>
<p>Image: {{ container.image }}</p> </mat-card-header>
<p>Status: {{ container.status }}</p> <mat-card-content>
<p>State: {{ container.state }}</p> <p>ID: {{ container.id }}</p>
</div> <p>Image: {{ container.image }}</p>
<p>Status: {{ container.status }}</p>
<p>State: {{ container.state }}</p>
</mat-card-content>
</mat-card>
} }
</div> </div>
<router-outlet></router-outlet> <router-outlet></router-outlet>
`, `,
styles: [
`
.spacer {
flex: 1 1 auto;
}
.content-container {
padding: 16px;
}
`,
],
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
private readonly service = inject(StatsWsService); private readonly service = inject(StatsWsService);
private readonly themeToggleService = inject(ThemeToggleService);
protected containers = this.service.containers; protected containers = this.service.containers;
// Add this to check current theme for the template
protected isDarkTheme = () => this.themeToggleService.isDark();
switchTheme() {
this.themeToggleService.toggleTheme();
}
ngOnInit() { ngOnInit() {
this.service.initContainers(); this.service.initContainers();
} }
} }

View File

@ -1 +1,16 @@
/* You can add global styles to this file, and also import other style files */ html,
body {
height: 100%;
max-width: 100vw;
overflow-x: hidden;
}
body {
margin: 0;
font-family: Roboto, 'Helvetica Neue', sans-serif;
background-color: #fafafa;
transition: background-color 0.3s ease;
}
body.dark-theme {
background-color: #303030;
}

View File

@ -16,7 +16,8 @@
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@frontend/shared/environment": ["libs/shared/environment/src/index.ts"], "@frontend/shared/environment": ["libs/shared/environment/src/index.ts"],
"@frontend/shared/stats-ws": ["libs/shared/stats-ws/src/index.ts"] "@frontend/shared/stats-ws": ["libs/shared/stats-ws/src/index.ts"],
"@frontend/shared/theme-toggle": ["libs/shared/theme-toggle/src/index.ts"]
} }
}, },
"exclude": ["node_modules", "tmp"] "exclude": ["node_modules", "tmp"]