├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature_request.md └── workflows │ └── verify-build.yml ├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── additional-hacks.md ├── angular.json ├── eslint.config.mjs ├── logo.png ├── logo.xcf ├── package.json ├── pnpm-lock.yaml ├── projects └── material-css-vars │ ├── README.md │ ├── eslint.config.mjs │ ├── index.scss │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── _internal-helper.scss │ │ ├── _main.scss │ │ ├── _mat-lib-overwrites.scss │ │ ├── _public-color-util.scss │ │ ├── _public-util.scss │ │ ├── _variables.scss │ │ ├── default-cfg.const.ts │ │ ├── material-css-vars.module.ts │ │ ├── material-css-vars.service.spec.ts │ │ ├── material-css-vars.service.ts │ │ └── model.ts │ ├── mat-css-config-token.const.ts │ ├── public-api.ts │ ├── test.ts │ └── test │ │ ├── global-styles.scss │ │ └── integration.spec.ts │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── src ├── app │ ├── _app.theme.scss │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ └── app.config.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts └── styles.scss ├── tsconfig.app.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [json-derulo, johannesjo] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: Bug, Needs Triage 6 | assignees: "" 7 | --- 8 | 9 | 12 | 13 | ## :ghost: Brief Description 14 | 15 | A clear and concise description of what the bug is. 16 | 17 | ## :pancakes: Affected version 18 | 19 | Version where you are encountering the issue. 20 | 21 | ## Reproduction 22 | 23 | StackBlitz / reproduction repository link: 24 | 25 | Steps to reproduce the behavior: 26 | 27 | 1. Go to '...' 28 | 2. Click on '....' 29 | 3. Scroll down to '....' 30 | 4. See error 31 | 32 | ## :police_car: Expected behavior 33 | 34 | A clear and concise description of what you expected to happen. 35 | 36 | ## :heavy_plus_sign: Additional context 37 | 38 | Add any other context about the problem here. e.g. related issues or existing pull requests. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: Enhancement 6 | assignees: "" 7 | --- 8 | 9 | ## :frowning: Problem Statement 10 | 11 | A clear and concise description of what the problem is. E.g. I'm always frustrated when [...] 12 | 13 | ## :grey_question: Possible Solution 14 | 15 | A clear and concise description of what you want to happen. 16 | 17 | ## :arrow_heading_up: Describe alternatives you've considered 18 | 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | ## :heavy_plus_sign: Additional context 22 | 23 | Add any other context about the problem here. e.g. related issues or existing pull requests. 24 | -------------------------------------------------------------------------------- /.github/workflows/verify-build.yml: -------------------------------------------------------------------------------- 1 | name: Verify build 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request_target: 6 | types: [opened, synchronize, reopened] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | ref: ${{ github.event.pull_request.head.sha }} 16 | 17 | - uses: pnpm/action-setup@v4 18 | name: Install pnpm 19 | with: 20 | run_install: false 21 | 22 | - name: install 23 | run: pnpm install --frozen-lockfile 24 | 25 | - name: build lib 26 | run: pnpm lib 27 | 28 | - name: build demo 29 | run: pnpm build 30 | 31 | lint: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | with: 37 | ref: ${{ github.event.pull_request.head.sha }} 38 | 39 | - uses: pnpm/action-setup@v4 40 | name: Install pnpm 41 | with: 42 | run_install: false 43 | 44 | - name: install 45 | run: pnpm install --frozen-lockfile 46 | 47 | - name: lint 48 | run: pnpm lint 49 | 50 | test-chrome: 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Checkout 54 | uses: actions/checkout@v4 55 | with: 56 | ref: ${{ github.event.pull_request.head.sha }} 57 | 58 | - uses: pnpm/action-setup@v4 59 | name: Install pnpm 60 | with: 61 | run_install: false 62 | 63 | - name: install 64 | run: pnpm install --frozen-lockfile 65 | 66 | - name: test 67 | run: pnpm test --watch=false --browsers=ChromeHeadless 68 | 69 | test-firefox: 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Checkout 73 | uses: actions/checkout@v4 74 | with: 75 | ref: ${{ github.event.pull_request.head.sha }} 76 | 77 | - uses: pnpm/action-setup@v4 78 | name: Install pnpm 79 | with: 80 | run_install: false 81 | 82 | - name: install 83 | run: pnpm install --frozen-lockfile 84 | 85 | - name: test 86 | run: pnpm test --watch=false --browsers=FirefoxHeadless 87 | 88 | test-safari: 89 | runs-on: macos-latest 90 | steps: 91 | - name: Checkout 92 | uses: actions/checkout@v4 93 | with: 94 | ref: ${{ github.event.pull_request.head.sha }} 95 | 96 | - uses: pnpm/action-setup@v4 97 | name: Install pnpm 98 | with: 99 | run_install: false 100 | 101 | - name: install 102 | run: pnpm install --frozen-lockfile 103 | 104 | - name: test 105 | run: pnpm test --watch=false --browsers=SafariNative 106 | 107 | prettier: 108 | runs-on: ubuntu-latest 109 | steps: 110 | - name: Checkout 111 | uses: actions/checkout@v4 112 | with: 113 | ref: ${{ github.event.pull_request.head.sha }} 114 | 115 | - uses: pnpm/action-setup@v4 116 | name: Install pnpm 117 | with: 118 | run_install: false 119 | 120 | - name: install 121 | run: pnpm install --frozen-lockfile 122 | 123 | - name: prettier 124 | run: pnpm prettier-check 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.angular/cache 36 | /.sass-cache 37 | /connect.lock 38 | /coverage 39 | /libpeerconnection.log 40 | npm-debug.log 41 | yarn-error.log 42 | testem.log 43 | /typings 44 | /.nx 45 | 46 | # System Files 47 | .DS_Store 48 | Thumbs.db 49 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Johannes Millan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

logo

2 | 3 | You want to style your angular material dynamically with all the colors in the rainbow? Look no further! 4 | 5 | [Check out the Demo!](https://johannesjo.github.io/angular-material-css-vars/) 6 | 7 | ## Setup 8 | 9 | 1. Install: 10 | ```bash 11 | npm i angular-material-css-vars -S 12 | ``` 13 | 2. If @angular/material is already configured remove `@include mat.core()` from your main stylesheet file if present. 14 | 3. Add this to your main stylesheet instead: 15 | 16 | ```scss 17 | @use "angular-material-css-vars" as mat-css-vars; 18 | 19 | @include mat-css-vars.init-material-css-vars(); 20 | ``` 21 | 22 | 4. Add to your main module: 23 | 24 | ```typescript 25 | import { MaterialCssVarsModule } from "angular-material-css-vars"; 26 | 27 | @NgModule({ 28 | imports: [ 29 | MaterialCssVarsModule.forRoot({ 30 | // all optional 31 | isAutoContrast: true, 32 | primary: "#3f51b5", 33 | // ... 34 | }), 35 | ], 36 | }) 37 | export class AppModule {} 38 | ``` 39 | 40 | In standalone workspaces, add the following to your `app.config.ts`: 41 | 42 | ```typescript 43 | import { ApplicationConfig } from "@angular/core"; 44 | import { provideMaterialCssVars } from "angular-material-css-vars"; 45 | 46 | export const appConfig: ApplicationConfig = { 47 | providers: [ 48 | provideMaterialCssVars({ 49 | // all optional 50 | isAutoContrast: true, 51 | primary: "#3f51b5", 52 | // ... 53 | }), 54 | ], 55 | }; 56 | ``` 57 | 58 | 5. If you want to adjust the theme at runtime, you can use `MaterialCssVarsService`: 59 | 60 | ```typescript 61 | import { MaterialCssVarsService } from "angular-material-css-vars"; 62 | 63 | export class SomeComponentOrService { 64 | constructor(public materialCssVarsService: MaterialCssVarsService) { 65 | const hex = "#3f51b5"; 66 | this.materialCssVarsService.setDarkTheme(true); 67 | this.materialCssVarsService.setPrimaryColor(hex); 68 | this.materialCssVarsService.setAccentColor("#333"); 69 | } 70 | } 71 | ``` 72 | 73 | > Angular Material v18 introduced stable support for M3 theme. 74 | > For now, this library only supports M2. 75 | > More information can be found in this [issue](https://github.com/johannesjo/angular-material-css-vars/issues/166). 76 | 77 | ## Additional Features 78 | 79 | - Auto or manually set contrast color via 80 | - `setAutoContrastEnabled(bool)` 81 | - `setContrastColorThreshold(hueVal: HueValue)` 82 | - Helper to set all variables 83 | - `setVariable(cssVarName: MaterialCssVariables, value: string)` 84 | - You can use the `MaterialCssVariables` enum [from here](https://github.com/johannesjo/angular-material-css-vars/blob/master/projects/material-css-vars/src/lib/model.ts) to make sure you get the variable name right. 85 | - Rudimentary dark theme support via body class 86 | - `setDarkTheme(isDark: boolean)` 87 | 88 | ## Utility 89 | 90 | There are also several [utility functions and mixins](https://github.com/johannesjo/angular-material-css-vars/blob/master/projects/material-css-vars/src/lib/_public-util.scss). 91 | 92 | ```scss 93 | @use "angular-material-css-vars" as mat-css-vars; 94 | 95 | .with-color { 96 | border-color: mat-css-vars.mat-css-color-primary(300); 97 | } 98 | 99 | .color-and-contrast { 100 | @include mat-css-vars.mat-css-color-and-contrast(300); 101 | } 102 | 103 | .with-bg { 104 | @include mat-css-vars.mat-css-bg(300); 105 | } 106 | ``` 107 | 108 | There are also [some additional hacks](additional-hacks.md) (e.g. adding a color to the elevation shadow) available in case you need them. 109 | 110 | ## Initialization Options 111 | 112 | You can provide different options before initialization to change the body class used for the dark theme and to provide different default styles: 113 | 114 | ```scss 115 | ... 116 | @use 'angular-material-css-vars' as mat-css-vars with ( 117 | $dark-theme-selector: '.isDarkTheme', 118 | $light-theme-selector: '.isLightTheme' 119 | ); 120 | ... 121 | 122 | @include mat-css-vars.init-material-css-vars(); 123 | 124 | ``` 125 | 126 | ### Set default (fallback palettes) 127 | 128 | There are two ways to set the default fallback theme. One is using the `mat-css-palette-defaults` mixin. 129 | 130 | ```scss 131 | @use "angular-material-css-vars" as mat-css-vars; 132 | @use "@angular/material" as mat; 133 | 134 | @include mat-css-vars.init-material-css-vars(); 135 | 136 | @include mat-css-vars.mat-css-set-palette-defaults(mat.$light-blue-palette, "primary"); 137 | @include mat-css-vars.mat-css-set-palette-defaults(mat.$pink-palette, "accent"); 138 | @include mat-css-vars.mat-css-set-palette-defaults(mat.$red-palette, "warn"); 139 | ``` 140 | 141 | The other is to include your own variables for [$default-light-theme](https://github.com/johannesjo/angular-material-css-vars/blob/master/projects/material-css-vars/src/lib/_variables.scss). 142 | 143 | ```scss 144 | @use "angular-material-css-vars" as mat-css-vars with ( 145 | $default-light-theme: ( 146 | --palette-primary-50: mat-css-vars.hex-to-rgb(#e1f5fe), 147 | --palette-primary-100: mat-css-vars.hex-to-rgb(#b3e5fc), 148 | --palette-primary-200: mat-css-vars.hex-to-rgb(#81d4fa), 149 | --palette-primary-300: mat-css-vars.hex-to-rgb(#4fc3f7), 150 | --palette-primary-400: mat-css-vars.hex-to-rgb(#29b6f6), 151 | --palette-primary-500: mat-css-vars.hex-to-rgb(#03a9f4), 152 | // ... 153 | ) 154 | ); 155 | 156 | @include mat-css-vars.init-material-css-vars(); 157 | ``` 158 | 159 | ### Set global density 160 | 161 | To set the global density level, just pass the `$density` variable to the `init-material-css-vars()` mixin like the following: 162 | 163 | ```scss 164 | @use "angular-material-css-vars" as mat-css-vars; 165 | 166 | @include mat-css-vars.init-material-css-vars($density: -2); 167 | ``` 168 | 169 | ## App Theme Mixins 170 | 171 | The `init-material-css-vars` mixin allows content to be passed into it. This allows you to create app themes that can take advantage of the dynamic theme created inside this mixin. It may be possible to do all theming using the utility mixins outlined above, but in other cases, you may need access to the theme palette, including foreground and background palettes. 172 | 173 | See the Material guide on [Theming your custom component](https://material.angular.io/guide/theming-your-components) for more information. 174 | 175 | ## Font config 176 | 177 | If needed the typography can be adjusted as well. You can introduce your own CSS variables, if you wish. 178 | 179 | ```scss 180 | @use "angular-material-css-vars" as mat-css-vars; 181 | @use "@angular/material" as mat; 182 | 183 | // example 184 | $custom-typography: mat.define-typography-config( 185 | // optionally, you introduce your own CSS variables: `$font-family: var(--my-custom-font-family)` 186 | $font-family: "Roboto, monospace", 187 | $headline: mat.define-typography-level(32px, 48px, 700), 188 | $body-1: mat.define-typography-level(16px, 24px, 500) 189 | ); 190 | 191 | @include mat-css-vars.init-material-css-vars($typography-config: $custom-typography) using($mat-css-theme) { 192 | @include app-theme($mat-css-theme); 193 | } 194 | 195 | @mixin app-theme($theme) { 196 | // Your app theme 197 | } 198 | ``` 199 | 200 | ## Legacy components support 201 | 202 | Angular Material v15 introduces MDC based components, which is basically a re-write for a lot of the available components. `angular-material-css-vars` v5+ only supports MDC components. 203 | 204 | In case you are still using the legacy components, you can use the package [angular-material-css-vars-legacy](https://github.com/json-derulo/angular-material-css-vars-legacy). 205 | 206 | ## Angular compatibility table 207 | 208 | | Angular | angular-material-css-vars | 209 | | ------- | ------------------------- | 210 | | 20 | 9.x | 211 | | 19 | 8.x | 212 | | 18 | 7.x | 213 | | 17 | 6.x | 214 | | 16 | 5.x | 215 | | 15 | 4.x | 216 | | 13/14 | 3.x | 217 | | 12 | 2.x | 218 | | 11 | 1.x | 219 | 220 | ## Credit... 221 | 222 | ...goes to @zbirizdo [project](https://github.com/zbirizdo/material-css-vars) on which parts of this are based which is in turn supposedly based on [this gist](https://gist.github.com/shprink/c7f333e3ad51830f14a6383f3ab35439). 223 | 224 | ...and to @pedrojrivera without whom there would be no support for @angular/material v12. 225 | 226 | ## Development server 227 | 228 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 229 | -------------------------------------------------------------------------------- /additional-hacks.md: -------------------------------------------------------------------------------- 1 | ## Custom elevation shadow color 2 | 3 | In case you also want to add a color to your shadows, you can include the following mixin directly before you execute `@include initMaterialCssVars();`. Thanks goes to @picc09 for finding this out. 4 | 5 | ```scss 6 | $mat-css-palette-foreground: map-merge( 7 | // if you don't want to enter ALL the properties 8 | $mat-css-palette-foreground, 9 | ( 10 | elevation: var(--your-elevation-color-variable), 11 | ) 12 | ); 13 | 14 | @mixin mat-elevation($zValue, $color: $mat-elevation-color, $opacity: $mat-elevation-opacity) { 15 | @if type-of($zValue) != number or not unitless($zValue) { 16 | @error '$zValue must be a unitless number'; 17 | } 18 | @if $zValue < 0 or $zValue > 24 { 19 | @error '$zValue must be between 0 and 24'; 20 | } 21 | 22 | $color-umbra: $color; 23 | $color-penumbra: $color; 24 | $color-ambient: $color; 25 | @if type-of($color) != color { 26 | $color-umbra: rgba($color, $opacity * 0.2); 27 | $color-penumbra: rgba($color, $opacity * 0.14); 28 | $color-ambient: rgba($color, $opacity * 0.12); 29 | } 30 | 31 | box-shadow: 32 | #{map-get(_get-umbra-map($color-umbra, $opacity), $zValue)}, 33 | #{map-get(_get-penumbra-map($color-penumbra, $opacity), $zValue)}, 34 | #{map-get(_get-ambient-map($color-ambient, $opacity), $zValue)}; 35 | } 36 | 37 | // finally initialize 38 | @include initMaterialCssVars(); 39 | ``` 40 | 41 | In this way you can change `elevation: black` into variables file. 42 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-material-css-vars": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular/build:application", 19 | "options": { 20 | "outputPath": "dist/angular-material-css-vars", 21 | "index": "src/index.html", 22 | "browser": "src/main.ts", 23 | "polyfills": ["zone.js"], 24 | "tsConfig": "tsconfig.app.json", 25 | "assets": ["src/favicon.ico", "src/assets"], 26 | "styles": ["src/styles.scss"], 27 | "scripts": [] 28 | }, 29 | "configurations": { 30 | "production": { 31 | "fileReplacements": [ 32 | { 33 | "replace": "src/environments/environment.ts", 34 | "with": "src/environments/environment.prod.ts" 35 | } 36 | ], 37 | "outputHashing": "all", 38 | "budgets": [ 39 | { 40 | "type": "initial", 41 | "maximumWarning": "2mb", 42 | "maximumError": "5mb" 43 | }, 44 | { 45 | "type": "anyComponentStyle", 46 | "maximumWarning": "6kb" 47 | } 48 | ] 49 | }, 50 | "development": { 51 | "optimization": false, 52 | "extractLicenses": false, 53 | "sourceMap": true, 54 | "namedChunks": true 55 | } 56 | } 57 | }, 58 | "serve": { 59 | "builder": "@angular/build:dev-server", 60 | "options": { 61 | "buildTarget": "angular-material-css-vars:build" 62 | }, 63 | "configurations": { 64 | "production": { 65 | "buildTarget": "angular-material-css-vars:build:production" 66 | }, 67 | "development": { 68 | "buildTarget": "angular-material-css-vars:build:development" 69 | } 70 | }, 71 | "defaultConfiguration": "development" 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular/build:extract-i18n", 75 | "options": { 76 | "buildTarget": "angular-material-css-vars:build" 77 | } 78 | }, 79 | "lint": { 80 | "builder": "@angular-eslint/builder:lint", 81 | "options": { 82 | "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] 83 | } 84 | } 85 | } 86 | }, 87 | "material-css-vars": { 88 | "projectType": "library", 89 | "root": "projects/material-css-vars", 90 | "sourceRoot": "projects/material-css-vars/src", 91 | "prefix": "lib", 92 | "architect": { 93 | "build": { 94 | "builder": "@angular/build:ng-packagr", 95 | "options": { 96 | "tsConfig": "projects/material-css-vars/tsconfig.lib.json", 97 | "project": "projects/material-css-vars/ng-package.json" 98 | } 99 | }, 100 | "test": { 101 | "builder": "@angular/build:karma", 102 | "options": { 103 | "codeCoverage": true, 104 | "main": "projects/material-css-vars/src/test.ts", 105 | "tsConfig": "projects/material-css-vars/tsconfig.spec.json", 106 | "karmaConfig": "projects/material-css-vars/karma.conf.js", 107 | "styles": ["projects/material-css-vars/src/test/global-styles.scss"] 108 | } 109 | }, 110 | "lint": { 111 | "builder": "@angular-eslint/builder:lint", 112 | "options": { 113 | "lintFilePatterns": [ 114 | "projects/material-css-vars/**/*.ts", 115 | "projects/material-css-vars/**/*.html" 116 | ] 117 | } 118 | } 119 | } 120 | } 121 | }, 122 | "cli": { 123 | "analytics": false, 124 | "cache": { 125 | "enabled": true 126 | }, 127 | "schematicCollections": ["@angular-eslint/schematics"] 128 | }, 129 | "schematics": { 130 | "@schematics/angular:component": { 131 | "type": "component" 132 | }, 133 | "@schematics/angular:directive": { 134 | "type": "directive" 135 | }, 136 | "@schematics/angular:service": { 137 | "type": "service" 138 | }, 139 | "@schematics/angular:guard": { 140 | "typeSeparator": "." 141 | }, 142 | "@schematics/angular:interceptor": { 143 | "typeSeparator": "." 144 | }, 145 | "@schematics/angular:module": { 146 | "typeSeparator": "." 147 | }, 148 | "@schematics/angular:pipe": { 149 | "typeSeparator": "." 150 | }, 151 | "@schematics/angular:resolver": { 152 | "typeSeparator": "." 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import tseslint from "typescript-eslint"; 2 | import angularEslint from "angular-eslint"; 3 | 4 | export default tseslint.config( 5 | { 6 | files: ["**/*.ts"], 7 | extends: [ 8 | ...tseslint.configs.strictTypeChecked, 9 | ...tseslint.configs.stylisticTypeChecked, 10 | ...angularEslint.configs.tsRecommended, 11 | ], 12 | processor: angularEslint.processInlineTemplates, 13 | languageOptions: { 14 | parserOptions: { 15 | project: true, 16 | }, 17 | }, 18 | rules: { 19 | "@typescript-eslint/restrict-template-expressions": "off", 20 | "@typescript-eslint/restrict-plus-operands": "off", 21 | "@angular-eslint/component-selector": [ 22 | "error", 23 | { 24 | prefix: "app", 25 | style: "kebab-case", 26 | type: "element", 27 | }, 28 | ], 29 | "@angular-eslint/directive-selector": [ 30 | "error", 31 | { 32 | prefix: "app", 33 | style: "camelCase", 34 | type: "attribute", 35 | }, 36 | ], 37 | }, 38 | }, 39 | { 40 | files: ["**/*.spec.ts"], 41 | rules: { 42 | "@typescript-eslint/dot-notation": "off", 43 | }, 44 | }, 45 | { 46 | files: ["**/*.html"], 47 | extends: [ 48 | ...angularEslint.configs.templateRecommended, 49 | ...angularEslint.configs.templateAccessibility, 50 | ], 51 | rules: { 52 | "@angular-eslint/template/no-call-expression": "error", 53 | "@angular-eslint/template/no-duplicate-attributes": "error", 54 | "@angular-eslint/template/no-interpolation-in-attributes": "error", 55 | "@angular-eslint/template/use-track-by-function": "error", 56 | "@angular-eslint/template/prefer-self-closing-tags": "error", 57 | }, 58 | }, 59 | ); 60 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannesjo/angular-material-css-vars/ca5e9918ed4bd28f215aa851ed34fbf882ac7e3b/logo.png -------------------------------------------------------------------------------- /logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannesjo/angular-material-css-vars/ca5e9918ed4bd28f215aa851ed34fbf882ac7e3b/logo.xcf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-material-css-vars", 3 | "version": "9.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "ubl": "npx browserslist@latest --update-db", 11 | "demo": "run-s demo.build demo.gh-pages", 12 | "demo.build": "ng build --aot --configuration production --base-href='./'", 13 | "demo.gh-pages": "gh-pages -d dist/angular-material-css-vars/browser", 14 | "lib": "run-s lib.build copy", 15 | "lib.build": "ng build material-css-vars", 16 | "copy": "run-s copy.licence copy.readme", 17 | "copy.licence": "copyfiles ./LICENSE ./dist/material-css-vars", 18 | "copy.readme": "copyfiles ./README.md ./dist/material-css-vars", 19 | "pub": "run-s lib && cd ./dist/material-css-vars/ && npm publish && cd .. && cd ..", 20 | "patch": "npm version patch && cd ./projects/material-css-vars && npm version patch && cd .. && cd .. && git add . && git commit -am\"chore: update lib version patch\"", 21 | "minor": "npm version minor && cd ./projects/material-css-vars && npm version minor && cd .. && cd .. && git add . && git commit -am\"chore: update lib version minor\"", 22 | "major": "npm version major && cd ./projects/material-css-vars && npm version major && cd .. && cd .. && git add . && git commit -am\"chore: update lib version major\"", 23 | "patch-release_": "run-s lib demo patch pub", 24 | "patch-release": "npm run patch-release_", 25 | "minor-release_": "run-s lib demo minor pub", 26 | "minor-release": "npm run minor-release_", 27 | "major-release_": "run-s lib demo major pub", 28 | "major-release": "npm run major-release_", 29 | "dryBuild": "run-s lib demo", 30 | "update": "npx npm-check-updates -u", 31 | "prettier-check": "prettier . --check", 32 | "prettier-fix": "prettier . --write" 33 | }, 34 | "dependencies": { 35 | "@angular/cdk": "^20.0.0", 36 | "@angular/common": "^20.0.0", 37 | "@angular/compiler": "^20.0.0", 38 | "@angular/core": "^20.0.0", 39 | "@angular/forms": "^20.0.0", 40 | "@angular/material": "^20.0.0", 41 | "@angular/platform-browser": "^20.0.0", 42 | "@angular/platform-browser-dynamic": "^20.0.0", 43 | "@angular/router": "^20.0.0", 44 | "@ctrl/tinycolor": "^4.1.0", 45 | "@types/node": "^22.15.23", 46 | "ngx-color-picker": "^19.0.0", 47 | "rxjs": "~7.8.2", 48 | "tslib": "^2.8.1", 49 | "zone.js": "~0.15.1" 50 | }, 51 | "devDependencies": { 52 | "@angular/build": "^20.0.0", 53 | "@angular/cli": "^20.0.0", 54 | "@angular/compiler-cli": "^20.0.0", 55 | "@angular/language-service": "^20.0.0", 56 | "@types/jasmine": "^5.1.8", 57 | "angular-eslint": "^19.6.0", 58 | "copyfiles": "^2.4.1", 59 | "eslint": "^9.27.0", 60 | "gh-pages": "^6.3.0", 61 | "jasmine-core": "^5.7.1", 62 | "karma": "^6.4.4", 63 | "karma-chrome-launcher": "~3.2.0", 64 | "karma-coverage": "^2.2.1", 65 | "karma-firefox-launcher": "^2.1.3", 66 | "karma-jasmine": "~5.1.0", 67 | "karma-jasmine-html-reporter": "^2.1.0", 68 | "karma-safarinative-launcher": "^1.1.0", 69 | "ng-packagr": "^20.0.0", 70 | "npm-run-all": "^4.1.5", 71 | "prettier": "^3.5.3", 72 | "typescript": "~5.8.3", 73 | "typescript-eslint": "^8.33.0" 74 | }, 75 | "engines": { 76 | "npm": "Please use pnpm instead of npm to install dependencies", 77 | "yarn": "Please use pnpm instead of yarn to install dependencies", 78 | "pnpm": "^10.7.0" 79 | }, 80 | "pnpm": { 81 | "overrides": { 82 | "jasmine-core": "$jasmine-core" 83 | } 84 | }, 85 | "packageManager": "pnpm@10.7.0" 86 | } 87 | -------------------------------------------------------------------------------- /projects/material-css-vars/README.md: -------------------------------------------------------------------------------- 1 | # Angular Material CSS Vars 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.1.3. 4 | 5 | ## Code scaffolding 6 | 7 | Run `ng generate component component-name --project material-css-vars` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project material-css-vars`. 8 | 9 | > Note: Don't forget to add `--project material-css-vars` or else it will be added to the default project in your `angular.json` file. 10 | 11 | ## Build 12 | 13 | Run `ng build material-css-vars` to build the project. The build artifacts will be stored in the `dist/` directory. 14 | 15 | ## Publishing 16 | 17 | After building your library with `ng build material-css-vars`, go to the dist folder `cd dist/material-css-vars` and run `npm publish`. 18 | 19 | ## Running unit tests 20 | 21 | Run `ng test material-css-vars` to execute the unit tests via [Karma](https://karma-runner.github.io). 22 | 23 | ## Further help 24 | 25 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 26 | -------------------------------------------------------------------------------- /projects/material-css-vars/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import baseConfig from "../../eslint.config.mjs"; 2 | 3 | export default [ 4 | ...baseConfig, 5 | { 6 | files: "**/*.ts", 7 | rules: { 8 | "@angular-eslint/directive-selector": [ 9 | "error", 10 | { 11 | type: "attribute", 12 | prefix: "lib", 13 | style: "camelCase", 14 | }, 15 | ], 16 | "@angular-eslint/component-selector": [ 17 | "error", 18 | { 19 | type: "element", 20 | prefix: "lib", 21 | style: "kebab-case", 22 | }, 23 | ], 24 | }, 25 | }, 26 | ]; 27 | -------------------------------------------------------------------------------- /projects/material-css-vars/index.scss: -------------------------------------------------------------------------------- 1 | @forward "./src/lib/variables"; 2 | @forward "./src/lib/main"; 3 | @forward "./src/lib/public-util"; 4 | @forward "./src/lib/public-color-util"; 5 | -------------------------------------------------------------------------------- /projects/material-css-vars/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: "", 7 | frameworks: ["jasmine"], 8 | plugins: [ 9 | require("karma-jasmine"), 10 | require("karma-coverage"), 11 | require("karma-chrome-launcher"), 12 | require("karma-firefox-launcher"), 13 | require("karma-safarinative-launcher"), 14 | require("karma-jasmine-html-reporter"), 15 | ], 16 | client: { 17 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 18 | }, 19 | coverageReporter: { 20 | dir: require("path").join(__dirname, "../../coverage/material-css-vars"), 21 | subdir: ".", 22 | reporters: [{ type: "html" }, { type: "text-summary" }], 23 | }, 24 | reporters: ["progress", "kjhtml"], 25 | port: 9876, 26 | colors: true, 27 | logLevel: config.LOG_INFO, 28 | autoWatch: true, 29 | browsers: ["Chrome"], 30 | singleRun: false, 31 | restartOnFileChange: true, 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /projects/material-css-vars/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/material-css-vars", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | }, 7 | "allowedNonPeerDependencies": ["tinycolor2", "@ctrl/tinycolor"], 8 | "assets": ["./index.scss", "./src/lib/*.scss"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/material-css-vars/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-material-css-vars", 3 | "version": "9.0.0", 4 | "description": "Little library to use css variables for @angular/material", 5 | "author": "johannesjo (http://super-productivity.com)", 6 | "contributors": [ 7 | { 8 | "name": "Daniel Kimmich", 9 | "email": "json-derulo@outlook.com", 10 | "url": "https://github.com/json-derulo" 11 | } 12 | ], 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/johannesjo/angular-material-css-vars" 17 | }, 18 | "keywords": [ 19 | "angular", 20 | "javascript", 21 | "typescript", 22 | "material", 23 | "css", 24 | "css variables", 25 | "angular-material" 26 | ], 27 | "peerDependencies": { 28 | "@angular/common": ">=20", 29 | "@angular/core": ">=20", 30 | "@angular/material": ">=20" 31 | }, 32 | "dependencies": { 33 | "@ctrl/tinycolor": "^4.0.0", 34 | "tslib": "^2.3.0" 35 | }, 36 | "exports": { 37 | ".": { 38 | "sass": "./index.scss" 39 | }, 40 | "./main": { 41 | "sass": "./src/lib/_main.scss" 42 | }, 43 | "./public-util": { 44 | "sass": "./src/lib/_public-util.scss" 45 | }, 46 | "./variables": { 47 | "sass": "./src/lib/_variables.scss" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/_internal-helper.scss: -------------------------------------------------------------------------------- 1 | @use "sass:meta"; 2 | 3 | // used to circumvent node sass issue: https://github.com/sass/node-sass/issues/2251 4 | @function rgb($r, $g: null, $b: null) { 5 | @if ($g == null) { 6 | @return unquote("#{$r}"); 7 | } 8 | 9 | @if ($g and $b) { 10 | @return unquote("#{$r}, #{$g}, #{$b}"); 11 | } 12 | 13 | @error "wrong number of arguments"; 14 | } 15 | 16 | // used to circumvent node sass issue: https://github.com/sass/node-sass/issues/2251 17 | @function rgba($r, $g, $b: null, $a: null) { 18 | @if ($b == null) { 19 | @return unquote("#{$r}, #{$g}"); 20 | } 21 | 22 | @if ($b and $a) { 23 | @return unquote("#{$r}, #{$g}, #{$b}, #{$a}"); 24 | } 25 | 26 | @error "wrong number of arguments"; 27 | } 28 | 29 | @function str-replace($string, $search, $replace: "") { 30 | $index: str-index($string, $search); 31 | 32 | @if $index { 33 | @return str-slice($string, 1, $index - 1) + $replace + 34 | str-replace( 35 | str-slice($string, $index + str-length($search)), 36 | $search, 37 | $replace 38 | ); 39 | } 40 | 41 | @return $string; 42 | } 43 | 44 | @mixin root($varMap: null) { 45 | @at-root :root { 46 | @each $varName, $varValue in $varMap { 47 | @if (meta.type-of($varValue) == string) { 48 | #{$varName}: $varValue; // to prevent quotes interpolation 49 | } @else { 50 | #{$varName}: #{$varValue}; 51 | } 52 | } 53 | } 54 | } 55 | 56 | @function strip-variable($color) { 57 | $var: $color; 58 | @if (str-index($var, "rgba(")) { 59 | $var: str-replace($var, "rgba(", ""); 60 | } 61 | @if (str-index($var, "rgb(")) { 62 | $var: str-replace($var, "rgb(", ""); 63 | } 64 | @if (str-index($var, "var(")) { 65 | $var: str-replace($var, ")", ""); // remove excess ")" 66 | $var: $var + ")"; 67 | } 68 | @return $var; 69 | } 70 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/_main.scss: -------------------------------------------------------------------------------- 1 | @use "@angular/material" as mat; 2 | @use "variables"; 3 | @use "internal-helper"; 4 | @use "public-util"; 5 | // contains main overwrite of `mat-color` to make it all work 6 | // needs to come after "@angular/material" 7 | @use "mat-lib-overwrites"; 8 | 9 | @mixin init-css-vars($default-theme, $text) { 10 | // init css variables 11 | @include internal-helper.root($text); 12 | @include internal-helper.root($default-theme); 13 | } 14 | 15 | @mixin init-mat-theme( 16 | $dark-theme-selector, 17 | $typography-config: mat.m2-define-typography-config(), 18 | $density 19 | ) { 20 | @include mat.elevation-classes(); 21 | @include mat.app-background(); 22 | 23 | $primary: mat.m2-define-palette(variables.$palette-primary); 24 | $accent: mat.m2-define-palette(variables.$palette-accent); 25 | $warn: mat.m2-define-palette(variables.$palette-warn); 26 | 27 | $theme: mat.m2-define-light-theme( 28 | ( 29 | color: ( 30 | primary: $primary, 31 | accent: $accent, 32 | warn: $warn, 33 | ), 34 | typography: $typography-config, 35 | density: $density, 36 | ) 37 | ); 38 | 39 | $dark-theme: mat.m2-define-dark-theme( 40 | ( 41 | color: ( 42 | primary: $primary, 43 | accent: $accent, 44 | warn: $warn, 45 | ), 46 | typography: $typography-config, 47 | density: $density, 48 | ) 49 | ); 50 | 51 | // set global variable so passed-in content can use the theme 52 | $mat-css-theme: $theme !global; // stays for backwards-compatibility 53 | 54 | // NOTE: light theme is the default theme 55 | @include mat.all-component-themes($theme); 56 | 57 | // Fix mat-typography class, see https://github.com/angular/components/issues/26184 58 | @include mat.typography-hierarchy($theme); 59 | 60 | @content ($mat-css-theme); 61 | 62 | @if $dark-theme-selector { 63 | $mat-css-theme: $dark-theme !global; 64 | #{$dark-theme-selector} { 65 | @include mat.all-component-colors($dark-theme); 66 | // add content passed in, which can use the $theme variable to apply the dark theme to 67 | // other theme mixins needed by the app 68 | @content ($mat-css-theme); 69 | } 70 | } 71 | 72 | @include mat-lib-overwrites.other-overwrites; 73 | 74 | $mat-css-theme: null !global; 75 | } 76 | 77 | @mixin init-material-css-vars( 78 | $default-theme: variables.$default-light-theme, 79 | $dark-theme-selector: variables.$dark-theme-selector, 80 | $default-theme-text: variables.$text, 81 | $typography-config: mat.m2-define-typography-config(), 82 | $density: 0 83 | ) { 84 | @include init-css-vars($default-theme, $default-theme-text); 85 | @include init-mat-theme($dark-theme-selector, $typography-config, $density) 86 | using ($mat-css-theme) { 87 | @content ($mat-css-theme); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/_mat-lib-overwrites.scss: -------------------------------------------------------------------------------- 1 | @use "@angular/material" as mat; 2 | @use "public-util"; 3 | @use "variables"; 4 | 5 | // --------------------------- 6 | // MDC Overwrites 7 | // --------------------------- 8 | @mixin other-overwrites { 9 | @include _mat-mdc-button-overwrites(); 10 | @include _mat-mdc-slide-toggle-overwrites(); 11 | @include _mat-mdc-checkbox-overwrites(); 12 | @include _mat-mdc-date-picker-overwrites(); 13 | @include _mat-mdc-progress-bar-overwrites(); 14 | @include _mat-mdc-slider-overwrites(); 15 | } 16 | 17 | // --------------------------- 18 | // BUTTON component 19 | // --------------------------- 20 | @mixin _mat-mdc-button-overwrites { 21 | .mat-mdc-button-base { 22 | @include public-util.mat-css-light-dark-theme-global { 23 | &.mat-primary { 24 | @include _mat-mdc-button-color-overwrites("primary"); 25 | } 26 | &.mat-accent { 27 | @include _mat-mdc-button-color-overwrites("accent"); 28 | } 29 | &.mat-warn { 30 | @include _mat-mdc-button-color-overwrites("warn"); 31 | } 32 | } 33 | } 34 | } 35 | 36 | @mixin _mat-mdc-button-color-overwrites($palette) { 37 | $contrast-color: public-util.mat-css-color(500, null, $palette, true); 38 | $ripple-color: public-util.mat-css-color(500, 0.1, $palette); 39 | 40 | @include mat.fab-overrides( 41 | ( 42 | foreground-color: $contrast-color, 43 | state-layer-color: $contrast-color, 44 | small-foreground-color: $contrast-color, 45 | small-state-layer-color: $contrast-color, 46 | ) 47 | ); 48 | 49 | @include mat.button-overrides( 50 | ( 51 | outlined-ripple-color: $ripple-color, 52 | text-ripple-color: $ripple-color, 53 | filled-label-text-color: $contrast-color, 54 | protected-label-text-color: $contrast-color, 55 | ) 56 | ); 57 | 58 | @include mat.icon-button-overrides( 59 | ( 60 | ripple-color: $ripple-color, 61 | ) 62 | ); 63 | } 64 | 65 | // --------------------------- 66 | // SLIDE TOGGLE component 67 | // --------------------------- 68 | @mixin _mat-mdc-slide-toggle-overwrites() { 69 | .mat-mdc-slide-toggle { 70 | @include public-util.mat-css-light-dark-theme-global { 71 | &.mat-primary { 72 | @include _mat-mdc-slide-toggle-color-overwrites("primary"); 73 | } 74 | &.mat-accent { 75 | @include _mat-mdc-slide-toggle-color-overwrites("accent"); 76 | } 77 | &.mat-warn { 78 | @include _mat-mdc-slide-toggle-color-overwrites("warn"); 79 | } 80 | } 81 | } 82 | } 83 | 84 | @mixin _mat-mdc-slide-toggle-color-overwrites($palette) { 85 | $contrast-color: public-util.mat-css-color(500, null, $palette, true); 86 | 87 | @include mat.slide-toggle-overrides( 88 | ( 89 | selected-icon-color: $contrast-color, 90 | ) 91 | ); 92 | } 93 | 94 | // --------------------------- 95 | // CHECKBOX component 96 | // --------------------------- 97 | @mixin _mat-mdc-checkbox-overwrites { 98 | .mat-mdc-checkbox { 99 | @include public-util.mat-css-light-dark-theme-global { 100 | &.mat-primary { 101 | @include _mat-mdc-checkbox-color-overwrites("primary"); 102 | } 103 | &.mat-accent { 104 | @include _mat-mdc-checkbox-color-overwrites("accent"); 105 | } 106 | &.mat-warn { 107 | @include _mat-mdc-checkbox-color-overwrites("warn"); 108 | } 109 | } 110 | } 111 | } 112 | 113 | @mixin _mat-mdc-checkbox-color-overwrites($palette) { 114 | $contrast-color: public-util.mat-css-color(500, null, $palette, true); 115 | 116 | @include mat.checkbox-overrides( 117 | ( 118 | selected-checkmark-color: $contrast-color, 119 | ) 120 | ); 121 | } 122 | 123 | // --------------------------- 124 | // DATE PICKER component 125 | // --------------------------- 126 | @mixin _mat-mdc-date-picker-overwrites { 127 | .mat-datepicker-content { 128 | @include public-util.mat-css-light-dark-theme-global { 129 | &.mat-primary { 130 | @include _mat-date-picker-color-overwrites("primary"); 131 | } 132 | &.mat-accent { 133 | @include _mat-date-picker-color-overwrites("accent"); 134 | } 135 | &.mat-warn { 136 | @include _mat-date-picker-color-overwrites("warn"); 137 | } 138 | } 139 | } 140 | } 141 | 142 | @mixin _mat-date-picker-color-overwrites($palette) { 143 | $date-background-color: public-util.mat-css-color(500, 0.3, $palette); 144 | $range-background-color: public-util.mat-css-color(500, 0.2, $palette); 145 | 146 | @include mat.datepicker-overrides( 147 | ( 148 | calendar-date-focus-state-background-color: $date-background-color, 149 | calendar-date-hover-state-background-color: $date-background-color, 150 | calendar-date-in-range-state-background-color: $range-background-color, 151 | ) 152 | ); 153 | } 154 | 155 | // --------------------------- 156 | // PROGRESS BAR component 157 | // --------------------------- 158 | @mixin _mat-mdc-progress-bar-overwrites { 159 | .mat-mdc-progress-bar { 160 | @include public-util.mat-css-light-dark-theme-global { 161 | &.mat-primary { 162 | @include _mat-mdc-progress-bar-color-overwrites("primary"); 163 | } 164 | &.mat-accent { 165 | @include _mat-mdc-progress-bar-color-overwrites("accent"); 166 | } 167 | &.mat-warn { 168 | @include _mat-mdc-progress-bar-color-overwrites("warn"); 169 | } 170 | } 171 | } 172 | } 173 | 174 | @mixin _mat-mdc-progress-bar-color-overwrites($palette) { 175 | $track-color: public-util.mat-css-color(500, 0.25, $palette); 176 | 177 | @include mat.progress-bar-overrides( 178 | ( 179 | track-color: $track-color, 180 | ) 181 | ); 182 | } 183 | 184 | // --------------------------- 185 | // SLIDER component 186 | // --------------------------- 187 | @mixin _mat-mdc-slider-overwrites() { 188 | .mat-mdc-slider { 189 | @include public-util.mat-css-light-dark-theme-global { 190 | &.mat-primary { 191 | @include _mat-mdc-slider-color-overwrites("primary"); 192 | } 193 | &.mat-accent { 194 | @include _mat-mdc-slider-color-overwrites("accent"); 195 | } 196 | &.mat-warn { 197 | @include _mat-mdc-slider-color-overwrites("warn"); 198 | } 199 | } 200 | } 201 | } 202 | 203 | @mixin _mat-mdc-slider-color-overwrites($palette) { 204 | $hover-background-color: public-util.mat-css-color(500, 0.05, $palette); 205 | $focus-background-color: public-util.mat-css-color(500, 0.2, $palette); 206 | 207 | @include mat.slider-overrides( 208 | ( 209 | hover-state-layer-color: $hover-background-color, 210 | focus-state-layer-color: $focus-background-color, 211 | ) 212 | ); 213 | } 214 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/_public-color-util.scss: -------------------------------------------------------------------------------- 1 | @use "sass:color"; 2 | 3 | @function hex-to-rgb($color) { 4 | @if ($color == null) { 5 | @return null; 6 | } 7 | @return color.channel($color, "red"), color.channel($color, "green"), 8 | color.channel($color, "blue"); 9 | } 10 | 11 | @function hex-to-rgba($color) { 12 | @if ($color == null) { 13 | @return null; 14 | } 15 | @return red($color), green($color), blue($color), alpha($color); 16 | } 17 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/_public-util.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | @use "sass:meta"; 3 | @use "public-color-util"; 4 | @use "variables"; 5 | 6 | // Utility mixins for the public 7 | 8 | // colors 9 | // ------ 10 | @function mat-css-color( 11 | $hue: 500, 12 | $opacity: null, 13 | $palette: "primary", 14 | $is-contrast-color: false 15 | ) { 16 | // If opacity is being provided, we need to to use *-no-rgb pallets 17 | // in order to return a proper variable value. 18 | @if ($opacity != null) { 19 | @return _mat-css-color-no-rgb($hue, $opacity, $palette, $is-contrast-color); 20 | } 21 | $palette_: variables.$palette-primary; 22 | @if ($is-contrast-color == true) { 23 | @if ($palette== "primary") { 24 | $palette_: variables.$contrast-palette; 25 | } @else if ($palette== "accent") { 26 | $palette_: variables.$contrast-palette-accent; 27 | } @else if ($palette== "warn") { 28 | $palette_: variables.$contrast-palette-warn; 29 | } @else { 30 | @error "Invalid contrast palette"; 31 | } 32 | } @else { 33 | @if ($palette== "primary") { 34 | $palette_: variables.$palette-primary; 35 | } @else if ($palette== "accent") { 36 | $palette_: variables.$palette-accent; 37 | } @else if ($palette== "warn") { 38 | $palette_: variables.$palette-warn; 39 | } @else { 40 | @error "Invalid palette"; 41 | } 42 | } 43 | 44 | $color: map.get($palette_, $hue); 45 | 46 | @if (meta.type-of($opacity) == number) { 47 | @return rgba($color, $opacity); 48 | } @else { 49 | @return $color; 50 | } 51 | } 52 | 53 | @function _mat-css-color-no-rgb( 54 | $hue: 500, 55 | $opacity: null, 56 | $palette: "primary", 57 | $is-contrast-color: false 58 | ) { 59 | $palette_: variables.$palette-primary-no-rgb; 60 | @if ($is-contrast-color == true) { 61 | @if ($palette== "primary") { 62 | $palette_: variables.$contrast-palette-no-rgb; 63 | } @else if ($palette== "accent") { 64 | $palette_: variables.$contrast-palette-accent-no-rgb; 65 | } @else if ($palette== "warn") { 66 | $palette_: variables.$contrast-palette-warn-no-rgb; 67 | } @else { 68 | @error "Invalid contrast palette"; 69 | } 70 | } @else { 71 | @if ($palette== "primary") { 72 | $palette_: variables.$palette-primary-no-rgb; 73 | } @else if ($palette== "accent") { 74 | $palette_: variables.$palette-accent-no-rgb; 75 | } @else if ($palette== "warn") { 76 | $palette_: variables.$palette-warn-no-rgb; 77 | } @else { 78 | @error "Invalid palette"; 79 | } 80 | } 81 | $var: map.get($palette_, $hue); 82 | @return #{rgba($var, $opacity)}; 83 | } 84 | 85 | @function mat-css-color-primary($hue: 500, $opacity: null) { 86 | @return mat-css-color($hue, $opacity, "primary"); 87 | } 88 | 89 | @function mat-css-color-accent($hue: 500, $opacity: null) { 90 | @return mat-css-color($hue, $opacity, "accent"); 91 | } 92 | 93 | @function mat-css-color-warn($hue: 500, $opacity: null) { 94 | @return mat-css-color($hue, $opacity, "warn"); 95 | } 96 | 97 | // contrast-colors 98 | // --------------- 99 | @function mat-css-contrast-color( 100 | $hue: 500, 101 | $opacity: null, 102 | $palette: "primary" 103 | ) { 104 | @return mat-css-color($hue, $opacity, $palette, true); 105 | } 106 | 107 | @function mat-css-contrast-color-primary($hue: 500, $opacity: null) { 108 | @return mat-css-contrast-color($hue, $opacity, "primary"); 109 | } 110 | 111 | @function mat-css-contrast-color-accent($hue: 500, $opacity: null) { 112 | @return mat-css-contrast-color($hue, $opacity, "accent"); 113 | } 114 | 115 | @function mat-css-contrast-color-warn($hue: 500, $opacity: null) { 116 | @return mat-css-contrast-color($hue, $opacity, "warn"); 117 | } 118 | 119 | // mixins 120 | // ------ 121 | @mixin mat-css-color-and-contrast-primary($hue) { 122 | background: mat-css-color-primary($hue); 123 | color: mat-css-contrast-color($hue); 124 | } 125 | 126 | @mixin mat-css-color-and-contrast-accent($hue) { 127 | background: mat-css-color-accent($hue); 128 | color: mat-css-contrast-color-accent($hue); 129 | } 130 | 131 | @mixin mat-css-color-and-contrast-warn($hue) { 132 | background: mat-css-color-warn($hue); 133 | color: mat-css-contrast-color-warn($hue); 134 | } 135 | 136 | @mixin mat-css-bg($hue) { 137 | background: mat-css-color-primary($hue); 138 | } 139 | 140 | @mixin mat-css-dark-theme { 141 | :host-context(#{variables.$dark-theme-selector}) & { 142 | @content; 143 | } 144 | } 145 | 146 | @mixin mat-css-light-theme { 147 | :host-context(#{variables.$light-theme-selector}) & { 148 | @content; 149 | } 150 | } 151 | 152 | @mixin mat-css-dark-theme-global { 153 | @if variables.$dark-theme-selector { 154 | #{variables.$dark-theme-selector} & { 155 | @content; 156 | } 157 | } 158 | } 159 | 160 | @mixin mat-css-light-theme-global { 161 | #{variables.$light-theme-selector} & { 162 | @content; 163 | } 164 | } 165 | 166 | // Short hand for both light and dark selectors 167 | @mixin mat-css-light-dark-theme-global { 168 | #{variables.$light-theme-selector} & { 169 | @content; 170 | } 171 | @if variables.$dark-theme-selector { 172 | #{variables.$dark-theme-selector} & { 173 | @content; 174 | } 175 | } 176 | } 177 | 178 | @mixin mat-css-set-palette-defaults($css-var-map, $paletteType: "primary") { 179 | $new-map: (); 180 | @each $var, $defaultVal in $css-var-map { 181 | @if ($var != "contrast") { 182 | $colorVal: public-color-util.hex-to-rgb($defaultVal); 183 | @if $colorVal != null { 184 | $new-map: map.merge( 185 | $new-map, 186 | (--palette-#{$paletteType}-#{$var}: #{$colorVal}) 187 | ); 188 | } 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/_variables.scss: -------------------------------------------------------------------------------- 1 | @use "@angular/material" as mat; 2 | @use "public-color-util"; 3 | @use "sass:map"; 4 | 5 | $dark-theme-selector: ".isDarkTheme" !default; 6 | $light-theme-selector: ".isLightTheme" !default; 7 | 8 | $dark-primary-text-no-rgb: 0, 0, 0, 0.87; 9 | $dark-secondary-text-no-rgb: 0, 0, 0, 0.54; 10 | $dark-disabled-text-no-rgb: 0, 0, 0, 0.38; 11 | $dark-dividers-no-rgb: 0, 0, 0, 0.12; 12 | $dark-focused-no-rgb: 0, 0, 0, 0.12; 13 | $light-primary-text-no-rgb: 255, 255, 255, 1; 14 | $light-secondary-text-no-rgb: 255, 255, 255, 0.7; 15 | $light-disabled-text-no-rgb: 255, 255, 255, 0.5; 16 | $light-dividers-no-rgb: 255, 255, 255, 0.12; 17 | $light-focused-no-rgb: 255, 255, 255, 0.12; 18 | 19 | $text: ( 20 | --dark-primary-text-no-rgb: $dark-primary-text-no-rgb, 21 | --dark-secondary-text-no-rgb: $dark-secondary-text-no-rgb, 22 | --dark-accent-text-no-rgb: $dark-primary-text-no-rgb, 23 | --dark-warn-text-no-rgb: $dark-primary-text-no-rgb, 24 | --dark-disabled-text-no-rgb: $dark-disabled-text-no-rgb, 25 | --dark-dividers-no-rgb: $dark-dividers-no-rgb, 26 | --dark-focused-no-rgb: $dark-focused-no-rgb, 27 | --light-primary-text-no-rgb: $light-primary-text-no-rgb, 28 | --light-secondary-text-no-rgb: $light-secondary-text-no-rgb, 29 | --light-accent-text-no-rgb: $light-primary-text-no-rgb, 30 | --light-warn-text-no-rgb: $light-primary-text-no-rgb, 31 | --light-disabled-text-no-rgb: $light-disabled-text-no-rgb, 32 | --light-dividers-no-rgb: $light-dividers-no-rgb, 33 | --light-focused-no-rgb: $light-focused-no-rgb, 34 | --dark-primary-text: rgba(var(--dark-primary-text-no-rgb)), 35 | --dark-secondary-text: rgba(var(--dark-secondary-text-no-rgb)), 36 | --dark-accent-text: rgba(var(--dark-accent-text-no-rgb)), 37 | --dark-warn-text: rgba(var(--dark-warn-text-no-rgb)), 38 | --dark-disabled-text: rgba(var(--dark-disabled-text-no-rgb)), 39 | --dark-dividers: rgba(var(--dark-dividers-no-rgb)), 40 | --dark-focused: rgba(var(--dark-focused-no-rgb)), 41 | --light-primary-text: rgba(var(--light-primary-text-no-rgb)), 42 | --light-secondary-text: rgba(var(--light-secondary-text-no-rgb)), 43 | --light-accent-text: rgba(var(--light-accent-text-no-rgb)), 44 | --light-warn-text: rgba(var(--light-warn-text-no-rgb)), 45 | --light-disabled-text: rgba(var(--light-disabled-text-no-rgb)), 46 | --light-dividers: rgba(var(--light-dividers-no-rgb)), 47 | --light-focused: rgba(var(--light-focused-no-rgb)), 48 | --dark-text-contrast: #000000, 49 | --light-text-contrast: var(--light-primary-text), 50 | ) !default; 51 | 52 | $default-light-theme: ( 53 | // PRIMARY 54 | --palette-primary-50-no-rgb: public-color-util.hex-to-rgb(#e1f5fe), 55 | --palette-primary-100-no-rgb: public-color-util.hex-to-rgb(#b3e5fc), 56 | --palette-primary-200-no-rgb: public-color-util.hex-to-rgb(#81d4fa), 57 | --palette-primary-300-no-rgb: public-color-util.hex-to-rgb(#4fc3f7), 58 | --palette-primary-400-no-rgb: public-color-util.hex-to-rgb(#29b6f6), 59 | --palette-primary-500-no-rgb: public-color-util.hex-to-rgb(#03a9f4), 60 | --palette-primary-600-no-rgb: public-color-util.hex-to-rgb(#039be5), 61 | --palette-primary-700-no-rgb: public-color-util.hex-to-rgb(#0288d1), 62 | --palette-primary-800-no-rgb: public-color-util.hex-to-rgb(#0277bd), 63 | --palette-primary-900-no-rgb: public-color-util.hex-to-rgb(#01579b), 64 | --palette-primary-A100-no-rgb: public-color-util.hex-to-rgb(#80d8ff), 65 | --palette-primary-A200-no-rgb: public-color-util.hex-to-rgb(#40c4ff), 66 | --palette-primary-A400-no-rgb: public-color-util.hex-to-rgb(#00b0ff), 67 | --palette-primary-A700-no-rgb: public-color-util.hex-to-rgb(#0091ea), 68 | --palette-primary-contrast-50-no-rgb: var(--dark-primary-text-no-rgb), 69 | --palette-primary-contrast-100-no-rgb: var(--dark-primary-text-no-rgb), 70 | --palette-primary-contrast-200-no-rgb: var(--dark-primary-text-no-rgb), 71 | --palette-primary-contrast-300-no-rgb: var(--dark-primary-text-no-rgb), 72 | --palette-primary-contrast-400-no-rgb: var(--dark-primary-text-no-rgb), 73 | --palette-primary-contrast-500-no-rgb: var(--dark-primary-text-no-rgb), 74 | --palette-primary-contrast-600-no-rgb: var(--dark-primary-text-no-rgb), 75 | --palette-primary-contrast-700-no-rgb: var(--dark-primary-text-no-rgb), 76 | --palette-primary-contrast-800-no-rgb: var(--dark-primary-text-no-rgb), 77 | --palette-primary-contrast-900-no-rgb: var(--dark-primary-text-no-rgb), 78 | --palette-primary-contrast-A100-no-rgb: var(--dark-primary-text-no-rgb), 79 | --palette-primary-contrast-A200-no-rgb: var(--dark-primary-text-no-rgb), 80 | --palette-primary-contrast-A400-no-rgb: var(--dark-primary-text-no-rgb), 81 | --palette-primary-contrast-A700-no-rgb: var(--dark-primary-text-no-rgb), 82 | --palette-primary-50: var(--palette-primary-50-rgb), 83 | --palette-primary-100: var(--palette-primary-100-rgb), 84 | --palette-primary-200: var(--palette-primary-200-rgb), 85 | --palette-primary-300: var(--palette-primary-300-rgb), 86 | --palette-primary-400: var(--palette-primary-400-rgb), 87 | --palette-primary-500: var(--palette-primary-500-rgb), 88 | --palette-primary-600: var(--palette-primary-600-rgb), 89 | --palette-primary-700: var(--palette-primary-700-rgb), 90 | --palette-primary-800: var(--palette-primary-800-rgb), 91 | --palette-primary-900: var(--palette-primary-900-rgb), 92 | --palette-primary-A100: var(--palette-primary-A100-rgb), 93 | --palette-primary-A200: var(--palette-primary-A200-rgb), 94 | --palette-primary-A400: var(--palette-primary-A400-rgb), 95 | --palette-primary-A700: var(--palette-primary-A700-rgb), 96 | --palette-primary-contrast-50: var(--palette-primary-contrast-50-rgb), 97 | --palette-primary-contrast-100: var(--palette-primary-contrast-100-rgb), 98 | --palette-primary-contrast-200: var(--palette-primary-contrast-200-rgb), 99 | --palette-primary-contrast-300: var(--palette-primary-contrast-300-rgb), 100 | --palette-primary-contrast-400: var(--palette-primary-contrast-400-rgb), 101 | --palette-primary-contrast-500: var(--palette-primary-contrast-500-rgb), 102 | --palette-primary-contrast-600: var(--palette-primary-contrast-600-rgb), 103 | --palette-primary-contrast-700: var(--palette-primary-contrast-700-rgb), 104 | --palette-primary-contrast-800: var(--palette-primary-contrast-800-rgb), 105 | --palette-primary-contrast-900: var(--palette-primary-contrast-900-rgb), 106 | --palette-primary-contrast-A100: var(--palette-primary-contrast-A100-rgb), 107 | --palette-primary-contrast-A200: var(--palette-primary-contrast-A200-rgb), 108 | --palette-primary-contrast-A400: var(--palette-primary-contrast-A400-rgb), 109 | --palette-primary-contrast-A700: var(--palette-primary-contrast-A700-rgb), 110 | // ACCENT 111 | --palette-accent-50-no-rgb: public-color-util.hex-to-rgb(#fce4ec), 112 | --palette-accent-100-no-rgb: public-color-util.hex-to-rgb(#f8bbd0), 113 | --palette-accent-200-no-rgb: public-color-util.hex-to-rgb(#f48fb1), 114 | --palette-accent-300-no-rgb: public-color-util.hex-to-rgb(#f06292), 115 | --palette-accent-400-no-rgb: public-color-util.hex-to-rgb(#ec407a), 116 | --palette-accent-500-no-rgb: public-color-util.hex-to-rgb(#e91e63), 117 | --palette-accent-600-no-rgb: public-color-util.hex-to-rgb(#d81b60), 118 | --palette-accent-700-no-rgb: public-color-util.hex-to-rgb(#c2185b), 119 | --palette-accent-800-no-rgb: public-color-util.hex-to-rgb(#ad1457), 120 | --palette-accent-900-no-rgb: public-color-util.hex-to-rgb(#880e4f), 121 | --palette-accent-A100-no-rgb: public-color-util.hex-to-rgb(#ff80ab), 122 | --palette-accent-A200-no-rgb: public-color-util.hex-to-rgb(#ff4081), 123 | --palette-accent-A400-no-rgb: public-color-util.hex-to-rgb(#f50057), 124 | --palette-accent-A700-no-rgb: public-color-util.hex-to-rgb(#c51162), 125 | --palette-accent-contrast-50-no-rgb: var(--dark-accent-text-no-rgb), 126 | --palette-accent-contrast-100-no-rgb: var(--dark-accent-text-no-rgb), 127 | --palette-accent-contrast-200-no-rgb: var(--dark-accent-text-no-rgb), 128 | --palette-accent-contrast-300-no-rgb: var(--dark-accent-text-no-rgb), 129 | --palette-accent-contrast-400-no-rgb: var(--dark-accent-text-no-rgb), 130 | --palette-accent-contrast-500-no-rgb: var(--dark-accent-text-no-rgb), 131 | --palette-accent-contrast-600-no-rgb: var(--dark-accent-text-no-rgb), 132 | --palette-accent-contrast-700-no-rgb: var(--light-accent-text-no-rgb), 133 | --palette-accent-contrast-800-no-rgb: var(--light-accent-text-no-rgb), 134 | --palette-accent-contrast-900-no-rgb: var(--light-accent-text-no-rgb), 135 | --palette-accent-contrast-A100-no-rgb: var(--dark-accent-text-no-rgb), 136 | --palette-accent-contrast-A200-no-rgb: var(--dark-accent-text-no-rgb), 137 | --palette-accent-contrast-A400-no-rgb: var(--dark-accent-text-no-rgb), 138 | --palette-accent-contrast-A700-no-rgb: var(--light-accent-text-no-rgb), 139 | --palette-accent-50: var(--palette-accent-50-rgb), 140 | --palette-accent-100: var(--palette-accent-100-rgb), 141 | --palette-accent-200: var(--palette-accent-200-rgb), 142 | --palette-accent-300: var(--palette-accent-300-rgb), 143 | --palette-accent-400: var(--palette-accent-400-rgb), 144 | --palette-accent-500: var(--palette-accent-500-rgb), 145 | --palette-accent-600: var(--palette-accent-600-rgb), 146 | --palette-accent-700: var(--palette-accent-700-rgb), 147 | --palette-accent-800: var(--palette-accent-800-rgb), 148 | --palette-accent-900: var(--palette-accent-900-rgb), 149 | --palette-accent-A100: var(--palette-accent-A100-rgb), 150 | --palette-accent-A200: var(--palette-accent-A200-rgb), 151 | --palette-accent-A400: var(--palette-accent-A400-rgb), 152 | --palette-accent-A700: var(--palette-accent-A700-rgb), 153 | --palette-accent-contrast-50: var(--palette-accent-contrast-50-rgb), 154 | --palette-accent-contrast-100: var(--palette-accent-contrast-100-rgb), 155 | --palette-accent-contrast-200: var(--palette-accent-contrast-200-rgb), 156 | --palette-accent-contrast-300: var(--palette-accent-contrast-300-rgb), 157 | --palette-accent-contrast-400: var(--palette-accent-contrast-400-rgb), 158 | --palette-accent-contrast-500: var(--palette-accent-contrast-500-rgb), 159 | --palette-accent-contrast-600: var(--palette-accent-contrast-600-rgb), 160 | --palette-accent-contrast-700: var(--palette-accent-contrast-700-rgb), 161 | --palette-accent-contrast-800: var(--palette-accent-contrast-800-rgb), 162 | --palette-accent-contrast-900: var(--palette-accent-contrast-900-rgb), 163 | --palette-accent-contrast-A100: var(--palette-accent-contrast-A100-rgb), 164 | --palette-accent-contrast-A200: var(--palette-accent-contrast-A200-rgb), 165 | --palette-accent-contrast-A400: var(--palette-accent-contrast-A400-rgb), 166 | --palette-accent-contrast-A700: var(--palette-accent-contrast-A700-rgb), 167 | // WARN 168 | --palette-warn-50-no-rgb: public-color-util.hex-to-rgb(#ffebee), 169 | --palette-warn-100-no-rgb: public-color-util.hex-to-rgb(#ffcdd2), 170 | --palette-warn-200-no-rgb: public-color-util.hex-to-rgb(#ef9a9a), 171 | --palette-warn-300-no-rgb: public-color-util.hex-to-rgb(#e57373), 172 | --palette-warn-400-no-rgb: public-color-util.hex-to-rgb(#ef5350), 173 | --palette-warn-500-no-rgb: public-color-util.hex-to-rgb(#f44336), 174 | --palette-warn-600-no-rgb: public-color-util.hex-to-rgb(#e53935), 175 | --palette-warn-700-no-rgb: public-color-util.hex-to-rgb(#d32f2f), 176 | --palette-warn-800-no-rgb: public-color-util.hex-to-rgb(#c62828), 177 | --palette-warn-900-no-rgb: public-color-util.hex-to-rgb(#b71c1c), 178 | --palette-warn-A100-no-rgb: public-color-util.hex-to-rgb(#ff8a80), 179 | --palette-warn-A200-no-rgb: public-color-util.hex-to-rgb(#ff5252), 180 | --palette-warn-A400-no-rgb: public-color-util.hex-to-rgb(#ff1744), 181 | --palette-warn-A700-no-rgb: public-color-util.hex-to-rgb(#d50000), 182 | --palette-warn-contrast-50-no-rgb: var(--dark-warn-text-no-rgb), 183 | --palette-warn-contrast-100-no-rgb: var(--dark-warn-text-no-rgb), 184 | --palette-warn-contrast-200-no-rgb: var(--dark-warn-text-no-rgb), 185 | --palette-warn-contrast-300-no-rgb: var(--dark-warn-text-no-rgb), 186 | --palette-warn-contrast-400-no-rgb: var(--dark-warn-text-no-rgb), 187 | --palette-warn-contrast-500-no-rgb: var(--dark-warn-text-no-rgb), 188 | --palette-warn-contrast-600-no-rgb: var(--dark-warn-text-no-rgb), 189 | --palette-warn-contrast-700-no-rgb: var(--dark-warn-text-no-rgb), 190 | --palette-warn-contrast-800-no-rgb: var(--dark-warn-text-no-rgb), 191 | --palette-warn-contrast-900-no-rgb: var(--dark-warn-text-no-rgb), 192 | --palette-warn-contrast-A100-no-rgb: var(--dark-warn-text-no-rgb), 193 | --palette-warn-contrast-A200-no-rgb: var(--dark-warn-text-no-rgb), 194 | --palette-warn-contrast-A400-no-rgb: var(--dark-warn-text-no-rgb), 195 | --palette-warn-contrast-A700-no-rgb: var(--dark-warn-text-no-rgb), 196 | --palette-warn-50: var(--palette-warn-50-rgb), 197 | --palette-warn-100: var(--palette-warn-100-rgb), 198 | --palette-warn-200: var(--palette-warn-200-rgb), 199 | --palette-warn-300: var(--palette-warn-300-rgb), 200 | --palette-warn-400: var(--palette-warn-400-rgb), 201 | --palette-warn-500: var(--palette-warn-500-rgb), 202 | --palette-warn-600: var(--palette-warn-600-rgb), 203 | --palette-warn-700: var(--palette-warn-700-rgb), 204 | --palette-warn-800: var(--palette-warn-800-rgb), 205 | --palette-warn-900: var(--palette-warn-900-rgb), 206 | --palette-warn-A100: var(--palette-warn-A100-rgb), 207 | --palette-warn-A200: var(--palette-warn-A200-rgb), 208 | --palette-warn-A400: var(--palette-warn-A400-rgb), 209 | --palette-warn-A700: var(--palette-warn-A700-rgb), 210 | --palette-warn-contrast-50: var(--palette-warn-contrast-50-rgb), 211 | --palette-warn-contrast-100: var(--palette-warn-contrast-100-rgb), 212 | --palette-warn-contrast-200: var(--palette-warn-contrast-200-rgb), 213 | --palette-warn-contrast-300: var(--palette-warn-contrast-300-rgb), 214 | --palette-warn-contrast-400: var(--palette-warn-contrast-400-rgb), 215 | --palette-warn-contrast-500: var(--palette-warn-contrast-500-rgb), 216 | --palette-warn-contrast-600: var(--palette-warn-contrast-600-rgb), 217 | --palette-warn-contrast-700: var(--palette-warn-contrast-700-rgb), 218 | --palette-warn-contrast-800: var(--palette-warn-contrast-800-rgb), 219 | --palette-warn-contrast-900: var(--palette-warn-contrast-900-rgb), 220 | --palette-warn-contrast-A100: var(--palette-warn-contrast-A100-rgb), 221 | --palette-warn-contrast-A200: var(--palette-warn-contrast-A200-rgb), 222 | --palette-warn-contrast-A400: var(--palette-warn-contrast-A400-rgb), 223 | --palette-warn-contrast-A700: var(--palette-warn-contrast-A700-rgb), 224 | //RGB-map 225 | --palette-primary-50-rgb: rgb(var(--palette-primary-50-no-rgb)), 226 | --palette-primary-100-rgb: rgb(var(--palette-primary-100-no-rgb)), 227 | --palette-primary-200-rgb: rgb(var(--palette-primary-200-no-rgb)), 228 | --palette-primary-300-rgb: rgb(var(--palette-primary-300-no-rgb)), 229 | --palette-primary-400-rgb: rgb(var(--palette-primary-400-no-rgb)), 230 | --palette-primary-500-rgb: rgb(var(--palette-primary-500-no-rgb)), 231 | --palette-primary-600-rgb: rgb(var(--palette-primary-600-no-rgb)), 232 | --palette-primary-700-rgb: rgb(var(--palette-primary-700-no-rgb)), 233 | --palette-primary-800-rgb: rgb(var(--palette-primary-800-no-rgb)), 234 | --palette-primary-900-rgb: rgb(var(--palette-primary-900-no-rgb)), 235 | --palette-primary-A100-rgb: rgb(var(--palette-primary-A100-no-rgb)), 236 | --palette-primary-A200-rgb: rgb(var(--palette-primary-A200-no-rgb)), 237 | --palette-primary-A400-rgb: rgb(var(--palette-primary-A400-no-rgb)), 238 | --palette-primary-A700-rgb: rgb(var(--palette-primary-A700-no-rgb)), 239 | --palette-primary-contrast-50-rgb: var(--dark-primary-text), 240 | --palette-primary-contrast-100-rgb: var(--dark-primary-text), 241 | --palette-primary-contrast-200-rgb: var(--dark-primary-text), 242 | --palette-primary-contrast-300-rgb: var(--dark-primary-text), 243 | --palette-primary-contrast-400-rgb: var(--dark-primary-text), 244 | --palette-primary-contrast-500-rgb: var(--light-primary-text), 245 | --palette-primary-contrast-600-rgb: var(--light-primary-text), 246 | --palette-primary-contrast-700-rgb: var(--light-primary-text), 247 | --palette-primary-contrast-800-rgb: var(--light-primary-text), 248 | --palette-primary-contrast-900-rgb: var(--light-primary-text), 249 | --palette-primary-contrast-A100-rgb: var(--light-primary-text), 250 | --palette-primary-contrast-A200-rgb: var(--light-primary-text), 251 | --palette-primary-contrast-A400-rgb: var(--light-primary-text), 252 | --palette-primary-contrast-A700-rgb: var(--light-primary-text), 253 | // ACCENT 254 | --palette-accent-50-rgb: rgb(var(--palette-accent-50-no-rgb)), 255 | --palette-accent-100-rgb: rgb(var(--palette-accent-100-no-rgb)), 256 | --palette-accent-200-rgb: rgb(var(--palette-accent-200-no-rgb)), 257 | --palette-accent-300-rgb: rgb(var(--palette-accent-300-no-rgb)), 258 | --palette-accent-400-rgb: rgb(var(--palette-accent-400-no-rgb)), 259 | --palette-accent-500-rgb: rgb(var(--palette-accent-500-no-rgb)), 260 | --palette-accent-600-rgb: rgb(var(--palette-accent-600-no-rgb)), 261 | --palette-accent-700-rgb: rgb(var(--palette-accent-700-no-rgb)), 262 | --palette-accent-800-rgb: rgb(var(--palette-accent-800-no-rgb)), 263 | --palette-accent-900-rgb: rgb(var(--palette-accent-900-no-rgb)), 264 | --palette-accent-A100-rgb: rgb(var(--palette-accent-A100)), 265 | --palette-accent-A200-rgb: rgb(var(--palette-accent-A200)), 266 | --palette-accent-A400-rgb: rgb(var(--palette-accent-A400)), 267 | --palette-accent-A700-rgb: rgb(var(--palette-accent-A700)), 268 | --palette-accent-contrast-50-rgb: var(--dark-accent-text), 269 | --palette-accent-contrast-100-rgb: var(--dark-accent-text), 270 | --palette-accent-contrast-200-rgb: var(--dark-accent-text), 271 | --palette-accent-contrast-300-rgb: var(--dark-accent-text), 272 | --palette-accent-contrast-400-rgb: var(--dark-accent-text), 273 | --palette-accent-contrast-500-rgb: var(--light-accent-text), 274 | --palette-accent-contrast-600-rgb: var(--light-accent-text), 275 | --palette-accent-contrast-700-rgb: var(--light-accent-text), 276 | --palette-accent-contrast-800-rgb: var(--light-accent-text), 277 | --palette-accent-contrast-900-rgb: var(--light-accent-text), 278 | --palette-accent-contrast-A100-rgb: var(--light-accent-text), 279 | --palette-accent-contrast-A200-rgb: var(--light-accent-text), 280 | --palette-accent-contrast-A400-rgb: var(--light-accent-text), 281 | --palette-accent-contrast-A700-rgb: var(--light-accent-text), 282 | // WARN 283 | --palette-warn-50-rgb: rgb(var(--palette-warn-50-no-rgb)), 284 | --palette-warn-100-rgb: rgb(var(--palette-warn-100-no-rgb)), 285 | --palette-warn-200-rgb: rgb(var(--palette-warn-200-no-rgb)), 286 | --palette-warn-300-rgb: rgb(var(--palette-warn-300-no-rgb)), 287 | --palette-warn-400-rgb: rgb(var(--palette-warn-400-no-rgb)), 288 | --palette-warn-500-rgb: rgb(var(--palette-warn-500-no-rgb)), 289 | --palette-warn-600-rgb: rgb(var(--palette-warn-600-no-rgb)), 290 | --palette-warn-700-rgb: rgb(var(--palette-warn-700-no-rgb)), 291 | --palette-warn-800-rgb: rgb(var(--palette-warn-800-no-rgb)), 292 | --palette-warn-900-rgb: rgb(var(--palette-warn-900-no-rgb)), 293 | --palette-warn-A100-rgb: rgb(var(--palette-warn-A100-no-rgb)), 294 | --palette-warn-A200-rgb: rgb(var(--palette-warn-A200-no-rgb)), 295 | --palette-warn-A400-rgb: rgb(var(--palette-warn-A400-no-rgb)), 296 | --palette-warn-A700-rgb: rgb(var(--palette-warn-A700-no-rgb)), 297 | --palette-warn-contrast-50-rgb: var(--dark-warn-text), 298 | --palette-warn-contrast-100-rgb: var(--dark-warn-text), 299 | --palette-warn-contrast-200-rgb: var(--dark-warn-text), 300 | --palette-warn-contrast-300-rgb: var(--dark-warn-text), 301 | --palette-warn-contrast-400-rgb: var(--dark-warn-text), 302 | --palette-warn-contrast-500-rgb: var(--light-warn-text), 303 | --palette-warn-contrast-600-rgb: var(--light-warn-text), 304 | --palette-warn-contrast-700-rgb: var(--light-warn-text), 305 | --palette-warn-contrast-800-rgb: var(--light-warn-text), 306 | --palette-warn-contrast-900-rgb: var(--light-warn-text), 307 | --palette-warn-contrast-A100-rgb: var(--light-warn-text), 308 | --palette-warn-contrast-A200-rgb: var(--light-warn-text), 309 | --palette-warn-contrast-A400-rgb: var(--light-warn-text), 310 | --palette-warn-contrast-A700-rgb: var(--light-warn-text) 311 | ) !default; 312 | 313 | $palette-primary: ( 314 | 50: var(--palette-primary-50), 315 | 100: var(--palette-primary-100), 316 | 200: var(--palette-primary-200), 317 | 300: var(--palette-primary-300), 318 | 400: var(--palette-primary-400), 319 | 500: var(--palette-primary-500), 320 | 600: var(--palette-primary-600), 321 | 700: var(--palette-primary-700), 322 | 800: var(--palette-primary-800), 323 | 900: var(--palette-primary-900), 324 | A100: var(--palette-primary-A100), 325 | A200: var(--palette-primary-A200), 326 | A400: var(--palette-primary-A400), 327 | A700: var(--palette-primary-A700), 328 | contrast: ( 329 | 50: var(--palette-primary-contrast-50), 330 | 100: var(--palette-primary-contrast-100), 331 | 200: var(--palette-primary-contrast-200), 332 | 300: var(--palette-primary-contrast-300), 333 | 400: var(--palette-primary-contrast-400), 334 | 500: var(--palette-primary-contrast-500), 335 | 600: var(--palette-primary-contrast-600), 336 | 700: var(--palette-primary-contrast-700), 337 | 800: var(--palette-primary-contrast-800), 338 | 900: var(--palette-primary-contrast-900), 339 | A100: var(--palette-primary-contrast-A100), 340 | A200: var(--palette-primary-contrast-A200), 341 | A400: var(--palette-primary-contrast-A400), 342 | A700: var(--palette-primary-contrast-A700), 343 | ), 344 | ) !default; 345 | 346 | $palette-primary-no-rgb: ( 347 | 50: var(--palette-primary-50-no-rgb), 348 | 100: var(--palette-primary-100-no-rgb), 349 | 200: var(--palette-primary-200-no-rgb), 350 | 300: var(--palette-primary-300-no-rgb), 351 | 400: var(--palette-primary-400-no-rgb), 352 | 500: var(--palette-primary-500-no-rgb), 353 | 600: var(--palette-primary-600-no-rgb), 354 | 700: var(--palette-primary-700-no-rgb), 355 | 800: var(--palette-primary-800-no-rgb), 356 | 900: var(--palette-primary-900-no-rgb), 357 | A100: var(--palette-primary-A100-no-rgb), 358 | A200: var(--palette-primary-A200-no-rgb), 359 | A400: var(--palette-primary-A400-no-rgb), 360 | A700: var(--palette-primary-A700-no-rgb), 361 | contrast: ( 362 | 50: var(--palette-primary-contrast-50-no-rgb), 363 | 100: var(--palette-primary-contrast-100-no-rgb), 364 | 200: var(--palette-primary-contrast-200-no-rgb), 365 | 300: var(--palette-primary-contrast-300-no-rgb), 366 | 400: var(--palette-primary-contrast-400-no-rgb), 367 | 500: var(--palette-primary-contrast-500-no-rgb), 368 | 600: var(--palette-primary-contrast-600-no-rgb), 369 | 700: var(--palette-primary-contrast-700-no-rgb), 370 | 800: var(--palette-primary-contrast-800-no-rgb), 371 | 900: var(--palette-primary-contrast-900b-no-rgb), 372 | A100: var(--palette-primary-contrast-A100-no-rgb), 373 | A200: var(--palette-primary-contrast-A200-no-rgb), 374 | A400: var(--palette-primary-contrast-A400-no-rgb), 375 | A700: var(--palette-primary-contrast-A700-no-rgb), 376 | ), 377 | ) !default; 378 | 379 | $palette-accent: ( 380 | 50: var(--palette-accent-50), 381 | 100: var(--palette-accent-100), 382 | 200: var(--palette-accent-200), 383 | 300: var(--palette-accent-300), 384 | 400: var(--palette-accent-400), 385 | 500: var(--palette-accent-500), 386 | 600: var(--palette-accent-600), 387 | 700: var(--palette-accent-700), 388 | 800: var(--palette-accent-800), 389 | 900: var(--palette-accent-900), 390 | A100: var(--palette-accent-A100), 391 | A200: var(--palette-accent-A200), 392 | A400: var(--palette-accent-A400), 393 | A700: var(--palette-accent-A700), 394 | contrast: ( 395 | 50: var(--palette-accent-contrast-50), 396 | 100: var(--palette-accent-contrast-100), 397 | 200: var(--palette-accent-contrast-200), 398 | 300: var(--palette-accent-contrast-300), 399 | 400: var(--palette-accent-contrast-400), 400 | 500: var(--palette-accent-contrast-500), 401 | 600: var(--palette-accent-contrast-600), 402 | 700: var(--palette-accent-contrast-700), 403 | 800: var(--palette-accent-contrast-800), 404 | 900: var(--palette-accent-contrast-900), 405 | A100: var(--palette-accent-contrast-A100), 406 | A200: var(--palette-accent-contrast-A200), 407 | A400: var(--palette-accent-contrast-A400), 408 | A700: var(--palette-accent-contrast-A700), 409 | ), 410 | ) !default; 411 | 412 | $palette-accent-no-rgb: ( 413 | 50: var(--palette-accent-50-no-rgb), 414 | 100: var(--palette-accent-100-no-rgb), 415 | 200: var(--palette-accent-200-no-rgb), 416 | 300: var(--palette-accent-300-no-rgb), 417 | 400: var(--palette-accent-400-no-rgb), 418 | 500: var(--palette-accent-500-no-rgb), 419 | 600: var(--palette-accent-600-no-rgb), 420 | 700: var(--palette-accent-700-no-rgb), 421 | 800: var(--palette-accent-800-no-rgb), 422 | 900: var(--palette-accent-900-no-rgb), 423 | A100: var(--palette-accent-A100-no-rgb), 424 | A200: var(--palette-accent-A200-no-rgb), 425 | A400: var(--palette-accent-A400-no-rgb), 426 | A700: var(--palette-accent-A700-no-rgb), 427 | contrast: ( 428 | 50: var(--palette-accent-contrast-50-no-rgb), 429 | 100: var(--palette-accent-contrast-100-no-rgb), 430 | 200: var(--palette-accent-contrast-200-no-rgb), 431 | 300: var(--palette-accent-contrast-300-no-rgb), 432 | 400: var(--palette-accent-contrast-400-no-rgb), 433 | 500: var(--palette-accent-contrast-500-no-rgb), 434 | 600: var(--palette-accent-contrast-600-no-rgb), 435 | 700: var(--palette-accent-contrast-700-no-rgb), 436 | 800: var(--palette-accent-contrast-800-no-rgb), 437 | 900: var(--palette-accent-contrast-900b-no-rgb), 438 | A100: var(--palette-accent-contrast-A100-no-rgb), 439 | A200: var(--palette-accent-contrast-A200-no-rgb), 440 | A400: var(--palette-accent-contrast-A400-no-rgb), 441 | A700: var(--palette-accent-contrast-A700-no-rgb), 442 | ), 443 | ) !default; 444 | 445 | $palette-warn: ( 446 | 50: var(--palette-warn-50), 447 | 100: var(--palette-warn-100), 448 | 200: var(--palette-warn-200), 449 | 300: var(--palette-warn-300), 450 | 400: var(--palette-warn-400), 451 | 500: var(--palette-warn-500), 452 | 600: var(--palette-warn-600), 453 | 700: var(--palette-warn-700), 454 | 800: var(--palette-warn-800), 455 | 900: var(--palette-warn-900), 456 | A100: var(--palette-warn-A100), 457 | A200: var(--palette-warn-A200), 458 | A400: var(--palette-warn-A400), 459 | A700: var(--palette-warn-A700), 460 | contrast: ( 461 | 50: var(--palette-warn-contrast-50), 462 | 100: var(--palette-warn-contrast-100), 463 | 200: var(--palette-warn-contrast-200), 464 | 300: var(--palette-warn-contrast-300), 465 | 400: var(--palette-warn-contrast-400), 466 | 500: var(--palette-warn-contrast-500), 467 | 600: var(--palette-warn-contrast-600), 468 | 700: var(--palette-warn-contrast-700), 469 | 800: var(--palette-warn-contrast-800), 470 | 900: var(--palette-warn-contrast-900), 471 | A100: var(--palette-warn-contrast-A100), 472 | A200: var(--palette-warn-contrast-A200), 473 | A400: var(--palette-warn-contrast-A400), 474 | A700: var(--palette-warn-contrast-A700), 475 | ), 476 | ) !default; 477 | 478 | $palette-warn-no-rgb: ( 479 | 50: var(--palette-warn-50-no-rgb), 480 | 100: var(--palette-warn-100-no-rgb), 481 | 200: var(--palette-warn-200-no-rgb), 482 | 300: var(--palette-warn-300-no-rgb), 483 | 400: var(--palette-warn-400-no-rgb), 484 | 500: var(--palette-warn-500-no-rgb), 485 | 600: var(--palette-warn-600-no-rgb), 486 | 700: var(--palette-warn-700-no-rgb), 487 | 800: var(--palette-warn-800-no-rgb), 488 | 900: var(--palette-warn-900-no-rgb), 489 | A100: var(--palette-warn-A100-no-rgb), 490 | A200: var(--palette-warn-A200-no-rgb), 491 | A400: var(--palette-warn-A400-no-rgb), 492 | A700: var(--palette-warn-A700-no-rgb), 493 | contrast: ( 494 | 50: var(--palette-warn-contrast-50-no-rgb), 495 | 100: var(--palette-warn-contrast-100-no-rgb), 496 | 200: var(--palette-warn-contrast-200-no-rgb), 497 | 300: var(--palette-warn-contrast-300-no-rgb), 498 | 400: var(--palette-warn-contrast-400-no-rgb), 499 | 500: var(--palette-warn-contrast-500-no-rgb), 500 | 600: var(--palette-warn-contrast-600-no-rgb), 501 | 700: var(--palette-warn-contrast-700-no-rgb), 502 | 800: var(--palette-warn-contrast-800-no-rgb), 503 | 900: var(--palette-warn-contrast-900-no-rgb), 504 | A100: var(--palette-warn-contrast-A100-no-rgb), 505 | A200: var(--palette-warn-contrast-A200-no-rgb), 506 | A400: var(--palette-warn-contrast-A400-no-rgb), 507 | A700: var(--palette-warn-contrast-A700-no-rgb), 508 | ), 509 | ) !default; 510 | 511 | $contrast-palette: map.get($palette-primary, "contrast") !default; 512 | $contrast-palette-no-rgb: map.get($palette-primary-no-rgb, "contrast") !default; 513 | $contrast-palette-accent: map.get($palette-accent, "contrast") !default; 514 | $contrast-palette-accent-no-rgb: map.get( 515 | $palette-accent-no-rgb, 516 | "contrast" 517 | ) !default; 518 | $contrast-palette-warn: map.get($palette-warn, "contrast") !default; 519 | $contrast-palette-warn-no-rgb: map.get( 520 | $palette-warn-no-rgb, 521 | "contrast" 522 | ) !default; 523 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/default-cfg.const.ts: -------------------------------------------------------------------------------- 1 | import { MaterialCssVariablesConfig } from "./model"; 2 | 3 | export const DEFAULT_MAT_CSS_CFG: MaterialCssVariablesConfig = { 4 | isAutoContrast: true, 5 | isAlternativeColorAlgorithm: false, 6 | darkThemeClass: "isDarkTheme", 7 | lightThemeClass: "isLightTheme", 8 | colorMap: [ 9 | { name: "50", map: [52, 0, 0] }, 10 | { name: "100", map: [37, 0, 0] }, 11 | { name: "200", map: [26, 0, 0] }, 12 | { name: "300", map: [12, 0, 0] }, 13 | { name: "400", map: [6, 0, 0] }, 14 | { name: "500", map: [0, 0, 0] }, 15 | { name: "600", map: [0, 6, 0] }, 16 | { name: "700", map: [0, 12, 0] }, 17 | { name: "800", map: [0, 18, 0] }, 18 | { name: "900", map: [0, 24, 0] }, 19 | { name: "A100", map: [50, 0, 30] }, 20 | { name: "A200", map: [30, 0, 30] }, 21 | { name: "A400", map: [10, 0, 15] }, 22 | { name: "A700", map: [5, 0, 5] }, 23 | ], 24 | sortedHues: [ 25 | "50", 26 | "100", 27 | "200", 28 | "300", 29 | "400", 30 | "500", 31 | "600", 32 | "700", 33 | "800", 34 | "900", 35 | ], 36 | }; 37 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/material-css-vars.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EnvironmentProviders, 3 | inject, 4 | makeEnvironmentProviders, 5 | ModuleWithProviders, 6 | NgModule, 7 | provideEnvironmentInitializer, 8 | } from "@angular/core"; 9 | import { CommonModule } from "@angular/common"; 10 | import { MaterialCssVariablesConfig } from "./model"; 11 | import { MATERIAL_CSS_VARS_CFG } from "../mat-css-config-token.const"; 12 | import { MaterialCssVarsService } from "./material-css-vars.service"; 13 | 14 | @NgModule({ 15 | imports: [CommonModule], 16 | }) 17 | // eslint-disable-next-line @typescript-eslint/no-extraneous-class 18 | export class MaterialCssVarsModule { 19 | static forRoot( 20 | config?: Partial, 21 | ): ModuleWithProviders { 22 | return { 23 | ngModule: MaterialCssVarsModule, 24 | providers: [provideMaterialCssVars(config)], 25 | }; 26 | } 27 | } 28 | 29 | export function provideMaterialCssVars( 30 | config?: Partial, 31 | ): EnvironmentProviders { 32 | return makeEnvironmentProviders([ 33 | { provide: MATERIAL_CSS_VARS_CFG, useValue: config }, 34 | provideEnvironmentInitializer(() => inject(MaterialCssVarsService)), 35 | ]); 36 | } 37 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/material-css-vars.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from "@angular/core/testing"; 2 | 3 | import { MaterialCssVarsService } from "./material-css-vars.service"; 4 | import { MATERIAL_CSS_VARS_CFG } from "../mat-css-config-token.const"; 5 | import { Renderer2, RendererStyleFlags2 } from "@angular/core"; 6 | import { MatCssPalettePrefix, MaterialCssVariables } from "./model"; 7 | import { TinyColor } from "@ctrl/tinycolor"; 8 | 9 | describe("MaterialCssVarsService", () => { 10 | let service: MaterialCssVarsService; 11 | let renderer: Renderer2; 12 | 13 | beforeEach(() => { 14 | TestBed.configureTestingModule({ 15 | providers: [ 16 | { 17 | provide: MATERIAL_CSS_VARS_CFG, 18 | useValue: { 19 | primary: "#123456", 20 | accent: "#654321", 21 | warn: "#FF0000", 22 | isDarkTheme: true, 23 | }, 24 | }, 25 | ], 26 | }); 27 | service = TestBed.inject(MaterialCssVarsService); 28 | renderer = service["renderer"]; 29 | }); 30 | 31 | it("should be created", () => { 32 | expect(service).toBeTruthy(); 33 | }); 34 | 35 | describe("constructor", () => { 36 | it("should apply the config from injection token", () => { 37 | expect(service.cfg.primary).toEqual("#123456"); 38 | }); 39 | 40 | it("should set default values", () => { 41 | expect(service.cfg.darkThemeClass).toEqual("isDarkTheme"); 42 | }); 43 | 44 | it("should set accent color from injection token config", () => { 45 | expect(service.accent).toEqual("#654321"); 46 | }); 47 | 48 | it("should set warn color from injection token config", () => { 49 | expect(service.warn).toEqual("#FF0000"); 50 | }); 51 | 52 | it("should apply dark mode from injection token config", () => { 53 | expect(service.isDarkTheme).toBeTrue(); 54 | }); 55 | }); 56 | 57 | describe("setPrimaryColor", () => { 58 | it("should set the primary color hex value", () => { 59 | service.setPrimaryColor("#1144aa"); 60 | 61 | expect(service.primary).toEqual("#1144aa"); 62 | }); 63 | 64 | it("should update the CSS variables", () => { 65 | const spy = spyOn(renderer, "setStyle"); 66 | service.setPrimaryColor("#1144aa"); 67 | 68 | expect(spy).toHaveBeenCalledWith( 69 | jasmine.anything(), 70 | "--palette-primary-500", 71 | "rgb(17, 68, 170)", 72 | RendererStyleFlags2.DashCase, 73 | ); 74 | }); 75 | }); 76 | 77 | describe("setAccentColor", () => { 78 | it("should set the accent color hex value", () => { 79 | service.setAccentColor("#11aa44"); 80 | 81 | expect(service.accent).toEqual("#11aa44"); 82 | }); 83 | 84 | it("should update the CSS variables", () => { 85 | const spy = spyOn(renderer, "setStyle"); 86 | service.setAccentColor("#11aa44"); 87 | 88 | expect(spy).toHaveBeenCalledWith( 89 | jasmine.anything(), 90 | "--palette-accent-500", 91 | "rgb(17, 170, 68)", 92 | RendererStyleFlags2.DashCase, 93 | ); 94 | }); 95 | }); 96 | 97 | describe("setWarnColor", () => { 98 | it("should set the warn color hex value", () => { 99 | service.setWarnColor("#aa1144"); 100 | 101 | expect(service.warn).toEqual("#aa1144"); 102 | }); 103 | 104 | it("should update the CSS variables", () => { 105 | const spy = spyOn(renderer, "setStyle"); 106 | service.setWarnColor("#aa1144"); 107 | 108 | expect(spy).toHaveBeenCalledWith( 109 | jasmine.anything(), 110 | "--palette-warn-500", 111 | "rgb(170, 17, 68)", 112 | RendererStyleFlags2.DashCase, 113 | ); 114 | }); 115 | }); 116 | 117 | it("should set a variable", () => { 118 | const spy = spyOn(renderer, "setStyle"); 119 | service.setVariable(MaterialCssVariables.DarkAccentText, "rgb(0, 0, 0)"); 120 | 121 | expect(spy).toHaveBeenCalledWith( 122 | jasmine.anything(), 123 | "--dark-accent-text", 124 | "rgb(0, 0, 0)", 125 | RendererStyleFlags2.DashCase, 126 | ); 127 | }); 128 | 129 | it("should enable dark theme", () => { 130 | service.setDarkTheme(true); 131 | 132 | expect(document.body.classList).toContain("isDarkTheme"); 133 | expect(document.body.classList).not.toContain("isLightTheme"); 134 | }); 135 | 136 | it("should enable light theme", () => { 137 | service.setDarkTheme(false); 138 | 139 | expect(document.body.classList).toContain("isLightTheme"); 140 | expect(document.body.classList).not.toContain("isDarkTheme"); 141 | }); 142 | 143 | describe("setAutoContrastEnabled", () => { 144 | it("should enable auto contrast", () => { 145 | service.setAutoContrastEnabled(true); 146 | 147 | expect(service.isAutoContrast).toBeTrue(); 148 | }); 149 | 150 | it("should disable auto contrast", () => { 151 | service.setAutoContrastEnabled(false); 152 | 153 | expect(service.isAutoContrast).toBeFalse(); 154 | }); 155 | 156 | it("should set primary color contrast", () => { 157 | const spy = spyOn(service, "setContrastColorThresholdPrimary"); 158 | service.setAutoContrastEnabled(false); 159 | 160 | expect(spy).toHaveBeenCalledTimes(1); 161 | }); 162 | 163 | it("should set accent color contrast", () => { 164 | const spy = spyOn(service, "setContrastColorThresholdAccent"); 165 | service.setAutoContrastEnabled(false); 166 | 167 | expect(spy).toHaveBeenCalledTimes(1); 168 | }); 169 | 170 | it("should set warn color contrast", () => { 171 | const spy = spyOn(service, "setContrastColorThresholdWarn"); 172 | service.setAutoContrastEnabled(false); 173 | 174 | expect(spy).toHaveBeenCalledTimes(1); 175 | }); 176 | }); 177 | 178 | describe("setContrastColorThreshold", () => { 179 | it("should do nothing when auto contrast is enabled", () => { 180 | service.isAutoContrast = true; 181 | const spy = spyOn(renderer, "setStyle"); 182 | service.setContrastColorThreshold("50", MatCssPalettePrefix.Primary); 183 | 184 | expect(spy).not.toHaveBeenCalled(); 185 | }); 186 | }); 187 | 188 | describe("setAlternativeColorAlgorithm", () => { 189 | it("should change the isAlternativeColorAlgorithm variable", () => { 190 | service.setAlternativeColorAlgorithm(true); 191 | 192 | expect(service.cfg.isAlternativeColorAlgorithm).toBeTrue(); 193 | }); 194 | }); 195 | 196 | describe("_getContrastColorVar", () => { 197 | it("should return obvious dark contrast color choice", () => { 198 | const color = new TinyColor("#111111"); 199 | 200 | expect(service["_getContrastColorVar"](color)).toBe( 201 | MaterialCssVarsService["LIGHT_TEXT_VAR"], 202 | ); 203 | }); 204 | 205 | it("should return obvious light contrast color choice", () => { 206 | const color = new TinyColor("#eeeeee"); 207 | 208 | expect(service["_getContrastColorVar"](color)).toBe( 209 | MaterialCssVarsService["DARK_TEXT_VAR"], 210 | ); 211 | }); 212 | 213 | it("should return the best contrast in edge cases", () => { 214 | const color = new TinyColor("#f0002f"); 215 | 216 | expect(service["_getContrastColorVar"](color)).toBe( 217 | MaterialCssVarsService["DARK_TEXT_VAR"], 218 | ); 219 | }); 220 | }); 221 | }); 222 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/material-css-vars.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Inject, 3 | Injectable, 4 | Renderer2, 5 | RendererFactory2, 6 | RendererStyleFlags2, 7 | DOCUMENT, 8 | } from "@angular/core"; 9 | import { Numberify, RGBA, TinyColor } from "@ctrl/tinycolor"; 10 | import { 11 | HueValue, 12 | MatCssHueColorContrastMapItem, 13 | MatCssHueColorMapItem, 14 | MatCssPalettePrefix, 15 | MaterialCssVariables, 16 | MaterialCssVariablesConfig, 17 | } from "./model"; 18 | import { DEFAULT_MAT_CSS_CFG } from "./default-cfg.const"; 19 | import { MATERIAL_CSS_VARS_CFG } from "../mat-css-config-token.const"; 20 | 21 | interface CssVariable { 22 | name: string; 23 | val: string; 24 | } 25 | 26 | interface Color { 27 | rgb: Numberify; 28 | isLight: boolean; 29 | } 30 | 31 | // @see: https://github.com/angular/angular/issues/20351 32 | /** @dynamic */ 33 | @Injectable({ 34 | providedIn: "root", 35 | }) 36 | export class MaterialCssVarsService { 37 | private static CONTRAST_PREFIX = "contrast-"; 38 | private static DARK_TEXT_VAR = "--dark-text-contrast"; 39 | private static LIGHT_TEXT_VAR = "--light-text-contrast"; 40 | // This should be readonly from the outside 41 | cfg: MaterialCssVariablesConfig; 42 | primary = "#03a9f4"; 43 | accent = "#e91e63"; 44 | warn = "#f44336"; 45 | isDarkTheme?: boolean; 46 | contrastColorThresholdPrimary: HueValue = "400"; 47 | contrastColorThresholdAccent: HueValue = "400"; 48 | contrastColorThresholdWarn: HueValue = "400"; 49 | isAutoContrast = false; 50 | private renderer: Renderer2; 51 | private ROOT: HTMLElement; 52 | private readonly _black = new TinyColor("#000000"); 53 | private readonly _white = new TinyColor("#ffffff"); 54 | 55 | constructor( 56 | rendererFactory: RendererFactory2, 57 | @Inject(DOCUMENT) private document: Document, 58 | @Inject(MATERIAL_CSS_VARS_CFG) cfg: MaterialCssVariablesConfig, 59 | ) { 60 | this.renderer = rendererFactory.createRenderer(null, null); 61 | this.ROOT = this.document.documentElement; 62 | 63 | this.cfg = { 64 | ...DEFAULT_MAT_CSS_CFG, 65 | ...cfg, 66 | }; 67 | this.isAutoContrast = this.cfg.isAutoContrast; 68 | 69 | if (typeof this.cfg.isDarkTheme === "boolean") { 70 | this.setDarkTheme(this.cfg.isDarkTheme); 71 | } 72 | if (this.cfg.primary) { 73 | this.setPrimaryColor(this.cfg.primary); 74 | } 75 | if (this.cfg.accent) { 76 | this.setAccentColor(this.cfg.accent); 77 | } 78 | if (this.cfg.warn) { 79 | this.setWarnColor(this.cfg.warn); 80 | } 81 | } 82 | 83 | setPrimaryColor(hex: string) { 84 | this.primary = hex; 85 | const varPrefix = MatCssPalettePrefix.Primary; 86 | const stylePrimary = this._computePaletteColors(varPrefix, this.primary); 87 | this._setStyle(stylePrimary); 88 | 89 | if (this.isAutoContrast) { 90 | this._recalculateAndSetContrastColor(varPrefix); 91 | } 92 | } 93 | 94 | setAccentColor(hex: string) { 95 | this.accent = hex; 96 | const varPrefix = MatCssPalettePrefix.Accent; 97 | const styleAccent = this._computePaletteColors(varPrefix, this.accent); 98 | this._setStyle(styleAccent); 99 | 100 | if (this.isAutoContrast) { 101 | this._recalculateAndSetContrastColor(varPrefix); 102 | } 103 | } 104 | 105 | setWarnColor(hex: string) { 106 | this.warn = hex; 107 | const varPrefix = MatCssPalettePrefix.Warn; 108 | const styleWarn = this._computePaletteColors(varPrefix, this.warn); 109 | this._setStyle(styleWarn); 110 | 111 | if (this.isAutoContrast) { 112 | this._recalculateAndSetContrastColor(varPrefix); 113 | } 114 | } 115 | 116 | setVariable(cssVarName: MaterialCssVariables, value: string) { 117 | this._setStyle([ 118 | { 119 | name: cssVarName, 120 | val: value, 121 | }, 122 | ]); 123 | } 124 | 125 | setDarkTheme(isDark: boolean) { 126 | if (isDark) { 127 | this.document.body.classList.remove(this.cfg.lightThemeClass); 128 | this.document.body.classList.add(this.cfg.darkThemeClass); 129 | } else { 130 | this.document.body.classList.remove(this.cfg.darkThemeClass); 131 | this.document.body.classList.add(this.cfg.lightThemeClass); 132 | } 133 | this.isDarkTheme = isDark; 134 | } 135 | 136 | setAutoContrastEnabled(val: boolean) { 137 | this.isAutoContrast = val; 138 | if (val) { 139 | this._recalculateAndSetContrastColor(MatCssPalettePrefix.Primary); 140 | this._recalculateAndSetContrastColor(MatCssPalettePrefix.Accent); 141 | this._recalculateAndSetContrastColor(MatCssPalettePrefix.Warn); 142 | } else { 143 | this.setContrastColorThresholdPrimary(this.contrastColorThresholdPrimary); 144 | this.setContrastColorThresholdAccent(this.contrastColorThresholdAccent); 145 | this.setContrastColorThresholdWarn(this.contrastColorThresholdWarn); 146 | } 147 | } 148 | 149 | setContrastColorThresholdPrimary(threshold: HueValue) { 150 | this.contrastColorThresholdPrimary = threshold; 151 | this.setContrastColorThreshold(threshold, MatCssPalettePrefix.Primary); 152 | } 153 | 154 | setContrastColorThresholdAccent(threshold: HueValue) { 155 | this.contrastColorThresholdAccent = threshold; 156 | this.setContrastColorThreshold(threshold, MatCssPalettePrefix.Accent); 157 | } 158 | 159 | setContrastColorThresholdWarn(threshold: HueValue) { 160 | this.contrastColorThresholdWarn = threshold; 161 | this.setContrastColorThreshold(threshold, MatCssPalettePrefix.Warn); 162 | } 163 | 164 | setContrastColorThreshold( 165 | threshold: HueValue, 166 | palettePrefix: MatCssPalettePrefix, 167 | ) { 168 | if (this.isAutoContrast) { 169 | return; 170 | } 171 | let color = MaterialCssVarsService.DARK_TEXT_VAR; 172 | const updates = this.cfg.sortedHues.map((hue) => { 173 | if (hue === threshold) { 174 | color = MaterialCssVarsService.LIGHT_TEXT_VAR; 175 | } 176 | return { 177 | val: `var(${color})`, //val: this._getCssVarValue(color), 178 | name: `${palettePrefix + MaterialCssVarsService.CONTRAST_PREFIX}${hue}`, 179 | }; 180 | }); 181 | this._setStyle(updates); 182 | } 183 | 184 | /** 185 | * Generate palette color based on traditional values 186 | */ 187 | setAlternativeColorAlgorithm(traditional: boolean): void { 188 | this.cfg.isAlternativeColorAlgorithm = traditional; 189 | this.setPrimaryColor(this.primary); 190 | this.setAccentColor(this.accent); 191 | this.setWarnColor(this.warn); 192 | } 193 | 194 | getPaletteForColor(hex: string): MatCssHueColorMapItem[] { 195 | if (this.cfg.isAlternativeColorAlgorithm) { 196 | return this.getTraditionalPaletteForColor(hex); 197 | } else { 198 | return this.getConstantinPaletteForColor(hex); 199 | } 200 | } 201 | 202 | getPaletteWithContrastForColor(hex: string): MatCssHueColorContrastMapItem[] { 203 | const lightText = this._getCssVarValue( 204 | MaterialCssVarsService.LIGHT_TEXT_VAR, 205 | ); 206 | const darkText = this._getCssVarValue(MaterialCssVarsService.DARK_TEXT_VAR); 207 | const palette = this.getPaletteForColor(hex); 208 | 209 | // TODO handle non auto case 210 | return palette.map((item) => { 211 | const contrastStr = item.isLight ? lightText : darkText; 212 | 213 | const sLight = this._replaceNoRgbValue("", contrastStr) 214 | .split(",") 215 | .map((v) => +v); 216 | const cco = { r: sLight[0], g: sLight[1], b: sLight[2], a: 1 }; 217 | return { 218 | ...item, 219 | contrast: { 220 | ...cco, 221 | str: `${cco.r},${cco.g},${cco.b}`, 222 | }, 223 | }; 224 | }); 225 | } 226 | 227 | private getTraditionalPaletteForColor(hex: string): MatCssHueColorMapItem[] { 228 | return this.cfg.colorMap.map((item) => { 229 | const mappedColor = new TinyColor(hex) 230 | .lighten(item.map[0]) 231 | .darken(item.map[1]) 232 | .saturate(item.map[2]); 233 | const c = new TinyColor(mappedColor); 234 | return { 235 | hue: item.name, 236 | isLight: c.isLight(), 237 | color: { 238 | ...c.toRgb(), 239 | str: `rgb(${c.toRgb().r},${c.toRgb().g},${c.toRgb().b})`, 240 | }, 241 | }; 242 | }); 243 | } 244 | 245 | private getConstantinPaletteForColor(hex: string): MatCssHueColorMapItem[] { 246 | return this.cfg.colorMap.map((item) => { 247 | const c = this.computePalletTriad(hex, item.name); 248 | return { 249 | hue: item.name, 250 | isLight: c.isLight, 251 | color: { 252 | ...c.rgb, 253 | str: `rgb(${c.rgb.r},${c.rgb.g},${c.rgb.b})`, 254 | }, 255 | }; 256 | }); 257 | } 258 | 259 | private _computePaletteColors( 260 | prefix: MatCssPalettePrefix, 261 | hex: string, 262 | ): CssVariable[] { 263 | return this.getPaletteForColor(hex).map((item) => { 264 | const c = item.color; 265 | return { 266 | name: `${prefix}${item.hue}`, 267 | val: `rgb(${c.r}, ${c.g}, ${c.b})`, 268 | }; 269 | }); 270 | } 271 | 272 | private _recalculateAndSetContrastColor(palettePrefix: MatCssPalettePrefix) { 273 | const updates = this._calculateContrastColorsForCurrentValues( 274 | palettePrefix, 275 | ).map(({ contrastColorVar, hue }) => { 276 | return { 277 | val: `var(${contrastColorVar})`, //this._getCssVarValue(contrastColorVar), 278 | name: `${palettePrefix + MaterialCssVarsService.CONTRAST_PREFIX}${hue}`, 279 | }; 280 | }); 281 | this._setStyle(updates); 282 | } 283 | 284 | private _calculateContrastColorsForCurrentValues( 285 | palettePrefix: MatCssPalettePrefix, 286 | ): { contrastColorVar: string; hue: HueValue }[] { 287 | return this.cfg.sortedHues.map((hue) => { 288 | const hueVarVal = this._getCssVarValue(`${palettePrefix}${hue}`); 289 | const c = new TinyColor(`rgb(${hueVarVal})`); 290 | const contrastColorVar = this._getContrastColorVar(c); 291 | return { 292 | contrastColorVar, 293 | hue, 294 | }; 295 | }); 296 | } 297 | 298 | private _setStyle(vars: CssVariable[]) { 299 | vars.forEach((s) => { 300 | this.renderer.setStyle( 301 | this.ROOT, 302 | s.name, 303 | s.val, 304 | RendererStyleFlags2.DashCase, 305 | ); 306 | this.renderer.setStyle( 307 | this.ROOT, 308 | s.name + "-no-rgb", 309 | this._replaceNoRgbValue(s.name, s.val), 310 | RendererStyleFlags2.DashCase, 311 | ); 312 | }); 313 | } 314 | 315 | /** 316 | * Replace variables that are formatted as rgba(var(rgb(xxx))) to be var(xxx) to allow proper formatting 317 | * in variable overrides. 318 | * @param value 319 | * @returns 320 | */ 321 | private _replaceNoRgbValue(name: string, value: string) { 322 | const isContrast: boolean = name.includes( 323 | MaterialCssVarsService.CONTRAST_PREFIX, 324 | ); 325 | let noRgb = ""; 326 | if (isContrast) { 327 | noRgb = value.replace(")", "-no-rgb)"); 328 | } else { 329 | noRgb = value.replace("rgba(", "").replace("rgb(", "").replace(")", ""); 330 | if (noRgb.startsWith("var(")) { 331 | noRgb = noRgb.concat(")"); 332 | } 333 | } 334 | return noRgb; 335 | } 336 | 337 | private _getCssVarValue(v: string): string { 338 | return getComputedStyle(this.ROOT).getPropertyValue(v); 339 | } 340 | 341 | /** 342 | * Compute pallet colors based on a Triad (Constantin) 343 | * see: https://github.com/mbitson/mcg 344 | */ 345 | private computePalletTriad(hex: string, hue: HueValue): Color { 346 | const baseLight = new TinyColor("#ffffff"); 347 | const baseDark = this.multiply( 348 | new TinyColor(hex).toRgb(), 349 | new TinyColor(hex).toRgb(), 350 | ); 351 | const baseTriad = new TinyColor(hex).tetrad(); 352 | let color: Color; 353 | 354 | switch (hue) { 355 | case "50": 356 | color = this.getColorObject(baseLight.mix(hex, 12)); 357 | break; 358 | case "100": 359 | color = this.getColorObject(baseLight.mix(hex, 30)); 360 | break; 361 | case "200": 362 | color = this.getColorObject(baseLight.mix(hex, 50)); 363 | break; 364 | case "300": 365 | color = this.getColorObject(baseLight.mix(hex, 70)); 366 | break; 367 | case "400": 368 | color = this.getColorObject(baseLight.mix(hex, 85)); 369 | break; 370 | case "500": 371 | color = this.getColorObject(baseLight.mix(hex, 100)); 372 | break; 373 | case "600": 374 | color = this.getColorObject(baseDark.mix(hex, 87)); 375 | break; 376 | case "700": 377 | color = this.getColorObject(baseDark.mix(hex, 70)); 378 | break; 379 | case "800": 380 | color = this.getColorObject(baseDark.mix(hex, 54)); 381 | break; 382 | case "900": 383 | color = this.getColorObject(baseDark.mix(hex, 25)); 384 | break; 385 | case "A100": 386 | color = this.getColorObject( 387 | baseDark.mix(baseTriad[4], 15).saturate(80).lighten(65), 388 | ); 389 | break; 390 | case "A200": 391 | color = this.getColorObject( 392 | baseDark.mix(baseTriad[4], 15).saturate(80).lighten(55), 393 | ); 394 | break; 395 | case "A400": 396 | color = this.getColorObject( 397 | baseDark.mix(baseTriad[4], 15).saturate(100).lighten(45), 398 | ); 399 | break; 400 | case "A700": 401 | color = this.getColorObject( 402 | baseDark.mix(baseTriad[4], 15).saturate(100).lighten(40), 403 | ); 404 | break; 405 | } 406 | return color; 407 | } 408 | 409 | private multiply(rgb1: Numberify, rgb2: Numberify): TinyColor { 410 | rgb1.b = Math.floor((rgb1.b * rgb2.b) / 255); 411 | rgb1.g = Math.floor((rgb1.g * rgb2.g) / 255); 412 | rgb1.r = Math.floor((rgb1.r * rgb2.r) / 255); 413 | return new TinyColor("rgb " + rgb1.r + " " + rgb1.g + " " + rgb1.b); 414 | } 415 | 416 | private getColorObject(value: TinyColor): Color { 417 | const c = new TinyColor(value); 418 | return { rgb: c.toRgb(), isLight: c.isLight() }; 419 | } 420 | 421 | private _getContrastColorVar(color: TinyColor): string { 422 | const contrastDark = this._getContrast(color, this._black); 423 | const contrastLight = this._getContrast(color, this._white); 424 | return contrastLight > contrastDark 425 | ? MaterialCssVarsService.LIGHT_TEXT_VAR 426 | : MaterialCssVarsService.DARK_TEXT_VAR; 427 | } 428 | 429 | private _getContrast(color1: TinyColor, color2: TinyColor): number { 430 | const luminance1 = color1.getLuminance(); 431 | const luminance2 = color2.getLuminance(); 432 | const brightest = Math.max(luminance1, luminance2); 433 | const darkest = Math.min(luminance1, luminance2); 434 | return (brightest + 0.05) / (darkest + 0.05); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/lib/model.ts: -------------------------------------------------------------------------------- 1 | export type HueValue = 2 | | "50" 3 | | "100" 4 | | "200" 5 | | "300" 6 | | "400" 7 | | "500" 8 | | "600" 9 | | "700" 10 | | "800" 11 | | "900" 12 | | "A100" 13 | | "A200" 14 | | "A400" 15 | | "A700"; 16 | 17 | export enum MatCssPalettePrefix { 18 | Primary = "--palette-primary-", 19 | Accent = "--palette-accent-", 20 | Warn = "--palette-warn-", 21 | } 22 | 23 | export interface MatCssHueColorMapItem { 24 | hue: HueValue; 25 | isLight: boolean; 26 | color: { 27 | r: number; 28 | g: number; 29 | b: number; 30 | a: number; 31 | str: string; 32 | }; 33 | } 34 | 35 | export interface MatCssHueColorContrastMapItem extends MatCssHueColorMapItem { 36 | contrast: { 37 | r: number; 38 | g: number; 39 | b: number; 40 | a: number; 41 | str: string; 42 | }; 43 | } 44 | 45 | export interface MaterialCssColorMapperEntry { 46 | name: HueValue; 47 | map: [number, number, number]; 48 | } 49 | 50 | export interface MaterialCssVariablesConfig { 51 | isAutoContrast: boolean; 52 | isAlternativeColorAlgorithm: boolean; 53 | 54 | darkThemeClass: string; 55 | lightThemeClass: string; 56 | 57 | colorMap: MaterialCssColorMapperEntry[]; 58 | sortedHues: HueValue[]; 59 | 60 | isDarkTheme?: boolean; 61 | primary?: string; 62 | accent?: string; 63 | warn?: string; 64 | } 65 | 66 | export enum MaterialCssVariables { 67 | "Primary50" = "--palette-primary-50", 68 | "Primary100" = "--palette-primary-100", 69 | "Primary200" = "--palette-primary-200", 70 | "Primary300" = "--palette-primary-300", 71 | "Primary400" = "--palette-primary-400", 72 | "Primary500" = "--palette-primary-500", 73 | "Primary600" = "--palette-primary-600", 74 | "Primary700" = "--palette-primary-700", 75 | "Primary800" = "--palette-primary-800", 76 | "Primary900" = "--palette-primary-900", 77 | "PrimaryA100" = "--palette-primary-A100", 78 | "PrimaryA200" = "--palette-primary-A200", 79 | "PrimaryA400" = "--palette-primary-A400", 80 | "PrimaryA700" = "--palette-primary-A700", 81 | 82 | "PrimaryContrast50" = "--palette-primary-contrast-50", 83 | "PrimaryContrast100" = "--palette-primary-contrast-100", 84 | "PrimaryContrast200" = "--palette-primary-contrast-200", 85 | "PrimaryContrast300" = "--palette-primary-contrast-300", 86 | "PrimaryContrast400" = "--palette-primary-contrast-400", 87 | "PrimaryContrast500" = "--palette-primary-contrast-500", 88 | "PrimaryContrast600" = "--palette-primary-contrast-600", 89 | "PrimaryContrast700" = "--palette-primary-contrast-700", 90 | "PrimaryContrast800" = "--palette-primary-contrast-800", 91 | "PrimaryContrast900" = "--palette-primary-contrast-900", 92 | "PrimaryContrastA100" = "--palette-primary-contrast-A100", 93 | "PrimaryContrastA200" = "--palette-primary-contrast-A200", 94 | "PrimaryContrastA400" = "--palette-primary-contrast-A400", 95 | "PrimaryContrastA700" = "--palette-primary-contrast-A700", 96 | 97 | // ACCENT 98 | "Accent50" = "--palette-accent-50", 99 | "Accent100" = "--palette-accent-100", 100 | "Accent200" = "--palette-accent-200", 101 | "Accent300" = "--palette-accent-300", 102 | "Accent400" = "--palette-accent-400", 103 | "Accent500" = "--palette-accent-500", 104 | "Accent600" = "--palette-accent-600", 105 | "Accent700" = "--palette-accent-700", 106 | "Accent800" = "--palette-accent-800", 107 | "Accent900" = "--palette-accent-900", 108 | "AccentA100" = "--palette-accent-A100", 109 | "AccentA200" = "--palette-accent-A200", 110 | "AccentA400" = "--palette-accent-A400", 111 | "AccentA700" = "--palette-accent-A700", 112 | "DarkAccentText" = "--dark-accent-text", 113 | "LightAccentText" = "--light-accent-text", 114 | 115 | // WARN 116 | "Warn50" = "--palette-warn-50", 117 | "Warn100" = "--palette-warn-100", 118 | "Warn200" = "--palette-warn-200", 119 | "Warn300" = "--palette-warn-300", 120 | "Warn400" = "--palette-warn-400", 121 | "Warn500" = "--palette-warn-500", 122 | "Warn600" = "--palette-warn-600", 123 | "Warn700" = "--palette-warn-700", 124 | "Warn800" = "--palette-warn-800", 125 | "Warn900" = "--palette-warn-900", 126 | "WarnA100" = "--palette-warn-A100", 127 | "WarnA200" = "--palette-warn-A200", 128 | "WarnA400" = "--palette-warn-A400", 129 | "WarnA700" = "--palette-warn-A700", 130 | "DarkWarnText" = "--dark-warn-text", 131 | "LightWarnText" = "--light-warn-text", 132 | } 133 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/mat-css-config-token.const.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from "@angular/core"; 2 | import { MaterialCssVariablesConfig } from "./lib/model"; 3 | 4 | export const MATERIAL_CSS_VARS_CFG = 5 | new InjectionToken("Mat Css Config"); 6 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of material-css-vars 3 | */ 4 | 5 | export * from "./lib/material-css-vars.service"; 6 | export * from "./lib/material-css-vars.module"; 7 | export * from "./lib/model"; 8 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import "zone.js"; 4 | import "zone.js/testing"; 5 | import { getTestBed } from "@angular/core/testing"; 6 | import { 7 | BrowserTestingModule, 8 | platformBrowserTesting, 9 | } from "@angular/platform-browser/testing"; 10 | 11 | // First, initialize the Angular testing environment. 12 | getTestBed().initTestEnvironment( 13 | BrowserTestingModule, 14 | platformBrowserTesting(), 15 | { 16 | teardown: { destroyAfterEach: false }, 17 | }, 18 | ); 19 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/test/global-styles.scss: -------------------------------------------------------------------------------- 1 | @use "../../index" as mat-css-vars; 2 | 3 | @include mat-css-vars.init-material-css-vars; 4 | -------------------------------------------------------------------------------- /projects/material-css-vars/src/test/integration.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from "@angular/core"; 2 | import { MatButtonModule } from "@angular/material/button"; 3 | import { ThemePalette } from "@angular/material/core"; 4 | import { ComponentFixture, TestBed } from "@angular/core/testing"; 5 | import { MaterialCssVarsModule } from "../lib/material-css-vars.module"; 6 | import { By } from "@angular/platform-browser"; 7 | 8 | @Component({ 9 | template: ` `, 10 | imports: [MatButtonModule], 11 | }) 12 | class ButtonComponent { 13 | @Input() color: ThemePalette; 14 | } 15 | 16 | function getButtonComputedStyle( 17 | fixture: ComponentFixture, 18 | ): CSSStyleDeclaration { 19 | const buttonElement = fixture.debugElement.query(By.css("button")) 20 | .nativeElement as HTMLButtonElement; 21 | return getComputedStyle(buttonElement); 22 | } 23 | 24 | describe("integration", () => { 25 | ["isLightTheme", "isDarkTheme", "unthemed"].forEach((theme) => { 26 | const isDarkTheme = 27 | theme === "unthemed" ? undefined : theme === "isDarkTheme"; 28 | 29 | describe(theme, () => { 30 | describe("custom colors", () => { 31 | let button: ButtonComponent; 32 | let fixture: ComponentFixture; 33 | 34 | beforeEach(() => { 35 | TestBed.configureTestingModule({ 36 | imports: [ 37 | MaterialCssVarsModule.forRoot({ 38 | primary: "#00ff00", 39 | accent: "#0000ff", 40 | warn: "#ff0000", 41 | isDarkTheme, 42 | }), 43 | ], 44 | }); 45 | fixture = TestBed.createComponent(ButtonComponent); 46 | button = fixture.componentInstance; 47 | }); 48 | 49 | it("should render a button in the given primary color", () => { 50 | button.color = "primary"; 51 | fixture.detectChanges(); 52 | 53 | expect(getButtonComputedStyle(fixture).backgroundColor).toEqual( 54 | "rgb(0, 255, 0)", 55 | ); 56 | }); 57 | 58 | it("should choose the right contrast color for the primary color", () => { 59 | button.color = "primary"; 60 | fixture.detectChanges(); 61 | 62 | expect(getButtonComputedStyle(fixture).color).toEqual("rgb(0, 0, 0)"); 63 | }); 64 | 65 | it("should render a button in the given accent color", () => { 66 | button.color = "accent"; 67 | fixture.detectChanges(); 68 | 69 | expect(getButtonComputedStyle(fixture).backgroundColor).toEqual( 70 | "rgb(0, 0, 255)", 71 | ); 72 | }); 73 | 74 | it("should choose the right contrast color for the accent color", () => { 75 | button.color = "accent"; 76 | fixture.detectChanges(); 77 | 78 | expect(getButtonComputedStyle(fixture).color).toEqual( 79 | "rgb(255, 255, 255)", 80 | ); 81 | }); 82 | 83 | it("should render a button in the given warn color", () => { 84 | button.color = "warn"; 85 | fixture.detectChanges(); 86 | 87 | expect(getButtonComputedStyle(fixture).backgroundColor).toEqual( 88 | "rgb(255, 0, 0)", 89 | ); 90 | }); 91 | 92 | it("should choose the right contrast color for the warn color", () => { 93 | button.color = "warn"; 94 | fixture.detectChanges(); 95 | 96 | expect(getButtonComputedStyle(fixture).color).toEqual("rgb(0, 0, 0)"); 97 | }); 98 | }); 99 | 100 | describe("default colors", () => { 101 | let button: ButtonComponent; 102 | let fixture: ComponentFixture; 103 | 104 | beforeEach(() => { 105 | TestBed.configureTestingModule({ 106 | imports: [MaterialCssVarsModule.forRoot({ isDarkTheme })], 107 | }); 108 | fixture = TestBed.createComponent(ButtonComponent); 109 | button = fixture.componentInstance; 110 | }); 111 | 112 | it("should render a button in the default primary color", () => { 113 | button.color = "primary"; 114 | fixture.detectChanges(); 115 | 116 | expect(getButtonComputedStyle(fixture).backgroundColor).toEqual( 117 | "rgb(3, 169, 244)", 118 | ); 119 | }); 120 | 121 | it("should choose the right contrast color for the primary color", () => { 122 | button.color = "primary"; 123 | fixture.detectChanges(); 124 | 125 | expect(getButtonComputedStyle(fixture).color).toEqual( 126 | "rgb(255, 255, 255)", 127 | ); 128 | }); 129 | 130 | it("should render a button in the default accent color", () => { 131 | button.color = "accent"; 132 | fixture.detectChanges(); 133 | 134 | expect(getButtonComputedStyle(fixture).backgroundColor).toEqual( 135 | "rgb(233, 30, 99)", 136 | ); 137 | }); 138 | 139 | it("should choose the right contrast color for the accent color", () => { 140 | button.color = "accent"; 141 | fixture.detectChanges(); 142 | 143 | expect(getButtonComputedStyle(fixture).color).toEqual( 144 | "rgb(255, 255, 255)", 145 | ); 146 | }); 147 | 148 | it("should render a button in the default warn color", () => { 149 | button.color = "warn"; 150 | fixture.detectChanges(); 151 | 152 | expect(getButtonComputedStyle(fixture).backgroundColor).toEqual( 153 | "rgb(244, 67, 54)", 154 | ); 155 | }); 156 | 157 | it("should choose the right contrast color for the warn color", () => { 158 | button.color = "warn"; 159 | fixture.detectChanges(); 160 | 161 | expect(getButtonComputedStyle(fixture).color).toEqual( 162 | "rgb(255, 255, 255)", 163 | ); 164 | }); 165 | }); 166 | }); 167 | }); 168 | 169 | afterEach(() => { 170 | // clear CSS variables 171 | document.documentElement.removeAttribute("style"); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /projects/material-css-vars/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declaration": true, 6 | "inlineSources": true, 7 | "types": [], 8 | "lib": ["dom", "es2018"] 9 | }, 10 | "angularCompilerOptions": { 11 | "annotateForClosureCompiler": false, 12 | "compilationMode": "partial", 13 | "skipTemplateCodegen": true, 14 | "strictMetadataEmit": true, 15 | "fullTemplateTypeCheck": true, 16 | "strictInjectionParameters": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": ["src/test.ts", "**/*.spec.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /projects/material-css-vars/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/app/_app.theme.scss: -------------------------------------------------------------------------------- 1 | @use "sass:map"; 2 | @use "@angular/material" as mat; 3 | 4 | @mixin theme($theme) { 5 | // Extract the palettes you need from the theme definition. 6 | $color: mat.m2-get-color-config($theme); 7 | $primary: map.get($color, primary); 8 | $accent: map.get($color, accent); 9 | 10 | // Define any styles affected by the theme. 11 | .app-header { 12 | // Use mat-color to extract individual colors from a palette. 13 | // background-color: mat-color($primary); 14 | border: medium solid mat.m2-get-color-from-palette($accent, A400); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular Material CSS Vars Demo 5 | 6 | 7 |
8 |
9 |

Configure Theme

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 |

Primary color

30 |
41 |
42 |
43 | 44 | 45 |

Accent color

46 |
57 |
58 |
59 | 60 | 61 | 62 |

Warn color

63 |
74 |
75 |
76 | 77 | 78 | 79 |

Contrast Color

80 | 81 | Auto Contrast Colors 85 | 86 |
87 |
88 | 89 | 90 | Primary Contrast Threshold 91 | 98 | @for (hue of hues; track hue.value) { 99 | 100 | {{ hue.viewValue }} 101 | 102 | } 103 | 104 | 105 |
106 | 107 | 108 | Accent Contrast Threshold 109 | 116 | @for (hue of hues; track hue.value) { 117 | 118 | {{ hue.viewValue }} 119 | 120 | } 121 | 122 | 123 |
124 | 125 | 126 | Warn Contrast Threshold 127 | 132 | @for (hue of hues; track hue.value) { 133 | 134 | {{ hue.viewValue }} 135 | 136 | } 137 | 138 | 139 |
140 |
141 | 142 | 143 | 144 |

Dark/Light

145 | Dark Theme Enabled 147 | 148 |
149 |
150 | 151 | 152 | 153 |

Color Algorithm

154 | {{ colorAlgorithm }} 158 | 159 |
160 |
161 |
162 | 163 | 164 | 165 |

Palette

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 |
175 |
176 |
50
177 |
178 |
179 |
100
180 |
181 |
182 |
200
183 |
184 |
185 |
300
186 |
187 |
188 |
400
189 |
190 |
191 |
primary
500
192 |
193 |
194 |
600
195 |
196 |
197 |
700
198 |
199 |
200 |
800
201 |
202 |
203 |
900
204 |
205 |
206 | 207 |
208 |
209 |
50
210 |
211 |
212 |
100
213 |
214 |
215 |
200
216 |
217 |
218 |
300
219 |
220 |
221 |
400
222 |
223 |
224 |
primary
500
225 |
226 |
227 |
600
228 |
229 |
230 |
700
231 |
232 |
233 |
800
234 |
235 |
236 |
900
237 |
238 |
239 | 240 |
241 |
242 |
50
243 |
244 |
245 |
100
246 |
247 |
248 |
200
249 |
250 |
251 |
300
252 |
253 |
254 |
400
255 |
256 |
257 |
primary
500
258 |
259 |
260 |
600
261 |
262 |
263 |
700
264 |
265 |
266 |
800
267 |
268 |
269 |
900
270 |
271 |
272 |
273 |
274 |
275 | 276 |
277 |

Demo Components

278 | 279 | 280 | 281 | 282 | 283 | 284 |

Material Buttons

285 | 286 |
287 | 288 | 289 | 290 | 291 | 292 | Link 293 |
294 | 295 | 296 |
297 | 298 | 299 | 300 | 301 | 302 | Link 303 |
304 | 305 | 306 |
307 | 308 | 309 | 310 | 311 | 312 | Link 313 |
314 | 315 | 316 |
317 | 318 | 319 | 320 | 321 | 322 | Link 323 |
324 | 325 | 326 |
327 | 333 | 340 | 347 | 354 | 361 |
362 | 363 | 364 |
365 | 368 | 371 | 374 | 377 | 378 | link 379 | 380 |
381 | 382 | 383 |
384 | 387 | 390 | 393 | 396 | 397 | link 398 | 399 |
400 | 401 |
402 | 406 | 410 | 414 | 418 | 419 | Link 420 | link 421 | 422 |
423 |
424 |
425 | 426 |
427 | 428 | 429 | 430 | 431 | 432 |

Material Input

433 | 434 | First Name 435 | 436 | This is a hint 437 | 438 | 439 | Nickname 440 | 446 | 447 | {{ nickname.value.length }} / 50 448 | 449 | 450 | 451 | android 452 | Phone Number 453 | 454 | 455 | 456 | motorcycle 457 | Motorcycle Model 458 | 459 | 460 |
461 |
462 | 463 | 464 | 465 | 466 | 467 |

Material Checkbox

468 |
469 | Check me! 470 | Disabled 473 |
474 | 475 |
476 | 477 | 484 | {{ task.name }} 485 | 486 | 487 | 488 |
    489 | @for (subtask of task.subtasks; track subtask.name) { 490 |
  • 491 | 496 | {{ subtask.name }} 497 | 498 |
  • 499 | } 500 |
501 |
502 |
503 |
504 |
505 | 506 |
507 | 508 | 509 | 510 |
511 | 512 | 513 |

Progress spinner configuration

514 | 515 |
516 | 519 | 523 | 524 | Primary 525 | 526 | 527 | Accent 528 | 529 | 530 | Warn 531 | 532 | 533 |
534 | 535 |
536 | 539 | 540 | 544 | Determinate 545 | 546 | 550 | Indeterminate 551 | 552 | 553 |
554 | 555 | @if (spinnerMode === "determinate") { 556 |
557 | 560 | 561 | 562 | 563 |
564 | } 565 |
566 |
567 | 568 | 569 | 570 |

Result

571 | 572 | 578 |
579 |
580 |
581 | 582 | 583 | 584 | 585 | 586 |

Material Progress Bar

587 | 590 | 596 | 599 | 607 |
608 |
609 | 610 |
611 | 612 | 613 | 614 | 615 | 616 |

Material Chips

617 | 618 | @for ( 619 | chipColor of availableSpinnerColors; 620 | track chipColor.name 621 | ) { 622 | 623 | {{ chipColor.name }} 624 | 625 | } 626 | Disabled 627 | 628 |
629 |
630 | 631 | 632 | 633 | 634 | 635 |

Material Menu

636 | 639 | 640 | 644 | 648 | 652 | 653 |
654 |
655 | 656 | 657 | 658 | 659 | 660 |

Material Select

661 | 662 | Choose an option 663 | 664 | Option 1 665 | Option 2 (disabled) 668 | Option 3 669 | 670 | 671 |
672 |
673 | 674 | 675 | 676 | 677 | 678 |

Material Date Picker

679 | 680 | Choose a date (default) 681 | 682 | 683 | 684 | 685 | 686 | Choose a date (accent) 687 | 688 | 689 | 690 | 691 | 692 | Choose a date (warn) 693 | 694 | 695 | 696 | 697 | 698 | Custom (accent/primary) 699 | 700 | 701 | 702 | 703 |
704 |
705 | 706 | 707 | 708 | 709 | 710 |

Material Date Range Picker

711 | 712 | Enter a date range (default) 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | Enter a date range (accent) 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | Enter a date range (warn) 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 |
739 |
740 | 741 | 742 | 743 | 744 | Slider 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 |
753 | 754 | 755 | 756 | Message 757 | 758 | 759 | 760 | 761 | Action 762 | 763 | 764 | 765 | 771 | 772 |
773 |
774 | 775 | 776 | 781 | favorite 782 | 783 | 784 |
785 |
786 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | @use "../../projects/material-css-vars" as mat-css-vars; 2 | 3 | $lighter-dark: rgb(88, 88, 88); 4 | 5 | .bg-50 { 6 | @include mat-css-vars.mat-css-color-and-contrast-primary(50); 7 | } 8 | 9 | .bg-100 { 10 | @include mat-css-vars.mat-css-color-and-contrast-primary(100); 11 | } 12 | 13 | .bg-200 { 14 | @include mat-css-vars.mat-css-color-and-contrast-primary(200); 15 | } 16 | 17 | .bg-300 { 18 | @include mat-css-vars.mat-css-color-and-contrast-primary(300); 19 | } 20 | 21 | .bg-400 { 22 | @include mat-css-vars.mat-css-color-and-contrast-primary(400); 23 | } 24 | 25 | .bg-500 { 26 | @include mat-css-vars.mat-css-color-and-contrast-primary(500); 27 | border: 4px dashed mat-css-vars.mat-css-color-accent(); 28 | } 29 | 30 | .bg-600 { 31 | @include mat-css-vars.mat-css-color-and-contrast-primary(600); 32 | } 33 | 34 | .bg-700 { 35 | @include mat-css-vars.mat-css-color-and-contrast-primary(700); 36 | } 37 | 38 | .bg-800 { 39 | @include mat-css-vars.mat-css-color-and-contrast-primary(800); 40 | } 41 | 42 | .bg-900 { 43 | @include mat-css-vars.mat-css-color-and-contrast-primary(900); 44 | } 45 | 46 | .bga-50 { 47 | @include mat-css-vars.mat-css-color-and-contrast-accent(50); 48 | } 49 | 50 | .bga-100 { 51 | @include mat-css-vars.mat-css-color-and-contrast-accent(100); 52 | } 53 | 54 | .bga-200 { 55 | @include mat-css-vars.mat-css-color-and-contrast-accent(200); 56 | } 57 | 58 | .bga-300 { 59 | @include mat-css-vars.mat-css-color-and-contrast-accent(300); 60 | } 61 | 62 | .bga-400 { 63 | @include mat-css-vars.mat-css-color-and-contrast-accent(400); 64 | } 65 | 66 | .bga-500 { 67 | @include mat-css-vars.mat-css-color-and-contrast-accent(500); 68 | border: 4px dashed mat-css-vars.mat-css-color-primary(); 69 | } 70 | 71 | .bga-600 { 72 | @include mat-css-vars.mat-css-color-and-contrast-accent(600); 73 | } 74 | 75 | .bga-700 { 76 | @include mat-css-vars.mat-css-color-and-contrast-accent(700); 77 | } 78 | 79 | .bga-800 { 80 | @include mat-css-vars.mat-css-color-and-contrast-accent(800); 81 | } 82 | 83 | .bga-900 { 84 | @include mat-css-vars.mat-css-color-and-contrast-accent(900); 85 | } 86 | 87 | .bgw-50 { 88 | @include mat-css-vars.mat-css-color-and-contrast-warn(50); 89 | } 90 | 91 | .bgw-100 { 92 | @include mat-css-vars.mat-css-color-and-contrast-warn(100); 93 | } 94 | 95 | .bgw-200 { 96 | @include mat-css-vars.mat-css-color-and-contrast-warn(200); 97 | } 98 | 99 | .bgw-300 { 100 | @include mat-css-vars.mat-css-color-and-contrast-warn(300); 101 | } 102 | 103 | .bgw-400 { 104 | @include mat-css-vars.mat-css-color-and-contrast-warn(400); 105 | } 106 | 107 | .bgw-500 { 108 | @include mat-css-vars.mat-css-color-and-contrast-warn(500); 109 | border: 4px dashed mat-css-vars.mat-css-color-primary(); 110 | } 111 | 112 | .bgw-600 { 113 | @include mat-css-vars.mat-css-color-and-contrast-warn(600); 114 | } 115 | 116 | .bgw-700 { 117 | @include mat-css-vars.mat-css-color-and-contrast-warn(700); 118 | } 119 | 120 | .bgw-800 { 121 | @include mat-css-vars.mat-css-color-and-contrast-warn(800); 122 | } 123 | 124 | .bgw-900 { 125 | @include mat-css-vars.mat-css-color-and-contrast-warn(900); 126 | } 127 | 128 | ::ng-deep color-picker { 129 | .color-picker { 130 | @include mat-css-vars.mat-css-dark-theme { 131 | color: #fff; 132 | background: $lighter-dark; 133 | } 134 | } 135 | } 136 | 137 | .config-cards { 138 | display: flex; 139 | flex-wrap: wrap; 140 | 141 | > mat-card { 142 | flex: 1; 143 | margin-right: 12px; 144 | 145 | &:last-child { 146 | margin-right: 0; 147 | } 148 | } 149 | } 150 | 151 | .palette { 152 | display: flex; 153 | flex-wrap: wrap; 154 | 155 | > div { 156 | flex: 1; 157 | height: 50px; 158 | text-align: center; 159 | position: relative; 160 | box-sizing: border-box; 161 | 162 | div { 163 | position: absolute; 164 | left: 50%; 165 | top: 50%; 166 | transform: translate(-50%, -50%); 167 | } 168 | } 169 | } 170 | 171 | :host ::ng-deep { 172 | .mat-tab-body-content { 173 | padding: 12px; 174 | } 175 | } 176 | 177 | .content { 178 | padding: 5px; 179 | } 180 | 181 | .box { 182 | margin: 6px; 183 | } 184 | 185 | .content mat-card { 186 | margin-top: 12px; 187 | } 188 | 189 | .content mat-checkbox { 190 | margin: 10px; 191 | } 192 | 193 | .toolbar-filler { 194 | flex: 1 1 auto; 195 | } 196 | 197 | .fab { 198 | position: fixed !important; 199 | bottom: 20px; 200 | right: 20px; 201 | z-index: 3; 202 | } 203 | 204 | .spinner { 205 | height: 30px; 206 | width: 30px; 207 | display: inline-block; 208 | } 209 | 210 | .button { 211 | margin: 5px; 212 | } 213 | 214 | .progress { 215 | margin: 5px; 216 | } 217 | 218 | .example-section { 219 | display: flex; 220 | align-content: center; 221 | align-items: center; 222 | height: 60px; 223 | } 224 | 225 | .example-margin { 226 | margin: 10px; 227 | } 228 | 229 | .mat-slider-horizontal { 230 | width: 300px; 231 | } 232 | 233 | .mat-slider-vertical { 234 | height: 300px; 235 | } 236 | 237 | mat-chip { 238 | max-width: 200px; 239 | } 240 | 241 | .picker-label { 242 | margin-top: 10px; 243 | margin-bottom: 0; 244 | font-weight: bold; 245 | } 246 | 247 | .example-button-row { 248 | margin-bottom: 10px; 249 | 250 | button { 251 | margin: 4px; 252 | } 253 | } 254 | 255 | mat-form-field { 256 | margin-right: 12px; 257 | } 258 | 259 | .checkbox-section { 260 | margin: 12px 0; 261 | } 262 | 263 | .checkbox-margin { 264 | margin: 0 12px; 265 | } 266 | 267 | ul { 268 | list-style-type: none; 269 | margin-top: 4px; 270 | } 271 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { 3 | ThemePalette, 4 | MatOptionModule, 5 | MatNativeDateModule, 6 | } from "@angular/material/core"; 7 | import { MatDialog, MatDialogModule } from "@angular/material/dialog"; 8 | import { MatSnackBar, MatSnackBarModule } from "@angular/material/snack-bar"; 9 | import { MaterialCssVarsService } from "../../projects/material-css-vars/src/lib/material-css-vars.service"; 10 | import { 11 | HueValue, 12 | MatCssHueColorContrastMapItem, 13 | } from "../../projects/material-css-vars/src/lib/model"; 14 | import { 15 | ProgressSpinnerMode, 16 | MatProgressSpinnerModule, 17 | } from "@angular/material/progress-spinner"; 18 | import { MatDatepickerModule } from "@angular/material/datepicker"; 19 | import { MatMenuModule } from "@angular/material/menu"; 20 | import { MatChipsModule } from "@angular/material/chips"; 21 | import { MatProgressBarModule } from "@angular/material/progress-bar"; 22 | import { MatSliderModule } from "@angular/material/slider"; 23 | import { MatRadioModule } from "@angular/material/radio"; 24 | import { MatCheckboxModule } from "@angular/material/checkbox"; 25 | import { MatInputModule } from "@angular/material/input"; 26 | import { MatIconModule } from "@angular/material/icon"; 27 | import { MatButtonModule } from "@angular/material/button"; 28 | import { MatTabsModule } from "@angular/material/tabs"; 29 | import { FormsModule } from "@angular/forms"; 30 | import { MatSelectModule } from "@angular/material/select"; 31 | import { MatFormFieldModule } from "@angular/material/form-field"; 32 | import { MatSlideToggleModule } from "@angular/material/slide-toggle"; 33 | import { ColorPickerDirective } from "ngx-color-picker"; 34 | import { MatCardModule } from "@angular/material/card"; 35 | import { MatToolbarModule } from "@angular/material/toolbar"; 36 | import { MatSidenavModule } from "@angular/material/sidenav"; 37 | 38 | export interface Task { 39 | name: string; 40 | completed: boolean; 41 | color: ThemePalette; 42 | subtasks?: Task[]; 43 | } 44 | 45 | interface Hue { 46 | value: string; 47 | viewValue: string; 48 | } 49 | 50 | interface SpinnerColor { 51 | name: string; 52 | color: ThemePalette; 53 | } 54 | 55 | @Component({ 56 | selector: "app-root", 57 | templateUrl: "./app.component.html", 58 | styleUrls: ["./app.component.scss"], 59 | imports: [ 60 | MatSidenavModule, 61 | MatToolbarModule, 62 | MatCardModule, 63 | ColorPickerDirective, 64 | MatSlideToggleModule, 65 | MatFormFieldModule, 66 | MatSelectModule, 67 | FormsModule, 68 | MatOptionModule, 69 | MatTabsModule, 70 | MatButtonModule, 71 | MatIconModule, 72 | MatInputModule, 73 | MatCheckboxModule, 74 | MatRadioModule, 75 | MatSliderModule, 76 | MatProgressSpinnerModule, 77 | MatProgressBarModule, 78 | MatChipsModule, 79 | MatMenuModule, 80 | MatDatepickerModule, 81 | MatDialogModule, 82 | MatSnackBarModule, 83 | MatNativeDateModule, 84 | ], 85 | }) 86 | export class AppComponent { 87 | isDarkTheme = false; 88 | threshold?: HueValue; 89 | isAlternativeColorAlgorithm = false; 90 | 91 | palettePrimary?: MatCssHueColorContrastMapItem[]; 92 | 93 | readonly hues: Hue[] = [ 94 | { value: "50", viewValue: "50" }, 95 | { value: "100", viewValue: "100" }, 96 | { value: "200", viewValue: "200" }, 97 | { value: "300", viewValue: "300" }, 98 | { value: "400", viewValue: "400" }, 99 | { value: "500", viewValue: "500" }, 100 | { value: "600", viewValue: "600" }, 101 | { value: "700", viewValue: "700" }, 102 | { value: "800", viewValue: "800" }, 103 | { value: "900", viewValue: "900" }, 104 | { value: "A100", viewValue: "A100" }, 105 | { value: "A200", viewValue: "A200" }, 106 | { value: "A400", viewValue: "A400" }, 107 | { value: "A700", viewValue: "A700" }, 108 | ]; 109 | 110 | spinnerMode: ProgressSpinnerMode = "indeterminate"; 111 | spinnerValue = 25; 112 | spinnerColor: ThemePalette = "primary"; 113 | readonly availableSpinnerColors: SpinnerColor[] = [ 114 | { name: "none", color: undefined }, 115 | { name: "Primary", color: "primary" }, 116 | { name: "Accent", color: "accent" }, 117 | { name: "Warn", color: "warn" }, 118 | ]; 119 | 120 | progress = 0; 121 | 122 | readonly task: Task = { 123 | name: "Indeterminate", 124 | completed: false, 125 | color: "primary", 126 | subtasks: [ 127 | { name: "Primary", completed: false, color: "primary" }, 128 | { name: "Accent", completed: false, color: "accent" }, 129 | { name: "Warn", completed: false, color: "warn" }, 130 | ], 131 | }; 132 | 133 | allComplete = false; 134 | someComplete = false; 135 | 136 | constructor( 137 | private _dialog: MatDialog, 138 | private _snackbar: MatSnackBar, 139 | public materialCssVarsService: MaterialCssVarsService, 140 | ) { 141 | this.toggleTheme(); 142 | // this.onPrimaryChange(this.primary); 143 | // this.onAccentChange(this.accent); 144 | 145 | // Update the value for the progress-bar on an interval. 146 | setInterval(() => { 147 | this.progress = (this.progress + Math.floor(Math.random() * 4) + 1) % 100; 148 | }, 200); 149 | } 150 | 151 | showSnackbar(message: string, action: string) { 152 | // this._snackbar.open('YUM SNACKS', 'CHEW'); 153 | this._snackbar.open(message, action); 154 | } 155 | 156 | onPrimaryChange(hex: string) { 157 | this.materialCssVarsService.setPrimaryColor(hex); 158 | this.palettePrimary = 159 | this.materialCssVarsService.getPaletteWithContrastForColor(hex); 160 | } 161 | 162 | onAccentChange(hex: string) { 163 | this.materialCssVarsService.setAccentColor(hex); 164 | } 165 | 166 | onWarnChange(hex: string) { 167 | this.materialCssVarsService.setWarnColor(hex); 168 | } 169 | 170 | onChangeThresholdPrimary(threshold: HueValue) { 171 | this.threshold = threshold; 172 | this.materialCssVarsService.setContrastColorThresholdPrimary(threshold); 173 | } 174 | 175 | onChangeThresholdAccent(threshold: HueValue) { 176 | this.threshold = threshold; 177 | this.materialCssVarsService.setContrastColorThresholdAccent(threshold); 178 | } 179 | 180 | onChangeThresholdWarn(threshold: HueValue) { 181 | this.threshold = threshold; 182 | this.materialCssVarsService.setContrastColorThresholdWarn(threshold); 183 | } 184 | 185 | toggleAutoContrast() { 186 | this.materialCssVarsService.setAutoContrastEnabled( 187 | !this.materialCssVarsService.isAutoContrast, 188 | ); 189 | } 190 | 191 | toggleTheme() { 192 | this.isDarkTheme = !this.isDarkTheme; 193 | this.materialCssVarsService.setDarkTheme(this.isDarkTheme); 194 | } 195 | 196 | toggleTraditionalColor() { 197 | this.isAlternativeColorAlgorithm = !this.isAlternativeColorAlgorithm; 198 | this.materialCssVarsService.setAlternativeColorAlgorithm( 199 | this.isAlternativeColorAlgorithm, 200 | ); 201 | } 202 | 203 | get colorAlgorithm(): string { 204 | return this.isAlternativeColorAlgorithm 205 | ? "Alternative" 206 | : "Constantin (default)"; 207 | } 208 | 209 | updateCompletionState() { 210 | this.allComplete = this.task.subtasks?.every((t) => t.completed) ?? false; 211 | this.someComplete = 212 | (!this.allComplete && this.task.subtasks?.some((t) => t.completed)) ?? 213 | false; 214 | } 215 | 216 | setAll(completed: boolean) { 217 | this.allComplete = completed; 218 | if (this.task.subtasks == null) { 219 | return; 220 | } 221 | this.task.subtasks.forEach((t) => (t.completed = completed)); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig } from "@angular/core"; 2 | import { provideMaterialCssVars } from "../../projects/material-css-vars/src/lib/material-css-vars.module"; 3 | import { APP_BASE_HREF } from "@angular/common"; 4 | 5 | export const appConfig: ApplicationConfig = { 6 | providers: [ 7 | provideMaterialCssVars({ 8 | primary: "#3f51b5", 9 | accent: "#e91e63", 10 | warn: "#f44336", 11 | }), 12 | { provide: APP_BASE_HREF, useValue: window._app_base ?? "/" }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannesjo/angular-material-css-vars/ca5e9918ed4bd28f215aa851ed34fbf882ac7e3b/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannesjo/angular-material-css-vars/ca5e9918ed4bd28f215aa851ed34fbf882ac7e3b/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularMaterialCssVars 6 | 7 | 8 | 9 | 10 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from "@angular/core"; 2 | import { environment } from "./environments/environment"; 3 | import { AppComponent } from "./app/app.component"; 4 | import { bootstrapApplication } from "@angular/platform-browser"; 5 | import { appConfig } from "./app/app.config"; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | bootstrapApplication(AppComponent, appConfig).catch((err: unknown) => { 12 | console.error(err); 13 | }); 14 | 15 | declare global { 16 | interface Window { 17 | _app_base?: string; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @use "../projects/material-css-vars" as mat-css-vars; 2 | @use "@angular/material" as mat; 3 | 4 | @use "./app/app.theme" as app; 5 | 6 | $custom-typography: mat.m2-define-typography-config( 7 | $font-family: "Roboto, monospace", 8 | $body-1: mat.m2-define-typography-level(16px, 24px, 500), 9 | $headline-1: mat.m2-define-typography-level(16px, 24px, 500), 10 | ); 11 | 12 | @include mat-css-vars.init-material-css-vars( 13 | $typography-config: $custom-typography 14 | ) 15 | using($mat-css-theme) { 16 | @include app.theme($mat-css-theme); 17 | } 18 | 19 | @include mat-css-vars.mat-css-set-palette-defaults( 20 | mat.$m2-light-blue-palette, 21 | "primary" 22 | ); 23 | @include mat-css-vars.mat-css-set-palette-defaults( 24 | mat.$m2-pink-palette, 25 | "accent" 26 | ); 27 | @include mat-css-vars.mat-css-set-palette-defaults(mat.$m2-red-palette, "warn"); 28 | 29 | body { 30 | padding: 0; 31 | margin: 0; 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts"], 8 | "include": ["src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "esModuleInterop": true, 8 | "declaration": false, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "importHelpers": true, 13 | "target": "ES2022", 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "typeRoots": ["node_modules/@types"], 19 | "lib": ["es2018", "dom"], 20 | "paths": { 21 | "angular-material-css-variables": ["dist/angular-material-css-variables"], 22 | "angular-material-css-variables/*": [ 23 | "dist/angular-material-css-variables/*" 24 | ], 25 | "material-css-vars": ["dist/material-css-vars"], 26 | "material-css-vars/*": ["dist/material-css-vars/*"] 27 | }, 28 | "useDefineForClassFields": false 29 | }, 30 | "angularCompilerOptions": { 31 | "strictTemplates": true, 32 | "strictInjectionParameters": true, 33 | "strictInputAccessModifiers": true 34 | } 35 | } 36 | --------------------------------------------------------------------------------