From ea5eb9aaf76a2f56d77c3f7b5734c8bc7c4af1b7 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Fri, 19 Sep 2025 11:28:07 -0700 Subject: [PATCH] [CL-737] Migrate last copy click input to signal (#16291) --- .../src/copy-click/copy-click.directive.ts | 33 ++-- .../src/copy-click/copy-click.stories.ts | 142 ++++++++++++++++++ 2 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 libs/components/src/copy-click/copy-click.stories.ts diff --git a/libs/components/src/copy-click/copy-click.directive.ts b/libs/components/src/copy-click/copy-click.directive.ts index 6b30a66c511..3eb075a2b5c 100644 --- a/libs/components/src/copy-click/copy-click.directive.ts +++ b/libs/components/src/copy-click/copy-click.directive.ts @@ -1,11 +1,11 @@ import { Directive, HostListener, - Input, InjectionToken, Inject, Optional, input, + computed, } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -26,8 +26,19 @@ export const COPY_CLICK_LISTENER = new InjectionToken("CopyCl selector: "[appCopyClick]", }) export class CopyClickDirective { - private _showToast = false; - private toastVariant: ToastVariant = "success"; + private _showToast = computed(() => { + return this.showToast() !== undefined; + }); + + private toastVariant = computed(() => { + const showToast = this.showToast(); + // When the `showToast` is set without a value, an empty string will be passed + if (showToast === "" || showToast === undefined) { + return "success"; + } else { + return showToast; + } + }); constructor( private platformUtilsService: PlatformUtilsService, @@ -57,17 +68,7 @@ export class CopyClickDirective { * * ``` */ - // TODO: Skipped for signal migration because: - // Accessor inputs cannot be migrated as they are too complex. - @Input() set showToast(value: ToastVariant | "") { - // When the `showToast` is set without a value, an empty string will be passed - if (value === "") { - this._showToast = true; - } else { - this._showToast = true; - this.toastVariant = value; - } - } + showToast = input(); @HostListener("click") onClick() { const valueToCopy = this.valueToCopy(); @@ -77,14 +78,14 @@ export class CopyClickDirective { this.copyListener.onCopy(valueToCopy); } - if (this._showToast) { + if (this._showToast()) { const valueLabel = this.valueLabel(); const message = valueLabel ? this.i18nService.t("valueCopied", valueLabel) : this.i18nService.t("copySuccessful"); this.toastService.showToast({ - variant: this.toastVariant, + variant: this.toastVariant(), message, }); } diff --git a/libs/components/src/copy-click/copy-click.stories.ts b/libs/components/src/copy-click/copy-click.stories.ts new file mode 100644 index 00000000000..50b9549dedd --- /dev/null +++ b/libs/components/src/copy-click/copy-click.stories.ts @@ -0,0 +1,142 @@ +import { provideNoopAnimations } from "@angular/platform-browser/animations"; +import { applicationConfig, moduleMetadata, StoryObj } from "@storybook/angular"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { FormFieldModule } from "../form-field"; +import { IconButtonModule } from "../icon-button"; +import { InputModule } from "../input"; +import { ToastModule } from "../toast"; +import { I18nMockService } from "../utils"; + +import { CopyClickDirective } from "./copy-click.directive"; + +export default { + title: "Component Library/Copy Click Directive", + component: CopyClickDirective, + decorators: [ + moduleMetadata({ + imports: [ToastModule, FormFieldModule, InputModule, IconButtonModule], + }), + applicationConfig({ + providers: [ + ToastModule.forRoot().providers!, + { + provide: PlatformUtilsService, + useValue: { + // eslint-disable-next-line + copyToClipboard: (text: string) => console.log(`"${text}" copied to clipboard`), + }, + }, + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + valueCopied: (text) => `${text} copied`, + copySuccessful: "Copy Successful", + success: "Success", + close: "Close", + info: "Info", + }); + }, + }, + provideNoopAnimations(), + ], + }), + ], +}; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ({ + props: { + value: "testValue123", + ...args, + }, + template: /*html*/ ` + + API Key + + + + `, + }), +}; + +export const WithDefaultToast: Story = { + render: (args) => ({ + props: { + value: "testValue123", + ...args, + }, + template: /*html*/ ` + + API Key + + + + `, + }), +}; + +export const WithCustomToastVariant: Story = { + render: (args) => ({ + props: { + value: "testValue123", + ...args, + }, + template: /*html*/ ` + + API Key + + + + `, + }), +}; + +export const WithCustomValueLabel: Story = { + render: (args) => ({ + props: { + value: "testValue123", + ...args, + }, + template: /*html*/ ` + + API Key + + + + `, + }), +};