mirror of
https://github.com/bitwarden/browser
synced 2026-03-01 11:01:17 +00:00
merged main. fix conflicts
This commit is contained in:
@@ -1,39 +1,23 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core";
|
||||
import { Directive, effect, ElementRef, input } from "@angular/core";
|
||||
|
||||
import { setA11yTitleAndAriaLabel } from "./set-a11y-title-and-aria-label";
|
||||
|
||||
@Directive({
|
||||
selector: "[appA11yTitle]",
|
||||
})
|
||||
export class A11yTitleDirective implements OnInit {
|
||||
// TODO: Skipped for signal migration because:
|
||||
// Accessor inputs cannot be migrated as they are too complex.
|
||||
@Input() set appA11yTitle(title: string) {
|
||||
this.title = title;
|
||||
this.setAttributes();
|
||||
}
|
||||
export class A11yTitleDirective {
|
||||
title = input.required<string>({ alias: "appA11yTitle" });
|
||||
|
||||
private title: string;
|
||||
private originalTitle: string | null;
|
||||
private originalAriaLabel: string | null;
|
||||
constructor(private el: ElementRef) {
|
||||
const originalTitle = this.el.nativeElement.getAttribute("title");
|
||||
const originalAriaLabel = this.el.nativeElement.getAttribute("aria-label");
|
||||
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private renderer: Renderer2,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.originalTitle = this.el.nativeElement.getAttribute("title");
|
||||
this.originalAriaLabel = this.el.nativeElement.getAttribute("aria-label");
|
||||
this.setAttributes();
|
||||
}
|
||||
|
||||
private setAttributes() {
|
||||
if (this.originalTitle === null) {
|
||||
this.renderer.setAttribute(this.el.nativeElement, "title", this.title);
|
||||
}
|
||||
if (this.originalAriaLabel === null) {
|
||||
this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title);
|
||||
}
|
||||
effect(() => {
|
||||
setA11yTitleAndAriaLabel({
|
||||
element: this.el.nativeElement,
|
||||
title: originalTitle ?? this.title(),
|
||||
label: originalAriaLabel ?? this.title(),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
12
libs/components/src/a11y/aria-disable.directive.ts
Normal file
12
libs/components/src/a11y/aria-disable.directive.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Directive, inject } from "@angular/core";
|
||||
|
||||
import { AriaDisabledClickCaptureService } from "./aria-disabled-click-capture.service";
|
||||
|
||||
@Directive({
|
||||
host: {
|
||||
"[attr.bit-aria-disable]": "true",
|
||||
},
|
||||
})
|
||||
export class AriaDisableDirective {
|
||||
protected ariaDisabledClickCaptureService = inject(AriaDisabledClickCaptureService);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { DOCUMENT } from "@angular/common";
|
||||
import { Injectable, Inject, NgZone, OnDestroy } from "@angular/core";
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class AriaDisabledClickCaptureService implements OnDestroy {
|
||||
private listener!: (e: MouseEvent | KeyboardEvent) => void;
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private document: Document,
|
||||
private ngZone: NgZone,
|
||||
) {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
this.listener = (e: MouseEvent | KeyboardEvent) => {
|
||||
const btn = (e.target as HTMLElement).closest(
|
||||
'[aria-disabled="true"][bit-aria-disable="true"]',
|
||||
);
|
||||
if (btn) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
this.document.addEventListener("click", this.listener, /* capture */ true);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.document.removeEventListener("click", this.listener, true);
|
||||
}
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
export * from "./a11y-title.directive";
|
||||
export * from "./aria-disabled-click-capture.service";
|
||||
export * from "./aria-disable.directive";
|
||||
|
||||
16
libs/components/src/a11y/set-a11y-title-and-aria-label.ts
Normal file
16
libs/components/src/a11y/set-a11y-title-and-aria-label.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export function setA11yTitleAndAriaLabel({
|
||||
element,
|
||||
title,
|
||||
label,
|
||||
}: {
|
||||
element: HTMLElement;
|
||||
title?: string;
|
||||
label?: string;
|
||||
}): void {
|
||||
if (title) {
|
||||
element.setAttribute("title", title);
|
||||
}
|
||||
if (label) {
|
||||
element.setAttribute("aria-label", label);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { ChangeDetectorRef, Component, OnInit, inject, DestroyRef } from "@angular/core";
|
||||
import { ChangeDetectorRef, Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router";
|
||||
import { filter, switchMap, tap } from "rxjs";
|
||||
import { Subject, filter, of, switchMap, tap } from "rxjs";
|
||||
|
||||
import { Icon } from "@bitwarden/assets/svg";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
import { Translation } from "../dialog";
|
||||
import { Icon } from "../icon";
|
||||
|
||||
import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service";
|
||||
import { AnonLayoutComponent, AnonLayoutMaxWidth } from "./anon-layout.component";
|
||||
@@ -53,13 +51,15 @@ export interface AnonLayoutWrapperData {
|
||||
imports: [AnonLayoutComponent, RouterModule],
|
||||
})
|
||||
export class AnonLayoutWrapperComponent implements OnInit {
|
||||
protected pageTitle: string;
|
||||
protected pageSubtitle: string;
|
||||
protected pageIcon: Icon;
|
||||
protected showReadonlyHostname: boolean;
|
||||
protected maxWidth: AnonLayoutMaxWidth;
|
||||
protected hideCardWrapper: boolean;
|
||||
protected hideIcon: boolean = false;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
protected pageTitle?: string | null;
|
||||
protected pageSubtitle?: string | null;
|
||||
protected pageIcon?: Icon | null;
|
||||
protected showReadonlyHostname?: boolean | null;
|
||||
protected maxWidth?: AnonLayoutMaxWidth | null;
|
||||
protected hideCardWrapper?: boolean | null;
|
||||
protected hideIcon?: boolean | null;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
@@ -85,7 +85,7 @@ export class AnonLayoutWrapperComponent implements OnInit {
|
||||
filter((event) => event instanceof NavigationEnd),
|
||||
// reset page data on page changes
|
||||
tap(() => this.resetPageData()),
|
||||
switchMap(() => this.route.firstChild?.data || null),
|
||||
switchMap(() => this.route.firstChild?.data || of(null)),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
)
|
||||
.subscribe((firstChildRouteData: Data | null) => {
|
||||
@@ -93,7 +93,7 @@ export class AnonLayoutWrapperComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
private setAnonLayoutWrapperDataFromRouteData(firstChildRouteData: Data | null) {
|
||||
private setAnonLayoutWrapperDataFromRouteData(firstChildRouteData?: Data | null) {
|
||||
if (!firstChildRouteData) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "@storybook/angular";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { LockIcon, RegistrationCheckEmailIcon } from "@bitwarden/assets/svg";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import {
|
||||
EnvironmentService,
|
||||
@@ -18,7 +19,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { ButtonModule } from "../button";
|
||||
import { LockIcon, RegistrationCheckEmailIcon } from "../icon/icons";
|
||||
import { I18nMockService } from "../utils";
|
||||
|
||||
import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<main
|
||||
class="tw-flex tw-w-full tw-mx-auto tw-flex-col tw-bg-background-alt tw-px-6 tw-py-4 tw-text-main"
|
||||
class="tw-relative tw-flex tw-w-full tw-mx-auto tw-flex-col tw-bg-background-alt tw-p-5 tw-text-main"
|
||||
[ngClass]="{
|
||||
'tw-min-h-screen': clientType === 'web',
|
||||
'tw-min-h-full': clientType === 'browser' || clientType === 'desktop',
|
||||
@@ -8,7 +8,7 @@
|
||||
<a
|
||||
*ngIf="!hideLogo()"
|
||||
[routerLink]="['/']"
|
||||
class="tw-w-[128px] tw-block tw-mb-12 [&>*]:tw-align-top"
|
||||
class="tw-w-[200px] tw-block tw-mb-12 [&>*]:tw-align-top"
|
||||
>
|
||||
<bit-icon [icon]="logo" [ariaLabel]="'appLogoLabel' | i18n"></bit-icon>
|
||||
</a>
|
||||
@@ -20,11 +20,11 @@
|
||||
|
||||
<ng-container *ngIf="title()">
|
||||
<!-- Small screens -->
|
||||
<h1 bitTypography="h3" class="tw-mt-2 sm:tw-hidden">
|
||||
<h1 bitTypography="h2" class="tw-mt-2 sm:tw-hidden">
|
||||
{{ title() }}
|
||||
</h1>
|
||||
<!-- Medium to Larger screens -->
|
||||
<h1 bitTypography="h2" class="tw-mt-2 tw-hidden sm:tw-block">
|
||||
<h1 bitTypography="h1" class="tw-mt-2 tw-hidden sm:tw-block">
|
||||
{{ title() }}
|
||||
</h1>
|
||||
</ng-container>
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tw-grow tw-w-full tw-mx-auto tw-flex tw-flex-col tw-items-center sm:tw-min-w-[28rem]"
|
||||
class="tw-z-10 tw-grow tw-w-full tw-mx-auto tw-flex tw-flex-col tw-items-center sm:tw-min-w-[28rem]"
|
||||
[ngClass]="maxWidthClass"
|
||||
>
|
||||
@if (hideCardWrapper()) {
|
||||
@@ -62,6 +62,17 @@
|
||||
<div bitTypography="body2">{{ version }}</div>
|
||||
</ng-container>
|
||||
</footer>
|
||||
|
||||
<div
|
||||
class="tw-hidden md:tw-block [&_svg]:tw-absolute tw-z-[1] tw-opacity-[.11] [&_svg]:tw-bottom-0 [&_svg]:tw-start-0 [&_svg]:tw-w-[35%] [&_svg]:tw-max-w-[450px]"
|
||||
>
|
||||
<bit-icon [icon]="leftIllustration"></bit-icon>
|
||||
</div>
|
||||
<div
|
||||
class="tw-hidden md:tw-block [&_svg]:tw-absolute tw-z-[1] tw-opacity-[.11] [&_svg]:tw-bottom-0 [&_svg]:tw-end-0 [&_svg]:tw-w-[35%] [&_svg]:tw-max-w-[450px]"
|
||||
>
|
||||
<bit-icon [icon]="rightIllustration"></bit-icon>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<ng-template #defaultContent>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import {
|
||||
Component,
|
||||
@@ -13,13 +11,18 @@ import {
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import {
|
||||
AnonLayoutBitwardenShield,
|
||||
BackgroundLeftIllustration,
|
||||
BackgroundRightIllustration,
|
||||
BitwardenLogo,
|
||||
Icon,
|
||||
} from "@bitwarden/assets/svg";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { IconModule, Icon } from "../icon";
|
||||
import { BitwardenLogo } from "../icon/icons";
|
||||
import { AnonLayoutBitwardenShield } from "../icon/logos";
|
||||
import { IconModule } from "../icon";
|
||||
import { SharedModule } from "../shared";
|
||||
import { TypographyModule } from "../typography";
|
||||
|
||||
@@ -37,6 +40,9 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
|
||||
return ["tw-h-full"];
|
||||
}
|
||||
|
||||
readonly leftIllustration = BackgroundLeftIllustration;
|
||||
readonly rightIllustration = BackgroundRightIllustration;
|
||||
|
||||
readonly title = input<string>();
|
||||
readonly subtitle = input<string>();
|
||||
readonly icon = model<Icon>();
|
||||
@@ -56,8 +62,8 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
|
||||
protected logo = BitwardenLogo;
|
||||
protected year: string;
|
||||
protected clientType: ClientType;
|
||||
protected hostname: string;
|
||||
protected version: string;
|
||||
protected hostname?: string;
|
||||
protected version?: string;
|
||||
|
||||
protected hideYearAndVersion = false;
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ example) to construct the page via routable composition:
|
||||
|
||||
```typescript
|
||||
// File: oss-routing.module.ts
|
||||
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, LockIcon } from "@bitwarden/auth/angular";
|
||||
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/auth/angular";
|
||||
import { LockIcon } from "@bitwarden/assets/svg";
|
||||
|
||||
{
|
||||
path: "",
|
||||
@@ -116,7 +117,8 @@ in the `AnonLayoutWrapperData` interface:
|
||||
All of these properties are optional.
|
||||
|
||||
```typescript
|
||||
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, LockIcon } from "@bitwarden/auth/angular";
|
||||
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/auth/angular";
|
||||
import { LockIcon } from "@bitwarden/assets/svg";
|
||||
|
||||
{
|
||||
// ...
|
||||
@@ -137,7 +139,8 @@ on your client) as a component with `outlet: "environment-selector"`.
|
||||
|
||||
```javascript
|
||||
// File: oss-routing.module.ts
|
||||
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, LockIcon } from "@bitwarden/auth/angular";
|
||||
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/auth/angular";
|
||||
|
||||
import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component";
|
||||
|
||||
{
|
||||
|
||||
@@ -2,14 +2,13 @@ import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { Icon, LockIcon } from "@bitwarden/assets/svg";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { ButtonModule } from "../button";
|
||||
import { Icon } from "../icon";
|
||||
import { LockIcon } from "../icon/icons";
|
||||
import { I18nMockService } from "../utils/i18n-mock.service";
|
||||
|
||||
import { AnonLayoutComponent } from "./anon-layout.component";
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, HostListener, model, Optional, inject, DestroyRef } from "@angular/core";
|
||||
import { DestroyRef, Directive, HostListener, inject, model, Optional } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { BehaviorSubject, finalize, tap } from "rxjs";
|
||||
|
||||
@@ -38,7 +36,7 @@ export class BitActionDirective {
|
||||
|
||||
disabled = false;
|
||||
|
||||
readonly handler = model<FunctionReturningAwaitable>(undefined, { alias: "bitAction" });
|
||||
readonly handler = model.required<FunctionReturningAwaitable>({ alias: "bitAction" });
|
||||
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, OnInit, Optional, input, inject, DestroyRef } from "@angular/core";
|
||||
import { DestroyRef, Directive, OnInit, Optional, inject, input } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormGroupDirective } from "@angular/forms";
|
||||
import { BehaviorSubject, catchError, filter, of, switchMap } from "rxjs";
|
||||
@@ -22,7 +20,7 @@ export class BitSubmitDirective implements OnInit {
|
||||
private _loading$ = new BehaviorSubject<boolean>(false);
|
||||
private _disabled$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
readonly handler = input<FunctionReturningAwaitable>(undefined, { alias: "bitSubmit" });
|
||||
readonly handler = input.required<FunctionReturningAwaitable>({ alias: "bitSubmit" });
|
||||
|
||||
readonly allowDisabledFormSubmit = input<boolean>(false);
|
||||
|
||||
@@ -63,7 +61,7 @@ export class BitSubmitDirective implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.formGroupDirective.statusChanges
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
?.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((c) => {
|
||||
if (this.allowDisabledFormSubmit()) {
|
||||
this._disabled$.next(false);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, Optional, input } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
|
||||
|
||||
@@ -125,7 +125,13 @@ handler.
|
||||
|
||||
```html
|
||||
<button type="button" bitFormButton bitButton [bitAction]="handler">Do action</button>
|
||||
<button type="button" bitFormButton bitIconButton="bwi-star" [bitAction]="handler"></button>
|
||||
<button
|
||||
type="button"
|
||||
bitFormButton
|
||||
bitIconButton="bwi-star"
|
||||
label="Your label here"
|
||||
[bitAction]="handler"
|
||||
></button>
|
||||
```
|
||||
|
||||
## `[bitSubmit]` Disabled Form Submit
|
||||
|
||||
@@ -7,6 +7,7 @@ import { delay, of } from "rxjs";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
|
||||
import { A11yTitleDirective } from "../a11y";
|
||||
import { ButtonModule } from "../button";
|
||||
import { FormFieldModule } from "../form-field";
|
||||
import { IconButtonModule } from "../icon-button";
|
||||
@@ -28,20 +29,21 @@ const template = `
|
||||
<bit-form-field>
|
||||
<bit-label>Email</bit-label>
|
||||
<input bitInput formControlName="email" />
|
||||
<button type="button" bitSuffix bitIconButton="bwi-refresh" bitFormButton [bitAction]="refresh"></button>
|
||||
<button type="button" label="Refresh" bitSuffix bitIconButton="bwi-refresh" bitFormButton [bitAction]="refresh"></button>
|
||||
</bit-form-field>
|
||||
|
||||
<button class="tw-me-2" type="submit" buttonType="primary" bitButton bitFormButton>Submit</button>
|
||||
<button class="tw-me-2" type="button" buttonType="secondary" bitButton bitFormButton>Cancel</button>
|
||||
<button class="tw-me-2" type="button" buttonType="danger" bitButton bitFormButton [bitAction]="delete">Delete</button>
|
||||
<button class="tw-me-2" type="button" buttonType="secondary" bitButton bitFormButton [disabled]="true">Disabled</button>
|
||||
<button class="tw-me-2" type="button" buttonType="muted" bitIconButton="bwi-star" bitFormButton [bitAction]="delete">Delete</button>
|
||||
<button class="tw-me-2" type="button" buttonType="muted" bitIconButton="bwi-star" label="Delete" bitFormButton [bitAction]="delete">Delete</button>
|
||||
</form>`;
|
||||
|
||||
@Component({
|
||||
selector: "app-promise-example",
|
||||
template,
|
||||
imports: [
|
||||
A11yTitleDirective,
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
FormFieldModule,
|
||||
@@ -86,6 +88,7 @@ class PromiseExampleComponent {
|
||||
selector: "app-observable-example",
|
||||
template,
|
||||
imports: [
|
||||
A11yTitleDirective,
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
FormFieldModule,
|
||||
|
||||
@@ -63,7 +63,7 @@ from how click handlers are usually defined with the output syntax `(click)="han
|
||||
```html
|
||||
<button bitButton [bitAction]="handler">Do action</button>
|
||||
|
||||
<button bitIconButton="bwi-trash" [bitAction]="handler"></button>`;
|
||||
<button bitIconButton="bwi-trash" label="Your label here" [bitAction]="handler"></button>`;
|
||||
```
|
||||
|
||||
## Stories
|
||||
|
||||
@@ -16,7 +16,7 @@ const template = /*html*/ `
|
||||
<button type="button" bitButton buttonType="primary" [bitAction]="action" class="tw-me-2">
|
||||
Perform action {{ statusEmoji }}
|
||||
</button>
|
||||
<button type="button" bitIconButton="bwi-trash" buttonType="danger" [bitAction]="action"></button>`;
|
||||
<button type="button" label="Delete" bitIconButton="bwi-trash" buttonType="danger" [bitAction]="action"></button>`;
|
||||
|
||||
@Component({
|
||||
template,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { NgClass } from "@angular/common";
|
||||
import { Component, OnChanges, input } from "@angular/core";
|
||||
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";
|
||||
@@ -9,11 +7,11 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
type SizeTypes = "xlarge" | "large" | "default" | "small" | "xsmall";
|
||||
|
||||
const SizeClasses: Record<SizeTypes, string[]> = {
|
||||
xlarge: ["tw-h-24", "tw-w-24"],
|
||||
large: ["tw-h-16", "tw-w-16"],
|
||||
default: ["tw-h-10", "tw-w-10"],
|
||||
small: ["tw-h-7", "tw-w-7"],
|
||||
xsmall: ["tw-h-6", "tw-w-6"],
|
||||
xlarge: ["tw-h-24", "tw-w-24", "tw-min-w-24"],
|
||||
large: ["tw-h-16", "tw-w-16", "tw-min-w-16"],
|
||||
default: ["tw-h-10", "tw-w-10", "tw-min-w-10"],
|
||||
small: ["tw-h-7", "tw-w-7", "tw-min-w-7"],
|
||||
xsmall: ["tw-h-6", "tw-w-6", "tw-min-w-6"],
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -41,7 +39,7 @@ export class AvatarComponent implements OnChanges {
|
||||
private svgFontSize = 20;
|
||||
private svgFontWeight = 300;
|
||||
private svgSize = 48;
|
||||
src: SafeResourceUrl;
|
||||
src?: SafeResourceUrl;
|
||||
|
||||
constructor(public sanitizer: DomSanitizer) {}
|
||||
|
||||
@@ -56,8 +54,14 @@ export class AvatarComponent implements OnChanges {
|
||||
}
|
||||
|
||||
private generate() {
|
||||
let chars: string = null;
|
||||
const upperCaseText = this.text()?.toUpperCase() ?? "";
|
||||
const color = this.color();
|
||||
const text = this.text();
|
||||
const id = this.id();
|
||||
if (!text && !color && !id) {
|
||||
throw new Error("Must supply `text`, `color`, or `id` input.");
|
||||
}
|
||||
let chars: string | null = null;
|
||||
const upperCaseText = text?.toUpperCase() ?? "";
|
||||
|
||||
chars = this.getFirstLetters(upperCaseText, this.svgCharCount);
|
||||
|
||||
@@ -66,18 +70,17 @@ export class AvatarComponent implements OnChanges {
|
||||
}
|
||||
|
||||
// If the chars contain an emoji, only show it.
|
||||
if (chars.match(Utils.regexpEmojiPresentation)) {
|
||||
chars = chars.match(Utils.regexpEmojiPresentation)[0];
|
||||
const emojiMatch = chars.match(Utils.regexpEmojiPresentation);
|
||||
if (emojiMatch) {
|
||||
chars = emojiMatch[0];
|
||||
}
|
||||
|
||||
let svg: HTMLElement;
|
||||
let hexColor = this.color();
|
||||
|
||||
const id = this.id();
|
||||
if (!Utils.isNullOrWhitespace(this.color())) {
|
||||
let hexColor = color ?? "";
|
||||
if (!Utils.isNullOrWhitespace(hexColor)) {
|
||||
svg = this.createSvgElement(this.svgSize, hexColor);
|
||||
} else if (!Utils.isNullOrWhitespace(id)) {
|
||||
hexColor = Utils.stringToColor(id.toString());
|
||||
} else if (!Utils.isNullOrWhitespace(id ?? "")) {
|
||||
hexColor = Utils.stringToColor(id!.toString());
|
||||
svg = this.createSvgElement(this.svgSize, hexColor);
|
||||
} else {
|
||||
hexColor = Utils.stringToColor(upperCaseText);
|
||||
@@ -95,7 +98,7 @@ export class AvatarComponent implements OnChanges {
|
||||
);
|
||||
}
|
||||
|
||||
private getFirstLetters(data: string, count: number): string {
|
||||
private getFirstLetters(data: string, count: number): string | null {
|
||||
const parts = data.split(" ");
|
||||
if (parts.length > 1) {
|
||||
let text = "";
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
buttonType="main"
|
||||
size="small"
|
||||
(click)="onClose.emit()"
|
||||
[attr.title]="'close' | i18n"
|
||||
[attr.aria-label]="'close' | i18n"
|
||||
[label]="'close' | i18n"
|
||||
></button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
import { Component, EventEmitter, Output, TemplateRef, ViewChild, input } from "@angular/core";
|
||||
import { Component, EventEmitter, Output, TemplateRef, input, viewChild } from "@angular/core";
|
||||
import { QueryParamsHandling } from "@angular/router";
|
||||
|
||||
@Component({
|
||||
@@ -20,7 +17,7 @@ export class BreadcrumbComponent {
|
||||
@Output()
|
||||
click = new EventEmitter();
|
||||
|
||||
@ViewChild(TemplateRef, { static: true }) content: TemplateRef<unknown>;
|
||||
readonly content = viewChild(TemplateRef);
|
||||
|
||||
onClick(args: unknown) {
|
||||
this.click.next(args);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
[queryParams]="breadcrumb.queryParams()"
|
||||
[queryParamsHandling]="breadcrumb.queryParamsHandling()"
|
||||
>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content()"></ng-container>
|
||||
</a>
|
||||
} @else {
|
||||
<button
|
||||
@@ -18,7 +18,7 @@
|
||||
class="tw-my-2 tw-inline-block"
|
||||
(click)="breadcrumb.onClick($event)"
|
||||
>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content()"></ng-container>
|
||||
</button>
|
||||
}
|
||||
@if (!last) {
|
||||
@@ -35,6 +35,7 @@
|
||||
bitIconButton="bwi-ellipsis-h"
|
||||
[bitMenuTriggerFor]="overflowMenu"
|
||||
size="small"
|
||||
[label]="'moreBreadcrumbs' | i18n"
|
||||
></button>
|
||||
<bit-menu #overflowMenu>
|
||||
@for (breadcrumb of overflow; track breadcrumb) {
|
||||
@@ -46,11 +47,11 @@
|
||||
[queryParams]="breadcrumb.queryParams()"
|
||||
[queryParamsHandling]="breadcrumb.queryParamsHandling()"
|
||||
>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content()"></ng-container>
|
||||
</a>
|
||||
} @else {
|
||||
<button type="button" bitMenuItem linkType="primary" (click)="breadcrumb.onClick($event)">
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content()"></ng-container>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
@@ -66,7 +67,7 @@
|
||||
[queryParams]="breadcrumb.queryParams()"
|
||||
[queryParamsHandling]="breadcrumb.queryParamsHandling()"
|
||||
>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content()"></ng-container>
|
||||
</a>
|
||||
} @else {
|
||||
<button
|
||||
@@ -76,7 +77,7 @@
|
||||
class="tw-my-2 tw-inline-block"
|
||||
(click)="breadcrumb.onClick($event)"
|
||||
>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content"></ng-container>
|
||||
<ng-container [ngTemplateOutlet]="breadcrumb.content()"></ng-container>
|
||||
</button>
|
||||
}
|
||||
@if (!last) {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { CommonModule } from "@angular/common";
|
||||
import { Component, ContentChildren, QueryList, input } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
import { IconButtonModule } from "../icon-button";
|
||||
import { LinkModule } from "../link";
|
||||
import { MenuModule } from "../menu";
|
||||
@@ -16,7 +18,7 @@ import { BreadcrumbComponent } from "./breadcrumb.component";
|
||||
@Component({
|
||||
selector: "bit-breadcrumbs",
|
||||
templateUrl: "./breadcrumbs.component.html",
|
||||
imports: [CommonModule, LinkModule, RouterModule, IconButtonModule, MenuModule],
|
||||
imports: [I18nPipe, CommonModule, LinkModule, RouterModule, IconButtonModule, MenuModule],
|
||||
})
|
||||
export class BreadcrumbsComponent {
|
||||
readonly show = input(3);
|
||||
|
||||
@@ -2,9 +2,12 @@ import { Component, importProvidersFrom } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
import { IconButtonModule } from "../icon-button";
|
||||
import { LinkModule } from "../link";
|
||||
import { MenuModule } from "../menu";
|
||||
import { I18nMockService } from "../utils";
|
||||
|
||||
import { BreadcrumbComponent } from "./breadcrumb.component";
|
||||
import { BreadcrumbsComponent } from "./breadcrumbs.component";
|
||||
@@ -26,6 +29,16 @@ export default {
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [LinkModule, MenuModule, IconButtonModule, RouterModule, BreadcrumbComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: I18nService,
|
||||
useFactory: () => {
|
||||
return new I18nMockService({
|
||||
moreBreadcrumbs: "More breadcrumbs",
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
applicationConfig({
|
||||
providers: [
|
||||
|
||||
@@ -34,23 +34,25 @@ describe("Button", () => {
|
||||
expect(buttonDebugElement.nativeElement.disabled).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should be disabled when disabled is true", () => {
|
||||
it("should be aria-disabled and not html attribute disabled when disabled is true", () => {
|
||||
testAppComponent.disabled = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(buttonDebugElement.nativeElement.disabled).toBeTruthy();
|
||||
expect(buttonDebugElement.attributes["aria-disabled"]).toBe("true");
|
||||
expect(buttonDebugElement.nativeElement.disabled).toBeFalsy();
|
||||
// Anchor tags cannot be disabled.
|
||||
});
|
||||
|
||||
it("should be disabled when attribute disabled is true", () => {
|
||||
expect(disabledButtonDebugElement.nativeElement.disabled).toBeTruthy();
|
||||
it("should be aria-disabled not html attribute disabled when attribute disabled is true", () => {
|
||||
fixture.detectChanges();
|
||||
expect(disabledButtonDebugElement.attributes["aria-disabled"]).toBe("true");
|
||||
expect(disabledButtonDebugElement.nativeElement.disabled).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should be disabled when loading is true", () => {
|
||||
testAppComponent.loading = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(buttonDebugElement.nativeElement.disabled).toBeTruthy();
|
||||
expect(buttonDebugElement.attributes["aria-disabled"]).toBe("true");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { NgClass } from "@angular/common";
|
||||
import { input, HostBinding, Component, model, computed, booleanAttribute } from "@angular/core";
|
||||
import {
|
||||
input,
|
||||
HostBinding,
|
||||
Component,
|
||||
model,
|
||||
computed,
|
||||
booleanAttribute,
|
||||
inject,
|
||||
ElementRef,
|
||||
} from "@angular/core";
|
||||
import { toObservable, toSignal } from "@angular/core/rxjs-interop";
|
||||
import { debounce, interval } from "rxjs";
|
||||
|
||||
import { AriaDisableDirective } from "../a11y";
|
||||
import { ButtonLikeAbstraction, ButtonType, ButtonSize } from "../shared/button-like.abstraction";
|
||||
import { ariaDisableElement } from "../utils";
|
||||
|
||||
const focusRing = [
|
||||
"focus-visible:tw-ring-2",
|
||||
@@ -50,9 +61,7 @@ const buttonStyles: Record<ButtonType, string[]> = {
|
||||
templateUrl: "button.component.html",
|
||||
providers: [{ provide: ButtonLikeAbstraction, useExisting: ButtonComponent }],
|
||||
imports: [NgClass],
|
||||
host: {
|
||||
"[attr.disabled]": "disabledAttr()",
|
||||
},
|
||||
hostDirectives: [AriaDisableDirective],
|
||||
})
|
||||
export class ButtonComponent implements ButtonLikeAbstraction {
|
||||
@HostBinding("class") get classList() {
|
||||
@@ -72,14 +81,15 @@ export class ButtonComponent implements ButtonLikeAbstraction {
|
||||
.concat(
|
||||
this.showDisabledStyles() || this.disabled()
|
||||
? [
|
||||
"disabled:tw-bg-secondary-300",
|
||||
"disabled:hover:tw-bg-secondary-300",
|
||||
"disabled:tw-border-secondary-300",
|
||||
"disabled:hover:tw-border-secondary-300",
|
||||
"disabled:!tw-text-muted",
|
||||
"disabled:hover:!tw-text-muted",
|
||||
"disabled:tw-cursor-not-allowed",
|
||||
"disabled:hover:tw-no-underline",
|
||||
"aria-disabled:!tw-bg-secondary-300",
|
||||
"hover:tw-bg-secondary-300",
|
||||
"aria-disabled:tw-border-secondary-300",
|
||||
"hover:tw-border-secondary-300",
|
||||
"aria-disabled:!tw-text-muted",
|
||||
"hover:!tw-text-muted",
|
||||
"aria-disabled:tw-cursor-not-allowed",
|
||||
"hover:tw-no-underline",
|
||||
"aria-disabled:tw-pointer-events-none",
|
||||
]
|
||||
: [],
|
||||
)
|
||||
@@ -88,7 +98,7 @@ export class ButtonComponent implements ButtonLikeAbstraction {
|
||||
|
||||
protected disabledAttr = computed(() => {
|
||||
const disabled = this.disabled() != null && this.disabled() !== false;
|
||||
return disabled || this.loading() ? true : null;
|
||||
return disabled || this.loading();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -128,4 +138,9 @@ export class ButtonComponent implements ButtonLikeAbstraction {
|
||||
);
|
||||
|
||||
disabled = model<boolean>(false);
|
||||
private el = inject(ElementRef<HTMLButtonElement>);
|
||||
|
||||
constructor() {
|
||||
ariaDisableElement(this.el.nativeElement, this.disabledAttr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<aside
|
||||
class="tw-mb-4 tw-box-border tw-rounded-lg tw-bg-background tw-ps-3 tw-pe-3 tw-py-2 tw-leading-5 tw-text-main"
|
||||
[ngClass]="calloutClass"
|
||||
[ngClass]="calloutClass()"
|
||||
[attr.aria-labelledby]="titleId"
|
||||
>
|
||||
@if (titleComputed(); as title) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, computed, input } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -50,11 +48,11 @@ export class CalloutComponent {
|
||||
return title;
|
||||
});
|
||||
|
||||
protected titleId = `bit-callout-title-${nextId++}`;
|
||||
protected readonly titleId = `bit-callout-title-${nextId++}`;
|
||||
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
get calloutClass() {
|
||||
protected readonly calloutClass = computed(() => {
|
||||
switch (this.type()) {
|
||||
case "danger":
|
||||
return "tw-bg-danger-100";
|
||||
@@ -65,5 +63,5 @@ export class CalloutComponent {
|
||||
case "warning":
|
||||
return "tw-bg-warning-100";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, HostBinding, Input, Optional, Self } from "@angular/core";
|
||||
import { booleanAttribute, Component, HostBinding, input, Optional, Self } from "@angular/core";
|
||||
import { NgControl, Validators } from "@angular/forms";
|
||||
|
||||
import { BitFormControlAbstraction } from "../form-control";
|
||||
@@ -9,6 +7,9 @@ import { BitFormControlAbstraction } from "../form-control";
|
||||
selector: "input[type=checkbox][bitCheckbox]",
|
||||
template: "",
|
||||
providers: [{ provide: BitFormControlAbstraction, useExisting: CheckboxComponent }],
|
||||
host: {
|
||||
"[disabled]": "disabled",
|
||||
},
|
||||
})
|
||||
export class CheckboxComponent implements BitFormControlAbstraction {
|
||||
@HostBinding("class")
|
||||
@@ -23,6 +24,9 @@ export class CheckboxComponent implements BitFormControlAbstraction {
|
||||
"tw-align-sub",
|
||||
"tw-flex-none", // Flexbox fix for bit-form-control
|
||||
"!tw-p-1",
|
||||
// Give checkbox explicit height and width to fix iOS rendering bug
|
||||
"tw-h-[calc(1.12rem_+_theme(spacing.2))]",
|
||||
"tw-w-[calc(1.12rem_+_theme(spacing.2))]",
|
||||
"after:tw-inset-1",
|
||||
// negative margin to negate the positioning added by the padding
|
||||
"!-tw-mt-1",
|
||||
@@ -104,37 +108,25 @@ export class CheckboxComponent implements BitFormControlAbstraction {
|
||||
protected indeterminateImage =
|
||||
`url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 13 13"%3E%3Cpath stroke="%23fff" stroke-width="2" d="M2.5 6.5h8"/%3E%3C/svg%3E%0A')`;
|
||||
|
||||
// TODO: Skipped for signal migration because:
|
||||
// Accessor inputs cannot be migrated as they are too complex.
|
||||
@HostBinding()
|
||||
@Input()
|
||||
get disabled() {
|
||||
return this._disabled ?? this.ngControl?.disabled ?? false;
|
||||
}
|
||||
set disabled(value: any) {
|
||||
this._disabled = value != null && value !== false;
|
||||
}
|
||||
private _disabled: boolean;
|
||||
readonly disabledInput = input(false, { transform: booleanAttribute, alias: "disabled" });
|
||||
|
||||
// TODO migrate to computed signal when Angular adds signal support to reactive forms
|
||||
// https://bitwarden.atlassian.net/browse/CL-819
|
||||
get disabled() {
|
||||
return this.disabledInput() || this.ngControl?.disabled || false;
|
||||
}
|
||||
|
||||
// TODO: Skipped for signal migration because:
|
||||
// Accessor inputs cannot be migrated as they are too complex.
|
||||
@Input()
|
||||
get required() {
|
||||
return (
|
||||
this._required ?? this.ngControl?.control?.hasValidator(Validators.requiredTrue) ?? false
|
||||
);
|
||||
return this.ngControl?.control?.hasValidator(Validators.requiredTrue) ?? false;
|
||||
}
|
||||
set required(value: any) {
|
||||
this._required = value != null && value !== false;
|
||||
}
|
||||
private _required: boolean;
|
||||
|
||||
get hasError() {
|
||||
return this.ngControl?.status === "INVALID" && this.ngControl?.touched;
|
||||
return !!(this.ngControl?.status === "INVALID" && this.ngControl?.touched);
|
||||
}
|
||||
|
||||
get error(): [string, any] {
|
||||
const key = Object.keys(this.ngControl.errors)[0];
|
||||
return [key, this.ngControl.errors[key]];
|
||||
const errors = this.ngControl?.errors ?? {};
|
||||
const key = Object.keys(errors)[0];
|
||||
return [key, errors[key]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
@@ -9,12 +7,12 @@ import {
|
||||
HostListener,
|
||||
Input,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
booleanAttribute,
|
||||
inject,
|
||||
signal,
|
||||
input,
|
||||
viewChild,
|
||||
} from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
|
||||
@@ -50,9 +48,9 @@ export type ChipSelectOption<T> = Option<T> & {
|
||||
],
|
||||
})
|
||||
export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, AfterViewInit {
|
||||
@ViewChild(MenuComponent) menu: MenuComponent;
|
||||
@ViewChildren(MenuItemDirective) menuItems: QueryList<MenuItemDirective>;
|
||||
@ViewChild("chipSelectButton") chipSelectButton: ElementRef<HTMLButtonElement>;
|
||||
readonly menu = viewChild(MenuComponent);
|
||||
@ViewChildren(MenuItemDirective) menuItems?: QueryList<MenuItemDirective>;
|
||||
readonly chipSelectButton = viewChild<ElementRef<HTMLButtonElement>>("chipSelectButton");
|
||||
|
||||
/** Text to show when there is no selected option */
|
||||
readonly placeholderText = input.required<string>();
|
||||
@@ -60,7 +58,7 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
/** Icon to show when there is no selected option or the selected option does not have an icon */
|
||||
readonly placeholderIcon = input<string>();
|
||||
|
||||
private _options: ChipSelectOption<T>[];
|
||||
private _options: ChipSelectOption<T>[] = [];
|
||||
|
||||
// TODO: Skipped for signal migration because:
|
||||
// Accessor inputs cannot be migrated as they are too complex.
|
||||
@@ -103,13 +101,13 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
/** Tree constructed from `this.options` */
|
||||
private rootTree: ChipSelectOption<T>;
|
||||
private rootTree?: ChipSelectOption<T> | null;
|
||||
|
||||
/** Options that are currently displayed in the menu */
|
||||
protected renderedOptions: ChipSelectOption<T>;
|
||||
protected renderedOptions?: ChipSelectOption<T> | null;
|
||||
|
||||
/** The option that is currently selected by the user */
|
||||
protected selectedOption: ChipSelectOption<T>;
|
||||
protected selectedOption?: ChipSelectOption<T> | null;
|
||||
|
||||
/**
|
||||
* The initial calculated width of the menu when it opens, which is used to
|
||||
@@ -123,7 +121,7 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
}
|
||||
|
||||
/** The icon to show in the chip button */
|
||||
protected get icon(): string {
|
||||
protected get icon(): string | undefined {
|
||||
return this.selectedOption?.icon || this.placeholderIcon();
|
||||
}
|
||||
|
||||
@@ -133,7 +131,7 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
*/
|
||||
protected setOrResetRenderedOptions(): void {
|
||||
this.renderedOptions = this.selectedOption
|
||||
? this.selectedOption.children?.length > 0
|
||||
? (this.selectedOption.children?.length ?? 0) > 0
|
||||
? this.selectedOption
|
||||
: this.getParent(this.selectedOption)
|
||||
: this.rootTree;
|
||||
@@ -171,7 +169,14 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
* @param value the option value to look for
|
||||
* @returns the `ChipSelectOption` associated with the provided value, or null if not found
|
||||
*/
|
||||
private findOption(tree: ChipSelectOption<T>, value: T): ChipSelectOption<T> | null {
|
||||
private findOption(
|
||||
tree: ChipSelectOption<T> | null | undefined,
|
||||
value: T,
|
||||
): ChipSelectOption<T> | null {
|
||||
if (!tree) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result = null;
|
||||
if (tree.value !== null && compareValues(tree.value, value)) {
|
||||
return tree;
|
||||
@@ -197,7 +202,7 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
});
|
||||
}
|
||||
|
||||
protected getParent(option: ChipSelectOption<T>): ChipSelectOption<T> | null {
|
||||
protected getParent(option: ChipSelectOption<T>): ChipSelectOption<T> | null | undefined {
|
||||
return this.childParentMap.get(option);
|
||||
}
|
||||
|
||||
@@ -217,8 +222,8 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
* menuItems will change when the user navigates into or out of a submenu. when that happens, we want to
|
||||
* direct their focus to the first item in the new menu
|
||||
*/
|
||||
this.menuItems.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||
this.menu.keyManager.setFirstItemActive();
|
||||
this.menuItems?.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||
this.menu()?.keyManager?.setFirstItemActive();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -227,17 +232,17 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
* the initially rendered options
|
||||
*/
|
||||
protected setMenuWidth() {
|
||||
const chipWidth = this.chipSelectButton.nativeElement.getBoundingClientRect().width;
|
||||
const chipWidth = this.chipSelectButton()?.nativeElement.getBoundingClientRect().width ?? 0;
|
||||
|
||||
const firstMenuItemWidth =
|
||||
this.menu.menuItems.first.elementRef.nativeElement.getBoundingClientRect().width;
|
||||
this.menu()?.menuItems().at(0)?.elementRef.nativeElement.getBoundingClientRect().width ?? 0;
|
||||
|
||||
this.menuWidth = Math.max(chipWidth, firstMenuItemWidth);
|
||||
}
|
||||
|
||||
/** Control Value Accessor */
|
||||
|
||||
private notifyOnChange?: (value: T) => void;
|
||||
private notifyOnChange?: (value: T | null) => void;
|
||||
private notifyOnTouched?: () => void;
|
||||
|
||||
/** Implemented as part of NG_VALUE_ACCESSOR */
|
||||
@@ -247,7 +252,7 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor, A
|
||||
}
|
||||
|
||||
/** Implemented as part of NG_VALUE_ACCESSOR */
|
||||
registerOnChange(fn: (value: T) => void): void {
|
||||
registerOnChange(fn: (value: T | null) => void): void {
|
||||
this.notifyOnChange = fn;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
buttonType="main"
|
||||
size="default"
|
||||
bitDialogClose
|
||||
[attr.title]="'close' | i18n"
|
||||
[attr.aria-label]="'close' | i18n"
|
||||
[label]="'close' | i18n"
|
||||
></button>
|
||||
}
|
||||
</header>
|
||||
|
||||
@@ -101,8 +101,7 @@ export const Default: Story = {
|
||||
bitIconButton="bwi-trash"
|
||||
buttonType="danger"
|
||||
size="default"
|
||||
title="Delete"
|
||||
aria-label="Delete"></button>
|
||||
label="Delete"></button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
`,
|
||||
@@ -219,7 +218,7 @@ export const WithCards: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Foo
|
||||
</h2>
|
||||
<button type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
<button type="button" label="Favorite" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</bit-section-header>
|
||||
<bit-card>
|
||||
<bit-form-field>
|
||||
@@ -239,7 +238,7 @@ export const WithCards: Story = {
|
||||
<h2 bitTypography="h6">
|
||||
Bar
|
||||
</h2>
|
||||
<button type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
<button label="Favorite" type="button" bitIconButton="bwi-star" size="small" slot="end"></button>
|
||||
</bit-section-header>
|
||||
<bit-card>
|
||||
<bit-form-field>
|
||||
@@ -265,8 +264,7 @@ export const WithCards: Story = {
|
||||
bitIconButton="bwi-trash"
|
||||
buttonType="danger"
|
||||
size="default"
|
||||
title="Delete"
|
||||
aria-label="Delete"></button>
|
||||
label="Delete"></button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// 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 { Component, Inject } from "@angular/core";
|
||||
import { FormGroup, ReactiveFormsModule } from "@angular/forms";
|
||||
@@ -47,10 +45,10 @@ export class SimpleConfigurableDialogComponent {
|
||||
];
|
||||
}
|
||||
|
||||
protected title: string;
|
||||
protected content: string;
|
||||
protected acceptButtonText: string;
|
||||
protected cancelButtonText: string;
|
||||
protected title?: string;
|
||||
protected content?: string;
|
||||
protected acceptButtonText?: string;
|
||||
protected cancelButtonText?: string;
|
||||
protected formGroup = new FormGroup({});
|
||||
|
||||
protected showCancelButton = this.simpleDialogOpts.cancelButtonText !== null;
|
||||
@@ -58,7 +56,7 @@ export class SimpleConfigurableDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: DialogRef,
|
||||
private i18nService: I18nService,
|
||||
@Inject(DIALOG_DATA) public simpleDialogOpts?: SimpleDialogOptions,
|
||||
@Inject(DIALOG_DATA) public simpleDialogOpts: SimpleDialogOptions,
|
||||
) {
|
||||
this.localizeText();
|
||||
}
|
||||
@@ -76,24 +74,27 @@ export class SimpleConfigurableDialogComponent {
|
||||
private localizeText() {
|
||||
this.title = this.translate(this.simpleDialogOpts.title);
|
||||
this.content = this.translate(this.simpleDialogOpts.content);
|
||||
this.acceptButtonText = this.translate(this.simpleDialogOpts.acceptButtonText, "yes");
|
||||
this.acceptButtonText = this.translate(
|
||||
this.simpleDialogOpts.acceptButtonText ?? { key: "yes" },
|
||||
);
|
||||
|
||||
if (this.showCancelButton) {
|
||||
// If accept text is overridden, use cancel, otherwise no
|
||||
this.cancelButtonText = this.translate(
|
||||
this.simpleDialogOpts.cancelButtonText,
|
||||
this.simpleDialogOpts.acceptButtonText !== undefined ? "cancel" : "no",
|
||||
this.simpleDialogOpts.cancelButtonText ??
|
||||
(this.simpleDialogOpts.acceptButtonText !== undefined
|
||||
? { key: "cancel" }
|
||||
: { key: "no" }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private translate(translation: string | Translation, defaultKey?: string): string {
|
||||
// Translation interface use implies we must localize.
|
||||
private translate(translation: string | Translation): string {
|
||||
// Object implies we must localize.
|
||||
if (typeof translation === "object") {
|
||||
return this.i18nService.t(translation.key, ...(translation.placeholders ?? []));
|
||||
}
|
||||
|
||||
// Use string that is already translated or use default key post translate
|
||||
return translation ?? this.i18nService.t(defaultKey);
|
||||
return translation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, HostBinding, HostListener, input } from "@angular/core";
|
||||
|
||||
import { DisclosureComponent } from "./disclosure.component";
|
||||
@@ -12,7 +10,7 @@ export class DisclosureTriggerForDirective {
|
||||
/**
|
||||
* Accepts template reference for a bit-disclosure component instance
|
||||
*/
|
||||
readonly disclosure = input<DisclosureComponent>(undefined, { alias: "bitDisclosureTriggerFor" });
|
||||
readonly disclosure = input.required<DisclosureComponent>({ alias: "bitDisclosureTriggerFor" });
|
||||
|
||||
@HostBinding("attr.aria-expanded") get ariaExpanded() {
|
||||
return this.disclosure().open;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
@@ -30,6 +28,7 @@ let nextId = 0;
|
||||
* bitIconButton="bwi-sliders"
|
||||
* [buttonType]="'muted'"
|
||||
* [bitDisclosureTriggerFor]="disclosureRef"
|
||||
* [label]="'Settings' | i18n"
|
||||
* ></button>
|
||||
* <bit-disclosure #disclosureRef open>click button to hide this content</bit-disclosure>
|
||||
* ```
|
||||
@@ -40,11 +39,10 @@ let nextId = 0;
|
||||
template: `<ng-content></ng-content>`,
|
||||
})
|
||||
export class DisclosureComponent {
|
||||
private _open: boolean;
|
||||
|
||||
/** Emits the visibility of the disclosure content */
|
||||
@Output() openChange = new EventEmitter<boolean>();
|
||||
|
||||
private _open?: boolean;
|
||||
/**
|
||||
* Optionally init the disclosure in its opened state
|
||||
*/
|
||||
@@ -54,14 +52,13 @@ export class DisclosureComponent {
|
||||
this._open = isOpen;
|
||||
this.openChange.emit(isOpen);
|
||||
}
|
||||
get open(): boolean {
|
||||
return !!this._open;
|
||||
}
|
||||
|
||||
@HostBinding("class") get classList() {
|
||||
return this.open ? "" : "tw-hidden";
|
||||
}
|
||||
|
||||
@HostBinding("id") id = `bit-disclosure-${nextId++}`;
|
||||
|
||||
get open(): boolean {
|
||||
return this._open;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export const DisclosureWithIconButton: Story = {
|
||||
render: (args) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<button type="button" bitIconButton="bwi-sliders" [buttonType]="'muted'" [bitDisclosureTriggerFor]="disclosureRef">
|
||||
<button type="button" label="Settings" bitIconButton="bwi-sliders" [buttonType]="'muted'" [bitDisclosureTriggerFor]="disclosureRef">
|
||||
</button>
|
||||
<bit-disclosure #disclosureRef class="tw-text-main tw-block" open>click button to hide this content</bit-disclosure>
|
||||
`,
|
||||
|
||||
@@ -5,11 +5,5 @@
|
||||
{{ title() }}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
bitIconButton="bwi-close"
|
||||
type="button"
|
||||
bitDrawerClose
|
||||
[attr.title]="'close' | i18n"
|
||||
[attr.aria-label]="'close' | i18n"
|
||||
></button>
|
||||
<button bitIconButton="bwi-close" type="button" bitDrawerClose [label]="'close' | i18n"></button>
|
||||
</header>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
export abstract class BitFormControlAbstraction {
|
||||
disabled: boolean;
|
||||
required: boolean;
|
||||
hasError: boolean;
|
||||
error: [string, any];
|
||||
abstract disabled: boolean;
|
||||
abstract required: boolean;
|
||||
abstract hasError: boolean;
|
||||
abstract error: [string, any];
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<label
|
||||
class="tw-transition tw-items-start [&:has(input[type='checkbox'])]:tw-gap-[.25rem] [&:has(input[type='radio'])]:tw-gap-1.5 tw-select-none tw-mb-0 tw-inline-flex tw-rounded has-[:focus-visible]:tw-ring has-[:focus-visible]:tw-ring-primary-600"
|
||||
[ngClass]="[formControl.disabled ? 'tw-cursor-auto' : 'tw-cursor-pointer']"
|
||||
class="tw-transition tw-items-start [&:has(input[type='checkbox'])]:tw-gap-1 [&:has(input[type='radio'])]:tw-gap-1.5 tw-select-none tw-mb-0 tw-inline-flex tw-rounded has-[:focus-visible]:tw-ring has-[:focus-visible]:tw-ring-primary-600"
|
||||
[ngClass]="[formControl().disabled ? 'tw-cursor-auto' : 'tw-cursor-pointer']"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
<span
|
||||
class="tw-inline-flex tw-flex-col"
|
||||
[ngClass]="formControl.disabled ? 'tw-text-muted' : 'tw-text-main'"
|
||||
[ngClass]="formControl().disabled ? 'tw-text-muted' : 'tw-text-main'"
|
||||
>
|
||||
<span bitTypography="body2">
|
||||
<ng-content select="bit-label"></ng-content>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { NgClass } from "@angular/common";
|
||||
import { booleanAttribute, Component, ContentChild, HostBinding, input } from "@angular/core";
|
||||
import { booleanAttribute, Component, HostBinding, input, contentChild } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
@@ -22,10 +20,10 @@ export class FormControlComponent {
|
||||
|
||||
readonly disableMargin = input(false, { transform: booleanAttribute });
|
||||
|
||||
@ContentChild(BitFormControlAbstraction) protected formControl: BitFormControlAbstraction;
|
||||
protected readonly formControl = contentChild.required(BitFormControlAbstraction);
|
||||
|
||||
@HostBinding("class") get classes() {
|
||||
return []
|
||||
return ([] as string[])
|
||||
.concat(this.inline() ? ["tw-inline-block", "tw-me-4"] : ["tw-block"])
|
||||
.concat(this.disableMargin() ? [] : ["tw-mb-4"]);
|
||||
}
|
||||
@@ -33,15 +31,15 @@ export class FormControlComponent {
|
||||
constructor(private i18nService: I18nService) {}
|
||||
|
||||
get required() {
|
||||
return this.formControl.required;
|
||||
return this.formControl().required;
|
||||
}
|
||||
|
||||
get hasError() {
|
||||
return this.formControl.hasError;
|
||||
return this.formControl().hasError;
|
||||
}
|
||||
|
||||
get error() {
|
||||
return this.formControl.error;
|
||||
return this.formControl().error;
|
||||
}
|
||||
|
||||
get displayError() {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, ElementRef, HostBinding, input, Optional } from "@angular/core";
|
||||
|
||||
@@ -32,7 +30,7 @@ export class BitLabel {
|
||||
];
|
||||
|
||||
@HostBinding("title") get title() {
|
||||
return this.elementRef.nativeElement.textContent.trim();
|
||||
return this.elementRef.nativeElement.textContent?.trim() ?? "";
|
||||
}
|
||||
|
||||
readonly id = input(`bit-label-${nextId++}`);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { FormControl } from "@angular/forms";
|
||||
|
||||
import { forbiddenCharacters } from "./forbidden-characters.validator";
|
||||
@@ -42,6 +40,6 @@ describe("forbiddenCharacters", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function createControl(input: string) {
|
||||
function createControl(input: string | null) {
|
||||
return new FormControl(input);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { FormControl } from "@angular/forms";
|
||||
|
||||
import { trimValidator as validate } from "./trim.validator";
|
||||
@@ -58,6 +56,6 @@ describe("trimValidator", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function createControl(input: string) {
|
||||
function createControl(input: string | null) {
|
||||
return new FormControl(input);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
import { Component, input } from "@angular/core";
|
||||
import { AbstractControl, UntypedFormGroup } from "@angular/forms";
|
||||
|
||||
@@ -21,7 +18,8 @@ export class BitErrorSummary {
|
||||
readonly formGroup = input<UntypedFormGroup>();
|
||||
|
||||
get errorCount(): number {
|
||||
return this.getErrorCount(this.formGroup());
|
||||
const form = this.formGroup();
|
||||
return form ? this.getErrorCount(form) : 0;
|
||||
}
|
||||
|
||||
get errorString() {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, HostBinding, input } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -24,6 +22,10 @@ export class BitErrorComponent {
|
||||
|
||||
get displayError() {
|
||||
const error = this.error();
|
||||
if (!error) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (error[0]) {
|
||||
case "required":
|
||||
return this.i18nService.t("inputRequired");
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
|
||||
import { ModelSignal, Signal } from "@angular/core";
|
||||
|
||||
// @ts-strict-ignore
|
||||
export type InputTypes =
|
||||
| "text"
|
||||
| "password"
|
||||
@@ -16,14 +13,14 @@ export type InputTypes =
|
||||
| "time";
|
||||
|
||||
export abstract class BitFormFieldControl {
|
||||
ariaDescribedBy: string;
|
||||
id: Signal<string>;
|
||||
labelForId: string;
|
||||
required: boolean;
|
||||
hasError: boolean;
|
||||
error: [string, any];
|
||||
type?: ModelSignal<InputTypes>;
|
||||
spellcheck?: ModelSignal<boolean | undefined>;
|
||||
readOnly?: boolean;
|
||||
focus?: () => void;
|
||||
abstract ariaDescribedBy?: string;
|
||||
abstract id: Signal<string>;
|
||||
abstract labelForId: string;
|
||||
abstract required: boolean;
|
||||
abstract hasError: boolean;
|
||||
abstract error: [string, any];
|
||||
abstract type?: ModelSignal<InputTypes | undefined>;
|
||||
abstract spellcheck?: ModelSignal<boolean | undefined>;
|
||||
abstract readOnly?: boolean;
|
||||
abstract focus?: () => void;
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
>
|
||||
<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"
|
||||
[attr.for]="input().labelForId"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="labelContent"></ng-container>
|
||||
@if (input.required) {
|
||||
@if (input().required) {
|
||||
<span class="tw-text-[0.625rem] tw-relative tw-bottom-[-1px]">
|
||||
({{ "required" | i18n }})</span
|
||||
>
|
||||
@@ -46,7 +46,7 @@
|
||||
</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"
|
||||
class="tw-gap-1 tw-bg-background tw-rounded-lg tw-flex tw-min-h-11 [&:has(input:read-only,textarea:read-only):not(:has(button:not([aria-disabled='true'])))]:tw-bg-secondary-100"
|
||||
>
|
||||
<div
|
||||
#prefixContainer
|
||||
@@ -78,7 +78,7 @@
|
||||
<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"
|
||||
[attr.for]="input.labelForId"
|
||||
[attr.for]="input().labelForId"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="labelContent"></ng-container>
|
||||
</label>
|
||||
@@ -109,11 +109,11 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@switch (input.hasError) {
|
||||
@switch (input().hasError) {
|
||||
@case (false) {
|
||||
<ng-content select="bit-hint"></ng-content>
|
||||
}
|
||||
@case (true) {
|
||||
<bit-error [error]="input.error"></bit-error>
|
||||
<bit-error [error]="input().error"></bit-error>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import {
|
||||
AfterContentChecked,
|
||||
booleanAttribute,
|
||||
Component,
|
||||
ContentChild,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
ViewChild,
|
||||
signal,
|
||||
input,
|
||||
Input,
|
||||
contentChild,
|
||||
viewChild,
|
||||
} from "@angular/core";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
@@ -30,14 +28,14 @@ import { BitFormFieldControl } from "./form-field-control";
|
||||
imports: [CommonModule, BitErrorComponent, I18nPipe],
|
||||
})
|
||||
export class BitFormFieldComponent implements AfterContentChecked {
|
||||
@ContentChild(BitFormFieldControl) input: BitFormFieldControl;
|
||||
@ContentChild(BitHintComponent) hint: BitHintComponent;
|
||||
@ContentChild(BitLabel) label: BitLabel;
|
||||
readonly input = contentChild.required(BitFormFieldControl);
|
||||
readonly hint = contentChild(BitHintComponent);
|
||||
readonly label = contentChild(BitLabel);
|
||||
|
||||
@ViewChild("prefixContainer") prefixContainer: ElementRef<HTMLDivElement>;
|
||||
@ViewChild("suffixContainer") suffixContainer: ElementRef<HTMLDivElement>;
|
||||
readonly prefixContainer = viewChild<ElementRef<HTMLDivElement>>("prefixContainer");
|
||||
readonly suffixContainer = viewChild<ElementRef<HTMLDivElement>>("suffixContainer");
|
||||
|
||||
@ViewChild(BitErrorComponent) error: BitErrorComponent;
|
||||
readonly error = viewChild(BitErrorComponent);
|
||||
|
||||
readonly disableMargin = input(false, { transform: booleanAttribute });
|
||||
|
||||
@@ -54,7 +52,7 @@ export class BitFormFieldComponent implements AfterContentChecked {
|
||||
const shouldFocusBorderAppear = this.defaultContentIsFocused();
|
||||
|
||||
const groupClasses = [
|
||||
this.input.hasError
|
||||
this.input().hasError
|
||||
? "group-hover/bit-form-field:tw-border-danger-700"
|
||||
: "group-hover/bit-form-field:tw-border-primary-600",
|
||||
// the next 2 selectors override the above hover selectors when the input (or text area) is non-interactive (i.e. readonly, disabled)
|
||||
@@ -68,7 +66,7 @@ export class BitFormFieldComponent implements AfterContentChecked {
|
||||
: "",
|
||||
];
|
||||
|
||||
const baseInputBorderClasses = inputBorderClasses(this.input.hasError);
|
||||
const baseInputBorderClasses = inputBorderClasses(this.input().hasError);
|
||||
|
||||
const borderClasses = baseInputBorderClasses.concat(groupClasses);
|
||||
|
||||
@@ -100,19 +98,21 @@ export class BitFormFieldComponent implements AfterContentChecked {
|
||||
}
|
||||
|
||||
protected get readOnly(): boolean {
|
||||
return this.input.readOnly;
|
||||
return !!this.input().readOnly;
|
||||
}
|
||||
|
||||
ngAfterContentChecked(): void {
|
||||
if (this.error) {
|
||||
this.input.ariaDescribedBy = this.error.id;
|
||||
} else if (this.hint) {
|
||||
this.input.ariaDescribedBy = this.hint.id;
|
||||
const error = this.error();
|
||||
const hint = this.hint();
|
||||
if (error) {
|
||||
this.input().ariaDescribedBy = error.id;
|
||||
} else if (hint) {
|
||||
this.input().ariaDescribedBy = hint.id;
|
||||
} else {
|
||||
this.input.ariaDescribedBy = undefined;
|
||||
this.input().ariaDescribedBy = undefined;
|
||||
}
|
||||
|
||||
this.prefixHasChildren.set(this.prefixContainer?.nativeElement.childElementCount > 0);
|
||||
this.suffixHasChildren.set(this.suffixContainer?.nativeElement.childElementCount > 0);
|
||||
this.prefixHasChildren.set((this.prefixContainer()?.nativeElement.childElementCount ?? 0) > 0);
|
||||
this.suffixHasChildren.set((this.suffixContainer()?.nativeElement.childElementCount ?? 0) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { TextFieldModule } from "@angular/cdk/text-field";
|
||||
import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core";
|
||||
import {
|
||||
AbstractControl,
|
||||
UntypedFormBuilder,
|
||||
@@ -15,6 +12,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
import { A11yTitleDirective } from "../a11y/a11y-title.directive";
|
||||
import { AsyncActionsModule } from "../async-actions";
|
||||
import { BadgeModule } from "../badge";
|
||||
import { ButtonModule } from "../button";
|
||||
@@ -31,41 +29,6 @@ import { I18nMockService } from "../utils/i18n-mock.service";
|
||||
import { BitFormFieldComponent } from "./form-field.component";
|
||||
import { FormFieldModule } from "./form-field.module";
|
||||
|
||||
// TOOD: This solves a circular dependency between components and angular.
|
||||
@Directive({
|
||||
selector: "[appA11yTitle]",
|
||||
})
|
||||
export class A11yTitleDirective implements OnInit {
|
||||
@Input() set appA11yTitle(title: string) {
|
||||
this.title = title;
|
||||
this.setAttributes();
|
||||
}
|
||||
|
||||
private title: string;
|
||||
private originalTitle: string | null;
|
||||
private originalAriaLabel: string | null;
|
||||
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private renderer: Renderer2,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.originalTitle = this.el.nativeElement.getAttribute("title");
|
||||
this.originalAriaLabel = this.el.nativeElement.getAttribute("aria-label");
|
||||
this.setAttributes();
|
||||
}
|
||||
|
||||
private setAttributes() {
|
||||
if (this.originalTitle === null) {
|
||||
this.renderer.setAttribute(this.el.nativeElement, "title", this.title);
|
||||
}
|
||||
if (this.originalAriaLabel === null) {
|
||||
this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
title: "Component Library/Form/Field",
|
||||
component: BitFormFieldComponent,
|
||||
@@ -276,8 +239,8 @@ export const Readonly: Story = {
|
||||
<bit-form-field>
|
||||
<bit-label>Input</bit-label>
|
||||
<input bitInput type="password" value="Foobar" [readonly]="true" />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Input'"></button>
|
||||
<button type="button" label="Toggle password" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [label]="'Clone Input'"></button>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
@@ -298,7 +261,7 @@ export const Readonly: Story = {
|
||||
<bit-label>Input</bit-label>
|
||||
<input bitInput type="password" value="Foobar" readonly />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Input'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [label]="'Clone Input'"></button>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
@@ -346,11 +309,11 @@ export const ButtonInputGroup: Story = {
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-label>
|
||||
<button type="button" bitPrefix bitIconButton="bwi-star" [appA11yTitle]="'Favorite Label'"></button>
|
||||
<button type="button" bitPrefix bitIconButton="bwi-star" [label]="'Favorite Label'"></button>
|
||||
<input bitInput placeholder="Placeholder" />
|
||||
<button type="button" bitSuffix bitIconButton="bwi-eye" [appA11yTitle]="'Hide Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" [appA11yTitle]="'Menu Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-eye" [label]="'Hide Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [label]="'Clone Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" [label]="'Menu Label'"></button>
|
||||
</bit-form-field>
|
||||
`,
|
||||
}),
|
||||
@@ -363,11 +326,11 @@ export const DisabledButtonInputGroup: Story = {
|
||||
template: /*html*/ `
|
||||
<bit-form-field>
|
||||
<bit-label>Label</bit-label>
|
||||
<button type="button" bitPrefix bitIconButton="bwi-star" disabled [appA11yTitle]="'Favorite Label'"></button>
|
||||
<button type="button" bitPrefix bitIconButton="bwi-star" disabled [label]="'Favorite Label'"></button>
|
||||
<input bitInput placeholder="Placeholder" disabled />
|
||||
<button type="button" bitSuffix bitIconButton="bwi-eye" disabled [appA11yTitle]="'Hide Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" disabled [appA11yTitle]="'Clone Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled [appA11yTitle]="'Menu Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-eye" disabled [label]="'Hide Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" disabled [label]="'Clone Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled [label]="'Menu Label'"></button>
|
||||
|
||||
</bit-form-field>
|
||||
`,
|
||||
@@ -382,9 +345,9 @@ export const PartiallyDisabledButtonInputGroup: Story = {
|
||||
<bit-form-field>
|
||||
<bit-label>Label</bit-label>
|
||||
<input bitInput placeholder="Placeholder" disabled />
|
||||
<button type="button" bitSuffix bitIconButton="bwi-eye" [appA11yTitle]="'Hide Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [appA11yTitle]="'Clone Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled [appA11yTitle]="'Menu Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-eye" [label]="'Hide Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-clone" [label]="'Clone Label'"></button>
|
||||
<button type="button" bitSuffix bitIconButton="bwi-ellipsis-v" disabled [label]="'Menu Label'"></button>
|
||||
</bit-form-field>
|
||||
`,
|
||||
}),
|
||||
|
||||
@@ -57,17 +57,19 @@ export class BitPasswordInputToggleDirective implements AfterContentInit, OnChan
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
if (this.formField.input?.type) {
|
||||
this.toggled.set(this.formField.input.type() !== "password");
|
||||
const input = this.formField.input();
|
||||
if (input?.type) {
|
||||
this.toggled.set(input.type() !== "password");
|
||||
}
|
||||
this.button.icon.set(this.icon);
|
||||
}
|
||||
|
||||
private update() {
|
||||
this.button.icon.set(this.icon);
|
||||
if (this.formField.input?.type != null) {
|
||||
this.formField.input.type.set(this.toggled() ? "text" : "password");
|
||||
this.formField?.input?.spellcheck?.set(this.toggled() ? false : undefined);
|
||||
const input = this.formField.input();
|
||||
if (input?.type != null) {
|
||||
input.type.set(this.toggled() ? "text" : "password");
|
||||
input?.spellcheck?.set(this.toggled() ? false : undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,13 @@ import { BitPasswordInputToggleDirective } from "./password-input-toggle.directi
|
||||
<bit-form-field>
|
||||
<bit-label>Password</bit-label>
|
||||
<input bitInput type="password" />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<button
|
||||
type="button"
|
||||
label="Toggle password visibility"
|
||||
bitIconButton
|
||||
bitSuffix
|
||||
bitPasswordInputToggle
|
||||
></button>
|
||||
</bit-form-field>
|
||||
</form>
|
||||
`,
|
||||
@@ -55,7 +61,7 @@ describe("PasswordInputToggle", () => {
|
||||
button = buttonEl.componentInstance;
|
||||
const formFieldEl = fixture.debugElement.query(By.directive(BitFormFieldComponent));
|
||||
const formField: BitFormFieldComponent = formFieldEl.componentInstance;
|
||||
input = formField.input;
|
||||
input = formField.input();
|
||||
});
|
||||
|
||||
describe("initial state", () => {
|
||||
|
||||
@@ -48,7 +48,7 @@ export const Default: Story = {
|
||||
<bit-form-field>
|
||||
<bit-label>Password</bit-label>
|
||||
<input bitInput type="password" />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<button type="button" label="Toggle password visibility" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
</bit-form-field>
|
||||
</form>
|
||||
`,
|
||||
@@ -63,7 +63,7 @@ export const Binding: Story = {
|
||||
<bit-form-field>
|
||||
<bit-label>Password</bit-label>
|
||||
<input bitInput type="password" />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle [(toggled)]="toggled"></button>
|
||||
<button type="button" label="Toggle password visibility" bitIconButton bitSuffix bitPasswordInputToggle [(toggled)]="toggled"></button>
|
||||
</bit-form-field>
|
||||
|
||||
<label class="tw-text-main">
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { NgClass } from "@angular/common";
|
||||
import { Component, computed, ElementRef, HostBinding, input, model } from "@angular/core";
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
effect,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
inject,
|
||||
input,
|
||||
model,
|
||||
} from "@angular/core";
|
||||
import { toObservable, toSignal } from "@angular/core/rxjs-interop";
|
||||
import { debounce, interval } from "rxjs";
|
||||
|
||||
import { AriaDisableDirective } from "../a11y";
|
||||
import { setA11yTitleAndAriaLabel } from "../a11y/set-a11y-title-and-aria-label";
|
||||
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
|
||||
import { FocusableElement } from "../shared/focusable-element";
|
||||
import { ariaDisableElement } from "../utils";
|
||||
|
||||
export type IconButtonType = "primary" | "danger" | "contrast" | "main" | "muted" | "nav-contrast";
|
||||
|
||||
@@ -64,7 +74,7 @@ const sizes: Record<IconButtonSize, string[]> = {
|
||||
small: ["tw-text-base", "tw-p-2", "tw-rounded"],
|
||||
};
|
||||
/**
|
||||
* 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`.
|
||||
* 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` that are added via the `label` input.
|
||||
|
||||
* 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.
|
||||
|
||||
@@ -79,7 +89,6 @@ const sizes: Record<IconButtonSize, string[]> = {
|
||||
],
|
||||
imports: [NgClass],
|
||||
host: {
|
||||
"[attr.disabled]": "disabledAttr()",
|
||||
/**
|
||||
* When the `bitIconButton` input is dynamic from a consumer, Angular doesn't put the
|
||||
* `bitIconButton` attribute into the DOM. We use the attribute as a css selector in
|
||||
@@ -88,14 +97,23 @@ const sizes: Record<IconButtonSize, string[]> = {
|
||||
*/
|
||||
"[attr.bitIconButton]": "icon()",
|
||||
},
|
||||
hostDirectives: [AriaDisableDirective],
|
||||
})
|
||||
export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableElement {
|
||||
readonly icon = model<string>(undefined, { alias: "bitIconButton" });
|
||||
readonly icon = model.required<string>({ alias: "bitIconButton" });
|
||||
|
||||
readonly buttonType = input<IconButtonType>("main");
|
||||
|
||||
readonly size = model<IconButtonSize>("default");
|
||||
|
||||
/**
|
||||
* label input will be used to set the `aria-label` attributes on the button.
|
||||
* This is for accessibility purposes, as it provides a text alternative for the icon button.
|
||||
*
|
||||
* NOTE: It will also be used to set the `title` attribute on the button if no `title` is provided.
|
||||
*/
|
||||
readonly label = input<string>();
|
||||
|
||||
@HostBinding("class") get classList() {
|
||||
return [
|
||||
"tw-font-semibold",
|
||||
@@ -111,7 +129,7 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE
|
||||
.concat(sizes[this.size()])
|
||||
.concat(
|
||||
this.showDisabledStyles() || this.disabled()
|
||||
? ["disabled:tw-opacity-60", "disabled:hover:!tw-bg-transparent"]
|
||||
? ["aria-disabled:tw-opacity-60", "aria-disabled:hover:!tw-bg-transparent"]
|
||||
: [],
|
||||
);
|
||||
}
|
||||
@@ -122,7 +140,7 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE
|
||||
|
||||
protected disabledAttr = computed(() => {
|
||||
const disabled = this.disabled() != null && this.disabled() !== false;
|
||||
return disabled || this.loading() ? true : null;
|
||||
return disabled || this.loading();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -161,5 +179,21 @@ export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableE
|
||||
return this.elementRef.nativeElement;
|
||||
}
|
||||
|
||||
constructor(private elementRef: ElementRef) {}
|
||||
private elementRef = inject(ElementRef);
|
||||
|
||||
constructor() {
|
||||
const element = this.elementRef.nativeElement;
|
||||
|
||||
ariaDisableElement(element, this.disabledAttr);
|
||||
|
||||
const originalTitle = element.getAttribute("title");
|
||||
|
||||
effect(() => {
|
||||
setA11yTitleAndAriaLabel({
|
||||
element: this.elementRef.nativeElement,
|
||||
title: originalTitle ?? this.label(),
|
||||
label: this.label(),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
component: BitIconButtonComponent,
|
||||
args: {
|
||||
bitIconButton: "bwi-plus",
|
||||
label: "Your button label here",
|
||||
},
|
||||
argTypes: {
|
||||
buttonType: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, effect, input } from "@angular/core";
|
||||
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";
|
||||
|
||||
import { Icon, isIcon } from "./icon";
|
||||
import { Icon, isIcon } from "@bitwarden/assets/svg";
|
||||
|
||||
@Component({
|
||||
selector: "bit-icon",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { Icon, svgIcon } from "./icon";
|
||||
import { Icon, svgIcon } from "@bitwarden/assets/svg";
|
||||
|
||||
import { BitIconComponent } from "./icon.component";
|
||||
|
||||
describe("IconComponent", () => {
|
||||
|
||||
@@ -23,9 +23,10 @@ import { IconModule } from "@bitwarden/components";
|
||||
[SVG Formatter Beautifier](https://codebeautify.org/svg-formatter-beautifier) to make applying
|
||||
classes easier.
|
||||
|
||||
2. **Rename the file** as a `<name>.icon.ts` TypeScript file.
|
||||
2. **Rename the file** as a `<name>.icon.ts` TypeScript file and place it in the `libs/assets/svg`
|
||||
lib.
|
||||
|
||||
3. **Import** `svgIcon` from `@bitwarden/components`.
|
||||
3. **Import** `svgIcon` from `./icon-service`.
|
||||
|
||||
4. **Define and export** a `const` to represent your `svgIcon`.
|
||||
|
||||
@@ -74,16 +75,19 @@ import { IconModule } from "@bitwarden/components";
|
||||
[viewBox](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox) attribute in order
|
||||
to allow the SVG to scale to fit its container.
|
||||
- **Note:** Scaling is required for any SVG used as an
|
||||
[AnonLayout](?path=/docs/auth-anon-layout--docs) `pageIcon`.
|
||||
[AnonLayout](?path=/docs/component-library-anon-layout--docs) `pageIcon`.
|
||||
|
||||
7. **Import your SVG const** anywhere you want to use the SVG.
|
||||
7. **Replace any generic `clipPath` ids** (such as `id="a"`) with a unique id, and update the
|
||||
referencing element to use the new id (such as `clip-path="url(#unique-id-here)"`).
|
||||
|
||||
8. **Import your SVG const** anywhere you want to use the SVG.
|
||||
- **Angular Component Example:**
|
||||
- **TypeScript:**
|
||||
|
||||
```typescript
|
||||
import { Component } from "@angular/core";
|
||||
import { ExampleIcon } from "your/path/here";
|
||||
import { IconModule } from '@bitwarden/components';
|
||||
import { ExampleIcon, Example2Icon } from "@bitwarden/assets/svg";
|
||||
|
||||
@Component({
|
||||
selector: "app-example",
|
||||
@@ -112,5 +116,5 @@ import { IconModule } from "@bitwarden/components";
|
||||
<bit-icon [icon]="Icons.ExampleIcon" [ariaLabel]="Your custom label text here"></bit-icon>
|
||||
```
|
||||
|
||||
8. **Ensure your SVG renders properly** according to Figma in both light and dark modes on a client
|
||||
9. **Ensure your SVG renders properly** according to Figma in both light and dark modes on a client
|
||||
which supports multiple style modes.
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import * as IconExports from "./icon";
|
||||
import { DynamicContentNotAllowedError, isIcon, svgIcon } from "./icon";
|
||||
|
||||
describe("Icon", () => {
|
||||
it("exports should not expose Icon class", () => {
|
||||
expect(Object.keys(IconExports)).not.toContain("Icon");
|
||||
});
|
||||
|
||||
describe("isIcon", () => {
|
||||
it("should return true when input is icon", () => {
|
||||
const result = isIcon(svgIcon`icon`);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when input is not an icon", () => {
|
||||
const result = isIcon({ svg: "not an icon" });
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("template literal", () => {
|
||||
it("should throw when attempting to create dynamic icons", () => {
|
||||
const dynamic = "some user input";
|
||||
|
||||
const f = () => svgIcon`static and ${dynamic}`;
|
||||
|
||||
expect(f).toThrow(DynamicContentNotAllowedError);
|
||||
});
|
||||
|
||||
it("should return svg content when supplying icon with svg string", () => {
|
||||
const icon = svgIcon`safe static content`;
|
||||
|
||||
expect(icon.svg).toBe("safe static content");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Meta, StoryObj } from "@storybook/angular";
|
||||
import { Meta } from "@storybook/angular";
|
||||
|
||||
import * as SvgIcons from "@bitwarden/assets/svg";
|
||||
|
||||
import { BitIconComponent } from "./icon.component";
|
||||
import * as GenericIcons from "./icons";
|
||||
|
||||
export default {
|
||||
title: "Component Library/Icon",
|
||||
@@ -14,21 +15,36 @@ export default {
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
type Story = StoryObj<BitIconComponent>;
|
||||
const {
|
||||
// Filtering out the few non-icons in the libs/assets/svg import
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
DynamicContentNotAllowedError: _DynamicContentNotAllowedError,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
isIcon,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
svgIcon,
|
||||
...Icons
|
||||
}: {
|
||||
[key: string]: any;
|
||||
} = SvgIcons;
|
||||
|
||||
export const Default: Story = {
|
||||
export const Default = {
|
||||
render: (args: { icons: [string, any][] }) => ({
|
||||
props: args,
|
||||
template: /*html*/ `
|
||||
<div class="tw-bg-secondary-100 tw-p-2 tw-grid tw-grid-cols-[repeat(auto-fit,minmax(224px,1fr))] tw-gap-2">
|
||||
@for (icon of icons; track icon[0]) {
|
||||
<div class="tw-size-56 tw-border tw-border-secondary-300 tw-rounded-md">
|
||||
<div class="tw-text-xs tw-text-center">{{icon[0]}}</div>
|
||||
<div class="tw-size-52 tw-w-full tw-content-center">
|
||||
<bit-icon [icon]="icon[1]" class="tw-flex tw-justify-center tw-max-h-full"></bit-icon>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
args: {
|
||||
icon: GenericIcons.NoAccess,
|
||||
},
|
||||
argTypes: {
|
||||
icon: {
|
||||
options: Object.keys(GenericIcons),
|
||||
mapping: GenericIcons,
|
||||
control: { type: "select" },
|
||||
},
|
||||
ariaLabel: {
|
||||
control: "text",
|
||||
description: "the text used by a screen reader to describe the icon",
|
||||
},
|
||||
icons: Object.entries(Icons),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
class Icon {
|
||||
constructor(readonly svg: string) {}
|
||||
}
|
||||
|
||||
// We only export the type to prohibit the creation of Icons without using
|
||||
// the `svgIcon` template literal tag.
|
||||
export type { Icon };
|
||||
|
||||
export function isIcon(icon: unknown): icon is Icon {
|
||||
return icon instanceof Icon;
|
||||
}
|
||||
|
||||
export class DynamicContentNotAllowedError extends Error {
|
||||
constructor() {
|
||||
super("Dynamic content in icons is not allowed due to risk of user-injected XSS.");
|
||||
}
|
||||
}
|
||||
|
||||
export function svgIcon(strings: TemplateStringsArray, ...values: unknown[]): Icon {
|
||||
if (values.length > 0) {
|
||||
throw new DynamicContentNotAllowedError();
|
||||
}
|
||||
|
||||
return new Icon(strings[0]);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const BitwardenLogo = svgIcon`
|
||||
<svg viewBox="0 0 290 45" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Bitwarden</title>
|
||||
<path class="tw-fill-marketing-logo" fill-rule="evenodd" clip-rule="evenodd" d="M69.799 10.713c3.325 0 5.911 1.248 7.811 3.848 1.9 2.549 2.85 6.033 2.85 10.453 0 4.576-.95 8.113-2.902 10.61-1.953 2.547-4.592 3.743-7.918 3.743-3.325 0-5.858-1.144-7.758-3.536h-.528l-1.003 2.444a.976.976 0 0 1-.897.572H55.23a.94.94 0 0 1-.95-.936V1.352a.94.94 0 0 1 .95-.936h5.7a.94.94 0 0 1 .95.936v8.009c0 1.144-.105 2.964-.316 5.46h.317c1.741-2.704 4.433-4.108 7.917-4.108Zm-2.428 6.084c-1.847 0-3.273.572-4.17 1.717-.844 1.144-1.32 3.068-1.32 5.668v.832c0 2.964.423 5.097 1.32 6.345.897 1.248 2.322 1.924 4.275 1.924 1.531 0 2.85-.728 3.748-2.184.897-1.404 1.372-3.537 1.372-6.189 0-2.704-.475-4.732-1.372-6.084-.95-1.352-2.27-2.029-3.853-2.029ZM93.022 38.9h-5.7a.94.94 0 0 1-.95-.936V12.221a.94.94 0 0 1 .95-.936h5.7a.94.94 0 0 1 .95.936v25.69c.053.468-.422.988-.95.988Zm20.849-5.564c1.108 0 2.428-.208 4.011-.624a.632.632 0 0 1 .792.624v4.316a.64.64 0 0 1-.37.572c-1.794.728-4.064 1.092-6.597 1.092-3.062 0-5.278-.728-6.651-2.288-1.372-1.508-2.111-3.796-2.111-6.812V16.953h-3.008c-.37 0-.634-.26-.634-.624v-2.444c0-.052.053-.104.053-.156l4.17-2.444 2.058-5.408c.106-.26.317-.417.581-.417h3.8c.369 0 .633.26.633.625v5.252h7.548c.158 0 .317.156.317.312v4.68c0 .364-.264.624-.634.624h-7.178v13.21c0 1.04.317 1.872.897 2.34.528.572 1.373.832 2.323.832Zm35.521 5.564c-.739 0-1.319-.468-1.636-1.144l-5.595-16.797c-.369-1.196-.844-3.016-1.478-5.357h-.158l-.528 1.873-1.108 3.536-5.753 16.797c-.211.676-.845 1.092-1.584 1.092a1.628 1.628 0 0 1-1.583-1.196l-7.02-24.182c-.211-.728.369-1.508 1.214-1.508h.158c.528 0 1.003.364 1.161.884l4.117 14.717c1.003 3.849 1.689 6.657 2.006 8.53h.158c.95-3.85 1.689-6.397 2.164-7.698l5.331-15.393c.211-.624.792-1.04 1.531-1.04.686 0 1.267.416 1.478 1.04l4.961 15.29c1.214 3.9 1.953 6.396 2.217 7.696h.158c.159-1.04.792-3.952 2.006-8.633l3.958-14.509c.159-.52.634-.884 1.162-.884.791 0 1.372.728 1.161 1.508l-6.651 24.182c-.211.728-.844 1.196-1.636 1.196h-.211Zm31.352 0a.962.962 0 0 1-.95-.832l-.475-3.432h-.264c-1.372 1.716-2.745 2.964-4.223 3.692-1.425.728-3.166 1.04-5.119 1.04-2.692 0-4.751-.676-6.228-2.028-1.32-1.196-2.059-2.808-2.164-4.836-.212-2.704.95-5.305 3.166-6.813 2.27-1.456 5.437-2.34 9.712-2.34l5.173-.156v-1.768c0-2.6-.528-4.473-1.637-5.773-1.108-1.3-2.744-1.924-5.067-1.924-2.216 0-4.433.52-6.756 1.612-.58.26-1.266 0-1.53-.572s0-1.248.58-1.456c2.639-1.04 5.226-1.612 7.865-1.612 3.008 0 5.225.78 6.756 2.34 1.478 1.508 2.216 3.953 2.216 7.125v16.901c-.052.312-.527.832-1.055.832Zm-10.926-1.768c2.956 0 5.226-.832 6.862-2.444 1.689-1.612 2.533-3.952 2.533-6.813v-2.6l-4.75.208c-3.853.156-6.545.78-8.234 1.768-1.636.988-2.481 2.6-2.481 4.68 0 1.665.528 3.017 1.531 3.953 1.161.78 2.639 1.248 4.539 1.248Zm31.246-25.638c.792 0 1.584.052 2.481.156a1.176 1.176 0 0 1 1.003 1.352c-.106.624-.739.988-1.372.884-.792-.104-1.584-.208-2.375-.208-2.323 0-4.223.988-5.701 2.912-1.478 1.925-2.217 4.42-2.217 7.333v13.625c0 .676-.527 1.196-1.214 1.196-.686 0-1.213-.52-1.213-1.196V13.105c0-.572.475-1.04 1.055-1.04.581 0 1.056.416 1.056.988l.211 3.848h.158c1.109-1.976 2.323-3.38 3.589-4.16 1.214-.832 2.745-1.248 4.539-1.248Zm18.579 0c1.953 0 3.695.364 5.12 1.04 1.478.676 2.745 1.924 3.853 3.64h.158a122.343 122.343 0 0 1-.158-6.084V1.612c0-.676.528-1.196 1.214-1.196.686 0 1.214.52 1.214 1.196v36.351c0 .468-.37.832-.845.832a.852.852 0 0 1-.844-.78l-.528-3.38h-.211c-2.058 3.068-5.067 4.576-8.92 4.576-3.8 0-6.598-1.144-8.656-3.484-1.953-2.34-3.008-5.668-3.008-10.089 0-4.628.95-8.165 2.955-10.66 2.006-2.237 4.856-3.485 8.656-3.485Zm0 2.236c-3.008 0-5.225 1.04-6.756 3.12-1.478 2.029-2.216 4.993-2.216 8.945 0 7.593 3.008 11.39 9.025 11.39 3.114 0 5.331-.885 6.756-2.653 1.478-1.768 2.164-4.68 2.164-8.737v-.416c0-4.16-.686-7.124-2.164-8.893-1.372-1.872-3.642-2.756-6.809-2.756Zm31.616 25.638c-3.959 0-7.02-1.196-9.289-3.64-2.217-2.392-3.326-5.772-3.326-10.089 0-4.316 1.056-7.748 3.22-10.297 2.164-2.6 5.014-3.9 8.656-3.9 3.167 0 5.753 1.092 7.548 3.276 1.9 2.184 2.797 5.2 2.797 8.997v1.976h-19.634c.052 3.692.897 6.5 2.639 8.477 1.741 1.976 4.169 2.86 7.389 2.86 1.531 0 2.956-.104 4.117-.312.844-.156 1.847-.416 3.061-.832.686-.26 1.425.26 1.425.988 0 .416-.264.832-.686.988-1.267.52-2.481.832-3.589 1.04-1.32.364-2.745.468-4.328.468Zm-.739-25.69c-2.639 0-4.75.832-6.334 2.548-1.583 1.665-2.48 4.16-2.797 7.333h16.89c0-3.068-.686-5.564-2.059-7.28-1.372-1.717-3.272-2.6-5.7-2.6ZM288.733 38.9c-.686 0-1.214-.52-1.214-1.196V21.426c0-2.704-.58-4.68-1.689-5.877-1.214-1.196-2.955-1.872-5.383-1.872-3.273 0-5.648.78-7.126 2.444-1.478 1.613-2.322 4.265-2.322 7.853V37.6c0 .676-.528 1.196-1.214 1.196-.686 0-1.214-.52-1.214-1.196V13.105c0-.624.475-1.092 1.108-1.092.581 0 1.003.416 1.109.936l.316 2.704h.159c1.794-2.808 4.908-4.212 9.448-4.212 6.175 0 9.289 3.276 9.289 9.829V37.6c-.053.727-.633 1.3-1.267 1.3ZM90.225 0c-2.48 0-4.486 1.872-4.486 4.212v.416c0 2.289 2.058 4.213 4.486 4.213s4.486-1.924 4.486-4.213v-.364C94.711 1.872 92.653 0 90.225 0Z" />
|
||||
<path class="tw-fill-marketing-logo" d="M32.041 24.546V5.95H18.848v33.035c2.336-1.22 4.427-2.547 6.272-3.98 4.614-3.565 6.921-7.051 6.921-10.46Zm5.654-22.314v22.314c0 1.665-.329 3.317-.986 4.953-.658 1.637-1.473 3.09-2.445 4.359-.971 1.268-2.13 2.503-3.475 3.704-1.345 1.2-2.586 2.199-3.725 2.993a46.963 46.963 0 0 1-3.563 2.251c-1.237.707-2.116 1.187-2.636 1.439-.52.251-.938.445-1.252.58-.235.117-.49.175-.765.175s-.53-.058-.766-.174c-.314-.136-.731-.33-1.252-.581-.52-.252-1.398-.732-2.635-1.439a47.003 47.003 0 0 1-3.564-2.251c-1.138-.794-2.38-1.792-3.725-2.993-1.345-1.2-2.503-2.436-3.475-3.704-.972-1.27-1.787-2.722-2.444-4.359C.329 27.863 0 26.211 0 24.546V2.232c0-.504.187-.94.56-1.308A1.823 1.823 0 0 1 1.885.372H35.81c.511 0 .953.184 1.326.552.373.368.56.804.56 1.308Z" />
|
||||
</svg>
|
||||
`;
|
||||
File diff suppressed because one or more lines are too long
@@ -1,14 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const GeneratorInactive = svgIcon`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.05169 10.7766C3.00697 11.1884 3.30455 11.5585 3.71634 11.6032C4.12814 11.6479 4.49821 11.3503 4.54292 10.9386C4.93486 7.32891 8.05586 4.49609 11.8708 4.49609C14.608 4.49609 16.9911 5.95574 18.2619 8.11589H16.2676C15.8534 8.11589 15.5176 8.45168 15.5176 8.86589C15.5176 9.2801 15.8534 9.61589 16.2676 9.61589H20.2491C20.6634 9.61589 20.9991 9.2801 20.9991 8.86589V4.88347C20.9991 4.46926 20.6634 4.13347 20.2491 4.13347C19.8349 4.13347 19.4991 4.46926 19.4991 4.88347V7.26212C17.9511 4.70467 15.1112 2.99609 11.8708 2.99609C7.30492 2.99609 3.52788 6.39108 3.05169 10.7766ZM20.9425 13.2164C20.9872 12.8046 20.6896 12.4345 20.2778 12.3898C19.866 12.3451 19.4959 12.6427 19.4512 13.0545C19.0593 16.6641 15.9383 19.4969 12.1233 19.4969C9.38639 19.4969 7.0034 18.0375 5.73253 15.8776H7.72852C8.14273 15.8776 8.47852 15.5418 8.47852 15.1276C8.47852 14.7134 8.14273 14.3776 7.72852 14.3776H3.74695C3.33273 14.3776 2.99695 14.7134 2.99695 15.1276V19.11C2.99695 19.5242 3.33273 19.86 3.74695 19.86C4.16116 19.86 4.49695 19.5242 4.49695 19.11V16.7341C6.04539 19.2898 8.88426 20.9969 12.1233 20.9969C16.6892 20.9969 20.4663 17.6019 20.9425 13.2164ZM12.7514 9.43718C12.7514 9.02297 12.4156 8.68718 12.0014 8.68718C11.5872 8.68718 11.2514 9.02297 11.2514 9.43718V11.0305L9.75498 10.5402C9.36135 10.4113 8.93772 10.6258 8.80876 11.0195C8.6798 11.4131 8.89436 11.8367 9.28799 11.9657L10.798 12.4604L9.85608 13.7799C9.61543 14.117 9.69364 14.5854 10.0308 14.826C10.3679 15.0667 10.8363 14.9885 11.0769 14.6514L12.0014 13.3562L12.9261 14.6514C13.1667 14.9885 13.6351 15.0667 13.9723 14.826C14.3094 14.5853 14.3876 14.1169 14.1469 13.7798L13.2049 12.4603L14.7146 11.9657C15.1083 11.8367 15.3228 11.4131 15.1939 11.0195C15.0649 10.6258 14.6412 10.4113 14.2476 10.5402L12.7514 11.0305V9.43718Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
export const GeneratorActive = svgIcon`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 12C18 15.3137 15.3137 18 12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12Z" class="tw-fill-primary-100" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.05169 10.7766C3.00697 11.1884 3.30455 11.5585 3.71634 11.6032C4.12814 11.6479 4.49821 11.3503 4.54292 10.9386C4.93486 7.32891 8.05586 4.49609 11.8708 4.49609C14.608 4.49609 16.9911 5.95574 18.2619 8.11589H16.2676C15.8534 8.11589 15.5176 8.45168 15.5176 8.86589C15.5176 9.2801 15.8534 9.61589 16.2676 9.61589H20.2491C20.6634 9.61589 20.9991 9.2801 20.9991 8.86589V4.88347C20.9991 4.46926 20.6634 4.13347 20.2491 4.13347C19.8349 4.13347 19.4991 4.46926 19.4991 4.88347V7.26212C17.9511 4.70467 15.1112 2.99609 11.8708 2.99609C7.30492 2.99609 3.52788 6.39108 3.05169 10.7766ZM20.9425 13.2164C20.9872 12.8046 20.6896 12.4345 20.2778 12.3898C19.866 12.3451 19.4959 12.6427 19.4512 13.0545C19.0593 16.6641 15.9383 19.4969 12.1233 19.4969C9.38639 19.4969 7.0034 18.0375 5.73253 15.8776H7.72852C8.14273 15.8776 8.47852 15.5418 8.47852 15.1276C8.47852 14.7134 8.14273 14.3776 7.72852 14.3776H3.74695C3.33273 14.3776 2.99695 14.7134 2.99695 15.1276V19.11C2.99695 19.5242 3.33273 19.86 3.74695 19.86C4.16116 19.86 4.49695 19.5242 4.49695 19.11V16.7341C6.04539 19.2898 8.88426 20.9969 12.1233 20.9969C16.6892 20.9969 20.4663 17.6019 20.9425 13.2164ZM12.7514 9.43718C12.7514 9.02297 12.4156 8.68718 12.0014 8.68718C11.5872 8.68718 11.2514 9.02297 11.2514 9.43718V11.0305L9.75498 10.5402C9.36135 10.4113 8.93772 10.6258 8.80876 11.0195C8.6798 11.4131 8.89436 11.8367 9.28799 11.9657L10.798 12.4604L9.85608 13.7799C9.61543 14.117 9.69364 14.5854 10.0308 14.826C10.3679 15.0667 10.8363 14.9885 11.0769 14.6514L12.0014 13.3562L12.9261 14.6514C13.1667 14.9885 13.6351 15.0667 13.9723 14.826C14.3094 14.5853 14.3876 14.1169 14.1469 13.7798L13.2049 12.4603L14.7146 11.9657C15.1083 11.8367 15.3228 11.4131 15.1939 11.0195C15.0649 10.6258 14.6412 10.4113 14.2476 10.5402L12.7514 11.0305V9.43718Z" class="tw-fill-primary-600" />
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,12 +0,0 @@
|
||||
export * from "./bitwarden-logo.icon";
|
||||
export * from "./extension-bitwarden-logo.icon";
|
||||
export * from "./lock.icon";
|
||||
export * from "./generator";
|
||||
export * from "./no-access";
|
||||
export * from "./no-results";
|
||||
export * from "./registration-check-email.icon";
|
||||
export * from "./search";
|
||||
export * from "./security";
|
||||
export * from "./send";
|
||||
export * from "./settings";
|
||||
export * from "./vault";
|
||||
@@ -1,17 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const LockIcon = svgIcon`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 100" fill="none">
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M27.5 48.218a9 9 0 0 1 9-9h47a9 9 0 0 1 9 9v7.5h-2v-7.5a7 7 0 0 0-7-7h-47a7 7 0 0 0-7 7v7.5h-2v-7.5Zm2 30.75v3.75a7 7 0 0 0 7 7h47a7 7 0 0 0 7-7v-3.75h2v3.75a9 9 0 0 1-9 9h-47a9 9 0 0 1-9-9v-3.75h2Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M60 10.718c-11.144 0-20 7.942-20 17.414v11.586h-2V28.132C38 17.317 48.007 8.718 60 8.718c11.991 0 22 8.552 22 19.414v11.586h-2V28.132c0-9.516-8.855-17.414-20-17.414ZM32.028 61.28a1 1 0 0 1 1 1v5.678a1 1 0 1 1-2 0v-5.679a1 1 0 0 1 1-1Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M38.452 65.897a1 1 0 0 1-.647 1.258l-5.472 1.755a1 1 0 1 1-.61-1.904l5.471-1.755a1 1 0 0 1 1.258.646Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M31.442 67.147a1 1 0 0 1 1.396.225l3.356 4.646a1 1 0 0 1-1.622 1.171l-3.355-4.646a1 1 0 0 1 .225-1.396Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M32.607 67.143a1 1 0 0 1 .236 1.394l-3.304 4.646a1 1 0 0 1-1.63-1.159l3.304-4.646a1 1 0 0 1 1.394-.235Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M25.656 65.895a1 1 0 0 1 1.26-.644l5.42 1.755a1 1 0 1 1-.616 1.903l-5.42-1.755a1 1 0 0 1-.644-1.26ZM50.508 61.28a1 1 0 0 1 1 1v5.678a1 1 0 1 1-2 0v-5.679a1 1 0 0 1 1-1Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M56.88 65.895a1 1 0 0 1-.644 1.26l-5.42 1.754a1 1 0 1 1-.616-1.903l5.42-1.755a1 1 0 0 1 1.26.644Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M49.922 67.147a1 1 0 0 1 1.397.225l3.355 4.646a1 1 0 1 1-1.621 1.171l-3.356-4.646a1 1 0 0 1 .225-1.396Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M51.093 67.147a1 1 0 0 1 .226 1.396l-3.356 4.646a1 1 0 0 1-1.621-1.17l3.355-4.647a1 1 0 0 1 1.396-.225Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-primary" fill-rule="evenodd" d="M44.136 65.895a1 1 0 0 1 1.26-.644l5.42 1.755a1 1 0 1 1-.616 1.903l-5.42-1.755a1 1 0 0 1-.644-1.26ZM62.568 72.603a1 1 0 0 1 1-1h10.84a1 1 0 1 1 0 2h-10.84a1 1 0 0 1-1-1ZM81.049 72.603a1 1 0 0 1 1-1h10.84a1 1 0 1 1 0 2H82.05a1 1 0 0 1-1-1Z" clip-rule="evenodd"/>
|
||||
<path class="tw-fill-art-accent" fill-rule="evenodd" d="M17.5 67.468c0-7.042 5.708-12.75 12.75-12.75h59.5c7.041 0 12.75 5.708 12.75 12.75s-5.709 12.75-12.75 12.75h-59.5c-7.042 0-12.75-5.708-12.75-12.75Zm12.75-10.75c-5.937 0-10.75 4.813-10.75 10.75s4.813 10.75 10.75 10.75h59.5c5.937 0 10.75-4.813 10.75-10.75s-4.813-10.75-10.75-10.75h-59.5Z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,12 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const NoAccess = svgIcon`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="154" height="130" fill="none">
|
||||
<path class="tw-stroke-secondary-600" d="M60.795 112.1h55.135a4 4 0 0 0 4-4V59.65M32.9 51.766V6a4 4 0 0 1 4-4h79.03a4 4 0 0 1 4 4v19.992" stroke-width="4"/>
|
||||
<path class="tw-stroke-secondary-600" d="M46.997 21.222h13.806M69.832 21.222h13.806M93.546 21.222h13.806M46.997 44.188h13.806M69.832 44.188h13.806M93.546 44.188h13.806M50.05 67.02h10.753M69.832 67.02h13.806M93.546 67.02h13.806M46.997 90.118h13.806M69.832 90.118h13.806M93.546 90.118h13.806" stroke-width="2" stroke-linecap="round"/>
|
||||
<path class="tw-stroke-secondary-600" d="M30.914 89.366c10.477 0 18.97-8.493 18.97-18.97 0-10.476-8.493-18.97-18.97-18.97-10.476 0-18.969 8.494-18.969 18.97 0 10.477 8.493 18.97 18.97 18.97ZM2.313 117.279c2.183-16.217 15.44-27.362 29.623-27.362 14.07 0 25.942 11.022 27.898 27.33.167 1.39-.988 2.753-2.719 2.753H5c-1.741 0-2.87-1.366-2.687-2.721Z" stroke-width="4"/>
|
||||
<path class="tw-stroke-danger-600" d="m147.884 50.361-15.89-27.522c-2.31-4-8.083-4-10.392 0l-15.891 27.523c-2.309 4 .578 9 5.196 9h31.781c4.619 0 7.505-5 5.196-9Z" stroke-width="4"/>
|
||||
<path class="tw-stroke-danger-600" d="M126.798 29.406v16.066" stroke-width="4" stroke-linecap="round"/>
|
||||
<path class="tw-fill-danger-600" d="M126.798 54.727a2.635 2.635 0 1 0 0-5.27 2.635 2.635 0 0 0 0 5.27Z" />
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,18 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const NoResults = svgIcon`
|
||||
<svg width="98" height="96" viewBox="0 0 98 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="tw-stroke-art-primary" d="M8.8545 86.7919L56.9901 86.7919C60.2321 86.7919 62.8603 84.1637 62.8603 80.9217L62.8603 32.2678C62.8603 30.7472 62.2702 29.2859 61.2143 28.1916L47.5536 14.0345C46.4473 12.8881 44.9225 12.2405 43.3293 12.2405L8.85451 12.2405C5.61249 12.2405 2.98431 14.8687 2.98431 18.1107L2.98431 80.9217C2.98431 84.1637 5.61248 86.7919 8.8545 86.7919Z" stroke-width="1.76106"/>
|
||||
<path class="tw-fill-background tw-stroke-art-primary" d="M18.8335 76.8125L66.9691 76.8125C70.2111 76.8125 72.8393 74.1844 72.8393 70.9423L72.8393 21.8271C72.8393 20.3144 72.2554 18.8601 71.2093 17.7675L57.5349 3.48471C56.4276 2.32814 54.8959 1.67408 53.2947 1.67408L18.8335 1.67407C15.5915 1.67407 12.9633 4.30225 12.9633 7.54427L12.9633 70.9423C12.9633 74.1844 15.5915 76.8125 18.8335 76.8125Z" stroke-width="1.76106"/>
|
||||
<path class="tw-stroke-art-primary" d="M54.3484 2.26123L54.3484 14.0016C54.3484 17.2436 56.9766 19.8718 60.2186 19.8718L72.546 19.8718" stroke-width="1.76106"/>
|
||||
<path class="tw-stroke-art-accent" d="M20.0914 15.9861L43.5722 15.9861" stroke-width="0.880529" stroke-linecap="round" stroke-dasharray="11.74 4.7"/>
|
||||
<path class="tw-stroke-art-accent" d="M20.0914 30.8945L51.2034 30.8945" stroke-width="0.880529" stroke-linecap="round" stroke-dasharray="11.74 4.7"/>
|
||||
<path class="tw-stroke-art-accent" d="M20.0914 45.803L45.9203 45.803" stroke-width="0.880529" stroke-linecap="round" stroke-dasharray="11.74 4.7"/>
|
||||
<path class="tw-stroke-art-accent" d="M20.0914 60.7112L45.9203 60.7112" stroke-width="0.880529" stroke-linecap="round" stroke-dasharray="11.74 4.7"/>
|
||||
<path class="tw-fill-background tw-stroke-art-primary" d="M85.4233 53.9449C81.9863 66.772 68.6684 74.3484 55.6768 70.8674C42.6853 67.3863 34.9398 54.1659 38.3768 41.3388C41.8138 28.5117 55.1318 20.9353 68.1234 24.4163C81.1149 27.8974 88.8604 41.1178 85.4233 53.9449Z" stroke-width="1.76106" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path class="tw-stroke-art-accent" d="M55.1859 41.5395C55.1859 41.5395 55.2828 39.2314 57.5434 37.273C58.8998 36.084 60.5145 35.7692 61.9678 35.7343C63.2919 35.6993 64.4868 35.9441 65.1649 36.3288C66.3921 36.9583 68.7497 38.462 68.7497 41.7144C68.7497 45.1416 66.6828 46.6804 64.3576 48.394C62.0324 50.1076 62.3667 52.3385 62.3667 54.227" stroke-width="1.76106" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path class="tw-fill-art-accent tw-stroke-secondary-600" d="M62.2727 59.2015C62.759 59.2015 63.1533 58.8073 63.1533 58.321C63.1533 57.8347 62.759 57.4404 62.2727 57.4404C61.7864 57.4404 61.3922 57.8347 61.3922 58.321C61.3922 58.8073 61.7864 59.2015 62.2727 59.2015Z"/>
|
||||
<path class="tw-fill-secondary-300 tw-stroke-art-primary" d="M96.0333 89.0621L95.4703 89.5329C94.2269 90.5728 92.3758 90.4078 91.3359 89.1644L78.2766 73.5488L74.79 69.3798C74.4843 69.0105 74.6096 68.4514 75.0271 68.2155C76.7198 67.2592 78.097 65.9974 78.8894 65.1364C79.1502 64.853 79.6089 64.8477 79.856 65.1431L83.3425 69.3121L96.4018 84.9277C97.4418 86.1712 97.2768 88.0222 96.0333 89.0621Z" stroke-width="1.76106" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
`;
|
||||
@@ -1,23 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const RegistrationCheckEmailIcon = svgIcon`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 100" fill="none">
|
||||
<path class="tw-stroke-art-primary" stroke-linecap="round" stroke-width="2.477"
|
||||
d="M104.552 42.242v12.023M48.415 17.31l6.51-4.858a7.43 7.43 0 0 1 8.984.074l1.172.904M24.844 34.897l-6.857 5.116A7.43 7.43 0 0 0 15 45.97v42.366a7.43 7.43 0 0 0 7.43 7.43h74.691a7.43 7.43 0 0 0 7.431-7.43V64.291" />
|
||||
<path class="tw-stroke-art-primary" stroke-linecap="round" stroke-width="2.477"
|
||||
d="M25.48 50.5V20.058a2.477 2.477 0 0 1 2.476-2.477h32.091" />
|
||||
<path class="tw-stroke-art-accent" stroke-linecap="round" stroke-width="2.477"
|
||||
d="M33.778 27.71h15.674M33.778 37.037h15.217M33.778 46.364h17.728M33.778 55.691h23.206" />
|
||||
<path class="tw-stroke-art-primary" stroke-width="2.477"
|
||||
d="M102.645 93.86 74.211 67.614a12.384 12.384 0 0 0-8.4-3.284H52.41c-3.216 0-6.306 1.251-8.616 3.488L16.904 93.86" />
|
||||
<path class="tw-stroke-art-primary" stroke-linecap="round" stroke-width="2.477" d="m71.36 64.73 8.904-5.106" />
|
||||
<path class="tw-stroke-art-primary" stroke-linecap="round" stroke-width="2.477" d="M1.238-1.238h37.47"
|
||||
transform="matrix(.85856 .5127 .51278 -.85852 15.476 43.85)" />
|
||||
<path class="tw-stroke-art-primary" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.477"
|
||||
d="M104.722 34.286c0 13.922-11.286 25.207-25.209 25.207-13.922 0-25.208-11.285-25.208-25.207 0-13.92 11.286-25.206 25.208-25.206 13.923 0 25.209 11.285 25.209 25.206Z" />
|
||||
<path class="tw-stroke-art-accent" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.238"
|
||||
d="M101.346 34.287c0 11.972-9.82 21.678-21.932 21.678m0-43.355c-12.113 0-21.932 9.705-21.932 21.677" />
|
||||
<path class="tw-stroke-art-primary" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.477"
|
||||
d="m99.636 49.689 3.853 3.852 14.322 14.32a3.096 3.096 0 0 1 0 4.379l-.303.303a3.097 3.097 0 0 1-4.379 0l-14.322-14.32-3.852-3.853" />
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,18 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const Search = svgIcon`
|
||||
<svg width="120" height="120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity=".49">
|
||||
<path class="tw-fill-secondary-300" fill-rule="evenodd" clip-rule="evenodd" d="M40.36 73.256a30.004 30.004 0 0 0 10.346 1.826c16.282 0 29.482-12.912 29.482-28.84 0-.384-.008-.766-.023-1.145h28.726v39.57H40.36v-11.41Z" />
|
||||
<path class="tw-stroke-secondary-600" d="M21.546 46.241c0 15.929 13.2 28.841 29.482 28.841S80.51 62.17 80.51 46.241c0-15.928-13.2-28.841-29.482-28.841S21.546 30.313 21.546 46.241Z" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path class="tw-fill-secondary-600" d="M35.36 70.595a1.2 1.2 0 0 0-2.4 0h2.4Zm77.475-30.356a2.343 2.343 0 0 1 2.365 2.33h2.4c0-2.593-2.107-4.73-4.765-4.73v2.4Zm2.365 2.33v46.047h2.4V42.57h-2.4Zm0 46.047c0 1.293-1.058 2.33-2.365 2.33v2.4c2.59 0 4.765-2.069 4.765-4.73h-2.4Zm-2.365 2.33h-75.11v2.4h75.11v-2.4Zm-75.11 0a2.343 2.343 0 0 1-2.365-2.33h-2.4c0 2.594 2.107 4.73 4.766 4.73v-2.4Zm-2.365-2.33v-18.02h-2.4v18.02h2.4Zm44.508-48.377h32.967v-2.4H79.868v2.4Z" />
|
||||
<path class="tw-stroke-secondary-600" d="M79.907 45.287h29.114v39.57H40.487V73.051" stroke-width="2" stroke-linejoin="round" />
|
||||
<path class="tw-stroke-secondary-600" d="M57.356 102.56h35.849" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path class="tw-stroke-secondary-600" d="M68.954 92.147v10.413m11.599-10.413v10.413" stroke-width="4" stroke-linejoin="round" />
|
||||
<path class="tw-stroke-secondary-600" d="m27.44 64.945-4.51 4.51L5.72 86.663a3 3 0 0 0 0 4.243l1.238 1.238a3 3 0 0 0 4.243 0L28.41 74.936l4.51-4.51" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path class="tw-stroke-secondary-600" d="M101.293 53.154H85.178m16.115 6.043H90.214m-5.036 0h-7.553m23.668 6.043h-7.05m-5.54 0h-15.61m28.2 6.042H85.178m-5.538 0h-8.562m30.215 6.043H78.632m-5.539 0H60m-5.54 0h-8.057" stroke-width="2" stroke-linecap="round" />
|
||||
<path class="tw-stroke-secondary-600" d="M29.164 33.01h41.529a2.4 2.4 0 0 1 2.4 2.4v6.28a2.4 2.4 0 0 1-2.4 2.4h-41.53a2.4 2.4 0 0 1-2.4-2.4v-6.28a2.4 2.4 0 0 1 2.4-2.4Z" stroke-width="4" />
|
||||
<path class="tw-stroke-secondary-600" d="M22.735 54.16h34.361a2.4 2.4 0 0 1 2.4 2.4v6.28a2.4 2.4 0 0 1-2.4 2.4H28.778m50.358-11.08h-6.161a2.4 2.4 0 0 0-2.4 2.4v6.414a2.266 2.266 0 0 0 2.266 2.265" stroke-width="4" stroke-linecap="round" />
|
||||
</g>
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,50 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const Security = svgIcon`
|
||||
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="96" height="96" class="tw-fill-background"/>
|
||||
<rect x="5" y="5" width="86" height="77" rx="7" class="tw-stroke-art-primary" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="63" y="15" width="18" height="18" rx="3" class="tw-stroke-art-primary" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="39" y="15" width="18" height="18" rx="3" class="tw-stroke-art-primary" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="15" y="15" width="18" height="18" rx="3" class="tw-stroke-art-primary" stroke-width="2" stroke-linecap="round"/>
|
||||
<rect x="13" y="41" width="70" height="14" rx="7" class="tw-stroke-art-primary tw-fill-background" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M21.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0039 48.3525L23.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0039 48.3524L22.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0029 48.3524L19.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.0022 48.3525L18.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0039 48.3525L32.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0039 48.3524L31.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0029 48.3524L28.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M30.0022 48.3525L27.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0039 48.3525L41.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0039 48.3524L40.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0029 48.3524L37.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M39.0022 48.3525L36.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0039 48.3525L50.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0039 48.3524L49.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0029 48.3524L46.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0022 48.3525L45.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0039 48.3525L59.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0039 48.3524L58.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0029 48.3524L55.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M57.0022 48.3525L54.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0039 48.3525L68.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0039 48.3524L67.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0029 48.3524L64.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.0022 48.3525L63.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0039 48.3526V45.5728" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0039 48.3525L77.6272 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0039 48.3524L76.6279 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0029 48.3524L73.3789 50.6268" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M75.0022 48.3525L72.3789 47.4933" class="tw-stroke-art-accent" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="35" y="72" width="26" height="20" rx="2" class="tw-stroke-art-primary tw-fill-background" stroke-width="2"/>
|
||||
<rect x="47" y="78" width="2" height="8" rx="1" class="tw-stroke-art-accent"/>
|
||||
<path d="M55 71V69C55 65.134 51.866 62 48 62V62C44.134 62 41 65.134 41 69V71" class="tw-stroke-art-primary" stroke-width="2"/>
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const SendInactive = svgIcon`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.26134 15.8194L3.32764 13.1612C2.24617 12.6767 2.2182 11.1515 3.28118 10.6277L19.4762 2.6472C20.5111 2.13723 21.6841 3.02766 21.471 4.16154L18.5696 19.6026C18.3851 20.5849 17.2603 21.0629 16.4249 20.5141L14.1152 18.9965L11.7106 21.1294C11.4413 21.3683 11.0937 21.5003 10.7337 21.5003C9.92052 21.5003 9.26134 20.8411 9.26134 20.0279V15.8194ZM4.13499 11.8792L19.9599 4.08112L17.1231 19.178L10.8804 15.0764L15.5129 10.6535C15.8125 10.3675 15.8235 9.89271 15.5374 9.59312C15.2514 9.29353 14.7767 9.28254 14.4771 9.56857L9.52695 14.2947L4.13499 11.8792ZM12.8167 18.1433L10.7613 16.7929V19.9664L12.8167 18.1433Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
export const SendActive = svgIcon`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0621 20.2221V14.7319L15.4504 9.56824L20.2795 3.68408L17.32 19.6084L13.9377 17.5259L10.9361 20.2221H10.0621Z" class="tw-fill-primary-100" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.26134 15.8194L3.32764 13.1612C2.24617 12.6767 2.2182 11.1515 3.28118 10.6277L19.4762 2.6472C20.5111 2.13723 21.6841 3.02766 21.471 4.16154L18.5696 19.6026C18.3851 20.5849 17.2603 21.0629 16.4249 20.5141L14.1152 18.9965L11.7106 21.1294C11.4413 21.3683 11.0937 21.5003 10.7337 21.5003C9.92052 21.5003 9.26134 20.8411 9.26134 20.0279V15.8194ZM4.13499 11.8792L19.9599 4.08112L17.1231 19.178L10.8804 15.0764L15.5129 10.6535C15.8125 10.3675 15.8235 9.89271 15.5374 9.59312C15.2514 9.29353 14.7767 9.28254 14.4771 9.56857L9.52695 14.2947L4.13499 11.8792ZM12.8167 18.1433L10.7613 16.7929V19.9664L12.8167 18.1433Z" class="tw-fill-primary-600" />
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,16 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const SettingsInactive = svgIcon`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12ZM14.5 12C14.5 13.3807 13.3807 14.5 12 14.5C10.6193 14.5 9.5 13.3807 9.5 12C9.5 10.6193 10.6193 9.5 12 9.5C13.3807 9.5 14.5 10.6193 14.5 12Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.8618 4.17107L14.6161 2.69633C14.5491 2.29451 14.2014 2 13.7941 2H10.2059C9.79857 2 9.45091 2.29451 9.38394 2.69633L9.13815 4.17107C8.22425 4.50524 7.38523 4.99487 6.6531 5.60794L5.25079 5.08259C4.86931 4.93967 4.44043 5.0935 4.23675 5.44629L2.44269 8.55369C2.239 8.90648 2.32023 9.35482 2.63473 9.61373L3.78982 10.5646C3.70886 11.0309 3.66666 11.5105 3.66666 12C3.66666 12.4895 3.70886 12.969 3.78981 13.4354L2.63473 14.3863C2.32023 14.6452 2.239 15.0935 2.44269 15.4463L4.23675 18.5537C4.44043 18.9065 4.86931 19.0603 5.25079 18.9174L6.65308 18.392C7.38521 19.0051 8.22424 19.4948 9.13815 19.8289L9.38394 21.3037C9.45091 21.7055 9.79857 22 10.2059 22H13.7941C14.2014 22 14.5491 21.7055 14.6161 21.3037L14.8618 19.8289C15.7757 19.4948 16.6148 19.0051 17.3469 18.3921L18.7492 18.9174C19.1306 19.0603 19.5595 18.9065 19.7632 18.5537L21.5573 15.4463C21.7609 15.0935 21.6797 14.6452 21.3652 14.3863L20.2102 13.4354C20.2911 12.9691 20.3333 12.4895 20.3333 12C20.3333 11.5105 20.2911 11.0309 20.2102 10.5646L21.3652 9.61373C21.6797 9.35482 21.7609 8.90648 21.5573 8.55369L19.7632 5.44629C19.5595 5.0935 19.1306 4.93967 18.7492 5.08259L17.3469 5.60793C16.6148 4.99486 15.7757 4.50524 14.8618 4.17107ZM14.3467 5.57985L13.5259 5.27973L13.2293 3.5H10.7707L10.4741 5.27973L9.65327 5.57985C8.90522 5.85338 8.21727 6.25458 7.61612 6.75798L6.94571 7.31937L5.25341 6.68538L4.0241 8.8146L5.417 9.96128L5.26771 10.8212C5.2014 11.2031 5.16666 11.5969 5.16666 12C5.16666 12.4031 5.2014 12.7968 5.26771 13.1788L5.417 14.0387L4.0241 15.1854L5.25341 17.3146L6.94569 16.6806L7.6161 17.242C8.21726 17.7454 8.90521 18.1466 9.65327 18.4201L10.4741 18.7203L10.7707 20.5H13.2293L13.5259 18.7203L14.3467 18.4201C15.0948 18.1466 15.7827 17.7454 16.3839 17.242L17.0543 16.6806L18.7465 17.3146L19.9758 15.1854L18.583 14.0387L18.7323 13.1788C18.7986 12.7969 18.8333 12.4031 18.8333 12C18.8333 11.5969 18.7986 11.2031 18.7323 10.8212L18.583 9.96125L19.9758 8.8146L18.7465 6.68538L17.0543 7.31936L16.3839 6.75797C15.7827 6.25458 15.0948 5.85337 14.3467 5.57985ZM20.2582 14.6963L20.2577 14.6973L20.2582 14.6963ZM13.1365 2.94293C13.1364 2.94267 13.1365 2.9432 13.1365 2.94293V2.94293Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
export const SettingsActive = svgIcon`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.9672 5.03286C19.2837 5.00445 19.5977 5.15974 19.7632 5.44632L21.5573 8.55372C21.7609 8.90651 21.6797 9.35485 21.3652 9.61376L20.2102 10.5646C20.2911 11.0309 20.3333 11.5106 20.3333 12C20.3333 12.4895 20.2911 12.9691 20.2102 13.4354L21.3652 14.3863C21.6797 14.6452 21.7609 15.0935 21.5573 15.4463L19.7632 18.5537C19.5595 18.9065 19.1306 19.0603 18.7492 18.9174L17.3469 18.3921C16.6148 19.0052 15.7757 19.4948 14.8618 19.829L14.6161 21.3037C14.5491 21.7055 14.2014 22 13.7941 22H10.2059C9.79857 22 9.45091 21.7055 9.38394 21.3037L9.13815 19.829C8.22424 19.4948 7.38521 19.0052 6.65308 18.3921L5.25079 18.9174C5.17921 18.9442 5.10596 18.9606 5.03284 18.9672L10.2322 13.7678C10.6846 14.2202 11.3096 14.5 12 14.5C13.3807 14.5 14.5 13.3807 14.5 12C14.5 11.3097 14.2202 10.6847 13.7678 10.2323L18.9672 5.03286Z" class="tw-fill-primary-100" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12ZM14.5 12C14.5 13.3807 13.3807 14.5 12 14.5C10.6193 14.5 9.5 13.3807 9.5 12C9.5 10.6193 10.6193 9.5 12 9.5C13.3807 9.5 14.5 10.6193 14.5 12Z" class="tw-fill-primary-600" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.8618 4.17107L14.6161 2.69633C14.5491 2.29451 14.2014 2 13.7941 2H10.2059C9.79857 2 9.45091 2.29451 9.38394 2.69633L9.13815 4.17107C8.22425 4.50524 7.38523 4.99487 6.6531 5.60794L5.25079 5.08259C4.86931 4.93967 4.44043 5.0935 4.23675 5.44629L2.44269 8.55369C2.239 8.90648 2.32023 9.35482 2.63473 9.61373L3.78982 10.5646C3.70886 11.0309 3.66666 11.5105 3.66666 12C3.66666 12.4895 3.70886 12.969 3.78981 13.4354L2.63473 14.3863C2.32023 14.6452 2.239 15.0935 2.44269 15.4463L4.23675 18.5537C4.44043 18.9065 4.86931 19.0603 5.25079 18.9174L6.65308 18.392C7.38521 19.0051 8.22424 19.4948 9.13815 19.8289L9.38394 21.3037C9.45091 21.7055 9.79857 22 10.2059 22H13.7941C14.2014 22 14.5491 21.7055 14.6161 21.3037L14.8618 19.8289C15.7757 19.4948 16.6148 19.0051 17.3469 18.3921L18.7492 18.9174C19.1306 19.0603 19.5595 18.9065 19.7632 18.5537L21.5573 15.4463C21.7609 15.0935 21.6797 14.6452 21.3652 14.3863L20.2102 13.4354C20.2911 12.9691 20.3333 12.4895 20.3333 12C20.3333 11.5105 20.2911 11.0309 20.2102 10.5646L21.3652 9.61373C21.6797 9.35482 21.7609 8.90648 21.5573 8.55369L19.7632 5.44629C19.5595 5.0935 19.1306 4.93967 18.7492 5.08259L17.3469 5.60793C16.6148 4.99486 15.7757 4.50524 14.8618 4.17107ZM14.3467 5.57985L13.5259 5.27973L13.2293 3.5H10.7707L10.4741 5.27973L9.65327 5.57985C8.90522 5.85338 8.21727 6.25458 7.61612 6.75798L6.94571 7.31937L5.25341 6.68538L4.0241 8.8146L5.417 9.96128L5.26771 10.8212C5.2014 11.2031 5.16666 11.5969 5.16666 12C5.16666 12.4031 5.2014 12.7968 5.26771 13.1788L5.417 14.0387L4.0241 15.1854L5.25341 17.3146L6.94569 16.6806L7.6161 17.242C8.21726 17.7454 8.90521 18.1466 9.65327 18.4201L10.4741 18.7203L10.7707 20.5H13.2293L13.5259 18.7203L14.3467 18.4201C15.0948 18.1466 15.7827 17.7454 16.3839 17.242L17.0543 16.6806L18.7465 17.3146L19.9758 15.1854L18.583 14.0387L18.7323 13.1788C18.7986 12.7969 18.8333 12.4031 18.8333 12C18.8333 11.5969 18.7986 11.2031 18.7323 10.8212L18.583 9.96125L19.9758 8.8146L18.7465 6.68538L17.0543 7.31936L16.3839 6.75797C15.7827 6.25458 15.0948 5.85337 14.3467 5.57985ZM20.2582 14.6963L20.2577 14.6973L20.2582 14.6963ZM13.1365 2.94293C13.1364 2.94267 13.1365 2.9432 13.1365 2.94293V2.94293Z" class="tw-fill-primary-600" />
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,16 +0,0 @@
|
||||
import { svgIcon } from "../icon";
|
||||
|
||||
export const VaultInactive = svgIcon`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.16151 14.7066C7.57847 15.1817 8.12811 15.5302 8.75 15.6897V16.5488C8.75 16.963 9.08579 17.2988 9.5 17.2988C9.91421 17.2988 10.25 16.963 10.25 16.5488V15.6993C10.881 15.5462 11.4395 15.199 11.8635 14.7222L12.6407 15.1704C12.9995 15.3773 13.4584 15.2544 13.6657 14.8957C13.873 14.5371 13.7501 14.0786 13.3913 13.8717L12.5794 13.4035C12.6587 13.1163 12.7012 12.8131 12.7012 12.4997C12.7012 12.1815 12.6574 11.874 12.5758 11.583L13.3929 11.1118C13.7517 10.9048 13.8745 10.4464 13.6673 10.0877C13.46 9.72912 13.0011 9.60616 12.6423 9.8131L11.8547 10.2673C11.4318 9.79552 10.8766 9.45211 10.25 9.30002V8.43391C10.25 8.0197 9.91421 7.68391 9.5 7.68391C9.08579 7.68391 8.75 8.0197 8.75 8.43391V9.30968C8.13244 9.46809 7.58613 9.81284 7.17024 10.2828L6.35577 9.8131C5.99696 9.60616 5.53805 9.72912 5.33077 10.0877C5.1235 10.4464 5.24635 10.9048 5.60516 11.1118L6.45732 11.6032C6.37929 11.8882 6.33754 12.1889 6.33754 12.4997C6.33754 12.8058 6.37804 13.1021 6.45381 13.3832L5.60676 13.8717C5.24794 14.0786 5.12509 14.5371 5.33237 14.8957C5.53964 15.2544 5.99855 15.3773 6.35737 15.1704L7.16151 14.7066ZM7.83754 12.4997C7.83754 13.5327 8.636 14.2864 9.51935 14.2864C10.4027 14.2864 11.2012 13.5327 11.2012 12.4997C11.2012 11.4667 10.4027 10.713 9.51935 10.713C8.636 10.713 7.83754 11.4667 7.83754 12.4997Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7 5C21.418 5 22 5.58203 22 6.3V18.6838C22 19.4018 21.418 19.9838 20.7 19.9838H19.3105V20.2957C19.3105 20.7099 18.9748 21.0457 18.5605 21.0457C18.1463 21.0457 17.8105 20.7099 17.8105 20.2957V19.9838H6.18555V20.2957C6.18555 20.7099 5.84976 21.0457 5.43555 21.0457C5.02133 21.0457 4.68555 20.7099 4.68555 20.2957V19.9838H3.3C2.58203 19.9838 2 19.4018 2 18.6838V6.3C2 5.58203 2.58203 5 3.3 5H20.7ZM20.5 16.0509V18.4838H3.5V6.5H20.5V8.93202H19.1875C18.7733 8.93202 18.4375 9.26781 18.4375 9.68202C18.4375 10.0962 18.7733 10.432 19.1875 10.432H20.5V14.5509H19.1875C18.7733 14.5509 18.4375 14.8867 18.4375 15.3009C18.4375 15.7152 18.7733 16.0509 19.1875 16.0509H20.5Z" class="tw-fill-secondary-600 group-hover/tab-nav-btn:tw-fill-primary-600" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
export const VaultActive = svgIcon`
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.5034 18.4716H3.50201L9.67797 14.294C10.3648 14.1965 10.7743 13.8378 10.9689 13.4272L20.5034 6.47314V18.4716Z" class="tw-fill-primary-100" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.16151 14.7066C7.57847 15.1817 8.12811 15.5302 8.75 15.6897V16.5488C8.75 16.963 9.08579 17.2988 9.5 17.2988C9.91421 17.2988 10.25 16.963 10.25 16.5488V15.6993C10.881 15.5462 11.4395 15.199 11.8635 14.7222L12.6407 15.1704C12.9995 15.3773 13.4584 15.2544 13.6657 14.8957C13.873 14.5371 13.7501 14.0786 13.3913 13.8717L12.5794 13.4035C12.6587 13.1163 12.7012 12.8131 12.7012 12.4997C12.7012 12.1815 12.6574 11.874 12.5758 11.583L13.3929 11.1118C13.7517 10.9048 13.8745 10.4464 13.6673 10.0877C13.46 9.72912 13.0011 9.60616 12.6423 9.8131L11.8547 10.2673C11.4318 9.79552 10.8766 9.45211 10.25 9.30002V8.43391C10.25 8.0197 9.91421 7.68391 9.5 7.68391C9.08579 7.68391 8.75 8.0197 8.75 8.43391V9.30968C8.13244 9.46809 7.58613 9.81284 7.17024 10.2828L6.35577 9.8131C5.99696 9.60616 5.53805 9.72912 5.33077 10.0877C5.1235 10.4464 5.24635 10.9048 5.60516 11.1118L6.45732 11.6032C6.37929 11.8882 6.33754 12.1889 6.33754 12.4997C6.33754 12.8058 6.37804 13.1021 6.45381 13.3832L5.60676 13.8717C5.24794 14.0786 5.12509 14.5371 5.33237 14.8957C5.53964 15.2544 5.99855 15.3773 6.35737 15.1704L7.16151 14.7066ZM7.83754 12.4997C7.83754 13.5327 8.636 14.2864 9.51935 14.2864C10.4027 14.2864 11.2012 13.5327 11.2012 12.4997C11.2012 11.4667 10.4027 10.713 9.51935 10.713C8.636 10.713 7.83754 11.4667 7.83754 12.4997Z" class="tw-fill-primary-600" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7 5C21.418 5 22 5.58203 22 6.3V18.6838C22 19.4018 21.418 19.9838 20.7 19.9838H19.3105V20.2957C19.3105 20.7099 18.9748 21.0457 18.5605 21.0457C18.1463 21.0457 17.8105 20.7099 17.8105 20.2957V19.9838H6.18555V20.2957C6.18555 20.7099 5.84976 21.0457 5.43555 21.0457C5.02133 21.0457 4.68555 20.7099 4.68555 20.2957V19.9838H3.3C2.58203 19.9838 2 19.4018 2 18.6838V6.3C2 5.58203 2.58203 5 3.3 5H20.7ZM20.5 16.0509V18.4838H3.5V6.5H20.5V8.93202H19.1875C18.7733 8.93202 18.4375 9.26781 18.4375 9.68202C18.4375 10.0962 18.7733 10.432 19.1875 10.432H20.5V14.5509H19.1875C18.7733 14.5509 18.4375 14.8867 18.4375 15.3009C18.4375 15.7152 18.7733 16.0509 19.1875 16.0509H20.5Z" class="tw-fill-primary-600" />
|
||||
</svg>
|
||||
`;
|
||||
@@ -1,10 +1 @@
|
||||
export * from "./icon.module";
|
||||
export * from "./icon";
|
||||
export * as Icons from "./icons";
|
||||
export {
|
||||
AdminConsoleLogo,
|
||||
BusinessUnitPortalLogo,
|
||||
PasswordManagerLogo,
|
||||
ProviderPortalLogo,
|
||||
SecretsManagerLogo,
|
||||
} from "./logos";
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +0,0 @@
|
||||
export { default as AdminConsoleLogo } from "./admin-console";
|
||||
export { default as BusinessUnitPortalLogo } from "./business-unit-portal";
|
||||
export * from "./shield";
|
||||
export { default as PasswordManagerLogo } from "./password-manager";
|
||||
export { default as ProviderPortalLogo } from "./provider-portal";
|
||||
export { default as SecretsManagerLogo } from "./secrets-manager";
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,16 +0,0 @@
|
||||
import { svgIcon } from "../../icon";
|
||||
|
||||
/**
|
||||
* Shield logo with extra space in the viewbox.
|
||||
*/
|
||||
const AnonLayoutBitwardenShield = svgIcon`
|
||||
<svg viewBox="0 0 120 132" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="tw-fill-marketing-logo" d="M82.2944 69.1899V37.2898H60V93.9624C63.948 91.869 67.4812 89.5927 70.5998 87.1338C78.3962 81.0196 82.2944 75.0383 82.2944 69.1899ZM91.8491 30.9097V69.1899C91.8491 72.0477 91.2934 74.8805 90.182 77.6883C89.0706 80.4962 87.6938 82.9884 86.0516 85.1649C84.4094 87.3415 82.452 89.4598 80.1794 91.5201C77.9068 93.5803 75.8084 95.2916 73.8842 96.654C71.96 98.0164 69.9528 99.304 67.8627 100.517C65.7726 101.73 64.288 102.552 63.4088 102.984C62.5297 103.416 61.8247 103.748 61.2939 103.981C60.8958 104.18 60.4645 104.28 60 104.28C59.5355 104.28 59.1042 104.18 58.7061 103.981C58.1753 103.748 57.4703 103.416 56.5911 102.984C55.712 102.552 54.2273 101.73 52.1372 100.517C50.0471 99.304 48.04 98.0164 46.1158 96.654C44.1916 95.2916 42.0932 93.5803 39.8206 91.5201C37.548 89.4598 35.5906 87.3415 33.9484 85.1649C32.3062 82.9884 30.9294 80.4962 29.818 77.6883C28.7066 74.8805 28.1509 72.0477 28.1509 69.1899V30.9097C28.1509 30.0458 28.4661 29.2981 29.0964 28.6668C29.7267 28.0354 30.4732 27.7197 31.3358 27.7197H88.6642C89.5268 27.7197 90.2732 28.0354 90.9036 28.6668C91.5339 29.2981 91.8491 30.0458 91.8491 30.9097Z" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
const BitwardenShield = svgIcon`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="32" fill="none"><g clip-path="url(#a)"><path class="tw-fill-text-alt2" d="M22.01 17.055V4.135h-9.063v22.954c1.605-.848 3.041-1.77 4.31-2.766 3.169-2.476 4.753-4.899 4.753-7.268Zm3.884-15.504v15.504a9.256 9.256 0 0 1-.677 3.442 12.828 12.828 0 0 1-1.68 3.029 18.708 18.708 0 0 1-2.386 2.574 27.808 27.808 0 0 1-2.56 2.08 32.251 32.251 0 0 1-2.448 1.564c-.85.49-1.453.824-1.81.999-.357.175-.644.31-.86.404-.162.08-.337.12-.526.12s-.364-.04-.526-.12a22.99 22.99 0 0 1-.86-.404c-.357-.175-.96-.508-1.81-1a32.242 32.242 0 0 1-2.448-1.564 27.796 27.796 0 0 1-2.56-2.08 18.706 18.706 0 0 1-2.386-2.573 12.828 12.828 0 0 1-1.68-3.029A9.256 9.256 0 0 1 0 17.055V1.551C0 1.2.128.898.384.642.641.386.944.26 1.294.26H24.6c.35 0 .654.127.91.383s.384.559.384.909Z"/></g><defs><clipPath id="a"><path class="tw-fill-text-alt2" d="M0 0h26v32H0z"/></clipPath></defs></svg>
|
||||
`;
|
||||
|
||||
export { AnonLayoutBitwardenShield, BitwardenShield };
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./bitwarden";
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
AfterContentChecked,
|
||||
booleanAttribute,
|
||||
@@ -73,11 +71,13 @@ export class AutofocusDirective implements AfterContentChecked {
|
||||
private focus() {
|
||||
const el = this.getElement();
|
||||
|
||||
el.focus();
|
||||
this.focused = el === document.activeElement;
|
||||
if (el) {
|
||||
el.focus();
|
||||
this.focused = el === document.activeElement;
|
||||
}
|
||||
}
|
||||
|
||||
private getElement() {
|
||||
private getElement(): HTMLElement | undefined {
|
||||
if (this.focusableElement) {
|
||||
return this.focusableElement.getFocusTarget();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
Directive,
|
||||
ElementRef,
|
||||
@@ -63,7 +61,7 @@ export class BitInputDirective implements BitFormFieldControl {
|
||||
|
||||
readonly id = input(`bit-input-${nextId++}`);
|
||||
|
||||
@HostBinding("attr.aria-describedby") ariaDescribedBy: string;
|
||||
@HostBinding("attr.aria-describedby") ariaDescribedBy?: string;
|
||||
|
||||
@HostBinding("attr.aria-invalid") get ariaInvalid() {
|
||||
return this.hasError ? true : undefined;
|
||||
@@ -83,7 +81,7 @@ export class BitInputDirective implements BitFormFieldControl {
|
||||
set required(value: any) {
|
||||
this._required = value != null && value !== false;
|
||||
}
|
||||
private _required: boolean;
|
||||
private _required?: boolean;
|
||||
|
||||
readonly hasPrefix = input(false);
|
||||
readonly hasSuffix = input(false);
|
||||
@@ -101,19 +99,20 @@ export class BitInputDirective implements BitFormFieldControl {
|
||||
|
||||
get hasError() {
|
||||
if (this.showErrorsWhenDisabled()) {
|
||||
return (
|
||||
return !!(
|
||||
(this.ngControl?.status === "INVALID" || this.ngControl?.status === "DISABLED") &&
|
||||
this.ngControl?.touched &&
|
||||
this.ngControl?.errors != null
|
||||
);
|
||||
} else {
|
||||
return this.ngControl?.status === "INVALID" && this.ngControl?.touched;
|
||||
return !!(this.ngControl?.status === "INVALID" && this.ngControl?.touched);
|
||||
}
|
||||
}
|
||||
|
||||
get error(): [string, any] {
|
||||
const key = Object.keys(this.ngControl.errors)[0];
|
||||
return [key, this.ngControl.errors[key]];
|
||||
const errors = this.ngControl.errors ?? {};
|
||||
const key = Object.keys(errors)[0];
|
||||
return [key, errors[key]];
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
import { NgClass } from "@angular/common";
|
||||
import {
|
||||
AfterContentChecked,
|
||||
@@ -8,8 +5,8 @@ import {
|
||||
Component,
|
||||
ElementRef,
|
||||
signal,
|
||||
ViewChild,
|
||||
input,
|
||||
viewChild,
|
||||
} from "@angular/core";
|
||||
|
||||
import { TypographyModule } from "../typography";
|
||||
@@ -30,7 +27,7 @@ import { TypographyModule } from "../typography";
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ItemContentComponent implements AfterContentChecked {
|
||||
@ViewChild("endSlot") endSlot: ElementRef<HTMLDivElement>;
|
||||
readonly endSlot = viewChild<ElementRef<HTMLDivElement>>("endSlot");
|
||||
|
||||
protected endSlotHasChildren = signal(false);
|
||||
|
||||
@@ -42,6 +39,6 @@ export class ItemContentComponent implements AfterContentChecked {
|
||||
readonly truncate = input(true);
|
||||
|
||||
ngAfterContentChecked(): void {
|
||||
this.endSlotHasChildren.set(this.endSlot?.nativeElement.childElementCount > 0);
|
||||
this.endSlotHasChildren.set((this.endSlot()?.nativeElement.childElementCount ?? 0) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +102,10 @@ Actions are commonly icon buttons or badge buttons.
|
||||
<button type="button" bitBadge variant="primary">Auto-fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" aria-label="Copy"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" label="Copy"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" aria-label="Options"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" label="Options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
|
||||
@@ -77,10 +77,10 @@ export const Default: Story = {
|
||||
<button type="button" bitBadge variant="primary">Fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -150,10 +150,10 @@ export const TextOverflowTruncate: Story = {
|
||||
</bit-item-content>
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -173,10 +173,10 @@ export const TextOverflowWrap: Story = {
|
||||
</bit-item-content>
|
||||
<ng-container slot="end">
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -198,10 +198,10 @@ const multipleActionListTemplate = /*html*/ `
|
||||
<button type="button" bitBadge variant="primary">Fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -217,10 +217,10 @@ const multipleActionListTemplate = /*html*/ `
|
||||
<button type="button" bitBadge variant="primary">Fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -236,10 +236,10 @@ const multipleActionListTemplate = /*html*/ `
|
||||
<button type="button" bitBadge variant="primary">Fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -255,10 +255,10 @@ const multipleActionListTemplate = /*html*/ `
|
||||
<button type="button" bitBadge variant="primary">Fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -274,10 +274,10 @@ const multipleActionListTemplate = /*html*/ `
|
||||
<button type="button" bitBadge variant="primary">Fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -293,10 +293,10 @@ const multipleActionListTemplate = /*html*/ `
|
||||
<button type="button" bitBadge variant="primary">Fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -410,10 +410,10 @@ export const VirtualScrolling: Story = {
|
||||
<button type="button" bitBadge variant="primary">Fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" size="small" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" size="small" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
@@ -440,10 +440,10 @@ export const WithoutBorderRadius: Story = {
|
||||
<button type="button" bitBadge variant="primary">Fill</button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-clone"></button>
|
||||
<button type="button" bitIconButton="bwi-clone" label="Clone"></button>
|
||||
</bit-item-action>
|
||||
<bit-item-action>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v"></button>
|
||||
<button type="button" bitIconButton="bwi-ellipsis-v" label="More options"></button>
|
||||
</bit-item-action>
|
||||
</ng-container>
|
||||
</bit-item>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { input, HostBinding, Directive } from "@angular/core";
|
||||
import { input, HostBinding, Directive, inject, ElementRef, booleanAttribute } from "@angular/core";
|
||||
|
||||
import { AriaDisableDirective } from "../a11y";
|
||||
import { ariaDisableElement } from "../utils";
|
||||
|
||||
export type LinkType = "primary" | "secondary" | "contrast" | "light";
|
||||
|
||||
@@ -58,6 +61,11 @@ const commonStyles = [
|
||||
"before:tw-transition",
|
||||
"focus-visible:before:tw-ring-2",
|
||||
"focus-visible:tw-z-10",
|
||||
"aria-disabled:tw-no-underline",
|
||||
"aria-disabled:tw-pointer-events-none",
|
||||
"aria-disabled:!tw-text-secondary-300",
|
||||
"aria-disabled:hover:!tw-text-secondary-300",
|
||||
"aria-disabled:hover:tw-no-underline",
|
||||
];
|
||||
|
||||
@Directive()
|
||||
@@ -86,11 +94,21 @@ export class AnchorLinkDirective extends LinkDirective {
|
||||
|
||||
@Directive({
|
||||
selector: "button[bitLink]",
|
||||
hostDirectives: [AriaDisableDirective],
|
||||
})
|
||||
export class ButtonLinkDirective extends LinkDirective {
|
||||
private el = inject(ElementRef<HTMLButtonElement>);
|
||||
|
||||
disabled = input(false, { transform: booleanAttribute });
|
||||
|
||||
@HostBinding("class") get classList() {
|
||||
return ["before:-tw-inset-y-[0.25rem]"]
|
||||
.concat(commonStyles)
|
||||
.concat(linkStyles[this.linkType()] ?? []);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
ariaDisableElement(this.el.nativeElement, this.disabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div
|
||||
class="tw-my-2 tw-border-0 tw-border-t tw-border-solid tw-border-t-secondary-500"
|
||||
class="tw-my-2 tw-border-0 tw-border-t tw-border-solid tw-border-t-secondary-100"
|
||||
role="separator"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { hasModifierKey } from "@angular/cdk/keycodes";
|
||||
import { Overlay, OverlayConfig, OverlayRef } from "@angular/cdk/overlay";
|
||||
import { TemplatePortal } from "@angular/cdk/portal";
|
||||
@@ -31,9 +29,9 @@ export class MenuTriggerForDirective implements OnDestroy {
|
||||
|
||||
readonly role = input("button");
|
||||
|
||||
readonly menu = input<MenuComponent>(undefined, { alias: "bitMenuTriggerFor" });
|
||||
readonly menu = input.required<MenuComponent>({ alias: "bitMenuTriggerFor" });
|
||||
|
||||
private overlayRef: OverlayRef;
|
||||
private overlayRef: OverlayRef | null = null;
|
||||
private defaultMenuConfig: OverlayConfig = {
|
||||
panelClass: "bit-menu-panel",
|
||||
hasBackdrop: true,
|
||||
@@ -52,8 +50,8 @@ export class MenuTriggerForDirective implements OnDestroy {
|
||||
.withFlexibleDimensions(false)
|
||||
.withPush(true),
|
||||
};
|
||||
private closedEventsSub: Subscription;
|
||||
private keyDownEventsSub: Subscription;
|
||||
private closedEventsSub: Subscription | null = null;
|
||||
private keyDownEventsSub: Subscription | null = null;
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef<HTMLElement>,
|
||||
@@ -78,28 +76,30 @@ export class MenuTriggerForDirective implements OnDestroy {
|
||||
this.isOpen = true;
|
||||
this.overlayRef = this.overlay.create(this.defaultMenuConfig);
|
||||
|
||||
const templatePortal = new TemplatePortal(menu.templateRef, this.viewContainerRef);
|
||||
const templatePortal = new TemplatePortal(menu.templateRef(), this.viewContainerRef);
|
||||
this.overlayRef.attach(templatePortal);
|
||||
|
||||
this.closedEventsSub = this.getClosedEvents().subscribe((event: KeyboardEvent | undefined) => {
|
||||
// Closing the menu is handled in this.destroyMenu, so we want to prevent the escape key
|
||||
// from doing its normal default action, which would otherwise cause a parent component
|
||||
// (like a dialog) or extension window to close
|
||||
if (event?.key === "Escape" && !hasModifierKey(event)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
this.closedEventsSub =
|
||||
this.getClosedEvents()?.subscribe((event: KeyboardEvent | undefined) => {
|
||||
// Closing the menu is handled in this.destroyMenu, so we want to prevent the escape key
|
||||
// from doing its normal default action, which would otherwise cause a parent component
|
||||
// (like a dialog) or extension window to close
|
||||
if (event?.key === "Escape" && !hasModifierKey(event)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event?.key && ["Tab", "Escape"].includes(event.key)) {
|
||||
// Required to ensure tab order resumes correctly
|
||||
this.elementRef.nativeElement.focus();
|
||||
}
|
||||
this.destroyMenu();
|
||||
}) ?? null;
|
||||
|
||||
if (["Tab", "Escape"].includes(event?.key)) {
|
||||
// Required to ensure tab order resumes correctly
|
||||
this.elementRef.nativeElement.focus();
|
||||
}
|
||||
this.destroyMenu();
|
||||
});
|
||||
if (menu.keyManager) {
|
||||
menu.keyManager.setFirstItemActive();
|
||||
this.keyDownEventsSub = this.overlayRef
|
||||
.keydownEvents()
|
||||
.subscribe((event: KeyboardEvent) => this.menu().keyManager.onKeydown(event));
|
||||
.subscribe((event: KeyboardEvent) => this.menu().keyManager?.onKeydown(event));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,10 @@ export class MenuTriggerForDirective implements OnDestroy {
|
||||
this.menu().closed.emit();
|
||||
}
|
||||
|
||||
private getClosedEvents(): Observable<any> {
|
||||
private getClosedEvents(): Observable<any> | null {
|
||||
if (!this.overlayRef) {
|
||||
return null;
|
||||
}
|
||||
const detachments = this.overlayRef.detachments();
|
||||
const escKey = this.overlayRef.keydownEvents().pipe(
|
||||
filter((event: KeyboardEvent) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<ng-template>
|
||||
<div
|
||||
(click)="closed.emit()"
|
||||
class="tw-flex tw-shrink-0 tw-flex-col tw-rounded-lg tw-border tw-border-solid tw-border-secondary-500 tw-bg-background tw-bg-clip-padding tw-py-1 tw-overflow-y-auto"
|
||||
class="tw-flex tw-shrink-0 tw-flex-col tw-rounded-lg tw-border tw-border-solid tw-border-secondary-100 tw-bg-background tw-shadow-md tw-bg-clip-padding tw-py-1 tw-overflow-y-auto"
|
||||
[attr.role]="ariaRole()"
|
||||
[attr.aria-label]="ariaLabel()"
|
||||
cdkTrapFocus
|
||||
|
||||
@@ -58,6 +58,14 @@ describe("Menu", () => {
|
||||
|
||||
expect(getBitMenuPanel()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should not open when the trigger button is disabled", () => {
|
||||
const buttonDebugElement = fixture.debugElement.query(By.directive(MenuTriggerForDirective));
|
||||
buttonDebugElement.nativeElement.setAttribute("disabled", "true");
|
||||
(buttonDebugElement.nativeElement as HTMLButtonElement).click();
|
||||
|
||||
expect(getBitMenuPanel()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { FocusKeyManager, CdkTrapFocus } from "@angular/cdk/a11y";
|
||||
import {
|
||||
Component,
|
||||
Output,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
EventEmitter,
|
||||
ContentChildren,
|
||||
QueryList,
|
||||
AfterContentInit,
|
||||
input,
|
||||
viewChild,
|
||||
contentChildren,
|
||||
} from "@angular/core";
|
||||
|
||||
import { MenuItemDirective } from "./menu-item.directive";
|
||||
@@ -22,10 +19,9 @@ import { MenuItemDirective } from "./menu-item.directive";
|
||||
imports: [CdkTrapFocus],
|
||||
})
|
||||
export class MenuComponent implements AfterContentInit {
|
||||
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
|
||||
readonly templateRef = viewChild.required(TemplateRef);
|
||||
@Output() closed = new EventEmitter<void>();
|
||||
@ContentChildren(MenuItemDirective, { descendants: true })
|
||||
menuItems: QueryList<MenuItemDirective>;
|
||||
readonly menuItems = contentChildren(MenuItemDirective, { descendants: true });
|
||||
keyManager?: FocusKeyManager<MenuItemDirective>;
|
||||
|
||||
readonly ariaRole = input<"menu" | "dialog">("menu");
|
||||
@@ -34,9 +30,9 @@ export class MenuComponent implements AfterContentInit {
|
||||
|
||||
ngAfterContentInit() {
|
||||
if (this.ariaRole() === "menu") {
|
||||
this.keyManager = new FocusKeyManager(this.menuItems)
|
||||
this.keyManager = new FocusKeyManager(this.menuItems())
|
||||
.withWrap()
|
||||
.skipPredicate((item) => item.disabled);
|
||||
.skipPredicate((item) => !!item.disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const OpenMenu: Story = {
|
||||
|
||||
<div class="tw-h-40">
|
||||
<div class="cdk-overlay-pane bit-menu-panel">
|
||||
<ng-container *ngTemplateOutlet="myMenu.templateRef"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="myMenu.templateRef()"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { hasModifierKey } from "@angular/cdk/keycodes";
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
Optional,
|
||||
@@ -14,6 +11,7 @@ import {
|
||||
input,
|
||||
model,
|
||||
booleanAttribute,
|
||||
viewChild,
|
||||
} from "@angular/core";
|
||||
import {
|
||||
ControlValueAccessor,
|
||||
@@ -48,10 +46,10 @@ let nextId = 0;
|
||||
* This component has been implemented to only support Multi-select list events
|
||||
*/
|
||||
export class MultiSelectComponent implements OnInit, BitFormFieldControl, ControlValueAccessor {
|
||||
@ViewChild(NgSelectComponent) select: NgSelectComponent;
|
||||
readonly select = viewChild.required(NgSelectComponent);
|
||||
|
||||
// Parent component should only pass selectable items (complete list - selected items = baseItems)
|
||||
readonly baseItems = model<SelectItemView[]>();
|
||||
readonly baseItems = model.required<SelectItemView[]>();
|
||||
// Defaults to native ng-select behavior - set to "true" to clear selected items on dropdown close
|
||||
readonly removeSelectedItems = input(false);
|
||||
readonly placeholder = model<string>();
|
||||
@@ -61,10 +59,10 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro
|
||||
@Input({ transform: booleanAttribute }) disabled?: boolean;
|
||||
|
||||
// Internal tracking of selected items
|
||||
protected selectedItems: SelectItemView[];
|
||||
protected selectedItems: SelectItemView[] | null = null;
|
||||
|
||||
// Default values for our implementation
|
||||
loadingText: string;
|
||||
loadingText?: string;
|
||||
|
||||
protected searchInputId = `search-input-${nextId++}`;
|
||||
|
||||
@@ -95,13 +93,14 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro
|
||||
/** Function for customizing keyboard navigation */
|
||||
/** Needs to be arrow function to retain `this` scope. */
|
||||
keyDown = (event: KeyboardEvent) => {
|
||||
if (!this.select.isOpen && event.key === "Enter" && !hasModifierKey(event)) {
|
||||
const select = this.select();
|
||||
if (!select.isOpen && event.key === "Enter" && !hasModifierKey(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.select.isOpen && event.key === "Escape" && !hasModifierKey(event)) {
|
||||
if (select.isOpen && event.key === "Escape" && !hasModifierKey(event)) {
|
||||
this.selectedItems = [];
|
||||
this.select.close();
|
||||
select.close();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
@@ -183,11 +182,11 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro
|
||||
get ariaDescribedBy() {
|
||||
return this._ariaDescribedBy;
|
||||
}
|
||||
set ariaDescribedBy(value: string) {
|
||||
set ariaDescribedBy(value: string | undefined) {
|
||||
this._ariaDescribedBy = value;
|
||||
this.select?.searchInput.nativeElement.setAttribute("aria-describedby", value);
|
||||
this.select()?.searchInput.nativeElement.setAttribute("aria-describedby", value ?? "");
|
||||
}
|
||||
private _ariaDescribedBy: string;
|
||||
private _ariaDescribedBy?: string;
|
||||
|
||||
/**Implemented as part of BitFormFieldControl */
|
||||
get labelForId() {
|
||||
@@ -208,16 +207,17 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro
|
||||
set required(value: any) {
|
||||
this._required = value != null && value !== false;
|
||||
}
|
||||
private _required: boolean;
|
||||
private _required?: boolean;
|
||||
|
||||
/**Implemented as part of BitFormFieldControl */
|
||||
get hasError() {
|
||||
return this.ngControl?.status === "INVALID" && this.ngControl?.touched;
|
||||
return !!(this.ngControl?.status === "INVALID" && this.ngControl?.touched);
|
||||
}
|
||||
|
||||
/**Implemented as part of BitFormFieldControl */
|
||||
get error(): [string, any] {
|
||||
const key = Object.keys(this.ngControl?.errors)[0];
|
||||
return [key, this.ngControl?.errors[key]];
|
||||
const errors = this.ngControl?.errors ?? {};
|
||||
const key = Object.keys(errors)[0];
|
||||
return [key, errors[key]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,19 @@
|
||||
$ng-select-highlight: rgb(var(--color-primary-700)) !default;
|
||||
$ng-select-primary-text: rgb(var(--color-text-main)) !default;
|
||||
$ng-select-disabled-text: rgb(var(--color-secondary-100)) !default;
|
||||
$ng-select-border: rgb(var(--color-secondary-600)) !default;
|
||||
$ng-select-border: rgb(var(--color-secondary-100)) !default;
|
||||
$ng-select-border-radius: 0.5rem !default;
|
||||
$ng-select-bg: rgb(var(--color-background)) !default;
|
||||
$ng-select-selected: transparent !default;
|
||||
$ng-select-selected-text: $ng-select-primary-text !default;
|
||||
|
||||
$ng-select-marked: rgb(var(--color-primary-100)) !default;
|
||||
$ng-select-marked: var(--color-hover-default) !default;
|
||||
$ng-select-marked-text: $ng-select-primary-text !default;
|
||||
|
||||
$ng-select-box-shadow: none !default;
|
||||
$ng-select-dropdown-box-shadow:
|
||||
0 4px 6px -1px rgb(0 0 0 / 0.1),
|
||||
0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
$ng-select-placeholder: rgb(var(--color-text-muted)) !default;
|
||||
$ng-select-height: 100%;
|
||||
$ng-select-value-padding-left: 1rem !default;
|
||||
@@ -248,7 +251,7 @@ $ng-dropdown-shadow: rgb(var(--color-secondary-100)) !default;
|
||||
background-color: $ng-select-dropdown-bg;
|
||||
border: 1px solid $ng-select-dropdown-border;
|
||||
border-radius: $ng-select-border-radius;
|
||||
box-shadow: $ng-select-box-shadow;
|
||||
box-shadow: $ng-select-dropdown-box-shadow;
|
||||
left: 0;
|
||||
&.ng-select-top {
|
||||
bottom: 100%;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, EventEmitter, Output, input } from "@angular/core";
|
||||
import { RouterLink, RouterLinkActive } from "@angular/router";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- 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()"
|
||||
@@ -18,11 +18,10 @@
|
||||
[buttonType]="'nav-contrast'"
|
||||
(click)="toggle($event)"
|
||||
size="small"
|
||||
[title]="'toggleCollapse' | i18n"
|
||||
aria-haspopup="true"
|
||||
[attr.aria-expanded]="open().toString()"
|
||||
[attr.aria-controls]="contentId"
|
||||
[attr.aria-label]="['toggleCollapse' | i18n, text()].join(' ')"
|
||||
[label]="['toggleCollapse' | i18n, text()].join(' ')"
|
||||
></button>
|
||||
</ng-template>
|
||||
<ng-container slot="end">
|
||||
|
||||
@@ -2,14 +2,13 @@ import { CommonModule } from "@angular/common";
|
||||
import {
|
||||
booleanAttribute,
|
||||
Component,
|
||||
ContentChildren,
|
||||
EventEmitter,
|
||||
Optional,
|
||||
Output,
|
||||
QueryList,
|
||||
SkipSelf,
|
||||
input,
|
||||
model,
|
||||
contentChildren,
|
||||
} from "@angular/core";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
@@ -30,10 +29,7 @@ import { SideNavService } from "./side-nav.service";
|
||||
imports: [CommonModule, NavItemComponent, IconButtonModule, I18nPipe],
|
||||
})
|
||||
export class NavGroupComponent extends NavBaseComponent {
|
||||
@ContentChildren(NavBaseComponent, {
|
||||
descendants: true,
|
||||
})
|
||||
nestedNavComponents!: QueryList<NavBaseComponent>;
|
||||
readonly nestedNavComponents = contentChildren(NavBaseComponent, { descendants: true });
|
||||
|
||||
/** When the side nav is open, the parent nav item should not show active styles when open. */
|
||||
protected get parentHideActiveStyles(): boolean {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user