mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-21807] Migrate fido components to tailwind (#16645)
* Update CSS class & refactor to use control flow instead of *ngIf directives * Migrate to Tailwind classes for styling on the use-browser-link component * Refactor if directive - use control flow instead * Refactor to leverage Tailwind CSS
This commit is contained in:
@@ -9,15 +9,17 @@
|
||||
<app-vault-icon slot="start" [cipher]="cipher"></app-vault-icon>
|
||||
<span data-testid="item-name">
|
||||
{{ cipher.name }}
|
||||
<i
|
||||
*ngIf="cipher.organizationId"
|
||||
[appA11yTitle]="'shared' | i18n"
|
||||
class="bwi bwi-collection-shared text-muted"
|
||||
></i>
|
||||
@if (cipher.organizationId) {
|
||||
<i [appA11yTitle]="'shared' | i18n" class="bwi bwi-collection-shared tw-text-muted"></i>
|
||||
}
|
||||
</span>
|
||||
<ng-container slot="secondary">
|
||||
<div *ngIf="getSubName(cipher)">{{ getSubName(cipher) }}</div>
|
||||
<div *ngIf="cipher.subTitle">{{ cipher.subTitle }}</div>
|
||||
@if (getSubName(cipher)) {
|
||||
<div>{{ getSubName(cipher) }}</div>
|
||||
}
|
||||
@if (cipher.subTitle) {
|
||||
<div>{{ cipher.subTitle }}</div>
|
||||
}
|
||||
</ng-container>
|
||||
</button>
|
||||
</bit-item>
|
||||
|
||||
@@ -1,52 +1,24 @@
|
||||
<ng-container *ngIf="(fido2PopoutSessionData$ | async).fallbackSupported">
|
||||
<div class="useBrowserlink">
|
||||
<button
|
||||
type="button"
|
||||
(click)="toggle()"
|
||||
cdkOverlayOrigin
|
||||
#trigger="cdkOverlayOrigin"
|
||||
aria-haspopup="dialog"
|
||||
aria-controls="cdk-overlay-container"
|
||||
>
|
||||
<span class="text-primary">
|
||||
@if ((fido2PopoutSessionData$ | async).fallbackSupported) {
|
||||
<div class="tw-flex tw-items-center tw-justify-center tw-p-2">
|
||||
<button type="button" [bitMenuTriggerFor]="deviceMenu">
|
||||
<span bitTypography="body2">
|
||||
{{ "useDeviceOrHardwareKey" | i18n }}
|
||||
</span>
|
||||
<i class="bwi bwi-fw bwi-sm bwi-angle-down" aria-hidden="true"></i>
|
||||
</button>
|
||||
<bit-menu #deviceMenu>
|
||||
<button type="button" bitMenuItem (click)="abort(false)">
|
||||
{{ "justOnce" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="abort()">
|
||||
{{ "alwaysForThisSite" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
</div>
|
||||
|
||||
<ng-template
|
||||
cdkConnectedOverlay
|
||||
[cdkConnectedOverlayOrigin]="trigger"
|
||||
[cdkConnectedOverlayOpen]="isOpen"
|
||||
[cdkConnectedOverlayPositions]="overlayPosition"
|
||||
[cdkConnectedOverlayHasBackdrop]="true"
|
||||
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
|
||||
(backdropClick)="isOpen = false"
|
||||
(detach)="close()"
|
||||
>
|
||||
<div class="box-content">
|
||||
<div
|
||||
class="fido2-browser-selector-dropdown"
|
||||
[@transformPanel]="'open'"
|
||||
cdkTrapFocus
|
||||
cdkTrapFocusAutoCapture
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<button type="button" class="fido2-browser-selector-dropdown-item" (click)="abort(false)">
|
||||
<span>{{ "justOnce" | i18n }}</span>
|
||||
</button>
|
||||
<br />
|
||||
<button type="button" class="fido2-browser-selector-dropdown-item" (click)="abort()">
|
||||
<span>{{ "alwaysForThisSite" | i18n }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
*ngIf="showOverlay"
|
||||
class="tw-absolute tw-size-full tw-bg-background-alt tw-inset-0 tw-bg-opacity-80 tw-z-50"
|
||||
></div>
|
||||
</ng-container>
|
||||
@if (showOverlay) {
|
||||
<div
|
||||
class="tw-absolute tw-size-full tw-bg-background-alt tw-inset-0 tw-bg-opacity-80 tw-z-50"
|
||||
></div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { animate, state, style, transition, trigger } from "@angular/animations";
|
||||
import { A11yModule } from "@angular/cdk/a11y";
|
||||
import { ConnectedPosition, CdkOverlayOrigin, CdkConnectedOverlay } from "@angular/cdk/overlay";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
@@ -13,6 +10,7 @@ import { NeverDomains } from "@bitwarden/common/models/domain/domain-service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { MenuModule } from "@bitwarden/components";
|
||||
|
||||
import { fido2PopoutSessionData$ } from "../../../vault/popup/utils/fido2-popout-session-data";
|
||||
import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-fido2-user-interface.service";
|
||||
@@ -20,63 +18,24 @@ import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-f
|
||||
@Component({
|
||||
selector: "app-fido2-use-browser-link",
|
||||
templateUrl: "fido2-use-browser-link.component.html",
|
||||
imports: [A11yModule, CdkConnectedOverlay, CdkOverlayOrigin, CommonModule, JslibModule],
|
||||
animations: [
|
||||
trigger("transformPanel", [
|
||||
state(
|
||||
"void",
|
||||
style({
|
||||
opacity: 0,
|
||||
}),
|
||||
),
|
||||
transition(
|
||||
"void => open",
|
||||
animate(
|
||||
"100ms linear",
|
||||
style({
|
||||
opacity: 1,
|
||||
}),
|
||||
),
|
||||
),
|
||||
transition("* => void", animate("100ms linear", style({ opacity: 0 }))),
|
||||
]),
|
||||
],
|
||||
imports: [CommonModule, JslibModule, MenuModule],
|
||||
})
|
||||
export class Fido2UseBrowserLinkComponent {
|
||||
showOverlay = false;
|
||||
isOpen = false;
|
||||
overlayPosition: ConnectedPosition[] = [
|
||||
{
|
||||
originX: "start",
|
||||
originY: "bottom",
|
||||
overlayX: "start",
|
||||
overlayY: "top",
|
||||
offsetY: 5,
|
||||
},
|
||||
];
|
||||
|
||||
protected fido2PopoutSessionData$ = fido2PopoutSessionData$();
|
||||
|
||||
constructor(
|
||||
private domainSettingsService: DomainSettingsService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private readonly domainSettingsService: DomainSettingsService,
|
||||
private readonly platformUtilsService: PlatformUtilsService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
toggle() {
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts the current FIDO2 session and fallsback to the browser.
|
||||
* @param excludeDomain - Identifies if the domain should be excluded from future FIDO2 prompts.
|
||||
*/
|
||||
protected async abort(excludeDomain = true) {
|
||||
this.close();
|
||||
const sessionData = await firstValueFrom(this.fido2PopoutSessionData$);
|
||||
|
||||
if (!excludeDomain) {
|
||||
|
||||
@@ -1,144 +1,140 @@
|
||||
<popup-page *ngIf="data$ | async as data">
|
||||
<popup-header
|
||||
slot="header"
|
||||
pageTitle="{{
|
||||
(passkeyAction === PasskeyActions.Register ? 'savePasskey' : 'logInWithPasskeyQuestion')
|
||||
| i18n
|
||||
}}"
|
||||
>
|
||||
<button
|
||||
*ngIf="showNewPasskeyButton"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
(click)="addCipher()"
|
||||
slot="end"
|
||||
@if (data$ | async; as data) {
|
||||
<popup-page>
|
||||
<popup-header
|
||||
slot="header"
|
||||
pageTitle="{{
|
||||
(passkeyAction === PasskeyActions.Register ? 'savePasskey' : 'logInWithPasskeyQuestion')
|
||||
| i18n
|
||||
}}"
|
||||
>
|
||||
<i class="bwi bwi-plus" aria-hidden="true"></i>
|
||||
{{ "new" | i18n }}
|
||||
</button>
|
||||
</popup-header>
|
||||
@if (showNewPasskeyButton) {
|
||||
<button bitButton buttonType="primary" type="button" (click)="addCipher()" slot="end">
|
||||
<i class="bwi bwi-plus" aria-hidden="true"></i>
|
||||
{{ "new" | i18n }}
|
||||
</button>
|
||||
}
|
||||
</popup-header>
|
||||
|
||||
<div class="tw-p-2">
|
||||
<bit-section *ngIf="passkeyAction === PasskeyActions.Register">
|
||||
<bit-search
|
||||
appAutofocus
|
||||
autocomplete="off"
|
||||
id="search"
|
||||
placeholder="{{ 'searchVault' | i18n }}"
|
||||
(ngModelChange)="search()"
|
||||
[(ngModel)]="searchText"
|
||||
></bit-search>
|
||||
</bit-section>
|
||||
<div class="tw-p-2">
|
||||
@if (passkeyAction === PasskeyActions.Register) {
|
||||
<bit-section>
|
||||
<bit-search
|
||||
appAutofocus
|
||||
autocomplete="off"
|
||||
id="search"
|
||||
placeholder="{{ 'searchVault' | i18n }}"
|
||||
(ngModelChange)="search()"
|
||||
[(ngModel)]="searchText"
|
||||
></bit-search>
|
||||
</bit-section>
|
||||
}
|
||||
|
||||
<!-- Display when adding a new passkey -->
|
||||
<bit-section *ngIf="data.message.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest">
|
||||
<!-- Display when matching ciphers (i.e. same domain, no passkeys) exist -->
|
||||
<ng-container *ngIf="displayedCiphers.length > 0">
|
||||
<bit-section-header>
|
||||
<h2 bitTypography="h6">{{ "chooseCipherForPasskeySave" | i18n }}</h2>
|
||||
</bit-section-header>
|
||||
<app-fido2-cipher-row
|
||||
*ngFor="let cipherItem of displayedCiphers"
|
||||
[cipher]="cipherItem"
|
||||
title="{{ 'passkeyItem' | i18n }}"
|
||||
(onSelected)="handleCipherItemSelect($event)"
|
||||
></app-fido2-cipher-row>
|
||||
</ng-container>
|
||||
@switch (data.message.type) {
|
||||
@case (BrowserFido2MessageTypes.ConfirmNewCredentialRequest) {
|
||||
<bit-section>
|
||||
@if (displayedCiphers.length > 0) {
|
||||
<bit-section-header>
|
||||
<h2 bitTypography="h6">{{ "chooseCipherForPasskeySave" | i18n }}</h2>
|
||||
</bit-section-header>
|
||||
@for (cipherItem of displayedCiphers; track cipherItem.id) {
|
||||
<app-fido2-cipher-row
|
||||
[cipher]="cipherItem"
|
||||
title="{{ 'passkeyItem' | i18n }}"
|
||||
(onSelected)="handleCipherItemSelect($event)"
|
||||
></app-fido2-cipher-row>
|
||||
}
|
||||
}
|
||||
@if (!displayedCiphers.length) {
|
||||
<bit-no-items [icon]="noResultsIcon">
|
||||
<ng-container slot="title">{{
|
||||
(hasSearched ? "noItemsMatchSearch" : "noMatchingLoginsForSite") | i18n
|
||||
}}</ng-container>
|
||||
<ng-container slot="description">{{
|
||||
(hasSearched ? "searchSavePasskeyNewLogin" : "clearFiltersOrTryAnother") | i18n
|
||||
}}</ng-container>
|
||||
|
||||
<!-- Display when no matching ciphers exist -->
|
||||
<ng-container *ngIf="!displayedCiphers.length">
|
||||
<bit-no-items class="tw-text-main" [icon]="noResultsIcon">
|
||||
<ng-container slot="title">{{
|
||||
(hasSearched ? "noItemsMatchSearch" : "noMatchingLoginsForSite") | i18n
|
||||
}}</ng-container>
|
||||
<ng-container slot="description">{{
|
||||
(hasSearched ? "searchSavePasskeyNewLogin" : "clearFiltersOrTryAnother") | i18n
|
||||
}}</ng-container>
|
||||
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
slot="button"
|
||||
type="button"
|
||||
(click)="hasSearched ? clearSearch() : saveNewLogin()"
|
||||
[loading]="loading"
|
||||
>
|
||||
{{ (hasSearched ? "multiSelectClearAll" : "savePasskeyNewLogin") | i18n }}
|
||||
</button>
|
||||
</bit-no-items>
|
||||
</ng-container>
|
||||
</bit-section>
|
||||
|
||||
<!-- Display when the passkey being saved already exists -->
|
||||
<bit-section
|
||||
*ngIf="data.message.type === BrowserFido2MessageTypes.InformExcludedCredentialRequest"
|
||||
>
|
||||
<div class="auth-flow">
|
||||
<p class="subtitle">{{ "passkeyAlreadyExists" | i18n }}</p>
|
||||
<div class="box list">
|
||||
<div class="box-content">
|
||||
<app-fido2-cipher-row
|
||||
*ngFor="let cipherItem of displayedCiphers"
|
||||
[cipher]="cipherItem"
|
||||
title="{{ 'passkeyItem' | i18n }}"
|
||||
(onSelected)="handleCipherItemSelect($event)"
|
||||
></app-fido2-cipher-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</bit-section>
|
||||
|
||||
<!-- Display when picking a passkey to login with -->
|
||||
<bit-section *ngIf="data.message.type === BrowserFido2MessageTypes.PickCredentialRequest">
|
||||
<!-- Display when matching ciphers exist -->
|
||||
<ng-container *ngIf="displayedCiphers.length > 0">
|
||||
<ng-container slot="title">{{ "chooseCipherForPasskeyAuth" | i18n }}</ng-container>
|
||||
<app-fido2-cipher-row
|
||||
*ngFor="let cipherItem of displayedCiphers"
|
||||
[cipher]="cipherItem"
|
||||
title="{{ 'passkeyItem' | i18n }}"
|
||||
(onSelected)="handleCipherItemSelect($event)"
|
||||
></app-fido2-cipher-row>
|
||||
</ng-container>
|
||||
|
||||
<!-- Display when no matching ciphers exist -->
|
||||
<ng-container *ngIf="!displayedCiphers.length">
|
||||
<bit-no-items class="tw-text-main" [icon]="noResultsIcon">
|
||||
<ng-container slot="title">{{
|
||||
(hasSearched ? "noItemsMatchSearch" : "noMatchingLoginsForSite") | i18n
|
||||
}}</ng-container>
|
||||
<ng-container slot="description">{{
|
||||
(hasSearched ? "searchSavePasskeyNewLogin" : "clearFiltersOrTryAnother") | i18n
|
||||
}}</ng-container>
|
||||
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
slot="button"
|
||||
type="button"
|
||||
(click)="hasSearched ? clearSearch() : saveNewLogin()"
|
||||
[loading]="loading"
|
||||
>
|
||||
{{ (hasSearched ? "multiSelectClearAll" : "savePasskeyNewLogin") | i18n }}
|
||||
</button>
|
||||
</bit-no-items>
|
||||
</ng-container>
|
||||
</bit-section>
|
||||
|
||||
<!-- Display when initiating passkey login, but no cooresponding cipher is found in the vault -->
|
||||
<bit-section
|
||||
*ngIf="data.message.type === BrowserFido2MessageTypes.InformCredentialNotFoundRequest"
|
||||
>
|
||||
<div class="auth-flow">
|
||||
<p class="subtitle">{{ "noPasskeysFoundForThisApplication" | i18n }}</p>
|
||||
</div>
|
||||
<button type="button" class="btn primary block" (click)="abort(false)">
|
||||
<span [hidden]="loading">{{ "close" | i18n }}</span>
|
||||
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
</bit-section>
|
||||
|
||||
<app-fido2-use-browser-link></app-fido2-use-browser-link>
|
||||
</div>
|
||||
</popup-page>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
slot="button"
|
||||
type="button"
|
||||
(click)="hasSearched ? clearSearch() : saveNewLogin()"
|
||||
[loading]="loading"
|
||||
>
|
||||
{{ (hasSearched ? "multiSelectClearAll" : "savePasskeyNewLogin") | i18n }}
|
||||
</button>
|
||||
</bit-no-items>
|
||||
}
|
||||
</bit-section>
|
||||
}
|
||||
@case (BrowserFido2MessageTypes.InformExcludedCredentialRequest) {
|
||||
<bit-section>
|
||||
<div class="tw-space-y-4">
|
||||
<p>{{ "passkeyAlreadyExists" | i18n }}</p>
|
||||
<div class="tw-divide-y tw-divide-secondary-300">
|
||||
@for (cipherItem of displayedCiphers; track cipherItem.id) {
|
||||
<app-fido2-cipher-row
|
||||
[cipher]="cipherItem"
|
||||
title="{{ 'passkeyItem' | i18n }}"
|
||||
(onSelected)="handleCipherItemSelect($event)"
|
||||
></app-fido2-cipher-row>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</bit-section>
|
||||
}
|
||||
@case (BrowserFido2MessageTypes.PickCredentialRequest) {
|
||||
<bit-section>
|
||||
@if (displayedCiphers.length > 0) {
|
||||
<ng-container slot="title">{{ "chooseCipherForPasskeyAuth" | i18n }}</ng-container>
|
||||
@for (cipherItem of displayedCiphers; track cipherItem.id) {
|
||||
<app-fido2-cipher-row
|
||||
[cipher]="cipherItem"
|
||||
title="{{ 'passkeyItem' | i18n }}"
|
||||
(onSelected)="handleCipherItemSelect($event)"
|
||||
></app-fido2-cipher-row>
|
||||
}
|
||||
} @else {
|
||||
<bit-no-items [icon]="noResultsIcon">
|
||||
<ng-container slot="title">{{
|
||||
(hasSearched ? "noItemsMatchSearch" : "noMatchingLoginsForSite") | i18n
|
||||
}}</ng-container>
|
||||
<ng-container slot="description">{{
|
||||
(hasSearched ? "searchSavePasskeyNewLogin" : "clearFiltersOrTryAnother") | i18n
|
||||
}}</ng-container>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
slot="button"
|
||||
type="button"
|
||||
(click)="hasSearched ? clearSearch() : saveNewLogin()"
|
||||
[loading]="loading"
|
||||
>
|
||||
{{ (hasSearched ? "multiSelectClearAll" : "savePasskeyNewLogin") | i18n }}
|
||||
</button>
|
||||
</bit-no-items>
|
||||
}
|
||||
</bit-section>
|
||||
}
|
||||
@case (BrowserFido2MessageTypes.InformCredentialNotFoundRequest) {
|
||||
<bit-section>
|
||||
<div class="tw-space-y-4">
|
||||
<p>{{ "noPasskeysFoundForThisApplication" | i18n }}</p>
|
||||
</div>
|
||||
<button
|
||||
bitButton
|
||||
block
|
||||
buttonType="primary"
|
||||
type="button"
|
||||
(click)="abort(false)"
|
||||
[loading]="loading"
|
||||
>
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</bit-section>
|
||||
}
|
||||
}
|
||||
<app-fido2-use-browser-link></app-fido2-use-browser-link>
|
||||
</div>
|
||||
</popup-page>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user