diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html
index 60214a9fd61..dc5d4c7929e 100644
--- a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html
+++ b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html
@@ -103,48 +103,11 @@
*ngIf="filter.type !== 'trash' && filter.collectionId !== Unassigned && organization"
class="tw-shrink-0"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts
index b343d5874bc..121a5c03ffe 100644
--- a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts
+++ b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts
@@ -25,6 +25,7 @@ import {
SearchModule,
SimpleDialogOptions,
} from "@bitwarden/components";
+import { NewCipherMenuComponent } from "@bitwarden/vault";
import { HeaderModule } from "../../../../layouts/header/header.module";
import { SharedModule } from "../../../../shared";
@@ -45,6 +46,7 @@ import { CollectionDialogTabType } from "../../shared/components/collection-dial
HeaderModule,
SearchModule,
JslibModule,
+ NewCipherMenuComponent,
],
})
export class VaultHeaderComponent {
diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts
index 3793db6f76a..18dfa73ac5a 100644
--- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts
+++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts
@@ -510,7 +510,7 @@ export class VaultItemsComponent {
private compareNames(a: VaultItem, b: VaultItem): number {
const getName = (item: VaultItem) => item.collection?.name || item.cipher?.name;
- return getName(a).localeCompare(getName(b));
+ return getName(a)?.localeCompare(getName(b)) ?? -1;
}
/**
diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html
index 4ef8204cdfc..711d1166d7b 100644
--- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html
+++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.html
@@ -68,35 +68,13 @@
-
-
-
- @for (item of cipherMenuItems$ | async; track item.type) {
-
- }
-
-
-
-
-
+
diff --git a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts
index 49e159143dd..f4f3ba32428 100644
--- a/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-header/vault-header.component.ts
@@ -3,7 +3,7 @@
import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core";
import { Router } from "@angular/router";
-import { firstValueFrom, map, shareReplay } from "rxjs";
+import { firstValueFrom } from "rxjs";
import {
Unassigned,
@@ -18,13 +18,13 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
-import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
import {
BreadcrumbsModule,
DialogService,
MenuModule,
SimpleDialogOptions,
} from "@bitwarden/components";
+import { NewCipherMenuComponent } from "@bitwarden/vault";
import { CollectionDialogTabType } from "../../../admin-console/organizations/shared/components/collection-dialog";
import { HeaderModule } from "../../../layouts/header/header.module";
@@ -46,6 +46,7 @@ import {
HeaderModule,
PipesModule,
JslibModule,
+ NewCipherMenuComponent,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
@@ -54,21 +55,6 @@ export class VaultHeaderComponent {
protected All = All;
protected CollectionDialogTabType = CollectionDialogTabType;
protected CipherType = CipherType;
- protected allCipherMenuItems = [
- { type: CipherType.Login, icon: "bwi-globe", labelKey: "typeLogin" },
- { type: CipherType.Card, icon: "bwi-credit-card", labelKey: "typeCard" },
- { type: CipherType.Identity, icon: "bwi-id-card", labelKey: "typeIdentity" },
- { type: CipherType.SecureNote, icon: "bwi-sticky-note", labelKey: "note" },
- { type: CipherType.SshKey, icon: "bwi-key", labelKey: "typeSshKey" },
- ];
- protected cipherMenuItems$ = this.restrictedItemTypesService.restricted$.pipe(
- map((restrictedTypes) => {
- return this.allCipherMenuItems.filter((item) => {
- return !restrictedTypes.some((restrictedType) => restrictedType.cipherType === item.type);
- });
- }),
- shareReplay({ bufferSize: 1, refCount: true }),
- );
/**
* Boolean to determine the loading state of the header.
@@ -109,7 +95,6 @@ export class VaultHeaderComponent {
private dialogService: DialogService,
private router: Router,
private configService: ConfigService,
- private restrictedItemTypesService: RestrictedItemTypesService,
) {}
/**
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index b272dc32e3b..a58126adac5 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -647,6 +647,9 @@
"typeSecureNote": {
"message": "Secure note"
},
+ "typeNote": {
+ "message": "Note"
+ },
"typeSshKey": {
"message": "SSH key"
},
@@ -8928,7 +8931,7 @@
},
"uriMatchDefaultStrategyHint": {
"message": "URI match detection is how Bitwarden identifies autofill suggestions.",
- "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item."
+ "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item."
},
"regExAdvancedOptionWarning": {
"message": "\"Regular expression\" is an advanced option with increased risk of exposing credentials.",
diff --git a/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.html b/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.html
new file mode 100644
index 00000000000..38b5875a605
--- /dev/null
+++ b/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.html
@@ -0,0 +1,37 @@
+
+
+
+
+ @for (item of cipherMenuItems$ | async; track item.type) {
+
+ }
+
+
+
+
+
+
diff --git a/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.ts b/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.ts
new file mode 100644
index 00000000000..eb3194bc5f0
--- /dev/null
+++ b/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.ts
@@ -0,0 +1,38 @@
+import { CommonModule } from "@angular/common";
+import { Component, input, output } from "@angular/core";
+import { map, shareReplay } from "rxjs";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { CipherType } from "@bitwarden/common/vault/enums";
+import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
+import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items";
+import { ButtonModule, MenuModule } from "@bitwarden/components";
+import { I18nPipe } from "@bitwarden/ui-common";
+
+@Component({
+ selector: "vault-new-cipher-menu",
+ templateUrl: "new-cipher-menu.component.html",
+ imports: [ButtonModule, CommonModule, MenuModule, I18nPipe, JslibModule],
+})
+export class NewCipherMenuComponent {
+ canCreateCipher = input(false);
+ canCreateFolder = input(false);
+ canCreateCollection = input(false);
+ folderAdded = output();
+ collectionAdded = output();
+ cipherAdded = output();
+
+ constructor(private restrictedItemTypesService: RestrictedItemTypesService) {}
+
+ /**
+ * Returns an observable that emits the cipher menu items, filtered by the restricted types.
+ */
+ cipherMenuItems$ = this.restrictedItemTypesService.restricted$.pipe(
+ map((restrictedTypes) => {
+ return CIPHER_MENU_ITEMS.filter((item) => {
+ return !restrictedTypes.some((restrictedType) => restrictedType.cipherType === item.type);
+ });
+ }),
+ shareReplay({ bufferSize: 1, refCount: true }),
+ );
+}
diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts
index b39bb85ab30..9d037d8fe5e 100644
--- a/libs/vault/src/index.ts
+++ b/libs/vault/src/index.ts
@@ -19,6 +19,7 @@ export { DecryptionFailureDialogComponent } from "./components/decryption-failur
export { openPasswordHistoryDialog } from "./components/password-history/password-history.component";
export * from "./components/add-edit-folder-dialog/add-edit-folder-dialog.component";
export * from "./components/carousel";
+export * from "./components/new-cipher-menu/new-cipher-menu.component";
export * as VaultIcons from "./icons";