From d98a016087378cf44a51d4aa0dcd992369091b1e Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 12 Apr 2022 19:21:55 -0700 Subject: [PATCH] Initial build of primary color contrast --- src-ui/src/app/services/settings.service.ts | 14 ++ src-ui/src/app/utils/color.ts | 19 +- src-ui/src/styles.scss | 20 +- src-ui/src/theme.scss | 192 ++++++++++++++++++++ src-ui/src/theme_dark.scss | 171 ----------------- 5 files changed, 237 insertions(+), 179 deletions(-) delete mode 100644 src-ui/src/theme_dark.scss diff --git a/src-ui/src/app/services/settings.service.ts b/src-ui/src/app/services/settings.service.ts index b3447ee26..e9017d147 100644 --- a/src-ui/src/app/services/settings.service.ts +++ b/src-ui/src/app/services/settings.service.ts @@ -132,8 +132,22 @@ export class SettingsService { : this.renderer.removeClass(this.document.body, 'color-scheme-dark') } + // remove these in case they were there + this.renderer.removeClass(this.document.body, 'text-bg-dark') + this.renderer.removeClass(this.document.body, 'text-bg-light') + if (themeColor) { const hsl = hexToHsl(themeColor) + const useDarkTextColor = + parseInt(themeColor.replace('#', ''), 16) > 0xffffff / 1.5 + + if (useDarkTextColor) { + this.renderer.addClass(this.document.body, 'text-bg-dark') + this.renderer.removeClass(this.document.body, 'text-bg-light') + } else { + this.renderer.addClass(this.document.body, 'text-bg-light') + this.renderer.removeClass(this.document.body, 'text-bg-dark') + } this.renderer.setStyle( document.documentElement, '--pngx-primary', diff --git a/src-ui/src/app/utils/color.ts b/src-ui/src/app/utils/color.ts index 79d237f27..5c083c2b9 100644 --- a/src-ui/src/app/utils/color.ts +++ b/src-ui/src/app/utils/color.ts @@ -1,4 +1,4 @@ -import { HSL } from 'ngx-color' +import { HSL, RGB } from 'ngx-color' function componentToHex(c) { var hex = Math.floor(c).toString(16) @@ -86,14 +86,19 @@ export function rgbToHsl(r, g, b) { } export function hexToHsl(hex: string): HSL { + const rgb = hexToRGB(hex) + const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b) + return { h: hsl[0], s: hsl[1], l: hsl[2] } +} + +export function hexToRGB(hex: string): RGB { hex = hex.replace('#', '') let aRgbHex = hex.match(/.{1,2}/g) - const hsl = rgbToHsl( - parseInt(aRgbHex[0], 16), - parseInt(aRgbHex[1], 16), - parseInt(aRgbHex[2], 16) - ) - return { h: hsl[0], s: hsl[1], l: hsl[2] } + return { + r: parseInt(aRgbHex[0], 16), + g: parseInt(aRgbHex[1], 16), + b: parseInt(aRgbHex[2], 16), + } } export function randomColor() { diff --git a/src-ui/src/styles.scss b/src-ui/src/styles.scss index 6c6d5f965..6922e3d7c 100644 --- a/src-ui/src/styles.scss +++ b/src-ui/src/styles.scss @@ -4,7 +4,6 @@ $enable-negative-margins: true; @import "node_modules/bootstrap/scss/bootstrap"; @import "~@ng-select/ng-select/themes/default.theme.css"; @import "theme"; -@import "theme_dark"; @import "print"; // Paperless-ngx styles @@ -36,9 +35,19 @@ svg.logo { .bg-primary { background-color: var(--bs-primary) !important; + color: var(--pngx-primary-text-contrast); +} + +.navbar-brand { + color: var(--pngx-primary-text-contrast) !important; +} + +.navbar .dropdown .btn { + color: var(--pngx-primary-text-contrast) !important; } .btn-primary { + color: var(--pngx-primary-text-contrast); background-color: var(--bs-primary); border-color: var(--bs-primary); @@ -48,6 +57,7 @@ svg.logo { } &:disabled, &.disabled { + color: var(--pngx-primary-text-contrast); background-color: var(--pngx-primary-darken-10) !important; border-color: var(--pngx-primary-darken-10) !important; } @@ -350,6 +360,14 @@ table.table { } } +.toast { + color: var(--pngx-primary-text-contrast); + + .toast-header { + color: var(--pngx-primary-text-contrast); + } +} + .close { color: var(--bs-body-color); } diff --git a/src-ui/src/theme.scss b/src-ui/src/theme.scss index f52fba1f5..ea7995be8 100644 --- a/src-ui/src/theme.scss +++ b/src-ui/src/theme.scss @@ -15,3 +15,195 @@ --pngx-bg-darker: var(--bs-gray-100); --pngx-focus-alpha: 0.3; } + +// Dark text colors allow for maintain contrast with theme color changes +$text-color-light-bg: #212529; +$text-color-dark-bg: #abb2bf; +$text-color-dark-bg-accent: lighten($text-color-dark-bg, 10%); +// Taken from bootstrap +$form-check-input-checked-bg-image-dark: url("data:image/svg+xml,"); +$form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,"); + +.text-bg-light { + --pngx-primary-text-contrast: #{$text-color-light-bg} !important; + + .form-check-input:checked[type=checkbox] { + background-image: escape-svg($form-check-input-checked-bg-image-dark); + } + + .form-check-input:checked[type=radio] { + background-image: escape-svg($form-check-radio-checked-bg-image-dark); + } +} + +.text-bg-dark { + --pngx-primary-text-contrast: #{$text-color-dark-bg} !important; +} + +// Dark mode +$primary-dark-mode: #45973a; +$primary-dark-mode-rgb: 69, 151, 58; +$primary-dark-mode-darken-10: darken($primary-dark-mode, 10%); +$danger-dark-mode: #b71631; +$danger-dark-mode-rgb: 183, 22, 49; +$bg-dark-mode: #161618; +$bg-dark-mode-rgb: 22, 22, 24; +$bg-dark-mode-accent: #101216; +$bg-dark-mode-alt: #242529; +$bg-light-dark-mode: #1c1c1f; +$bg-light-dark-mode-rgb: 28, 28, 31; +$border-color-dark-mode: #47494f; + +@mixin dark-mode { + --bs-primary: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 10%)); + --bs-body-color: #{$text-color-dark-bg}; + --pngx-body-color-accent: #{$text-color-dark-bg-accent}; + --bs-danger: #{$danger-dark-mode}; + --bs-danger-rgb: #{$danger-dark-mode-rgb}; + --bs-body-bg: #{$bg-dark-mode}; + --bs-body-bg-rgb: #{$bg-dark-mode-rgb}; + --bs-light: #{$bg-light-dark-mode}; + --bs-light-rgb: #{$bg-light-dark-mode-rgb}; + --bs-border-color: #{$border-color-dark-mode}; + --pngx-bg-darker: #{$bg-dark-mode-accent}; + --pngx-bg-alt: #{$bg-dark-mode-alt}; + --pngx-focus-alpha: 0.7; + --pngx-primary-faded: var(--pngx-primary-darken-15); + --pngx-primary-text-contrast: var(--bs-body-color); + + .navbar.bg-primary { + --bs-primary: hsl(var(--pngx-primary),var(--pngx-primary-lightness)); + --bs-primary-rgb: var(--bs-primary); + } + + .border { + border-color: var(--bs-border-color) !important; + } + + .border-end { + border-right: 1px solid var(--bs-border-color) !important; + } + + .border-start { + border-left: 1px solid var(--bs-border-color) !important; + } + + .border-bottom { + border-bottom: 1px solid var(--bs-border-color) !important; + } + + .text-dark, .text-light { + color: var(--bs-body-color) !important; + } + + .btn-outline-primary, .btn-primary { + &:hover, &:focus, &.active, &:active { + color: var(--bs-light) !important; + } + } + + .btn-outline-secondary { + &:hover, &:focus, &.active, &:active { + background-color: var(--pngx-bg-darker); + color: var(--bs-primary); + } + } + + .search-form-container { + input, input:focus { + color: var(--bs-body-color) !important; + } + } + + .card { + background-color: var(--bs-body-bg); + + .card-header { + background-color: rgba(0, 0, 0, 0.12); + } + } + + .modal-content, .modal-header, .modal-body, .modal-footer { + background-color: var(--bs-body-bg); + border-color: var(--bs-border-color); + } + + app-tag .badge { + filter: brightness(.8); + } + + .doc-img-container { + border: none !important; + border-top-left-radius: .25rem; + border-top-right-radius: .25rem; + overflow: hidden; + } + + .doc-img { + mix-blend-mode: normal; + border-radius: 0; + border-color: var(--bs-border-color); + filter: invert(10%); + + &.border-end { + border-right: none !important; + } + } + + .doc-img.inverted { + filter: invert(95%) hue-rotate(180deg); + } + + .card-selected .doc-img { + mix-blend-mode: luminosity; + } + + .ng-dropdown-panel .ng-dropdown-panel-items .ng-option:hover, + .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked { + background-color: $bg-light-dark-mode; + } + + table { + .des, + .asc { + &::after { + filter: invert(0.8); /* arrow is a black inline png bkgd image (!) so use filter */ + } + } + + &.table-hover > tbody > tr:hover > * { + background-color: $bg-light-dark-mode; + color: var(--pngx-body-color-accent); + } + + } + + .table-striped > tbody > tr:nth-of-type(odd) > * { + color: var(--pngx-body-color-accent); + } + + .close, .modal .btn-close, .alert .btn-close { + text-shadow: 0 1px 0 #666; + } + + .modal .btn-close, .alert .btn-close { + filter: invert(1) grayscale(100%) brightness(200%); + } + + .toast { + background-color: hsla(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 18%), 0.9); + } + + .toast-header { + background-color: hsla(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 10%), 0.9); + } +} + +body.color-scheme-dark { + @include dark-mode; +} +body.color-scheme-system { + @media (prefers-color-scheme: dark) { + @include dark-mode; + } +} diff --git a/src-ui/src/theme_dark.scss b/src-ui/src/theme_dark.scss deleted file mode 100644 index 240312845..000000000 --- a/src-ui/src/theme_dark.scss +++ /dev/null @@ -1,171 +0,0 @@ -$primary-dark-mode: #45973a; -$primary-dark-mode-rgb: 69, 151, 58; -$primary-dark-mode-darken-10: darken($primary-dark-mode, 10%); -$danger-dark-mode: #b71631; -$danger-dark-mode-rgb: 183, 22, 49; -$bg-dark-mode: #161618; -$bg-dark-mode-rgb: 22, 22, 24; -$bg-dark-mode-accent: #101216; -$bg-dark-mode-alt: #242529; -$bg-light-dark-mode: #1c1c1f; -$bg-light-dark-mode-rgb: 28, 28, 31; -$text-color-dark-mode: #abb2bf; -$text-color-dark-mode-accent: lighten($text-color-dark-mode, 10%); -$border-color-dark-mode: #47494f; - -@mixin dark-mode { - --bs-primary: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 10%)); - --bs-danger: #{$danger-dark-mode}; - --bs-danger-rgb: #{$danger-dark-mode-rgb}; - --bs-body-bg: #{$bg-dark-mode}; - --bs-body-bg-rgb: #{$bg-dark-mode-rgb}; - --bs-body-color: #{$text-color-dark-mode}; - --bs-light: #{$bg-light-dark-mode}; - --bs-light-rgb: #{$bg-light-dark-mode-rgb}; - --bs-border-color: #{$border-color-dark-mode}; - --pngx-bg-darker: #{$bg-dark-mode-accent}; - --pngx-bg-alt: #{$bg-dark-mode-alt}; - --pngx-body-color-accent: #{$text-color-dark-mode-accent}; - --pngx-focus-alpha: 0.7; - --pngx-primary-faded: var(--pngx-primary-darken-15); - --pngx-primary-text-contrast: var(--bs-body-color); - - .navbar.bg-primary{ - --bs-primary: hsl(var(--pngx-primary),var(--pngx-primary-lightness)); - --bs-primary-rgb: var(--bs-primary); - } - - .navbar-brand { - color: var(--bs-body-color); - } - - .border { - border-color: var(--bs-border-color) !important; - } - - .border-end { - border-right: 1px solid var(--bs-border-color) !important; - } - - .border-start { - border-left: 1px solid var(--bs-border-color) !important; - } - - .border-bottom { - border-bottom: 1px solid var(--bs-border-color) !important; - } - - .text-dark, .text-light { - color: var(--bs-body-color) !important; - } - - .btn-outline-primary, .btn-primary { - &:hover, &:focus, &.active, &:active { - color: var(--bs-light) !important; - } - } - - .btn-outline-secondary { - &:hover, &:focus, &.active, &:active { - background-color: var(--pngx-bg-darker); - color: var(--bs-primary); - } - } - - .search-form-container { - input, input:focus { - color: var(--bs-body-color) !important; - } - } - - .card { - background-color: var(--bs-body-bg); - - .card-header { - background-color: rgba(0, 0, 0, 0.12); - } - } - - .modal-content, .modal-header, .modal-body, .modal-footer { - background-color: var(--bs-body-bg); - border-color: var(--bs-border-color); - } - - app-tag .badge { - filter: brightness(.8); - } - - .doc-img-container { - border: none !important; - border-top-left-radius: .25rem; - border-top-right-radius: .25rem; - overflow: hidden; - } - - .doc-img { - mix-blend-mode: normal; - border-radius: 0; - border-color: var(--bs-border-color); - filter: invert(10%); - - &.border-end { - border-right: none !important; - } - } - - .doc-img.inverted { - filter: invert(95%) hue-rotate(180deg); - } - - .card-selected .doc-img { - mix-blend-mode: luminosity; - } - - .ng-dropdown-panel .ng-dropdown-panel-items .ng-option:hover, - .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-marked { - background-color: $bg-light-dark-mode; - } - - table { - .des, - .asc { - &::after { - filter: invert(0.8); /* arrow is a black inline png bkgd image (!) so use filter */ - } - } - - &.table-hover > tbody > tr:hover > * { - background-color: $bg-light-dark-mode; - color: $text-color-dark-mode-accent; - } - } - - .table-striped > tbody > tr:nth-of-type(odd) > * { - color: $text-color-dark-mode-accent; - } - - .close, .modal .btn-close, .alert .btn-close { - text-shadow: 0 1px 0 #666; - } - - .modal .btn-close, .alert .btn-close { - filter: invert(1) grayscale(100%) brightness(200%); - } - - .toast { - background-color: hsla(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 18%), 0.9); - } - - .toast-header { - background-color: hsla(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 10%), 0.9); - } -} - -body.color-scheme-dark { - @include dark-mode; -} -body.color-scheme-system { - @media (prefers-color-scheme: dark) { - @include dark-mode; - } -}