From 0c02cfea2fff39b6fda66560839658a23487d0b2 Mon Sep 17 00:00:00 2001 From: Danny Murphy Date: Wed, 29 Sep 2021 23:06:20 +0100 Subject: [PATCH] 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 --- .../manage/accept-provider.component.html | 2 +- .../setup/setup-provider.component.html | 2 +- jslib | 2 +- .../accounts/accept-emergency.component.html | 2 +- src/app/accounts/login.component.html | 2 +- src/app/accounts/sso.component.html | 2 +- .../verify-email-token.component.html | 2 +- src/app/layouts/navbar.component.html | 2 +- src/app/services/services.module.ts | 19 +- src/app/settings/options.component.html | 13 +- src/app/settings/options.component.ts | 18 + src/app/settings/payment.component.ts | 76 +- src/app/vault/add-edit.component.html | 2 +- src/images/loading-white.svg | 6 + src/images/logo-white@2x.png | Bin 0 -> 11690 bytes src/images/totp-countdown.png | Bin 1636 -> 2208 bytes src/index.html | 19 +- src/locales/en/messages.json | 15 + src/scss/base.scss | 246 +++++ src/scss/buttons.scss | 224 +++++ src/scss/callouts.scss | 83 ++ src/scss/cards.scss | 93 ++ src/scss/export.module.scss | 8 + src/scss/export.module.scss.d.ts | 9 + src/scss/forms.scss | 180 ++++ src/scss/modals.scss | 147 +++ src/scss/navigation.scss | 109 +++ src/scss/pages.scss | 283 ++++++ src/scss/plugins.scss | 204 +++-- src/scss/register-layout.scss | 63 -- src/scss/styles.scss | 865 +----------------- src/scss/tables.scss | 123 +++ src/scss/toasts.scss | 110 +++ src/scss/variables.scss | 349 +++++++ src/services/htmlStorage.service.ts | 2 +- src/services/webPlatformUtils.service.ts | 28 +- webpack.config.js | 2 +- 37 files changed, 2259 insertions(+), 1053 deletions(-) create mode 100644 src/images/loading-white.svg create mode 100644 src/images/logo-white@2x.png create mode 100644 src/scss/base.scss create mode 100644 src/scss/buttons.scss create mode 100644 src/scss/callouts.scss create mode 100644 src/scss/cards.scss create mode 100644 src/scss/export.module.scss create mode 100644 src/scss/export.module.scss.d.ts create mode 100644 src/scss/forms.scss create mode 100644 src/scss/modals.scss create mode 100644 src/scss/navigation.scss create mode 100644 src/scss/pages.scss delete mode 100644 src/scss/register-layout.scss create mode 100644 src/scss/tables.scss create mode 100644 src/scss/toasts.scss create mode 100644 src/scss/variables.scss diff --git a/bitwarden_license/src/app/providers/manage/accept-provider.component.html b/bitwarden_license/src/app/providers/manage/accept-provider.component.html index e9a6014eaa2..9ed5795059f 100644 --- a/bitwarden_license/src/app/providers/manage/accept-provider.component.html +++ b/bitwarden_license/src/app/providers/manage/accept-provider.component.html @@ -1,6 +1,6 @@
- +

{{'loading' | i18n}} diff --git a/bitwarden_license/src/app/providers/setup/setup-provider.component.html b/bitwarden_license/src/app/providers/setup/setup-provider.component.html index 64bc4c361b5..7bc73e49d24 100644 --- a/bitwarden_license/src/app/providers/setup/setup-provider.component.html +++ b/bitwarden_license/src/app/providers/setup/setup-provider.component.html @@ -1,6 +1,6 @@

- +

{{'loading' | i18n}} diff --git a/jslib b/jslib index 206ef610d06..ce71c0c0bd6 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit 206ef610d068c83039f4f232a12204baf0d9035b +Subproject commit ce71c0c0bd6667573e0e611222dc415770ba3909 diff --git a/src/app/accounts/accept-emergency.component.html b/src/app/accounts/accept-emergency.component.html index 3f12f6e021a..dc8d295ff34 100644 --- a/src/app/accounts/accept-emergency.component.html +++ b/src/app/accounts/accept-emergency.component.html @@ -1,6 +1,6 @@

- +

{{'loading' | i18n}} diff --git a/src/app/accounts/login.component.html b/src/app/accounts/login.component.html index e2078d0b19c..26120564c68 100644 --- a/src/app/accounts/login.component.html +++ b/src/app/accounts/login.component.html @@ -1,7 +1,7 @@

- +

{{'loginOrCreateNewAccount' | i18n}}

diff --git a/src/app/accounts/sso.component.html b/src/app/accounts/sso.component.html index 1a33b79cd05..eb69f1e4ea4 100644 --- a/src/app/accounts/sso.component.html +++ b/src/app/accounts/sso.component.html @@ -1,7 +1,7 @@
- +
diff --git a/src/app/accounts/verify-email-token.component.html b/src/app/accounts/verify-email-token.component.html index 03f68183b9a..de6d6e755a4 100644 --- a/src/app/accounts/verify-email-token.component.html +++ b/src/app/accounts/verify-email-token.component.html @@ -1,6 +1,6 @@
- +

{{'loading' | i18n}} diff --git a/src/app/layouts/navbar.component.html b/src/app/layouts/navbar.component.html index bdc5df36f35..48e8ffd8854 100644 --- a/src/app/layouts/navbar.component.html +++ b/src/app/layouts/navbar.component.html @@ -1,4 +1,4 @@ -

-
+
@@ -82,6 +82,17 @@
{{'enableFullWidthDesc' | i18n}}
+
+
+
+ + + {{'themeDesc' | i18n}} +
+
+
diff --git a/src/app/settings/options.component.ts b/src/app/settings/options.component.ts index aad583f5a17..b218cf81a66 100644 --- a/src/app/settings/options.component.ts +++ b/src/app/settings/options.component.ts @@ -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('enableGravatars'); this.enableFullWidth = await this.storageService.get('enableFullWidth'); this.locale = this.startingLocale = await this.storageService.get(ConstantsService.localeKey); + this.theme = this.startingTheme = await this.storageService.get(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(); diff --git a/src/app/settings/payment.component.ts b/src/app/settings/payment.component.ts index 4a51f500795..0c8b11a5203 100644 --- a/src/app/settings/payment.component.ts +++ b/src/app/settings/payment.component.ts @@ -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; + } + } } diff --git a/src/app/vault/add-edit.component.html b/src/app/vault/add-edit.component.html index 160c7148651..d6e7fbf831d 100644 --- a/src/app/vault/add-edit.component.html +++ b/src/app/vault/add-edit.component.html @@ -103,7 +103,7 @@
- diff --git a/src/images/loading-white.svg b/src/images/loading-white.svg new file mode 100644 index 00000000000..30239140069 --- /dev/null +++ b/src/images/loading-white.svg @@ -0,0 +1,6 @@ + + + Loading... + + diff --git a/src/images/logo-white@2x.png b/src/images/logo-white@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b1f01b750d5b6a7bf67b9e286b65bdf2c5e489 GIT binary patch literal 11690 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

Xk6d;7(HdT(O)J2*B-Jgy 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 delta 1630 zcmV-k2BG<&5#$Vz8Gix*003q#@#p{m00d`2O+f$vugEtAis=9V02y>eSaefwW^{L9 za%BKPWN%_+AW3auXJt}lVPtu6$z?nM00s6*L_t(|+U#0QZ`xQCeIa~>#37+++-B&Y z8M#qVRW>M#5$XtOc3Jv|vdn50Y$8>vydev!%9@G^O@cxR#D9tT$k&|bXT!rb#t>MI zuOh|9e$UVM+;i@|wpX5?pUGr9Z+DwF{f<7ke|&1|_xNQi5s9pZl8NZfW^8Y3jRdx6 zTXBU10H}>y^-{xz$6#P1y%8jF0~>)Wmk3F%c~|+j&A>>mMfSE6J3?$dwwj2AOJB}L z=V9E62~7UC)PLS~z?j8Y*tQQa)P{x)z>wWbzTS*YM$-fa;C)t`k|^YU-3lDKV3sJ~ zTc8Lw5cQdz^+{upRGK$EAg2!=`QI@F0|TEf3=+lx{6L#xn13PX|C!x2QL)s|yG{#iIwb1l zEu4sa?vX#dOW{A>@0fM4k?k>I;Mtc!Vn`P1tGQZ@<%dT0^KW-scxD!w{|g03P8n-fYU0?6-)^r_NPo=UW#Eq?je0}O=dGp2#k5G2j*czo zYQ8C>Q(JWvkvhD3DlHLJ(|RNopT$dKQ<79pmPg=aC8b<%^fKHX_G*gKkcvf8F3$nU zpv*t}@W>^4pSFAJu~nD&t-}5JG=B?qRZ6EU=Mm*wiyvTBYm_DrGZbA73V~^Qr$NMI z!helTh)*fYhX94ZI;tUiS<&v-T&DC`nFZkfqMPAC{n`e};DP05H!Hy|)N<*h)-#w_p5P?v4q}A^jp+Tn*iep3m6oYEvpdT^r426%G9Mfe z2o;RV#1{brMC_eR|B@1_A@!_sJ^2~eG=JkFF_9E!mkOs*xky7Hk4uV?H4AP&qB=G1 zbD9=JD)=SU$ZPVfzH&}EMj(aaHBv^#sN!M(vBUE*Ts=LjNbhpn21^oADp`8}!QXgJ zEWn>}sjxakDqMW?IK?%U5=+#wT$I2UMwqDz14fmMY%I%1c>=yL!Yo9z4AudFVSlk; zCXw8d9^l1a6+R|3iz^`}S$F+MKSKp!q^<_D#~(-D-4BH;5)SzUgvxP}p3Gk3U|8`Z>J1bi6fFPiO(wx0cE(~s~Mg%VpEUHABM*n@p$ z?p3vucSg}$uB6Rdb2^J9VY`UaqPMJ!s&IBzHszZlu6e;M6YR|h&2~AjUVm-D-8&k( z$Z7}$($$SCQLvtr*ZPL4#g$l?&~NJw(K#Y!k|xd$)P%^X8CXVuJ;lyVtvo>S#2ucTggOp)>kIXpnDij zjF&FK2P^YFT#2S%_@bA`1BgPnf)~Au$tA-b<)2pV=k(7ZYzDbw{K&jo&;K5L4zW;3 z2=~ztvi*VgM?(fVzMglkjK}*yN?#TvEK3jHpF_dkZYKawcs0b-3|GbO!{ZZRxVY9? z;TwEfw{%EYIjdgYf-ciPKotelS3y2sIwU+}np(fD5AGkHI$g?%Bitwarden Web Vault + + @@ -19,7 +36,7 @@

- +

diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index e87893e143a..2b4a9ae4d44 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -4094,6 +4094,21 @@ "removeUsersWarning": { "message": "Are you sure you want to remove the following users? The process may take a few seconds to complete and cannot be interrupted or canceled." }, + "theme": { + "message": "Theme" + }, + "themeDesc": { + "message": "Choose a theme for your web vault." + }, + "themeSystem": { + "message": "Use System Theme" + }, + "themeDark": { + "message": "Dark" + }, + "themeLight": { + "message": "Light" + }, "confirmSelected": { "message": "Confirm Selected" }, diff --git a/src/scss/base.scss b/src/scss/base.scss new file mode 100644 index 00000000000..b4ba0a1635c --- /dev/null +++ b/src/scss/base.scss @@ -0,0 +1,246 @@ +html { + font-size: 14px; +} + +body { + min-width: 1010px; + + &.layout_frontend { + @media (prefers-color-scheme: dark) { + background-color: $darkDarkBlue2; + } + @media (prefers-color-scheme: light) { + background-color: $white; + } + @include themify($themes) { + background-color: themed('layoutFrontendColor'); + color: themed('headingColor'); + } + } + + @include themify($themes) { + background-color: themed('backgroundColor'); + color: themed('textColor'); + } + + &.full-width:not(.layout_frontend) { + .container { + min-width: 980px; + width: 90%; + } + } +} + +.container { + margin: 0 auto; + max-width: none !important; + padding: 0; + width: 980px; +} + +.page-header, .secondary-header { + margin-bottom: 0.5rem; + padding-bottom: 0.6rem; + @include themify($themes) { + border-bottom: 1px solid themed('separator'); + } + + &:not(.text-danger) { + h1, h2, h3, h4 { + margin: 0; + @include themify($themes) { + color: themed('headingColor'); + } + } + } +} + +.secondary-header, .spaced-header { + margin-top: 4rem; +} + +img.logo { + display: block; + height: 43px; + margin: 0 auto; + width: 284px; + @include themify($themes) { + content: url('../images/logo-' + themed('logoSuffix') + '@2x.png'); + } +} + +.page-content { + margin-top: 20px; +} + +.footer { + margin-top: 40px; + padding: 40px 0 40px 0; + @include themify($themes) { + border-top: 1px solid themed('separator'); + } +} + +hr, .dropdown-divider { + @include themify($themes) { + border-top: 1px solid themed('separatorHr'); + } +} + +.min-height-fix { + min-height: 1px; +} + +.overflow-hidden { + overflow: hidden; +} + +.cursor-move { + cursor: move !important; +} + + +h1, h2, h3, h4, h5 { + @include themify($themes) { + color: themed('headingColor'); + } + + small { + font-size: 80%; + } + + &.spaced-header { + @include themify($themes) { + color: themed('headingColor'); + } + } +} + +a { + @include themify($themes) { + color: themed('linkColor'); + } + + &.text-body { + @include themify($themes) { + color: themed('headingColor') !important; + font-weight: themed('linkWeight'); + } + } +} + +a:hover { + @include themify($themes) { + color: themed('linkColorHover'); + } +} + +code { + @include themify($themes) { + color: themed('codeColor'); + } +} + +.fa-icon-above-input { + height: 1.5em; +} + +.text-lg { + font-size: $font-size-lg; +} + +.text-strike { + text-decoration: line-through; +} + +.font-weight-semibold { + font-weight: 600; +} + +.btn:focus, .swal2-popup .swal2-actions button:focus, .btn.focus, .swal2-popup .swal2-actions button.focus, .form-control:focus { + @include themify($themes) { + box-shadow: 0 0 0 0.2rem themed('focus'); + } +} + +/* Override Bootstrap theming */ + +.bg-primary { + @include themify($themes) { + background-color: themed('bgPrimaryColor'); + } +} + +.bg-light { + @include themify($themes) { + background-color: themed('bgLightColor') !important; + } +} + +.border-primary { + @include themify($themes) { + border-color: themed('borderPrimaryColor') !important; + } +} + +.border-warning { + @include themify($themes) { + border-color: themed('warning') !important; + } +} + +.border-danger { + @include themify($themes) { + border-color: themed('danger') !important; + } +} + +.border-info { + @include themify($themes) { + border-color: themed('info') !important; + } +} + +.text-success { + @include themify($themes) { + color: themed('success') !important; + } + + & > h1,h2,h3,h4 { + @include themify($themes) { + color: themed('success') !important; + } + } +} + +.text-warning { + @include themify($themes) { + color: themed('warning') !important; + } + + & > h1,h2,h3,h4 { + @include themify($themes) { + color: themed('warning') !important; + } + } +} + +.text-danger { + &:not(.dropdown-item) { + @include themify($themes) { + color: themed('danger') !important; + } + + & > h1,h2,h3,h4 { + @include themify($themes) { + color: themed('danger') !important; + } + } + } +} + +.text-muted { + @include themify($themes) { + color: themed('textMuted') !important; + } +} diff --git a/src/scss/buttons.scss b/src/scss/buttons.scss new file mode 100644 index 00000000000..74ab57716ca --- /dev/null +++ b/src/scss/buttons.scss @@ -0,0 +1,224 @@ +.btn-primary, .swal2-confirm { + @include themify($themes) { + background-color: themed('btnPrimary'); + border-color: themed('btnPrimary'); + color: themed('btnPrimaryText'); + } + + &:hover:not(:disabled), &:active:not(:disabled) { + @include themify($themes) { + background-color: themed('btnPrimaryHover'); + border-color: themed('btnPrimaryBorderHover'); + color: themed('btnPrimaryText'); + } + } + + &:disabled { + opacity: 0.65; + } +} + +.btn-outline-primary { + @include themify($themes) { + background-color: themed('btnOutlinePrimaryBackground'); + border-color: themed('btnOutlinePrimaryBorder'); + color: themed('btnOutlinePrimaryText'); + } + + &:hover:not(:disabled), &:active { + @include themify($themes) { + background-color: themed('btnOutlinePrimaryBackgroundHover'); + border-color: themed('btnOutlinePrimaryBorderHover'); + color: themed('btnOutlinePrimaryTextHover'); + } + } +} + +.btn-secondary, .swal2-cancel { + @include themify($themes) { + background-color: themed('btnSecondary'); + border-color: themed('btnSecondaryBorder'); + color: themed('btnSecondaryText'); + } + + &:hover:not(:disabled), &:active:not(:disabled) { + @include themify($themes) { + background-color: themed('btnSecondaryHover'); + border-color: themed('btnSecondaryBorderHover'); + color: themed('btnSecondaryTextHover'); + } + } + + &:disabled { + opacity: 0.65; + } + + &:focus, + &.focus { + @include themify($themes) { + box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq(themed('primary')), themed('primary'), 15%), .5); + } + } +} + +.btn-outline-secondary { + @include themify($themes) { + background-color: themed('btnOutlineSecondaryBackground'); + border-color: themed('btnOutlineSecondaryBorder'); + color: themed('btnOutlineSecondaryText'); + } + + &:hover:not(:disabled), &:active { + @include themify($themes) { + background-color: themed('btnOutlineSecondaryBackgroundHover'); + border-color: themed('btnOutlineSecondaryBorderHover'); + color: themed('btnOutlineSecondaryTextHover'); + } + } +} + +.show > .btn-outline-secondary { + &.dropdown-toggle, &:focus { + @include themify($themes) { + background-color: themed('btnOutlineSecondaryBackground'); + border-color: themed('btnOutlineSecondaryBorder'); + color: themed('btnOutlineSecondaryText'); + } + } + + &:hover { + @include themify($themes) { + background-color: themed('btnOutlineSecondaryBackgroundHover'); + border-color: themed('btnOutlineSecondaryBorderHover'); + color: themed('btnOutlineSecondaryTextHover'); + } + } +} + +.btn-danger, .swal2-deny { + @include themify($themes) { + background-color: themed('btnDanger'); + border-color: themed('btnDanger'); + color: themed('btnDangerText'); + } + + &:hover:not(:disabled), &:active:not(:disabled) { + @include themify($themes) { + background-color: themed('btnDangerHover'); + border-color: themed('btnDangerHover'); + color: themed('btnDangerText'); + } + } +} + +.btn-outline-danger { + @include themify($themes) { + background-color: themed('btnOutlineDangerBackground'); + border-color: themed('btnOutlineDangerBorder'); + color: themed('btnOutlineDangerText'); + } + + &:hover:not(:disabled), &:active { + @include themify($themes) { + background-color: themed('btnOutlineDangerBackgroundHover'); + border-color: themed('btnOutlineDangerBorderHover'); + color: themed('btnOutlineDangerTextHover'); + } + } +} + +.btn-link { + &:focus, + &.focus { + outline-color: -webkit-focus-ring-color; + outline-offset: 1px; + outline-style: auto; + outline-width: 1px; + } + + &:not(.text-danger):not(.cursor-move) { + @include themify($themes) { + color: themed('btnLinkText'); + } + } + + &:hover:not(.text-danger):not(.cursor-move) { + @include themify($themes) { + color: themed('btnLinkTextHover'); + } + } +} + +.btn-submit { + position: relative; + + .fa-spinner { + align-items: center; + display: none; + justify-content: center; + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: 0; + } + + &:disabled:not(.manual), &.loading { + .fa-spinner { + display: flex; + } + + span { + visibility: hidden; + } + } +} + +.badge-primary { + @include themify($themes) { + background-color: themed('badgePrimaryBackground'); + color: themed('badgePrimaryText'); + } + + &:hover { + @include themify($themes) { + background-color: themed('badgePrimaryBackgroundHover'); + color: themed('badgePrimaryText'); + } + } +} + +.badge-secondary { + @include themify($themes) { + background-color: themed('badgeSecondaryBackground'); + color: themed('badgeSecondaryText'); + } +} + +.badge-info { + @include themify($themes) { + background-color: themed('badgeInfoBackground'); + color: themed('badgeInfoText'); + } +} + +.badge-danger { + @include themify($themes) { + background-color: themed('badgeDangerBackground'); + color: themed('badgeDangerText'); + } +} + +.badge-warning { + @include themify($themes) { + background-color: themed('warning'); + color: themed('warningTextColor'); + } +} + +.badge-success { + @include themify($themes) { + background-color: themed('success'); + color: themed('successTextColor'); + } +} diff --git a/src/scss/callouts.scss b/src/scss/callouts.scss new file mode 100644 index 00000000000..43763dbabf9 --- /dev/null +++ b/src/scss/callouts.scss @@ -0,0 +1,83 @@ +.callout { + border-left-width: 5px !important; + border-radius: $card-inner-border-radius; + margin-bottom: $alert-margin-bottom; + padding: $alert-padding-y $alert-padding-x; + @include themify($themes) { + background-color: themed('calloutBackground'); + border: 1px solid themed('borderColor'); + color: themed('calloutColor'); + } + + .callout-heading { + margin-top: 0; + } + + h3.callout-heading { + font-weight: bold; + text-transform: uppercase; + } + + &.callout-primary { + @include themify($themes) { + border-left-color: themed('primary'); + } + .callout-heading { + @include themify($themes) { + color: themed('primary'); + } + } + } + + &.callout-info { + @include themify($themes) { + border-left-color: themed('info'); + } + + .callout-heading { + @include themify($themes) { + color: themed('info'); + } + } + } + + &.callout-danger { + @include themify($themes) { + border-left-color: themed('danger'); + } + + .callout-heading { + @include themify($themes) { + color: themed('danger'); + } + } + } + + &.callout-success { + @include themify($themes) { + border-left-color: themed('success'); + } + + .callout-heading { + @include themify($themes) { + color: themed('success'); + } + } + } + + &.callout-warning { + @include themify($themes) { + border-left-color: themed('warning'); + } + + .callout-heading { + @include themify($themes) { + color: themed('warning'); + } + } + } + + .enforced-policy-options ul { + margin-bottom: 0px; + } +} diff --git a/src/scss/cards.scss b/src/scss/cards.scss new file mode 100644 index 00000000000..b1f514bdf0d --- /dev/null +++ b/src/scss/cards.scss @@ -0,0 +1,93 @@ +.card { + @include themify($themes) { + background-color: themed('foregroundColor'); + border-color: themed('borderColor'); + color: themed('textColor'); + } + + &.text-danger { + &.text-danger > .card-body { + @include themify($themes) { + color: themed('danger'); + } + } + } +} + +.card-header, .modal-header { + font-weight: bold; + text-transform: uppercase; + + small { + font-weight: normal; + text-transform: none; + @extend .text-muted; + } +} + +.card-header { + @include themify($themes) { + background-color: themed('headerColor'); + color: themed('headingColor'); + } + + a:hover { + &:not(.badge){ + @include themify($themes) { + color: themed('learnMoreHover'); + } + } + } +} + +.card-body-header { + font-size: $font-size-lg; + @extend .mb-4 +} + +.card ul.fa-ul.card-ul { + margin-left: 1.9em; + + li { + word-break: break-all; + } + + .fa-li { + top: 4px; + } + + &.carets { + margin-left: 1.1em; + + .fa-li { + left: -17px; + width: 1.1em; + } + } + + ul { + &.carets { + margin-left: 0.85em; + } + } +} + +.card-org-plans { + h2 { + font-size: $font-size-lg; + } +} + +.card-body { + &:not(.bg-light > .card-body) { + @include themify($themes) { + background-color: themed('foregroundColor'); + color: themed('textColor'); + } + &.card-body a:not(li a) { + @include themify($themes) { + font-weight: themed('linkWeight'); + } + } + } +} diff --git a/src/scss/export.module.scss b/src/scss/export.module.scss new file mode 100644 index 00000000000..f727344c192 --- /dev/null +++ b/src/scss/export.module.scss @@ -0,0 +1,8 @@ +@import 'variables'; + +:export { + lightInputColor: $lightInputColor; + lightInputPlaceholderColor: $lightInputPlaceholderColor; + darkInputColor: $darkInputColor; + darkInputPlaceholderColor: $darkInputPlaceholderColor; +} diff --git a/src/scss/export.module.scss.d.ts b/src/scss/export.module.scss.d.ts new file mode 100644 index 00000000000..c4b74b98104 --- /dev/null +++ b/src/scss/export.module.scss.d.ts @@ -0,0 +1,9 @@ +export interface ThemeVariableExport { + lightInputColor: string; + lightInputPlaceholderColor: string; + darkInputColor: string; + darkInputPlaceholderColor: string; +} + +export const ThemeVariables: ThemeVariableExport; +export default ThemeVariables; diff --git a/src/scss/forms.scss b/src/scss/forms.scss new file mode 100644 index 00000000000..5d372aafa6b --- /dev/null +++ b/src/scss/forms.scss @@ -0,0 +1,180 @@ +::-ms-reveal { + display: none; +} + +::placeholder { + @include themify($themes) { + color: themed('inputPlaceholderColor'); + } +} + +input, select, textarea { + &:required { + box-shadow: none; + } +} + +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: + -cancel-button; +} + +label:not(.form-check-label):not(.btn), label.bold { + font-weight: 600; + @include themify($themes) { + color: themed('headingColor'); + } +} + +label.form-check-label, .form-control-file { + @include themify($themes) { + color: themed('headingColor'); + } +} + +.form-check-block { + .form-check-label { + font-weight: 600; + + > small { + display: block; + font-weight: normal; + @include themify($themes) { + color: themed('textMuted'); + } + } + + > span { + display: block; + font-weight: normal; + @extend .mt-2; + } + } +} + +.form-check-block + .form-check-block { + &:not(.mt-2) { + @extend .mt-3; + } +} + +.form-inline { + input[type='datetime-local'] { + width: 200px; + } +} + +.form-control { + @include themify($themes) { + background-color: themed('inputBackgroundColor'); + border-color: themed('inputBorderColor'); + color: themed('inputTextColor'); + } + + &:disabled, &[readonly] { + @include themify($themes) { + background-color: themed('inputDisabledBackground'); + color: themed('inputDisabledColor'); + } + } +} + +input[type="radio"], input[type="checkbox"] { + cursor: pointer; +} + +.form-control.stripe-form-control { + padding-top: 0.55rem; + + &.is-focused { + outline: 0; + @include themify($themes) { + background-color: themed('inputBackgroundColor'); + border-color: themed('inputBorderColor'); + box-shadow: 0 0 0 $input-focus-width rgba(mix(color-yiq(themed('primary')), themed('primary'), 15%), .5); + color: themed('inputTextColor'); + } + + &.is-invalid { + opacity: 0.75; + @include themify($themes) { + box-shadow: 0 0 0 $input-focus-width themed('danger'); + } + } + } + + &.is-invalid { + @include themify($themes) { + border-color: themed('danger'); + } + } +} + +.dropdown-menu, .dropdown-item { + @include themify($themes) { + background-color: themed('dropdownBackground'); + color: themed('dropdownTextColor'); + } +} + +.dropdown-item { + &.text-danger { + color: #FFFFFF !important; + @include themify($themes) { + background-color: themed('danger'); + } + + &:hover { + color: #FFFFFF !important; + @include themify($themes){ + background-color: themed('dropdownDangerHover') !important; + } + } + } + &:hover:not(.text-danger) { + @include themify($themes) { + background-color: themed('dropdownHover'); + color: themed('dropdownTextColor'); + } + } + &:active{ + background-color: rgba(0,0,0,0.1) !important; + } +} + +.dropdown-menu { + button { + cursor: pointer; + } + @include themify($themes) { + border: 1px solid themed('listItemBorder'); + } +} + +.list-group-item { + &:focus, + &.focus { + z-index: 100; + } + @include themify($themes) { + background-color: themed('foregroundColor'); + border-color: themed('listItemBorder'); + color: themed('listItemColor'); + font-weight: themed('linkWeight'); + } + &:hover { + @include themify($themes) { + color: themed('listItemColorHover'); + } + } +} + +.list-group-item.active { + font-weight: bold !important; + padding-left: calc(#{$list-group-item-padding-x} - 3px); + @include themify($themes) { + border-color: themed('borderColor'); + border-left: 3px solid themed('borderPrimaryColor'); + color: themed('listItemActive'); + } +} diff --git a/src/scss/modals.scss b/src/scss/modals.scss new file mode 100644 index 00000000000..d39b1714bfd --- /dev/null +++ b/src/scss/modals.scss @@ -0,0 +1,147 @@ +.modal-content { + border: none; + border-radius: none; + @include themify($themes) { + background-color: themed('backgroundColor'); + } +} + +.modal-dialog { + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + width: $modal-md; +} + +.modal-sm { + width: $modal-sm; +} + +.modal-lg { + width: $modal-lg; +} + +.modal-header { + @include themify($themes) { + background-color: themed('foregroundColor'); + border-bottom: 1px solid themed('separator'); + color: themed('textColor'); + } +} + +.modal-body { + @include themify($themes) { + background-color: themed('foregroundColor'); + color: themed('textColor'); + } + + h3, .section-header > * { + font-weight: normal; + text-transform: uppercase; + @include themify($themes) { + color: themed('textMuted'); + } + } +} +.modal .list-group-flush { + :first-child { + border-top: none; + } + + :last-child { + border-bottom: none; + } +} + +.modal-footer { + border-radius: 0.3rem 0.3rem 0 0; + justify-content: flex-start; + @include themify($themes) { + background-color: themed('footerBackgroundColor'); + border-top: 1px solid themed('separator'); + } +} + +.close { + @include themify($themes) { + color: themed('textColor'); + } +} + +#totpImage { + @include themify($themes) { + filter: themed('imgFilter'); + } +} + +.totp { + .totp-code { + @extend .text-monospace; + font-size: 1.2rem; + } + + .totp-countdown { + display: block; + margin: 3px 3px 0 0; + user-select: none; + + .totp-sec { + font-size: 0.85em; + line-height: 32px; + position: absolute; + text-align: center; + width: 32px; + } + + svg { + height: 32px; + transform: rotate(-90deg); + width: 32px; + } + + .totp-circle { + fill: none; + @include themify($themes) { + stroke: themed('primary'); + } + + &.inner { + stroke-dasharray: 78.6; + stroke-dashoffset: 0; + stroke-width: 3; + } + + &.outer { + stroke-dasharray: 88; + stroke-dashoffset: 0; + stroke-width: 2; + } + } + } + + > .align-items-center { + margin-bottom: -5px; + } + + &.low { + .totp-sec, .totp-code { + @include themify($themes) { + color: themed('danger'); + } + } + + .totp-circle { + @include themify($themes) { + stroke: themed('danger'); + } + } + } +} + +.cdk-drag-preview { + border-radius: $border-radius; + opacity: 0.8; + z-index: $zindex-tooltip !important; + @include themify($themes) { + background: themed('cdkDraggingBackground'); + } +} diff --git a/src/scss/navigation.scss b/src/scss/navigation.scss new file mode 100644 index 00000000000..230cdbef170 --- /dev/null +++ b/src/scss/navigation.scss @@ -0,0 +1,109 @@ +.navbar { + padding-left: 0; + padding-right: 0; + @include themify($themes) { + background-color: themed('navBackground') !important; + } + + .dropdown-menu { + max-width: 300px; + min-width: 200px; + + .dropdown-item-text { + line-height: 1.3; + @include themify($themes) { + color: themed('dropdownTextColor'); + } + + span, small { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + &.text-muted { + @include themify($themes) { + color: themed('dropdownTextMuted') !important; + } + } + } + } + } + .nav-item { + > .nav-link { + @include themify($themes) { + font-weight: themed('navWeight'); + } + } + &.active > .nav-link { + @include themify($themes) { + font-weight: themed('navActiveWeight'); + } + } + } +} + +.navbar-brand { + margin-bottom: -20px; + margin-top: -20px; +} + +.nav-tabs .nav-link.active { + @include themify($themes) { + background: themed('navActiveBackground'); + border-color: themed('borderColor'); + } +} + +.org-nav { + height: 100px; + min-height: 100px; + @include themify($themes) { + background-color: themed('navOrgBackgroundColor'); + border-bottom: 1px solid themed('borderColor'); + color: themed('textColor'); + } + + .container { + height: 100%; + } + + .nav-tabs { + border-bottom: none; + + a { + &:not(.active) { + border-color: transparent; + @include themify($themes) { + color: themed('textColor'); + } + } + + &.active { + font-weight: bold; + padding-top: calc(#{$nav-link-padding-y} - 2px); + @include themify($themes) { + border-top: 3px solid themed('primary'); + color: themed('linkColor'); + } + } + + &.disabled { + @include themify($themes) { + color: themed('inputDisabledColor'); + } + } + } + } + + .org-name { + line-height: 1; + span { + display: block; + font-size: $font-size-lg; + @include themify($themes) { + color: themed('headingColor'); + } + } + } +} + diff --git a/src/scss/pages.scss b/src/scss/pages.scss new file mode 100644 index 00000000000..aa19235cf23 --- /dev/null +++ b/src/scss/pages.scss @@ -0,0 +1,283 @@ +.password-wrapper { + min-width: 0; + white-space: pre-wrap; + word-break: break-all; +} + +.password-row { + min-width: 0; +} + +.password-letter { + @include themify($themes) { + color: themed('pwLetter'); + } +} + +.password-number { + @include themify($themes) { + color: themed('pwNumber'); + } +} + +.password-special { + @include themify($themes) { + color: themed('pwSpecial'); + } +} + +app-vault-groupings, app-org-vault-groupings, .groupings { + .card { + #search { + margin-bottom: 1rem; + @include themify($themes) { + background-color: themed('inputBackgroundColor'); + border-color: themed('inputBorderColor'); + color: themed('inputTextColor'); + } + + &::placeholder { + @include themify($themes) { + color: themed('inputPlaceholderColor'); + } + } + } + + h3 { + font-weight: normal; + text-transform: uppercase; + @include themify($themes) { + color: themed('textMuted'); + } + } + + ul:last-child { + margin-bottom: 0; + } + + .card-body a { + @include themify($themes) { + color: themed('headingColor'); + font-weight: themed('linkWeight'); + } + + &:hover { + &.text-muted { + @include themify($themes) { + color: themed('iconHover') !important; + } + } + } + } + + .show-active { + display: none; + } + + li { + > .fa, > div > .fa { + cursor: pointer; + } + } + + li.active { + > .show-active, > div .show-active { + display: inline; + } + } + + li.active { + > a:first-of-type, > div a:first-of-type { + font-weight: bold; + @include themify($themes) { + color: themed('linkColor'); + } + } + + > .fa, > div > .fa { + @include themify($themes) { + color: themed('linkColor'); + } + } + } + } +} + +app-password-generator { + #lengthRange { + width: 100%; + } + + .card-password { + .card-body { + @include themify($themes) { + background: themed('foregroundColor'); + } + align-items: center; + display: flex; + flex-wrap: wrap; + font-family: $font-family-monospace; + font-size: $font-size-lg; + justify-content: center; + text-align: center; + } + } +} + +app-password-generator-history { + .list-group-item { + line-height: 1; + @include themify($themes) { + background: themed('backgroundColor'); + } + + .password { + font-family: $font-family-monospace; + } + } +} + +app-import { + textarea { + height: 150px; + } +} + +app-avatar { + img { + @extend .rounded; + } +} + +app-user-billing { + .progress { + height: 20px; + + .progress-bar { + min-width: 50px; + } + } +} + + +/* Register Layout Page - Exempt from themify */ +body.theme_light_force { + background-color: #ECF0F5; + a, .btn-link { + color: #175DDC; + &:hover { + color: #104097; + } + } + + .btn-outline-secondary { + color: #6c757d; + &:hover { + color: #212529; + } + } + + .text-muted { + color: #6C757D !important; + } + + .card, .card-body { + background-color: #FFFFFF; + border-color: rgba(0,0,0,.125); + } + + .form-control { + background-color: #FBFBFB; + border: 1px solid #CED4DA; + color: #495057; + } + + label { + color: #333333; + font-weight: 600; + &.small { + font-weight: 400; + } + } + + hr { + border-top: 1px solid rgba(0,0,0,.1) + } + .progress { + background-color: #E9ECEF; + } + .bg-primary { + background-color: #175DDC !important; + } + .bg-success { + background-color: #00A65A !important; + } + .bg-warning { + background-color: #BF7E16 !important; + } + .bg-danger { + background-color: #DD4B39 !important; + } + + .layout { + &.enterprise2 { + header { + background: #175DDC; + color: #CED4DA; + + &:before { + background: #175DDC; + content: ""; + height: 416px; + left: 0; + position: absolute; + top: -76px; + transform: skewY(-3deg); + width: 100%; + z-index: -1; + } + img.logo { + margin: 12px 0 0; + max-width: 284px; + height: 57px; + width: 284px; + } + } + + h2 { + color: #FFFFFF; + font-size: 1.8rem; + margin: 100px 0 150px 0; + } + + p { + font-size: 1.4rem; + margin: 20px 0 40px 0; + + &:before { + content: "/"; + padding-right: 12px; + } + &:not(.highlight) { + &:before { + color: #1252A3; + } + } + + b { + &:after { + content: "⟶"; + font-size: 2rem; + padding-left: 6px; + } + } + } + + blockquote { + font-size: 1.4rem; + margin: 20px 0 0 0; + padding-right: 40px; + } + } + } +} diff --git a/src/scss/plugins.scss b/src/scss/plugins.scss index 3fc34da1bcb..5415f801fce 100644 --- a/src/scss/plugins.scss +++ b/src/scss/plugins.scss @@ -1,107 +1,127 @@ -$fa-font-path: "~font-awesome/fonts"; -@import "~font-awesome/scss/font-awesome.scss"; -@import "~angular2-toaster/toaster"; -@import "~sweetalert2/src/sweetalert2.scss"; - -.toast-container { - &.toast-top-right { - top: 76px; +#duo-frame { + height: 330px; + @include themify($themes) { + background: themed('imgLoading') 0 0 no-repeat; } - .toast-close-button { - margin-right: 4px; - font-size: 18px; + iframe { + border: none; + height: 100%; + width: 100%; + } +} + +#web-authn-frame { + height: 290px; + @include themify($themes) { + background: themed('imgLoading') 0 0 no-repeat; } - .toast { - opacity: 1 !important; - background-image: none !important; - border-radius: $border-radius; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); - display: flex; - align-items: center; + iframe { + border: none; + height: 100%; + width: 100%; + } +} - &:hover { - box-shadow: 0 0 10px rgba(0, 0, 0, 0.6); - } +#hcaptcha_iframe { + border: none; + transition: height 0.25s linear; + width: 100%; +} - &:before { - font-family: FontAwesome; - font-size: 25px; - line-height: 20px; - float: left; - color: #ffffff; - margin: auto 0 auto 15px; - } +.list-group-2fa { + .logo-2fa { + min-width: 100px; + } +} - .toast-content { - padding: 15px; - } +.progress { + @include themify($themes) { + background-color: themed('pwStrengthBackground'); + } +} - .toaster-icon { - display: none; - } +// Braintree - .toast-message { - p { - margin-bottom: 0.5rem; +#bt-dropin-container { + min-height: 50px; + @include themify($themes) { + background: themed("loadingSvg") center center no-repeat; + } +} - &:last-child { - margin-bottom: 0; - } - } - } +.braintree-placeholder, .braintree-sheet__header { + display: none; +} - &.toast-danger, &.toast-error { - background-image: none !important; - background-color: $danger; +.braintree-sheet__content--button { + min-height: 0; + padding: 0; + text-align: left; +} - &:before { - content: "\f0e7"; - } - } +.braintree-sheet__container { + margin-bottom: 0; +} - &.toast-warning { - background-image: none !important; - background-color: $warning; +.braintree-sheet { + border: none; +} - &:before { - content: "\f071"; - } - } +[data-braintree-id="upper-container"]::before { + @include themify($themes) { + background-color: themed('backgroundColor'); + } +} - &.toast-info { - background-image: none !important; - background-color: $info; +.card [data-braintree-id="upper-container"]::before { + @include themify($themes) { + background-color: themed('foregroundColor'); + } +} - &:before { - content: "\f05a"; - } - } +[data-braintree-id="paypal-button"] { + @include themify($themes) { + background-color: themed('backgroundColor'); + } +} - &.toast-success { - background-image: none !important; - background-color: $success; +.card [data-braintree-id="paypal-button"] { + @include themify($themes) { + background-color: themed('foregroundColor'); + } +} - &:before { - content: "\f00C"; - } - } +.paypal-button-text { + @include themify($themes) { + color: themed('textColor'); } } // SweetAlert2 +[class*="swal2-"] { + &:not(.swal2-container, .swal2-confirm, .swal2-cancel, .swal2-deny) { + @include themify($themes) { + background-color: themed('backgroundColor'); + color: themed('textColor'); + } + } +} + .swal2-container { background-color: rgba(0,0,0,.3); } .swal2-popup { - padding: 15px 0 0; - background-color: $modal-content-bg; - color: $body-color; + @include themify($themes) { + background-color: themed('backgroundColor'); + color: themed('textColor'); + } border: $modal-content-border-width solid #9a9a9a; @include border-radius($modal-content-border-radius); + padding: 15px 0 0; width: 34em; // slightly bigger than the hardcoded 478px in v1. .swal2-header { @@ -118,7 +138,9 @@ $fa-font-path: "~font-awesome/fonts"; .swal2-content { padding-bottom: 15px; font-size: $font-size-base; - border-bottom: $modal-footer-border-width solid $modal-footer-border-color; + @include themify($themes) { + border-bottom: $modal-footer-border-width solid themed('separator'); + } } i.swal-custom-icon { @@ -128,41 +150,37 @@ $fa-font-path: "~font-awesome/fonts"; } .swal2-title { - padding: 10px 0 15px; - margin: 0; font-size: $font-size-lg; - color: $body-color; + margin: 0; + padding: 10px 0 15px; + @include themify($themes) { + color: themed('headingColor'); + } } .swal2-content { font-size: $font-size-base; - color: $body-color; padding: 0 15px 15px; + @include themify($themes) { + color: themed('textColor'); + } } .swal2-actions { - padding: 15px; - margin: 0; - background-color: $input-bg; @include border-radius($modal-content-border-radius); display: flex; flex-direction: row; - justify-content: flex-start; font-size: $font-size-base; + justify-content: flex-start; + margin: 0; + padding: 15px; + @include themify($themes) { + background-color: themed('backgroundColor'); + } button { margin-right: 10px; @extend .btn; - - &.swal2-confirm { - @extend .btn-primary; - font-weight: bold; - } - - &.swal2-cancel { - @extend .btn-outline-secondary; - background-color: #ffffff; - } } } diff --git a/src/scss/register-layout.scss b/src/scss/register-layout.scss deleted file mode 100644 index 62af3e37987..00000000000 --- a/src/scss/register-layout.scss +++ /dev/null @@ -1,63 +0,0 @@ -.layout { - &.enterprise2 { - - header { - color: $secondary; - background-color: $primary; - - &:before { - content: ""; - position: absolute; - z-index: -1; - width: 100%; - height: 340px; - left: 0; - transform: skewY(-3deg); - background: $primary; - } - - img.logo { - - margin: 12px 0 0; - width: 284px; - max-width: 284px; - height: auto; - } - } - - h2 { - color: #ffffff; - font-size: 1.8rem; - margin: 100px 0 150px 0; - } - - p { - margin: 20px 0 40px 0; - font-size: 1.4rem; - - &:before { - content: "/"; - padding-right: 12px; - } - &:not(.highlight) { - &:before { - color: $primary-accent; - } - } - - b { - &:after { - content: "⟶"; - font-size: 2rem; - padding-left: 6px; - } - } - } - - blockquote { - margin: 20px 0 0 0; - font-size: 1.4rem; - padding-right: 40px; - } - } -} diff --git a/src/scss/styles.scss b/src/scss/styles.scss index deadb00692c..45a91fe1844 100644 --- a/src/scss/styles.scss +++ b/src/scss/styles.scss @@ -1,77 +1,5 @@ @import "../../jslib/angular/src/scss/webfonts.css"; - -$primary: #175DDC; -$primary-accent: #1252A3; -$secondary: #ced4da; -$secondary-alt: #1A3B66; -$success: #00a65a; -$info: #555555; -$warning: #bf7e16; -$danger: #dd4b39; - -$theme-colors: ( - "primary-accent": $primary-accent, - "secondary-alt": $secondary-alt, -); - -$body-bg: #ffffff; -$body-color: #333333; - -$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica, - Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol'; - -$h1-font-size: 1.7rem; -$h2-font-size: 1.3rem; -$h3-font-size: 1rem; -$h4-font-size: 1rem; -$h5-font-size: 1rem; -$h6-font-size: 1rem; - -$small-font-size: 90%; -$font-size-lg: 1.15rem; -$code-font-size: 100%; - -$navbar-padding-y: .75rem; -$grid-gutter-width: 20px; -$card-spacer-y: .6rem; - -$list-group-item-padding-y: .6rem; -$list-group-active-color: $body-color; -$list-group-active-bg: #ffffff; -$list-group-active-border-color: rgba(#000000, .125); - -$dropdown-link-color: $body-color; -$dropdown-link-hover-bg: rgba(#000000, .06); -$dropdown-link-active-color: $dropdown-link-color; -$dropdown-link-active-bg: rgba(#000000, .1); -$dropdown-item-padding-x: 1rem; - -$navbar-brand-font-size: 35px; -$navbar-brand-height: 35px; -$navbar-brand-padding-y: 0; -$navbar-dark-color: rgba(#ffffff, .7); -$navbar-dark-hover-color: rgba(#ffffff, .9); -$navbar-nav-link-padding-x: 0.8rem; - -$input-bg: #fbfbfb; -$input-focus-bg: #ffffff; -$input-disabled-bg: #e0e0e0; -$input-placeholder-color: #b4b4b4; - -$table-accent-bg: rgba(#000000, .02); -$table-hover-bg: rgba(#000000, .03); - -$modal-backdrop-opacity: 0.3; -$btn-font-weight: 600; -$lead-font-weight: normal; - -$grid-breakpoints: ( - xs: 0, - sm: 1px, - md: 2px, - lg: 3px, - xl: 4px -); +@import "./variables"; //@import "~bootstrap/scss/bootstrap"; @import "~bootstrap/scss/_functions"; @@ -111,780 +39,19 @@ $grid-breakpoints: ( @import "~bootstrap/scss/_spinners"; @import "~bootstrap/scss/_utilities"; @import "~bootstrap/scss/_print"; + +@import "~angular2-toaster/toaster"; +@import "~font-awesome/scss/font-awesome.scss"; +@import "~sweetalert2/src/sweetalert2.scss"; + +@import "./base"; +@import "./buttons"; +@import "./callouts"; +@import "./cards"; +@import "./forms"; +@import "./navigation"; +@import "./modals"; +@import "./pages"; @import "./plugins"; - -html { - font-size: 14px; -} - -body { - min-width: 1010px; - - &.layout_frontend { - background-color: #ecf0f5; - } - - &.full-width:not(.layout_frontend) { - .container { - min-width: 980px; - width: 90%; - } - } -} - -.page-header, .secondary-header { - border-bottom: 1px solid $border-color; - padding-bottom: 0.6rem; - margin-bottom: 0.5rem; - - h1, h2, h3, h4 { - margin: 0; - } -} - -h1, h2, h3, h4, h5 { - small { - font-size: 80%; - } -} - -input, select, textarea { - &:required { - box-shadow: none; - } -} - -.secondary-header, .spaced-header { - margin-top: 4rem; -} - -.navbar { - padding-left: 0; - padding-right: 0; - - .dropdown-menu { - min-width: 200px; - max-width: 300px; - - .dropdown-item-text { - line-height: 1.3; - - span, small { - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - } - - .nav-link { - font-weight: 600; - } -} - -.navbar-brand { - margin-top: -20px; - margin-bottom: -20px; -} - -.dropdown-menu { - button { - cursor: pointer; - } -} - -.container { - width: 980px; - max-width: none !important; - margin: 0 auto; - padding: 0; -} - -.page-content { - margin-top: 20px; -} - -.footer { - margin-top: 40px; - padding: 40px 0 40px 0; - border-top: 1px solid $border-color; -} - -.list-group-item.active { - border-left: 3px solid theme-color("primary"); - font-weight: bold; - padding-left: calc(#{$list-group-item-padding-x} - 3px); -} - -.card-header, .modal-header { - font-weight: bold; - text-transform: uppercase; - - small { - font-weight: normal; - text-transform: none; - @extend .text-muted; - } -} - -.card-body-header { - font-size: $font-size-lg; - @extend .mb-4 -} - -.card ul.fa-ul.card-ul { - margin-left: 1.9em; - - li { - word-break: break-all; - } - - .fa-li { - top: 4px; - } - - &.carets { - margin-left: 1.1em; - - .fa-li { - left: -17px; - width: 1.1em; - } - } - - ul { - &.carets { - margin-left: 0.85em; - } - } -} - -.card-org-plans { - h2 { - font-size: $font-size-lg; - } -} - -.modal-dialog { - width: $modal-md; -} - -.modal-sm { - width: $modal-sm; -} - -.modal-lg { - width: $modal-lg; -} - -.modal-body { - h3, .section-header > * { - font-weight: normal; - text-transform: uppercase; - color: $text-muted; - } -} -.modal .list-group-flush { - :first-child { - border-top: none; - } - :last-child { - border-bottom: none; - } -} - -.modal-footer { - justify-content: flex-start; - background-color: $input-bg; - @include border-radius($modal-content-border-radius); -} - -label:not(.form-check-label):not(.btn), label.bold { - font-weight: 600; -} - -input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: searchfield-cancel-button; -} - -.btn[class*="btn-outline-"] { - &:not(:hover) { - border-color: $secondary; - background-color: #fbfbfb; - } -} - -.btn-link { - &:focus, - &.focus { - outline-color: -webkit-focus-ring-color; - outline-offset: 1px; - outline-style: auto; - outline-width: 1px; - } -} - -.btn-outline-secondary { - color: $text-muted; - - &:hover:not(:disabled) { - color: $body-color; - } - - &:disabled { - opacity: 1; - } - - &:focus, - &.focus { - box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($primary), $primary, 15%), .5); - } -} - -.btn-submit { - position: relative; - - .fa-spinner { - position: absolute; - display: none; - align-items: center; - justify-content: center; - bottom: 0; - top: 0; - left: 0; - right: 0; - } - - &:disabled:not(.manual), &.loading { - .fa-spinner { - display: flex; - } - - span { - visibility: hidden; - } - } -} - -.list-group-item { - &:focus, - &.focus { - z-index: 100; - } -} - -.fa-icon-above-input { - height: 1.5em; -} - -.table.table-list { - thead th { - border-top: none; - } - - tr:first-child { - td { - border: none; - } - } - - td { - vertical-align: middle; - - &.reduced-lh { - line-height: 1; - small { - font-size: 80%; - } - } - - small, > .fa, .icon { - color: $text-muted; - } - } - - td.wrap { - word-break: break-all; - } - - td.table-list-options { - width: 76px; - max-width: 76px; - text-align: right; - height: 50px; - - &.wider { - width: 100px; - max-width: 100px; - } - - .btn { - line-height: 1; - transition: initial; - } - - .dropdown-menu { - line-height: $line-height-base; - } - } - - tr:not(:hover) td.table-list-options { - > .dropdown:not(.show) button:not(:focus):not(:active), > button:not(:focus):not(:active) { - @extend .sr-only; - } - } - - td.table-list-icon { - width: 45px; - max-width: 45px; - text-align: center; - - img { - @extend .rounded; - @extend .img-fluid; - max-height: 24px; - } - } - - td.table-list-checkbox { - width: 35px; - max-width: 35px; - } - - td.table-list-strike { - color: $text-muted; - text-decoration: line-through; - } -} - -.text-lg { - font-size: $font-size-lg; -} - -.text-strike { - text-decoration: line-through; -} - -.font-weight-semibold { - font-weight: 600; -} - -.password-wrapper { - word-break: break-all; - white-space: pre-wrap; - min-width: 0; -} - -.password-row { - min-width: 0; -} - -.password-number { - color: #007fde; -} - -.password-special { - color: #c40800; -} - -app-vault-groupings, app-org-vault-groupings, .groupings { - .card { - #search { - margin-bottom: 1rem; - } - - h3 { - font-weight: normal; - text-transform: uppercase; - color: $text-muted; - } - - ul:last-child { - margin-bottom: 0; - } - - .card-body a { - color: $body-color; - - &:hover { - &.text-muted { - color: $body-color !important; - } - } - } - - .show-active { - display: none; - } - - li { - > .fa, > div > .fa { - cursor: pointer; - } - } - - li.active { - > .show-active, > div .show-active { - display: inline; - } - } - - li.active { - > a:first-of-type, > div a:first-of-type { - font-weight: bold; - color: theme-color("primary"); - } - - > .fa, > div > .fa { - color: theme-color("primary"); - } - } - } -} - -app-password-generator { - #lengthRange { - width: 100%; - } - - .card-password { - .card-body { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; - text-align: center; - font-size: $font-size-lg; - font-family: $font-family-monospace; - } - } -} - -app-password-generator-history { - .list-group-item { - line-height: 1; - - .password { - font-family: $font-family-monospace; - } - } -} - -app-import { - textarea { - height: 150px; - } -} - -app-avatar { - img { - @extend .rounded; - } -} - -app-user-billing { - .progress { - height: 20px; - - .progress-bar { - min-width: 50px; - } - } -} - -#duo-frame { - background: url('../images/loading.svg') 0 0 no-repeat; - height: 330px; - - iframe { - width: 100%; - height: 100%; - border: none; - } -} - -#web-authn-frame { - background: url('../images/loading.svg') 0 0 no-repeat; - height: 290px; - - iframe { - width: 100%; - height: 100%; - border: none; - } -} - -#hcaptcha_iframe { - width: 100%; - border: none; - transition: height 0.25s linear; -} - -#bt-dropin-container { - background: url('../images/loading.svg') 0 0 no-repeat; - min-height: 50px; -} - -.braintree-placeholder, .braintree-sheet__header { - display: none; -} - -.braintree-sheet__content--button { - text-align: left; - padding: 0; - min-height: 0; -} - -.braintree-sheet__container { - margin-bottom: 0; -} - -.braintree-sheet { - border: none; -} - -.totp { - .totp-code { - @extend .text-monospace; - font-size: 1.2rem; - } - - .totp-countdown { - margin: 3px 3px 0 0; - display: block; - user-select: none; - - .totp-sec { - font-size: 0.85em; - position: absolute; - line-height: 32px; - width: 32px; - text-align: center; - } - - svg { - width: 32px; - height: 32px; - transform: rotate(-90deg); - } - - .totp-circle { - fill: none; - stroke: $primary; - - &.inner { - stroke-width: 3; - stroke-dasharray: 78.6; - stroke-dashoffset: 0; - } - - &.outer { - stroke-width: 2; - stroke-dasharray: 88; - stroke-dashoffset: 0; - } - } - } - - > .align-items-center { - margin-bottom: -5px; - } - - &.low { - .totp-sec, .totp-code { - color: $danger; - } - - .totp-circle { - stroke: $danger; - } - } -} - -.callout { - padding: $alert-padding-y $alert-padding-x; - margin-bottom: $alert-margin-bottom; - border: 1px solid $card-border-color; - border-left-width: 5px; - border-radius: $card-inner-border-radius; - background-color: #fafafa; - - .callout-heading { - margin-top: 0; - } - - h3.callout-heading { - font-weight: bold; - text-transform: uppercase; - } - - &.callout-primary { - border-left-color: $primary; - - .callout-heading { - color: $primary; - } - } - - &.callout-info { - border-left-color: $gray-800; - - .callout-heading { - color: $gray-800; - } - } - - &.callout-danger { - border-left-color: $danger; - - .callout-heading { - color: $danger; - } - } - - &.callout-success { - border-left-color: $success; - - .callout-heading { - color: $success; - } - } - - &.callout-warning { - border-left-color: $warning; - - .callout-heading { - color: $warning; - } - } - - .enforced-policy-options ul { - margin-bottom: 0px; - } -} - -.list-group-2fa { - .logo-2fa { - min-width: 100px; - } -} - -.form-check-block { - .form-check-label { - font-weight: 600; - - > small { - display: block; - color: $text-muted; - font-weight: normal; - } - - > span { - display: block; - font-weight: normal; - @extend .mt-2; - } - } -} - -.form-check-block + .form-check-block { - &:not(.mt-2) { - @extend .mt-3; - } -} - -.form-inline { - input[type='datetime-local'] { - width: 200px; - } -} - -.form-control.stripe-form-control { - padding-top: 0.55rem; - - &.is-focused { - color: $input-focus-color; - background-color: $input-focus-bg; - border-color: $input-focus-border-color; - outline: 0; - box-shadow: $input-focus-box-shadow; - - &.is-invalid { - box-shadow: 0 0 0 $input-focus-width rgba($form-feedback-invalid-color, .25); - } - } - - &.is-invalid { - border-color: $form-feedback-invalid-color; - } -} - -.org-nav { - background-color: $input-bg; - border-bottom: 1px solid $border-color; - height: 100px; - min-height: 100px; - - .container { - height: 100%; - } - - .nav-tabs { - border-bottom: none; - - a { - color: $body-color; - - &:not(.active) { - border-color: transparent; - } - - &.active { - border-top: 3px solid theme-color("primary"); - font-weight: bold; - padding-top: calc(#{$nav-link-padding-y} - 2px); - } - - &.disabled { - color: $input-placeholder-color; - } - } - } - - .org-name { - line-height: 1; - span { - font-size: $font-size-lg; - display: block; - } - } -} - -img.logo { - width: 284px; - height: 43px; - margin: 0 auto; - display: block; -} - -.min-height-fix { - min-height: 1px; -} - -.overflow-hidden { - overflow: hidden; -} - -.cdk-drag-preview { - z-index: $zindex-tooltip !important; - opacity: 0.8; - background-color: $white; - border-radius: $border-radius; -} - -.cursor-move { - cursor: move !important; -} - -@import "./register-layout"; +@import "./tables"; +@import "./toasts"; diff --git a/src/scss/tables.scss b/src/scss/tables.scss new file mode 100644 index 00000000000..cb4fe220222 --- /dev/null +++ b/src/scss/tables.scss @@ -0,0 +1,123 @@ +.table.table-list { + @include themify($themes) { + color: themed('textColor'); + } + + &.table td, .table th { + &:not(tr:first-child td) { + @include themify($themes) { + border-top: 1px solid themed('tableSeparator'); + } + } + } + + thead th { + border-top: none; + } + + tr:first-child { + td { + border: none; + } + } + + td { + vertical-align: middle; + @include themify($themes) { + color: themed('textColor'); + } + + & > a { + @include themify($themes) { + color: themed('tableLinkColor'); + } + &:hover { + @include themify($themes) { + color: themed('tableLinkColorHover'); + } + } + } + + &.reduced-lh { + line-height: 1; + + small { + font-size: 80%; + } + } + + small, > .fa, .icon { + @include themify($themes) { + color: themed('textMuted'); + } + } + + .fa-globe { + @include themify($themes) { + color: themed('iconColor'); + } + } + } + + td.wrap { + word-break: break-all; + } + + td.table-list-options { + height: 50px; + max-width: 76px; + text-align: right; + width: 76px; + + &.wider { + max-width: 100px; + width: 100px; + } + + .btn { + line-height: 1; + transition: initial; + } + + .dropdown-menu { + line-height: $line-height-base; + } + } + + tr:not(:hover) td.table-list-options { + > .dropdown:not(.show) button:not(:focus):not(:active), > button:not(:focus):not(:active) { + @extend .sr-only; + } + } + + td.table-list-icon { + max-width: 45px; + text-align: center; + width: 45px; + + img { + @extend .rounded; + @extend .img-fluid; + max-height: 24px; + } + } + + td.table-list-checkbox { + max-width: 35px; + width: 35px; + } + + td.table-list-strike { + text-decoration: line-through; + @include themify($themes) { + color: themed('textMuted'); + } + } +} + +.table-hover tbody tr:hover { + @include themify($themes) { + background-color: themed('tableRowHover'); + color: themed('tableColorHover'); + } +} diff --git a/src/scss/toasts.scss b/src/scss/toasts.scss new file mode 100644 index 00000000000..62adbcea286 --- /dev/null +++ b/src/scss/toasts.scss @@ -0,0 +1,110 @@ +.toast-container { + &.toast-top-right { + top: 76px; + } + + .toast-close-button { + font-size: 18px; + margin-right: 4px; + } + + .toast { + align-items: center; + background-image: none !important; + border-radius: $border-radius; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); + display: flex; + opacity: 1 !important; + + &:hover { + box-shadow: 0 0 10px rgba(0, 0, 0, 0.6); + } + + &:before { + color: #FFFFFF; + float: left; + font-family: FontAwesome; + font-size: 25px; + line-height: 20px; + margin: auto 0 auto 15px; + } + + .toast-content { + padding: 15px; + } + + .toaster-icon { + display: none; + } + + .toast-message { + p { + margin-bottom: 0.5rem; + + &:last-child { + margin-bottom: 0; + } + } + } + + &.toast-danger, &.toast-error { + background-image: none !important; + + &:before { + content: "\f0e7"; + } + } + + &.toast-warning { + background-image: none !important; + + &:before { + content: "\f071"; + } + } + + &.toast-info { + background-image: none !important; + + &:before { + content: "\f05a"; + } + } + + &.toast-success { + background-image: none !important; + + &:before { + content: "\f00C"; + } + } + } +} + +.toast-error, .toast-container .toast-error:before, .toast-danger, .toast-container .toast-danger:before, .bg-danger { + @include themify($themes) { + background-color: themed('danger') !important; + color: themed('dangerTextColor') !important; + } +} + +.toast-warning, .toast-container .toast-warning:before, .bg-warning { + @include themify($themes) { + background-color: themed('warning') !important; + color: themed('warningTextColor') !important; + } +} + +.toast-success, .toast-container .toast-success:before, .bg-success { + @include themify($themes) { + background-color: themed('success') !important; + color: themed('successTextColor') !important; + } +} + +.toast-info, .toast-container .toast-info:before, .bg-info { + @include themify($themes) { + background-color: themed('info') !important; + color: themed('infoTextColor') !important; + } +} diff --git a/src/scss/variables.scss b/src/scss/variables.scss new file mode 100644 index 00000000000..34947251f92 --- /dev/null +++ b/src/scss/variables.scss @@ -0,0 +1,349 @@ +$primary: #175DDC; +$primary-accent: #1252A3; +$secondary: #CED4DA; +$secondary-alt: #1A3B66; +$success: #00A65A; +$info: #555555; +$warning: #BF7E16; +$danger: #DD4B39; +$white: #FFFFFF; + +$theme-colors: ( + "primary-accent": $primary-accent, + "secondary-alt": $secondary-alt, +); + +$body-bg: $white; +$body-color: #333333; + +$font-family-sans-serif: 'Open Sans','Helvetica Neue',Helvetica, + Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol'; + +$h1-font-size: 1.7rem; +$h2-font-size: 1.3rem; +$h3-font-size: 1rem; +$h4-font-size: 1rem; +$h5-font-size: 1rem; +$h6-font-size: 1rem; + +$small-font-size: 90%; +$font-size-lg: 1.15rem; +$code-font-size: 100%; + +$navbar-padding-y: .75rem; +$grid-gutter-width: 20px; +$card-spacer-y: .6rem; + +$list-group-item-padding-y: .6rem; +$list-group-active-color: $body-color; +$list-group-active-bg: $white; +$list-group-active-border-color: rgba(#000000, .125); + +$dropdown-link-color: $body-color; +$dropdown-link-hover-bg: rgba(#000000, .06); +$dropdown-link-active-color: $dropdown-link-color; +$dropdown-link-active-bg: rgba(#000000, .1); +$dropdown-item-padding-x: 1rem; + +$navbar-brand-font-size: 35px; +$navbar-brand-height: 35px; +$navbar-brand-padding-y: 0; +$navbar-dark-color: rgba($white, .7); +$navbar-dark-hover-color: rgba($white, .9); +$navbar-nav-link-padding-x: 0.8rem; + +$input-bg: #FBFBFB; +$input-focus-bg: $white; +$input-disabled-bg: #E0E0E0; +$input-placeholder-color: #B4B4B4; + +$table-accent-bg: rgba(#000000, .02); +$table-hover-bg: rgba(#000000, .03); + +$modal-backdrop-opacity: 0.3; +$btn-font-weight: 600; +$lead-font-weight: normal; + +$grid-breakpoints: ( + xs: 0, + sm: 1px, + md: 2px, + lg: 3px, + xl: 4px +); + +$text-color: #333333; +$border-color: $secondary; + +$fa-font-path: "~font-awesome/fonts"; + +// Theme Variables + +$lightDangerHover: #C43421; + +$darkPrimary: #6A99F0; +$darkPrimary-alt: #B4CCF9; +$darkDanger: #FF8D85; +$darkDangerHover: #FFBfBB; +$darkSuccess: #52E07C; +$darkWarning: #FFEB66; +$darkInfo: #A4B0C6; +$darkLinks: #6A99F0; +$darkGrey1: #BAC0CE; +$darkGrey2: #8D94A5; +$darkBlue1: #4C525F; +$darkBlue2: #3C424E; +$darkDarkBlue1: #2F343D; +$darkDarkBlue2: #1F242E; + +$lightInputColor: #465057; +$lightInputPlaceholderColor: #B6B8B8; +$darkInputColor: $white; +$darkInputPlaceholderColor: $darkGrey1; + +$themes: ( + light: ( + logoSuffix: 'dark', + primary: $primary, + primaryAlt: $primary-accent, + danger: $danger, + success: $success, + warning: $warning, + info: #343A40, + textColor: $text-color, + headingColor: #333333, + darkTextColor: #495057, + warningTextColor: $white, + successTextColor: $white, + dangerTextColor: $white, + infoTextColor: $white, + textMuted: #6C757D, + backgroundColor: $body-bg, + badgeDangerBackground: $danger, + badgeDangerText: $white, + badgeInfoBackground: #555555, + badgeInfoText: $white, + badgePrimaryBackground: $primary, + badgePrimaryBackgroundHover: #134EB9, + badgePrimaryText: $white, + badgeSecondaryBackground: #CED4DA, + badgeSecondaryText: #212529, + bgPrimaryColor: $primary, + bgLightColor: #F8F9FA, + borderColor: $border-color, + borderPrimaryColor: $primary, + btnDanger: $danger, + btnDangerHover: $lightDangerHover, + btnDangerText: $white, + btnLinkTextColor: $primary, + btnLinkTextColorHover: #104097, + btnOutlineDangerBackground: $input-bg, + btnOutlineDangerBackgroundHover: $danger, + btnOutlineDangerBorder: #CED4DA, + btnOutlineDangerBorderHover: $danger, + btnOutlineDangerText: $danger, + btnOutlineDangerTextHover: $white, + btnOutlinePrimaryBackground: $input-bg, + btnOutlinePrimaryBackgroundHover: $primary, + btnOutlinePrimaryBorder: #CED4DA, + btnOutlinePrimaryBorderHover: $primary, + btnOutlinePrimaryText: $primary, + btnOutlinePrimaryTextHover: $white, + btnOutlineSecondaryBackground: $input-bg, + btnOutlineSecondaryBackgroundHover: #CED4DA, + btnOutlineSecondaryBorder: #CED4DA, + btnOutlineSecondaryBorderHover: #CED4DA, + btnOutlineSecondaryText: #6C757D, + btnOutlineSecondaryTextHover: #333333, + btnPrimary: $primary, + btnPrimaryBorderHover: #1249AE, + btnPrimaryHover: #134EB9, + btnPrimaryText: $white, + btnSecondary: $secondary, + btnSecondaryBorder: $secondary, + btnSecondaryBorderHover: #B1BBC4, + btnSecondaryHover: #B8C1CA, + btnSecondaryText: #212529, + btnSecondaryTextHover: #212529, + calloutBackground: #FAFAFA, + calloutColor: #212529, + cdkDraggingBackground: $white, + codeColor: #E83E8C, + dropdownBackground: $white, + dropdownDangerHover: #C43421, + dropdownHover: rgba(0,0,0,0.06), + dropdownTextColor: $text-color, + dropdownTextMuted: #6C757D, + focus: rgb(23 93 220 / 25%), + footerBackgroundColor: #FBFBFB, + foregroundColor: $white, + headerColor: rgba(0,0,0,0.03), + iconColor: #777777, + iconHover: $body-color, + imgFilter: invert(0) grayscale(0), + inputBackgroundColor: #FBFBFB, + inputBorderColor: $border-color, + inputDisabledBackground: #E0E0E0, + inputDisabledColor: #6C757D, + inputPlaceholderColor: $lightInputPlaceholderColor, + inputTextColor: $lightInputColor, + layoutFrontendColor: #ECF0F5, + learnMoreHover: #104097, + linkColor: $primary, + linkColorHover: #104097, + linkWeight: 400, + listItemActive: $text-color, + listItemBorder: rgba(0,0,0,.125), + listItemColor: $primary, + listItemColorHover: #104097, + loadingSvg: url('../images/loading.svg'), + navActiveBackground: $white, + navActiveWeight: 600, + navBackground: $primary, + navOrgBackgroundColor: #FBFBFB, + navWeight: 600, + pwLetter: $text-color, + pwNumber: #007FDE, + pwSpecial: #C40800, + pwStrengthBackground: #E9ECEF, + registerHeadingColor: $white, + separator: $secondary, + separatorHr: rgb(0,0,0,0.1), + tableColorHover: #333333, + tableLinkColor: $primary, + tableLinkColorHover: #104097, + tableRowHover: rgba(0,0,0,0.03), + tableSeparator: #DEE2E6 + ), + dark: ( + logoSuffix: 'white', + primary: $darkPrimary, + primaryAlt: $darkPrimary-alt, + danger: $darkDanger, + success: $darkSuccess, + warning: $darkWarning, + info: $darkInfo, + textColor: $darkGrey1, + headingColor: $white, + darkTextColor: $darkDarkBlue2, + warningTextColor: $darkDarkBlue1, + successTextColor: $darkDarkBlue1, + dangerTextColor: $white, + infoTextColor: $white, + textMuted: $darkGrey1, + backgroundColor: $darkDarkBlue2, + badgeDangerBackground: $darkDanger, + badgeDangerText: $darkDarkBlue2, + badgeInfoBackground: $darkInfo, + badgeInfoText: $darkDarkBlue2, + badgePrimaryBackground: $darkLinks, + badgePrimaryBackgroundHover: $darkPrimary-alt, + badgePrimaryText: $darkDarkBlue2, + badgeSecondaryBackground: $darkDarkBlue2, + badgeSecondaryText: $white, + bgPrimaryColor: $darkPrimary, + bgLightColor: $darkDarkBlue2, + borderColor: $darkBlue1, + borderPrimaryColor: $darkPrimary, + btnDanger: $darkDanger, + btnDangerHover: $darkDangerHover, + btnDangerText: $white, + btnLinkText: $darkGrey1, + btnLinkTextHover: $white, + btnOutlineDangerBackground: $darkDanger, + btnOutlineDangerBackgroundHover: $darkDangerHover, + btnOutlineDangerBorder: $darkDanger, + btnOutlineDangerBorderHover: $darkDangerHover, + btnOutlineDangerText: $darkDarkBlue2, + btnOutlineDangerTextHover: $darkDarkBlue2, + btnOutlinePrimaryBackground: $darkPrimary, + btnOutlinePrimaryBackgroundHover: $darkPrimary-alt, + btnOutlinePrimaryBorder: $darkPrimary, + btnOutlinePrimaryBorderHover: $darkPrimary-alt, + btnOutlinePrimaryText: $darkDarkBlue2, + btnOutlinePrimaryTextHover: $darkDarkBlue2, + btnOutlineSecondaryBackground: transparent, + btnOutlineSecondaryBackgroundHover: transparent, + btnOutlineSecondaryBorder: $darkGrey1, + btnOutlineSecondaryBorderHover: $darkGrey2, + btnOutlineSecondaryText: $white, + btnOutlineSecondaryTextHover: $darkGrey2, + btnPrimary: $darkLinks, + btnPrimaryBorderHover: $darkPrimary-alt, + btnPrimaryHover: $darkPrimary-alt, + btnPrimaryText: $darkDarkBlue2, + btnSecondary: transparent, + btnSecondaryBorder: $darkGrey1, + btnSecondaryBorderHover: $darkGrey2, + btnSecondaryHover: transparent, + btnSecondaryText: $white, + btnSecondaryTextHover: $darkGrey2, + calloutBackground: $darkBlue2, + calloutColor: $white, + cdkDraggingBackground: $darkDarkBlue1, + codeColor: #E83E8C, + dropdownBackground: $darkDarkBlue1, + dropdownDangerHover: $darkDangerHover, + dropdownHover: rgba(0,0,0,0.03), + dropdownTextColor: $white, + dropdownTextMuted: #BEC6CF, + focus: rgb(106 153 240 / 25%), + footerBackgroundColor: $darkBlue1, + foregroundColor: $darkDarkBlue1, + headerColor: $darkBlue1, + iconColor: #777777, + iconHover: $darkGrey2, + imgFilter: invert(1) grayscale(1), + inputBackgroundColor: $darkDarkBlue1, + inputBorderColor: $darkGrey1, + inputDisabledBackground: $darkBlue2, + inputDisabledColor: $darkGrey1, + inputPlaceholderColor: $darkInputPlaceholderColor, + inputTextColor: $darkInputColor, + layoutFrontendColor: $darkDarkBlue2, + learnMoreHover: $darkPrimary-alt, + linkColor: $darkLinks, + linkColorHover: $darkLinks, + linkWeight: 600, + listItemActive: $darkPrimary, + listItemBorder: $darkBlue1, + listItemColor: $white, + listItemColorHover: $white, + loadingSvg: url('../images/loading-white.svg'), + navActiveBackground: $darkDarkBlue1, + navActiveWeight: 600, + navBackground: $darkDarkBlue1, + navOrgBackgroundColor: #161C26, + navWeight: 400, + pwLetter: $white, + pwNumber: #52BDFB, + pwSpecial: #FF7C70, + pwStrengthBackground: $darkBlue2, + registerHeadingColor: $white, + separator: $darkBlue1, + separatorHr: $darkBlue1, + tableColorHover: $darkGrey1, + tableLinkColor: $white, + tableLinkColorHover: $white, + tableRowHover: rgba(0,0,0,0.03), + tableSeparator: $darkBlue1 + ), +); + +@mixin themify($themes: $themes) { + @each $theme, $map in $themes { + html.theme_#{$theme} & { + $theme-map: () !global; + @each $key, $submap in $map { + $value: map-get(map-get($themes, $theme), '#{$key}'); + $theme-map: map-merge($theme-map, ($key: $value)) !global; + } + @content; + $theme-map: null !global; + } + } +}; + +@function themed($key) { + @return map-get($theme-map, $key); +}; diff --git a/src/services/htmlStorage.service.ts b/src/services/htmlStorage.service.ts index ae855bc2359..d1516987b17 100644 --- a/src/services/htmlStorage.service.ts +++ b/src/services/htmlStorage.service.ts @@ -7,7 +7,7 @@ export class HtmlStorageService implements StorageService { ConstantsService.disableFaviconKey, 'rememberEmail', 'enableGravatars', 'enableFullWidth', ConstantsService.localeKey, ConstantsService.autoConfirmFingerprints, ConstantsService.vaultTimeoutKey, ConstantsService.vaultTimeoutActionKey, ConstantsService.ssoCodeVerifierKey, - ConstantsService.ssoStateKey, 'ssoOrgIdentifier']); + ConstantsService.ssoStateKey, 'ssoOrgIdentifier', ConstantsService.themeKey]); private localStorageStartsWithKeys = ['twoFactorToken_', ConstantsService.collapsedGroupingsKey + '_']; private memoryStorageStartsWithKeys = ['ciphers_', 'folders_', 'collections_', 'settings_', 'lastSync_']; private memoryStorage = new Map(); diff --git a/src/services/webPlatformUtils.service.ts b/src/services/webPlatformUtils.service.ts index 2a881600e0c..d7764f1dfde 100644 --- a/src/services/webPlatformUtils.service.ts +++ b/src/services/webPlatformUtils.service.ts @@ -1,19 +1,24 @@ import Swal, { SweetAlertIcon } from 'sweetalert2'; import { DeviceType } from 'jslib-common/enums/deviceType'; +import { ThemeType } from 'jslib-common/enums/themeType'; import { I18nService } from 'jslib-common/abstractions/i18n.service'; import { LogService } from 'jslib-common/abstractions/log.service'; import { MessagingService } from 'jslib-common/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; +import { StorageService } from 'jslib-common/abstractions/storage.service'; + +import { ConstantsService } from 'jslib-common/services/constants.service'; export class WebPlatformUtilsService implements PlatformUtilsService { identityClientId: string = 'web'; private browserCache: DeviceType = null; + private prefersColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)'); constructor(private i18nService: I18nService, private messagingService: MessagingService, - private logService: LogService) { } + private logService: LogService, private storageService: () => StorageService) { } getDevice(): DeviceType { if (this.browserCache != null) { @@ -283,11 +288,24 @@ export class WebPlatformUtilsService implements PlatformUtilsService { return false; } - getDefaultSystemTheme() { - return Promise.resolve(null as 'light' | 'dark'); + getDefaultSystemTheme(): Promise { + return Promise.resolve(this.prefersColorSchemeDark.matches ? ThemeType.Dark : ThemeType.Light); } - onDefaultSystemThemeChange() { - /* noop */ + async getEffectiveTheme(): Promise { + const theme = await this.storageService().get(ConstantsService.themeKey); + if (theme === ThemeType.Dark) { + return ThemeType.Dark; + } else if (theme === ThemeType.System) { + return this.getDefaultSystemTheme(); + } else { + return ThemeType.Light; + } + } + + onDefaultSystemThemeChange(callback: ((theme: ThemeType.Light | ThemeType.Dark) => unknown)) { + this.prefersColorSchemeDark.addEventListener('change', ({ matches }) => { + callback(matches ? ThemeType.Dark : ThemeType.Light); + }); } } diff --git a/webpack.config.js b/webpack.config.js index 094a1d453bb..857685e5abc 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -28,7 +28,7 @@ const moduleRules = [ }, { test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, - exclude: /loading.svg/, + exclude: /loading(|-white).svg/, use: [{ loader: 'file-loader', options: {