├── .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 |

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 |
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 |
178 |
181 |
184 |
187 |
190 |
193 |
196 |
199 |
202 |
205 |
206 |
207 |
208 |
211 |
214 |
217 |
220 |
223 |
226 |
229 |
232 |
235 |
238 |
239 |
240 |
241 |
244 |
247 |
250 |
253 |
256 |
259 |
262 |
265 |
268 |
271 |
272 |
273 |
274 |
275 |
276 |
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 |
--------------------------------------------------------------------------------