Core welcome tour functionality

This commit is contained in:
Michael Shamoon 2022-09-21 21:44:15 -07:00
parent e7ebc33090
commit 4e56fe339e
16 changed files with 169 additions and 35 deletions

View File

@ -26,6 +26,7 @@
"ngx-color": "^8.0.3", "ngx-color": "^8.0.3",
"ngx-cookie-service": "^14.0.1", "ngx-cookie-service": "^14.0.1",
"ngx-file-drop": "^14.0.1", "ngx-file-drop": "^14.0.1",
"ngx-ui-tour-ng-bootstrap": "^11.0.0",
"rxjs": "~7.5.7", "rxjs": "~7.5.7",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"uuid": "^9.0.0", "uuid": "^9.0.0",
@ -12936,6 +12937,36 @@
"@angular/core": ">=14.0.0" "@angular/core": ">=14.0.0"
} }
}, },
"node_modules/ngx-ui-tour-core": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-ui-tour-core/-/ngx-ui-tour-core-9.0.0.tgz",
"integrity": "sha512-3HvrprosDaZm9jHi/w+oA8N9bPeaV9k0Y70nsEkRPRQ1jM302JyjGYFuM6/FzbXU5FITGLChtrFJpBn/Q4yJIA==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "^14.0.0",
"@angular/core": "^14.0.0",
"@angular/router": "^14.0.0",
"rxjs": ">=6.0.0",
"typescript": ">=3.8.0"
}
},
"node_modules/ngx-ui-tour-ng-bootstrap": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/ngx-ui-tour-ng-bootstrap/-/ngx-ui-tour-ng-bootstrap-11.0.0.tgz",
"integrity": "sha512-oF5ySZEiO4ib/RfYno81V2UBaX7EMzxSPwfCdedDr38e5BEPTOrcCufOaiOfuQzQftg+7CC/BbFY8Df9kmeL1A==",
"dependencies": {
"ngx-ui-tour-core": "9.0.0",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "^14.0.0",
"@angular/core": "^14.0.0",
"@ng-bootstrap/ng-bootstrap": "^13.0.0",
"typescript": ">=3.8.0"
}
},
"node_modules/nice-napi": { "node_modules/nice-napi": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@ -27156,6 +27187,23 @@
"tslib": "^2.3.0" "tslib": "^2.3.0"
} }
}, },
"ngx-ui-tour-core": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-ui-tour-core/-/ngx-ui-tour-core-9.0.0.tgz",
"integrity": "sha512-3HvrprosDaZm9jHi/w+oA8N9bPeaV9k0Y70nsEkRPRQ1jM302JyjGYFuM6/FzbXU5FITGLChtrFJpBn/Q4yJIA==",
"requires": {
"tslib": "^2.0.0"
}
},
"ngx-ui-tour-ng-bootstrap": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/ngx-ui-tour-ng-bootstrap/-/ngx-ui-tour-ng-bootstrap-11.0.0.tgz",
"integrity": "sha512-oF5ySZEiO4ib/RfYno81V2UBaX7EMzxSPwfCdedDr38e5BEPTOrcCufOaiOfuQzQftg+7CC/BbFY8Df9kmeL1A==",
"requires": {
"ngx-ui-tour-core": "9.0.0",
"tslib": "^2.0.0"
}
},
"nice-napi": { "nice-napi": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",

View File

@ -31,6 +31,7 @@
"ngx-color": "^8.0.3", "ngx-color": "^8.0.3",
"ngx-cookie-service": "^14.0.1", "ngx-cookie-service": "^14.0.1",
"ngx-file-drop": "^14.0.1", "ngx-file-drop": "^14.0.1",
"ngx-ui-tour-ng-bootstrap": "^11.0.0",
"rxjs": "~7.5.7", "rxjs": "~7.5.7",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"uuid": "^9.0.0", "uuid": "^9.0.0",

View File

@ -11,3 +11,27 @@
</div> </div>
</ng-template> </ng-template>
</ngx-file-drop> </ngx-file-drop>
<tour-step-template>
<ng-template #tourStep let-step="step">
<p class="tour-step-content" [innerHTML]="step?.content"></p>
<div class="d-flex justify-content-between align-items-end">
<span class="badge bg-secondary">{{ tourService.steps?.indexOf(step) + 1 }}/{{ tourService.steps?.length }}</span>
<div class="tour-step-navigation btn-toolbar" role="toolbar" aria-label="Controls">
<div class="btn-group me-2" role="group" aria-label="Dismiss">
<button class="btn btn-outline-danger btn-sm" (click)="tourService.end()">
{{ step?.endBtnTitle }}
</button>
</div>
<div class="btn-group align-self-end" role="group" aria-label="Previous / Next">
<button *ngIf="tourService.hasPrev(step)" class="btn btn-outline-primary btn-sm" (click)="tourService.prev()">
« {{ step?.prevBtnTitle }}
</button>
<button *ngIf="tourService.hasNext(step)" class="btn btn-outline-primary btn-sm" (click)="tourService.next()">
{{ step?.nextBtnTitle }} »
</button>
</div>
</div>
</div>
</ng-template>
</tour-step-template>

View File

@ -1,6 +1,6 @@
import { SettingsService } from './services/settings.service' import { SettingsService } from './services/settings.service'
import { SETTINGS_KEYS } from './data/paperless-uisettings' import { SETTINGS_KEYS } from './data/paperless-uisettings'
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component, OnDestroy, OnInit, Renderer2 } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { ConsumerStatusService } from './services/consumer-status.service' import { ConsumerStatusService } from './services/consumer-status.service'
@ -8,6 +8,7 @@ import { ToastService } from './services/toast.service'
import { NgxFileDropEntry } from 'ngx-file-drop' import { NgxFileDropEntry } from 'ngx-file-drop'
import { UploadDocumentsService } from './services/upload-documents.service' import { UploadDocumentsService } from './services/upload-documents.service'
import { TasksService } from './services/tasks.service' import { TasksService } from './services/tasks.service'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -29,7 +30,9 @@ export class AppComponent implements OnInit, OnDestroy {
private toastService: ToastService, private toastService: ToastService,
private router: Router, private router: Router,
private uploadDocumentsService: UploadDocumentsService, private uploadDocumentsService: UploadDocumentsService,
private tasksService: TasksService private tasksService: TasksService,
public tourService: TourService,
private renderer: Renderer2
) { ) {
let anyWindow = window as any let anyWindow = window as any
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js' anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
@ -112,6 +115,40 @@ export class AppComponent implements OnInit, OnDestroy {
}) })
} }
}) })
this.tourService.initialize([
{
anchorId: 'tour.intro',
title: `Hello ${this.settings.displayName}, welcome to Paperless-ngx!`,
content:
"Here's a tutorial to guide you around some of Paperless-ngx's most useful features.",
route: '/dashboard',
},
{
anchorId: 'tour.dashboard',
title: 'The Dashboard',
content: "Here's some dashboard info",
route: '/dashboard',
},
{
anchorId: 'tour.documents',
title: 'Documents List',
content: "Here's some dashboard info",
route: '/documents',
delayAfterNavigation: 500,
},
])
this.tourService.start$.subscribe(() => {
this.renderer.addClass(document.body, 'tour-active')
})
this.tourService.end$.subscribe(() => {
// animation time
setTimeout(() => {
this.renderer.removeClass(document.body, 'tour-active')
}, 500)
})
} }
public get dragDropEnabled(): boolean { public get dragDropEnabled(): boolean {

View File

@ -69,6 +69,11 @@ import { ColorComponent } from './components/common/input/color/color.component'
import { DocumentAsnComponent } from './components/document-asn/document-asn.component' import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import { DocumentCommentsComponent } from './components/document-comments/document-comments.component' import { DocumentCommentsComponent } from './components/document-comments/document-comments.component'
import { DirtyDocGuard } from './guards/dirty-doc.guard' import { DirtyDocGuard } from './guards/dirty-doc.guard'
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
import { StoragePathEditDialogComponent } from './components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { SettingsService } from './services/settings.service'
import { TasksComponent } from './components/manage/tasks/tasks.component'
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
import localeBe from '@angular/common/locales/be' import localeBe from '@angular/common/locales/be'
import localeCs from '@angular/common/locales/cs' import localeCs from '@angular/common/locales/cs'
@ -89,10 +94,6 @@ import localeSr from '@angular/common/locales/sr'
import localeSv from '@angular/common/locales/sv' import localeSv from '@angular/common/locales/sv'
import localeTr from '@angular/common/locales/tr' import localeTr from '@angular/common/locales/tr'
import localeZh from '@angular/common/locales/zh' import localeZh from '@angular/common/locales/zh'
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
import { StoragePathEditDialogComponent } from './components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { SettingsService } from './services/settings.service'
import { TasksComponent } from './components/manage/tasks/tasks.component'
registerLocaleData(localeBe) registerLocaleData(localeBe)
registerLocaleData(localeCs) registerLocaleData(localeCs)
@ -188,6 +189,7 @@ function initializeApp(settings: SettingsService) {
PdfViewerModule, PdfViewerModule,
NgSelectModule, NgSelectModule,
ColorSliderModule, ColorSliderModule,
TourNgBootstrapModule.forRoot(),
], ],
providers: [ providers: [
{ {

View File

@ -4,8 +4,8 @@
(click)="isMenuCollapsed = !isMenuCollapsed"> (click)="isMenuCollapsed = !isMenuCollapsed">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<a class="navbar-brand me-0 px-3 py-3 order-sm-0" [ngClass]="slimSidebarEnabled ? 'slim' : 'col-auto col-md-3 col-lg-2'" routerLink="/dashboard"> <a class="navbar-brand col-auto col-md-3 col-lg-2 me-0 px-3 py-3 order-sm-0" [ngClass]="slimSidebarEnabled ? 'slim' : 'col-auto col-md-3 col-lg-2'" routerLink="/dashboard" tourAnchor="tour.intro">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" class="me-2" fill="currentColor">
<path d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z" transform="translate(0 0)"/> <path d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z" transform="translate(0 0)"/>
</svg> </svg>
<span class="ms-2" [class.visually-hidden]="slimSidebarEnabled" i18n="app title">Paperless-ngx</span> <span class="ms-2" [class.visually-hidden]="slimSidebarEnabled" i18n="app title">Paperless-ngx</span>

View File

@ -52,7 +52,7 @@ export class AppFrameComponent implements OnInit, ComponentCanDeactivate {
} }
this.tasksService.reload() this.tasksService.reload()
} }
versionString = `${environment.appTitle} ${environment.version}` versionString = `${environment.appTitle} ${environment.version}`
appRemoteVersion appRemoteVersion

View File

@ -19,14 +19,14 @@
</svg> </svg>
</app-page-header> </app-page-header>
<div class='row'> <div class='row' tourAnchor="tour.dashboard">
<div class="col-lg-8"> <div class="col-lg-8">
<ng-container *ngIf="savedViewService.loading"> <ng-container *ngIf="savedViewService.loading">
<div class="spinner-border spinner-border-sm me-2" role="status"></div> <div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container> <ng-container i18n>Loading...</ng-container>
</ng-container> </ng-container>
<app-welcome-widget *ngIf="!savedViewService.loading && savedViewService.dashboardViews.length == 0"></app-welcome-widget> <app-welcome-widget *ngIf="settingsService.offerTour()"></app-welcome-widget>
<ng-container *ngFor="let v of savedViewService.dashboardViews"> <ng-container *ngFor="let v of savedViewService.dashboardViews">
<app-saved-view-widget [savedView]="v"></app-saved-view-widget> <app-saved-view-widget [savedView]="v"></app-saved-view-widget>

View File

@ -1,5 +1,4 @@
import { Component, OnInit } from '@angular/core' import { Component } from '@angular/core'
import { Meta } from '@angular/platform-browser'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
@ -16,9 +15,9 @@ export class DashboardComponent {
get subtitle() { get subtitle() {
if (this.settingsService.displayName) { if (this.settingsService.displayName) {
return $localize`Hello ${this.settingsService.displayName}, welcome to Paperless-ngx!` return $localize`Hello ${this.settingsService.displayName}, welcome to Paperless-ngx`
} else { } else {
return $localize`Welcome to Paperless-ngx!` return $localize`Welcome to Paperless-ngx`
} }
} }
} }

View File

@ -1,16 +1,10 @@
<app-widget-frame title="First steps" i18n-title> <ngb-alert type="primary" [dismissible]="false">
<!-- [dismissible]="isFinished(status)" (closed)="dismiss(status)" -->
<ng-container content> <h4 class="alert-heading" i18n>Paperless-ngx is running!</h4>
<img src="assets/save-filter.png" class="float-right"> <p i18n>You're ready to start uploading documents! Feel free to explore the various features or start a quick tour using the button below.</p>
<p i18n>Paperless is running! :)</p> <p i18n>More detail on how to use and configure paperless is always available in the <a href="https://paperless-ngx.readthedocs.io" target="_blank">documentation</a>.</p>
<p i18n>You can start uploading documents by dropping them in the file upload box to the right or by dropping them in the configured consumption folder and they'll start showing up in the documents list. <hr>
After you've added some metadata to your documents, use the filtering mechanisms of paperless to create custom views (such as 'Recently added', 'Tagged TODO') and they will appear on the dashboard instead of this message.</p> <div class="btn-toolbar" role="toolbar" aria-label="Controls">
<p i18n>Paperless offers some more features that try to make your life easier:</p> <button class="btn btn-primary ms-auto" (click)="tourService.start()"><ng-container i18n>Start Tour</ng-container> &rarr;</button>
<ul> </div>
<li i18n>Once you've got a couple documents in paperless and added metadata to them, paperless can assign that metadata to new documents automatically.</li> </ngb-alert>
<li i18n>You can configure paperless to read your mails and add documents from attached files.</li>
</ul>
<p i18n>Consult the documentation on how to use these features. The section on basic usage also has some information on how to use paperless in general.</p>
</ng-container>
</app-widget-frame>

View File

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
@Component({ @Component({
selector: 'app-welcome-widget', selector: 'app-welcome-widget',
@ -6,7 +7,7 @@ import { Component, OnInit } from '@angular/core'
styleUrls: ['./welcome-widget.component.scss'], styleUrls: ['./welcome-widget.component.scss'],
}) })
export class WelcomeWidgetComponent implements OnInit { export class WelcomeWidgetComponent implements OnInit {
constructor() {} constructor(public readonly tourService: TourService) {}
ngOnInit(): void {} ngOnInit(): void {}
} }

View File

@ -75,10 +75,12 @@
</app-page-header> </app-page-header>
<div class="row sticky-top pt-3 pt-sm-4 pb-2 pb-lg-4 bg-body"> <div class="row sticky-top pt-3 pt-sm-4 pb-2 pb-lg-4 bg-body">
<div tourAnchor="tour.documents"></div>
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" #filterEditor></app-filter-editor> <app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" #filterEditor></app-filter-editor>
<app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor> <app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor>
</div> </div>
<ng-template #pagination> <ng-template #pagination>
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<p> <p>
@ -103,7 +105,6 @@
</ng-container> </ng-container>
<ng-template #documentListNoError> <ng-template #documentListNoError>
<div *ngIf="displayMode == 'largeCards'"> <div *ngIf="displayMode == 'largeCards'">
<app-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" *ngFor="let d of list.documents; trackBy: trackByDocumentId" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickStoragePath)="clickStoragePath($event)" (clickMoreLike)="clickMoreLike(d.id)"> <app-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" *ngFor="let d of list.documents; trackBy: trackByDocumentId" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickStoragePath)="clickStoragePath($event)" (clickMoreLike)="clickMoreLike(d.id)">
</app-document-card-large> </app-document-card-large>

View File

@ -446,4 +446,9 @@ export class SettingsService {
get updateCheckingIsSet(): boolean { get updateCheckingIsSet(): boolean {
return this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED) return this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)
} }
offerTour(): boolean {
return true
// !savedViewService.loading && savedViewService.dashboardViews.length == 0
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -479,6 +479,12 @@ table.table {
user-select: none !important; user-select: none !important;
} }
.alert-primary {
--bs-alert-color: var(--bs-primary);
--bs-alert-bg: var(--pngx-primary-faded);
--bs-alert-border-color: var(--bs-primary);
}
.alert-danger { .alert-danger {
color: var(--bs-body-color); color: var(--bs-body-color);
background-color: var(--bs-danger); background-color: var(--bs-danger);
@ -496,14 +502,23 @@ table.table {
} }
.popover { .popover {
background-color: var(--pngx-bg-alt);
.popover-header {
background-color: var(--pngx-bg-alt);
}
.popover-header, .popover-header,
.popover-body { .popover-body {
background-color: var(--pngx-bg-alt);
border-color: var(--bs-border-color); border-color: var(--bs-border-color);
color: var(--bs-body-color); color: var(--bs-body-color);
} }
} }
// Tour popovers
.tour-active .popover {
min-width: 400px;
margin: 0 .25rem .25rem !important;
}
// fix popover carat colors // fix popover carat colors
.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="left"] { .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="left"] {
border-left-color: var(--pngx-bg-alt); border-left-color: var(--pngx-bg-alt);

View File

@ -10,11 +10,12 @@ body {
--bs-primary: hsl(var(--pngx-primary), var(--pngx-primary-lightness)); --bs-primary: hsl(var(--pngx-primary), var(--pngx-primary-lightness));
--bs-border-color: var(--bs-gray-400); --bs-border-color: var(--bs-gray-400);
--pngx-primary-faded: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 72%)); --pngx-primary-faded: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 76%));
--pngx-primary-lighten-30: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 30%)); --pngx-primary-lighten-30: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 30%));
--pngx-primary-darken-5: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 5%)); --pngx-primary-darken-5: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 5%));
--pngx-primary-darken-15: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 15%)); --pngx-primary-darken-15: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 15%));
--pngx-primary-darken-18: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 18%)); --pngx-primary-darken-18: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 18%));
--pngx-primary-darken-27: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 27%));
--pngx-bg-alt: #fff; --pngx-bg-alt: #fff;
--pngx-bg-darker: var(--bs-gray-100); --pngx-bg-darker: var(--bs-gray-100);
--pngx-focus-alpha: 0.3; --pngx-focus-alpha: 0.3;
@ -177,6 +178,12 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
color: var(--bs-body-color); color: var(--bs-body-color);
} }
.alert-primary {
--bs-alert-color: var(--pngx-primary-text-contrast);
--bs-alert-bg: var(--pngx-primary-darken-27);
--bs-alert-border-color: var(--pngx-bg-darker);
}
.table-striped > tbody > tr:nth-of-type(odd) > * { .table-striped > tbody > tr:nth-of-type(odd) > * {
color: var(--pngx-body-color-accent); color: var(--pngx-body-color-accent);
} }