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;
- }
-}