From 508131ac1e2562b4279902376f9ac379e35f1090 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 9 Dec 2025 19:26:26 +0100 Subject: [PATCH] [PM-294489 Extract send table to libs/tools (#17841) * Extract send table to libs/tools so it can be re-used in desktop * Remove *ngIf * Add story * Move story under sends * Remove duplicate class * Move the dates to prevent any changes in storybook * Revert changes to web component to speed up merge --- libs/tools/send/send-ui/src/index.ts | 1 + .../src/send-table/send-table.component.html | 102 ++++++++++++++++++ .../send-table.component.stories.ts | 100 +++++++++++++++++ .../src/send-table/send-table.component.ts | 92 ++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 libs/tools/send/send-ui/src/send-table/send-table.component.html create mode 100644 libs/tools/send/send-ui/src/send-table/send-table.component.stories.ts create mode 100644 libs/tools/send/send-ui/src/send-table/send-table.component.ts diff --git a/libs/tools/send/send-ui/src/index.ts b/libs/tools/send/send-ui/src/index.ts index 2803e91c418..ac8b9383681 100644 --- a/libs/tools/send/send-ui/src/index.ts +++ b/libs/tools/send/send-ui/src/index.ts @@ -6,3 +6,4 @@ export { SendItemsService } from "./services/send-items.service"; export { SendSearchComponent } from "./send-search/send-search.component"; export { SendListFiltersComponent } from "./send-list-filters/send-list-filters.component"; export { SendListFiltersService } from "./services/send-list-filters.service"; +export { SendTableComponent } from "./send-table/send-table.component"; diff --git a/libs/tools/send/send-ui/src/send-table/send-table.component.html b/libs/tools/send/send-ui/src/send-table/send-table.component.html new file mode 100644 index 00000000000..6b93b9d879e --- /dev/null +++ b/libs/tools/send/send-ui/src/send-table/send-table.component.html @@ -0,0 +1,102 @@ + + + + {{ "name" | i18n }} + {{ "deletionDate" | i18n }} + {{ "options" | i18n }} + + + + + +
+ + + @if (s.disabled) { + + {{ "disabled" | i18n }} + } + @if (s.password) { + + {{ "password" | i18n }} + } + @if (s.maxAccessCountReached) { + + {{ "maxAccessCountReached" | i18n }} + } + @if (s.expired) { + + {{ "expired" | i18n }} + } + @if (s.pendingDelete) { + + {{ "pendingDeletion" | i18n }} + } +
+ + + {{ s.deletionDate | date: "medium" }} + + + + + + @if (s.password && !disableSend()) { + + } + + + + +
+
diff --git a/libs/tools/send/send-ui/src/send-table/send-table.component.stories.ts b/libs/tools/send/send-ui/src/send-table/send-table.component.stories.ts new file mode 100644 index 00000000000..d2d630b69a2 --- /dev/null +++ b/libs/tools/send/send-ui/src/send-table/send-table.component.stories.ts @@ -0,0 +1,100 @@ +import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { TableDataSource, I18nMockService } from "@bitwarden/components"; + +import { SendTableComponent } from "./send-table.component"; + +function createMockSend(id: number, overrides: Partial = {}): SendView { + const send = new SendView(); + + send.id = `send-${id}`; + send.name = "My Send"; + send.type = SendType.Text; + send.deletionDate = new Date("2030-01-01T12:00:00Z"); + send.password = null as any; + + Object.assign(send, overrides); + + return send; +} + +const dataSource = new TableDataSource(); +dataSource.data = [ + createMockSend(0, { + name: "Project Documentation", + type: SendType.Text, + }), + createMockSend(1, { + name: "Meeting Notes", + type: SendType.File, + }), + createMockSend(2, { + name: "Password Protected Send", + type: SendType.Text, + password: "123", + }), + createMockSend(3, { + name: "Disabled Send", + type: SendType.Text, + disabled: true, + }), + createMockSend(4, { + name: "Expired Send", + type: SendType.File, + expirationDate: new Date("2025-12-01T00:00:00Z"), + }), + createMockSend(5, { + name: "Max Access Reached", + type: SendType.Text, + maxAccessCount: 5, + accessCount: 5, + password: "123", + }), +]; + +export default { + title: "Tools/Sends/Send Table", + component: SendTableComponent, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + name: "Name", + deletionDate: "Deletion Date", + options: "Options", + disabled: "Disabled", + password: "Password", + maxAccessCountReached: "Max access count reached", + expired: "Expired", + pendingDeletion: "Pending deletion", + copySendLink: "Copy Send link", + removePassword: "Remove password", + delete: "Delete", + loading: "Loading", + }); + }, + }, + ], + }), + ], + args: { + dataSource, + disableSend: false, + }, + argTypes: { + editSend: { action: "editSend" }, + copySend: { action: "copySend" }, + removePassword: { action: "removePassword" }, + deleteSend: { action: "deleteSend" }, + }, +} as Meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/libs/tools/send/send-ui/src/send-table/send-table.component.ts b/libs/tools/send/send-ui/src/send-table/send-table.component.ts new file mode 100644 index 00000000000..c912a01f98a --- /dev/null +++ b/libs/tools/send/send-ui/src/send-table/send-table.component.ts @@ -0,0 +1,92 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, input, output } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; +import { + BadgeModule, + ButtonModule, + IconButtonModule, + LinkModule, + MenuModule, + TableDataSource, + TableModule, + TypographyModule, +} from "@bitwarden/components"; + +/** + * A table component for displaying Send items with sorting, status indicators, and action menus. Handles the presentation of sends in a tabular format with options + * for editing, copying links, removing passwords, and deleting. + */ +@Component({ + selector: "tools-send-table", + templateUrl: "./send-table.component.html", + imports: [ + CommonModule, + JslibModule, + TableModule, + ButtonModule, + LinkModule, + IconButtonModule, + MenuModule, + BadgeModule, + TypographyModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SendTableComponent { + protected readonly sendType = SendType; + + /** + * The data source containing the Send items to display in the table. + */ + readonly dataSource = input>(); + + /** + * Whether Send functionality is disabled by policy. + * When true, the "Remove Password" option is hidden from the action menu. + */ + readonly disableSend = input(false); + + /** + * Emitted when a user clicks on a Send item to edit it. + * The clicked SendView is passed as the event payload. + */ + readonly editSend = output(); + + /** + * Emitted when a user clicks the "Copy Send Link" action. + * The SendView is passed as the event payload for generating and copying the link. + */ + readonly copySend = output(); + + /** + * Emitted when a user clicks the "Remove Password" action. + * The SendView is passed as the event payload for password removal. + * This action is only available if the Send has a password and Send is not disabled. + */ + readonly removePassword = output(); + + /** + * Emitted when a user clicks the "Delete" action. + * The SendView is passed as the event payload for deletion. + */ + readonly deleteSend = output(); + + protected onEditSend(send: SendView): void { + this.editSend.emit(send); + } + + protected onCopy(send: SendView): void { + this.copySend.emit(send); + } + + protected onRemovePassword(send: SendView): void { + this.removePassword.emit(send); + } + + protected onDelete(send: SendView): void { + this.deleteSend.emit(send); + } +}