mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
[CL-95] loading spinner (#16363)
* add spiner from previous branch * add loading spinner to button * Add spinner to dialog * Add spinner to icon button * add spinner to multi select component * fix spinner positioning * Add mock i18n in stories where needed * round stroke caps. Update classes * fix ts error * fix broken tests * add missing translation keys to stories * Add mising key for layout * Add mising key for nav group * Add mising key for spotlight * Add mising key for product switcher * Add mising key for dialog service * add translation to copy click story
This commit is contained in:
@@ -124,6 +124,7 @@ export default {
|
|||||||
switchProducts: "Switch Products",
|
switchProducts: "Switch Products",
|
||||||
secureYourInfrastructure: "Secure your infrastructure",
|
secureYourInfrastructure: "Secure your infrastructure",
|
||||||
protectYourFamilyOrBusiness: "Protect your family or business",
|
protectYourFamilyOrBusiness: "Protect your family or business",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const meta: Meta<SpotlightComponent> = {
|
|||||||
useFactory: () => {
|
useFactory: () => {
|
||||||
return new I18nMockService({
|
return new I18nMockService({
|
||||||
close: "Close",
|
close: "Close",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ export default {
|
|||||||
required: "required",
|
required: "required",
|
||||||
inputRequired: "Input is required.",
|
inputRequired: "Input is required.",
|
||||||
inputEmail: "Input is not an email-address.",
|
inputEmail: "Input is not an email-address.",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { action } from "@storybook/addon-actions";
|
|||||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||||
import { delay, of } from "rxjs";
|
import { delay, of } from "rxjs";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
|
|
||||||
import { ButtonModule } from "../button";
|
import { ButtonModule } from "../button";
|
||||||
import { IconButtonModule } from "../icon-button";
|
import { IconButtonModule } from "../icon-button";
|
||||||
|
import { I18nMockService } from "../utils";
|
||||||
|
|
||||||
import { AsyncActionsModule } from "./async-actions.module";
|
import { AsyncActionsModule } from "./async-actions.module";
|
||||||
import { BitActionDirective } from "./bit-action.directive";
|
import { BitActionDirective } from "./bit-action.directive";
|
||||||
@@ -103,6 +105,14 @@ export default {
|
|||||||
error: action("LogService.error"),
|
error: action("LogService.error"),
|
||||||
} as Partial<LogService>,
|
} as Partial<LogService>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
loading: "Loading",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ describe("BannerComponent", () => {
|
|||||||
useFactory: () =>
|
useFactory: () =>
|
||||||
new I18nMockService({
|
new I18nMockService({
|
||||||
close: "Close",
|
close: "Close",
|
||||||
|
loading: "Loading",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export default {
|
|||||||
useFactory: () => {
|
useFactory: () => {
|
||||||
return new I18nMockService({
|
return new I18nMockService({
|
||||||
close: "Close",
|
close: "Close",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export default {
|
|||||||
useFactory: () => {
|
useFactory: () => {
|
||||||
return new I18nMockService({
|
return new I18nMockService({
|
||||||
moreBreadcrumbs: "More breadcrumbs",
|
moreBreadcrumbs: "More breadcrumbs",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
<span [ngClass]="{ 'tw-invisible': showLoadingStyle() }">
|
<span [ngClass]="{ 'tw-invisible': showLoadingStyle() }">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</span>
|
</span>
|
||||||
<span
|
@if (showLoadingStyle()) {
|
||||||
class="tw-absolute tw-inset-0 tw-flex tw-items-center tw-justify-center"
|
<span class="tw-absolute tw-inset-0 tw-flex tw-items-center tw-justify-center">
|
||||||
[ngClass]="{ 'tw-invisible': !showLoadingStyle() }"
|
<bit-spinner size="fill" noColor></bit-spinner>
|
||||||
>
|
</span>
|
||||||
<i class="bwi bwi-spinner bwi-lg bwi-spin" aria-hidden="true"></i>
|
}
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { debounce, interval } from "rxjs";
|
|||||||
|
|
||||||
import { AriaDisableDirective } from "../a11y";
|
import { AriaDisableDirective } from "../a11y";
|
||||||
import { ButtonLikeAbstraction, ButtonType, ButtonSize } from "../shared/button-like.abstraction";
|
import { ButtonLikeAbstraction, ButtonType, ButtonSize } from "../shared/button-like.abstraction";
|
||||||
|
import { SpinnerComponent } from "../spinner";
|
||||||
import { ariaDisableElement } from "../utils";
|
import { ariaDisableElement } from "../utils";
|
||||||
|
|
||||||
const focusRing = [
|
const focusRing = [
|
||||||
@@ -60,7 +61,7 @@ const buttonStyles: Record<ButtonType, string[]> = {
|
|||||||
selector: "button[bitButton], a[bitButton]",
|
selector: "button[bitButton], a[bitButton]",
|
||||||
templateUrl: "button.component.html",
|
templateUrl: "button.component.html",
|
||||||
providers: [{ provide: ButtonLikeAbstraction, useExisting: ButtonComponent }],
|
providers: [{ provide: ButtonLikeAbstraction, useExisting: ButtonComponent }],
|
||||||
imports: [NgClass],
|
imports: [NgClass, SpinnerComponent],
|
||||||
hostDirectives: [AriaDisableDirective],
|
hostDirectives: [AriaDisableDirective],
|
||||||
})
|
})
|
||||||
export class ButtonComponent implements ButtonLikeAbstraction {
|
export class ButtonComponent implements ButtonLikeAbstraction {
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
import { Meta, StoryObj } from "@storybook/angular";
|
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet";
|
import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet";
|
||||||
|
import { I18nMockService } from "../utils/i18n-mock.service";
|
||||||
|
|
||||||
import { ButtonComponent } from "./button.component";
|
import { ButtonComponent } from "./button.component";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Component Library/Button",
|
title: "Component Library/Button",
|
||||||
component: ButtonComponent,
|
component: ButtonComponent,
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () =>
|
||||||
|
new I18nMockService({
|
||||||
|
loading: "Loading",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
args: {
|
args: {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export default {
|
|||||||
success: "Success",
|
success: "Success",
|
||||||
close: "Close",
|
close: "Close",
|
||||||
info: "Info",
|
info: "Info",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export default {
|
|||||||
toggleSideNavigation: "Toggle side navigation",
|
toggleSideNavigation: "Toggle side navigation",
|
||||||
yes: "Yes",
|
yes: "Yes",
|
||||||
no: "No",
|
no: "No",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
>
|
>
|
||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<div class="tw-absolute tw-flex tw-size-full tw-items-center tw-justify-center">
|
<div class="tw-absolute tw-flex tw-size-full tw-items-center tw-justify-center">
|
||||||
<i class="bwi bwi-spinner bwi-spin bwi-lg" [attr.aria-label]="'loading' | i18n"></i>
|
<bit-spinner></bit-spinner>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { combineLatest, switchMap } from "rxjs";
|
|||||||
import { I18nPipe } from "@bitwarden/ui-common";
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
|
|
||||||
import { BitIconButtonComponent } from "../../icon-button/icon-button.component";
|
import { BitIconButtonComponent } from "../../icon-button/icon-button.component";
|
||||||
|
import { SpinnerComponent } from "../../spinner";
|
||||||
import { TypographyDirective } from "../../typography/typography.directive";
|
import { TypographyDirective } from "../../typography/typography.directive";
|
||||||
import { hasScrollableContent$ } from "../../utils/";
|
import { hasScrollableContent$ } from "../../utils/";
|
||||||
import { hasScrolledFrom } from "../../utils/has-scrolled-from";
|
import { hasScrolledFrom } from "../../utils/has-scrolled-from";
|
||||||
@@ -41,6 +42,7 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai
|
|||||||
I18nPipe,
|
I18nPipe,
|
||||||
CdkTrapFocus,
|
CdkTrapFocus,
|
||||||
CdkScrollable,
|
CdkScrollable,
|
||||||
|
SpinnerComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DialogComponent {
|
export class DialogComponent {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
|
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
import { ButtonModule } from "../../button";
|
import { ButtonModule } from "../../button";
|
||||||
|
import { I18nMockService } from "../../utils";
|
||||||
import { DialogModule } from "../dialog.module";
|
import { DialogModule } from "../dialog.module";
|
||||||
|
|
||||||
import { SimpleDialogComponent } from "./simple-dialog.component";
|
import { SimpleDialogComponent } from "./simple-dialog.component";
|
||||||
@@ -12,6 +15,16 @@ export default {
|
|||||||
decorators: [
|
decorators: [
|
||||||
moduleMetadata({
|
moduleMetadata({
|
||||||
imports: [ButtonModule, NoopAnimationsModule, DialogModule],
|
imports: [ButtonModule, NoopAnimationsModule, DialogModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
loading: "Loading",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
import { IconButtonModule } from "../icon-button";
|
import { IconButtonModule } from "../icon-button";
|
||||||
|
import { I18nMockService } from "../utils";
|
||||||
|
|
||||||
import { DisclosureTriggerForDirective } from "./disclosure-trigger-for.directive";
|
import { DisclosureTriggerForDirective } from "./disclosure-trigger-for.directive";
|
||||||
import { DisclosureComponent } from "./disclosure.component";
|
import { DisclosureComponent } from "./disclosure.component";
|
||||||
@@ -11,6 +14,16 @@ export default {
|
|||||||
decorators: [
|
decorators: [
|
||||||
moduleMetadata({
|
moduleMetadata({
|
||||||
imports: [DisclosureTriggerForDirective, DisclosureComponent, IconButtonModule],
|
imports: [DisclosureTriggerForDirective, DisclosureComponent, IconButtonModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
loading: "Loading",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export default {
|
|||||||
return new I18nMockService({
|
return new I18nMockService({
|
||||||
...mockLayoutI18n,
|
...mockLayoutI18n,
|
||||||
close: "Close",
|
close: "Close",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export default {
|
|||||||
inputRequired: "Input is required.",
|
inputRequired: "Input is required.",
|
||||||
inputEmail: "Input is not an email-address.",
|
inputEmail: "Input is not an email-address.",
|
||||||
toggleVisibility: "Toggle visibility",
|
toggleVisibility: "Toggle visibility",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ describe("PasswordInputToggle", () => {
|
|||||||
provide: I18nService,
|
provide: I18nService,
|
||||||
useValue: new I18nMockService({
|
useValue: new I18nMockService({
|
||||||
toggleVisibility: "Toggle visibility",
|
toggleVisibility: "Toggle visibility",
|
||||||
|
loading: "Loading",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ export default {
|
|||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: I18nService,
|
provide: I18nService,
|
||||||
useValue: new I18nMockService({ toggleVisibility: "Toggle visibility" }),
|
useValue: new I18nMockService({
|
||||||
|
toggleVisibility: "Toggle visibility",
|
||||||
|
loading: "Loading",
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -6,10 +6,6 @@
|
|||||||
class="tw-absolute tw-inset-0 tw-flex tw-items-center tw-justify-center"
|
class="tw-absolute tw-inset-0 tw-flex tw-items-center tw-justify-center"
|
||||||
[ngClass]="{ 'tw-invisible': !showLoadingStyle() }"
|
[ngClass]="{ 'tw-invisible': !showLoadingStyle() }"
|
||||||
>
|
>
|
||||||
<i
|
<bit-spinner size="fill" noColor></bit-spinner>
|
||||||
class="bwi bwi-spinner bwi-spin"
|
|
||||||
aria-hidden="true"
|
|
||||||
[ngClass]="{ 'bwi-lg': size() === 'default' }"
|
|
||||||
></i>
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { AriaDisableDirective } from "../a11y";
|
|||||||
import { setA11yTitleAndAriaLabel } from "../a11y/set-a11y-title-and-aria-label";
|
import { setA11yTitleAndAriaLabel } from "../a11y/set-a11y-title-and-aria-label";
|
||||||
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
|
import { ButtonLikeAbstraction } from "../shared/button-like.abstraction";
|
||||||
import { FocusableElement } from "../shared/focusable-element";
|
import { FocusableElement } from "../shared/focusable-element";
|
||||||
|
import { SpinnerComponent } from "../spinner";
|
||||||
import { ariaDisableElement } from "../utils";
|
import { ariaDisableElement } from "../utils";
|
||||||
|
|
||||||
export type IconButtonType = "primary" | "danger" | "contrast" | "main" | "muted" | "nav-contrast";
|
export type IconButtonType = "primary" | "danger" | "contrast" | "main" | "muted" | "nav-contrast";
|
||||||
@@ -87,7 +88,7 @@ const sizes: Record<IconButtonSize, string[]> = {
|
|||||||
{ provide: ButtonLikeAbstraction, useExisting: BitIconButtonComponent },
|
{ provide: ButtonLikeAbstraction, useExisting: BitIconButtonComponent },
|
||||||
{ provide: FocusableElement, useExisting: BitIconButtonComponent },
|
{ provide: FocusableElement, useExisting: BitIconButtonComponent },
|
||||||
],
|
],
|
||||||
imports: [NgClass],
|
imports: [NgClass, SpinnerComponent],
|
||||||
host: {
|
host: {
|
||||||
/**
|
/**
|
||||||
* When the `bitIconButton` input is dynamic from a consumer, Angular doesn't put the
|
* When the `bitIconButton` input is dynamic from a consumer, Angular doesn't put the
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
import { Meta, StoryObj } from "@storybook/angular";
|
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet";
|
import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet";
|
||||||
|
import { I18nMockService } from "../utils";
|
||||||
|
|
||||||
import { BitIconButtonComponent } from "./icon-button.component";
|
import { BitIconButtonComponent } from "./icon-button.component";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Component Library/Icon Button",
|
title: "Component Library/Icon Button",
|
||||||
component: BitIconButtonComponent,
|
component: BitIconButtonComponent,
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
loading: "Loading",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
args: {
|
args: {
|
||||||
bitIconButton: "bwi-plus",
|
bitIconButton: "bwi-plus",
|
||||||
label: "Your button label here",
|
label: "Your button label here",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export default {
|
|||||||
skipToContent: "Skip to content",
|
skipToContent: "Skip to content",
|
||||||
submenu: "submenu",
|
submenu: "submenu",
|
||||||
toggleCollapse: "toggle collapse",
|
toggleCollapse: "toggle collapse",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ export const mockLayoutI18n = {
|
|||||||
skipToContent: "Skip to content",
|
skipToContent: "Skip to content",
|
||||||
submenu: "submenu",
|
submenu: "submenu",
|
||||||
toggleCollapse: "toggle collapse",
|
toggleCollapse: "toggle collapse",
|
||||||
|
loading: "Loading",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { OverlayModule } from "@angular/cdk/overlay";
|
import { OverlayModule } from "@angular/cdk/overlay";
|
||||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
import { ButtonModule } from "../button/button.module";
|
import { ButtonModule } from "../button/button.module";
|
||||||
|
import { I18nMockService } from "../utils";
|
||||||
|
|
||||||
import { MenuTriggerForDirective } from "./menu-trigger-for.directive";
|
import { MenuTriggerForDirective } from "./menu-trigger-for.directive";
|
||||||
import { MenuModule } from "./menu.module";
|
import { MenuModule } from "./menu.module";
|
||||||
@@ -12,6 +15,12 @@ export default {
|
|||||||
decorators: [
|
decorators: [
|
||||||
moduleMetadata({
|
moduleMetadata({
|
||||||
imports: [MenuModule, OverlayModule, ButtonModule],
|
imports: [MenuModule, OverlayModule, ButtonModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useValue: new I18nMockService({ loading: "Loading" }),
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
appendTo="body"
|
appendTo="body"
|
||||||
>
|
>
|
||||||
<ng-template ng-loadingspinner-tmp>
|
<ng-template ng-loadingspinner-tmp>
|
||||||
<i class="bwi bwi-spinner bwi-spin tw-me-1" [title]="loadingText" aria-hidden="true"></i>
|
<bit-spinner></bit-spinner>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ng-label-tmp let-item="item" let-clear="clear">
|
<ng-template ng-label-tmp let-item="item" let-clear="clear">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { I18nPipe } from "@bitwarden/ui-common";
|
|||||||
|
|
||||||
import { BadgeModule } from "../badge";
|
import { BadgeModule } from "../badge";
|
||||||
import { BitFormFieldControl } from "../form-field/form-field-control";
|
import { BitFormFieldControl } from "../form-field/form-field-control";
|
||||||
|
import { SpinnerComponent } from "../spinner";
|
||||||
|
|
||||||
import { SelectItemView } from "./models/select-item-view";
|
import { SelectItemView } from "./models/select-item-view";
|
||||||
|
|
||||||
@@ -37,7 +38,14 @@ let nextId = 0;
|
|||||||
selector: "bit-multi-select",
|
selector: "bit-multi-select",
|
||||||
templateUrl: "./multi-select.component.html",
|
templateUrl: "./multi-select.component.html",
|
||||||
providers: [{ provide: BitFormFieldControl, useExisting: MultiSelectComponent }],
|
providers: [{ provide: BitFormFieldControl, useExisting: MultiSelectComponent }],
|
||||||
imports: [NgSelectModule, ReactiveFormsModule, FormsModule, BadgeModule, I18nPipe],
|
imports: [
|
||||||
|
NgSelectModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormsModule,
|
||||||
|
BadgeModule,
|
||||||
|
I18nPipe,
|
||||||
|
SpinnerComponent,
|
||||||
|
],
|
||||||
host: {
|
host: {
|
||||||
"[id]": "this.id()",
|
"[id]": "this.id()",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export default {
|
|||||||
toggleCollapse: "toggle collapse",
|
toggleCollapse: "toggle collapse",
|
||||||
toggleSideNavigation: "Toggle side navigation",
|
toggleSideNavigation: "Toggle side navigation",
|
||||||
skipToContent: "Skip to content",
|
skipToContent: "Skip to content",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export default {
|
|||||||
toggleCollapse: "toggle collapse",
|
toggleCollapse: "toggle collapse",
|
||||||
toggleSideNavigation: "Toggle side navigation",
|
toggleSideNavigation: "Toggle side navigation",
|
||||||
skipToContent: "Skip to content",
|
skipToContent: "Skip to content",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ import {
|
|||||||
Security,
|
Security,
|
||||||
VaultOpen,
|
VaultOpen,
|
||||||
} from "@bitwarden/assets/svg";
|
} from "@bitwarden/assets/svg";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
import { ButtonModule } from "../button";
|
import { ButtonModule } from "../button";
|
||||||
|
import { I18nMockService } from "../utils";
|
||||||
|
|
||||||
import { NoItemsComponent } from "./no-items.component";
|
import { NoItemsComponent } from "./no-items.component";
|
||||||
import { NoItemsModule } from "./no-items.module";
|
import { NoItemsModule } from "./no-items.module";
|
||||||
@@ -27,6 +29,12 @@ export default {
|
|||||||
decorators: [
|
decorators: [
|
||||||
moduleMetadata({
|
moduleMetadata({
|
||||||
imports: [ButtonModule, NoItemsModule],
|
imports: [ButtonModule, NoItemsModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useValue: new I18nMockService({ loading: "Loading" }),
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { Meta, StoryObj, componentWrapperDecorator, moduleMetadata } from "@storybook/angular";
|
import { Meta, StoryObj, componentWrapperDecorator, moduleMetadata } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
import { CardComponent } from "../card";
|
import { CardComponent } from "../card";
|
||||||
import { IconButtonModule } from "../icon-button";
|
import { IconButtonModule } from "../icon-button";
|
||||||
import { ItemModule } from "../item";
|
import { ItemModule } from "../item";
|
||||||
import { TypographyModule } from "../typography";
|
import { TypographyModule } from "../typography";
|
||||||
|
import { I18nMockService } from "../utils";
|
||||||
|
|
||||||
import { SectionComponent, SectionHeaderComponent } from "./";
|
import { SectionComponent, SectionHeaderComponent } from "./";
|
||||||
|
|
||||||
@@ -19,6 +22,12 @@ export default {
|
|||||||
IconButtonModule,
|
IconButtonModule,
|
||||||
ItemModule,
|
ItemModule,
|
||||||
],
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useValue: new I18nMockService({ loading: "Loading" }),
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
componentWrapperDecorator((story) => `<div class="tw-text-main">${story}</div>`),
|
componentWrapperDecorator((story) => `<div class="tw-text-main">${story}</div>`),
|
||||||
],
|
],
|
||||||
|
|||||||
1
libs/components/src/spinner/index.ts
Normal file
1
libs/components/src/spinner/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./spinner.component";
|
||||||
27
libs/components/src/spinner/spinner.component.html
Normal file
27
libs/components/src/spinner/spinner.component.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 56 56"
|
||||||
|
fill="none"
|
||||||
|
class="tw-size-full tw-animate-spin"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
cx="28"
|
||||||
|
cy="28"
|
||||||
|
r="23"
|
||||||
|
class="tw-stroke-primary-600"
|
||||||
|
pathLength="4"
|
||||||
|
stroke-width="5"
|
||||||
|
stroke-dasharray="1 3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
></circle>
|
||||||
|
<circle
|
||||||
|
cx="28"
|
||||||
|
cy="28"
|
||||||
|
r="23"
|
||||||
|
class="tw-stroke-primary-600"
|
||||||
|
stroke-width="5"
|
||||||
|
opacity="0.4"
|
||||||
|
></circle>
|
||||||
|
</svg>
|
||||||
|
<span class="tw-sr-only" *ngIf="sr">{{ title }}</span>
|
||||||
|
After Width: | Height: | Size: 519 B |
51
libs/components/src/spinner/spinner.component.ts
Normal file
51
libs/components/src/spinner/spinner.component.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { Component, HostBinding, Input, booleanAttribute } from "@angular/core";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "bit-spinner",
|
||||||
|
templateUrl: "spinner.component.html",
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
})
|
||||||
|
export class SpinnerComponent {
|
||||||
|
/**
|
||||||
|
* The size of the spinner. Defaults to `large`.
|
||||||
|
*/
|
||||||
|
@Input() size: "fill" | "small" | "large" = "large";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the default color of the spinner, inherits the text color.
|
||||||
|
*/
|
||||||
|
@Input({ transform: booleanAttribute }) noColor = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accessibility title. Defaults to `Loading`.
|
||||||
|
*/
|
||||||
|
@Input() title = this.i18nService.t("loading");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display text for screen readers.
|
||||||
|
*/
|
||||||
|
@Input({ transform: booleanAttribute }) sr = true;
|
||||||
|
|
||||||
|
@HostBinding("class") get classList() {
|
||||||
|
return ["tw-inline-block", "tw-overflow-hidden", "tw-flex", "tw-items-center"]
|
||||||
|
.concat(this.sizeClass)
|
||||||
|
.concat([this.noColor ? "" : "tw-text-primary-600"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private i18nService: I18nService) {}
|
||||||
|
|
||||||
|
get sizeClass() {
|
||||||
|
switch (this.size) {
|
||||||
|
case "small":
|
||||||
|
return ["tw-h-4"];
|
||||||
|
case "large":
|
||||||
|
return ["tw-h-16"];
|
||||||
|
default:
|
||||||
|
return ["tw-h-full", "tw-w-full"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
libs/components/src/spinner/spinner.stories.ts
Normal file
53
libs/components/src/spinner/spinner.stories.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
|
import { I18nMockService } from "../utils";
|
||||||
|
|
||||||
|
import { SpinnerComponent } from "./spinner.component";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Component Library/Spinner",
|
||||||
|
component: SpinnerComponent,
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
imports: [CommonModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useFactory: () => {
|
||||||
|
return new I18nMockService({
|
||||||
|
loading: "Loading",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
declarations: [],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: "figma",
|
||||||
|
url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A16956",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<SpinnerComponent>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Fill: Story = {
|
||||||
|
args: {
|
||||||
|
size: "fill",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Small: Story = {
|
||||||
|
args: {
|
||||||
|
size: "small",
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -23,6 +23,7 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
|
|||||||
inputMaxValue: (max) => `Input value must not exceed ${max}.`,
|
inputMaxValue: (max) => `Input value must not exceed ${max}.`,
|
||||||
inputMinValue: (min) => `Input value must be at least ${min}.`,
|
inputMinValue: (min) => `Input value must be at least ${min}.`,
|
||||||
inputRequired: "Input is required.",
|
inputRequired: "Input is required.",
|
||||||
|
loading: "Loading",
|
||||||
multiSelectClearAll: "Clear all",
|
multiSelectClearAll: "Clear all",
|
||||||
multiSelectLoading: "Retrieving options...",
|
multiSelectLoading: "Retrieving options...",
|
||||||
multiSelectNotFound: "No items found",
|
multiSelectNotFound: "No items found",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export default {
|
|||||||
toggleSideNavigation: "Toggle side navigation",
|
toggleSideNavigation: "Toggle side navigation",
|
||||||
yes: "Yes",
|
yes: "Yes",
|
||||||
no: "No",
|
no: "No",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ import { Component, importProvidersFrom } from "@angular/core";
|
|||||||
import { RouterModule } from "@angular/router";
|
import { RouterModule } from "@angular/router";
|
||||||
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
|
||||||
import { ButtonModule } from "../button";
|
import { ButtonModule } from "../button";
|
||||||
import { FormFieldModule } from "../form-field";
|
import { FormFieldModule } from "../form-field";
|
||||||
|
import { I18nMockService } from "../utils";
|
||||||
|
|
||||||
import { TabGroupComponent } from "./tab-group/tab-group.component";
|
import { TabGroupComponent } from "./tab-group/tab-group.component";
|
||||||
import { TabsModule } from "./tabs.module";
|
import { TabsModule } from "./tabs.module";
|
||||||
@@ -56,6 +59,12 @@ export default {
|
|||||||
ItemWithChildCounterDummyComponent,
|
ItemWithChildCounterDummyComponent,
|
||||||
DisabledDummyComponent,
|
DisabledDummyComponent,
|
||||||
],
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useValue: new I18nMockService({ loading: "Loading" }),
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
applicationConfig({
|
applicationConfig({
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export default {
|
|||||||
error: "Error",
|
error: "Error",
|
||||||
warning: "Warning",
|
warning: "Warning",
|
||||||
info: "Info",
|
info: "Info",
|
||||||
|
loading: "Loading",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user