diff --git a/apps/desktop/src/app/tools/send/new-send/new-send-dropdown.component.html b/apps/desktop/src/app/tools/send/new-send/new-send-dropdown.component.html
new file mode 100644
index 00000000000..5007f900e70
--- /dev/null
+++ b/apps/desktop/src/app/tools/send/new-send/new-send-dropdown.component.html
@@ -0,0 +1,29 @@
+
+
+
+
+ {{ "sendTypeText" | i18n }}
+
+
+
+ {{ "sendTypeFile" | i18n }}
+
+
+
diff --git a/apps/desktop/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts b/apps/desktop/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts
new file mode 100644
index 00000000000..d264882bc88
--- /dev/null
+++ b/apps/desktop/src/app/tools/send/new-send/new-send-dropdown.component.spec.ts
@@ -0,0 +1,86 @@
+import { CommonModule } from "@angular/common";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { of } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { ButtonModule, MenuModule, BadgeModule } from "@bitwarden/components";
+
+import { NewSendDropdownComponent } from "./new-send-dropdown.component";
+
+describe("NewSendDropdownComponent", () => {
+ let component: NewSendDropdownComponent;
+ let fixture: ComponentFixture;
+ let accountServiceMock: any;
+ let billingAccountProfileStateServiceMock: any;
+ let messagingServiceMock: any;
+
+ beforeEach(async () => {
+ accountServiceMock = {
+ activeAccount$: of({ id: "test-account-id" }),
+ };
+
+ billingAccountProfileStateServiceMock = {
+ hasPremiumFromAnySource$: jest.fn().mockReturnValue(of(true)),
+ };
+
+ messagingServiceMock = {
+ send: jest.fn(),
+ };
+
+ await TestBed.configureTestingModule({
+ imports: [CommonModule, JslibModule, ButtonModule, MenuModule, BadgeModule],
+ providers: [
+ { provide: I18nService, useValue: { t: (key: string) => key } },
+ { provide: AccountService, useValue: accountServiceMock },
+ {
+ provide: BillingAccountProfileStateService,
+ useValue: billingAccountProfileStateServiceMock,
+ },
+ { provide: MessagingService, useValue: messagingServiceMock },
+ ],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NewSendDropdownComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("should emit onCreateSendOfType when createSend is called with a valid type", async () => {
+ const sendType = SendType.Text;
+ jest.spyOn(component.onCreateSendOfType, "emit");
+
+ await component.createSend(sendType);
+
+ expect(component.onCreateSendOfType.emit).toHaveBeenCalledWith(sendType);
+ });
+
+ it("should call messagingService.send when createSend is called with SendType.File and no premium access", async () => {
+ billingAccountProfileStateServiceMock.hasPremiumFromAnySource$.mockReturnValue(of(false));
+
+ await component.createSend(SendType.File);
+
+ expect(messagingServiceMock.send).toHaveBeenCalledWith("openPremium");
+ });
+
+ it("should not call messagingService.send when createSend is called with SendType.File and has premium access", async () => {
+ const sendType = SendType.File;
+ jest.spyOn(component.onCreateSendOfType, "emit");
+ billingAccountProfileStateServiceMock.hasPremiumFromAnySource$.mockReturnValue(of(true));
+
+ await component.createSend(sendType);
+
+ expect(messagingServiceMock.send).not.toHaveBeenCalled();
+ expect(component.onCreateSendOfType.emit).toHaveBeenCalledWith(sendType);
+ });
+});
diff --git a/apps/desktop/src/app/tools/send/new-send/new-send-dropdown.component.ts b/apps/desktop/src/app/tools/send/new-send/new-send-dropdown.component.ts
new file mode 100644
index 00000000000..86fd49d8957
--- /dev/null
+++ b/apps/desktop/src/app/tools/send/new-send/new-send-dropdown.component.ts
@@ -0,0 +1,61 @@
+import { CommonModule } from "@angular/common";
+import { Component, EventEmitter, Input, Output } from "@angular/core";
+import { firstValueFrom, Observable, of, switchMap } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
+import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
+import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
+import { BadgeModule, ButtonModule, MenuModule } from "@bitwarden/components";
+
+@Component({
+ selector: "tools-new-send-dropdown",
+ templateUrl: "new-send-dropdown.component.html",
+ standalone: true,
+ imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule],
+})
+/**
+ * A dropdown component that allows the user to create a new Send of a specific type.
+ */
+export class NewSendDropdownComponent {
+ /** If true, the plus icon will be hidden */
+ @Input() hideIcon: boolean = false;
+
+ /** SendType provided for the markup to pass back the selected type of Send */
+ protected sendType = SendType;
+
+ /** Indicates whether the user can access premium features. */
+ protected canAccessPremium$: Observable;
+
+ /** Emitted when an allowed SendType has been selected. */
+ @Output() onCreateSendOfType = new EventEmitter();
+
+ constructor(
+ private billingAccountProfileStateService: BillingAccountProfileStateService,
+ private accountService: AccountService,
+ private messagingService: MessagingService,
+ ) {
+ this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
+ switchMap((account) =>
+ account
+ ? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id)
+ : of(false),
+ ),
+ );
+ }
+
+ /**
+ * Emits an event with the user selected SendType, for the hosting control to launch the Add new Send page with the provided SendType.
+ * If has user does not have premium access and the type is File, the user will be redirected to the premium settings page.
+ * @param type The type of Send to create.
+ */
+ async createSend(type: SendType) {
+ if (!(await firstValueFrom(this.canAccessPremium$)) && type === SendType.File) {
+ this.messagingService.send("openPremium");
+ return;
+ }
+
+ this.onCreateSendOfType.emit(type);
+ }
+}
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 7739ab84577..790ee411747 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -3439,6 +3439,12 @@
}
}
},
+ "new": {
+ "message": "New"
+ },
+ "premium": {
+ "message": "Premium"
+ },
"backTo": {
"message": "Back to $NAME$",
"description": "Navigate back to a previous folder or collection",