mirror of
https://github.com/bitwarden/browser
synced 2026-01-01 08:03:20 +00:00
Dark Theme (#1017)
* Stylesheets * Theme Configuration * Options Area * swal2 style * Icon styling * Fix theme not saving * Update English * Update messages.json * dropdown and login logo * btn-link and totp fix * Organisation Styling * Update webauthn-fallback.ts * Fix contrast issues * Add Paypal Container and Loading svg file * Password Generator contrast fix * Dark Mode Fix buttons and foreground * Fix button hover * Fix Styles after rebase * Add hover on nav dropdown-item * Disable Theme Preview * Options Fix for Default Theme Changes * Updated Colour Scheme * Toast fix * Button and Text Styling * Options Update and Messages Fix * Added Search Icon and Fixed Callout styling * Add theme styling to Stripe * Refactor logic for setting color * Reorder logic to avoid race condition * PayPal Loading and Misc Fix * text-state bug fix * Badge Colour Fix * Remove PayPal Tagline The colour cannot be styled so it's not visible on a dark theme * Adding the Styling from #1131 * Update to New Design * Form and Nav restyle * Modal Opacity and Callout * Nav Colours * Missing Borders * Light theme fix * Improved border for listgroup * Change Org Nav Colour * Save theme to localStorage for persistence * Undo change to Wired image * !Important removal and tweaks * Fix regression with navbar * Light theme by default * Refactor to use getEffectiveTheme * Refactor theme constants to use enum * Set theme in index.html before app loads * Use scss selector to set logo image * Export Sass to TS * Update jslib Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<div class="mt-5 d-flex justify-content-center" *ngIf="loading">
|
||||
<div>
|
||||
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||
<img class="mb-4 logo" alt="Bitwarden">
|
||||
<p class="text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" class="container" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<img src="../../images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden">
|
||||
<img class="mb-2 logo" alt="Bitwarden">
|
||||
<p class="lead text-center mx-4 mb-4">{{'loginOrCreateNewAccount' | i18n}}</p>
|
||||
<div class="card d-block">
|
||||
<div class="card-body">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<form #form (ngSubmit)="submit()" class="container" [appApiAction]="initiateSsoFormPromise" ngNativeValidate>
|
||||
<div class="row justify-content-md-center mt-5">
|
||||
<div class="col-5">
|
||||
<img src="../../images/logo-dark@2x.png" class="logo mb-2" alt="Bitwarden">
|
||||
<img class="logo mb-2" alt="Bitwarden">
|
||||
<div class="card d-block mt-4">
|
||||
<div class="card-body" *ngIf="loggingIn">
|
||||
<i class="fa fa-spinner fa-spin" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="mt-5 d-flex justify-content-center">
|
||||
<div>
|
||||
<img src="../../images/logo-dark@2x.png" class="mb-4 logo" alt="Bitwarden">
|
||||
<img class="mb-4 logo" alt="Bitwarden">
|
||||
<p class="text-center">
|
||||
<i class="fa fa-spinner fa-spin fa-2x text-muted" title="{{'loading' | i18n}}" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{'loading' | i18n}}</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<nav class="navbar navbar-expand navbar-dark bg-primary" [ngClass]="{'bg-secondary-alt': selfHosted}">
|
||||
<nav class="navbar navbar-expand navbar-dark" [ngClass]="{'bg-secondary-alt': selfHosted}">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" routerLink="/" appA11yTitle="{{'pageTitle' | i18n : 'Bitwarden'}}">
|
||||
<i class="fa fa-shield" aria-hidden="true"></i>
|
||||
|
||||
@@ -91,12 +91,14 @@ import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions
|
||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service';
|
||||
import { ModalService } from './modal.service';
|
||||
|
||||
import { ThemeType } from 'jslib-common/enums/themeType';
|
||||
|
||||
const i18nService = new I18nService(window.navigator.language, 'locales');
|
||||
const stateService = new StateService();
|
||||
const broadcasterService = new BroadcasterService();
|
||||
const messagingService = new BroadcasterMessagingService(broadcasterService);
|
||||
const consoleLogService = new ConsoleLogService(false);
|
||||
const platformUtilsService = new WebPlatformUtilsService(i18nService, messagingService, consoleLogService);
|
||||
const platformUtilsService = new WebPlatformUtilsService(i18nService, messagingService, consoleLogService, () => storageService);
|
||||
const storageService: StorageServiceAbstraction = new HtmlStorageService(platformUtilsService);
|
||||
const secureStorageService: StorageServiceAbstraction = new MemoryStorageService();
|
||||
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new WebCryptoFunctionService(window,
|
||||
@@ -161,11 +163,16 @@ export function initFactory(): Function {
|
||||
authService.init();
|
||||
const htmlEl = window.document.documentElement;
|
||||
htmlEl.classList.add('locale_' + i18nService.translationLocale);
|
||||
let theme = await storageService.get<string>(ConstantsService.themeKey);
|
||||
if (theme == null) {
|
||||
theme = 'light';
|
||||
}
|
||||
htmlEl.classList.add('theme_' + theme);
|
||||
|
||||
// Initial theme is set in index.html which must be updated if there are any changes to theming logic
|
||||
platformUtilsService.onDefaultSystemThemeChange(async sysTheme => {
|
||||
const bwTheme = await storageService.get<ThemeType>(ConstantsService.themeKey);
|
||||
if (bwTheme === ThemeType.System) {
|
||||
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark);
|
||||
htmlEl.classList.add('theme_' + sysTheme);
|
||||
}
|
||||
});
|
||||
|
||||
stateService.save(ConstantsService.disableFaviconKey,
|
||||
await storageService.get<boolean>(ConstantsService.disableFaviconKey));
|
||||
stateService.save('enableGravatars', await storageService.get<boolean>('enableGravatars'));
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
</div>
|
||||
<small class="form-text text-muted">{{'enableGravatarsDesc' | i18n}}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="enableFullWidth" name="enableFullWidth"
|
||||
[(ngModel)]="enableFullWidth">
|
||||
@@ -82,6 +82,17 @@
|
||||
</div>
|
||||
<small class="form-text text-muted">{{'enableFullWidthDesc' | i18n}}</small>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="theme">{{'theme' | i18n}}</label>
|
||||
<select id="theme" name="theme" [(ngModel)]="theme" class="form-control">
|
||||
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{o.name}}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{'themeDesc' | i18n}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{'save' | i18n}}
|
||||
</button>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.serv
|
||||
|
||||
import { ConstantsService } from 'jslib-common/services/constants.service';
|
||||
|
||||
import { ThemeType } from 'jslib-common/enums/themeType';
|
||||
import { Utils } from 'jslib-common/misc/utils';
|
||||
|
||||
@Component({
|
||||
@@ -26,13 +27,16 @@ export class OptionsComponent implements OnInit {
|
||||
disableIcons: boolean;
|
||||
enableGravatars: boolean;
|
||||
enableFullWidth: boolean;
|
||||
theme: string = null;
|
||||
locale: string;
|
||||
vaultTimeouts: { name: string; value: number; }[];
|
||||
localeOptions: any[];
|
||||
themeOptions: any[];
|
||||
|
||||
vaultTimeout: FormControl = new FormControl(null);
|
||||
|
||||
private startingLocale: string;
|
||||
private startingTheme: string;
|
||||
|
||||
constructor(private storageService: StorageService, private stateService: StateService,
|
||||
private i18nService: I18nService, private toasterService: ToasterService,
|
||||
@@ -62,6 +66,11 @@ export class OptionsComponent implements OnInit {
|
||||
localeOptions.sort(Utils.getSortFunction(i18nService, 'name'));
|
||||
localeOptions.splice(0, 0, { name: i18nService.t('default'), value: null });
|
||||
this.localeOptions = localeOptions;
|
||||
this.themeOptions = [
|
||||
{ name: i18nService.t('themeLight'), value: null },
|
||||
{ name: i18nService.t('themeDark'), value: ThemeType.Dark },
|
||||
{ name: i18nService.t('themeSystem'), value: ThemeType.System },
|
||||
];
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -71,6 +80,7 @@ export class OptionsComponent implements OnInit {
|
||||
this.enableGravatars = await this.storageService.get<boolean>('enableGravatars');
|
||||
this.enableFullWidth = await this.storageService.get<boolean>('enableFullWidth');
|
||||
this.locale = this.startingLocale = await this.storageService.get<string>(ConstantsService.localeKey);
|
||||
this.theme = this.startingTheme = await this.storageService.get<ThemeType>(ConstantsService.themeKey);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
@@ -86,6 +96,14 @@ export class OptionsComponent implements OnInit {
|
||||
await this.stateService.save('enableGravatars', this.enableGravatars);
|
||||
await this.storageService.save('enableFullWidth', this.enableFullWidth);
|
||||
this.messagingService.send('setFullWidth');
|
||||
if (this.theme !== this.startingTheme) {
|
||||
await this.storageService.save(ConstantsService.themeKey, this.theme);
|
||||
this.startingTheme = this.theme;
|
||||
const effectiveTheme = await this.platformUtilsService.getEffectiveTheme();
|
||||
const htmlEl = window.document.documentElement;
|
||||
htmlEl.classList.remove('theme_' + ThemeType.Light, 'theme_' + ThemeType.Dark);
|
||||
htmlEl.classList.add('theme_' + effectiveTheme);
|
||||
}
|
||||
await this.storageService.save(ConstantsService.localeKey, this.locale);
|
||||
if (this.locale !== this.startingLocale) {
|
||||
window.location.reload();
|
||||
|
||||
@@ -9,24 +9,14 @@ import { PaymentMethodType } from 'jslib-common/enums/paymentMethodType';
|
||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||
import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service';
|
||||
|
||||
const StripeElementStyle = {
|
||||
base: {
|
||||
color: '#333333',
|
||||
fontFamily: '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' +
|
||||
'"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
||||
fontSize: '14px',
|
||||
fontSmoothing: 'antialiased',
|
||||
},
|
||||
invalid: {
|
||||
color: '#333333',
|
||||
},
|
||||
};
|
||||
import { ThemeType } from 'jslib-common/enums/themeType';
|
||||
|
||||
const StripeElementClasses = {
|
||||
focus: 'is-focused',
|
||||
empty: 'is-empty',
|
||||
invalid: 'is-invalid',
|
||||
};
|
||||
import ThemeVariables from 'src/scss/export.module.scss';
|
||||
|
||||
const lightInputColor = ThemeVariables.lightInputColor;
|
||||
const lightInputPlaceholderColor = ThemeVariables.lightInputPlaceholderColor;
|
||||
const darkInputColor = ThemeVariables.darkInputColor;
|
||||
const darkInputPlaceholderColor = ThemeVariables.darkInputPlaceholderColor;
|
||||
|
||||
@Component({
|
||||
selector: 'app-payment',
|
||||
@@ -59,6 +49,8 @@ export class PaymentComponent implements OnInit {
|
||||
private stripeCardNumberElement: any = null;
|
||||
private stripeCardExpiryElement: any = null;
|
||||
private stripeCardCvcElement: any = null;
|
||||
private StripeElementStyle: any;
|
||||
private StripeElementClasses: any;
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService, private apiService: ApiService) {
|
||||
this.stripeScript = window.document.createElement('script');
|
||||
@@ -72,14 +64,36 @@ export class PaymentComponent implements OnInit {
|
||||
this.btScript = window.document.createElement('script');
|
||||
this.btScript.src = `scripts/dropin.js?cache=${process.env.CACHE_TAG}`;
|
||||
this.btScript.async = true;
|
||||
this.StripeElementStyle = {
|
||||
base: {
|
||||
color: null,
|
||||
fontFamily: '"Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' +
|
||||
'"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
||||
fontSize: '14px',
|
||||
fontSmoothing: 'antialiased',
|
||||
'::placeholder': {
|
||||
color: null,
|
||||
},
|
||||
},
|
||||
invalid: {
|
||||
color: null,
|
||||
|
||||
},
|
||||
};
|
||||
this.StripeElementClasses = {
|
||||
focus: 'is-focused',
|
||||
empty: 'is-empty',
|
||||
invalid: 'is-invalid',
|
||||
};
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
async ngOnInit() {
|
||||
if (!this.showOptions) {
|
||||
this.hidePaypal = this.method !== PaymentMethodType.PayPal;
|
||||
this.hideBank = this.method !== PaymentMethodType.BankAccount;
|
||||
this.hideCredit = this.method !== PaymentMethodType.Credit;
|
||||
}
|
||||
await this.setTheme();
|
||||
window.document.head.appendChild(this.stripeScript);
|
||||
if (!this.hidePaypal) {
|
||||
window.document.head.appendChild(this.btScript);
|
||||
@@ -133,6 +147,7 @@ export class PaymentComponent implements OnInit {
|
||||
size: 'medium',
|
||||
shape: 'pill',
|
||||
color: 'blue',
|
||||
tagline: 'false',
|
||||
},
|
||||
},
|
||||
}, (createErr: any, instance: any) => {
|
||||
@@ -216,21 +231,21 @@ export class PaymentComponent implements OnInit {
|
||||
if (this.showMethods && this.method === PaymentMethodType.Card) {
|
||||
if (this.stripeCardNumberElement == null) {
|
||||
this.stripeCardNumberElement = this.stripeElements.create('cardNumber', {
|
||||
style: StripeElementStyle,
|
||||
classes: StripeElementClasses,
|
||||
style: this.StripeElementStyle,
|
||||
classes: this.StripeElementClasses,
|
||||
placeholder: '',
|
||||
});
|
||||
}
|
||||
if (this.stripeCardExpiryElement == null) {
|
||||
this.stripeCardExpiryElement = this.stripeElements.create('cardExpiry', {
|
||||
style: StripeElementStyle,
|
||||
classes: StripeElementClasses,
|
||||
style: this.StripeElementStyle,
|
||||
classes: this.StripeElementClasses,
|
||||
});
|
||||
}
|
||||
if (this.stripeCardCvcElement == null) {
|
||||
this.stripeCardCvcElement = this.stripeElements.create('cardCvc', {
|
||||
style: StripeElementStyle,
|
||||
classes: StripeElementClasses,
|
||||
style: this.StripeElementStyle,
|
||||
classes: this.StripeElementClasses,
|
||||
placeholder: '',
|
||||
});
|
||||
}
|
||||
@@ -240,4 +255,17 @@ export class PaymentComponent implements OnInit {
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
private async setTheme() {
|
||||
const theme = await this.platformUtilsService.getEffectiveTheme();
|
||||
if (theme === ThemeType.Dark) {
|
||||
this.StripeElementStyle.base.color = darkInputColor;
|
||||
this.StripeElementStyle.base['::placeholder'].color = darkInputPlaceholderColor;
|
||||
this.StripeElementStyle.invalid.color = darkInputColor;
|
||||
} else {
|
||||
this.StripeElementStyle.base.color = lightInputColor;
|
||||
this.StripeElementStyle.base['::placeholder'].color = lightInputPlaceholderColor;
|
||||
this.StripeElementStyle.invalid.color = lightInputColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
</div>
|
||||
<div class="col-6 form-group totp d-flex align-items-end" [ngClass]="{'low': totpLow}">
|
||||
<div *ngIf="!cipher.login.totp || !totpCode">
|
||||
<img src="../../images/totp-countdown.png" title="{{'verificationCodeTotp' | i18n}}"
|
||||
<img src="../../images/totp-countdown.png" id="totpImage" title="{{'verificationCodeTotp' | i18n}}"
|
||||
class="ml-2">
|
||||
<a href="#" appStopClick class="badge badge-primary ml-3" (click)="premiumRequired()"
|
||||
*ngIf="!organization && !cipher.organizationId && !canAccessPremium">
|
||||
|
||||
Reference in New Issue
Block a user