diff --git a/angular.json b/angular.json
index b68640b..5c54b35 100644
--- a/angular.json
+++ b/angular.json
@@ -27,7 +27,10 @@
"input": "public"
}
],
- "styles": ["src/styles.scss"]
+ "styles": ["src/styles.scss"],
+ "stylePreprocessorOptions": {
+ "includePaths": ["src/styles"]
+ }
},
"configurations": {
"production": {
@@ -80,7 +83,10 @@
"input": "public"
}
],
- "styles": ["src/styles.scss"]
+ "styles": ["src/styles.scss"],
+ "stylePreprocessorOptions": {
+ "includePaths": ["src/styles"]
+ }
}
}
}
diff --git a/package-lock.json b/package-lock.json
index 089bce3..c157539 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,10 +8,12 @@
"name": "angular-starter-project",
"version": "1.0.3",
"dependencies": {
+ "@angular/cdk": "^20.2.14",
"@angular/common": "^20.3.0",
"@angular/compiler": "^20.3.0",
"@angular/core": "^20.3.0",
"@angular/forms": "^20.3.0",
+ "@angular/material": "^20.2.14",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
"@ngrx/effects": "^20.1.0",
@@ -555,6 +557,21 @@
}
}
},
+ "node_modules/@angular/cdk": {
+ "version": "20.2.14",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz",
+ "integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==",
+ "license": "MIT",
+ "dependencies": {
+ "parse5": "^8.0.0",
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/common": "^20.0.0 || ^21.0.0",
+ "@angular/core": "^20.0.0 || ^21.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/cli": {
"version": "20.3.8",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.8.tgz",
@@ -694,6 +711,23 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
+ "node_modules/@angular/material": {
+ "version": "20.2.14",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.14.tgz",
+ "integrity": "sha512-IbAgV6XLsvmHiJzxycVhcNC1PA4M30qi+ERCOir6cT333Bxm8vDV32gsOjfL52uzG5YRARroPC+8s1XqR2oxeA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/cdk": "20.2.14",
+ "@angular/common": "^20.0.0 || ^21.0.0",
+ "@angular/core": "^20.0.0 || ^21.0.0",
+ "@angular/forms": "^20.0.0 || ^21.0.0",
+ "@angular/platform-browser": "^20.0.0 || ^21.0.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/@angular/platform-browser": {
"version": "20.3.9",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.9.tgz",
@@ -16333,7 +16367,6 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
"integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
@@ -16387,7 +16420,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
- "dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
diff --git a/package.json b/package.json
index 9737e89..3b98310 100644
--- a/package.json
+++ b/package.json
@@ -35,10 +35,12 @@
},
"private": true,
"dependencies": {
+ "@angular/cdk": "^20.2.14",
"@angular/common": "^20.3.0",
"@angular/compiler": "^20.3.0",
"@angular/core": "^20.3.0",
"@angular/forms": "^20.3.0",
+ "@angular/material": "^20.2.14",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
"@ngrx/effects": "^20.1.0",
diff --git a/src/app/app.html b/src/app/app.html
index 175cab4..f85f3ce 100644
--- a/src/app/app.html
+++ b/src/app/app.html
@@ -1,351 +1,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Hello, {{ title() }}
-
Congratulations! Your app is running. 🎉
-
-
-
-
- @for (
- item of [
- { title: 'Explore the Docs', link: 'https://angular.dev' },
- { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
- {
- title: 'Prompt and best practices for AI',
- link: 'https://angular.dev/ai/develop-with-ai',
- },
- { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
- {
- title: 'Angular Language Service',
- link: 'https://angular.dev/tools/language-service',
- },
- { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
- ];
- track item.title
- ) {
-
- {{ item.title }}
-
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
Angular Starter Project
+
+
diff --git a/src/app/app.scss b/src/app/app.scss
index e69de29..ccbf013 100644
--- a/src/app/app.scss
+++ b/src/app/app.scss
@@ -0,0 +1,7 @@
+.app {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+}
diff --git a/src/app/app.spec.ts b/src/app/app.spec.ts
index 96ac153..8a2b85b 100644
--- a/src/app/app.spec.ts
+++ b/src/app/app.spec.ts
@@ -20,6 +20,6 @@ describe('App', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
- expect(compiled.querySelector('h1')?.textContent).toContain('Hello, angular-starter-project');
+ expect(compiled.querySelector('h1')?.textContent).toContain('Angular Starter Project');
});
});
diff --git a/src/app/app.ts b/src/app/app.ts
index 8281f59..4e43811 100644
--- a/src/app/app.ts
+++ b/src/app/app.ts
@@ -4,19 +4,56 @@ import { Store } from '@ngrx/store';
import { addItem } from './state/item.actions';
import { ItemState } from './state/item.reducer';
+import { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatIconModule } from '@angular/material/icon';
+import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
+import { MatTableModule } from '@angular/material/table';
+
+export interface PeriodicElement {
+ name: string;
+ position: number;
+ weight: number;
+ symbol: string;
+}
+
+const ELEMENT_DATA: PeriodicElement[] = [
+ { position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
+ { position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
+ { position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
+];
+
@Component({
selector: 'app-root',
- imports: [RouterOutlet],
+ imports: [
+ RouterOutlet,
+ MatButtonModule,
+ MatCardModule,
+ MatIconModule,
+ MatSnackBarModule,
+ MatTableModule,
+ ],
templateUrl: './app.html',
styleUrl: './app.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
protected readonly title = signal('angular-starter-project');
+ public displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
+ public dataSource = ELEMENT_DATA;
- constructor(private store: Store) {}
+ constructor(
+ private store: Store,
+ private snackBar: MatSnackBar,
+ ) {}
public addItem() {
this.store.dispatch(addItem({ item: 'Your new item' }));
}
+
+ public openSnackbar(): void {
+ this.snackBar.open('This is a snackbar with custom theme!', 'Close', {
+ duration: 3000,
+ });
+ }
}
diff --git a/src/app/styles/.gitkeep b/src/app/styles/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/index.html b/src/index.html
index 8edfeec..0539b4a 100644
--- a/src/index.html
+++ b/src/index.html
@@ -6,6 +6,11 @@
+
+
diff --git a/src/styles.scss b/src/styles.scss
index 90d4ee0..9e81e94 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1 +1,11 @@
-/* You can add global styles to this file, and also import other style files */
+@use '@angular/material' as mat;
+@use './styles//material/_custom-theme.scss';
+
+html,
+body {
+ height: 100%;
+}
+body {
+ margin: 16px;
+ font-family: Roboto, 'Helvetica Neue', sans-serif;
+}
diff --git a/src/styles/material/_custom-theme.scss b/src/styles/material/_custom-theme.scss
new file mode 100644
index 0000000..0742914
--- /dev/null
+++ b/src/styles/material/_custom-theme.scss
@@ -0,0 +1,23 @@
+@use '@angular/material' as mat;
+@use './material_theme-colors' as my-colors;
+
+@include mat.core();
+
+$my-theme: mat.define-theme(
+ (
+ color: (
+ theme-type: dark,
+ primary: my-colors.$primary-palette,
+ tertiary: my-colors.$tertiary-palette,
+ ),
+ typography: (
+ brand-family: 'Roboto, sans-serif',
+ plain-family: 'Roboto, sans-serif',
+ ),
+ density: (
+ scale: 0,
+ ),
+ )
+);
+
+@include mat.all-component-themes($my-theme);
diff --git a/src/styles/material/_variables.scss b/src/styles/material/_variables.scss
new file mode 100644
index 0000000..fc1c3c6
--- /dev/null
+++ b/src/styles/material/_variables.scss
@@ -0,0 +1,19 @@
+:root {
+ /* --- PRIMARY (Your Main Brand Color) --- */
+ --mat-sys-primary: #f0b90b; /* Your EXACT Primary Hex */
+ --mat-sys-on-primary: #000; /* Text color on top of Primary */
+
+ /* --- SECONDARY (Formerly 'Accent') --- */
+ /* Used for Floating Action Buttons (FABs), selection controls, chips */
+ --mat-sys-secondary: #00cc99; /* Your EXACT Secondary Hex */
+ --mat-sys-on-secondary: #000000;
+
+ /* --- TERTIARY (New in M3) --- */
+ /* Used for contrasting accents or distinctive visual elements */
+ --mat-sys-tertiary: #ffcc00; /* Your EXACT Tertiary Hex */
+ --mat-sys-on-tertiary: #000000;
+
+ /* --- ERROR (Formerly 'Warn') --- */
+ --mat-sys-error: #ff0000; /* Your EXACT Error/Warn Hex */
+ --mat-sys-on-error: #ffffff;
+}
diff --git a/src/styles/material/material_theme-colors.scss b/src/styles/material/material_theme-colors.scss
new file mode 100644
index 0000000..bdc7f28
--- /dev/null
+++ b/src/styles/material/material_theme-colors.scss
@@ -0,0 +1,204 @@
+@use 'sass:map';
+@use '@angular/material' as mat;
+
+// Note: Color palettes are generated from primary: #F0B90B, secondary: #0ECB81, tertiary: #848E9C, neutral: #181A20, error: #F6465D
+$_palettes: (
+ primary: (
+ 0: #000000,
+ 10: #251a00,
+ 20: #3f2e00,
+ 25: #4c3800,
+ 30: #5a4300,
+ 35: #684f00,
+ 40: #775a00,
+ 50: #957200,
+ 60: #b58a00,
+ 70: #d6a400,
+ 80: #f6be16,
+ 90: #ffdf99,
+ 95: #ffefd2,
+ 98: #fff8f2,
+ 99: #fffbff,
+ 100: #ffffff,
+ ),
+ secondary: (
+ 0: #000000,
+ 10: #002111,
+ 20: #003920,
+ 25: #004528,
+ 30: #005231,
+ 35: #005f3a,
+ 40: #006d42,
+ 50: #008955,
+ 60: #00a668,
+ 70: #00c47b,
+ 80: #3be194,
+ 90: #60feae,
+ 95: #c0ffd6,
+ 98: #e9ffed,
+ 99: #f5fff5,
+ 100: #ffffff,
+ ),
+ tertiary: (
+ 0: #000000,
+ 10: #131c27,
+ 20: #27313d,
+ 25: #323c48,
+ 30: #3e4854,
+ 35: #495360,
+ 40: #555f6c,
+ 50: #6e7885,
+ 60: #8892a0,
+ 70: #a2acbb,
+ 80: #bdc7d6,
+ 90: #d9e3f3,
+ 95: #e9f1ff,
+ 98: #f8f9ff,
+ 99: #fdfcff,
+ 100: #ffffff,
+ ),
+ neutral: (
+ 0: #000000,
+ 10: #191b21,
+ 20: #2e3037,
+ 25: #393b42,
+ 30: #45464d,
+ 35: #515259,
+ 40: #5d5e65,
+ 50: #75777e,
+ 60: #8f9098,
+ 70: #aaabb3,
+ 80: #c5c6ce,
+ 90: #e2e2ea,
+ 95: #f0f0f9,
+ 98: #faf8ff,
+ 99: #fefbff,
+ 100: #ffffff,
+ 4: #0c0e14,
+ 6: #111319,
+ 12: #1d1f26,
+ 17: #282a30,
+ 22: #33353b,
+ 24: #373940,
+ 87: #d9d9e2,
+ 92: #e7e7f0,
+ 94: #ededf6,
+ 96: #f3f3fb,
+ ),
+ neutral-variant: (
+ 0: #000000,
+ 10: #221b0b,
+ 20: #372f1e,
+ 25: #433a28,
+ 30: #4f4633,
+ 35: #5b513e,
+ 40: #675d49,
+ 50: #817660,
+ 60: #9b8f79,
+ 70: #b7aa92,
+ 80: #d3c5ac,
+ 90: #efe1c7,
+ 95: #feefd5,
+ 98: #fff8f2,
+ 99: #fffbff,
+ 100: #ffffff,
+ ),
+ error: (
+ 0: #000000,
+ 10: #40000b,
+ 20: #680018,
+ 25: #7c001f,
+ 30: #920026,
+ 35: #a8002d,
+ 40: #ba1437,
+ 50: #de334d,
+ 60: #ff5166,
+ 70: #ff888f,
+ 80: #ffb3b5,
+ 90: #ffdada,
+ 95: #ffedec,
+ 98: #fff8f7,
+ 99: #fffbff,
+ 100: #ffffff,
+ ),
+);
+
+$_rest: (
+ secondary: map.get($_palettes, secondary),
+ neutral: map.get($_palettes, neutral),
+ neutral-variant: map.get($_palettes, neutral-variant),
+ error: map.get($_palettes, error),
+);
+
+$primary-palette: map.merge(map.get($_palettes, primary), $_rest);
+$tertiary-palette: map.merge(map.get($_palettes, tertiary), $_rest);
+
+@function _high-contrast-value($light, $dark, $theme-type) {
+ @if ($theme-type == light) {
+ @return $light;
+ }
+ @if ($theme-type == dark) {
+ @return $dark;
+ }
+ @if ($theme-type == color-scheme) {
+ @return light-dark(#{$light}, #{$dark});
+ }
+
+ @error 'Unknown theme-type #{$theme-type}. Expected light, dark, or color-scheme';
+}
+
+@mixin high-contrast-overrides($theme-type) {
+ @include mat.theme-overrides(
+ (
+ primary: _high-contrast-value(#392a00, #ffeecf, $theme-type),
+ on-primary: _high-contrast-value(#ffffff, #000000, $theme-type),
+ primary-container: _high-contrast-value(#5d4600, #f2bb0f, $theme-type),
+ on-primary-container: _high-contrast-value(#ffffff, #110a00, $theme-type),
+ inverse-primary: _high-contrast-value(#f6be16, #5b4500, $theme-type),
+ primary-fixed: _high-contrast-value(#5d4600, #ffdf99, $theme-type),
+ primary-fixed-dim: _high-contrast-value(#413000, #f6be16, $theme-type),
+ on-primary-fixed: _high-contrast-value(#ffffff, #000000, $theme-type),
+ on-primary-fixed-variant: _high-contrast-value(#ffffff, #181000, $theme-type),
+ secondary: _high-contrast-value(#00341d, #bcffd4, $theme-type),
+ on-secondary: _high-contrast-value(#ffffff, #000000, $theme-type),
+ secondary-container: _high-contrast-value(#005533, #35dc91, $theme-type),
+ on-secondary-container: _high-contrast-value(#ffffff, #000e05, $theme-type),
+ secondary-fixed: _high-contrast-value(#005533, #60feae, $theme-type),
+ secondary-fixed-dim: _high-contrast-value(#003b22, #3be194, $theme-type),
+ on-secondary-fixed: _high-contrast-value(#ffffff, #000000, $theme-type),
+ on-secondary-fixed-variant: _high-contrast-value(#ffffff, #001509, $theme-type),
+ tertiary: _high-contrast-value(#232d38, #e8f1ff, $theme-type),
+ on-tertiary: _high-contrast-value(#ffffff, #000000, $theme-type),
+ tertiary-container: _high-contrast-value(#404a56, #b9c3d2, $theme-type),
+ on-tertiary-container: _high-contrast-value(#ffffff, #030c16, $theme-type),
+ tertiary-fixed: _high-contrast-value(#404a56, #d9e3f3, $theme-type),
+ tertiary-fixed-dim: _high-contrast-value(#2a333f, #bdc7d6, $theme-type),
+ on-tertiary-fixed: _high-contrast-value(#ffffff, #000000, $theme-type),
+ on-tertiary-fixed-variant: _high-contrast-value(#ffffff, #08121c, $theme-type),
+ background: _high-contrast-value(#faf8ff, #111319, $theme-type),
+ on-background: _high-contrast-value(#191b21, #e2e2ea, $theme-type),
+ surface: _high-contrast-value(#faf8ff, #111319, $theme-type),
+ surface-dim: _high-contrast-value(#b8b8c0, #111319, $theme-type),
+ surface-bright: _high-contrast-value(#faf8ff, #4e5057, $theme-type),
+ surface-container-low: _high-contrast-value(#f0f0f9, #1d1f26, $theme-type),
+ surface-container-lowest: _high-contrast-value(#ffffff, #000000, $theme-type),
+ surface-container: _high-contrast-value(#e2e2ea, #2e3037, $theme-type),
+ surface-container-high: _high-contrast-value(#d3d4dc, #393b42, $theme-type),
+ surface-container-highest: _high-contrast-value(#c5c6ce, #45464d, $theme-type),
+ on-surface: _high-contrast-value(#000000, #ffffff, $theme-type),
+ shadow: _high-contrast-value(#000000, #000000, $theme-type),
+ scrim: _high-contrast-value(#000000, #000000, $theme-type),
+ surface-tint: _high-contrast-value(#775a00, #f6be16, $theme-type),
+ inverse-surface: _high-contrast-value(#2e3037, #e2e2ea, $theme-type),
+ inverse-on-surface: _high-contrast-value(#ffffff, #000000, $theme-type),
+ outline: _high-contrast-value(#332b1a, #fdeed4, $theme-type),
+ outline-variant: _high-contrast-value(#514835, #cfc1a8, $theme-type),
+ error: _high-contrast-value(#600016, #ffeceb, $theme-type),
+ on-error: _high-contrast-value(#ffffff, #000000, $theme-type),
+ error-container: _high-contrast-value(#960027, #ffadb0, $theme-type),
+ on-error-container: _high-contrast-value(#ffffff, #220003, $theme-type),
+ surface-variant: _high-contrast-value(#efe1c7, #4f4633, $theme-type),
+ on-surface-variant: _high-contrast-value(#000000, #ffffff, $theme-type),
+ )
+ );
+}