mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 16:53:34 +00:00
Merge branch 'feature/sm-billing' into AC-1418-add-secrets-manager-manage-subscription-component
This commit is contained in:
@@ -2,9 +2,9 @@ import { Directive } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Directive } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
|
||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { FormBuilder, FormControl } from "@angular/forms";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Verification } from "@bitwarden/common/types/verification";
|
||||
|
||||
import { ModalRef } from "../../components/modal/modal.ref";
|
||||
import { ModalConfig } from "../../services/modal.service";
|
||||
@@ -16,7 +17,12 @@ export class UserVerificationPromptComponent {
|
||||
confirmDescription = this.config.data.confirmDescription;
|
||||
confirmButtonText = this.config.data.confirmButtonText;
|
||||
modalTitle = this.config.data.modalTitle;
|
||||
secret = new FormControl();
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
secret: this.formBuilder.control<Verification | null>(null),
|
||||
});
|
||||
|
||||
protected invalidSecret = false;
|
||||
|
||||
constructor(
|
||||
private modalRef: ModalRef,
|
||||
@@ -27,19 +33,31 @@ export class UserVerificationPromptComponent {
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
//Incorrect secret will throw an invalid password error.
|
||||
await this.userVerificationService.verifyUser(this.secret.value);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("error"),
|
||||
this.i18nService.t("invalidMasterPassword")
|
||||
);
|
||||
get secret() {
|
||||
return this.formGroup.controls.secret;
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalRef.close(true);
|
||||
try {
|
||||
//Incorrect secret will throw an invalid password error.
|
||||
await this.userVerificationService.verifyUser(this.secret.value);
|
||||
this.invalidSecret = false;
|
||||
} catch (e) {
|
||||
this.invalidSecret = true;
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("error"), e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.close(true);
|
||||
};
|
||||
|
||||
close(success: boolean) {
|
||||
this.modalRef.close(success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { ControlValueAccessor, FormControl } from "@angular/forms";
|
||||
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { ControlValueAccessor, FormControl, Validators } from "@angular/forms";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { Verification } from "@bitwarden/common/types/verification";
|
||||
|
||||
@@ -17,29 +19,65 @@ import { Verification } from "@bitwarden/common/types/verification";
|
||||
selector: "app-user-verification",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
||||
usesKeyConnector = false;
|
||||
export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy {
|
||||
private _invalidSecret = false;
|
||||
@Input()
|
||||
get invalidSecret() {
|
||||
return this._invalidSecret;
|
||||
}
|
||||
set invalidSecret(value: boolean) {
|
||||
this._invalidSecret = value;
|
||||
this.invalidSecretChange.emit(value);
|
||||
|
||||
// ISSUE: This is pretty hacky but unfortunately there is no way of knowing if the parent
|
||||
// control has been marked as touched, see: https://github.com/angular/angular/issues/10887
|
||||
// When that functionality has been added we should also look into forwarding reactive form
|
||||
// controls errors so that we don't need a separate input/output `invalidSecret`.
|
||||
if (value) {
|
||||
this.secret.markAsTouched();
|
||||
}
|
||||
this.secret.updateValueAndValidity({ emitEvent: false });
|
||||
}
|
||||
@Output() invalidSecretChange = new EventEmitter<boolean>();
|
||||
|
||||
usesKeyConnector = true;
|
||||
disableRequestOTP = false;
|
||||
sentCode = false;
|
||||
|
||||
secret = new FormControl("");
|
||||
secret = new FormControl("", [
|
||||
Validators.required,
|
||||
() => {
|
||||
if (this.invalidSecret) {
|
||||
return {
|
||||
invalidSecret: {
|
||||
message: this.usesKeyConnector
|
||||
? this.i18nService.t("incorrectCode")
|
||||
: this.i18nService.t("incorrectPassword"),
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
private onChange: (value: Verification) => void;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private userVerificationService: UserVerificationService
|
||||
private userVerificationService: UserVerificationService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
|
||||
this.processChanges(this.secret.value);
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
this.secret.valueChanges.subscribe((secret: string) => this.processChanges(secret));
|
||||
this.secret.valueChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((secret: string) => this.processChanges(secret));
|
||||
}
|
||||
|
||||
async requestOTP() {
|
||||
requestOTP = async () => {
|
||||
if (this.usesKeyConnector) {
|
||||
this.disableRequestOTP = true;
|
||||
try {
|
||||
@@ -49,7 +87,7 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
||||
this.disableRequestOTP = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.secret.setValue(obj);
|
||||
@@ -72,7 +110,14 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private processChanges(secret: string) {
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
protected processChanges(secret: string) {
|
||||
this.invalidSecret = false;
|
||||
|
||||
if (this.onChange == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ import { OrganizationUserService } from "@bitwarden/common/abstractions/organiza
|
||||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
|
||||
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
@@ -48,6 +46,8 @@ import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarde
|
||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
||||
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service";
|
||||
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
|
||||
|
||||
@@ -3,9 +3,9 @@ import { UntypedFormBuilder, Validators } from "@angular/forms";
|
||||
import { merge, startWith, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { EncryptedExportType, EventType } from "@bitwarden/common/enums";
|
||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VerifyOTPRequest } from "../../auth/models/request/verify-otp.request";
|
||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||
|
||||
export abstract class UserVerificationApiServiceAbstraction {
|
||||
postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise<void>;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SecretVerificationRequest } from "../../auth/models/request/secret-verification.request";
|
||||
import { Verification } from "../../types/verification";
|
||||
import { Verification } from "../../../types/verification";
|
||||
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
|
||||
|
||||
export abstract class UserVerificationService {
|
||||
buildRequest: <T extends SecretVerificationRequest>(
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { UserVerificationService } from "../../abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { LogService } from "../../platform/abstractions/log.service";
|
||||
import { Verification } from "../../types/verification";
|
||||
import { AccountApiService } from "../abstractions/account-api.service";
|
||||
import { InternalAccountService } from "../abstractions/account.service";
|
||||
import { UserVerificationService } from "../abstractions/user-verification/user-verification.service.abstraction";
|
||||
|
||||
export class AccountApiServiceImplementation implements AccountApiService {
|
||||
constructor(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
|
||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||
|
||||
export class UserVerificationApiService implements UserVerificationApiServiceAbstraction {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../../abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||
import { Verification } from "../../../types/verification";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../abstractions/user-verification/user-verification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "../../enums/verification-type";
|
||||
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
|
||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||
|
||||
67
libs/components/src/avatar/avatar.mdx
Normal file
67
libs/components/src/avatar/avatar.mdx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./avatar.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Avatar
|
||||
|
||||
Avatars display a unique color that helps a user visually recognize their logged in account.
|
||||
|
||||
A variance in color across the avatar component is important as it is used in Account Switching as a
|
||||
visual indicator to recognize which of a personal or work account a user is logged into.
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Size
|
||||
|
||||
### Large: 64px
|
||||
|
||||
<Story of={stories.Large} />
|
||||
|
||||
### Default: 48px
|
||||
|
||||
<Story of={stories.Default} />
|
||||
|
||||
### Small 28px
|
||||
|
||||
<Story of={stories.Small} />
|
||||
|
||||
## Background color
|
||||
|
||||
The Background color can be set 3 ways. The color is generated using the following order of
|
||||
priority:
|
||||
|
||||
- Color
|
||||
- ID
|
||||
- Text, usually set to the user's Name field
|
||||
|
||||
<Story of={stories.ColorByText} />
|
||||
Use the user 'ID' field if `Name` is not defined.
|
||||
<Story of={stories.ColorByID} />
|
||||
|
||||
## Outline
|
||||
|
||||
If the avatar is displayed on one of the theme's `background` color variables or is interactive,
|
||||
display the avatar with a 1 pixel `secondary-500` border to meet WCAG AA graphic contrast guidelines
|
||||
for interactive elements.
|
||||
|
||||
<Story of={stories.Border} />
|
||||
|
||||
## Avatar as a button
|
||||
|
||||
The Avatar can be used as a button.
|
||||
|
||||
Typically this is only in the navigation on client apps where account switching is used and in the
|
||||
web app for the account menu indicator.
|
||||
|
||||
When the avatar is used as a button, the following states should be used:
|
||||
|
||||
`TODO:` [Jira add stories](https://bitwarden.atlassian.net/browse/CL-101) for button avatars.
|
||||
[See Figma](https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?type=design&node-id=9730-31746&mode=design&t=IjDIHDb6FZl6bUQW-4)
|
||||
|
||||
## Accessibility
|
||||
|
||||
Avatar background color should have 3.1:1 contrast with it’s background; or include the
|
||||
`secondary-500` border Avatar text should have 4.5:1 contrast with the avatar background color
|
||||
67
libs/components/src/badge/badge.mdx
Normal file
67
libs/components/src/badge/badge.mdx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./badge.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Badge
|
||||
|
||||
The Badge directive can be used on a `<span>` (non clickable events), or an `<a>` or `<button>` tag
|
||||
for interactive events. The Focus and Hover states only apply to badges used for interactive events.
|
||||
|
||||
Typically Badges are only used with text set to `text-xs`. If additional sizes are needed, the
|
||||
component configurations may be reviewed and adjusted.
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Styles
|
||||
|
||||
### Primary
|
||||
|
||||
The primary badge is used to indicate an active status (example: device management page) or provide
|
||||
additional information (example: type of emergency access granted).
|
||||
|
||||
<Story of={stories.Primary} />
|
||||
|
||||
### Secondary
|
||||
|
||||
The secondary badge style is typically is a default badge style. It is often used to indicate
|
||||
neutral information.
|
||||
|
||||
<Story of={stories.Secondary} />
|
||||
|
||||
### Success
|
||||
|
||||
The success badge is used to indicate a positive status, OR to indicate a feature requires a Premium
|
||||
subscription. See [Premium Badge](?path=/docs/web-premium-badge--docs)
|
||||
|
||||
<Story of={stories.Success} />
|
||||
|
||||
### Danger
|
||||
|
||||
The danger badge is used to indicate a negative status.
|
||||
|
||||
<Story of={stories.Danger} />
|
||||
|
||||
### Warning
|
||||
|
||||
The warning badge is used to indicate a status waiting on an additional action from the active user.
|
||||
|
||||
<Story of={stories.Warning} />
|
||||
|
||||
### Info
|
||||
|
||||
The info badge is used to indicate a low emphasis informative information.
|
||||
|
||||
<Story of={stories.Info} />
|
||||
|
||||
## Badges as counters
|
||||
|
||||
Badges can be used as part of links or buttons to provide a counter. See the
|
||||
[Toggle Group](?path=/docs/component-library-toggle-group--docs) component.
|
||||
|
||||
## Accessibility
|
||||
|
||||
Be sure to use the correct html tag based on the purpose or function of the badge. Follow color WCAG
|
||||
color contrast guidelines for small text.
|
||||
@@ -10,7 +10,7 @@ Banners are used for important communication with the user that needs to be seen
|
||||
little effect on the experience. Banners appear at the top of the user's screen on page load and
|
||||
persist across all pages a user navigates to.
|
||||
|
||||
- They should always be dismissable and never use a timeout. If a user dismisses a banner, it should
|
||||
- They should always be dismissible and never use a timeout. If a user dismisses a banner, it should
|
||||
not reappear during that same active session.
|
||||
- Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their
|
||||
effectiveness may decrease if too many are used.
|
||||
|
||||
54
libs/components/src/breadcrumbs/breadcrumbs.mdx
Normal file
54
libs/components/src/breadcrumbs/breadcrumbs.mdx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./breadcrumbs.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Breadcrumbs
|
||||
|
||||
Breadcrumbs are used to help users understand where they are in a products navigation. Typically
|
||||
Bitwarden uses this component to indicate the user's current location in a set of data organized in
|
||||
containers (Collections, Folders, or Projects).
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Display
|
||||
|
||||
Breadcrumbs display above the page title. The current page should not appear as a breadcrumb link.
|
||||
See [Header with Breadcrumbs](?path=/story/web-header--with-breadcrumbs).
|
||||
|
||||
### Top Level
|
||||
|
||||
When a user is 1 level deep into a tree, the top level is displayed as a single link above the page
|
||||
title.
|
||||
|
||||
<Story of={stories.TopLevel} />
|
||||
|
||||
### Second Level
|
||||
|
||||
When a user is 2 or more levels deep into a tree, the top level is displayed followed by an
|
||||
|
||||
<i class="bwi bwi-angle-right"></i> icon, and the following pages.
|
||||
|
||||
<Story of={stories.SecondLevel} />
|
||||
|
||||
### Overflow
|
||||
|
||||
When a user is several levels deep into a tree, the top level or 2 are displayed followed by an
|
||||
|
||||
<i class="bwi bwi-ellipsis-h"> </i> icon button, and then the page directly above the current page.
|
||||
|
||||
When the user selects the <i class="bwi bwi-ellipsis-h"></i> icon button, a menu opens displaying
|
||||
the pages between the top level and the previous page.
|
||||
|
||||
<Story of={stories.Overflow} />
|
||||
|
||||
### Small screens
|
||||
|
||||
If a screen's width is not large enough to display the full breadcrumb path, display a link to the
|
||||
previous page and an <i class="bwi bwi-angle-right"></i> icon to take the user back to the previous
|
||||
page.
|
||||
|
||||
`TODO:` [Jira add stories](https://bitwarden.atlassian.net/browse/CL-102) for responsive screen
|
||||
width/small screens
|
||||
@@ -33,31 +33,6 @@ Groups within page content, dialog footers or forms should have the `primary` ca
|
||||
to left. Groups in headers and navigational areas should have the `primary` call to action on the
|
||||
right.
|
||||
|
||||
## Accessibility
|
||||
|
||||
Please follow these guidelines to ensure that buttons are accessible to all users.
|
||||
|
||||
### Color contrast
|
||||
|
||||
All button styles are WCAG compliant when displayed on `background` and `background-alt` colors. To
|
||||
use a button on a different background, double check that the color contrast is sufficient in both
|
||||
the light and dark themes.
|
||||
|
||||
### Loading Buttons
|
||||
|
||||
Include an `aria-label` attribute that defaults to “loading” but can be configurable per
|
||||
implementation. On click, the screen reader should announce the `aria-label`. Once the action is
|
||||
compelted, use another messaging pattern to alert the user that the action is complete (example:
|
||||
success toast).
|
||||
|
||||
### Submit and async actions
|
||||
|
||||
Both submit and async action buttons use a loading button state while an action is taken. If your
|
||||
button is preforming a long running task in the background like a server API call, be sure to review
|
||||
the [Async Actions Directive](?path=/story/component-library-async-actions-overview--page).
|
||||
|
||||
<Story of={stories.Loading} />
|
||||
|
||||
## Styles
|
||||
|
||||
There are 3 main styles for the button: Primary, Secondary, and Danger.
|
||||
@@ -96,3 +71,39 @@ Typically button widths expand with their text. In some causes though buttons ma
|
||||
where the width is fixed and the text wraps to 2 lines if exceeding the button’s width.
|
||||
|
||||
<Story of={stories.Block} />
|
||||
|
||||
## Accessibility
|
||||
|
||||
Please follow these guidelines to ensure that buttons are accessible to all users.
|
||||
|
||||
### Color contrast
|
||||
|
||||
All button styles are WCAG compliant when displayed on `background` and `background-alt` colors. To
|
||||
use a button on a different background, double check that the color contrast is sufficient in both
|
||||
the light and dark themes.
|
||||
|
||||
### Loading Buttons
|
||||
|
||||
Include an `aria-label` attribute that defaults to "loading" but can be configurable per
|
||||
implementation. On click, the screen reader should announce the `aria-label`. Once the action is
|
||||
completed, use another messaging pattern to alert the user that the action is complete (example:
|
||||
success toast).
|
||||
|
||||
### Submit and async actions
|
||||
|
||||
Both submit and async action buttons use a loading button state while an action is taken. If your
|
||||
button is preforming a long running task in the background like a server API call, be sure to review
|
||||
the [Async Actions Directive](?path=/story/component-library-async-actions-overview--page).
|
||||
|
||||
<Story of={stories.Loading} />
|
||||
|
||||
### appA11yTitle
|
||||
|
||||
`appA11yTitle` is a directive that auto assigns the same string to the `title` and `aria-label`
|
||||
attributes.
|
||||
|
||||
When a button uses accessible content (e.i. actual text), DO NOT include this as it adds redundant
|
||||
content for someone using assistive technology.
|
||||
|
||||
`appA11yTitle` should only be used if the element it applies to does not include accessible text,
|
||||
e.i. an icon.
|
||||
|
||||
66
libs/components/src/callout/callout.mdx
Normal file
66
libs/components/src/callout/callout.mdx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./callout.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Callouts
|
||||
|
||||
Callouts are used to communicate important information to the user. Callouts should be used
|
||||
sparingly, as they command a large amount of visual attention. Avoid using more than 1 callout in
|
||||
the same location.
|
||||
|
||||
## Styles
|
||||
|
||||
Icons should remain consistent across these types. Do not change the icon without consulting a
|
||||
designer. Use the following guidelines to help choose the correct type of callout.
|
||||
|
||||
### Success
|
||||
|
||||
Use the success callout to communicate a positive messaging to the user.
|
||||
|
||||
**Example:** a positive report results shows a success callout.
|
||||
|
||||
The success callout may also be used for the information related to a premium membership. In this
|
||||
case, replace the icon with <i class="bwi bwi-star" title="bwi-star" aria-label="bwi-star"></i>
|
||||
|
||||
<Story of={stories.Success} />
|
||||
|
||||
### Info
|
||||
|
||||
Use an info callout to call attention to important information the user should be aware of, but has
|
||||
low risk of the user receiving and unintended or irreversible results if they do not read the
|
||||
information.
|
||||
|
||||
**Example:** in the Domain Claiming modal, an info callout is used to tell the user the domain will
|
||||
automatically be checked.
|
||||
|
||||
<Story of={stories.Info} />
|
||||
|
||||
### Warning
|
||||
|
||||
Use a warning callout if the user is about to perform an action that may have unintended or
|
||||
irreversible results.
|
||||
|
||||
**Example:** the warning callout is used before the change master password and encryption key form
|
||||
to alert the user that they will be logged out.
|
||||
|
||||
<Story of={stories.Warning} />
|
||||
|
||||
### Danger
|
||||
|
||||
Use the danger callout to communicate an action the user is about to take is dangerous and typically
|
||||
not reversible.
|
||||
|
||||
The danger callout can also be used to alert the user of an error or errors, such as a server side
|
||||
errors after form submit or failed communication request.
|
||||
|
||||
<Story of={stories.Danger} />
|
||||
|
||||
## Accessibility
|
||||
|
||||
Use the `role=”alert”` only if the callout is appearing on a page after the user takes an action. If
|
||||
the content is static, do not use the alert role. This will cause a screen reader to announce the
|
||||
callout content on page load.
|
||||
|
||||
Ensure the title's color contrast remains WCAG compliant with the callout's background.
|
||||
34
libs/components/src/color-password/color-password.mdx
Normal file
34
libs/components/src/color-password/color-password.mdx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./color-password.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Color password
|
||||
|
||||
The color password is used primarily in the Generator pages and in the Login type form. It includes
|
||||
the logic for displaying letters as `text-main`, numbers as `primary`, and special symbols as
|
||||
`danger`.
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Password Count
|
||||
|
||||
The password count option is used in the Login type form. It is used to highlight each character's
|
||||
position in the password string.
|
||||
|
||||
<Story of={stories.ColorPasswordCount} />
|
||||
|
||||
## Wrapped Password
|
||||
|
||||
When the password length is longer than the container's width, it should wrap as shown below.
|
||||
|
||||
<Story of={stories.WrappedColorPassword} />
|
||||
|
||||
<Story of={stories.WrappedColorPasswordCount} />
|
||||
|
||||
## Accessibility
|
||||
|
||||
The colors used in the colored password should maintain WCAG compliant contrast with theme
|
||||
`background` and `background-alt` colors.
|
||||
73
libs/components/src/dialog/dialog/dialog.mdx
Normal file
73
libs/components/src/dialog/dialog/dialog.mdx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./dialog.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Dialog
|
||||
|
||||
Dialogs are used throughout the app to help the user focus on a specific action. Use this dialog
|
||||
component when content exceeds 384px width or there are a high number of interactive elements
|
||||
needed. **Example:** The web app's edit vault item form dialog
|
||||
|
||||
For alerts or simple confirmation actions, like speedbumps, use the
|
||||
[Simple Dialog](?path=/docs/component-library-dialogs-simple-dialog--docs).
|
||||
|
||||
Dialogs's should be used sparingly as they do call extra attention to themselves and can be
|
||||
interruptive if overused.
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Size
|
||||
|
||||
There are 3 main dialog sizes:
|
||||
|
||||
### Large
|
||||
|
||||
Use the large size for dialogs that have many interactive elements or tabbed content.
|
||||
|
||||
**Tailwind styling:**
|
||||
|
||||
`max-w-3xl` 48rem
|
||||
|
||||
<Story of={stories.Large} />
|
||||
|
||||
### Default
|
||||
|
||||
Use the Default size for most dialogs that require some content and a few interactive elements.
|
||||
**Example:** master password confirmation dialog
|
||||
|
||||
**Tailwind styling:**
|
||||
|
||||
`max-w-xl` 36rem
|
||||
|
||||
<Story of={stories.Default} />
|
||||
|
||||
### Small
|
||||
|
||||
**Tailwind styling:**
|
||||
|
||||
`max-w-sm` 24rem
|
||||
|
||||
<Story of={stories.Small} />
|
||||
|
||||
## Long Title
|
||||
|
||||
If a dialog's title is too long to fully display. It should be truncated and on hover shown in a
|
||||
tooltip.
|
||||
|
||||
<Story of={stories.LongTitle} />
|
||||
|
||||
## Loading
|
||||
|
||||
Similar to a page loading state, a Dialog that takes more than a few seconds to load should use a
|
||||
loading state.
|
||||
|
||||
<Story of={stories.Loading} />
|
||||
|
||||
## Tab Content
|
||||
|
||||
Use tabs to separate related content within a dialog.
|
||||
|
||||
<Story of={stories.TabContent} />
|
||||
56
libs/components/src/dialog/dialogs.mdx
Normal file
56
libs/components/src/dialog/dialogs.mdx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Meta, Story, Source } from "@storybook/addon-docs";
|
||||
|
||||
<Meta title="Component Library/Dialogs" />
|
||||
|
||||
# Dialog
|
||||
|
||||
Dialogs are used throughout the app to help the user focus on a specific action.
|
||||
|
||||
Use the main [Dialog Component](?path=/docs/component-library-dialogs-dialog--docs). when content
|
||||
exceeds 384px width or there are a high number of interactive elements needed. **Example:** The web
|
||||
app's edit vault item form dialog
|
||||
|
||||
For alerts or simple confirmation actions, like speedbumps, use the
|
||||
[Simple Dialog](?path=/docs/component-library-dialogs-simple-dialog--docs).
|
||||
|
||||
Dialogs's should be used sparingly as they do call extra attention to themselves and can be
|
||||
interruptive if overused.
|
||||
|
||||
## Placement
|
||||
|
||||
Dialogs should be centered vertically and horizontally on screen. Dialogs height should expand to
|
||||
fit its content until there are 2rems of margin on the top/bottom of the dialog; in this case, the
|
||||
dialog should become scrollable.
|
||||
|
||||
A backdrop should be used to hide the content below the dialog. Use `#000000` with `30% opacity`.
|
||||
|
||||
<Story id="component-library-dialogs-service--default" />
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Component behavior
|
||||
|
||||
- Dialog include `role="dialog"`
|
||||
- The Dialog title is an `<h1>`
|
||||
- A user should not be able to tab focus outside of the Dialog until it has been closed.
|
||||
- Clicking outside the dialog or clicking escape should close the dialog (this prevents a keyboard
|
||||
trap)
|
||||
|
||||
### Required per implementation
|
||||
|
||||
The triggering button should indicate to assistive technology that additional content will open or
|
||||
appear when the trigger is selected. Consider using `aria-haspopup="true"`
|
||||
|
||||
Dialog title should be announced by a screen reader when launched. Consider using `aria-labelledby`
|
||||
or `aria-label`
|
||||
|
||||
When opened, focus should follow the visual order of the popover’s focusable content. Typically
|
||||
focus is moved to the close button, but it is acceptable to move focus to the first interactive
|
||||
element after close since a user may not want to close the dialog immediately if there are
|
||||
additional interactive elements. See
|
||||
[WCAG Focus Order success criteria](https://www.w3.org/WAI/WCAG21/Understanding/focus-order.html)
|
||||
|
||||
Once closed, focus should remain on the the element which triggered the Dialog.
|
||||
|
||||
**Note:** If a Simple Dialog is triggered from a main Dialog, be sure to make sure focus is moved to
|
||||
the Simple Dialog.
|
||||
47
libs/components/src/dialog/simple-dialog/simple-dialog.mdx
Normal file
47
libs/components/src/dialog/simple-dialog/simple-dialog.mdx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./simple-dialog.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Simple Dialogs
|
||||
|
||||
Simple Dialogs are used throughout the app for simple alert or confirmation actions such as
|
||||
speedbumps.
|
||||
|
||||
For dialogs with a high number of interactive elements such as a form or content exceeding 384px,
|
||||
use the [Dialog component](?path=/docs/component-library-dialogs-dialog--docs).
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Configurable Simple Dialog
|
||||
|
||||
The Simple Dialog contains the following configuration points:
|
||||
|
||||
- `title`: string
|
||||
- `content`: string
|
||||
- `type`: SimpleDialogType
|
||||
- `icon`: string – if empty, infer from type
|
||||
- `acceptButtonText`: string – if empty, default to "Yes"
|
||||
- `cancelButtonText`: string – if empty, default to "No", unless acceptButtonText is overridden, in
|
||||
which case default to "Cancel"
|
||||
|
||||
To increase consistency, the simple dialog service supports some automation for setting the `icon`
|
||||
and `color` based on the defined type. See the following for how properties will be configured when
|
||||
the simple dialog's type is specified.
|
||||
|
||||
| type | icon name | icon | color |
|
||||
| ------- | ------------------------ | -------------------------------------------- | ----------- |
|
||||
| primary | bwi-business | <i class="bwi bwi-business"></i> | primary-500 |
|
||||
| success | bwi-star | <i class="bwi bwi-star"></i> | success-500 |
|
||||
| info | bwi-info-circle | <i class="bwi bwi-info-circle"></i> | info-500 |
|
||||
| warning | bwi-exclamation-triangle | <i class="bwi bwi-exclamation-triangle"></i> | warning-500 |
|
||||
| danger | bwi-error | <i class="bwi bwi-error"></i> | danger-500 |
|
||||
|
||||
## Scrolling Content
|
||||
|
||||
Simple dialogs can support scrolling content if necessary, but typically with larger quantities of
|
||||
content a [Dialog component](?path=/docs/component-library-dialogs-dialog--docs).
|
||||
|
||||
<Story of={stories.ScrollingContent} />
|
||||
@@ -1,8 +1,11 @@
|
||||
import { coerceBooleanProperty } from "@angular/cdk/coercion";
|
||||
import {
|
||||
AfterContentChecked,
|
||||
Component,
|
||||
ContentChild,
|
||||
ContentChildren,
|
||||
HostBinding,
|
||||
Input,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
} from "@angular/core";
|
||||
@@ -17,9 +20,6 @@ import { BitSuffixDirective } from "./suffix.directive";
|
||||
@Component({
|
||||
selector: "bit-form-field",
|
||||
templateUrl: "./form-field.component.html",
|
||||
host: {
|
||||
class: "tw-mb-6 tw-block",
|
||||
},
|
||||
})
|
||||
export class BitFormFieldComponent implements AfterContentChecked {
|
||||
@ContentChild(BitFormFieldControl) input: BitFormFieldControl;
|
||||
@@ -30,6 +30,19 @@ export class BitFormFieldComponent implements AfterContentChecked {
|
||||
@ContentChildren(BitPrefixDirective) prefixChildren: QueryList<BitPrefixDirective>;
|
||||
@ContentChildren(BitSuffixDirective) suffixChildren: QueryList<BitSuffixDirective>;
|
||||
|
||||
private _disableMargin = false;
|
||||
@Input() set disableMargin(value: boolean | "") {
|
||||
this._disableMargin = coerceBooleanProperty(value);
|
||||
}
|
||||
get disableMargin() {
|
||||
return this._disableMargin;
|
||||
}
|
||||
|
||||
@HostBinding("class")
|
||||
get classList() {
|
||||
return ["tw-block"].concat(this.disableMargin ? [] : ["tw-mb-6"]);
|
||||
}
|
||||
|
||||
ngAfterContentChecked(): void {
|
||||
if (this.error) {
|
||||
this.input.ariaDescribedBy = this.error.id;
|
||||
|
||||
178
libs/components/src/form/forms.mdx
Normal file
178
libs/components/src/form/forms.mdx
Normal file
@@ -0,0 +1,178 @@
|
||||
import { Meta, Story, Source } from "@storybook/addon-docs";
|
||||
|
||||
<Meta title="Component Library/Form" />
|
||||
|
||||
# Forms
|
||||
|
||||
Component Library forms should always be built using [Angular Reactive Forms][reactive]. Please read
|
||||
[ADR-0001][adr-0001] for a background to this decision. In practice this means that forms should
|
||||
always use the native `form` element and bind a `formGroup`.
|
||||
|
||||
<Story id="component-library-form--full-example" />
|
||||
|
||||
<Source id="component-library-form--full-example" />
|
||||
|
||||
## Form spacing and sections
|
||||
|
||||
Forms consists of 1 or more inputs, and ends with 1 or 2 buttons.
|
||||
|
||||
If there are many inputs in a form, they should should be organized into sections as content
|
||||
relates. **Example:** Item type form
|
||||
|
||||
Each input within a section should follow the following spacing guidelines (see
|
||||
[Tailwind CSS spacing documentation](https://tailwindcss.com/docs/customizing-spacing)):
|
||||
|
||||
- 1.5rem of vertical spacing between form elements: `mb-6`
|
||||
- 1.5rem of horizontal spacing between form elements: `mr-6`
|
||||
- 3rem of vertical spacing below a form section: `mb-12`
|
||||
- 1rem of vertical spacing between a form group divider and the group's title; so title tag has:
|
||||
`my-4`
|
||||
- Form section titles should be styled using `text-lg`
|
||||
- Each form sections may have a single column, double or triple column layout. No form should have
|
||||
more than 3 columns. Do NOT use different column layouts within the same form section. Choose the
|
||||
best layout based on the number of fields and type of fields included.
|
||||
|
||||
## Input Types
|
||||
|
||||
### Field
|
||||
|
||||
A form field is the most common input in a form. It consists of a label, control and an optional
|
||||
hint.
|
||||
|
||||
The styling of form fields applies to all field types: `text`, `number`, `select`, `text-area`,
|
||||
`date`, etc.
|
||||
|
||||
Be sure to use an appropriate type attribute on fields when defining new field components (e.g.
|
||||
`email` for email address or `number` for numerical information) to take advantage of newer input
|
||||
controls like email verification, number selection, and more.
|
||||
|
||||
#### Default with required attribute
|
||||
|
||||
<Story id="component-library-form-field--default" />
|
||||
|
||||
#### Password Toggle
|
||||
|
||||
<Story id="component-library-form-password-toggle--default" />
|
||||
|
||||
#### Search
|
||||
|
||||
<Story id="component-library-form-search--default" />
|
||||
|
||||
### Selects
|
||||
|
||||
#### Searchable single select (default)
|
||||
|
||||
<Story id="component-library-form-select--default" />
|
||||
|
||||
#### Multi-select
|
||||
|
||||
<Story id="component-library-form-multi-select--members" />
|
||||
|
||||
### Radio group
|
||||
|
||||
Radio buttons should always be in radio groups.
|
||||
|
||||
Radio groups are form fields that consists of a main label and multiple radio buttons. Each radio
|
||||
button consists of a label and a radio input.
|
||||
|
||||
The full form control + label should be selectable to allow the user a larger click target.
|
||||
|
||||
Radio groups should always have a default selected value.
|
||||
|
||||
Radio groups may optionally include extra helper text below each radio button.
|
||||
|
||||
If a radio group has more than 4 options and the options do not need helper text, a
|
||||
[select menu](?path=/docs/component-library-form-multi-select--docs) should be used instead. Avoid
|
||||
using a radio group for more than 5 options even if the options require additional explanation text.
|
||||
|
||||
`TODO: extend the select component to support a dropdown menu with descriptions below each option`
|
||||
|
||||
#### Block
|
||||
|
||||
<Story id="component-library-form-radio-button--block" />
|
||||
|
||||
#### Inline
|
||||
|
||||
<Story id="component-library-form-radio-button--inline" />
|
||||
|
||||
[reactive]: https://angular.io/guide/reactive-forms
|
||||
[adr-0001]: https://contributing.bitwarden.com/architecture/adr/reactive-forms
|
||||
|
||||
### Checkbox
|
||||
|
||||
The checkbox input is used to toggle an action on/off.
|
||||
|
||||
Checkboxes can be displayed on their own or in a group (select multiple form question). When
|
||||
displayed in a group, include an input Label and any associated required/validation logic for the
|
||||
field.
|
||||
|
||||
Unlike radio groups, checkbox groups are not required to have a default selected value.
|
||||
|
||||
Checkbox groups can include extra explanation text below each radio button or just the checkbox
|
||||
button itself.
|
||||
|
||||
If a checkbox group has more than 4 options a
|
||||
[multi-select components](?path=/docs/component-library-form-multi-select--docs) should be used.
|
||||
|
||||
#### Single checkbox
|
||||
|
||||
<Story id="component-library-form-checkbox--default" />
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Required Fields
|
||||
|
||||
- Use "(required)" in the label of each required form field styled the same as the field's helper
|
||||
text (`.muted-text`).
|
||||
- If whether or not a form field is required depends on another field, add this to the field's
|
||||
helper text.
|
||||
- **Example:** "Billing Email is required if owned by a business".
|
||||
|
||||
### Form Field Errors
|
||||
|
||||
- When a resting field is filled out, validation is triggered when the user de-focuses the field
|
||||
(`onblur`). If the control is invalid, assistive technology should announce the error (consider
|
||||
using `role="alert"` or an `aria-live="assertive"`).
|
||||
- Validation should not be triggered if the control is left untouched; this allows a user of
|
||||
assistive technology to read the entire form if they wish without triggering validation that could
|
||||
interrupt them. - **TODO:** research how we might implement this behavior; as previous research
|
||||
has shown Angular may not allow both validation when `dirty` `onblur` AND validation on Submit
|
||||
which is a requirement
|
||||
- A form control with an error should change to the error UI and the error text should be displayed
|
||||
below the element and be associated to their respective fields (consider using `aria-describedby`)
|
||||
- When a field with an error is focused, assistive technology should announce the label and
|
||||
elements' invalid state and then the error text.
|
||||
- **Example:** "URL required, Error, URL format is not acceptable."
|
||||
- Once the user has re-focused the field, and starts typing. The error will disappear. Validation
|
||||
should not occur when typing in most cases. Once th user unfocuses the field, validation triggers
|
||||
again.
|
||||
|
||||
### Validation on Submit
|
||||
|
||||
- Validation must also occur on submit. A user may select the submit button directly without
|
||||
changing focus from a form input. Or a user may disable their browser's javascript which is what
|
||||
supports the inline onblur validation. Finally, there may be a server side error that can only be
|
||||
checked on submit.
|
||||
- On submit, a summary error should appear near the submit button or at the top of the form alerting
|
||||
the user of what errors need to be addressed. This summary should be read out by assistive
|
||||
technology after submit regardless of whether or not it was already on screen.
|
||||
- Any invalid form control will display an inline error following the field's helper text (or in
|
||||
place of)
|
||||
- If submit is successful, use a success toast to alert the user of the successful action.
|
||||
- For any server side errors, the Danger toast may still be used. Be sure to adjust the toast's
|
||||
timeout to follow the 6 second
|
||||
|
||||
* 1 second for each additional 120 words rule.
|
||||
|
||||
### Helper Text
|
||||
|
||||
Similar to a field error, helper text should be associated to a field using `aria-describedby`. This
|
||||
allows assistive technology to read out the instructional text and field requirements in addition to
|
||||
the field’s label.
|
||||
|
||||
### Visual style
|
||||
|
||||
- All field inputs are interactive elements that must follow the WCAG graphic contrast guidelines.
|
||||
Maintain a ratio of 3:1 with the form's background.
|
||||
- Error styling should not rely only on using the `danger-500`color change. Use
|
||||
<i class="bwi bwi-error"></i> as a prefix to highlight the text as error text versus helper
|
||||
55
libs/components/src/icon-button/icon-button.mdx
Normal file
55
libs/components/src/icon-button/icon-button.mdx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./icon-button.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Icon Button
|
||||
|
||||
Icon buttons are used when no text accompanies the button. It consists of an icon that may be
|
||||
updated to any icon in the `bwi-font`, a `title` attribute, and an `aria-label`.
|
||||
|
||||
There are 3 common styles for button main, contrast, and danger. The main style is used on the
|
||||
theme’s main `background`; and the contrast style is used on a theme’s colored or contrasting
|
||||
backgrounds; danger is used for “trash” actions throughout the experience. The other styles are used
|
||||
sparingly.
|
||||
|
||||
The most common use of the icon button is in the banner, toast, and modal components as a close
|
||||
button. It can also be found in tables as the 3 dot option menu, or on navigation list items when
|
||||
there are options that need to be collapsed into a menu.
|
||||
|
||||
Similar to the main button components, spacing between icon buttons should be .5rem.
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
**Note:** Main and contrast styles appear on backgrounds where using `primary-700` as a focus
|
||||
indicator does not meet WCAG graphic contrast guidelines.
|
||||
|
||||
## Sizes
|
||||
|
||||
There are 2 sizes for the icon button: `small` and `default`.
|
||||
|
||||
Default is typically used for most instances. Small is used if the implementation needs a variant
|
||||
with less padding around the icon, such as in the navigation component.
|
||||
|
||||
## Usage
|
||||
|
||||
Icon buttons can be found in other components such as: the
|
||||
[banner](?path=/docs/component-library-banner--docs)
|
||||
[dialog](?path=/docs/component-library-dialogs--docs), and
|
||||
[table](?path=/docs/component-library-table--docs).
|
||||
|
||||
<Story id="component-library-banner--premium" />
|
||||
|
||||
## Accessibility
|
||||
|
||||
Follow guidelines outlined in the [Button docs](?path=/docs/component-library-button--doc)
|
||||
|
||||
Always use the `appA11yTitle` directive set to a string that describes the action of the
|
||||
icon-button. This will auto assign the same string to the `title` and `aria-label` attributes.
|
||||
|
||||
`aria-label` allows assistive technology to announce the action the button takes to the users.
|
||||
|
||||
`title` attribute provides a user with the browser tool tip if they do not understand what the icon
|
||||
is indicating.
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Meta } from "@storybook/addon-docs";
|
||||
|
||||
<Meta title="Component Library/Form/Input" />
|
||||
<Meta title="Component Library/Form/Input Directive" />
|
||||
|
||||
# Input
|
||||
|
||||
39
libs/components/src/link/link.mdx
Normal file
39
libs/components/src/link/link.mdx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./link.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Link / Text button
|
||||
|
||||
Text Links and Buttons use the `primary-500` color and can use either the `<a>` or `<button>` tags.
|
||||
Choose which based on the action the button takes:
|
||||
|
||||
- if navigating to a new page, use a `<a>`
|
||||
- if taking an action on the current page use a `<button>`
|
||||
|
||||
Text buttons or links are most commonly used in paragraphs of text or in forms to customize actions
|
||||
or show/hide additional form options.
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Sizes
|
||||
|
||||
There are 2 sizes for the component: default and small.
|
||||
|
||||
Default uses `text-base` and small uses `text-xs`
|
||||
|
||||
## With icons
|
||||
|
||||
Text Links/buttons can have icons on left or the right.
|
||||
|
||||
To indicate a new or add action, the <i class="bwi bwi-plus-circle"></i> icon on is used on the
|
||||
left.
|
||||
|
||||
An angle icon, <i class="bwi bwi-angle-right"></i>, is used on the left to indicate an expand to
|
||||
show/hide additional content.
|
||||
|
||||
## Accessibility
|
||||
|
||||
Make sure to only use the Link on backgrounds that maintain the WCAG color contrast ratios.
|
||||
21
libs/components/src/menu/menu.mdx
Normal file
21
libs/components/src/menu/menu.mdx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./menu.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Menu
|
||||
|
||||
Menus are used to help organize related options. Menus are most often used for item options in
|
||||
tables.
|
||||
|
||||
<Story of={stories.ClosedMenu} />
|
||||
<Controls />
|
||||
|
||||
## Accessibility
|
||||
|
||||
Follow WCAG AA best practices. Example: Insure the triggering element has `aria-haspopup="true"`
|
||||
prior to being clicked and `aria-expanded="true"` after the user clicks the element.
|
||||
|
||||
User should be able to navigate the opened menu via the up and down arrow keys and close the menu
|
||||
using the escape key or clicking out of the menu.
|
||||
48
libs/components/src/progress/progress.mdx
Normal file
48
libs/components/src/progress/progress.mdx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./progress.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Progress
|
||||
|
||||
Progress indicators may be used to visually indicate progress or to visually measure some other
|
||||
value, such as a password strength indicator.
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Labels
|
||||
|
||||
Always display a label to provide a text based description of what the indicator is measuring. This
|
||||
allows those who may not be familiar with the pattern to be able to read and digest the information.
|
||||
It also allows assistive technology to accurately describe the indicator to those who may be unable
|
||||
to see part or all of the indicator.
|
||||
|
||||
<Story of={stories.Full} />
|
||||
|
||||
## Text label
|
||||
|
||||
When measuring something other than progress, such as password strength, update the label to fit the
|
||||
context of the implementation.
|
||||
|
||||
<Story of={stories.CustomText} />
|
||||
|
||||
### Strength indicator styles
|
||||
|
||||
For a strength indicator use the following styles for fill:
|
||||
|
||||
- **Weak:** `danger-500`
|
||||
- **Weak2:** `warning-500`
|
||||
- **Good:** `primary-500`
|
||||
- **Strong:** `success-500`
|
||||
|
||||
## Accessibility
|
||||
|
||||
Be sure to include the proper `aria-valuemin`, `aria-valuemax`, and `aria-valuenow` attributes. An
|
||||
a`ria-valuetext` should also be configurable to include the text a screen reader should read to the
|
||||
user.
|
||||
|
||||
In the case of a password strength indicator; `aria-describedby` is used on the password field and
|
||||
points to the `id` of the progress bar. This results in the screen reader reading the password
|
||||
strength to the user after they finish typing.
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Meta, Story, Source } from "@storybook/addon-docs";
|
||||
|
||||
<Meta title="Component Library/Form" />
|
||||
|
||||
# Forms
|
||||
|
||||
Examples and usage guidelines for form control styles, layout options, and custom components for
|
||||
creating a wide variety of forms.
|
||||
|
||||
## Overview
|
||||
|
||||
Component Library forms should always be built using [Angular Reactive Forms][reactive]. Please read
|
||||
[ADR-0001][adr-0001] for a background to this decision. In practice this means that forms should
|
||||
always use the native `form` element and bind a `formGroup`.
|
||||
|
||||
Forms consists of one or multiple sections, and ends with one or multiple buttons.
|
||||
|
||||
### Form Field
|
||||
|
||||
A form field is the most common section in a form. It consists of a label, control and a optional
|
||||
hint.
|
||||
|
||||
<Story id="component-library-form-field--default" />
|
||||
|
||||
<Source id="component-library-form-field--default" />
|
||||
|
||||
### Radio group
|
||||
|
||||
A radio group is a form field that consists of a main label and multiple radio groups. Each group
|
||||
consists of a label and a radio input.
|
||||
|
||||
#### Block
|
||||
|
||||
<Story id="component-library-form-radio-button--block" />
|
||||
|
||||
<Source id="component-library-form-radio-button--block" />
|
||||
|
||||
#### Inline
|
||||
|
||||
<Story id="component-library-form-radio-button--inline" />
|
||||
|
||||
<Source id="component-library-form-radio-button--inline" />
|
||||
|
||||
## Full Example
|
||||
|
||||
<Story id="component-library-form--full-example" />
|
||||
|
||||
<Source id="component-library-form--full-example" />
|
||||
|
||||
[reactive]: https://angular.io/guide/reactive-forms
|
||||
[adr-0001]: https://contributing.bitwarden.com/architecture/adr/reactive-forms
|
||||
@@ -26,12 +26,18 @@ The UI component consists of a couple of elements.
|
||||
### Guidelines
|
||||
|
||||
- Always include a row or column header with your table; this allows screen readers to better
|
||||
contextualize the data
|
||||
contextualize the data.
|
||||
- Avoid spanning data across cells.
|
||||
- Be sure to make repeating actions unique by associating them with the object they relate to.
|
||||
Example: if there are multiple “Edit” buttons on a table, a screen reader should read “Edit,
|
||||
Netflix” for an edit option for a Netflix item.
|
||||
- Use [Virtual Scrolling](#virtual-scrolling) for large data sets.
|
||||
- For bulk menu options, display a 3 dot menu in the header. When multiple items are selected, the
|
||||
bulk menu will contain actions that can be completed in bulk for the selected items.
|
||||
- Note, this may result in some menu actions being hidden at times if they are not applicable to
|
||||
the selected item
|
||||
- Clicking on a row’s 3 dot menu will continue to result in actions specific to just that row's
|
||||
item
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -147,3 +153,12 @@ specify a `itemSize`, set `scrollWindow` to `true` and replace `*ngFor` with `*c
|
||||
</bit-table>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Always include a row or column header with your table; this allows assistive technology to better
|
||||
contextualize the data
|
||||
- Avoid spanning data across cells
|
||||
- Be sure to make repeating actions unique by associating them with the object they relate to
|
||||
- **Example:** if there are multiple “Edit” buttons on a table, a screen reader should read “Edit,
|
||||
Netflix” for an edit option for a Netflix item.
|
||||
|
||||
40
libs/components/src/tabs/tabs.mdx
Normal file
40
libs/components/src/tabs/tabs.mdx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./tabs.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Tabs
|
||||
|
||||
The tab navigation and content tabs share the same styling. The tab navigation uses links to
|
||||
navigate between pages, whereas the tab list uses `<buttons>` to toggle content on a single page.
|
||||
|
||||
Tabs should be displayed on the `background-alt` color, with their content area set to background
|
||||
and 1rem of padding on the left and right.
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Content Tabs
|
||||
|
||||
<Story of={stories.ContentTabs} />
|
||||
|
||||
## Navigation Tabs
|
||||
|
||||
<Story of={stories.NavigationTabs} />
|
||||
|
||||
## Content tabs in dialogs
|
||||
|
||||
Tabs can be used in dialogs to separate related content.
|
||||
|
||||
<Story id="component-library-dialogs-dialog--tab-content" />
|
||||
|
||||
## Accessibility
|
||||
|
||||
**Navigation tabs** are implemented using the `<nav>` element and `<a>` for each tab.
|
||||
|
||||
**Content tabs** should be implemented with the `tablist` role and:
|
||||
|
||||
- Use `<button>` for the tab elements
|
||||
- Set `aria-selected` for each tab; “true” for selected and “false” for unselected
|
||||
- See WCAG for more: https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html
|
||||
36
libs/components/src/toggle-group/toggle-group.mdx
Normal file
36
libs/components/src/toggle-group/toggle-group.mdx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Meta, Story, Primary, Controls } from "@storybook/addon-docs";
|
||||
|
||||
import * as stories from "./toggle-group.stories";
|
||||
|
||||
<Meta of={stories} />
|
||||
|
||||
# Toggle Group
|
||||
|
||||
Toggle groups are used for quick filters for a data set. **Example:** the Member’s page of the Admin
|
||||
Console uses a toggle group to filter members by their organization status: all, invited, needs
|
||||
confirmation, revoked.
|
||||
|
||||
Toggle groups function as radio buttons and a radio group under the hood.
|
||||
|
||||
A button in a toggle group can have a badge counter added to show the number of items existing
|
||||
within that filter.
|
||||
|
||||
For focus states, use `focus-visible`.
|
||||
|
||||
<Primary />
|
||||
<Controls />
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Follow contrast rules for the main button styles.
|
||||
- Focus:
|
||||
- Implement as a radio group with button styling and a context label (context label can be screen
|
||||
reader only depending on use case).
|
||||
- Since only 1 button can be selected at a time to filter the toggle group acts similarly to a
|
||||
radio group.
|
||||
- When moving focus to a button group, the focus should always move to the selected button. The
|
||||
screen reader should then announce the button group: example “[context label], [button content]
|
||||
selected, of [# of buttons]”), the number of buttons and the currently selected button. The user
|
||||
may navigate the options then via left/right arrow keys.
|
||||
|
||||
See WCAG for more: https://www.w3.org/WAI/ARIA/apg/patterns/radio/
|
||||
Reference in New Issue
Block a user