From 7de4119d7d8ea961003df7faa801a6320e921833 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:12:53 -0800 Subject: [PATCH 1/8] add prefillNameAndUriFromTab to new login button (#18305) --- .../popup/components/vault-v2/vault-v2.component.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 6382b5fee0e..34454371f21 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -20,7 +20,13 @@ {{ "emptyVaultDescription" | i18n }}

- + {{ "newLogin" | i18n }} From ccb59c6544ea441f62cd21800882010a5cbbbc4c Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 20 Jan 2026 10:14:09 -0800 Subject: [PATCH 2/8] [PM-30742] - do not show archive button in AC (#18345) * do not show archive button in AC * do not show archive button in AC --- .../vault-item-dialog/vault-item-dialog.component.html | 2 +- .../vault-item-dialog.component.spec.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html index 640febf41d2..e4030a7ab18 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html @@ -86,7 +86,7 @@ @if (showActionButtons) {
- @if (userCanArchive$ | async) { + @if ((userCanArchive$ | async) && !params.isAdminConsoleAction) { @if (isCipherArchived) { } @@ -61,7 +58,6 @@ @if (breadcrumb.route(); as route) { diff --git a/libs/components/src/link/link.directive.ts b/libs/components/src/link/link.directive.ts index e6de8ac8402..62f0d8b878f 100644 --- a/libs/components/src/link/link.directive.ts +++ b/libs/components/src/link/link.directive.ts @@ -3,21 +3,34 @@ import { input, HostBinding, Directive, inject, ElementRef, booleanAttribute } f import { AriaDisableDirective } from "../a11y"; import { ariaDisableElement } from "../utils"; -export type LinkType = "primary" | "secondary" | "contrast" | "light"; +export const LinkTypes = [ + "primary", + "secondary", + "contrast", + "light", + "default", + "subtle", + "success", + "warning", + "danger", +] as const; + +export type LinkType = (typeof LinkTypes)[number]; const linkStyles: Record = { - primary: [ - "!tw-text-primary-600", - "hover:!tw-text-primary-700", - "focus-visible:before:tw-ring-primary-600", - ], - secondary: ["!tw-text-main", "hover:!tw-text-main", "focus-visible:before:tw-ring-primary-600"], + primary: ["tw-text-fg-brand", "hover:tw-text-fg-brand-strong"], + default: ["tw-text-fg-brand", "hover:tw-text-fg-brand-strong"], + secondary: ["tw-text-fg-heading", "hover:tw-text-fg-heading"], + light: ["tw-text-fg-white", "hover:tw-text-fg-white", "focus-visible:before:tw-ring-fg-contrast"], + subtle: ["!tw-text-fg-heading", "hover:tw-text-fg-heading"], + success: ["tw-text-fg-success", "hover:tw-text-fg-success-strong"], + warning: ["tw-text-fg-warning", "hover:tw-text-fg-warning-strong"], + danger: ["tw-text-fg-danger", "hover:tw-text-fg-danger-strong"], contrast: [ - "!tw-text-contrast", - "hover:!tw-text-contrast", - "focus-visible:before:tw-ring-text-contrast", + "tw-text-fg-contrast", + "hover:tw-text-fg-contrast", + "focus-visible:before:tw-ring-fg-contrast", ], - light: ["!tw-text-alt2", "hover:!tw-text-alt2", "focus-visible:before:tw-ring-text-alt2"], }; const commonStyles = [ @@ -32,16 +45,18 @@ const commonStyles = [ "tw-rounded", "tw-transition", "tw-no-underline", + "tw-cursor-pointer", "hover:tw-underline", "hover:tw-decoration-1", "disabled:tw-no-underline", "disabled:tw-cursor-not-allowed", - "disabled:!tw-text-secondary-300", - "disabled:hover:!tw-text-secondary-300", + "disabled:!tw-text-fg-disabled", + "disabled:hover:!tw-text-fg-disabled", "disabled:hover:tw-no-underline", "focus-visible:tw-outline-none", "focus-visible:tw-underline", "focus-visible:tw-decoration-1", + "focus-visible:before:tw-ring-border-focus", // Workaround for html button tag not being able to be set to `display: inline` // and at the same time not being able to use `tw-ring-offset` because of box-shadow issue. @@ -63,14 +78,14 @@ const commonStyles = [ "focus-visible:tw-z-10", "aria-disabled:tw-no-underline", "aria-disabled:tw-pointer-events-none", - "aria-disabled:!tw-text-secondary-300", - "aria-disabled:hover:!tw-text-secondary-300", + "aria-disabled:!tw-text-fg-disabled", + "aria-disabled:hover:!tw-text-fg-disabled", "aria-disabled:hover:tw-no-underline", ]; @Directive() abstract class LinkDirective { - readonly linkType = input("primary"); + readonly linkType = input("default"); } /** diff --git a/libs/components/src/link/link.mdx b/libs/components/src/link/link.mdx index 072e0dd84d8..4954effb6c0 100644 --- a/libs/components/src/link/link.mdx +++ b/libs/components/src/link/link.mdx @@ -18,10 +18,15 @@ import { LinkModule } from "@bitwarden/components"; You can use one of the following variants by providing it as the `linkType` input: -- `primary` - most common, uses brand color -- `secondary` - matches the main text color +- @deprecated `primary` => use `default` instead +- @deprecated `secondary` => use `subtle` instead +- `default` - most common, uses brand color +- `subtle` - matches the main text color - `contrast` - for high contrast against a dark background (or a light background in dark mode) - `light` - always a light color, even in dark mode +- `warning` - used in association with warning callouts/banners +- `success` - used in association with success callouts/banners +- `danger` - used in association with danger callouts/banners ## Sizes diff --git a/libs/components/src/link/link.stories.ts b/libs/components/src/link/link.stories.ts index ae91c9be108..d27c4f74332 100644 --- a/libs/components/src/link/link.stories.ts +++ b/libs/components/src/link/link.stories.ts @@ -2,7 +2,7 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for-code-snippet"; -import { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive"; +import { AnchorLinkDirective, ButtonLinkDirective, LinkTypes } from "./link.directive"; import { LinkModule } from "./link.module"; export default { @@ -14,7 +14,7 @@ export default { ], argTypes: { linkType: { - options: ["primary", "secondary", "contrast"], + options: LinkTypes.map((type) => type), control: { type: "radio" }, }, }, @@ -30,48 +30,153 @@ type Story = StoryObj; export const Default: Story = { render: (args) => ({ + props: { + linkType: args.linkType, + backgroundClass: + args.linkType === "contrast" + ? "tw-bg-bg-contrast" + : args.linkType === "light" + ? "tw-bg-bg-brand" + : "tw-bg-transparent", + }, template: /*html*/ ` - (args)}>Your text here +
+ (args)}>Your text here +
`, }), + args: { + linkType: "primary", + }, + parameters: { + chromatic: { disableSnapshot: true }, + }, +}; + +export const AllVariations: Story = { + render: () => ({ + template: /*html*/ ` +
+
+ Primary +
+
+ Secondary +
+
+ Contrast +
+
+ Light +
+
+ Default +
+
+ Subtle +
+
+ Success +
+
+ Warning +
+
+ Danger +
+
+ `, + }), + parameters: { + controls: { + exclude: ["linkType"], + hideNoControlsWarning: true, + }, + }, }; export const InteractionStates: Story = { render: () => ({ template: /*html*/ ` -
+
+ -
+ -
+ - `, }), + parameters: { + controls: { + exclude: ["linkType"], + hideNoControlsWarning: true, + }, + }, }; export const Buttons: Story = { render: (args) => ({ - props: args, + props: { + linkType: args.linkType, + backgroundClass: + args.linkType === "contrast" + ? "tw-bg-bg-contrast" + : args.linkType === "light" + ? "tw-bg-bg-brand" + : "tw-bg-transparent", + }, template: /*html*/ ` -
+
@@ -100,9 +205,17 @@ export const Buttons: Story = { export const Anchors: StoryObj = { render: (args) => ({ - props: args, + props: { + linkType: args.linkType, + backgroundClass: + args.linkType === "contrast" + ? "tw-bg-bg-contrast" + : args.linkType === "light" + ? "tw-bg-bg-brand" + : "tw-bg-transparent", + }, template: /*html*/ ` -
+
@@ -138,18 +251,15 @@ export const Inline: Story = { `, }), - args: { - linkType: "primary", - }, }; -export const Disabled: Story = { +export const Inactive: Story = { render: (args) => ({ props: args, template: /*html*/ ` -
+
`, diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts index 23c95cafb8a..e54064f0c9d 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts @@ -73,7 +73,6 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; A random password
diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html index 5dd11b59999..cc7537333ad 100644 --- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html @@ -32,68 +32,63 @@ - + - - - {{ "name" | i18n }} - {{ "owner" | i18n }} - - + + {{ "name" | i18n }} + {{ "owner" | i18n }} - - - - - - - - {{ r.name }} - - - {{ r.name }} - - - - {{ "shared" | i18n }} - - - - {{ "attachments" | i18n }} - -
- {{ r.subTitle }} - - - + + + + + + {{ row.name }} - - - + + + {{ row.name }} + + + + {{ "shared" | i18n }} + + + + {{ "attachments" | i18n }} + +
+ {{ row.subTitle }} + + + + +
-
+
diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.html b/apps/web/src/app/dirt/reports/reports-layout.component.html index a27556a7aa9..0cb5d304a34 100644 --- a/apps/web/src/app/dirt/reports/reports-layout.component.html +++ b/apps/web/src/app/dirt/reports/reports-layout.component.html @@ -2,8 +2,10 @@ diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.ts b/apps/web/src/app/dirt/reports/reports-layout.component.ts index c2fbf858590..a6d84ccb037 100644 --- a/apps/web/src/app/dirt/reports/reports-layout.component.ts +++ b/apps/web/src/app/dirt/reports/reports-layout.component.ts @@ -1,6 +1,6 @@ -import { Component, OnDestroy } from "@angular/core"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router } from "@angular/router"; -import { Subscription } from "rxjs"; import { filter } from "rxjs/operators"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -10,20 +10,20 @@ import { filter } from "rxjs/operators"; templateUrl: "reports-layout.component.html", standalone: false, }) -export class ReportsLayoutComponent implements OnDestroy { +export class ReportsLayoutComponent { homepage = true; - subscription: Subscription; constructor(router: Router) { - this.subscription = router.events - .pipe(filter((event) => event instanceof NavigationEnd)) - // eslint-disable-next-line rxjs-angular/prefer-takeuntil + const reportsHomeRoute = "/reports"; + + this.homepage = router.url === reportsHomeRoute; + router.events + .pipe( + takeUntilDestroyed(), + filter((event) => event instanceof NavigationEnd), + ) .subscribe((event) => { - this.homepage = (event as NavigationEnd).url == "/reports"; + this.homepage = (event as NavigationEnd).url == reportsHomeRoute; }); } - - ngOnDestroy(): void { - this.subscription?.unsubscribe(); - } } From d4b85589562197087d56d6018934868aaa387460 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Wed, 21 Jan 2026 12:30:31 -0500 Subject: [PATCH 8/8] [PM-30748] update archived restored toast (#18367) --- apps/browser/src/_locales/en/messages.json | 3 +++ .../trash-list-items-container.component.ts | 9 ++++++++- apps/desktop/src/locales/en/messages.json | 3 +++ .../vault/app/vault/item-footer.component.ts | 9 ++++++++- .../vault/individual-vault/vault.component.ts | 18 ++++++++++++++++-- apps/web/src/locales/en/messages.json | 6 ++++++ 6 files changed, 44 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 90cc4a5c338..68149a9781e 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2473,6 +2473,9 @@ "permanentlyDeletedItem": { "message": "Item permanently deleted" }, + "archivedItemRestored": { + "message": "Archived item restored" + }, "restoreItem": { "message": "Restore item" }, diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index bad6011b2d8..edebdab062f 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -115,15 +115,22 @@ export class TrashListItemsContainerComponent { } async restore(cipher: PopupCipherViewLike) { + let toastMessage; try { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); await this.cipherService.restoreWithServer(cipher.id as string, activeUserId); + if (cipher.archivedDate) { + toastMessage = this.i18nService.t("archivedItemRestored"); + } else { + toastMessage = this.i18nService.t("restoredItem"); + } + await this.router.navigate(["/trash"]); this.toastService.showToast({ variant: "success", title: null, - message: this.i18nService.t("restoredItem"), + message: toastMessage, }); } catch (e) { this.logService.error(e); diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index f9947e16692..0ce98b8c62b 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2092,6 +2092,9 @@ "permanentlyDeletedItem": { "message": "Item permanently deleted" }, + "archivedItemRestored": { + "message": "Archived item restored" + }, "restoredItem": { "message": "Item restored" }, diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.ts b/apps/desktop/src/vault/app/vault/item-footer.component.ts index c80e4e59ae4..3f22a08d00e 100644 --- a/apps/desktop/src/vault/app/vault/item-footer.component.ts +++ b/apps/desktop/src/vault/app/vault/item-footer.component.ts @@ -173,16 +173,23 @@ export class ItemFooterComponent implements OnInit, OnChanges { } async restore(): Promise { + let toastMessage; if (!this.cipher.isDeleted) { return false; } + if (this.cipher.isArchived) { + toastMessage = this.i18nService.t("archivedItemRestored"); + } else { + toastMessage = this.i18nService.t("restoredItem"); + } + try { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); await this.restoreCipher(activeUserId); this.toastService.showToast({ variant: "success", - message: this.i18nService.t("restoredItem"), + message: toastMessage, }); this.onRestore.emit(this.cipher); } catch (e) { diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 527df0b5370..5ca3a11d5ab 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -1271,6 +1271,7 @@ export class VaultComponent implements OnInit, OnDestr } restore = async (c: C): Promise => { + let toastMessage; if (!CipherViewLikeUtils.isDeleted(c)) { return; } @@ -1284,13 +1285,19 @@ export class VaultComponent implements OnInit, OnDestr return; } + if (CipherViewLikeUtils.isArchived(c)) { + toastMessage = this.i18nService.t("archivedItemRestored"); + } else { + toastMessage = this.i18nService.t("restoredItem"); + } + try { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); await this.cipherService.restoreWithServer(uuidAsString(c.id), activeUserId); this.toastService.showToast({ variant: "success", title: null, - message: this.i18nService.t("restoredItem"), + message: toastMessage, }); this.refresh(); } catch (e) { @@ -1299,11 +1306,18 @@ export class VaultComponent implements OnInit, OnDestr }; async bulkRestore(ciphers: C[]) { + let toastMessage; if (ciphers.some((c) => !c.edit)) { this.showMissingPermissionsError(); return; } + if (ciphers.some((c) => !CipherViewLikeUtils.isArchived(c))) { + toastMessage = this.i18nService.t("restoredItems"); + } else { + toastMessage = this.i18nService.t("archivedItemsRestored"); + } + if (!(await this.repromptCipher(ciphers))) { return; } @@ -1323,7 +1337,7 @@ export class VaultComponent implements OnInit, OnDestr this.toastService.showToast({ variant: "success", title: null, - message: this.i18nService.t("restoredItems"), + message: toastMessage, }); this.refresh(); } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index b438920a99f..bf7c1a4c908 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5418,6 +5418,12 @@ "restoreSelected": { "message": "Restore selected" }, + "archivedItemRestored": { + "message": "Archived item restored" + }, + "archivedItemsRestored": { + "message": "Archived items restored" + }, "restoredItem": { "message": "Item restored" },