mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge pull request #243 from paperless-ngx/css-variables
Custom color theming
This commit is contained in:
		| @@ -19,7 +19,7 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|   constructor (private settings: SettingsService, private consumerStatusService: ConsumerStatusService, private toastService: ToastService, private router: Router) { | ||||
|     let anyWindow = (window as any) | ||||
|     anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'; | ||||
|     this.settings.updateDarkModeSettings() | ||||
|     this.settings.updateAppearanceSettings() | ||||
|   } | ||||
|  | ||||
|   ngOnDestroy(): void { | ||||
|   | ||||
| @@ -92,7 +92,7 @@ | ||||
|               <svg class="sidebaricon" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#file-text"/> | ||||
|               </svg> {{d.title | documentTitle}} | ||||
|               <span class="close bg-light" (click)="closeDocument(d); $event.preventDefault()"> | ||||
|               <span class="close" (click)="closeDocument(d); $event.preventDefault()"> | ||||
|                 <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16"> | ||||
|                   <use xlink:href="assets/bootstrap-icons.svg#x"/> | ||||
|                 </svg> | ||||
| @@ -169,7 +169,7 @@ | ||||
|           </li> | ||||
|           <li class="nav-item"> | ||||
|             <div class="d-flex w-100 flex-wrap"> | ||||
|               <a class="nav-link pe-0 pb-0" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx"> | ||||
|               <a class="nav-link pe-2 pb-1" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx"> | ||||
|                 <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="sidebaricon bi bi-github" viewBox="0 0 16 16"> | ||||
|                   <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/> | ||||
|                 </svg> <ng-container i18n>GitHub</ng-container> | ||||
|   | ||||
| @@ -35,16 +35,14 @@ | ||||
|  | ||||
| .sidebar .nav-link { | ||||
|   font-weight: 500; | ||||
|   color: #333; | ||||
| } | ||||
|  | ||||
| .sidebar .nav-link .sidebaricon { | ||||
|   margin-right: 4px; | ||||
|   color: #999; | ||||
| } | ||||
|  | ||||
| .sidebar .nav-link.active { | ||||
|   color: $primary; | ||||
|   color: var(--bs-primary); | ||||
|   font-weight: bold; | ||||
| } | ||||
|  | ||||
| @@ -172,8 +170,7 @@ | ||||
|     } | ||||
|  | ||||
|     &:focus { | ||||
|       background-color: #fff; | ||||
|       color: #212529; | ||||
|       background-color: rgba(0, 0, 0, 0.3); | ||||
|       flex-grow: 1; | ||||
|       padding-left: 0.5rem; | ||||
|     } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|       <p *ngIf="message">{{message}}</p> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|       <button type="button" class="btn btn-outline-dark" (click)="cancel()" [disabled]="!buttonsEnabled" i18n>Cancel</button> | ||||
|       <button type="button" class="btn btn-outline-secondary" (click)="cancel()" [disabled]="!buttonsEnabled" i18n>Cancel</button> | ||||
|       <button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled"> | ||||
|         {{btnCaption}} | ||||
|         <span *ngIf="!confirmButtonEnabled"> ({{seconds}})</span> | ||||
|   | ||||
| @@ -42,7 +42,7 @@ | ||||
|   filter: brightness(0.5); | ||||
|  | ||||
|   &.active { | ||||
|     background-color: lighten($primary, 30%); | ||||
|     background-color: var(--ngx-primary-lighten-30); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <div class="mb-3"> | ||||
|   <label [for]="inputId">{{title}}</label> | ||||
|   <label *ngIf="title" [for]="inputId">{{title}}</label> | ||||
|  | ||||
|   <div class="input-group" [class.is-invalid]="error"> | ||||
|     <span class="input-group-text" [style.background-color]="value">   </span> | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <app-widget-frame [title]="savedView.name"> | ||||
|  | ||||
|   <a header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a> | ||||
|   <a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a> | ||||
|  | ||||
|  | ||||
|   <table content class="table table-sm bg-light table-hover table-borderless mb-0"> | ||||
|   <table content class="table table-sm table-hover table-borderless mb-0"> | ||||
|     <thead> | ||||
|       <tr> | ||||
|         <th i18n>Created</th> | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|     </div> | ||||
|  | ||||
|   </div> | ||||
|   <div class="card-body text-dark"> | ||||
|   <div class="card-body text-dark bg-light"> | ||||
|     <ng-content select ="[content]"></ng-content> | ||||
|   </div> | ||||
| </div> | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
|         <div class="d-flex justify-content-between align-items-center"> | ||||
|           <h5 class="card-title"> | ||||
|             <ng-container *ngIf="document.correspondent"> | ||||
|               <a *ngIf="clickCorrespondent.observers.length ; else nolink" [routerLink]="[]" title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold">{{(document.correspondent$ | async)?.name}}</a> | ||||
|               <a *ngIf="clickCorrespondent.observers.length ; else nolink" [routerLink]="[]" title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name}}</a> | ||||
|               <ng-template #nolink>{{(document.correspondent$ | async)?.name}}</ng-template>: | ||||
|             </ng-container> | ||||
|             {{document.title | documentTitle}} | ||||
|   | ||||
| @@ -52,7 +52,7 @@ | ||||
| } | ||||
|  | ||||
| .card-selected { | ||||
|   border-color: $primary; | ||||
|   border-color: var(--bs-primary); | ||||
|  | ||||
|   .document-card-check { | ||||
|     display: block; | ||||
| @@ -60,7 +60,7 @@ | ||||
| } | ||||
|  | ||||
| .doc-img-background-selected { | ||||
|   background-color: $primaryFaded; | ||||
|   background-color: var(--ngx-primary-faded); | ||||
| } | ||||
|  | ||||
| .card-info { | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
|     <div class="card-body p-2"> | ||||
|       <p class="card-text"> | ||||
|         <ng-container *ngIf="document.correspondent"> | ||||
|           <a [routerLink]="[]" title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold">{{(document.correspondent$ | async)?.name}}</a>: | ||||
|           <a [routerLink]="[]" title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name}}</a>: | ||||
|         </ng-container> | ||||
|         {{document.title | documentTitle}} | ||||
|       </p> | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
| } | ||||
|  | ||||
| .card-selected { | ||||
|   border-color: $primary; | ||||
|   border-color:var(--bs-primary); | ||||
|  | ||||
|   .document-card-check { | ||||
|     display: block; | ||||
| @@ -45,7 +45,7 @@ | ||||
| } | ||||
|  | ||||
| .doc-img-background-selected { | ||||
|   background-color: $primaryFaded; | ||||
|   background-color: var(--ngx-primary-faded); | ||||
| } | ||||
|  | ||||
| .card-info { | ||||
| @@ -57,7 +57,7 @@ | ||||
|     &:hover, | ||||
|     &:focus { | ||||
|       background-color: transparent !important; | ||||
|       color: $primary; | ||||
|       color: var(--bs-primary); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ tr { | ||||
| } | ||||
|  | ||||
| .table-row-selected { | ||||
|   background-color: $primaryFaded; | ||||
|   background-color: var(--ngx-primary-faded); | ||||
| } | ||||
|  | ||||
| $paperless-card-breakpoints: ( | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|     <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check> | ||||
|   </div> | ||||
|   <div class="modal-footer"> | ||||
|     <button type="button" class="btn btn-outline-dark" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button> | ||||
|     <button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button> | ||||
|     <button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button> | ||||
|   </div> | ||||
| </form> | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
|  | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|       <button type="button" class="btn btn-outline-dark" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button> | ||||
|       <button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button> | ||||
|       <button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button> | ||||
|     </div> | ||||
|   </form> | ||||
|   | ||||
| @@ -100,6 +100,22 @@ | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="row mb-3"> | ||||
|           <div class="col-md-3 col-form-label"> | ||||
|             <span i18n>Theme Color</span> | ||||
|           </div> | ||||
|           <div class="col-3"> | ||||
|             <app-input-color i18n-title formControlName="themeColor" [error]="error?.color"></app-input-color> | ||||
|           </div> | ||||
|           <div class="col-2"> | ||||
|             <button class="btn btn-link btn-sm pt-2 ps-0" [disabled]="!this.settingsForm.get('themeColor').value" (click)="clearThemeColor()"> | ||||
|               <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||
|                 <path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> | ||||
|               </svg><ng-container i18n>Reset</ng-container> | ||||
|             </button> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <h4 class="mt-4" i18n>Bulk editing</h4> | ||||
|  | ||||
|         <div class="row mb-3"> | ||||
|   | ||||
| @@ -24,6 +24,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     'darkModeUseSystem': new FormControl(null), | ||||
|     'darkModeEnabled': new FormControl(null), | ||||
|     'darkModeInvertThumbs': new FormControl(null), | ||||
|     'themeColor': new FormControl(null), | ||||
|     'useNativePdfViewer': new FormControl(null), | ||||
|     'savedViews': this.savedViewGroup, | ||||
|     'displayLanguage': new FormControl(null), | ||||
| @@ -40,6 +41,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|   store: BehaviorSubject<any> | ||||
|   storeSub: Subscription | ||||
|   isDirty$: Observable<boolean> | ||||
|   isDirty: Boolean = false | ||||
|  | ||||
|   get computedDateLocale(): string { | ||||
|     return this.settingsForm.value.dateLocale || this.settingsForm.value.displayLanguage || this.currentLocale | ||||
| @@ -63,6 +65,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|         'darkModeUseSystem': this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM), | ||||
|         'darkModeEnabled': this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED), | ||||
|         'darkModeInvertThumbs': this.settings.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED), | ||||
|         'themeColor': this.settings.get(SETTINGS_KEYS.THEME_COLOR), | ||||
|         'useNativePdfViewer': this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER), | ||||
|         'savedViews': {}, | ||||
|         'displayLanguage': this.settings.getLanguage(), | ||||
| @@ -97,10 +100,21 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|  | ||||
|       // Initialize dirtyCheck | ||||
|       this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable()) | ||||
|  | ||||
|       // Record dirty in case we need to 'undo' appearance settings if not saved on close | ||||
|       this.isDirty$.subscribe(dirty => { | ||||
|         this.isDirty = dirty | ||||
|       }) | ||||
|        | ||||
|       // "Live" visual changes prior to save | ||||
|       this.settingsForm.valueChanges.subscribe(() => { | ||||
|         this.settings.updateAppearanceSettings(this.settingsForm.get('darkModeUseSystem').value, this.settingsForm.get('darkModeEnabled').value, this.settingsForm.get('themeColor').value) | ||||
|       }) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   ngOnDestroy() { | ||||
|     if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didnt save | ||||
|     this.storeSub && this.storeSub.unsubscribe(); | ||||
|   } | ||||
|  | ||||
| @@ -119,6 +133,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     this.settings.set(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, this.settingsForm.value.darkModeUseSystem) | ||||
|     this.settings.set(SETTINGS_KEYS.DARK_MODE_ENABLED, (this.settingsForm.value.darkModeEnabled == true).toString()) | ||||
|     this.settings.set(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, (this.settingsForm.value.darkModeInvertThumbs == true).toString()) | ||||
|     this.settings.set(SETTINGS_KEYS.THEME_COLOR, (this.settingsForm.value.themeColor).toString()) | ||||
|     this.settings.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, this.settingsForm.value.useNativePdfViewer) | ||||
|     this.settings.set(SETTINGS_KEYS.DATE_LOCALE, this.settingsForm.value.dateLocale) | ||||
|     this.settings.set(SETTINGS_KEYS.DATE_FORMAT, this.settingsForm.value.dateFormat) | ||||
| @@ -129,7 +144,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     this.settings.setLanguage(this.settingsForm.value.displayLanguage) | ||||
|     this.store.next(this.settingsForm.value) | ||||
|     this.documentListViewService.updatePageSize() | ||||
|     this.settings.updateDarkModeSettings() | ||||
|     this.settings.updateAppearanceSettings() | ||||
|     this.toastService.showInfo($localize`Settings saved successfully.`) | ||||
|   } | ||||
|  | ||||
| @@ -165,4 +180,8 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     } | ||||
|  | ||||
|   } | ||||
|  | ||||
|   clearThemeColor() { | ||||
|     this.settingsForm.get('themeColor').patchValue(''); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
|       <app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|       <button type="button" class="btn btn-outline-dark" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button> | ||||
|       <button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button> | ||||
|       <button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button> | ||||
|     </div> | ||||
|   </form> | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| import { DOCUMENT } from '@angular/common'; | ||||
| import { Inject, Injectable, LOCALE_ID, Renderer2, RendererFactory2 } from '@angular/core'; | ||||
| import { Inject, Injectable, LOCALE_ID, Renderer2, RendererFactory2, RendererStyleFlags2 } from '@angular/core'; | ||||
| import { Meta } from '@angular/platform-browser'; | ||||
| import { CookieService } from 'ngx-cookie-service'; | ||||
| import { hexToHsl } from 'src/app/utils/color'; | ||||
|  | ||||
| export interface PaperlessSettings { | ||||
|   key: string | ||||
| @@ -27,6 +28,7 @@ export const SETTINGS_KEYS = { | ||||
|   DARK_MODE_USE_SYSTEM: 'general-settings:dark-mode:use-system', | ||||
|   DARK_MODE_ENABLED: 'general-settings:dark-mode:enabled', | ||||
|   DARK_MODE_THUMB_INVERTED: 'general-settings:dark-mode:thumb-inverted', | ||||
|   THEME_COLOR: 'general-settings:theme:color', | ||||
|   USE_NATIVE_PDF_VIEWER: 'general-settings:document-details:native-pdf-viewer', | ||||
|   DATE_LOCALE: 'general-settings:date-display:date-locale', | ||||
|   DATE_FORMAT: 'general-settings:date-display:date-format', | ||||
| @@ -43,6 +45,7 @@ const SETTINGS: PaperlessSettings[] = [ | ||||
|   {key: SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, type: "boolean", default: true}, | ||||
|   {key: SETTINGS_KEYS.DARK_MODE_ENABLED, type: "boolean", default: false}, | ||||
|   {key: SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, type: "boolean", default: true}, | ||||
|   {key: SETTINGS_KEYS.THEME_COLOR, type: "string", default: ""}, | ||||
|   {key: SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, type: "boolean", default: false}, | ||||
|   {key: SETTINGS_KEYS.DATE_LOCALE, type: "string", default: ""}, | ||||
|   {key: SETTINGS_KEYS.DATE_FORMAT, type: "string", default: "mediumDate"}, | ||||
| @@ -68,12 +71,13 @@ export class SettingsService { | ||||
|   ) { | ||||
|     this.renderer = rendererFactory.createRenderer(null, null); | ||||
|  | ||||
|     this.updateDarkModeSettings() | ||||
|     this.updateAppearanceSettings() | ||||
|   } | ||||
|  | ||||
|   updateDarkModeSettings(): void { | ||||
|     let darkModeUseSystem = this.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM) | ||||
|     let darkModeEnabled = this.get(SETTINGS_KEYS.DARK_MODE_ENABLED) | ||||
|   public updateAppearanceSettings(darkModeUseSystem = null, darkModeEnabled = null, themeColor = null): void { | ||||
|     darkModeUseSystem ??= this.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM) | ||||
|     darkModeEnabled ??= this.get(SETTINGS_KEYS.DARK_MODE_ENABLED) | ||||
|     themeColor ??= this.get(SETTINGS_KEYS.THEME_COLOR); | ||||
|  | ||||
|     if (darkModeUseSystem) { | ||||
|       this.renderer.addClass(this.document.body, 'color-scheme-system') | ||||
| @@ -83,6 +87,14 @@ export class SettingsService { | ||||
|       darkModeEnabled ? this.renderer.addClass(this.document.body, 'color-scheme-dark') : this.renderer.removeClass(this.document.body, 'color-scheme-dark') | ||||
|     } | ||||
|  | ||||
|     if (themeColor) { | ||||
|       const hsl = hexToHsl(themeColor) | ||||
|       this.renderer.setStyle(document.documentElement, '--pngx-primary',`${+hsl.h * 360},${hsl.s * 100}%`, RendererStyleFlags2.DashCase) | ||||
|       this.renderer.setStyle(document.documentElement, '--pngx-primary-lightness',`${hsl.l * 100}%`, RendererStyleFlags2.DashCase) | ||||
|     } else { | ||||
|       this.renderer.removeStyle(document.documentElement, '--pngx-primary', RendererStyleFlags2.DashCase) | ||||
|       this.renderer.removeStyle(document.documentElement, '--pngx-primary-lightness', RendererStyleFlags2.DashCase) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   getLanguageOptions(): LanguageOption[] { | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import { HSL } from "ngx-color" | ||||
|  | ||||
| function componentToHex(c) { | ||||
|   var hex = Math.floor(c).toString(16) | ||||
| @@ -5,8 +6,8 @@ function componentToHex(c) { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c | ||||
|  * | ||||
|  * https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c | ||||
|  * Converts an HSL color value to RGB. Conversion formula | ||||
|  * adapted from http://en.wikipedia.org/wiki/HSL_color_space. | ||||
|  * Assumes h, s, and l are contained in the set [0, 1] and | ||||
| @@ -42,6 +43,46 @@ function hslToRgb(h, s, l){ | ||||
|   return [r * 255, g * 255, b * 255] | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c | ||||
|  * Converts an RGB color value to HSL. Conversion formula | ||||
|  * adapted from http://en.wikipedia.org/wiki/HSL_color_space. | ||||
|  * Assumes r, g, and b are contained in the set [0, 255] and | ||||
|  * returns h, s, and l in the set [0, 1]. | ||||
|  * | ||||
|  * @param   Number  r       The red color value | ||||
|  * @param   Number  g       The green color value | ||||
|  * @param   Number  b       The blue color value | ||||
|  * @return  Array           The HSL representation | ||||
|  */ | ||||
|  export function rgbToHsl(r, g, b){ | ||||
|   r /= 255, g /= 255, b /= 255; | ||||
|   var max = Math.max(r, g, b), min = Math.min(r, g, b); | ||||
|   var h, s, l = (max + min) / 2; | ||||
|  | ||||
|   if(max == min){ | ||||
|       h = s = 0; // achromatic | ||||
|   }else{ | ||||
|       var d = max - min; | ||||
|       s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | ||||
|       switch(max){ | ||||
|           case r: h = (g - b) / d + (g < b ? 6 : 0); break; | ||||
|           case g: h = (b - r) / d + 2; break; | ||||
|           case b: h = (r - g) / d + 4; break; | ||||
|       } | ||||
|       h /= 6; | ||||
|   } | ||||
|  | ||||
|   return [h, s, l]; | ||||
| } | ||||
|  | ||||
| export function hexToHsl(hex: string): HSL { | ||||
|   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] } | ||||
| } | ||||
|  | ||||
| export function randomColor() { | ||||
|   let rgb = hslToRgb(Math.random(), 0.6, Math.random() * 0.4 + 0.4) | ||||
|   return `#${componentToHex(rgb[0])}${componentToHex(rgb[1])}${componentToHex(rgb[2])}` | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| // bs options | ||||
| $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"; | ||||
| @import "node_modules/bootstrap/scss/bootstrap"; | ||||
| @import "~@ng-select/ng-select/themes/default.theme.css"; | ||||
|  | ||||
| .toolbaricon { | ||||
|   width: 1.2em; | ||||
| @@ -113,10 +116,6 @@ body { | ||||
|   } | ||||
| } | ||||
|  | ||||
| .ngx-file-drop__drop-zone--over { | ||||
|   background-color: $primaryFaded !important; | ||||
| } | ||||
|  | ||||
| // Bootstrap 5 tweaks | ||||
| a.badge { | ||||
|   text-decoration: none; | ||||
| @@ -130,6 +129,6 @@ a.badge { | ||||
| } | ||||
|  | ||||
| .btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle { | ||||
|     background-color: $paperless-green; | ||||
|     border-color: $paperless-green; | ||||
|     background-color: var(--bs-primary); | ||||
|     border-color: var(--bs-primary); | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,294 @@ | ||||
| $paperless-green: #17541f; | ||||
| $primary: #17541f; | ||||
| $primaryFaded: #d1ddd2; | ||||
| $enable-negative-margins: true; | ||||
| :root { | ||||
|   // base color e.g. #17541f = hsl(128, 57%, 21%) | ||||
|   --pngx-primary: 128, 57%; | ||||
|   --pngx-primary-lightness: 21%; | ||||
|  | ||||
|   --bs-primary: hsl(var(--pngx-primary), var(--pngx-primary-lightness)); | ||||
|   --bs-border-color: var(--bs-gray-400); | ||||
|   --ngx-primary-faded: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 72%)); | ||||
|   --ngx-primary-lighten-10: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 10%)); | ||||
|   --ngx-primary-lighten-30: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 30%)); | ||||
|   --ngx-primary-darken-10: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 10%)); | ||||
|   --ngx-primary-darken-15: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 15%)); | ||||
|   --ngx-primary-darken-18: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 18%)); | ||||
|   --ngx-bg-darker: var(--bs-gray-100); | ||||
|   --ngx-focus-alpha: 0.3; | ||||
| } | ||||
|  | ||||
| svg.logo { | ||||
|   .leaf { | ||||
|     fill: var(--bs-primary) !important; | ||||
|   } | ||||
|   .text { | ||||
|     fill: var(--bs-body-color) !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .nav-link, .list-group-item { | ||||
|   color: var(--bs-body-color); | ||||
| } | ||||
|  | ||||
| .bg-body { | ||||
|   background-color: #fff; | ||||
|   background-color: var(--bs-body-bg); | ||||
| } | ||||
|  | ||||
| .bg-primary { | ||||
|   background-color: var(--bs-primary) !important; | ||||
| } | ||||
|  | ||||
| .btn-primary { | ||||
|   background-color: var(--bs-primary); | ||||
|   border-color: var(--bs-primary); | ||||
|  | ||||
|   &:hover, &:focus { | ||||
|     background-color: var(--ngx-primary-darken-10); | ||||
|     border-color: var(--ngx-primary-darken-10); | ||||
|   } | ||||
|  | ||||
|   &:disabled, &.disabled { | ||||
|     background-color: var(--ngx-primary-darken-10) !important; | ||||
|     border-color: var(--ngx-primary-darken-10) !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .btn-outline-primary { | ||||
|   border-color: var(--bs-primary) !important; | ||||
|   color: var(--bs-primary) !important; | ||||
|  | ||||
|   &:hover, &:focus, &.active, &:active { | ||||
|     background-color: var(--bs-primary) !important; | ||||
|     color: var(--bs-light) !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .btn-outline-secondary { | ||||
|   color: var(--bs-secondary); | ||||
| } | ||||
|  | ||||
| .nav-item .sidebaricon { | ||||
|   color: var(--bs-secondary); | ||||
| } | ||||
|  | ||||
| .btn:focus, | ||||
| .btn:active:focus, | ||||
| .dropdown-item:focus, | ||||
| .btn-check:focus + .btn, | ||||
| .form-control:focus, | ||||
| .form-check-input:focus, | ||||
| .form-check-radio:focus, | ||||
| .form-select:focus { | ||||
|   box-shadow: 0 0 0 0.25rem hsla(var(--pngx-primary), var(--pngx-primary-lightness), var(--ngx-focus-alpha)); | ||||
| } | ||||
|  | ||||
| .form-switch .form-check-input:focus { | ||||
|   background-image: escape-svg(url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#bbb'/></svg>")); | ||||
| } | ||||
|  | ||||
| .nav-link:focus-visible, .nav-item a:focus-visible { | ||||
|   outline: none; | ||||
|   background-color: var(--ngx-bg-darker); | ||||
| } | ||||
|  | ||||
| a.navbar-brand:focus-visible { | ||||
|   outline: none; | ||||
|   color: var(--ngx-primary-darken-10); | ||||
| } | ||||
|  | ||||
| .dropdown.show { | ||||
|   > .btn-primary { | ||||
|     background-color: var(--bs-primary); | ||||
|     border-color: var(--bs-primary); | ||||
|   } | ||||
|  | ||||
|   > .btn-outline-primary { | ||||
|     color: var(--bs-body-color) !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| a, a:hover, .btn-link, .btn-link:hover { | ||||
|   color: var(--bs-primary); | ||||
| } | ||||
|  | ||||
| .form-control:not(.btn), | ||||
| input, | ||||
| select, | ||||
| textarea, | ||||
| .form-select:not(.is-invalid):not(:disabled), | ||||
| .form-check-input { | ||||
|   color: var(--bs-body-color); | ||||
|   background-color: var(--bs-body-bg); | ||||
|   border-color: var(--bs-border-color); | ||||
|  | ||||
|   &:focus { | ||||
|     background-color: var(--ngx-bg-darker); | ||||
|     color: var(--bs-body-color) !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .form-check-input:checked { | ||||
|   background-color: var(--bs-primary); | ||||
|   border-color: var(--bs-primary); | ||||
| } | ||||
|  | ||||
| .form-check-input:focus { | ||||
|   border-color: var(--bs-primary); | ||||
| } | ||||
|  | ||||
| .page-link { | ||||
|   color: var(--bs-light); | ||||
|   background-color: var(--bs-body-bg); | ||||
|   border-color: var(--bs-border-color) !important; | ||||
|  | ||||
|   &:hover, &:focus { | ||||
|     background-color: var(--bs-primary) !important; | ||||
|     color: var(--bs-light) !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .page-item.active .page-link { | ||||
|   background-color: var(--bs-primary); | ||||
|   border-color: var(--bs-primary) !important; | ||||
|   color: var(--bs-light); | ||||
| } | ||||
|  | ||||
| .page-item.disabled .page-link { | ||||
|   background-color: var(--ngx-bg-darker); | ||||
| } | ||||
|  | ||||
| .nav-tabs { | ||||
|   border-bottom: 1px solid var(--bs-border-color); | ||||
|  | ||||
|   .nav-link { | ||||
|     color: var(--bs-primary); | ||||
|  | ||||
|     &.active, &:hover { | ||||
|       border-color: var(--bs-border-color); | ||||
|       background-color: var(--bs-body-bg); | ||||
|       color: var(--bs-body-color); | ||||
|       border-bottom: 1px solid transparent; | ||||
|     } | ||||
|  | ||||
|     &:focus { | ||||
|       border-color: var(--bs-border-color); | ||||
|     } | ||||
|  | ||||
|     &.active:focus, &:active { | ||||
|       border-bottom: 1px solid transparent; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| .ng-select-container, | ||||
| .ng-select.ng-select-opened > .ng-select-container, | ||||
| .ng-dropdown-panel, | ||||
| .ng-dropdown-panel .ng-dropdown-panel-items .ng-option { | ||||
|   background-color: var(--bs-body-bg) !important; | ||||
|   color: var(--bs-body-color) !important; | ||||
|   border-color: var(--bs-border-color) !important; | ||||
|  | ||||
|   input:focus { | ||||
|     background-color: transparent !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .input-group-text { | ||||
|   color: var(--bs-body-color); | ||||
|   background-color: var(--bs-body-bg); | ||||
|   border-color: var(--bs-border-color); | ||||
| } | ||||
|  | ||||
| .list-group-item { | ||||
|   color: var(--bs-body-color); | ||||
|   background-color: var(--bs-body-bg); | ||||
|   border-color: var(--bs-border-color); | ||||
|  | ||||
|   &:hover, &:focus { | ||||
|     background-color: var(--bs-body-bg); | ||||
|   } | ||||
| } | ||||
|  | ||||
| .dropdown-menu { | ||||
|   background-color: var(--bs-body-bg); | ||||
|  | ||||
|   .dropdown-divider { | ||||
|     border-color: var(--bs-border-color); | ||||
|   } | ||||
|  | ||||
|   .dropdown-item { | ||||
|     color: var(--bs-body-color); | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: var(--ngx-bg-darker); | ||||
|       color: var(--bs-body-color); | ||||
|     } | ||||
|  | ||||
|     &.active { | ||||
|       background-color: var(--bs-primary); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| table.table { | ||||
|   color: var(--bs-body-color); | ||||
|  | ||||
|   .des,.asc { | ||||
|     background-color: var(--bs-body-bg) !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .close { | ||||
|   color: var(--bs-body-color); | ||||
| } | ||||
|  | ||||
| .modal .btn-close { | ||||
|   color: var(--bs-body-color); | ||||
| } | ||||
|  | ||||
| .ngx-file-drop__drop-zone--over { | ||||
|   background-color: var(--ngx-primary-faded) !important; | ||||
| } | ||||
|  | ||||
| .alert-danger { | ||||
|   color: var(--bs-body-color); | ||||
|   background-color: var(--bs-danger); | ||||
|   border-color: var(--bs-danger); | ||||
| } | ||||
|  | ||||
| .alert-secondary { | ||||
|   background-color: var(--ngx-primary-darken-18); | ||||
|   border-color: var(--ngx-primary-darken-15); | ||||
|   color: var(--bs-body-color); | ||||
| } | ||||
|  | ||||
| .ngb-dp-header, | ||||
| .ngb-dp-weekdays, | ||||
| .ngb-dp-month { | ||||
|   background-color: var(--bs-body-bg); | ||||
| } | ||||
|  | ||||
| .popover { | ||||
|   .popover-header, | ||||
|   .popover-body { | ||||
|     background-color: var(--ngx-bg-alt); | ||||
|     border-color: var(--bs-border-color); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // fix popover carat colors | ||||
| .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="left"] { | ||||
|   border-left-color: var(--ngx-bg-alt); | ||||
| } | ||||
| .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="right"] { | ||||
|   border-right-color: var(--ngx-bg-alt); | ||||
| } | ||||
| .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="top"] { | ||||
|   border-top-color: var(--ngx-bg-alt); | ||||
| } | ||||
| .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="bottom"] { | ||||
|   border-bottom-color: var(--ngx-bg-alt); | ||||
| } | ||||
|  | ||||
| .bs-popover-bottom .popover-header::before, | ||||
| .bs-popover-auto[x-placement^=bottom] .popover-header::before { | ||||
|   border-bottom-color: var(--ngx-bg-alt); | ||||
| } | ||||
| @@ -1,8 +1,14 @@ | ||||
| $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-accent: #21262d; | ||||
| $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; | ||||
| @@ -12,141 +18,79 @@ $border-color-dark-mode: #47494f; | ||||
| } | ||||
|  | ||||
| @mixin dark-mode { | ||||
|   background-color: $bg-dark-mode !important; | ||||
|   color: $text-color-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}; | ||||
|   --ngx-bg-darker: #{$bg-dark-mode-accent}; | ||||
|   --ngx-bg-alt: #{$bg-dark-mode-alt}; | ||||
|   --ngx-body-color-accent: #{$text-color-dark-mode-accent}; | ||||
|   --ngx-focus-alpha: 0.7; | ||||
|  | ||||
|   .navbar.bg-primary{ | ||||
|     --bs-primary: hsl(var(--pngx-primary),var(--pngx-primary-lightness)); | ||||
|     --bs-primary-rgb: var(--bs-primary); | ||||
|   } | ||||
|    | ||||
|   .navbar-brand { | ||||
|     color: $text-color-dark-mode; | ||||
|     color: var(--bs-body-color); | ||||
|   } | ||||
|  | ||||
|   svg.logo { | ||||
|     .leaf { | ||||
|       color: $primary-dark-mode !important; | ||||
|     } | ||||
|     .text { | ||||
|       fill: $text-color-dark-mode !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .bg-light { | ||||
|     background-color: $bg-light-dark-mode !important; | ||||
|  | ||||
|     a, | ||||
|     div { | ||||
|       color: $text-color-dark-mode; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .bg-body { | ||||
|     background-color: $bg-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .text-light { | ||||
|     color: $text-color-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|    | ||||
|   .border { | ||||
|     border-color: $border-color-dark-mode !important; | ||||
|     border-color: var(--bs-border-color) !important; | ||||
|   } | ||||
|  | ||||
|   .border-end { | ||||
|     border-right: 1px solid $border-color-dark-mode !important; | ||||
|     border-right: 1px solid var(--bs-border-color) !important; | ||||
|   } | ||||
|  | ||||
|   .border-start { | ||||
|     border-left: 1px solid $border-color-dark-mode !important; | ||||
|     border-left: 1px solid var(--bs-border-color) !important; | ||||
|   } | ||||
|  | ||||
|   .border-bottom { | ||||
|     border-bottom: 1px solid $border-color-dark-mode !important; | ||||
|     border-bottom: 1px solid var(--bs-border-color) !important; | ||||
|   } | ||||
|  | ||||
|   .nav-link { | ||||
|     color: $text-color-dark-mode !important; | ||||
|   .text-dark, .text-light { | ||||
|     color: var(--bs-body-color) !important; | ||||
|   } | ||||
|  | ||||
|     &.active { | ||||
|       background-color: $bg-dark-mode; | ||||
|       color: $text-color-dark-mode; | ||||
|       border-color: $border-color-dark-mode $border-color-dark-mode $bg-dark-mode; | ||||
|  | ||||
|       .close { | ||||
|         background-color: inherit !important; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|       color: $text-color-dark-mode-accent !important; | ||||
|       border-color: $border-color-dark-mode $border-color-dark-mode $bg-dark-mode; | ||||
|   .btn-outline-primary, .btn-primary { | ||||
|     &:hover, &:focus, &.active, &:active { | ||||
|       color: var(--ngx-body-color-accent) !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .page-item.active .page-link { | ||||
|     background-color: darken($primary-dark-mode, 10%); | ||||
|   } | ||||
|  | ||||
|   .nav-tabs { | ||||
|     border-color: $border-color-dark-mode; | ||||
|  | ||||
|     .nav-link { | ||||
|       color: $primary-dark-mode !important; | ||||
|  | ||||
|       &.active { | ||||
|         color: $text-color-dark-mode !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .dropdown-menu { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|  | ||||
|     .dropdown-divider { | ||||
|       border-color: $border-color-dark-mode; | ||||
|     } | ||||
|  | ||||
|     .dropdown-item { | ||||
|       color: $text-color-dark-mode; | ||||
|  | ||||
|       &:hover { | ||||
|         background-color: $bg-light-dark-mode; | ||||
|         color: $text-color-dark-mode; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .dropdown-item.disabled { | ||||
|       color: darken($text-color-dark-mode, 20%); | ||||
|   .btn-outline-secondary { | ||||
|     &:hover, &:focus, &.active, &:active { | ||||
|       background-color: var(--ngx-bg-darker); | ||||
|       color: var(--bs-primary); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .card { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     background-color: var(--bs-body-bg); | ||||
|  | ||||
|     .card-text { | ||||
|       color: $text-color-dark-mode; | ||||
|     .card-header { | ||||
|       background-color: rgba(0, 0, 0, 0.12); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .text-dark { | ||||
|     color: $text-color-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .modal-content, .modal-header, .modal-body, .modal-footer { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|     background-color: var(--bs-body-bg); | ||||
|     border-color: var(--bs-border-color); | ||||
|   } | ||||
|  | ||||
|   app-tag .badge { | ||||
|     filter: brightness(.8); | ||||
|   } | ||||
|  | ||||
|   .badge-light { | ||||
|     background-color: darken($bg-dark-mode, 20%); | ||||
|     color: $text-color-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   .badge.border-light { | ||||
|     border-color: $bg-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .doc-img-container { | ||||
|     border: none !important; | ||||
|     border-top-left-radius: .25rem; | ||||
| @@ -157,8 +101,9 @@ $border-color-dark-mode: #47494f; | ||||
|   .doc-img { | ||||
|     mix-blend-mode: normal; | ||||
|     border-radius: 0; | ||||
|     border-color: $bg-dark-mode; | ||||
|     border-color: var(--bs-border-color); | ||||
|     filter: invert(10%); | ||||
|  | ||||
|     &.border-end { | ||||
|       border-right: none !important; | ||||
|     } | ||||
| @@ -172,34 +117,14 @@ $border-color-dark-mode: #47494f; | ||||
|     mix-blend-mode: luminosity; | ||||
|   } | ||||
|  | ||||
|   .toast { | ||||
|     background-color: opacify($bg-light-dark-mode, .85); | ||||
|   } | ||||
|  | ||||
|   .toast-header { | ||||
|     background-color: opacify($bg-dark-mode, .85); | ||||
|   } | ||||
|  | ||||
|   a, | ||||
|   .card-title a { | ||||
|     color: $primary-dark-mode; | ||||
|  | ||||
|     &:hover { | ||||
|       color: lighten($primary, 10%); | ||||
|     } | ||||
|   .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 { | ||||
|     background-color: $bg-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|  | ||||
|     .des, | ||||
|     .asc { | ||||
|       background-color: transparent !important; | ||||
|       color: $text-color-dark-mode; | ||||
|       border-color: $border-color-dark-mode; | ||||
|  | ||||
|       &::after { | ||||
|         filter: invert(0.8); /* arrow is a black inline png bkgd image (!) so use filter */ | ||||
|       } | ||||
| @@ -209,238 +134,31 @@ $border-color-dark-mode: #47494f; | ||||
|       background-color: $bg-light-dark-mode; | ||||
|       color: $text-color-dark-mode-accent; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .table td, | ||||
|   .table th { | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .table-row-selected { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .table-striped > tbody > tr:nth-of-type(odd) > * { | ||||
|     color: $text-color-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   .close { | ||||
|     color: $text-color-dark-mode; | ||||
|   .close, .modal .btn-close { | ||||
|     text-shadow: 0 1px 0 #666; | ||||
|   } | ||||
|  | ||||
|   .modal .btn-close { | ||||
|     color: $text-color-dark-mode; | ||||
|     text-shadow: 0 1px 0 #666; | ||||
|     filter: invert(1) grayscale(100%) brightness(200%); | ||||
|   } | ||||
|  | ||||
|   .btn-outline-primary { | ||||
|     border-color: $primary-dark-mode; | ||||
|     color: $primary-dark-mode; | ||||
|  | ||||
|     &:not(:disabled):not(.disabled).active, | ||||
|     &:not(:disabled):not(.disabled):hover { | ||||
|       background-color: darken($primary-dark-mode, 10%); | ||||
|       border-color: darken($primary-dark-mode, 10%); | ||||
|       color: $text-color-dark-mode-accent; | ||||
|     } | ||||
|  | ||||
|     &.disabled.active { | ||||
|       background-color: darken($primary-dark-mode, 10%); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .btn-outline-secondary { | ||||
|     border-color: darken($text-color-dark-mode, 30%); | ||||
|     color: $text-color-dark-mode; | ||||
|  | ||||
|     &:not(:disabled):not(.disabled):hover { | ||||
|       background-color: $bg-dark-mode; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .btn-outline-danger { | ||||
|     border-color: $danger-dark-mode; | ||||
|     color: $danger-dark-mode; | ||||
|  | ||||
|     &:not(:disabled):not(.disabled):hover { | ||||
|       background-color: darken($danger-dark-mode, 10%); | ||||
|       border-color: darken($danger-dark-mode, 10%); | ||||
|       color: $text-color-dark-mode-accent; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .btn-outline-dark { | ||||
|     border-color: $border-color-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|  | ||||
|     &:not(:disabled):not(.disabled):hover { | ||||
|       color: $text-color-dark-mode-accent; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .btn-light:not(:disabled):not(.disabled) { | ||||
|     background-color: $bg-dark-mode; | ||||
|     color: $text-color-dark-mode-accent; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: $text-color-dark-mode; | ||||
|       color: $bg-dark-mode; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .btn-link:not(:disabled):not(.disabled) { | ||||
|     color: $primary-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .btn-link:hover, | ||||
|   .btn-outline-primary:not(:disabled):not(.disabled).active, | ||||
|   .btn-outline-primary:not(:disabled):not(.disabled):active, | ||||
|   .show > .btn-outline-primary.dropdown-toggle { | ||||
|     color: $text-color-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   button.bg-light:hover { | ||||
|     background-color: $bg-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .card-footer button:hover { | ||||
|     color: $primary-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .form-control:not(.is-invalid):not(.btn), | ||||
|   input:not(.is-invalid), | ||||
|   textarea:not(.is-invalid) { | ||||
|     border-color: $border-color-dark-mode; /* we dont want to override controls that get highlighting for errors */ | ||||
|   } | ||||
|  | ||||
|   .form-control:not(.btn), | ||||
|   input, | ||||
|   select, | ||||
|   textarea { | ||||
|     background-color: $bg-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|  | ||||
|     &::placeholder { | ||||
|       color: $text-color-dark-mode; | ||||
|     } | ||||
|  | ||||
|     &:focus { | ||||
|       background-color: $bg-light-dark-mode !important; | ||||
|       color: darken($text-color-dark-mode, 10%) !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .form-select:not(.is-invalid):not(:disabled) { | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .ng-select-container, | ||||
|   .ng-select.ng-select-opened > .ng-select-container, | ||||
|   .ng-dropdown-panel, | ||||
|   .ng-dropdown-panel .ng-dropdown-panel-items .ng-option { | ||||
|     background-color: $bg-dark-mode; | ||||
|     color: $text-color-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|  | ||||
|     input:focus { | ||||
|       background-color: transparent !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .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; | ||||
|   } | ||||
|  | ||||
|   .input-group-text { | ||||
|     color: $text-color-dark-mode; | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .list-group-item { | ||||
|     color: $text-color-dark-mode; | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .page-item.disabled .page-link { | ||||
|     background-color: $bg-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .list-group-item, | ||||
|   .page-link { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     border-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .page-item.active .page-link { | ||||
|     border-color: $border-color-dark-mode; | ||||
|     color: $text-color-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   .progress { | ||||
|     background-color: $border-color-dark-mode; | ||||
|   } | ||||
|  | ||||
|   .alert-danger { | ||||
|     color: $text-color-dark-mode-accent; | ||||
|     background-color: darken($danger-dark-mode, 20%); | ||||
|     border-color: darken($danger-dark-mode, 20%); | ||||
|   } | ||||
|  | ||||
|   .bg-dark { | ||||
|     background-color: $bg-light-dark-mode !important; | ||||
|   } | ||||
|  | ||||
|   .ngx-file-drop__drop-zone--over { | ||||
|     background-color: darken($primary-dark-mode, 35%) !important; | ||||
|     background-color: var(--ngx-primary-darken-15) !important; | ||||
|   } | ||||
|  | ||||
|   .alert-secondary { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|     border-color: darken($bg-light-dark-mode, 10%); | ||||
|     color: $text-color-dark-mode-accent; | ||||
|   .toast { | ||||
|     background-color: hsla(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 18%), 0.9); | ||||
|   } | ||||
|  | ||||
|   .progress-bar.bg-primary { | ||||
|     background-color: darken($primary-dark-mode, 5%) !important; | ||||
|   } | ||||
|  | ||||
|   .popover { | ||||
|     .popover-header, | ||||
|     .popover-body { | ||||
|       background-color: $bg-dark-mode-accent; | ||||
|       border-color: $border-color-dark-mode; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // fix popover carat colors | ||||
|   .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="left"] { | ||||
|     border-left-color: $bg-dark-mode-accent; | ||||
|   } | ||||
|   .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="right"] { | ||||
|     border-right-color: $bg-dark-mode-accent; | ||||
|   } | ||||
|   .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="top"] { | ||||
|     border-top-color: $bg-dark-mode-accent; | ||||
|   } | ||||
|   .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="bottom"] { | ||||
|     border-bottom-color: $bg-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   .bs-popover-bottom .popover-header::before, | ||||
|   .bs-popover-auto[x-placement^=bottom] .popover-header::before { | ||||
|     border-bottom-color: $bg-dark-mode-accent; | ||||
|   } | ||||
|  | ||||
|   .ngb-dp-header, | ||||
|   .ngb-dp-weekdays, | ||||
|   .ngb-dp-month { | ||||
|     background-color: $bg-light-dark-mode; | ||||
|    | ||||
|   .toast-header { | ||||
|     background-color: hsla(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 10%), 0.9); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon