diff --git a/apps/web/src/app/accounts/login/login.component.html b/apps/web/src/app/accounts/login/login.component.html index b9529295671..7436bca7538 100644 --- a/apps/web/src/app/accounts/login/login.component.html +++ b/apps/web/src/app/accounts/login/login.component.html @@ -77,18 +77,12 @@ {{ "masterPass" | i18n }} - + {{ "getMasterPasswordHint" | i18n diff --git a/apps/web/src/app/accounts/register-form/register-form.component.html b/apps/web/src/app/accounts/register-form/register-form.component.html index 9ae891a4701..5fc8bf61ccd 100644 --- a/apps/web/src/app/accounts/register-form/register-form.component.html +++ b/apps/web/src/app/accounts/register-form/register-form.component.html @@ -34,16 +34,16 @@ - + Important: {{ "masterPassImportant" | i18n }} @@ -65,16 +65,16 @@ - + diff --git a/apps/web/src/app/tools/import-export/export.component.html b/apps/web/src/app/tools/import-export/export.component.html index 909131b6c76..01cd7f10ad8 100644 --- a/apps/web/src/app/tools/import-export/export.component.html +++ b/apps/web/src/app/tools/import-export/export.component.html @@ -84,73 +84,41 @@
-
- - {{ "filePassword" | i18n }} - - -
- -
-
-
- {{ "exportPasswordDescription" | i18n }} -
-
-
- - {{ "confirmFilePassword" | i18n }} - -
- -
-
-
+ + {{ "filePassword" | i18n }} + + + {{ "exportPasswordDescription" | i18n }} + + + {{ "confirmFilePassword" | i18n }} + + +
diff --git a/apps/web/src/app/tools/import-export/export.component.ts b/apps/web/src/app/tools/import-export/export.component.ts index 6087d7a2027..135b3ace14b 100644 --- a/apps/web/src/app/tools/import-export/export.component.ts +++ b/apps/web/src/app/tools/import-export/export.component.ts @@ -23,6 +23,7 @@ import { UserVerificationPromptComponent } from "../../components/user-verificat export class ExportComponent extends BaseExportComponent { organizationId: string; encryptedExportType = EncryptedExportType; + protected showFilePassword: boolean; constructor( cryptoService: CryptoService, diff --git a/apps/web/src/app/tools/import-export/file-password-prompt.component.html b/apps/web/src/app/tools/import-export/file-password-prompt.component.html index 7250cc9e833..a42aae99a8b 100644 --- a/apps/web/src/app/tools/import-export/file-password-prompt.component.html +++ b/apps/web/src/app/tools/import-export/file-password-prompt.component.html @@ -14,32 +14,17 @@ class="tw-border-0 tw-border-t tw-border-solid tw-border-secondary-300 tw-pr-3.5 tw-pt-3.5 tw-pl-3.5" > {{ "confirmVaultImportDesc" | i18n }} - + {{ "confirmFilePassword" | i18n }} - +
- - + > @@ -59,40 +57,33 @@ id="clientSecret" /> - - - + + + + + > + > {{ "scimApiKeyHelperText" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/organizations/manage/sso.component.html b/bitwarden_license/bit-web/src/app/organizations/manage/sso.component.html index 01c435b722b..98b4bec957e 100644 --- a/bitwarden_license/bit-web/src/app/organizations/manage/sso.component.html +++ b/bitwarden_license/bit-web/src/app/organizations/manage/sso.component.html @@ -151,28 +151,24 @@ {{ "callbackPath" | i18n }} + > {{ "signedOutCallbackPath" | i18n }} + > @@ -292,14 +288,12 @@ {{ "spEntityId" | i18n }} + > @@ -315,28 +309,24 @@ + > {{ "spAcsUrl" | i18n }} + > diff --git a/libs/angular/src/components/export.component.ts b/libs/angular/src/components/export.component.ts index b38b1762714..34f196d57ce 100644 --- a/libs/angular/src/components/export.component.ts +++ b/libs/angular/src/components/export.component.ts @@ -21,8 +21,6 @@ export class ExportComponent implements OnInit, OnDestroy { formPromise: Promise; disabledByPolicy = false; - showFilePassword: boolean; - showConfirmFilePassword: boolean; exportForm = this.formBuilder.group({ format: ["json"], @@ -199,16 +197,6 @@ export class ExportComponent implements OnInit, OnDestroy { return this.exportForm.get("fileEncryptionType").value; } - toggleFilePassword() { - this.showFilePassword = !this.showFilePassword; - document.getElementById("filePassword").focus(); - } - - toggleConfirmFilePassword() { - this.showConfirmFilePassword = !this.showConfirmFilePassword; - document.getElementById("confirmFilePassword").focus(); - } - adjustValidators() { this.exportForm.get("confirmFilePassword").reset(); this.exportForm.get("filePassword").reset(); diff --git a/libs/components/src/async-actions/in-forms.stories.ts b/libs/components/src/async-actions/in-forms.stories.ts index b492032df12..ef789dc106a 100644 --- a/libs/components/src/async-actions/in-forms.stories.ts +++ b/libs/components/src/async-actions/in-forms.stories.ts @@ -27,6 +27,7 @@ const template = ` Email + @@ -47,6 +48,12 @@ class PromiseExampleComponent { constructor(private formBuilder: FormBuilder) {} + refresh = async () => { + await new Promise((resolve, reject) => { + setTimeout(resolve, 2000); + }); + }; + submit = async () => { this.formObj.markAllAsTouched(); @@ -78,6 +85,10 @@ class ObservableExampleComponent { constructor(private formBuilder: FormBuilder) {} + refresh = () => { + return of("fake observable").pipe(delay(2000)); + }; + submit = () => { this.formObj.markAllAsTouched(); diff --git a/libs/components/src/button/button.component.html b/libs/components/src/button/button.component.html index 836eb0f9655..ee4d150dfcc 100644 --- a/libs/components/src/button/button.component.html +++ b/libs/components/src/button/button.component.html @@ -1,6 +1,5 @@ - { expect(buttonDebugElement.nativeElement.classList.contains("tw-border-danger-500")).toBe(true); expect(linkDebugElement.nativeElement.classList.contains("tw-border-danger-500")).toBe(true); + testAppComponent.buttonType = "unstyled"; + fixture.detectChanges(); + expect( + Array.from(buttonDebugElement.nativeElement.classList).some((klass: string) => + klass.startsWith("tw-bg") + ) + ).toBe(false); + expect( + Array.from(linkDebugElement.nativeElement.classList).some((klass: string) => + klass.startsWith("tw-bg") + ) + ).toBe(false); + testAppComponent.buttonType = null; fixture.detectChanges(); expect(buttonDebugElement.nativeElement.classList.contains("tw-border-text-muted")).toBe(true); diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index aeec8dfa688..0f3589ebf74 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -1,10 +1,15 @@ import { Input, HostBinding, Component } from "@angular/core"; -import { ButtonLikeAbstraction } from "../shared/button-like.abstraction"; +import { ButtonLikeAbstraction, ButtonType } from "../shared/button-like.abstraction"; -export type ButtonTypes = "primary" | "secondary" | "danger"; +const focusRing = [ + "focus-visible:tw-ring", + "focus-visible:tw-ring-offset-2", + "focus-visible:tw-ring-primary-700", + "focus-visible:tw-z-10", +]; -const buttonStyles: Record = { +const buttonStyles: Record = { primary: [ "tw-border-primary-500", "tw-bg-primary-500", @@ -15,6 +20,7 @@ const buttonStyles: Record = { "disabled:tw-border-primary-500/60", "disabled:!tw-text-contrast/60", "disabled:tw-bg-clip-padding", + ...focusRing, ], secondary: [ "tw-bg-transparent", @@ -26,6 +32,7 @@ const buttonStyles: Record = { "disabled:tw-bg-transparent", "disabled:tw-border-text-muted/60", "disabled:!tw-text-muted/60", + ...focusRing, ], danger: [ "tw-bg-transparent", @@ -37,7 +44,9 @@ const buttonStyles: Record = { "disabled:tw-bg-transparent", "disabled:tw-border-danger-500/60", "disabled:!tw-text-danger/60", + ...focusRing, ], + unstyled: [], }; @Component({ @@ -58,10 +67,6 @@ export class ButtonComponent implements ButtonLikeAbstraction { "tw-text-center", "hover:tw-no-underline", "focus:tw-outline-none", - "focus-visible:tw-ring", - "focus-visible:tw-ring-offset-2", - "focus-visible:tw-ring-primary-700", - "focus-visible:tw-z-10", ] .concat( this.block == null || this.block === false ? ["tw-inline-block"] : ["tw-w-full", "tw-block"] @@ -75,17 +80,14 @@ export class ButtonComponent implements ButtonLikeAbstraction { return disabled || this.loading ? true : null; } - @Input() buttonType: ButtonTypes = null; - + @Input() buttonType: ButtonType; @Input() block?: boolean; @Input() loading = false; @Input() disabled = false; - @Input("bitIconButton") icon: string; - - get iconClass() { - return [this.icon, "!tw-m-0"]; + setButtonType(value: "primary" | "secondary" | "danger" | "unstyled") { + this.buttonType = value; } } diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index 881824c6ae5..6619d7a3a3f 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -101,17 +101,3 @@ export const Block = BlockTemplate.bind({}); Block.args = { block: true, }; - -const IconTemplate: Story = (args) => ({ - props: args, - template: ` - - - - `, -}); - -export const Icon = IconTemplate.bind({}); -Icon.args = { - icon: "bwi-eye", -}; diff --git a/libs/components/src/form-field/form-field.stories.ts b/libs/components/src/form-field/form-field.stories.ts index 329f71c0ff5..c376d36a340 100644 --- a/libs/components/src/form-field/form-field.stories.ts +++ b/libs/components/src/form-field/form-field.stories.ts @@ -11,8 +11,10 @@ import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { AsyncActionsModule } from "../async-actions"; import { ButtonModule } from "../button"; import { CheckboxModule } from "../checkbox"; +import { IconButtonModule } from "../icon-button"; import { InputModule } from "../input/input.module"; import { RadioButtonModule } from "../radio-button"; import { I18nMockService } from "../utils/i18n-mock.service"; @@ -31,6 +33,8 @@ export default { FormFieldModule, InputModule, ButtonModule, + IconButtonModule, + AsyncActionsModule, CheckboxModule, RadioButtonModule, ], @@ -177,10 +181,13 @@ const ButtonGroupTemplate: Story = (args: BitFormFieldCom props: args, template: ` - Label - - - + + + + + `, }); @@ -195,9 +202,13 @@ const DisabledButtonInputGroupTemplate: Story = ( template: ` Label + - - + + + `, }); diff --git a/libs/components/src/form-field/password-input-toggle.directive.ts b/libs/components/src/form-field/password-input-toggle.directive.ts index 6189de636ea..3a3e3f116f6 100644 --- a/libs/components/src/form-field/password-input-toggle.directive.ts +++ b/libs/components/src/form-field/password-input-toggle.directive.ts @@ -3,13 +3,16 @@ import { Directive, EventEmitter, Host, + HostBinding, HostListener, Input, OnChanges, Output, } from "@angular/core"; -import { ButtonComponent } from "../button"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; + +import { BitIconButtonComponent } from "../icon-button/icon-button.component"; import { BitFormFieldComponent } from "./form-field.component"; @@ -17,9 +20,18 @@ import { BitFormFieldComponent } from "./form-field.component"; selector: "[bitPasswordInputToggle]", }) export class BitPasswordInputToggleDirective implements AfterContentInit, OnChanges { - @Input() toggled = false; + /** + * Whether the input is toggled to show the password. + */ + @HostBinding("attr.aria-pressed") @Input() toggled = false; @Output() toggledChange = new EventEmitter(); + @HostBinding("attr.title") title = this.i18nService.t("toggleVisibility"); + @HostBinding("attr.aria-label") label = this.i18nService.t("toggleVisibility"); + + /** + * Click handler to toggle the state of the input type. + */ @HostListener("click") onClick() { this.toggled = !this.toggled; this.toggledChange.emit(this.toggled); @@ -29,7 +41,11 @@ export class BitPasswordInputToggleDirective implements AfterContentInit, OnChan this.formField.input?.focus(); } - constructor(@Host() private button: ButtonComponent, private formField: BitFormFieldComponent) {} + constructor( + @Host() private button: BitIconButtonComponent, + private formField: BitFormFieldComponent, + private i18nService: I18nService + ) {} get icon() { return this.toggled ? "bwi-eye-slash" : "bwi-eye"; diff --git a/libs/components/src/form-field/password-input-toggle.spec.ts b/libs/components/src/form-field/password-input-toggle.spec.ts index 5c6a9d48d00..77001281b38 100644 --- a/libs/components/src/form-field/password-input-toggle.spec.ts +++ b/libs/components/src/form-field/password-input-toggle.spec.ts @@ -2,8 +2,12 @@ import { Component, DebugElement } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; -import { ButtonComponent, ButtonModule } from "../button"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; + +import { IconButtonModule } from "../icon-button"; +import { BitIconButtonComponent } from "../icon-button/icon-button.component"; import { InputModule } from "../input/input.module"; +import { I18nMockService } from "../utils/i18n-mock.service"; import { BitFormFieldControl } from "./form-field-control"; import { BitFormFieldComponent } from "./form-field.component"; @@ -17,7 +21,7 @@ import { BitPasswordInputToggleDirective } from "./password-input-toggle.directi Password - + `, @@ -26,21 +30,22 @@ class TestFormFieldComponent {} describe("PasswordInputToggle", () => { let fixture: ComponentFixture; - let button: ButtonComponent; + let button: BitIconButtonComponent; let input: BitFormFieldControl; let toggle: DebugElement; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FormFieldModule, ButtonModule, InputModule], + imports: [FormFieldModule, IconButtonModule, InputModule], declarations: [TestFormFieldComponent], + providers: [{ provide: I18nService, useValue: new I18nMockService({}) }], }).compileComponents(); fixture = TestBed.createComponent(TestFormFieldComponent); fixture.detectChanges(); toggle = fixture.debugElement.query(By.directive(BitPasswordInputToggleDirective)); - const buttonEl = fixture.debugElement.query(By.directive(ButtonComponent)); + const buttonEl = fixture.debugElement.query(By.directive(BitIconButtonComponent)); button = buttonEl.componentInstance; const formFieldEl = fixture.debugElement.query(By.directive(BitFormFieldComponent)); const formField: BitFormFieldComponent = formFieldEl.componentInstance; diff --git a/libs/components/src/form-field/password-input-toggle.stories.ts b/libs/components/src/form-field/password-input-toggle.stories.ts index ff6e14c0c91..f39974615bb 100644 --- a/libs/components/src/form-field/password-input-toggle.stories.ts +++ b/libs/components/src/form-field/password-input-toggle.stories.ts @@ -1,8 +1,11 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { Meta, moduleMetadata, Story } from "@storybook/angular"; -import { ButtonModule } from "../button"; +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; + +import { IconButtonModule } from "../icon-button"; import { InputModule } from "../input/input.module"; +import { I18nMockService } from "../utils/i18n-mock.service"; import { FormFieldModule } from "./form-field.module"; import { BitPasswordInputToggleDirective } from "./password-input-toggle.directive"; @@ -12,7 +15,13 @@ export default { component: BitPasswordInputToggleDirective, decorators: [ moduleMetadata({ - imports: [FormsModule, ReactiveFormsModule, FormFieldModule, InputModule, ButtonModule], + imports: [FormsModule, ReactiveFormsModule, FormFieldModule, InputModule, IconButtonModule], + providers: [ + { + provide: I18nService, + useValue: new I18nMockService({ toggleVisibility: "Toggle visibility" }), + }, + ], }), ], parameters: { @@ -40,7 +49,7 @@ const Template: Story = ( Password - + `, @@ -60,7 +69,7 @@ const TemplateBinding: Story = ( Password - +