mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +00:00
[CL-707] Migrate CL codebase to signals (#15340)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { Directive, EventEmitter, Output, input } from "@angular/core";
|
||||
import { RouterLink, RouterLinkActive } from "@angular/router";
|
||||
|
||||
/**
|
||||
@@ -11,17 +11,17 @@ export abstract class NavBaseComponent {
|
||||
/**
|
||||
* Text to display in main content
|
||||
*/
|
||||
@Input() text: string;
|
||||
readonly text = input<string>();
|
||||
|
||||
/**
|
||||
* `aria-label` for main content
|
||||
*/
|
||||
@Input() ariaLabel: string;
|
||||
readonly ariaLabel = input<string>();
|
||||
|
||||
/**
|
||||
* Optional icon, e.g. `"bwi-collection-shared"`
|
||||
*/
|
||||
@Input() icon: string;
|
||||
readonly icon = input<string>();
|
||||
|
||||
/**
|
||||
* Optional route to be passed to internal `routerLink`. If not provided, the nav component will render as a button.
|
||||
@@ -34,31 +34,31 @@ export abstract class NavBaseComponent {
|
||||
*
|
||||
* See: {@link https://github.com/angular/angular/issues/24482}
|
||||
*/
|
||||
@Input() route?: RouterLink["routerLink"];
|
||||
readonly route = input<RouterLink["routerLink"]>();
|
||||
|
||||
/**
|
||||
* Passed to internal `routerLink`
|
||||
*
|
||||
* See {@link RouterLink.relativeTo}
|
||||
*/
|
||||
@Input() relativeTo?: RouterLink["relativeTo"];
|
||||
readonly relativeTo = input<RouterLink["relativeTo"]>();
|
||||
|
||||
/**
|
||||
* Passed to internal `routerLink`
|
||||
*
|
||||
* See {@link RouterLinkActive.routerLinkActiveOptions}
|
||||
*/
|
||||
@Input() routerLinkActiveOptions?: RouterLinkActive["routerLinkActiveOptions"] = {
|
||||
readonly routerLinkActiveOptions = input<RouterLinkActive["routerLinkActiveOptions"]>({
|
||||
paths: "subset",
|
||||
queryParams: "ignored",
|
||||
fragment: "ignored",
|
||||
matrixParams: "ignored",
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* If `true`, do not change styles when nav item is active.
|
||||
*/
|
||||
@Input() hideActiveStyles = false;
|
||||
readonly hideActiveStyles = input(false);
|
||||
|
||||
/**
|
||||
* Fires when main content is clicked
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<!-- This a higher order component that composes `NavItemComponent` -->
|
||||
@if (!hideIfEmpty || nestedNavComponents.length > 0) {
|
||||
@if (!hideIfEmpty() || nestedNavComponents.length > 0) {
|
||||
<bit-nav-item
|
||||
[text]="text"
|
||||
[icon]="icon"
|
||||
[route]="route"
|
||||
[relativeTo]="relativeTo"
|
||||
[routerLinkActiveOptions]="routerLinkActiveOptions"
|
||||
[text]="text()"
|
||||
[icon]="icon()"
|
||||
[route]="route()"
|
||||
[relativeTo]="relativeTo()"
|
||||
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
||||
(mainContentClicked)="handleMainContentClicked()"
|
||||
[ariaLabel]="ariaLabel"
|
||||
[ariaLabel]="ariaLabel()"
|
||||
[hideActiveStyles]="parentHideActiveStyles"
|
||||
>
|
||||
<ng-template #button>
|
||||
<button
|
||||
type="button"
|
||||
class="tw-ms-auto"
|
||||
[bitIconButton]="open ? 'bwi-angle-up' : 'bwi-angle-down'"
|
||||
[bitIconButton]="open() ? 'bwi-angle-up' : 'bwi-angle-down'"
|
||||
[buttonType]="'light'"
|
||||
(click)="toggle($event)"
|
||||
size="small"
|
||||
[title]="'toggleCollapse' | i18n"
|
||||
aria-haspopup="true"
|
||||
[attr.aria-expanded]="open.toString()"
|
||||
[attr.aria-expanded]="open().toString()"
|
||||
[attr.aria-controls]="contentId"
|
||||
[attr.aria-label]="['toggleCollapse' | i18n, text].join(' ')"
|
||||
[attr.aria-label]="['toggleCollapse' | i18n, text()].join(' ')"
|
||||
></button>
|
||||
</ng-template>
|
||||
<ng-container slot="end">
|
||||
@@ -32,10 +32,10 @@
|
||||
</bit-nav-item>
|
||||
<!-- [attr.aria-controls] of the above button expects a unique ID on the controlled element -->
|
||||
@if (sideNavService.open$ | async) {
|
||||
@if (open) {
|
||||
@if (open()) {
|
||||
<div
|
||||
[attr.id]="contentId"
|
||||
[attr.aria-label]="[text, 'submenu' | i18n].join(' ')"
|
||||
[attr.aria-label]="[text(), 'submenu' | i18n].join(' ')"
|
||||
role="group"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
|
||||
@@ -4,11 +4,12 @@ import {
|
||||
Component,
|
||||
ContentChildren,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Optional,
|
||||
Output,
|
||||
QueryList,
|
||||
SkipSelf,
|
||||
input,
|
||||
model,
|
||||
} from "@angular/core";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
@@ -36,7 +37,7 @@ export class NavGroupComponent extends NavBaseComponent {
|
||||
|
||||
/** When the side nav is open, the parent nav item should not show active styles when open. */
|
||||
protected get parentHideActiveStyles(): boolean {
|
||||
return this.hideActiveStyles || (this.open && this.sideNavService.open);
|
||||
return this.hideActiveStyles() || (this.open() && this.sideNavService.open);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,14 +48,12 @@ export class NavGroupComponent extends NavBaseComponent {
|
||||
/**
|
||||
* Is `true` if the expanded content is visible
|
||||
*/
|
||||
@Input()
|
||||
open = false;
|
||||
readonly open = model(false);
|
||||
|
||||
/**
|
||||
* Automatically hide the nav group if there are no child buttons
|
||||
*/
|
||||
@Input({ transform: booleanAttribute })
|
||||
hideIfEmpty = false;
|
||||
readonly hideIfEmpty = input(false, { transform: booleanAttribute });
|
||||
|
||||
@Output()
|
||||
openChange = new EventEmitter<boolean>();
|
||||
@@ -67,24 +66,24 @@ export class NavGroupComponent extends NavBaseComponent {
|
||||
}
|
||||
|
||||
setOpen(isOpen: boolean) {
|
||||
this.open = isOpen;
|
||||
this.openChange.emit(this.open);
|
||||
this.open.set(isOpen);
|
||||
this.openChange.emit(this.open());
|
||||
// FIXME: Remove when updating file. Eslint update
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
this.open && this.parentNavGroup?.setOpen(this.open);
|
||||
this.open() && this.parentNavGroup?.setOpen(this.open());
|
||||
}
|
||||
|
||||
protected toggle(event?: MouseEvent) {
|
||||
event?.stopPropagation();
|
||||
this.setOpen(!this.open);
|
||||
this.setOpen(!this.open());
|
||||
}
|
||||
|
||||
protected handleMainContentClicked() {
|
||||
if (!this.sideNavService.open) {
|
||||
if (!this.route) {
|
||||
if (!this.route()) {
|
||||
this.sideNavService.setOpen();
|
||||
}
|
||||
this.open = true;
|
||||
this.open.set(true);
|
||||
} else {
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="tw-ps-2 tw-pe-2">
|
||||
@let open = sideNavService.open$ | async;
|
||||
@if (open || icon) {
|
||||
@if (open || icon()) {
|
||||
<div
|
||||
class="tw-relative tw-rounded-md tw-h-10"
|
||||
[ngClass]="[
|
||||
@@ -16,17 +16,17 @@
|
||||
<!-- Main content of `NavItem` -->
|
||||
<ng-template #anchorAndButtonContent>
|
||||
<div
|
||||
[title]="text"
|
||||
[title]="text()"
|
||||
class="tw-gap-2 tw-items-center tw-font-bold tw-h-full tw-content-center"
|
||||
[ngClass]="{ 'tw-text-center': !open, 'tw-flex': open }"
|
||||
>
|
||||
<i
|
||||
class="!tw-m-0 tw-w-4 tw-shrink-0 bwi bwi-fw tw-text-alt2 {{ icon }}"
|
||||
class="!tw-m-0 tw-w-4 tw-shrink-0 bwi bwi-fw tw-text-alt2 {{ icon() }}"
|
||||
[attr.aria-hidden]="open"
|
||||
[attr.aria-label]="text"
|
||||
[attr.aria-label]="text()"
|
||||
></i>
|
||||
@if (open) {
|
||||
<span class="tw-truncate">{{ text }}</span>
|
||||
<span class="tw-truncate">{{ text() }}</span>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
@@ -38,11 +38,11 @@
|
||||
<a
|
||||
class="tw-size-full tw-px-3 tw-block tw-truncate tw-border-none tw-bg-transparent tw-text-start !tw-text-alt2 hover:tw-text-alt2 hover:tw-no-underline focus:tw-outline-none"
|
||||
data-fvw
|
||||
[routerLink]="route"
|
||||
[relativeTo]="relativeTo"
|
||||
[attr.aria-label]="ariaLabel || text"
|
||||
[routerLink]="route()"
|
||||
[relativeTo]="relativeTo()"
|
||||
[attr.aria-label]="ariaLabel() || text()"
|
||||
routerLinkActive
|
||||
[routerLinkActiveOptions]="routerLinkActiveOptions"
|
||||
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
||||
[ariaCurrentWhenActive]="'page'"
|
||||
(isActiveChange)="setIsActive($event)"
|
||||
(click)="mainContentClicked.emit()"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, HostListener, Input, Optional } from "@angular/core";
|
||||
import { Component, HostListener, Optional, input } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { BehaviorSubject, map } from "rxjs";
|
||||
|
||||
@@ -21,7 +21,7 @@ export abstract class NavGroupAbstraction {
|
||||
})
|
||||
export class NavItemComponent extends NavBaseComponent {
|
||||
/** Forces active styles to be shown, regardless of the `routerLinkActiveOptions` */
|
||||
@Input() forceActiveStyles? = false;
|
||||
readonly forceActiveStyles = input<boolean>(false);
|
||||
|
||||
/**
|
||||
* Is `true` if `to` matches the current route
|
||||
@@ -34,7 +34,7 @@ export class NavItemComponent extends NavBaseComponent {
|
||||
}
|
||||
}
|
||||
protected get showActiveStyles() {
|
||||
return this.forceActiveStyles || (this._isActive && !this.hideActiveStyles);
|
||||
return this.forceActiveStyles() || (this._isActive && !this.hideActiveStyles());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
class="tw-px-2 tw-pt-5"
|
||||
>
|
||||
<a
|
||||
[routerLink]="route"
|
||||
[routerLink]="route()"
|
||||
class="tw-p-3 tw-block tw-rounded-md tw-bg-background-alt3 tw-outline-none focus-visible:tw-ring focus-visible:tw-ring-inset focus-visible:tw-ring-text-alt2 hover:tw-bg-hover-contrast [&_path]:tw-fill-text-alt2"
|
||||
[ngClass]="{ '[&_svg]:tw-w-[1.687rem]': !sideNavService.open }"
|
||||
[attr.aria-label]="label"
|
||||
[title]="label"
|
||||
[attr.aria-label]="label()"
|
||||
[title]="label()"
|
||||
routerLinkActive
|
||||
[ariaCurrentWhenActive]="'page'"
|
||||
>
|
||||
<bit-icon [icon]="sideNavService.open ? openIcon : closedIcon"></bit-icon>
|
||||
<bit-icon [icon]="sideNavService.open ? openIcon() : closedIcon()"></bit-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { Component, input } from "@angular/core";
|
||||
import { RouterLinkActive, RouterLink } from "@angular/router";
|
||||
|
||||
import { Icon } from "../icon";
|
||||
@@ -17,18 +18,18 @@ import { SideNavService } from "./side-nav.service";
|
||||
})
|
||||
export class NavLogoComponent {
|
||||
/** Icon that is displayed when the side nav is closed */
|
||||
@Input() closedIcon = BitwardenShield;
|
||||
readonly closedIcon = input(BitwardenShield);
|
||||
|
||||
/** Icon that is displayed when the side nav is open */
|
||||
@Input({ required: true }) openIcon: Icon;
|
||||
readonly openIcon = input.required<Icon>();
|
||||
|
||||
/**
|
||||
* Route to be passed to internal `routerLink`
|
||||
*/
|
||||
@Input({ required: true }) route: string | any[];
|
||||
readonly route = input.required<string | any[]>();
|
||||
|
||||
/** Passed to `attr.aria-label` and `attr.title` */
|
||||
@Input({ required: true }) label: string;
|
||||
readonly label = input.required<string>();
|
||||
|
||||
constructor(protected sideNavService: SideNavService) {}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
class="tw-fixed md:tw-sticky tw-inset-y-0 tw-left-0 tw-z-30 tw-flex tw-h-screen tw-flex-col tw-overscroll-none tw-overflow-auto tw-bg-background-alt3 tw-outline-none"
|
||||
[ngClass]="{ 'tw-w-60': data.open }"
|
||||
[ngStyle]="
|
||||
variant === 'secondary' && {
|
||||
variant() === 'secondary' && {
|
||||
'--color-text-alt2': 'var(--color-text-main)',
|
||||
'--color-background-alt3': 'var(--color-secondary-100)',
|
||||
'--color-background-alt4': 'var(--color-secondary-300)',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// @ts-strict-ignore
|
||||
import { CdkTrapFocus } from "@angular/cdk/a11y";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, ElementRef, Input, ViewChild } from "@angular/core";
|
||||
import { Component, ElementRef, ViewChild, input } from "@angular/core";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
@@ -19,7 +19,7 @@ export type SideNavVariant = "primary" | "secondary";
|
||||
imports: [CommonModule, CdkTrapFocus, NavDividerComponent, BitIconButtonComponent, I18nPipe],
|
||||
})
|
||||
export class SideNavComponent {
|
||||
@Input() variant: SideNavVariant = "primary";
|
||||
readonly variant = input<SideNavVariant>("primary");
|
||||
|
||||
@ViewChild("toggleButton", { read: ElementRef, static: true })
|
||||
private toggleButton: ElementRef<HTMLButtonElement>;
|
||||
|
||||
Reference in New Issue
Block a user