+
{{ "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
-
+