1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-14 01:01:31 +01:00

[CL-553] Migrate CL to Control Flow syntax (#12390)

This commit is contained in:
Oscar Hinton 2025-02-03 20:11:59 +01:00 committed by GitHub
parent 444e928895
commit e5ffc162b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 480 additions and 428 deletions

View File

@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { NgIf, NgClass } from "@angular/common";
import { NgClass } from "@angular/common";
import { Component, Input, OnChanges } from "@angular/core";
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";
@ -18,9 +18,11 @@ const SizeClasses: Record<SizeTypes, string[]> = {
@Component({
selector: "bit-avatar",
template: `<img *ngIf="src" [src]="src" title="{{ title || text }}" [ngClass]="classList" />`,
template: `@if (src) {
<img [src]="src" title="{{ title || text }}" [ngClass]="classList" />
}`,
standalone: true,
imports: [NgIf, NgClass],
imports: [NgClass],
})
export class AvatarComponent implements OnChanges {
@Input() border = false;

View File

@ -1,11 +1,15 @@
<div class="tw-inline-flex tw-flex-wrap tw-gap-2">
<ng-container *ngFor="let item of filteredItems; let last = last">
@for (item of filteredItems; track item; let last = $last) {
<span bitBadge [variant]="variant" [truncate]="truncate">
{{ item }}
</span>
<span class="tw-sr-only" *ngIf="!last || isFiltered">, </span>
</ng-container>
<span *ngIf="isFiltered" bitBadge [variant]="variant">
{{ "plusNMore" | i18n: (items.length - filteredItems.length).toString() }}
</span>
@if (!last || isFiltered) {
<span class="tw-sr-only">, </span>
}
}
@if (isFiltered) {
<span bitBadge [variant]="variant">
{{ "plusNMore" | i18n: (items.length - filteredItems.length).toString() }}
</span>
}
</div>

View File

@ -1,6 +1,6 @@
// 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, OnChanges } from "@angular/core";
import { I18nPipe } from "@bitwarden/ui-common";
@ -11,7 +11,7 @@ import { BadgeModule, BadgeVariant } from "../badge";
selector: "bit-badge-list",
templateUrl: "badge-list.component.html",
standalone: true,
imports: [CommonModule, BadgeModule, I18nPipe],
imports: [BadgeModule, I18nPipe],
})
export class BadgeListComponent implements OnChanges {
private _maxItems: number;

View File

@ -4,21 +4,24 @@
[attr.role]="useAlertRole ? 'status' : null"
[attr.aria-live]="useAlertRole ? 'polite' : null"
>
<i class="bwi tw-align-middle tw-text-base" [ngClass]="icon" *ngIf="icon" aria-hidden="true"></i>
@if (icon) {
<i class="bwi tw-align-middle tw-text-base" [ngClass]="icon" aria-hidden="true"></i>
}
<!-- Overriding focus-visible color for link buttons for a11y against colored background -->
<span class="tw-grow tw-text-base [&>button[bitlink]:focus-visible:before]:!tw-ring-text-main">
<ng-content></ng-content>
</span>
<!-- Overriding hover and focus-visible colors for a11y against colored background -->
<button
*ngIf="showClose"
class="hover:tw-border-text-main focus-visible:before:tw-ring-text-main"
type="button"
bitIconButton="bwi-close"
buttonType="main"
size="default"
(click)="onClose.emit()"
[attr.title]="'close' | i18n"
[attr.aria-label]="'close' | i18n"
></button>
@if (showClose) {
<button
class="hover:tw-border-text-main focus-visible:before:tw-ring-text-main"
type="button"
bitIconButton="bwi-close"
buttonType="main"
size="default"
(click)="onClose.emit()"
[attr.title]="'close' | i18n"
[attr.aria-label]="'close' | i18n"
></button>
}
</div>

View File

@ -1,3 +1,6 @@
<ng-template>
<i *ngIf="icon" class="bwi {{ icon }} !tw-mr-2" aria-hidden="true"></i><ng-content></ng-content>
@if (icon) {
<i class="bwi {{ icon }} !tw-mr-2" aria-hidden="true"></i>
}
<ng-content></ng-content>
</ng-template>

View File

@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { NgIf } from "@angular/common";
import { Component, EventEmitter, Input, Output, TemplateRef, ViewChild } from "@angular/core";
import { QueryParamsHandling } from "@angular/router";
@ -8,7 +8,6 @@ import { QueryParamsHandling } from "@angular/router";
selector: "bit-breadcrumb",
templateUrl: "./breadcrumb.component.html",
standalone: true,
imports: [NgIf],
})
export class BreadcrumbComponent {
@Input()

View File

@ -1,5 +1,5 @@
<ng-container *ngFor="let breadcrumb of beforeOverflow; let last = last">
<ng-container *ngIf="breadcrumb.route">
@for (breadcrumb of beforeOverflow; track breadcrumb; let last = $last) {
@if (breadcrumb.route) {
<a
bitLink
linkType="primary"
@ -10,8 +10,8 @@
>
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
</a>
</ng-container>
<ng-container *ngIf="!breadcrumb.route">
}
@if (!breadcrumb.route) {
<button
type="button"
bitLink
@ -21,13 +21,16 @@
>
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
</button>
</ng-container>
<i *ngIf="!last" class="bwi bwi-angle-right tw-mx-1.5 tw-text-main"></i>
</ng-container>
<ng-container *ngIf="hasOverflow">
<i *ngIf="beforeOverflow.length > 0" class="bwi bwi-angle-right tw-mx-1.5 tw-text-main"></i>
}
@if (!last) {
<i class="bwi bwi-angle-right tw-mx-1.5 tw-text-main"></i>
}
}
@if (hasOverflow) {
@if (beforeOverflow.length > 0) {
<i class="bwi bwi-angle-right tw-mx-1.5 tw-text-main"></i>
}
<button
type="button"
bitIconButton="bwi-ellipsis-h"
@ -35,10 +38,9 @@
size="small"
aria-haspopup
></button>
<bit-menu #overflowMenu>
<ng-container *ngFor="let breadcrumb of overflow">
<ng-container *ngIf="breadcrumb.route">
@for (breadcrumb of overflow; track breadcrumb) {
@if (breadcrumb.route) {
<a
bitMenuItem
linkType="primary"
@ -48,18 +50,17 @@
>
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
</a>
</ng-container>
<ng-container *ngIf="!breadcrumb.route">
}
@if (!breadcrumb.route) {
<button type="button" bitMenuItem linkType="primary" (click)="breadcrumb.onClick($event)">
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
</button>
</ng-container>
</ng-container>
}
}
</bit-menu>
<i class="bwi bwi-angle-right tw-mx-1.5 tw-text-main"></i>
<ng-container *ngFor="let breadcrumb of afterOverflow; let last = last">
<ng-container *ngIf="breadcrumb.route">
@for (breadcrumb of afterOverflow; track breadcrumb; let last = $last) {
@if (breadcrumb.route) {
<a
bitLink
linkType="primary"
@ -70,8 +71,8 @@
>
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
</a>
</ng-container>
<ng-container *ngIf="!breadcrumb.route">
}
@if (!breadcrumb.route) {
<button
type="button"
bitLink
@ -81,7 +82,9 @@
>
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
</button>
</ng-container>
<i *ngIf="!last" class="bwi bwi-angle-right tw-mx-1.5 tw-text-main"></i>
</ng-container>
</ng-container>
}
@if (!last) {
<i class="bwi bwi-angle-right tw-mx-1.5 tw-text-main"></i>
}
}
}

View File

@ -86,16 +86,15 @@ export const DisabledWithAttribute: Story = {
render: (args) => ({
props: args,
template: `
<ng-container *ngIf="disabled">
@if (disabled) {
<button bitButton disabled [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button>
<button bitButton disabled [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button>
<button bitButton disabled [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
</ng-container>
<ng-container *ngIf="!disabled">
} @else {
<button bitButton [loading]="loading" [block]="block" buttonType="primary" class="tw-mr-2">Primary</button>
<button bitButton [loading]="loading" [block]="block" buttonType="secondary" class="tw-mr-2">Secondary</button>
<button bitButton [loading]="loading" [block]="block" buttonType="danger" class="tw-mr-2">Danger</button>
</ng-container>
}
`,
}),
args: {

View File

@ -3,10 +3,14 @@
[ngClass]="calloutClass"
[attr.aria-labelledby]="titleId"
>
<header id="{{ titleId }}" class="tw-mb-1 tw-mt-0 tw-text-base tw-font-semibold" *ngIf="title">
<i class="bwi" [ngClass]="[icon, headerClass]" *ngIf="icon" aria-hidden="true"></i>
{{ title }}
</header>
@if (title) {
<header id="{{ titleId }}" class="tw-mb-1 tw-mt-0 tw-text-base tw-font-semibold">
@if (icon) {
<i class="bwi" [ngClass]="[icon, headerClass]" aria-hidden="true"></i>
}
{{ title }}
</header>
}
<div bitTypography="body2">
<ng-content></ng-content>
</div>

View File

@ -1,10 +1,8 @@
import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component } from "@angular/core";
@Component({
selector: "bit-card",
standalone: true,
imports: [CommonModule],
template: `<ng-content></ng-content>`,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {

View File

@ -30,78 +30,80 @@
<i class="bwi !tw-text-[inherit]" [ngClass]="icon"></i>
<span class="tw-truncate">{{ label }}</span>
</span>
<i
*ngIf="!selectedOption"
class="bwi tw-mt-0.5"
[ngClass]="menuTrigger.isOpen ? 'bwi-angle-up' : 'bwi-angle-down'"
></i>
@if (!selectedOption) {
<i
class="bwi tw-mt-0.5"
[ngClass]="menuTrigger.isOpen ? 'bwi-angle-up' : 'bwi-angle-down'"
></i>
}
</button>
<!-- Close button -->
<button
*ngIf="selectedOption"
type="button"
[attr.aria-label]="'removeItem' | i18n: label"
[disabled]="disabled"
class="tw-bg-transparent hover:tw-bg-transparent tw-outline-none tw-rounded-full tw-py-0.5 tw-px-1 tw-mr-1 tw-text-[color:inherit] tw-text-[length:inherit] tw-border-solid tw-border tw-border-transparent hover:tw-border-text-contrast hover:disabled:tw-border-transparent tw-flex tw-items-center tw-justify-center focus-visible:tw-ring-2 tw-ring-text-contrast focus-visible:hover:tw-border-transparent"
[ngClass]="{
'tw-cursor-not-allowed': disabled,
}"
(click)="clear()"
>
<i class="bwi bwi-close tw-text-xs"></i>
</button>
@if (selectedOption) {
<button
type="button"
[attr.aria-label]="'removeItem' | i18n: label"
[disabled]="disabled"
class="tw-bg-transparent hover:tw-bg-transparent tw-outline-none tw-rounded-full tw-py-0.5 tw-px-1 tw-mr-1 tw-text-[color:inherit] tw-text-[length:inherit] tw-border-solid tw-border tw-border-transparent hover:tw-border-text-contrast hover:disabled:tw-border-transparent tw-flex tw-items-center tw-justify-center focus-visible:tw-ring-2 tw-ring-text-contrast focus-visible:hover:tw-border-transparent"
[ngClass]="{
'tw-cursor-not-allowed': disabled,
}"
(click)="clear()"
>
<i class="bwi bwi-close tw-text-xs"></i>
</button>
}
</div>
<bit-menu #menu (closed)="handleMenuClosed()">
<div
*ngIf="renderedOptions"
class="tw-max-h-80 tw-min-w-32 tw-max-w-80 tw-text-sm"
[ngStyle]="menuWidth && { width: menuWidth + 'px' }"
>
<ng-container *ngIf="getParent(renderedOptions) as parent">
<button
type="button"
bitMenuItem
(click)="viewOption(parent, $event)"
class="tw-text-[length:inherit]"
[title]="'backTo' | i18n: parent.label ?? placeholderText"
>
<i slot="start" class="bwi bwi-angle-left" aria-hidden="true"></i>
{{ "backTo" | i18n: parent.label ?? placeholderText }}
</button>
<button
type="button"
bitMenuItem
(click)="selectOption(renderedOptions, $event)"
[title]="'viewItemsIn' | i18n: renderedOptions.label"
class="tw-text-[length:inherit]"
>
<i slot="start" class="bwi bwi-list" aria-hidden="true"></i>
{{ "viewItemsIn" | i18n: renderedOptions.label }}
</button>
</ng-container>
<button
type="button"
bitMenuItem
*ngFor="let option of renderedOptions.children"
(click)="option.children?.length ? viewOption(option, $event) : selectOption(option, $event)"
[disabled]="option.disabled"
[title]="option.label"
class="tw-text-[length:inherit]"
[attr.aria-haspopup]="option.children?.length ? 'menu' : null"
@if (renderedOptions) {
<div
class="tw-max-h-80 tw-min-w-32 tw-max-w-80 tw-text-sm"
[ngStyle]="menuWidth && { width: menuWidth + 'px' }"
>
<i
*ngIf="option.icon"
slot="start"
class="bwi"
[ngClass]="option.icon"
aria-hidden="true"
></i>
{{ option.label }}
<i *ngIf="option.children?.length" slot="end" class="bwi bwi-angle-right"></i>
</button>
</div>
@if (getParent(renderedOptions); as parent) {
<button
type="button"
bitMenuItem
(click)="viewOption(parent, $event)"
class="tw-text-[length:inherit]"
[title]="'backTo' | i18n: parent.label ?? placeholderText"
>
<i slot="start" class="bwi bwi-angle-left" aria-hidden="true"></i>
{{ "backTo" | i18n: parent.label ?? placeholderText }}
</button>
<button
type="button"
bitMenuItem
(click)="selectOption(renderedOptions, $event)"
[title]="'viewItemsIn' | i18n: renderedOptions.label"
class="tw-text-[length:inherit]"
>
<i slot="start" class="bwi bwi-list" aria-hidden="true"></i>
{{ "viewItemsIn" | i18n: renderedOptions.label }}
</button>
}
@for (option of renderedOptions.children; track option) {
<button
type="button"
bitMenuItem
(click)="
option.children?.length ? viewOption(option, $event) : selectOption(option, $event)
"
[disabled]="option.disabled"
[title]="option.label"
class="tw-text-[length:inherit]"
[attr.aria-haspopup]="option.children?.length ? 'menu' : null"
>
@if (option.icon) {
<i slot="start" class="bwi" [ngClass]="option.icon" aria-hidden="true"></i>
}
{{ option.label }}
@if (option.children?.length) {
<i slot="end" class="bwi bwi-angle-right"></i>
}
</button>
}
</div>
}
</bit-menu>

View File

@ -46,6 +46,7 @@ export type ChipSelectOption<T> = Option<T> & {
multi: true,
},
],
preserveWhitespaces: false,
})
export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, AfterViewInit {
@ViewChild(MenuComponent) menu: MenuComponent;

View File

@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { NgFor, NgIf } from "@angular/common";
import { Component, HostBinding, Input } from "@angular/core";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@ -14,18 +14,16 @@ enum CharacterType {
@Component({
selector: "bit-color-password",
template: `<span
*ngFor="let character of passwordArray; index as i"
[class]="getCharacterClass(character)"
>
<span>{{ character }}</span>
<span *ngIf="showCount" class="tw-whitespace-nowrap tw-text-xs tw-leading-5 tw-text-main">{{
i + 1
}}</span>
</span>`,
template: `@for (character of passwordArray; track character; let i = $index) {
<span [class]="getCharacterClass(character)">
<span>{{ character }}</span>
@if (showCount) {
<span class="tw-whitespace-nowrap tw-text-xs tw-leading-5 tw-text-main">{{ i + 1 }}</span>
}
</span>
}`,
preserveWhitespaces: false,
standalone: true,
imports: [NgFor, NgIf],
})
export class ColorPasswordComponent {
@Input() password: string = null;

View File

@ -1,4 +1,3 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
/**
@ -7,7 +6,6 @@ import { Component } from "@angular/core";
@Component({
selector: "bit-container",
templateUrl: "container.component.html",
imports: [CommonModule],
standalone: true,
})
export class ContainerComponent {}

View File

@ -13,9 +13,11 @@
class="tw-text-main tw-mb-0 tw-truncate"
>
{{ title }}
<span *ngIf="subtitle" class="tw-text-muted tw-font-normal tw-text-sm">
{{ subtitle }}
</span>
@if (subtitle) {
<span class="tw-text-muted tw-font-normal tw-text-sm">
{{ subtitle }}
</span>
}
<ng-content select="[bitDialogTitle]"></ng-content>
</h1>
<button
@ -35,9 +37,11 @@
'tw-min-h-60': loading,
}"
>
<div *ngIf="loading" class="tw-absolute tw-flex tw-size-full tw-items-center tw-justify-center">
<i class="bwi bwi-spinner bwi-spin bwi-lg" [attr.aria-label]="'loading' | i18n"></i>
</div>
@if (loading) {
<div class="tw-absolute tw-flex tw-size-full tw-items-center tw-justify-center">
<i class="bwi bwi-spinner bwi-spin bwi-lg" [attr.aria-label]="'loading' | i18n"></i>
</div>
}
<div
[ngClass]="{
'tw-p-4': !disablePadding,

View File

@ -11,16 +11,17 @@
{{ acceptButtonText }}
</button>
<button
*ngIf="showCancelButton"
type="button"
bitButton
bitFormButton
buttonType="secondary"
(click)="dialogRef.close(false)"
>
{{ cancelButtonText }}
</button>
@if (showCancelButton) {
<button
type="button"
bitButton
bitFormButton
buttonType="secondary"
(click)="dialogRef.close(false)"
>
{{ cancelButtonText }}
</button>
}
</ng-container>
</bit-simple-dialog>
</form>

View File

@ -1,7 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { NgIf } from "@angular/common";
import { Component, Inject } from "@angular/core";
import { FormGroup, ReactiveFormsModule } from "@angular/forms";
@ -39,7 +38,6 @@ const DEFAULT_COLOR: Record<SimpleDialogType, string> = {
IconDirective,
ButtonComponent,
BitFormButtonDirective,
NgIf,
],
})
export class SimpleConfigurableDialogComponent {

View File

@ -12,23 +12,24 @@ import { DialogModule } from "../../dialog.module";
@Component({
template: `
<div *ngFor="let group of dialogs">
<h2>{{ group.title }}</h2>
<div class="tw-mb-4 tw-flex tw-flex-row tw-gap-2">
<button
type="button"
*ngFor="let dialog of group.dialogs"
bitButton
(click)="openSimpleConfigurableDialog(dialog)"
>
{{ dialog.title }}
</button>
@for (group of dialogs; track group) {
<div>
<h2>{{ group.title }}</h2>
<div class="tw-mb-4 tw-flex tw-flex-row tw-gap-2">
@for (dialog of group.dialogs; track dialog) {
<button type="button" bitButton (click)="openSimpleConfigurableDialog(dialog)">
{{ dialog.title }}
</button>
}
</div>
</div>
</div>
}
<bit-callout *ngIf="showCallout" [type]="calloutType" title="Dialog Close Result">
{{ dialogCloseResult }}
</bit-callout>
@if (showCallout) {
<bit-callout [type]="calloutType" title="Dialog Close Result">
{{ dialogCloseResult }}
</bit-callout>
}
`,
})
class StoryDialogComponent {

View File

@ -3,12 +3,11 @@
@fadeIn
>
<div class="tw-flex tw-flex-col tw-items-center tw-gap-2 tw-px-4 tw-pt-4 tw-text-center">
<ng-container *ngIf="hasIcon; else elseBlock">
@if (hasIcon) {
<ng-content select="[bitDialogIcon]"></ng-content>
</ng-container>
<ng-template #elseBlock>
} @else {
<i class="bwi bwi-exclamation-triangle tw-text-3xl tw-text-warning" aria-hidden="true"></i>
</ng-template>
}
<h1
bitDialogTitleContainer
bitTypography="h3"

View File

@ -1,4 +1,3 @@
import { NgIf } from "@angular/common";
import { Component, ContentChild, Directive } from "@angular/core";
import { TypographyDirective } from "../../typography/typography.directive";
@ -16,7 +15,7 @@ export class IconDirective {}
templateUrl: "./simple-dialog.component.html",
animations: [fadeIn],
standalone: true,
imports: [NgIf, DialogTitleContainerDirective, TypographyDirective],
imports: [DialogTitleContainerDirective, TypographyDirective],
})
export class SimpleDialogComponent {
@ContentChild(IconDirective) icon!: IconDirective;

View File

@ -9,11 +9,17 @@
>
<span bitTypography="body2">
<ng-content select="bit-label"></ng-content>
<span *ngIf="required" class="tw-text-xs tw-font-normal"> ({{ "required" | i18n }})</span>
@if (required) {
<span class="tw-text-xs tw-font-normal"> ({{ "required" | i18n }})</span>
}
</span>
<ng-content select="bit-hint" *ngIf="!hasError"></ng-content>
@if (!hasError) {
<ng-content select="bit-hint"></ng-content>
}
</span>
</label>
<div *ngIf="hasError" class="tw-mt-1 tw-text-danger tw-text-xs tw-ml-0.5">
<i class="bwi bwi-error"></i> {{ displayError }}
</div>
@if (hasError) {
<div class="tw-mt-1 tw-text-danger tw-text-xs tw-ml-0.5">
<i class="bwi bwi-error"></i> {{ displayError }}
</div>
}

View File

@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { NgClass, NgIf } from "@angular/common";
import { NgClass } from "@angular/common";
import { Component, ContentChild, HostBinding, Input } from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -15,7 +15,7 @@ import { BitFormControlAbstraction } from "./form-control.abstraction";
selector: "bit-form-control",
templateUrl: "form-control.component.html",
standalone: true,
imports: [NgClass, TypographyDirective, NgIf, I18nPipe],
imports: [NgClass, TypographyDirective, I18nPipe],
})
export class FormControlComponent {
@Input() label: string;

View File

@ -5,10 +5,10 @@
<!-- labels inside a form control (checkbox, radio button) should not truncate -->
<span [ngClass]="{ 'tw-truncate': !isInsideFormControl }">
<ng-content></ng-content>
<ng-container *ngIf="isInsideFormControl">
@if (isInsideFormControl) {
<ng-container *ngTemplateOutlet="endSlotContent"></ng-container>
</ng-container>
}
</span>
<ng-container *ngIf="!isInsideFormControl">
@if (!isInsideFormControl) {
<ng-container *ngTemplateOutlet="endSlotContent"></ng-container>
</ng-container>
}

View File

@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { NgIf } from "@angular/common";
import { Component, Input } from "@angular/core";
import { AbstractControl, UntypedFormGroup } from "@angular/forms";
@ -8,15 +8,15 @@ import { I18nPipe } from "@bitwarden/ui-common";
@Component({
selector: "bit-error-summary",
template: ` <ng-container *ngIf="errorCount > 0">
template: ` @if (errorCount > 0) {
<i class="bwi bwi-error"></i> {{ "fieldsNeedAttention" | i18n: errorString }}
</ng-container>`,
}`,
host: {
class: "tw-block tw-text-danger tw-mt-2",
"aria-live": "assertive",
},
standalone: true,
imports: [NgIf, I18nPipe],
imports: [I18nPipe],
})
export class BitErrorSummary {
@Input()

View File

@ -15,63 +15,65 @@
<ng-content select="[bitSuffix]"></ng-content>
</ng-template>
<div *ngIf="!readOnly; else readOnlyView" class="tw-w-full tw-relative tw-group/bit-form-field">
<div class="tw-absolute tw-size-full tw-top-0 tw-pointer-events-none tw-z-20">
<div class="tw-size-full tw-flex">
<div
class="tw-min-w-3 tw-border-r-0 group-focus-within/bit-form-field:tw-border-r-0 !tw-rounded-l-lg"
[ngClass]="inputBorderClasses"
></div>
<div
class="tw-px-1 tw-shrink tw-min-w-0 tw-mt-px tw-border-x-0 tw-border-t-0 group-focus-within/bit-form-field:tw-border-x-0 group-focus-within/bit-form-field:tw-border-t-0 tw-hidden group-has-[bit-label]/bit-form-field:tw-block"
[ngClass]="inputBorderClasses"
>
<label
class="tw-flex tw-gap-1 tw-text-sm tw-text-muted -tw-translate-y-[0.675rem] tw-mb-0 tw-max-w-full tw-pointer-events-auto"
[attr.for]="input.labelForId"
@if (!readOnly) {
<div class="tw-w-full tw-relative tw-group/bit-form-field">
<div class="tw-absolute tw-size-full tw-top-0 tw-pointer-events-none tw-z-20">
<div class="tw-size-full tw-flex">
<div
class="tw-min-w-3 tw-border-r-0 group-focus-within/bit-form-field:tw-border-r-0 !tw-rounded-l-lg"
[ngClass]="inputBorderClasses"
></div>
<div
class="tw-px-1 tw-shrink tw-min-w-0 tw-mt-px tw-border-x-0 tw-border-t-0 group-focus-within/bit-form-field:tw-border-x-0 group-focus-within/bit-form-field:tw-border-t-0 tw-hidden group-has-[bit-label]/bit-form-field:tw-block"
[ngClass]="inputBorderClasses"
>
<ng-container *ngTemplateOutlet="labelContent"></ng-container>
<span *ngIf="input.required" class="tw-text-[0.625rem] tw-relative tw-bottom-[-1px]">
({{ "required" | i18n }})</span
<label
class="tw-flex tw-gap-1 tw-text-sm tw-text-muted -tw-translate-y-[0.675rem] tw-mb-0 tw-max-w-full tw-pointer-events-auto"
[attr.for]="input.labelForId"
>
</label>
<ng-container *ngTemplateOutlet="labelContent"></ng-container>
@if (input.required) {
<span class="tw-text-[0.625rem] tw-relative tw-bottom-[-1px]">
({{ "required" | i18n }})</span
>
}
</label>
</div>
<div
class="tw-min-w-3 tw-grow tw-border-l-0 group-focus-within/bit-form-field:tw-border-l-0 !tw-rounded-r-lg"
[ngClass]="inputBorderClasses"
></div>
</div>
</div>
<div
class="tw-gap-1 tw-bg-background tw-rounded-lg tw-flex tw-min-h-11 [&:not(:has(button:enabled)):has(input:read-only)]:tw-bg-secondary-100 [&:not(:has(button:enabled)):has(textarea:read-only)]:tw-bg-secondary-100"
>
<div
#prefixContainer
class="tw-flex tw-items-center tw-gap-1 tw-pl-3 tw-py-2"
[hidden]="!prefixHasChildren()"
>
<ng-container *ngTemplateOutlet="prefixContent"></ng-container>
</div>
<div
class="tw-min-w-3 tw-grow tw-border-l-0 group-focus-within/bit-form-field:tw-border-l-0 !tw-rounded-r-lg"
[ngClass]="inputBorderClasses"
></div>
class="default-content tw-w-full tw-relative tw-py-2 has-[bit-select]:tw-p-0 has-[bit-multi-select]:tw-p-0 has-[input:read-only:not([hidden])]:tw-bg-secondary-100 has-[textarea:read-only:not([hidden])]:tw-bg-secondary-100"
[ngClass]="[
prefixHasChildren() ? '' : 'tw-rounded-l-lg tw-pl-3',
suffixHasChildren() ? '' : 'tw-rounded-r-lg tw-pr-3',
]"
>
<ng-container *ngTemplateOutlet="defaultContent"></ng-container>
</div>
<div
#suffixContainer
class="tw-flex tw-items-center tw-gap-1 tw-pr-3 tw-py-2"
[hidden]="!suffixHasChildren()"
>
<ng-container *ngTemplateOutlet="suffixContent"></ng-container>
</div>
</div>
</div>
<div
class="tw-gap-1 tw-bg-background tw-rounded-lg tw-flex tw-min-h-11 [&:not(:has(button:enabled)):has(input:read-only)]:tw-bg-secondary-100 [&:not(:has(button:enabled)):has(textarea:read-only)]:tw-bg-secondary-100"
>
<div
#prefixContainer
class="tw-flex tw-items-center tw-gap-1 tw-pl-3 tw-py-2"
[hidden]="!prefixHasChildren()"
>
<ng-container *ngTemplateOutlet="prefixContent"></ng-container>
</div>
<div
class="default-content tw-w-full tw-relative tw-py-2 has-[bit-select]:tw-p-0 has-[bit-multi-select]:tw-p-0 has-[input:read-only:not([hidden])]:tw-bg-secondary-100 has-[textarea:read-only:not([hidden])]:tw-bg-secondary-100"
[ngClass]="[
prefixHasChildren() ? '' : 'tw-rounded-l-lg tw-pl-3',
suffixHasChildren() ? '' : 'tw-rounded-r-lg tw-pr-3',
]"
>
<ng-container *ngTemplateOutlet="defaultContent"></ng-container>
</div>
<div
#suffixContainer
class="tw-flex tw-items-center tw-gap-1 tw-pr-3 tw-py-2"
[hidden]="!suffixHasChildren()"
>
<ng-container *ngTemplateOutlet="suffixContent"></ng-container>
</div>
</div>
</div>
<ng-template #readOnlyView>
} @else {
<div class="tw-w-full tw-relative">
<label
class="tw-flex tw-gap-1 tw-text-sm tw-text-muted tw-mb-0 tw-max-w-full"
@ -107,9 +109,13 @@
</div>
</div>
</div>
</ng-template>
}
<ng-container [ngSwitch]="input.hasError">
<ng-content select="bit-hint" *ngSwitchCase="false"></ng-content>
<bit-error [error]="input.error" *ngSwitchCase="true"></bit-error>
</ng-container>
@switch (input.hasError) {
@case (false) {
<ng-content select="bit-hint"></ng-content>
}
@case (true) {
<bit-error [error]="input.error"></bit-error>
}
}

View File

@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import {
AfterContentChecked,
ChangeDetectionStrategy,
@ -16,7 +16,7 @@ import { TypographyModule } from "../typography";
@Component({
selector: "bit-item-content, [bit-item-content]",
standalone: true,
imports: [CommonModule, TypographyModule],
imports: [TypographyModule],
templateUrl: `item-content.component.html`,
host: {
class:

View File

@ -1,4 +1,3 @@
import { CommonModule } from "@angular/common";
import {
ChangeDetectionStrategy,
Component,
@ -14,7 +13,7 @@ import { ItemActionComponent } from "./item-action.component";
@Component({
selector: "bit-item",
standalone: true,
imports: [CommonModule, ItemActionComponent],
imports: [ItemActionComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: "item.component.html",
providers: [{ provide: A11yRowDirective, useExisting: ItemComponent }],

View File

@ -23,19 +23,21 @@
<ng-content></ng-content>
<!-- overlay backdrop for side-nav -->
<div
*ngIf="{
@if (
{
open: sideNavService.open$ | async,
} as data"
class="tw-pointer-events-none tw-fixed tw-inset-0 tw-z-10 tw-bg-black tw-bg-opacity-0 motion-safe:tw-transition-colors md:tw-hidden"
[ngClass]="[data.open ? 'tw-bg-opacity-30 md:tw-bg-opacity-0' : 'tw-bg-opacity-0']"
>
};
as data
) {
<div
*ngIf="data.open"
(click)="sideNavService.toggle()"
class="tw-pointer-events-auto tw-size-full"
></div>
</div>
class="tw-pointer-events-none tw-fixed tw-inset-0 tw-z-10 tw-bg-black tw-bg-opacity-0 motion-safe:tw-transition-colors md:tw-hidden"
[ngClass]="[data.open ? 'tw-bg-opacity-30 md:tw-bg-opacity-0' : 'tw-bg-opacity-0']"
>
@if (data.open) {
<div (click)="sideNavService.toggle()" class="tw-pointer-events-auto tw-size-full"></div>
}
</div>
}
</main>
<ng-template [cdkPortalOutlet]="drawerPortal()"></ng-template>
</div>

View File

@ -31,7 +31,9 @@
[disabled]="disabled"
(click)="clear(item)"
>
<i *ngIf="item.icon != null" class="bwi bwi-fw {{ item.icon }}" aria-hidden="true"></i>
@if (item.icon != null) {
<i class="bwi bwi-fw {{ item.icon }}" aria-hidden="true"></i>
}
<span class="tw-truncate">
{{ item.labelName }}
</span>
@ -41,10 +43,14 @@
<ng-template ng-option-tmp let-item="item">
<div class="tw-flex">
<div class="tw-w-7 tw-flex-none">
<i *ngIf="isSelected(item)" class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
@if (isSelected(item)) {
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
}
</div>
<div class="tw-mr-2 tw-flex-initial">
<i *ngIf="item.icon != null" class="bwi bwi-fw {{ item.icon }}" aria-hidden="true"></i>
@if (item.icon != null) {
<i class="bwi bwi-fw {{ item.icon }}" aria-hidden="true"></i>
}
</div>
<div class="tw-flex-1">
{{ item.listName }}

View File

@ -2,7 +2,6 @@
// @ts-strict-ignore
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { hasModifierKey } from "@angular/cdk/keycodes";
import { NgIf } from "@angular/common";
import {
Component,
Input,
@ -39,7 +38,7 @@ let nextId = 0;
templateUrl: "./multi-select.component.html",
providers: [{ provide: BitFormFieldControl, useExisting: MultiSelectComponent }],
standalone: true,
imports: [NgSelectModule, ReactiveFormsModule, FormsModule, BadgeModule, NgIf, I18nPipe],
imports: [NgSelectModule, ReactiveFormsModule, FormsModule, BadgeModule, I18nPipe],
})
/**
* This component has been implemented to only support Multi-select list events

View File

@ -1 +1,3 @@
<div *ngIf="sideNavService.open$ | async" class="tw-h-px tw-w-full tw-bg-secondary-300"></div>
@if (sideNavService.open$ | async) {
<div class="tw-h-px tw-w-full tw-bg-secondary-300"></div>
}

View File

@ -1,5 +1,5 @@
<!-- This a higher order component that composes `NavItemComponent` -->
<ng-container *ngIf="!hideIfEmpty || nestedNavComponents.length > 0">
@if (!hideIfEmpty || nestedNavComponents.length > 0) {
<bit-nav-item
[text]="text"
[icon]="icon"
@ -29,28 +29,29 @@
[attr.aria-label]="['toggleCollapse' | i18n, text].join(' ')"
></button>
</ng-template>
<!-- Show toggle to the left for trees otherwise to the right -->
<ng-container slot="start" *ngIf="variant === 'tree'">
<ng-container *ngTemplateOutlet="button"></ng-container>
</ng-container>
<ng-container slot="end">
<ng-content select="[slot=end]"></ng-content>
<ng-container *ngIf="variant !== 'tree'">
@if (variant === "tree") {
<ng-container slot="start">
<ng-container *ngTemplateOutlet="button"></ng-container>
</ng-container>
}
<ng-container slot="end">
<ng-content select="[slot=end]"></ng-content>
@if (variant !== "tree") {
<ng-container *ngTemplateOutlet="button"></ng-container>
}
</ng-container>
</bit-nav-item>
<!-- [attr.aria-controls] of the above button expects a unique ID on the controlled element -->
<ng-container *ngIf="sideNavService.open$ | async">
<div
*ngIf="open"
[attr.id]="contentId"
[attr.aria-label]="[text, 'submenu' | i18n].join(' ')"
role="group"
>
<ng-content></ng-content>
</div>
</ng-container>
</ng-container>
@if (sideNavService.open$ | async) {
@if (open) {
<div
[attr.id]="contentId"
[attr.aria-label]="[text, 'submenu' | i18n].join(' ')"
role="group"
>
<ng-content></ng-content>
</div>
}
}
}

View File

@ -29,6 +29,7 @@ import { SideNavService } from "./side-nav.service";
],
standalone: true,
imports: [CommonModule, NavItemComponent, IconButtonModule, I18nPipe],
preserveWhitespaces: false,
})
export class NavGroupComponent extends NavBaseComponent implements AfterContentInit {
@ContentChildren(NavBaseComponent, {

View File

@ -1,20 +1,23 @@
<div *ngIf="sideNavService.open" class="tw-sticky tw-top-0 tw-z-50">
<a
[routerLink]="route"
class="tw-px-5 tw-pb-5 tw-pt-7 tw-block tw-bg-background-alt3 tw-outline-none focus-visible:tw-ring focus-visible:tw-ring-inset focus-visible:tw-ring-text-alt2"
[attr.aria-label]="label"
[title]="label"
routerLinkActive
[ariaCurrentWhenActive]="'page'"
>
<bit-icon [icon]="openIcon"></bit-icon>
</a>
</div>
<bit-nav-item
class="tw-block tw-pt-7"
[hideActiveStyles]="true"
[route]="route"
[icon]="closedIcon"
*ngIf="!sideNavService.open"
[text]="label"
></bit-nav-item>
@if (sideNavService.open) {
<div class="tw-sticky tw-top-0 tw-z-50">
<a
[routerLink]="route"
class="tw-px-5 tw-pb-5 tw-pt-7 tw-block tw-bg-background-alt3 tw-outline-none focus-visible:tw-ring focus-visible:tw-ring-inset focus-visible:tw-ring-text-alt2"
[attr.aria-label]="label"
[title]="label"
routerLinkActive
[ariaCurrentWhenActive]="'page'"
>
<bit-icon [icon]="openIcon"></bit-icon>
</a>
</div>
}
@if (!sideNavService.open) {
<bit-nav-item
class="tw-block tw-pt-7"
[hideActiveStyles]="true"
[route]="route"
[icon]="closedIcon"
[text]="label"
></bit-nav-item>
}

View File

@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { NgIf } from "@angular/common";
import { Component, Input } from "@angular/core";
import { RouterLinkActive, RouterLink } from "@angular/router";
@ -14,7 +14,7 @@ import { SideNavService } from "./side-nav.service";
selector: "bit-nav-logo",
templateUrl: "./nav-logo.component.html",
standalone: true,
imports: [NgIf, RouterLinkActive, RouterLink, BitIconComponent, NavItemComponent],
imports: [RouterLinkActive, RouterLink, BitIconComponent, NavItemComponent],
})
export class NavLogoComponent {
/** Icon that is displayed when the side nav is closed */

View File

@ -1,42 +1,46 @@
<nav
*ngIf="{
@if (
{
open: sideNavService.open$ | async,
isOverlay: sideNavService.isOverlay$ | async,
} as data"
id="bit-side-nav"
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' && {
'--color-text-alt2': 'var(--color-text-main)',
'--color-background-alt3': 'var(--color-secondary-100)',
'--color-background-alt4': 'var(--color-secondary-300)',
}
"
[cdkTrapFocus]="data.isOverlay"
[attr.role]="data.isOverlay ? 'dialog' : null"
[attr.aria-modal]="data.isOverlay ? 'true' : null"
(keydown)="handleKeyDown($event)"
>
<ng-content></ng-content>
<div class="tw-sticky tw-bottom-0 tw-left-0 tw-z-20 tw-mt-auto tw-w-full tw-bg-background-alt3">
<bit-nav-divider></bit-nav-divider>
<ng-container *ngIf="data.open">
<ng-content select="[slot=footer]"></ng-content>
</ng-container>
<div class="tw-mx-0.5 tw-my-4 tw-w-[3.75rem]">
<button
#toggleButton
type="button"
class="tw-mx-auto tw-block tw-max-w-fit"
[bitIconButton]="data.open ? 'bwi-angle-left' : 'bwi-angle-right'"
buttonType="light"
size="small"
(click)="sideNavService.toggle()"
[attr.aria-label]="'toggleSideNavigation' | i18n"
[attr.aria-expanded]="data.open"
aria-controls="bit-side-nav"
></button>
};
as data
) {
<nav
id="bit-side-nav"
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' && {
'--color-text-alt2': 'var(--color-text-main)',
'--color-background-alt3': 'var(--color-secondary-100)',
'--color-background-alt4': 'var(--color-secondary-300)',
}
"
[cdkTrapFocus]="data.isOverlay"
[attr.role]="data.isOverlay ? 'dialog' : null"
[attr.aria-modal]="data.isOverlay ? 'true' : null"
(keydown)="handleKeyDown($event)"
>
<ng-content></ng-content>
<div class="tw-sticky tw-bottom-0 tw-left-0 tw-z-20 tw-mt-auto tw-w-full tw-bg-background-alt3">
<bit-nav-divider></bit-nav-divider>
@if (data.open) {
<ng-content select="[slot=footer]"></ng-content>
}
<div class="tw-mx-0.5 tw-my-4 tw-w-[3.75rem]">
<button
#toggleButton
type="button"
class="tw-mx-auto tw-block tw-max-w-fit"
[bitIconButton]="data.open ? 'bwi-angle-left' : 'bwi-angle-right'"
buttonType="light"
size="small"
(click)="sideNavService.toggle()"
[attr.aria-label]="'toggleSideNavigation' | i18n"
[attr.aria-expanded]="data.open"
aria-controls="bit-side-nav"
></button>
</div>
</div>
</div>
</nav>
</nav>
}

View File

@ -7,13 +7,12 @@
attr.aria-valuenow="{{ barWidth }}"
[ngStyle]="{ width: barWidth + '%' }"
>
<div
*ngIf="displayText"
class="tw-flex tw-h-full tw-flex-wrap tw-items-center tw-overflow-hidden"
>
<!-- If text is too long to fit, wrap it below to hide -->
<div class="tw-h-full">&nbsp;</div>
<div class="tw-pr-1">{{ textContent }}</div>
</div>
@if (displayText) {
<div class="tw-flex tw-h-full tw-flex-wrap tw-items-center tw-overflow-hidden">
<!-- If text is too long to fit, wrap it below to hide -->
<div class="tw-h-full">&nbsp;</div>
<div class="tw-pr-1">{{ textContent }}</div>
</div>
}
</div>
</div>

View File

@ -1,16 +1,18 @@
<ng-container *ngIf="label">
@if (label) {
<fieldset>
<legend class="tw-mb-1 tw-block tw-text-sm tw-font-semibold tw-text-main">
<ng-content select="bit-label"></ng-content>
<span *ngIf="required" class="tw-text-xs tw-font-normal"> ({{ "required" | i18n }})</span>
@if (required) {
<span class="tw-text-xs tw-font-normal"> ({{ "required" | i18n }})</span>
}
</legend>
<ng-container *ngTemplateOutlet="content"></ng-container>
</fieldset>
</ng-container>
}
<ng-container *ngIf="!label">
@if (!label) {
<ng-container *ngTemplateOutlet="content"></ng-container>
</ng-container>
}
<ng-template #content>
<div>

View File

@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { NgIf, NgTemplateOutlet } from "@angular/common";
import { NgTemplateOutlet } from "@angular/common";
import { Component, ContentChild, HostBinding, Input, Optional, Self } from "@angular/core";
import { ControlValueAccessor, NgControl, Validators } from "@angular/forms";
@ -14,7 +14,7 @@ let nextId = 0;
selector: "bit-radio-group",
templateUrl: "radio-group.component.html",
standalone: true,
imports: [NgIf, NgTemplateOutlet, I18nPipe],
imports: [NgTemplateOutlet, I18nPipe],
})
export class RadioGroupComponent implements ControlValueAccessor {
selected: unknown;

View File

@ -13,7 +13,9 @@
<ng-template ng-option-tmp let-item="item">
<div class="tw-flex" [title]="item.label">
<div class="tw-mr-2 tw-flex-initial">
<i *ngIf="item.icon != null" class="bwi bwi-fw {{ item.icon }}" aria-hidden="true"></i>
@if (item.icon != null) {
<i class="bwi bwi-fw {{ item.icon }}" aria-hidden="true"></i>
}
</div>
<div class="tw-flex-1 tw-text-ellipsis tw-overflow-hidden">
{{ item.label }}

View File

@ -1,6 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { NgIf } from "@angular/common";
import {
Component,
ContentChildren,
@ -36,7 +36,7 @@ let nextId = 0;
templateUrl: "select.component.html",
providers: [{ provide: BitFormFieldControl, useExisting: SelectComponent }],
standalone: true,
imports: [NgSelectModule, ReactiveFormsModule, FormsModule, NgIf],
imports: [NgSelectModule, ReactiveFormsModule, FormsModule],
})
export class SelectComponent<T> implements BitFormFieldControl, ControlValueAccessor {
@ViewChild(NgSelectComponent) select: NgSelectComponent;

View File

@ -49,11 +49,9 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
<bit-form-field>
<bit-label>Your favorite color</bit-label>
<bit-select formControlName="favColor">
<bit-option
*ngFor="let color of colors"
[value]="color.value"
[label]="color.name"
></bit-option>
@for (color of colors; track color) {
<bit-option [value]="color.value" [label]="color.name"></bit-option>
}
</bit-select>
</bit-form-field>

View File

@ -36,9 +36,11 @@ class KitchenSinkDialog {
<p class="tw-mt-4">
<bit-breadcrumbs>
<bit-breadcrumb *ngFor="let item of navItems" [icon]="item.icon" [route]="[item.route]">
{{ item.name }}
</bit-breadcrumb>
@for (item of navItems; track item) {
<bit-breadcrumb [icon]="item.icon" [route]="[item.route]">
{{ item.name }}
</bit-breadcrumb>
}
</bit-breadcrumbs>
</p>

View File

@ -16,11 +16,15 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
<bit-toggle value="small"> Mid-sized <span bitBadge variant="info">1</span> </bit-toggle>
</bit-toggle-group>
</div>
<ul *ngFor="let company of companyList">
<li *ngIf="company.size === selectedToggle || selectedToggle === 'all'">
{{ company.name }}
</li>
</ul>
@for (company of companyList; track company) {
<ul>
@if (company.size === selectedToggle || selectedToggle === "all") {
<li>
{{ company.name }}
</li>
}
</ul>
}
`,
})
export class KitchenSinkToggleList {

View File

@ -5,40 +5,41 @@
[attr.aria-label]="label"
(keydown)="keyManager.onKeydown($event)"
>
<button
bitTabListItem
*ngFor="let tab of tabs; let i = index"
type="button"
role="tab"
[id]="getTabLabelId(i)"
[active]="tab.isActive"
[disabled]="tab.disabled"
[attr.aria-selected]="selectedIndex === i"
[attr.tabindex]="selectedIndex === i ? 0 : -1"
(click)="selectTab(i)"
>
<ng-container [ngTemplateOutlet]="content"></ng-container>
<ng-template #content>
<ng-template [ngIf]="tab.templateLabel" [ngIfElse]="tabTextLabel">
<ng-container [ngTemplateOutlet]="tab.templateLabel.templateRef"></ng-container>
@for (tab of tabs; track tab; let i = $index) {
<button
bitTabListItem
type="button"
role="tab"
[id]="getTabLabelId(i)"
[active]="tab.isActive"
[disabled]="tab.disabled"
[attr.aria-selected]="selectedIndex === i"
[attr.tabindex]="selectedIndex === i ? 0 : -1"
(click)="selectTab(i)"
>
<ng-container [ngTemplateOutlet]="content"></ng-container>
<ng-template #content>
@if (tab.templateLabel) {
<ng-container [ngTemplateOutlet]="tab.templateLabel.templateRef"></ng-container>
} @else {
{{ tab.textLabel }}
}
</ng-template>
<ng-template #tabTextLabel>{{ tab.textLabel }}</ng-template>
</ng-template>
</button>
</button>
}
</div>
</bit-tab-header>
<div class="tw-px-4 tw-pt-5">
<bit-tab-body
role="tabpanel"
*ngFor="let tab of tabs; let i = index"
[id]="getTabContentId(i)"
[attr.tabindex]="tab.contentTabIndex"
[attr.labeledby]="getTabLabelId(i)"
[active]="tab.isActive"
[content]="tab.content"
[preserveContent]="preserveContent"
>
</bit-tab-body>
@for (tab of tabs; track tab; let i = $index) {
<bit-tab-body
role="tabpanel"
[id]="getTabContentId(i)"
[attr.tabindex]="tab.contentTabIndex"
[attr.labeledby]="getTabLabelId(i)"
[active]="tab.isActive"
[content]="tab.content"
[preserveContent]="preserveContent"
>
</bit-tab-body>
}
</div>

View File

@ -2,7 +2,7 @@
// @ts-strict-ignore
import { FocusKeyManager } from "@angular/cdk/a11y";
import { coerceNumberProperty } from "@angular/cdk/coercion";
import { CommonModule } from "@angular/common";
import { NgTemplateOutlet } from "@angular/common";
import {
AfterContentChecked,
AfterContentInit,
@ -33,7 +33,7 @@ let nextId = 0;
templateUrl: "./tab-group.component.html",
standalone: true,
imports: [
CommonModule,
NgTemplateOutlet,
TabHeaderComponent,
TabListContainerDirective,
TabListItemDirective,

View File

@ -7,15 +7,14 @@
<i aria-hidden="true" class="bwi tw-text-xl tw-py-1.5 tw-px-2.5 {{ iconClass }}"></i>
<div>
<span class="tw-sr-only">{{ variant | i18n }}</span>
<p *ngIf="title" data-testid="toast-title" class="tw-font-semibold tw-mb-0">{{ title }}</p>
<p
bitTypography="body2"
*ngFor="let m of messageArray"
data-testid="toast-message"
class="tw-mb-2 last:tw-mb-0"
>
{{ m }}
</p>
@if (title) {
<p data-testid="toast-title" class="tw-font-semibold tw-mb-0">{{ title }}</p>
}
@for (m of messageArray; track m) {
<p bitTypography="body2" data-testid="toast-message" class="tw-mb-2 last:tw-mb-0">
{{ m }}
</p>
}
</div>
<!-- Overriding hover and focus-visible colors for a11y against colored background -->
<button