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 e9a6014e..9ed57950 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 64bc4c36..7bc73e49 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 206ef610..ce71c0c0 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 3f12f6e0..dc8d295f 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 e2078d0b..26120564 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 1a33b79c..eb69f1e4 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 03f68183..de6d6e75 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 bdc5df36..48e8ffd8 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 aad583f5..b218cf81 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 4a51f500..0c8b11a5 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 160c7148..d6e7fbf8 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 00000000..30239140 --- /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 00000000..c9b1f01b Binary files /dev/null and b/src/images/logo-white@2x.png differ diff --git a/src/images/totp-countdown.png b/src/images/totp-countdown.png index 87b9fe14..76aafabf 100644 Binary files a/src/images/totp-countdown.png and b/src/images/totp-countdown.png differ diff --git a/src/index.html b/src/index.html index 3c663967..36dad0d5 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,23 @@ Bitwarden Web Vault + + @@ -19,7 +36,7 @@
- +

diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index e87893e1..2b4a9ae4 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 00000000..b4ba0a16 --- /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 00000000..74ab5771 --- /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 00000000..43763dba --- /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 00000000..b1f514bd --- /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 00000000..f727344c --- /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 00000000..c4b74b98 --- /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 00000000..5d372aaf --- /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 00000000..d39b1714 --- /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 00000000..230cdbef --- /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 00000000..aa19235c --- /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 3fc34da1..5415f801 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 62af3e37..00000000 --- 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 deadb006..45a91fe1 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 00000000..cb4fe220 --- /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 00000000..62adbcea --- /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 00000000..34947251 --- /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 ae855bc2..d1516987 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 2a881600..d7764f1d 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 094a1d45..857685e5 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: {