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), + ) + ); +}