mirror of
https://github.com/bitwarden/browser
synced 2026-02-07 20:24:01 +00:00
finish migrating side-nav module
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, input } from "@angular/core";
|
||||
import { Directive, EventEmitter, Output, input, model } from "@angular/core";
|
||||
import { RouterLink, RouterLinkActive } from "@angular/router";
|
||||
|
||||
/**
|
||||
@@ -21,10 +21,7 @@ export abstract class NavBaseComponent {
|
||||
/**
|
||||
* Optional icon, e.g. `"bwi-collection-shared"`
|
||||
*/
|
||||
// TODO: Skipped for migration because:
|
||||
// This input is used in a control flow expression (e.g. `@if` or `*ngIf`)
|
||||
// and migrating would break narrowing currently.
|
||||
@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.
|
||||
@@ -66,9 +63,7 @@ export abstract class NavBaseComponent {
|
||||
/**
|
||||
* Depth level when nested inside of a `'tree'` variant
|
||||
*/
|
||||
// TODO: Skipped for migration because:
|
||||
// Your application code writes to the input. This prevents migration.
|
||||
@Input() treeDepth = 0;
|
||||
treeDepth = model(0);
|
||||
|
||||
/**
|
||||
* If `true`, do not change styles when nav item is active.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@if (!hideIfEmpty() || nestedNavComponents.length > 0) {
|
||||
<bit-nav-item
|
||||
[text]="text()"
|
||||
[icon]="icon"
|
||||
[icon]="icon()"
|
||||
[route]="route()"
|
||||
[relativeTo]="relativeTo()"
|
||||
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
||||
@@ -17,14 +17,14 @@
|
||||
type="button"
|
||||
class="tw-ms-auto"
|
||||
[bitIconButton]="
|
||||
open ? 'bwi-angle-up' : variant() === 'tree' ? 'bwi-angle-right' : 'bwi-angle-down'
|
||||
open() ? 'bwi-angle-up' : variant() === 'tree' ? 'bwi-angle-right' : '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(' ')"
|
||||
></button>
|
||||
@@ -44,7 +44,7 @@
|
||||
</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(' ')"
|
||||
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
Component,
|
||||
ContentChildren,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Optional,
|
||||
Output,
|
||||
QueryList,
|
||||
SkipSelf,
|
||||
input,
|
||||
model,
|
||||
} from "@angular/core";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
@@ -38,7 +38,7 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI
|
||||
|
||||
/** 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,10 +49,7 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI
|
||||
/**
|
||||
* Is `true` if the expanded content is visible
|
||||
*/
|
||||
// TODO: Skipped for migration because:
|
||||
// Your application code writes to the input. This prevents migration.
|
||||
@Input()
|
||||
open = false;
|
||||
open = model(false);
|
||||
|
||||
/**
|
||||
* Automatically hide the nav group if there are no child buttons
|
||||
@@ -70,16 +67,16 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +87,7 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI
|
||||
return;
|
||||
}
|
||||
[...this.nestedNavComponents].forEach((navGroupOrItem) => {
|
||||
navGroupOrItem.treeDepth += 1;
|
||||
navGroupOrItem.treeDepth.update((depth) => depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,7 +96,7 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI
|
||||
if (!this.route()) {
|
||||
this.sideNavService.setOpen();
|
||||
}
|
||||
this.open = true;
|
||||
this.open.set(true);
|
||||
} else {
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
@@ -3,113 +3,114 @@
|
||||
open: sideNavService.open$ | async,
|
||||
} as data"
|
||||
>
|
||||
<div
|
||||
*ngIf="data.open || icon"
|
||||
class="tw-relative"
|
||||
[ngClass]="[
|
||||
showActiveStyles
|
||||
? 'tw-bg-background-alt4'
|
||||
: 'tw-bg-background-alt3 hover:tw-bg-primary-300/60',
|
||||
fvwStyles$ | async,
|
||||
]"
|
||||
>
|
||||
@if (data.open || icon()) {
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'padding-left': data.open
|
||||
? (variant() === 'tree' ? 2.5 : 1) + treeDepth * 1.5 + 'rem'
|
||||
: '0',
|
||||
}"
|
||||
class="tw-relative tw-flex"
|
||||
class="tw-relative"
|
||||
[ngClass]="[
|
||||
showActiveStyles
|
||||
? 'tw-bg-background-alt4'
|
||||
: 'tw-bg-background-alt3 hover:tw-bg-primary-300/60',
|
||||
fvwStyles$ | async,
|
||||
]"
|
||||
>
|
||||
<div [ngClass]="[variant() === 'tree' ? 'tw-py-1' : 'tw-py-2']">
|
||||
<div
|
||||
#slotStart
|
||||
class="[&>*:focus-visible::before]:!tw-ring-text-alt2 [&>*:hover]:!tw-border-text-alt2 [&>*]:!tw-text-alt2"
|
||||
>
|
||||
<ng-content select="[slot=start]"></ng-content>
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'padding-left': data.open
|
||||
? (variant() === 'tree' ? 2.5 : 1) + treeDepth * 1.5 + 'rem'
|
||||
: '0',
|
||||
}"
|
||||
class="tw-relative tw-flex"
|
||||
>
|
||||
<div [ngClass]="[variant() === 'tree' ? 'tw-py-1' : 'tw-py-2']">
|
||||
<div
|
||||
#slotStart
|
||||
class="[&>*:focus-visible::before]:!tw-ring-text-alt2 [&>*:hover]:!tw-border-text-alt2 [&>*]:!tw-text-alt2"
|
||||
>
|
||||
<ng-content select="[slot=start]"></ng-content>
|
||||
</div>
|
||||
<!-- Default content for #slotStart (for consistent sizing) -->
|
||||
<div
|
||||
*ngIf="slotStart.childElementCount === 0"
|
||||
[ngClass]="{
|
||||
'tw-w-0': variant() !== 'tree',
|
||||
}"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="tw-invisible"
|
||||
[bitIconButton]="'bwi-angle-down'"
|
||||
size="small"
|
||||
aria-hidden="true"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Default content for #slotStart (for consistent sizing) -->
|
||||
<div
|
||||
*ngIf="slotStart.childElementCount === 0"
|
||||
[ngClass]="{
|
||||
'tw-w-0': variant() !== 'tree',
|
||||
}"
|
||||
>
|
||||
|
||||
<ng-container *ngIf="route(); then isAnchor; else isButton"></ng-container>
|
||||
|
||||
<!-- Main content of `NavItem` -->
|
||||
<ng-template #anchorAndButtonContent>
|
||||
<div
|
||||
[title]="text()"
|
||||
class="tw-truncate"
|
||||
[ngClass]="[
|
||||
variant() === 'tree' ? 'tw-py-1' : 'tw-py-2',
|
||||
data.open ? 'tw-pe-4' : 'tw-text-center',
|
||||
]"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw tw-text-alt2 tw-mx-1 {{ icon() }}"
|
||||
[attr.aria-hidden]="data.open"
|
||||
[attr.aria-label]="text()"
|
||||
></i
|
||||
><span
|
||||
*ngIf="data.open"
|
||||
[ngClass]="showActiveStyles ? 'tw-font-bold' : 'tw-font-semibold'"
|
||||
>{{ text() }}</span
|
||||
>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<!-- Show if a value was passed to `this.to` -->
|
||||
<ng-template #isAnchor>
|
||||
<!-- The `data-fvw` attribute passes focus to `this.focusVisibleWithin$` -->
|
||||
<!-- The following `class` field should match the `#isButton` class field below -->
|
||||
<a
|
||||
class="tw-w-full tw-truncate tw-border-none tw-bg-transparent tw-p-0 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()"
|
||||
routerLinkActive
|
||||
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
||||
[ariaCurrentWhenActive]="'page'"
|
||||
(isActiveChange)="setIsActive($event)"
|
||||
(click)="mainContentClicked.emit()"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="anchorAndButtonContent"></ng-container>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<!-- Show if `this.to` is falsy -->
|
||||
<ng-template #isButton>
|
||||
<!-- Class field should match `#isAnchor` class field above -->
|
||||
<button
|
||||
type="button"
|
||||
class="tw-invisible"
|
||||
[bitIconButton]="'bwi-angle-down'"
|
||||
size="small"
|
||||
aria-hidden="true"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="route(); then isAnchor; else isButton"></ng-container>
|
||||
|
||||
<!-- Main content of `NavItem` -->
|
||||
<ng-template #anchorAndButtonContent>
|
||||
<div
|
||||
[title]="text()"
|
||||
class="tw-truncate"
|
||||
[ngClass]="[
|
||||
variant() === 'tree' ? 'tw-py-1' : 'tw-py-2',
|
||||
data.open ? 'tw-pe-4' : 'tw-text-center',
|
||||
]"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-fw tw-text-alt2 tw-mx-1 {{ icon }}"
|
||||
[attr.aria-hidden]="data.open"
|
||||
[attr.aria-label]="text()"
|
||||
></i
|
||||
><span
|
||||
*ngIf="data.open"
|
||||
[ngClass]="showActiveStyles ? 'tw-font-bold' : 'tw-font-semibold'"
|
||||
>{{ text() }}</span
|
||||
class="tw-w-full tw-truncate tw-border-none tw-bg-transparent tw-p-0 tw-text-start !tw-text-alt2 hover:tw-text-alt2 hover:tw-no-underline focus:tw-outline-none"
|
||||
data-fvw
|
||||
(click)="mainContentClicked.emit()"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="anchorAndButtonContent"></ng-container>
|
||||
</button>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
*ngIf="data.open"
|
||||
class="tw-flex -tw-ms-3 tw-pe-4 tw-gap-1 [&>*:focus-visible::before]:!tw-ring-text-alt2 [&>*:hover]:!tw-border-text-alt2 [&>*]:tw-text-alt2 empty:tw-hidden"
|
||||
[ngClass]="[variant() === 'tree' ? 'tw-py-1' : 'tw-py-2']"
|
||||
>
|
||||
<ng-content select="[slot=end]"></ng-content>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<!-- Show if a value was passed to `this.to` -->
|
||||
<ng-template #isAnchor>
|
||||
<!-- The `data-fvw` attribute passes focus to `this.focusVisibleWithin$` -->
|
||||
<!-- The following `class` field should match the `#isButton` class field below -->
|
||||
<a
|
||||
class="tw-w-full tw-truncate tw-border-none tw-bg-transparent tw-p-0 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()"
|
||||
routerLinkActive
|
||||
[routerLinkActiveOptions]="routerLinkActiveOptions()"
|
||||
[ariaCurrentWhenActive]="'page'"
|
||||
(isActiveChange)="setIsActive($event)"
|
||||
(click)="mainContentClicked.emit()"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="anchorAndButtonContent"></ng-container>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<!-- Show if `this.to` is falsy -->
|
||||
<ng-template #isButton>
|
||||
<!-- Class field should match `#isAnchor` class field above -->
|
||||
<button
|
||||
type="button"
|
||||
class="tw-w-full tw-truncate tw-border-none tw-bg-transparent tw-p-0 tw-text-start !tw-text-alt2 hover:tw-text-alt2 hover:tw-no-underline focus:tw-outline-none"
|
||||
data-fvw
|
||||
(click)="mainContentClicked.emit()"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="anchorAndButtonContent"></ng-container>
|
||||
</button>
|
||||
</ng-template>
|
||||
|
||||
<div
|
||||
*ngIf="data.open"
|
||||
class="tw-flex -tw-ms-3 tw-pe-4 tw-gap-1 [&>*:focus-visible::before]:!tw-ring-text-alt2 [&>*:hover]:!tw-border-text-alt2 [&>*]:tw-text-alt2 empty:tw-hidden"
|
||||
[ngClass]="[variant() === 'tree' ? 'tw-py-1' : 'tw-py-2']"
|
||||
>
|
||||
<ng-content select="[slot=end]"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
|
||||
Reference in New Issue
Block a user