diff --git a/.gitignore b/.gitignore index 59a9c88a366..049b1e580d5 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,9 @@ src/**/*.js src/**/*.js.map src/**/*.css.map +# Artefacts +projects/igniteui-angular/**/*.component.css + # Typedoc Theme extras/docs/themes/typedoc/bin extras/docs/themes/sassdoc/node_modules diff --git a/package-lock.json b/package-lock.json index 158e800c4cf..028cd56e2b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,7 @@ "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "autoprefixer": "^10.4.16", + "concurrently": "^9.0.1", "del": "^6.0.0", "eslint": "^9.15.0", "fs-extra": "^11.3.0", @@ -81,6 +82,7 @@ "karma-junit-reporter": "^2.0.1", "karma-parallel": "^0.3.1", "karma-spec-reporter": "^0.0.36", + "node-watch": "^0.7.4", "lit-html": "^3.2.1", "ng-packagr": "^19.1.0", "postcss": "^8.5.1", @@ -8521,6 +8523,124 @@ "node": ">=0.10.0" } }, + "node_modules/concurrently": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz", + "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/concurrently/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -16224,6 +16344,16 @@ "dev": true, "license": "MIT" }, + "node_modules/node-watch": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.4.tgz", + "integrity": "sha512-RinNxoz4W1cep1b928fuFhvAQ5ag/+1UlMDV7rbyGthBIgsiEouS4kvRayvvboxii4m8eolKOIBo3OjDqbc+uQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", diff --git a/package.json b/package.json index 6dc029aec23..a7e078169bb 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve --open --hmr", + "start": "concurrently -r \"npm run build:styles:components\" \"npm run watch:styles\" \"ng serve --open --hmr\"", "start:elements": "ng serve --project igniteui-angular-elements", "build": "ng build --configuration production", "test": "ng test igniteui-angular", @@ -22,8 +22,10 @@ "test:i18n": "ts-node --skip-project ./projects/igniteui-angular/src/lib/core/i18n/tests/tests.mjs", "test:elements": "ng test igniteui-angular-elements --watch=false --no-progress --code-coverage --source-map=false", "test:elements:watch": "ng test igniteui-angular-elements", - "build:lib": "ng build igniteui-angular --configuration production && npm run build:styles", - "build:styles": "node scripts/build-styles.mjs", + "build:lib": "ng build igniteui-angular --configuration production && npm run build:styles:all", + "build:styles:all": "node scripts/build-styles.mjs", + "build:styles:components": "node scripts/build-component-styles.mjs", + "watch:styles": "node scripts/watch-styles.mjs", "build:migrations": "gulp copyMigrations && tsc --listEmittedFiles --project ./projects/igniteui-angular/migrations/tsconfig.json", "build:schematics": "gulp copySchematics && tsc --listEmittedFiles --project ./projects/igniteui-angular/schematics/tsconfig.json", "build:docs": "sassdoc projects/igniteui-angular/src/lib/core/styles && gulp typedoc-build:theme", @@ -105,6 +107,7 @@ "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "autoprefixer": "^10.4.16", + "concurrently": "^9.0.1", "del": "^6.0.0", "eslint": "^9.15.0", "fs-extra": "^11.3.0", @@ -130,6 +133,7 @@ "karma-junit-reporter": "^2.0.1", "karma-parallel": "^0.3.1", "karma-spec-reporter": "^0.0.36", + "node-watch": "^0.7.4", "lit-html": "^3.2.1", "ng-packagr": "^19.1.0", "postcss": "^8.5.1", diff --git a/projects/igniteui-angular/src/lib/avatar/avatar.component.scss b/projects/igniteui-angular/src/lib/avatar/avatar.component.scss new file mode 100644 index 00000000000..dd684163cd7 --- /dev/null +++ b/projects/igniteui-angular/src/lib/avatar/avatar.component.scss @@ -0,0 +1,4 @@ +@use 'themes/base'; +@use 'themes/shared'; +@use 'themes/light'; +@use 'themes/dark'; diff --git a/projects/igniteui-angular/src/lib/avatar/avatar.component.ts b/projects/igniteui-angular/src/lib/avatar/avatar.component.ts index 3a5411bcaa0..bf1229c68a2 100644 --- a/projects/igniteui-angular/src/lib/avatar/avatar.component.ts +++ b/projects/igniteui-angular/src/lib/avatar/avatar.component.ts @@ -6,7 +6,9 @@ import { Input, OnInit, TemplateRef, - ViewChild + ViewChild, + ViewEncapsulation, + inject } from '@angular/core'; import { mkenum, normalizeURI } from '../core/utils'; @@ -53,9 +55,13 @@ export type IgxAvatarType = (typeof IgxAvatarType)[keyof typeof IgxAvatarType]; @Component({ selector: 'igx-avatar', templateUrl: 'avatar.component.html', + styleUrl: 'avatar.component.css', + encapsulation: ViewEncapsulation.None, imports: [IgxIconComponent, NgTemplateOutlet] }) export class IgxAvatarComponent implements OnInit { + public elementRef = inject(ElementRef); + /** * Returns the `aria-label` attribute of the avatar. * @@ -332,8 +338,6 @@ export class IgxAvatarComponent implements OnInit { } } - constructor(public elementRef: ElementRef) { } - /** * Returns the css url of the image. * diff --git a/projects/igniteui-angular/src/lib/avatar/themes/_base.scss b/projects/igniteui-angular/src/lib/avatar/themes/_base.scss new file mode 100644 index 00000000000..f230c09826e --- /dev/null +++ b/projects/igniteui-angular/src/lib/avatar/themes/_base.scss @@ -0,0 +1,61 @@ +@use 'igniteui-theming/sass/animations' as *; +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'light/tokens' as *; + +$theme: $material; + +@layer base { + @include b(igx-avatar) { + @include sizable(); + + --component-size: var(--ig-size, #{var-get($theme, 'default-size')}); + + position: relative; + display: inline-flex; + justify-content: center; + align-items: center; + user-select: none; + color: var-get($theme, 'color'); + background: var-get($theme, 'background'); + vertical-align: middle; + outline-style: none; + flex-shrink: 0; + width: var-get($theme, 'size'); + height: var-get($theme, 'size'); + + igx-icon { + --ig-size: 3; + + color: var-get($theme, 'icon-color'); + } + + &::after { + box-shadow: none; + transition: box-shadow 0.15s $ease-in-out-quad; + } + + @include e(image) { + width: 100%; + height: 100%; + border-radius: inherit; + background-size: cover; + background-repeat: no-repeat; + background-position: center; + } + + @include m(circle) { + border-radius: calc(#{var-get($theme, 'size')} / 2); + } + + @include m(rounded) { + border-radius: var-get($theme, 'border-radius'); + } + + @include m(initials) { + text-transform: uppercase; + font-size: calc(#{var-get($theme, 'size')} / 2); + line-height: calc(#{var-get($theme, 'size')} / 2); + } + } +} diff --git a/projects/igniteui-angular/src/lib/avatar/themes/dark/_index.scss b/projects/igniteui-angular/src/lib/avatar/themes/dark/_index.scss new file mode 100644 index 00000000000..9dbf039674c --- /dev/null +++ b/projects/igniteui-angular/src/lib/avatar/themes/dark/_index.scss @@ -0,0 +1,6 @@ +@use 'sass:meta'; +@use 'tokens'; +@use 'styles/themes/standalone' as *; + +$tokens: meta.module-variables(tokens); +@include themes(igx-avatar, $tokens, dark); diff --git a/projects/igniteui-angular/src/lib/avatar/themes/dark/_tokens.scss b/projects/igniteui-angular/src/lib/avatar/themes/dark/_tokens.scss new file mode 100644 index 00000000000..f5e829c9657 --- /dev/null +++ b/projects/igniteui-angular/src/lib/avatar/themes/dark/_tokens.scss @@ -0,0 +1,7 @@ +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/themes/schemas/components/dark/avatar' as *; + +$material: digest-schema($dark-material-avatar); +$bootstrap: digest-schema($dark-bootstrap-avatar); +$fluent: digest-schema($dark-fluent-avatar); +$indigo: digest-schema($dark-indigo-avatar); diff --git a/projects/igniteui-angular/src/lib/avatar/themes/light/_index.scss b/projects/igniteui-angular/src/lib/avatar/themes/light/_index.scss new file mode 100644 index 00000000000..b59827df365 --- /dev/null +++ b/projects/igniteui-angular/src/lib/avatar/themes/light/_index.scss @@ -0,0 +1,6 @@ +@use 'sass:meta'; +@use 'tokens'; +@use 'styles/themes/standalone' as *; + +$tokens: meta.module-variables(tokens); +@include themes(igx-avatar, $tokens, light); diff --git a/projects/igniteui-angular/src/lib/avatar/themes/light/_tokens.scss b/projects/igniteui-angular/src/lib/avatar/themes/light/_tokens.scss new file mode 100644 index 00000000000..93ea480da81 --- /dev/null +++ b/projects/igniteui-angular/src/lib/avatar/themes/light/_tokens.scss @@ -0,0 +1,8 @@ +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/themes/schemas/components/light/avatar' as *; + +$base: digest-schema($light-avatar); +$material: digest-schema($material-avatar); +$bootstrap: digest-schema($bootstrap-avatar); +$fluent: digest-schema($fluent-avatar); +$indigo: digest-schema($indigo-avatar); diff --git a/projects/igniteui-angular/src/lib/avatar/themes/shared/_index.scss b/projects/igniteui-angular/src/lib/avatar/themes/shared/_index.scss new file mode 100644 index 00000000000..ca3dd3bc266 --- /dev/null +++ b/projects/igniteui-angular/src/lib/avatar/themes/shared/_index.scss @@ -0,0 +1 @@ +@forward 'indigo'; diff --git a/projects/igniteui-angular/src/lib/avatar/themes/shared/indigo.scss b/projects/igniteui-angular/src/lib/avatar/themes/shared/indigo.scss new file mode 100644 index 00000000000..2be391a2a26 --- /dev/null +++ b/projects/igniteui-angular/src/lib/avatar/themes/shared/indigo.scss @@ -0,0 +1,11 @@ +@use 'igniteui-theming/sass/bem' as *; + +@layer indigo { + @container style(--theme: indigo) { + @include b(igx-avatar) { + igx-icon { + --ig-size: 1; + } + } + } +} diff --git a/projects/igniteui-angular/src/lib/badge/badge.component.ts b/projects/igniteui-angular/src/lib/badge/badge.component.ts index 5275adb98f9..2f2b27674cc 100644 --- a/projects/igniteui-angular/src/lib/badge/badge.component.ts +++ b/projects/igniteui-angular/src/lib/badge/badge.component.ts @@ -1,5 +1,5 @@ import { NgIf } from '@angular/common'; -import { booleanAttribute, Component, HostBinding, Input } from '@angular/core'; +import { booleanAttribute, Component, ElementRef, HostBinding, Input } from '@angular/core'; import { mkenum } from '../core/utils'; import { IgxIconComponent } from '../icon/icon.component'; @@ -140,6 +140,8 @@ export class IgxBadgeComponent { @HostBinding('class.igx-badge') public cssClass = 'igx-badge'; + constructor(public elementRef: ElementRef) { } + /** * Sets a square shape to the badge, if `shape` is set to `square`. * By default the shape of the badge is rounded. diff --git a/projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts b/projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts index 9556f9b18ec..d6dc48ac7d3 100644 --- a/projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts +++ b/projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts @@ -15,7 +15,7 @@ import { booleanAttribute, inject, DestroyRef, - Inject + Inject, } from '@angular/core'; import { ControlValueAccessor, NgControl, Validators } from '@angular/forms'; import { IgxRippleDirective } from '../directives/ripple/ripple.directive'; diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/base.scss b/projects/igniteui-angular/src/lib/checkbox/themes/base.scss new file mode 100644 index 00000000000..63a950f17f6 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/base.scss @@ -0,0 +1,858 @@ +@use 'sass:map'; +@use 'sass:math'; +@use 'igniteui-theming/sass/animations' as *; +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/typography' as *; +@use 'igniteui-theming/sass/color/functions' as *; +@use './light/themes' as *; + +$theme: $material; + +$theme-variant: map.get($theme, '_meta', 'theme-variant'); +$variant: map.get($theme, '_meta', 'variant'); + +@include scale-in-out($start-scale: 0.9); +@include scale-in-center(); + +%cbx-display { + position: relative; + display: inline-flex; + flex-flow: row nowrap; + align-items: center; + outline-style: none; + cursor: pointer; +} + +%cbx-display--disabled { + user-select: none; + pointer-events: none; + cursor: initial; +} + +%cbx-input { + @include hide-default(); +} + +%cbx-composite-wrapper { + align-items: center; + justify-content: center; + display: flex; + position: relative; + width: var(--size); + height: var(--size); + + // @if $variant == 'material' { + // padding: rem(20px); + // } + + //ripple color + --color: #{var-get($theme, 'empty-color')}; +} + +%cbx-composite-wrapper--x { + //ripple color + --color: #{var-get($theme, 'fill-color')}; +} + +%cbx-composite { + position: relative; + display: inline-block; + width: var(--size); + height: var(--size); + min-width: var(--size); + background: var-get($theme, 'empty-fill-color'); + border-width: var(--border-width); + border-style: solid; + border-color: var-get($theme, 'empty-color'); + border-radius: var-get($theme, 'border-radius'); + -webkit-tap-highlight-color: transparent; + transition: + border-color 0.2s $ease-out-quad, + background 0.2s $ease-out-quad; + overflow: hidden; +} + +%cbx-composite--hover { + border-color: var-get($theme, 'empty-color-hover'); +} + +%cbx-composite--x { + border-color: var-get($theme, 'fill-color'); + background: var-get($theme, 'fill-color'); +} + +%cbx-composite--invalid { + border-color: var-get($theme, 'error-color'); + + // @if $variant == 'bootstrap' and $theme-variant == 'dark' { + // %cbx-composite-mark { + // stroke: black; + // } + // } +} + +%cbx-composite-wrapper--invalid { + //ripple color + --color: #{var-get($theme, 'error-color')}; +} + +%cbx-composite--invalid--hover { + border-color: var-get($theme, 'error-color-hover'); +} + +%cbx-composite--x--invalid { + border-color: var-get($theme, 'error-color'); + background: var-get($theme, 'error-color'); +} + +%cbx-composite--x--hover { + border-color: var-get($theme, 'fill-color-hover'); + background: var-get($theme, 'fill-color-hover'); +} + +%cbx-composite--x--invalid--hover { + border-color: var-get($theme, 'error-color-hover'); + background: var-get($theme, 'error-color-hover'); +} + +%cbx-composite--disabled { + border-color: var-get($theme, 'disabled-color'); + + // @if $variant == 'bootstrap' and $theme-variant == 'dark' { + // background: color($color: 'surface', $variant: 500); + // } +} + +%cbx-composite--x--disabled { + // @if $variant == 'material' or $variant == 'fluent' { + // background: var-get($theme, 'disabled-color'); + // } + + // @if $variant == 'indigo' or $variant == 'bootstrap' { + // background: var-get($theme, 'disabled-indeterminate-color'); + // border-color: transparent; + // } + + // @if $variant != 'indigo' { + // %cbx-composite-mark { + // stroke: var-get($theme, 'disabled-tick-color'); + // } + // } @else { + // %cbx-composite-mark { + // stroke: unset; + // fill: var-get($theme, 'disabled-tick-color'); + // } + // } +} + +%cbx-composite-mark { + position: absolute; + inset: 0; + stroke: var-get($theme, 'tick-color'); + stroke-linecap: square; + stroke-width: var(--mark-stroke); + stroke-dasharray: var(--mark-length); + stroke-dashoffset: var(--mark-length); + fill: none; + opacity: 0; + z-index: 1; +} + +%cbx-composite-mark-material { + inset-inline-start: -0.5px; +} + +%cbx-composite-mark-indigo { + stroke: unset; + stroke-linecap: unset; + stroke-width: unset; + stroke-dasharray: unset; + stroke-dashoffset: unset; + fill: var-get($theme, 'tick-color'); + transition: none !important; + + rect { + fill: none; + } +} + +%igx-checkbox--indeterminate { + %cbx-composite-mark { + top: var(--mark-offset); + margin-inline-start: var(--mark-offset); + } + + &:hover { + %cbx-composite { + border-color: var-get($theme, 'fill-color-hover'); + background: var-get($theme, 'fill-color-hover'); + + &::before { + background: var-get($theme, 'fill-color-hover'); + } + + // @if $variant == 'fluent' { + // background: transparent; + // } + } + } +} + +%igx-checkbox--indeterminate-indigo { + %cbx-composite-mark { + fill: none !important; + stroke-dashoffset: unset !important; + transform: none !important; + + rect { + fill: var-get($theme, 'tick-color'); + opacity: 1; + } + } +} + +%igx-checkbox--disabled-indeterminate-indigo { + @extend %igx-checkbox--indeterminate-indigo; + + %cbx-composite-mark { + rect { + fill: var-get($theme, 'disabled-tick-color'); + } + } +} + +%igx-checkbox--indeterminate-fluent { + %cbx-composite-mark { + stroke: transparent; + } + + %cbx-composite { + background: transparent; + + &::before { + content: ''; + position: absolute; + top: calc(var(--size) / 2 - rem(6px)); + inset-inline-start: calc(var(--size) / 2 - rem(6px)); + width: rem(10px); + height: rem(10px); + border-radius: border-radius(rem(2px)); + background: var-get($theme, 'fill-color'); + z-index: 1; + } + } +} + +%igx-checkbox--disabled-indeterminate-fluent { + %cbx-composite-mark { + stroke: transparent; + } + + %cbx-composite--x--disabled { + background: transparent; + + &::before { + background: var-get($theme, 'disabled-color'); + } + } +} + +%igx-checkbox--disabled-indeterminate-material { + %cbx-composite--x--disabled { + border-color: var-get($theme, 'disabled-indeterminate-color'); + background: var-get($theme, 'disabled-indeterminate-color'); + } +} + +%igx-checkbox--indeterminate--invalid { + %cbx-composite--x { + border-color: var-get($theme, 'error-color'); + background: var-get($theme, 'error-color'); + } + + %cbx-composite--x--hover { + border-color: var-get($theme, 'error-color-hover'); + background: var-get($theme, 'error-color-hover'); + } + + // @if $variant == 'fluent' { + // %cbx-composite { + // border-color: var-get($theme, 'error-color'); + + // &::before { + // background: var-get($theme, 'error-color'); + // } + // } + + // %cbx-composite--x { + // background: transparent; + // } + + // &:hover { + // %cbx-composite { + // background: transparent; + // border-color: var-get($theme, 'error-color-hover'); + + // &::before { + // background: var-get($theme, 'error-color-hover'); + // } + // } + // } + // } +} + +%cbx-composite-mark--x { + stroke-dashoffset: 0; + opacity: 1; + transition: + all 0.2s $ease-out-quad, + opacity 0.2s $ease-out-quad; +} + +%cbx-composite-mark--in { + stroke-dashoffset: 41; /* length of path - adjacent line length */ + opacity: 1; + rotate: 45deg; + transform: translateX(calc(var(--mark-stroke) / var(--mark-length) * -1em)); +} + +%cbx-composite-mark--fluent { + // @if $variant == 'fluent' { + // @extend %cbx-composite-mark; + // @extend %cbx-composite-mark--x; + // stroke: var-get($theme, 'tick-color-hover'); + // } +} + +%cbx-composite-mark--x--fluent { + // @if $variant == 'fluent' { + // stroke: var-get($theme, 'tick-color'); + // } +} + +%cbx-composite-mark--invalid--fluent { + // @if $variant == 'fluent' { + // stroke: var-get($theme, 'error-color'); + // } + + // @if $variant == 'fluent' and $theme-variant == 'dark' { + // stroke: color($color: 'error', $variant: 500); + // } +} + +%cbx-composite-mark--in--fluent { + // @if $variant == 'fluent' { + // stroke: transparent; + // } +} + +%cbx-label { + display: inline-block; + color: var-get($theme, 'label-color'); + user-select: none; + word-wrap: break-all; + transition: color 0.2s $ease-out-quad; + + &:empty { + margin: 0; + } +} + +%cbx-label--hover { + color: var-get($theme, 'label-color-hover'); +} + +%cbx-label-pos--before, +%cbx-label-pos--after { + &:empty { + margin: 0; + } +} + +%cbx-label-pos--after { + margin-inline-start: var(--label-margin); +} + +%cbx-label-pos--before { + margin-inline-end: var(--label-margin); + order: -1; +} + +%cbx-label--invalid { + color: var-get($theme, 'error-color'); +} + +%cbx-label--disabled { + color: var-get($theme, 'disabled-color-label'); +} + +%cbx-ripple { + --_ripple-size: #{rem(40px)}; + + display: none; + position: absolute; + top: calc(50% - calc(var(--_ripple-size) / 2)); + width: var(--_ripple-size); + aspect-ratio: 1; + border-radius: var-get($theme, 'border-radius-ripple'); + overflow: hidden; + pointer-events: none; + filter: opacity(1); +} + +%cbx-ripple--hover { + background: var-get($theme, 'empty-color'); + transition: background 0.2s $ease-out-quad; + opacity: 0.06; + + @container style(--ig-theme-variant: dark) { + opacity: 0.12; + } +} + +%cbx-ripple--hover-checked { + background: var-get($theme, 'fill-color'); +} + +%cbx-ripple--hover-invalid { + background: var-get($theme, 'error-color'); +} + +%igx-checkbox--focused-indigo { + %cbx-composite { + border-radius: var-get($theme, 'border-radius'); + box-shadow: 0 0 0 rem(3px) var-get($theme, 'focus-outline-color'); + } +} + +%igx-checkbox--focused-fluent { + position: relative; + $focus-outline-offset: rem(2px); + + &::after { + content: ''; + position: absolute; + inset: -$focus-outline-offset; + box-shadow: 0 0 0 rem(1px) var-get($theme, 'focus-outline-color'); + } +} + +%igx-checkbox--focused-bootstrap { + %cbx-composite { + border-radius: var-get($theme, 'border-radius'); + border-color: color($color: 'primary', $variant: 200); + box-shadow: 0 0 0 rem(4px) var-get($theme, 'focus-outline-color'); + } +} + +%igx-checkbox--focused-hovered { + %cbx-composite { + border-color: color($color: 'primary', $variant: 300); + } +} + +%igx-checkbox--focused-checked-indigo { + %cbx-composite { + border-radius: var-get($theme, 'border-radius'); + box-shadow: 0 0 0 rem(3px) + var-get($theme, 'focus-outline-color-focused'); + } +} + +%igx-checkbox--focused-checked-bootstrap { + %cbx-composite { + border-color: transparent; + } +} + +%igx-checkbox--focused-invalid-indigo { + %cbx-composite { + box-shadow: 0 0 0 rem(3px) var-get($theme, 'focus-outline-color-error'); + } +} + +%igx-checkbox--focused-invalid-bootstrap { + %cbx-composite { + border-color: var-get($theme, 'error-color'); + box-shadow: 0 0 0 rem(4px) var-get($theme, 'focus-outline-color-error'); + } + + &:hover { + %cbx-composite { + border-color: var-get($theme, 'error-color-hover'); + } + } +} + +%cbx-ripple--focused { + background: var-get($theme, 'empty-color'); + transition: background 0.2s $ease-out-quad; + opacity: 0.12; + + @container style(--ig-theme-variant: dark) { + opacity: 0.24; + } +} + +%cbx-ripple--focused-checked { + background: var-get($theme, 'fill-color'); +} + +%cbx-ripple--focused-invalid { + background: var-get($theme, 'error-color'); +} + +%cbx-ripple--pressed { + opacity: 0.12; + + @container style(--ig-theme-variant: dark) { + opacity: 0.24; + } +} + +%cbx-display--plain { + %cbx-composite, + %cbx-composite::after, + %cbx-composite-mark, + %cbx-composite-mark--x { + transition: none; + } +} + +@include b(igx-checkbox) { + @include css-vars-from-theme($base, 'igx-checkbox'); + @extend %cbx-display !optional; + + &:hover { + @include e(ripple) { + @extend %cbx-ripple--hover !optional; + } + + @include e(composite-mark) { + @extend %cbx-composite-mark--fluent !optional; + } + } + + &:active { + @include e(ripple) { + @extend %cbx-ripple--hover !optional; + @extend %cbx-ripple--pressed !optional; + } + } + + @include e(input) { + @extend %cbx-input !optional; + } + + @include e(label) { + @extend %cbx-label !optional; + @extend %cbx-label-pos--after !optional; + } + + @include e(label, $m: before) { + @extend %cbx-label !optional; + @extend %cbx-label-pos--before !optional; + } + + @include e(composite-wrapper) { + @extend %cbx-composite-wrapper !optional; + } + + @include e(composite) { + @extend %cbx-composite !optional; + } + + @include e(composite-mark) { + @extend %cbx-composite-mark !optional; + } + + @include e(ripple) { + @extend %cbx-ripple !optional; + } + + @include m(bootstrap) { + @include e(composite) { + &:hover { + @extend %cbx-composite--hover !optional; + } + } + } + + @include m(indigo) { + @include e(composite) { + &:hover { + @extend %cbx-composite--hover !optional; + } + } + + @include e(composite-mark) { + @extend %cbx-composite-mark-indigo !optional; + } + + @include e(label) { + &:hover { + @extend %cbx-label--hover !optional; + } + } + } + + @include m(invalid) { + @include e(composite) { + @extend %cbx-composite--invalid !optional; + } + + @include e(composite-wrapper) { + @extend %cbx-composite-wrapper--invalid !optional; + } + + @include e(label) { + @extend %cbx-label--invalid !optional; + } + + &:hover { + @include e(ripple) { + @extend %cbx-ripple--hover !optional; + @extend %cbx-ripple--hover-invalid !optional; + } + + @include e(composite) { + @extend %cbx-composite--invalid--hover !optional; + } + + @include e(composite-mark) { + @extend %cbx-composite-mark--invalid--fluent !optional; + } + } + + &:active { + @include e(ripple) { + @extend %cbx-ripple--hover-invalid !optional; + } + } + } + + @include mx(invalid, checked) { + @include e(composite) { + @extend %cbx-composite--x--invalid !optional; + } + + &:hover { + @include e(composite) { + @extend %cbx-composite--x--invalid--hover !optional; + } + + @include e(composite-mark) { + @extend %cbx-composite-mark--x--fluent !optional; + } + } + } + + @include m(focused) { + @extend %igx-checkbox--focused !optional; + + @include e(ripple) { + @extend %cbx-ripple--focused !optional; + } + + &:hover { + @include e(ripple) { + @extend %cbx-ripple--focused !optional; + } + } + } + + @include mx(indigo, focused) { + @extend %igx-checkbox--focused-indigo !optional; + } + + @include mx(fluent, focused) { + @extend %igx-checkbox--focused-fluent !optional; + } + + @include mx(bootstrap, focused) { + @extend %igx-checkbox--focused-bootstrap !optional; + + &:hover { + @extend %igx-checkbox--focused-hovered !optional; + } + } + + @include mx(indigo, focused, checked) { + @extend %igx-checkbox--focused-checked-indigo !optional; + } + + @include mx(bootstrap, focused, checked) { + @extend %igx-checkbox--focused-checked-bootstrap !optional; + } + + @include mx(focused, checked) { + @extend %igx-checkbox--focused-checked !optional; + } + + @include mx(focused, invalid) { + @include e(ripple) { + @extend %cbx-ripple--focused-invalid !optional; + } + } + + @include mx(indigo, focused, invalid) { + @extend %igx-checkbox--focused-invalid-indigo !optional; + } + + @include mx(bootstrap, focused, invalid) { + @extend %igx-checkbox--focused-invalid-bootstrap !optional; + } + + @include m(indeterminate) { + @extend %igx-checkbox--indeterminate !optional; + + @include e(composite) { + @extend %cbx-composite--x !optional; + } + + @include e(composite-mark) { + @extend %cbx-composite-mark--in !optional; + } + + &:hover { + @include e(ripple) { + @extend %cbx-ripple--hover !optional; + @extend %cbx-ripple--hover-checked !optional; + } + + @include e(composite-mark) { + @extend %cbx-composite-mark--in--fluent !optional; + } + } + + &:active { + @include e(ripple) { + @extend %cbx-ripple--hover !optional; + @extend %cbx-ripple--hover-checked !optional; + @extend %cbx-ripple--pressed !optional; + } + } + } + + @include mx(indigo, indeterminate) { + @extend %igx-checkbox--indeterminate-indigo !optional; + } + + @include mx(fluent, indeterminate) { + @extend %igx-checkbox--indeterminate-fluent !optional; + } + + @include mx(invalid, indeterminate) { + @extend %igx-checkbox--indeterminate--invalid !optional; + + &:hover { + @include e(composite) { + @extend %cbx-composite--x--hover !optional; + } + } + } + + @include mx(material, disabled, indeterminate) { + @extend %igx-checkbox--disabled-indeterminate-material !optional; + } + + @include mx(fluent, disabled, indeterminate) { + @extend %igx-checkbox--disabled-indeterminate-fluent !optional; + } + + @include mx(indigo, disabled, indeterminate) { + @extend %igx-checkbox--disabled-indeterminate-indigo !optional; + } + + @include mx(indigo, focused, indeterminate) { + @extend %igx-checkbox--focused-checked-indigo !optional; + } + + @include mx(bootstrap, focused, indeterminate) { + @extend %igx-checkbox--focused-checked-bootstrap !optional; + } + + @include m(checked) { + @include e(composite) { + @extend %cbx-composite--x !optional; + } + + @include e(composite-mark) { + @extend %cbx-composite-mark--x !optional; + } + + @include e(composite-wrapper) { + @extend %cbx-composite-wrapper--x !optional; + } + + &:hover { + @include e(ripple) { + @extend %cbx-ripple--hover !optional; + @extend %cbx-ripple--hover-checked !optional; + } + + @include e(composite) { + @extend %cbx-composite--x--hover !optional; + } + + @include e(composite-mark) { + @extend %cbx-composite-mark--x--fluent !optional; + } + } + + &:active { + @include e(ripple) { + @extend %cbx-ripple--hover !optional; + @extend %cbx-ripple--hover-checked !optional; + @extend %cbx-ripple--pressed !optional; + } + } + } + + @include m(disabled) { + @extend %cbx-display--disabled !optional; + + @include e(label) { + @extend %cbx-label--disabled !optional; + } + + @include e(label, $m: before) { + @extend %cbx-label--disabled !optional; + } + + @include e(composite) { + @extend %cbx-composite--disabled !optional; + } + } + + @include m(plain) { + @extend %cbx-display--plain !optional; + } + + @include mx(focused, checked) { + @include e(ripple) { + @extend %cbx-ripple--focused !optional; + @extend %cbx-ripple--focused-checked !optional; + } + } + + @include mx(focused, indeterminate) { + @include e(ripple) { + @extend %cbx-ripple--focused !optional; + @extend %cbx-ripple--focused-checked !optional; + } + } + + @include mx(indeterminate, disabled) { + @include e(composite) { + @extend %cbx-composite--x--disabled !optional; + } + } + + @include mx(checked, disabled) { + @include e(composite) { + @extend %cbx-composite--x--disabled !optional; + } + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/dark/_themes.scss b/projects/igniteui-angular/src/lib/checkbox/themes/dark/_themes.scss new file mode 100644 index 00000000000..91fcfd5d2ea --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/dark/_themes.scss @@ -0,0 +1,7 @@ +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/themes/schemas/components/dark/checkbox' as *; + +$material: digest-schema($dark-material-checkbox); +$bootstrap: digest-schema($dark-bootstrap-checkbox); +$fluent: digest-schema($dark-fluent-checkbox); +$indigo: digest-schema($dark-indigo-checkbox); diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/dark/bootstrap.scss b/projects/igniteui-angular/src/lib/checkbox/themes/dark/bootstrap.scss new file mode 100644 index 00000000000..238f588d78b --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/dark/bootstrap.scss @@ -0,0 +1,28 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/utils/map' as *; +@use 'igniteui-theming/sass/color/functions' as *; +@use '../light/themes' as light; +@use 'themes' as *; + +$theme: $bootstrap; + +@container style(--theme: bootstrap) { + @include b(igx-checkbox) { + @include css-vars-from-theme(diff(light.$base, $theme), 'igx-checkbox'); + + @include m(invalid) { + @include e(composite-mark) { + // TODO: Check why this property is not part of the schemas + stroke: black; + } + } + + @include m(disabled) { + @include e(composite) { + // TODO: Check why this property is not part of the schemas + background: color($color: 'surface', $variant: 500); + } + } + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/dark/fluent.scss b/projects/igniteui-angular/src/lib/checkbox/themes/dark/fluent.scss new file mode 100644 index 00000000000..d2ba3956a91 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/dark/fluent.scss @@ -0,0 +1,23 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/color/functions' as *; +@use 'igniteui-theming/sass/utils/map' as *; +@use '../light/themes' as light; +@use 'themes' as *; + +$theme: $fluent; + +@container style(--theme: fluent) { + @include b(igx-checkbox) { + @include css-vars-from-theme(diff(light.$base, $theme), 'igx-checkbox'); + + @include m(invalid) { + &:hover { + @include e(composite-mark) { + // TODO: Check why this property is not part of the schemas + stroke: color($color: 'error', $variant: 500); + } + } + } + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/dark/indigo.scss b/projects/igniteui-angular/src/lib/checkbox/themes/dark/indigo.scss new file mode 100644 index 00000000000..77968828789 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/dark/indigo.scss @@ -0,0 +1,13 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/utils/map' as *; +@use '../light/themes' as light; +@use 'themes' as *; + +$theme: $indigo; + +@container style(--ig-theme: indigo) { + @include b(igx-checkbox) { + @include css-vars-from-theme(diff(light.$base, $theme), 'igx-checkbox'); + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/dark/material.scss b/projects/igniteui-angular/src/lib/checkbox/themes/dark/material.scss new file mode 100644 index 00000000000..763ff7f8199 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/dark/material.scss @@ -0,0 +1,13 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/utils/map' as *; +@use '../light/themes' as light; +@use 'themes' as *; + +$theme: $material; + +@container style(--theme: material) { + @include b(igx-checkbox) { + @include css-vars-from-theme(diff(light.$base, $theme), 'igx-checkbox'); + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/light/_themes.scss b/projects/igniteui-angular/src/lib/checkbox/themes/light/_themes.scss new file mode 100644 index 00000000000..782c0d49713 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/light/_themes.scss @@ -0,0 +1,8 @@ +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/themes/schemas/components/light/checkbox' as *; + +$base: digest-schema($light-checkbox); +$material: digest-schema($material-checkbox); +$bootstrap: digest-schema($bootstrap-checkbox); +$fluent: digest-schema($fluent-checkbox); +$indigo: digest-schema($indigo-checkbox); diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/light/bootstrap.scss b/projects/igniteui-angular/src/lib/checkbox/themes/light/bootstrap.scss new file mode 100644 index 00000000000..4b29e588f57 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/light/bootstrap.scss @@ -0,0 +1,12 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/utils/map' as *; +@use 'themes' as *; + +$theme: $bootstrap; + +@container style(--theme: bootstrap) { + @include b(igx-checkbox) { + @include css-vars-from-theme(diff($base, $theme), 'igx-checkbox'); + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/light/fluent.scss b/projects/igniteui-angular/src/lib/checkbox/themes/light/fluent.scss new file mode 100644 index 00000000000..9d09abe7ec9 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/light/fluent.scss @@ -0,0 +1,12 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/utils/map' as *; +@use 'themes' as *; + +$theme: $fluent; + +@container style(--theme: fluent) { + @include b(igx-checkbox) { + @include css-vars-from-theme(diff($base, $theme), 'igx-checkbox'); + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/light/indigo.scss b/projects/igniteui-angular/src/lib/checkbox/themes/light/indigo.scss new file mode 100644 index 00000000000..8ae2893a0b2 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/light/indigo.scss @@ -0,0 +1,12 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/utils/map' as *; +@use 'themes' as *; + +$theme: $indigo; + +@container style(--theme: indigo) { + @include b(igx-checkbox) { + @include css-vars-from-theme(diff($base, $theme), 'igx-checkbox'); + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/light/material.scss b/projects/igniteui-angular/src/lib/checkbox/themes/light/material.scss new file mode 100644 index 00000000000..bd364f81b76 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/light/material.scss @@ -0,0 +1,12 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@use 'igniteui-theming/sass/utils/map' as *; +@use 'themes' as *; + +$theme: $material; + +@container style(--theme: material) { + @include b(igx-checkbox) { + @include css-vars-from-theme(diff($base, $theme), 'igx-checkbox'); + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/shared/bootstrap.scss b/projects/igniteui-angular/src/lib/checkbox/themes/shared/bootstrap.scss new file mode 100644 index 00000000000..04f3076f763 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/shared/bootstrap.scss @@ -0,0 +1,34 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/typography' as *; +@use 'igniteui-theming/sass/themes/functions' as *; +@use '../light/themes' as *; + +$theme: $bootstrap; + +@container style(--theme: bootstrap) { + @include b(igx-checkbox) { + --size: #{rem(16px)}; + --border-width: #{rem(1px)}; + --label-margin: #{rem(2px)}; + --mark-stroke: 3; + --mark-length: 24; + --mark-offset: 1px; + + @include e(label) { + @include type-style(body-1) { + margin-block: 0; + } + } + + @include mx(indeterminate, disabled) { + @include e(composite) { + background: var-get($theme, 'disabled-indeterminate-color'); + border-color: transparent; + } + + @include e(composite-mark) { + stroke: var-get($theme, 'disabled-tick-color'); + } + } + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/shared/fluent.scss b/projects/igniteui-angular/src/lib/checkbox/themes/shared/fluent.scss new file mode 100644 index 00000000000..0e8f8b2eab1 --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/shared/fluent.scss @@ -0,0 +1,110 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/color/functions' as *; +@use 'igniteui-theming/sass/themes/functions' as *; +@use 'igniteui-theming/sass/typography' as *; +@use 'igniteui-theming/sass/animations' as *; +@use '../light/themes' as *; + +$theme: $fluent; + +@container style(--theme: fluent) { + @include b(igx-checkbox) { + --size: #{rem(20px)}; + --border-width: #{rem(1px)}; + --label-margin: #{rem(8px)}; + --mark-stroke: 1; + --mark-length: 24; + --mark-offset: -1px; + + &:hover { + @include e(composite-mark) { + // TODO: Check if this is even needed? + position: absolute; + inset: 0; + stroke-linecap: square; + stroke-width: var(--mark-stroke); + stroke-dasharray: var(--mark-length); + stroke-dashoffset: 0; + fill: none; + z-index: 1; + opacity: 1; + transition: + all 0.2s $ease-out-quad, + opacity 0.2s $ease-out-quad; + + stroke: var-get($theme, 'tick-color-hover'); + } + } + + @include e(label) { + @include type-style(body-2) { + margin-block: 0; + } + } + + @include m(indeterminate) { + &:hover { + @include e(composite) { + background: transparent; + } + + @include e(composite-mark) { + stroke: transparent; + } + } + } + + @include m(invalid) { + &:hover { + @include e(composite-mark) { + stroke: var-get($theme, 'error-color'); + } + } + } + + @include mx(indeterminate, invalid) { + @include e(composite) { + border-color: var-get($theme, 'error-color'); + + &::before { + background: var-get($theme, 'error-color'); + } + } + + &:hover { + @include e(composite) { + background: transparent; + border-color: var-get($theme, 'error-color-hover'); + + &::before { + background: var-get($theme, 'error-color-hover'); + } + } + } + } + + @include mx(indeterminate, invalid, checked) { + @include e(composite) { + background: transparent; + } + } + + @include mx(indeterminate, disabled) { + @include e(composite) { + background: var-get($theme, 'disabled-color'); + } + + @include e(composite-mark) { + stroke: var-get($theme, 'disabled-tick-color'); + } + } + + @include mx(invalid, checked) { + &:hover { + @include e(composite-mark) { + stroke: var-get($theme, 'tick-color'); + } + } + } + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/shared/indigo.scss b/projects/igniteui-angular/src/lib/checkbox/themes/shared/indigo.scss new file mode 100644 index 00000000000..f0c0431c55a --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/shared/indigo.scss @@ -0,0 +1,41 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/typography' as *; +@use 'igniteui-theming/sass/themes/functions' as *; +@use '../light/themes' as *; + +$theme: $indigo; + +@container style(--theme: indigo) { + @include b(igx-checkbox) { + --size: #{rem(16px)}; + --border-width: #{rem(2px)}; + --label-margin: #{rem(8px)}; + --mark-stroke: 3; + --mark-length: 24; + --mark-offset: 1px; + + @include e(label) { + @include type-style(body-2) { + margin-block: 0; + } + } + + @include m(invalid) { + @include e(label) { + color: var-get($theme, 'label-color'); + } + } + + @include mx(indeterminate, disabled) { + @include e(composite) { + background: var-get($theme, 'disabled-indeterminate-color'); + border-color: transparent; + } + + @include e(composite-mark) { + stroke: unset; + fill: var-get($theme, 'disabled-tick-color'); + } + } + } +} diff --git a/projects/igniteui-angular/src/lib/checkbox/themes/shared/material.scss b/projects/igniteui-angular/src/lib/checkbox/themes/shared/material.scss new file mode 100644 index 00000000000..19be967a4ef --- /dev/null +++ b/projects/igniteui-angular/src/lib/checkbox/themes/shared/material.scss @@ -0,0 +1,42 @@ +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/typography' as *; +@use 'igniteui-theming/sass/themes/functions' as *; +@use '../light/themes' as *; + +$theme: $material; + +@container style(--theme: material) { + @include b(igx-checkbox) { + // These CSS variables should be listed as design tokens in the schema files. + --size: #{rem(20px)}; + --border-width: #{rem(2px)}; + --label-margin: #{rem(2px)}; + --mark-stroke: 3; + --mark-length: 24; + --mark-offset: 0; + + @include e(label) { + @include type-style(subtitle-1) { + margin-block: 0; + } + } + + @include e(ripple) { + display: block; + } + + @include e(composite-wrapper) { + padding: rem(20px); + } + + @include mx(indeterminate, disabled) { + @include e(composite) { + background: var-get($theme, 'disabled-color'); + } + + @include e(composite-mark) { + stroke: var-get($theme, 'disabled-tick-color'); + } + } + } +} diff --git a/projects/igniteui-angular/src/lib/core/styles/components/avatar/_avatar-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/avatar/_avatar-theme.scss index 6c462663f50..2aa4367fb4d 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/avatar/_avatar-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/avatar/_avatar-theme.scss @@ -65,10 +65,8 @@ /// @see {mixin} css-vars /// @param {Map} $theme - The theme used to style the component. @mixin avatar($theme) { - @include css-vars($theme); - $variant: map.get($theme, '_meta', 'variant'); - + %igx-avatar-display { @include sizable(); @@ -82,7 +80,7 @@ color: var-get($theme, 'color'); background: var-get($theme, 'background'); vertical-align: middle; - outline-style: none; + // outline-style: none; flex-shrink: 0; width: var-get($theme, 'size'); height: var-get($theme, 'size'); diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/_standalone.scss b/projects/igniteui-angular/src/lib/core/styles/themes/_standalone.scss new file mode 100644 index 00000000000..56ca9f3e96e --- /dev/null +++ b/projects/igniteui-angular/src/lib/core/styles/themes/_standalone.scss @@ -0,0 +1,39 @@ +@use 'sass:map'; +@use 'igniteui-theming/sass/bem' as *; +@use 'igniteui-theming/sass/themes' as *; +@forward 'igniteui-theming/sass/themes'; +@forward 'igniteui-theming/sass/bem'; + +/// Includes a block element (@see block) for a specific component, theme, and variant. +/// @access private +/// @group bem +/// @param {String} $component - The class selector of the component. +/// @param {String} $theme - The target theme - material, bootstrap, fluent, indigo. +/// @param {String} $variant - The target variant - light, dark. +/// @requires {mixin} b +/// @example scss +@mixin themed-block($component, $theme, $variant) { + @layer #{$theme} { + @container style(--ig-theme: #{"" + $theme}) and style(--ig-theme-variant: #{$variant}) { + @include b($component) { + @content; + } + } + } +} + +/// Includes CSS variables for all themes given a map of tokens. +/// @access private +/// @param {String} $component - The class selector of the component. +/// @param {Map} $tokens - The resolved component schemas/tokens for each theme. +/// @param {String} $variant - The target variant - light, dark. +/// @requires {mixin} themed-block +/// @example scss +@mixin themes($component, $tokens, $variant) { + @each $theme in ('material', 'bootstrap', 'fluent', 'indigo') { + @include themed-block($component, $theme, $variant) { + $_t: map.get($tokens, $theme); + @include css-vars-from-theme($_t, $component); + } + } +} diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_base.scss b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_base.scss index 28be2e20fee..a449cbe39f3 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_base.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_base.scss @@ -77,6 +77,8 @@ $theme: map.get($schema, '_meta', 'theme'); $variant: map.get($schema, '_meta', 'variant'); + @layer base, #{$theme}, #{$theme}-overrides; + #{$scope} { --ig-size-small: 1; --ig-size-medium: 2; @@ -134,13 +136,13 @@ @include ripple($ripple-theme-map); } - @if is-used('igx-avatar', $exclude) { - $avatar-theme-map: avatar-theme( - $schema: $schema, - ); - $avatar-theme-map: meta.call($theme-handler, $avatar-theme-map); - @include avatar($avatar-theme-map); - } + // @if is-used('igx-avatar', $exclude) { + // $avatar-theme-map: avatar-theme( + // $schema: $schema, + // ); + // $avatar-theme-map: meta.call($theme-handler, $avatar-theme-map); + // @include avatar($avatar-theme-map); + // } @if is-used('igx-action-strip', $exclude) { $action-strip-theme-map: action-strip-theme( @@ -318,13 +320,13 @@ @include css-vars($shape-chart-theme-map); } - @if is-used('igx-checkbox', $exclude) { - $checkbox-theme-map: checkbox-theme( - $schema: $schema, - ); - $checkbox-theme-map: meta.call($theme-handler, $checkbox-theme-map); - @include checkbox($checkbox-theme-map); - } + // @if is-used('igx-checkbox', $exclude) { + // $checkbox-theme-map: checkbox-theme( + // $schema: $schema, + // ); + // $checkbox-theme-map: meta.call($theme-handler, $checkbox-theme-map); + // @include checkbox($checkbox-theme-map); + // } @if is-used('igx-chip', $exclude) { $chip-theme-map: chip-theme( diff --git a/projects/igniteui-angular/src/lib/icon/icon.component.ts b/projects/igniteui-angular/src/lib/icon/icon.component.ts index 8186a6d85eb..dbd531ce68a 100644 --- a/projects/igniteui-angular/src/lib/icon/icon.component.ts +++ b/projects/igniteui-angular/src/lib/icon/icon.component.ts @@ -14,7 +14,6 @@ import type { IconReference } from "./types"; import { filter, takeUntil } from "rxjs/operators"; import { Subject } from "rxjs"; import { SafeHtml } from "@angular/platform-browser"; -import { NgIf, NgTemplateOutlet } from "@angular/common"; /** * Icon provides a way to include material icons to markup diff --git a/projects/igniteui-angular/src/lib/services/theme/theme.token.ts b/projects/igniteui-angular/src/lib/services/theme/theme.token.ts index 1ea02f7aa99..25abc83f2a8 100644 --- a/projects/igniteui-angular/src/lib/services/theme/theme.token.ts +++ b/projects/igniteui-angular/src/lib/services/theme/theme.token.ts @@ -6,6 +6,7 @@ import { DOCUMENT } from "@angular/common"; export class ThemeToken { private document = inject(DOCUMENT); public subject: BehaviorSubject; + public variant: IgxThemeVariant; constructor(private t?: IgxTheme) { const globalTheme = globalThis.window @@ -13,6 +14,11 @@ export class ThemeToken { .getPropertyValue("--ig-theme") .trim() || 'material' as IgxTheme; + this.variant = globalThis.window + ?.getComputedStyle(this.document.body) + .getPropertyValue("--ig-theme-variant") + .trim() as IgxThemeVariant; + const _theme = t ?? globalTheme as IgxTheme; this.subject = new BehaviorSubject(_theme); } @@ -46,7 +52,14 @@ const Theme = /*@__PURE__*/ mkenum({ IndigoDesign: "indigo", }); +const ThemeVariant = /*@__PURE__*/ mkenum({ + Light: "light", + Dark: "dark" +}); + /** * Determines the component theme. */ export type IgxTheme = (typeof Theme)[keyof typeof Theme]; + +export type IgxThemeVariant = (typeof ThemeVariant)[keyof typeof ThemeVariant]; diff --git a/scripts/build-component-styles.mjs b/scripts/build-component-styles.mjs new file mode 100644 index 00000000000..0330498a969 --- /dev/null +++ b/scripts/build-component-styles.mjs @@ -0,0 +1,3 @@ +import { buildComponents } from "./sass.mjs"; + +await Promise.all([buildComponents()]); diff --git a/scripts/build-styles.mjs b/scripts/build-styles.mjs index 60aa5dc6749..743cc15bf59 100644 --- a/scripts/build-styles.mjs +++ b/scripts/build-styles.mjs @@ -1,111 +1,3 @@ -import * as sass from "sass-embedded"; -import postcss from "postcss"; -import autoprefixer from "autoprefixer"; -import { globby } from "globby"; -import path from "path"; -import { mkdirSync as makeDir } from "fs"; -import fsExtra from "fs-extra"; -import { fileURLToPath } from "url"; -import { writeFile } from "fs/promises"; +import { buildComponents, buildThemes } from "./sass.mjs"; -const report = { - success: (s) => console.log("\x1b[32m%s\x1b[0m", s), - warn: (s) => console.warn("\x1b[33m%s\x1b[0m", s), - error: (s) => console.error("\x1b[31m%s\x1b[0m", s), -}; - -const STYLES = { - SRC: "projects/igniteui-angular/src/lib/core/styles/themes/presets", - DIST: "../dist/igniteui-angular/styles", - THEMING: { - SRC: "projects/igniteui-angular/src/lib/core/styles/", - DIST: "dist/igniteui-angular/lib/core/styles/", - }, - CONFIG: { - style: "compressed", - loadPaths: ["node_modules"], - sourceMap: true, - sourceMapEmbed: true, - }, -}; - -const { copySync } = fsExtra; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const DEST_DIR = path.join.bind(null, path.resolve(__dirname, STYLES.DIST)); - -const stripComments = () => { - return { - postcssPlugin: "postcss-strip-comments", - OnceExit(root) { - root.walkComments((node) => node.remove()); - }, - }; -}; - -stripComments.postcss = true; - -const postProcessor = postcss([ - autoprefixer({ - cascade: false, - grid: true, - }), - stripComments, -]); - -async function createFile(fileName, content) { - const outputFile = DEST_DIR(fileName); - - makeDir(path.dirname(outputFile), { recursive: true }); - await writeFile(outputFile, content, "utf-8"); -} - -async function buildThemes() { - const paths = await globby(`${STYLES.SRC}/**/*.scss`); - const compiler = await sass.initAsyncCompiler(); - - try { - await Promise.all( - paths.map(async (path) => { - const result = await compiler.compileAsync(path, STYLES.CONFIG); - const fileName = path - .replace(/\.scss$/, ".css") - .replace(STYLES.SRC, ""); - const sourceMapComment = `/*# sourceMappingURL=maps${fileName}.map */`; - - let outCss = postProcessor.process(result.css).css; - - if (outCss.charCodeAt(0) === 0xfeff) { - outCss = outCss.substring(1); - } - - outCss = outCss + "\n".repeat(2) + sourceMapComment; - - await createFile(fileName, outCss); - await createFile( - `maps/${fileName}.map`, - JSON.stringify(result.sourceMap), - ); - }), - ); - } catch (err) { - await compiler.dispose(); - report.error(err); - process.exit(1); - } - - await compiler.dispose(); -} - -(async () => { - const startTime = new Date(); - - // Move theming files - copySync(STYLES.THEMING.SRC, STYLES.THEMING.DIST, { recursive: true }); - - // Build theme presets - console.info("Building themes..."); - await buildThemes(); - report.success( - `Themes generated in ${Math.round((Date.now() - startTime) / 1000)}s`, - ); -})(); +await Promise.all([buildComponents(), buildThemes()]); diff --git a/scripts/projects/igniteui-angular/src/lib/avatar/avatar.component.css b/scripts/projects/igniteui-angular/src/lib/avatar/avatar.component.css new file mode 100644 index 00000000000..b632e49d74c --- /dev/null +++ b/scripts/projects/igniteui-angular/src/lib/avatar/avatar.component.css @@ -0,0 +1,3 @@ +@layer base{.igx-avatar{--is-large: clamp(0, (var(--component-size, 1) + 1) - var(--ig-size-large, 3), 1);--is-medium: min( clamp(0, (var(--component-size, 1) + 1) - var(--ig-size-medium, 2), 1), clamp(0, var(--ig-size-large, 3) - var(--component-size, 1), 1) );--is-small: clamp(0, var(--ig-size-medium) - var(--component-size, 1), 1);--component-size: var(--ig-size, var(--default-size));position:relative;display:inline-flex;justify-content:center;align-items:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;color:var(--color);background:var(--background);vertical-align:middle;outline-style:none;flex-shrink:0;width:var(--size);height:var(--size)}.igx-avatar igx-icon{--component-size: 3;color:var(--icon-color)}.igx-avatar::after{box-shadow:none;transition:box-shadow .15s cubic-bezier(0.455, 0.03, 0.515, 0.955)}.igx-avatar__image{width:100%;height:100%;border-radius:inherit;background-size:cover;background-repeat:no-repeat;background-position:center}.igx-avatar--circle{border-radius:calc(var(--size)/2)}.igx-avatar--rounded{border-radius:var(--border-radius)}.igx-avatar--initials{text-transform:uppercase;font-size:calc(var(--size)/2);line-height:calc(var(--size)/2)}}@layer indigo{@container style(--theme: indigo){.igx-avatar igx-icon{--ig-size: 1}}}@container style(--theme: material) and style(--ig-theme-variant: light){.igx-avatar{--background: var(--igx-avatar-background, hsl(from var(--ig-gray-400) h s l/1));--color: var(--igx-avatar-color, hsl(from var(--ig-gray-800) h s l/1));--icon-color: var(--igx-avatar-icon-color, hsl(from var(--ig-gray-800) h s l/1));--border-radius: var(--igx-avatar-border-radius, 0.5rem);--size: var(--igx-avatar-size, max(var(--is-large, 1) * max(5.5rem, -1 * 5.5rem), var(--is-medium, 1) * max(4rem, -1 * 4rem), var(--is-small, 1) * max(2.5rem, -1 * 2.5rem)));--default-size: var(--igx-avatar-default-size, 1)}}@container style(--theme: bootstrap) and style(--ig-theme-variant: light){.igx-avatar{--background: var(--igx-avatar-background, hsl(from var(--ig-gray-400) h s l/1));--color: var(--igx-avatar-color, hsl(from var(--ig-gray-800) h s l/1));--icon-color: var(--igx-avatar-icon-color, hsl(from var(--ig-gray-800) h s l/1));--border-radius: var(--igx-avatar-border-radius, 0.25rem);--size: var(--igx-avatar-size, max(var(--is-large, 1) * max(5.5rem, -1 * 5.5rem), var(--is-medium, 1) * max(4rem, -1 * 4rem), var(--is-small, 1) * max(2.5rem, -1 * 2.5rem)));--default-size: var(--igx-avatar-default-size, 1)}}@container style(--theme: fluent) and style(--ig-theme-variant: light){.igx-avatar{--background: var(--igx-avatar-background, hsl(from var(--ig-gray-400) h s l/1));--color: var(--igx-avatar-color, hsl(from var(--ig-gray-800) h s l/1));--icon-color: var(--igx-avatar-icon-color, hsl(from var(--ig-gray-800) h s l/1));--border-radius: var(--igx-avatar-border-radius, 0.5rem);--size: var(--igx-avatar-size, max(var(--is-large, 1) * max(5.5rem, -1 * 5.5rem), var(--is-medium, 1) * max(4rem, -1 * 4rem), var(--is-small, 1) * max(2.5rem, -1 * 2.5rem)));--default-size: var(--igx-avatar-default-size, 1)}}@container style(--theme: indigo) and style(--ig-theme-variant: light){.igx-avatar{--background: var(--igx-avatar-background, hsl(from var(--ig-gray-300) h s l/1));--color: var(--igx-avatar-color, hsl(from var(--ig-gray-700) h s l/1));--icon-color: var(--igx-avatar-icon-color, hsl(from var(--ig-gray-600) h s l/1));--border-radius: var(--igx-avatar-border-radius, 0.25rem);--size: var(--igx-avatar-size, max(var(--is-large, 1) * max(2.25rem, -1 * 2.25rem), var(--is-medium, 1) * max(1.75rem, -1 * 1.75rem), var(--is-small, 1) * max(1.25rem, -1 * 1.25rem)));--default-size: var(--igx-avatar-default-size, 1)}}@container style(--theme: material) and style(--ig-theme-variant: dark){.igx-avatar{--background: var(--igx-avatar-background, hsl(from var(--ig-gray-400) h s l/1));--color: var(--igx-avatar-color, hsl(from var(--ig-gray-800) h s l/1));--icon-color: var(--igx-avatar-icon-color, hsl(from var(--ig-gray-800) h s l/1));--border-radius: var(--igx-avatar-border-radius, 0.5rem);--size: var(--igx-avatar-size, max(var(--is-large, 1) * max(5.5rem, -1 * 5.5rem), var(--is-medium, 1) * max(4rem, -1 * 4rem), var(--is-small, 1) * max(2.5rem, -1 * 2.5rem)));--default-size: var(--igx-avatar-default-size, 1)}}@container style(--theme: bootstrap) and style(--ig-theme-variant: dark){.igx-avatar{--background: var(--igx-avatar-background, hsl(from var(--ig-gray-400) h s l/1));--color: var(--igx-avatar-color, hsl(from var(--ig-gray-800) h s l/1));--icon-color: var(--igx-avatar-icon-color, hsl(from var(--ig-gray-800) h s l/1));--border-radius: var(--igx-avatar-border-radius, 0.25rem);--size: var(--igx-avatar-size, max(var(--is-large, 1) * max(5.5rem, -1 * 5.5rem), var(--is-medium, 1) * max(4rem, -1 * 4rem), var(--is-small, 1) * max(2.5rem, -1 * 2.5rem)));--default-size: var(--igx-avatar-default-size, 1)}}@container style(--theme: fluent) and style(--ig-theme-variant: dark){.igx-avatar{--background: var(--igx-avatar-background, hsl(from var(--ig-gray-400) h s l/1));--color: var(--igx-avatar-color, hsl(from var(--ig-gray-800) h s l/1));--icon-color: var(--igx-avatar-icon-color, hsl(from var(--ig-gray-800) h s l/1));--border-radius: var(--igx-avatar-border-radius, 0.5rem);--size: var(--igx-avatar-size, max(var(--is-large, 1) * max(5.5rem, -1 * 5.5rem), var(--is-medium, 1) * max(4rem, -1 * 4rem), var(--is-small, 1) * max(2.5rem, -1 * 2.5rem)));--default-size: var(--igx-avatar-default-size, 1)}}@container style(--theme: indigo) and style(--ig-theme-variant: dark){.igx-avatar{--background: var(--igx-avatar-background, hsl(from var(--ig-gray-300) h s l/1));--color: var(--igx-avatar-color, color-mix(in oklch, var(--ig-gray-300-contrast) 100%, transparent));--icon-color: var(--igx-avatar-icon-color, color-mix(in oklch, var(--ig-gray-300-contrast) 100%, transparent));--border-radius: var(--igx-avatar-border-radius, 0.25rem);--size: var(--igx-avatar-size, max(var(--is-large, 1) * max(2.25rem, -1 * 2.25rem), var(--is-medium, 1) * max(1.75rem, -1 * 1.75rem), var(--is-small, 1) * max(1.25rem, -1 * 1.25rem)));--default-size: var(--igx-avatar-default-size, 1)}}@container style(--theme: material) and style(--ig-theme-variant: dark){.igx-avatar__image{display:none}} + +/*# sourceMappingURL=mapsprojects/igniteui-angular/src/lib/avatar/avatar.component.css.map */ \ No newline at end of file diff --git a/scripts/report.mjs b/scripts/report.mjs new file mode 100644 index 00000000000..b00fd132261 --- /dev/null +++ b/scripts/report.mjs @@ -0,0 +1,19 @@ +import { stdout } from 'node:process'; +import { format } from 'node:util'; + +export default { + error: (s) => console.error('\x1b[31m%s\x1b[0m', s), + success: (s) => console.info('\x1b[32m%s\x1b[0m', s), + warn: (s) => console.warn('\x1b[33m%s\x1b[0m', s), + info: (s) => console.info('\x1b[36m%s\x1b[0m', s), + + stdout: { + clearLine: () => { + stdout.clearLine(0); + stdout.cursorTo(0); + }, + success: (s) => stdout.write(format('\x1b[32m%s\x1b[0m', s)), + warn: (s) => stdout.write(format('\x1b[33m%s\x1b[0m', s)), + info: (s) => stdout.write(format('\x1b[36m%s\x1b[0m', s)), + }, +}; diff --git a/scripts/sass.mjs b/scripts/sass.mjs new file mode 100644 index 00000000000..babd6c2eb9d --- /dev/null +++ b/scripts/sass.mjs @@ -0,0 +1,176 @@ +import * as sass from 'sass-embedded'; +import postcss from 'postcss'; +import autoprefixer from 'autoprefixer'; +import { globby } from 'globby'; +import path from 'path'; +import { resolve } from 'node:path'; +import { mkdirSync as makeDir } from 'fs'; +import fsExtra from 'fs-extra'; +import { fileURLToPath } from 'url'; +import { writeFile } from 'fs/promises'; +import report from './report.mjs'; + +const THEMES = { + SRC: 'projects/igniteui-angular/src/lib/core/styles/themes/presets', + DIST: '../dist/igniteui-angular/styles', + THEMING: { + SRC: 'projects/igniteui-angular/src/lib/core/styles/', + DIST: 'dist/igniteui-angular/lib/core/styles/', + }, + CONFIG: { + style: 'compressed', + loadPaths: ['node_modules'], + sourceMap: true, + sourceMapEmbed: true, + }, +}; + +const STYLES = { + SRC: 'projects/igniteui-angular/src/lib/**/*.component.scss', + DIST: './', + IGNORE: '!projects/igniteui-angular/src/lib/core/styles/**/*.scss', + CONFIG: { + style: 'compressed', + loadPaths: ['node_modules', 'projects/igniteui-angular/src/lib/core/'], + sourceMap: true, + sourceMapEmbed: true, + }, +}; + +const { copySync } = fsExtra; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const DEST_DIR = path.join.bind(null, resolve(__dirname, THEMES.DIST)); + +const stripComments = () => { + return { + postcssPlugin: 'postcss-strip-comments', + OnceExit(root) { + root.walkComments((node) => node.remove()); + }, + }; +}; + +stripComments.postcss = true; + +const postProcessor = postcss([ + autoprefixer({ + cascade: false, + grid: true, + }), + stripComments, +]); + +const _postProcessor = postcss([autoprefixer, stripComments]); + +async function createFile(outputFile, content) { + makeDir(path.dirname(outputFile), { recursive: true }); + await writeFile(outputFile, content, 'utf-8'); +} + +export async function compileSass(src, compiler, options) { + const compiled = await compiler.compileAsync(src, options); + + const out = _postProcessor.process(compiled.css).css; + return out.charCodeAt(0) === 0xfeff ? out.slice(1) : out; +} + +async function _buildThemes() { + const paths = await globby(`${THEMES.SRC}/**/*.scss`); + const compiler = await sass.initAsyncCompiler(); + + try { + await Promise.all( + paths.map(async (path) => { + const result = await compiler.compileAsync(path, THEMES.CONFIG); + const fileName = path + .replace(/\.scss$/, '.css') + .replace(THEMES.SRC, ''); + const sourceMapComment = `/*# sourceMappingURL=maps${fileName}.map */`; + + let outCss = postProcessor.process(result.css).css; + + if (outCss.charCodeAt(0) === 0xfeff) { + outCss = outCss.substring(1); + } + + outCss = outCss + '\n'.repeat(2) + sourceMapComment; + + const outputFile = DEST_DIR(fileName); + await createFile(outputFile, outCss); + }) + ); + } catch (err) { + await compiler.dispose(); + report.error(err); + process.exit(1); + } + + await compiler.dispose(); +} + +export async function buildComponentStyles() { + const [compiler, paths] = await Promise.all([ + sass.initAsyncCompiler(), + globby([STYLES.SRC, STYLES.IGNORE]), + ]); + + try { + await Promise.all( + paths.map(async (path) => { + const result = await compiler.compileAsync(path, STYLES.CONFIG); + const fileName = path + .replace(/\.scss$/, '.css') + .replace(STYLES.SRC, ''); + + const sm = JSON.stringify(result.sourceMap); + const smBase64 = (Buffer.from(sm, 'utf8') || '').toString('base64'); + const sourceMapComment = + '/*# sourceMappingURL=data:application/json;charset=utf-8;base64,' + + smBase64 + + ' */'; + + let outCss = postProcessor.process(result.css).css; + + if (outCss.charCodeAt(0) === 0xfeff) { + outCss = outCss.substring(1); + } + + outCss = outCss + '\n'.repeat(2) + sourceMapComment; + + await createFile(fileName, outCss); + }) + ); + } catch (err) { + await compiler.dispose(); + report.error(err); + process.exit(1); + } + + await compiler.dispose(); +} + +export async function buildComponents(isProduction = false) { + const start = performance.now(); + + await buildComponentStyles(); + + if (!isProduction) { + report.success( + `Component styles generated in ${((performance.now() - start) / 1000).toFixed(2)}s` + ); + } +} + +export async function buildThemes() { + const start = performance.now(); + + // Move theming files + copySync(THEMES.THEMING.SRC, THEMES.THEMING.DIST, { recursive: true }); + + // Build theme presets + console.info('Building themes...'); + await _buildThemes(); + report.success( + `Themes generated in ${((performance.now() - start) / 1000).toFixed(2)}s` + ); +} diff --git a/scripts/watch-styles.mjs b/scripts/watch-styles.mjs new file mode 100644 index 00000000000..ea197c98513 --- /dev/null +++ b/scripts/watch-styles.mjs @@ -0,0 +1,42 @@ +import { writeFile } from 'node:fs/promises'; +import watch from 'node-watch'; +import * as sass from 'sass-embedded'; +import report from './report.mjs'; +import { buildComponentStyles } from './sass.mjs'; +import { globby } from 'globby'; + +const watchOptions = { + recursive: true, + filter: (path) => { + return /.(?:scss)$/.test(path); + }, +}; + +let updating = false; +const compiler = await sass.initAsyncCompiler(); + +const watcher = watch( + ['projects/igniteui-angular'], + watchOptions, + async (_, path) => { + if (updating) { + return; + } + + report.warn(`Change detected: ${path}`); + updating = true; + + try { + await buildComponentStyles(); + } catch (err) { + report.error(err); + } + + report.success('Styles rebuilt 🎨'); + updating = false; + } +); + +watcher.on('close', () => compiler.dispose()); + +report.info('Styles watcher started...'); diff --git a/src/app/styleguide/colors/color.sample.scss b/src/app/styleguide/colors/color.sample.scss index 8e8f69b6f13..5f5f0d8f933 100644 --- a/src/app/styleguide/colors/color.sample.scss +++ b/src/app/styleguide/colors/color.sample.scss @@ -1,6 +1,14 @@ @use '../../../../projects/igniteui-angular/src/lib/core/styles/themes/utilities' as *; @use '../../../styles/variables' as *; +.test-100 { + color: color($palette, primary, 100); +} + +.test-200 { + color: color($palette, primary, 200); +} + .sample-wrapper { display: grid; grid-template-columns: 1fr; diff --git a/src/assets/styles/test-theme.css b/src/assets/styles/test-theme.css new file mode 100644 index 00000000000..206348450fc --- /dev/null +++ b/src/assets/styles/test-theme.css @@ -0,0 +1,7 @@ +@layer theme { + .igx-avatar { + --color: pink; + + outline: 1px solid red; + } +} diff --git a/src/styles/_demo-theme.scss b/src/styles/_demo-theme.scss index 5141a21465f..589769d769e 100644 --- a/src/styles/_demo-theme.scss +++ b/src/styles/_demo-theme.scss @@ -11,10 +11,6 @@ body { color: $foreground-color; } -:root { - --some-dynamic-color: hsl(#{math.random(360)}, 100%, 50%); -} - @include core(); @include typography($font-family: $typeface, $type-scale: $type-scale); @include theme( @@ -22,6 +18,16 @@ body { $schema: $schema, ); +.indigo { + --ig-theme-variant: dark; + + @include palette($dark-indigo-palette); +} + +.indigo-avatar { + @include css-vars(avatar-theme($schema: $dark-indigo-schema)); +} + .dock { @include css-vars(dock-manager-theme()); }