diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts
index c9b230fe18c..aa62194af5c 100644
--- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts
+++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts
@@ -77,7 +77,9 @@ type OverlayBackgroundExtensionMessageHandlers = {
updateFocusedFieldData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
unlockCompleted: ({ message }: BackgroundMessageParam) => void;
+ addedCipher: () => void;
addEditCipherSubmitted: () => void;
+ editedCipher: () => void;
deletedCipher: () => void;
};
diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts
index c469eb7dbbc..df4867640f4 100644
--- a/apps/browser/src/autofill/background/overlay.background.spec.ts
+++ b/apps/browser/src/autofill/background/overlay.background.spec.ts
@@ -1000,29 +1000,23 @@ describe("OverlayBackground", () => {
});
});
- describe("addEditCipherSubmitted message handler", () => {
- it("updates the overlay ciphers", () => {
- const message = {
- command: "addEditCipherSubmitted",
- };
- jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation();
+ describe("extension messages that trigger an update of the inline menu ciphers", () => {
+ const extensionMessages = [
+ "addedCipher",
+ "addEditCipherSubmitted",
+ "editedCipher",
+ "deletedCipher",
+ ];
- sendMockExtensionMessage(message);
-
- expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled();
+ beforeEach(() => {
+ jest.spyOn(overlayBackground, "updateOverlayCiphers").mockImplementation();
});
- });
- describe("deletedCipher message handler", () => {
- it("updates the overlay ciphers", () => {
- const message = {
- command: "deletedCipher",
- };
- jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation();
-
- sendMockExtensionMessage(message);
-
- expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled();
+ extensionMessages.forEach((message) => {
+ it(`triggers an update of the overlay ciphers when the ${message} message is received`, () => {
+ sendMockExtensionMessage({ command: message });
+ expect(overlayBackground.updateOverlayCiphers).toHaveBeenCalled();
+ });
});
});
});
diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts
index 56f6a3a2158..bf954c3419f 100644
--- a/apps/browser/src/autofill/background/overlay.background.ts
+++ b/apps/browser/src/autofill/background/overlay.background.ts
@@ -72,7 +72,9 @@ class OverlayBackground implements OverlayBackgroundInterface {
updateFocusedFieldData: ({ message, sender }) => this.setFocusedFieldData(message, sender),
collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender),
unlockCompleted: ({ message }) => this.unlockCompleted(message),
+ addedCipher: () => this.updateOverlayCiphers(),
addEditCipherSubmitted: () => this.updateOverlayCiphers(),
+ editedCipher: () => this.updateOverlayCiphers(),
deletedCipher: () => this.updateOverlayCiphers(),
};
private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = {
diff --git a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html b/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html
deleted file mode 100644
index 4fdbb823120..00000000000
--- a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts b/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts
deleted file mode 100644
index b33a2a0f330..00000000000
--- a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Component, Input } from "@angular/core";
-
-import { TypographyModule } from "@bitwarden/components";
-
-@Component({
- standalone: true,
- selector: "popup-section-header",
- templateUrl: "./popup-section-header.component.html",
- imports: [TypographyModule],
-})
-export class PopupSectionHeaderComponent {
- @Input() title: string;
-}
diff --git a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts b/apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts
deleted file mode 100644
index f5cb472a59c..00000000000
--- a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
-
-import {
- CardComponent,
- IconButtonModule,
- SectionComponent,
- TypographyModule,
-} from "@bitwarden/components";
-
-import { PopupSectionHeaderComponent } from "./popup-section-header.component";
-
-export default {
- title: "Browser/Popup Section Header",
- component: PopupSectionHeaderComponent,
- args: {
- title: "Title",
- },
- decorators: [
- moduleMetadata({
- imports: [SectionComponent, CardComponent, TypographyModule, IconButtonModule],
- }),
- ],
-} as Meta;
-
-type Story = StoryObj;
-
-export const OnlyTitle: Story = {
- render: (args) => ({
- props: args,
- template: `
-
- `,
- }),
- args: {
- title: "Only Title",
- },
-};
-
-export const TrailingText: Story = {
- render: (args) => ({
- props: args,
- template: `
-
- 13
-
- `,
- }),
- args: {
- title: "Trailing Text",
- },
-};
-
-export const TailingIcon: Story = {
- render: (args) => ({
- props: args,
- template: `
-
-
-
- `,
- }),
- args: {
- title: "Trailing Icon",
- },
-};
-
-export const TitleSuffix: Story = {
- render: (args) => ({
- props: args,
- template: `
-
-
-
- `,
- }),
- args: {
- title: "Title Suffix",
- },
-};
-
-export const WithSections: Story = {
- render: () => ({
- template: `
-
-
-
-
-
-
- Card 1 Content
-
-
-
-
-
-
-
- Card 2 Content
-
-
-
- `,
- }),
-};
diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts
index 86f4d9fa768..3c7f45e55f7 100644
--- a/apps/browser/src/popup/app.module.ts
+++ b/apps/browser/src/popup/app.module.ts
@@ -46,7 +46,6 @@ import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.comp
import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../platform/popup/layout/popup-page.component";
import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component";
-import { PopupSectionHeaderComponent } from "../platform/popup/popup-section-header/popup-section-header.component";
import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component";
import { GeneratorComponent } from "../tools/popup/generator/generator.component";
import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component";
@@ -119,7 +118,6 @@ import "../platform/popup/locales";
PopupFooterComponent,
PopupHeaderComponent,
UserVerificationDialogComponent,
- PopupSectionHeaderComponent,
CurrentAccountComponent,
],
declarations: [
diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html
index 83b07fc14cb..e52018ab27f 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html
+++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html
@@ -8,17 +8,19 @@
>
-
+
+
+ {{ "autofillSuggestions" | i18n }}
+
-
+
{{
"autofillSuggestionsTip" | i18n
}}
diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts
index 9a4670bb4c8..1b9876759f0 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts
+++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts
@@ -3,10 +3,14 @@ import { Component } from "@angular/core";
import { combineLatest, map, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
-import { IconButtonModule, SectionComponent, TypographyModule } from "@bitwarden/components";
+import {
+ IconButtonModule,
+ SectionComponent,
+ SectionHeaderComponent,
+ TypographyModule,
+} from "@bitwarden/components";
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
-import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component";
import { VaultPopupItemsService } from "../../../services/vault-popup-items.service";
import { PopupCipherView } from "../../../views/popup-cipher.view";
import { VaultListItemsContainerComponent } from "../vault-list-items-container/vault-list-items-container.component";
@@ -19,7 +23,7 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/
TypographyModule,
VaultListItemsContainerComponent,
JslibModule,
- PopupSectionHeaderComponent,
+ SectionHeaderComponent,
IconButtonModule,
],
selector: "app-autofill-vault-list-items",
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html
index 74ee1af1ff5..c2c345fd757 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html
+++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html
@@ -1,16 +1,18 @@
0">
-
- {{ ciphers.length }}
+
+
+ {{ title }}
+
-
+ {{ ciphers.length }}
+
Promise;
- /**
- * Sends a response to an auth request.
- */
- passwordlessLogin: (
- id: string,
- key: string,
- requestApproved: boolean,
- ) => Promise;
}
diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts
index f425bc697c5..7169fd69e93 100644
--- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts
+++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts
@@ -24,8 +24,6 @@ import {
PBKDF2KdfConfig,
} from "@bitwarden/common/auth/models/domain/kdf-config";
import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request";
-import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
-import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
@@ -39,7 +37,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { KdfType } from "@bitwarden/common/platform/enums/kdf-type.enum";
-import { Utils } from "@bitwarden/common/platform/misc/utils";
import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state";
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
@@ -263,47 +260,6 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
return await this.cryptoService.makeMasterKey(masterPassword, email, kdfConfig);
}
- // TODO: move to auth request service
- async passwordlessLogin(
- id: string,
- key: string,
- requestApproved: boolean,
- ): Promise {
- const pubKey = Utils.fromB64ToArray(key);
-
- const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
- const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
- let keyToEncrypt;
- let encryptedMasterKeyHash = null;
-
- if (masterKey) {
- keyToEncrypt = masterKey.encKey;
-
- // Only encrypt the master password hash if masterKey exists as
- // we won't have a masterKeyHash without a masterKey
- const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId));
- if (masterKeyHash != null) {
- encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt(
- Utils.fromUtf8ToArray(masterKeyHash),
- pubKey,
- );
- }
- } else {
- const userKey = await this.cryptoService.getUserKey();
- keyToEncrypt = userKey.key;
- }
-
- const encryptedKey = await this.cryptoService.rsaEncrypt(keyToEncrypt, pubKey);
-
- const request = new PasswordlessAuthRequest(
- encryptedKey.encryptedString,
- encryptedMasterKeyHash?.encryptedString,
- await this.appIdService.getAppId(),
- requestApproved,
- );
- return await this.apiService.putAuthRequest(id, request);
- }
-
private async clearCache(): Promise {
await this.currentAuthnTypeState.update((_) => null);
await this.loginStrategyCacheState.update((_) => null);
diff --git a/libs/components/src/section/index.ts b/libs/components/src/section/index.ts
index ea2aa32a22f..9a0983e18e4 100644
--- a/libs/components/src/section/index.ts
+++ b/libs/components/src/section/index.ts
@@ -1 +1,2 @@
export * from "./section.component";
+export * from "./section-header.component";
diff --git a/libs/components/src/section/section-header.component.html b/libs/components/src/section/section-header.component.html
new file mode 100644
index 00000000000..d17022d5eb7
--- /dev/null
+++ b/libs/components/src/section/section-header.component.html
@@ -0,0 +1,8 @@
+
diff --git a/libs/components/src/section/section-header.component.ts b/libs/components/src/section/section-header.component.ts
new file mode 100644
index 00000000000..9f7b1a21f16
--- /dev/null
+++ b/libs/components/src/section/section-header.component.ts
@@ -0,0 +1,16 @@
+import { Component } from "@angular/core";
+
+import { TypographyModule } from "../typography";
+
+@Component({
+ standalone: true,
+ selector: "bit-section-header",
+ templateUrl: "./section-header.component.html",
+ imports: [TypographyModule],
+ host: {
+ class:
+ // apply bottom and x padding when a `bit-card` or `bit-item` is the immediate sibling, or nested in the immediate sibling
+ "tw-block has-[+_*_bit-card]:tw-pb-1 has-[+_bit-card]:tw-pb-1 has-[+_*_bit-item]:tw-pb-1 has-[+_bit-item]:tw-pb-1 has-[+_*_bit-card]:tw-px-1 has-[+_bit-card]:tw-px-1 has-[+_*_bit-item]:tw-px-1 has-[+_bit-item]:tw-px-1",
+ },
+})
+export class SectionHeaderComponent {}
diff --git a/libs/components/src/section/section.mdx b/libs/components/src/section/section.mdx
new file mode 100644
index 00000000000..33e640e4f06
--- /dev/null
+++ b/libs/components/src/section/section.mdx
@@ -0,0 +1,89 @@
+import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs";
+
+import * as stories from "./section.stories";
+
+
+
+```ts
+import { SectionComponent, SectionHeaderComponent } from "@bitwarden/components";
+```
+
+# Section
+
+Sections are simple containers that apply a responsive bottom margin and utilize the semantic
+`section` HTML element.
+
+
+
+## Section Header
+
+Sections often contain a heading. Use `bit-section-header` inside of the `bit-section`.
+
+```html
+
+
+ I'm a section header
+
+ Section content here!
+
+```
+
+### Section Header Padding
+
+When placed inside of a section with a `bit-card` or `bit-item` as the immediate next sibling (or
+nested in the immediate next sibling), the section header will automatically apply bottom and x-axis
+padding to align the header with the border radius of the card/item.
+
+```html
+
+
+ I'm a section header
+
+
+
+ I'm card content
+
+
+```
+
+
+
+If placed inside of a section without a `bit-card` or `bit-item`, or with a `bit-card`/`bit-item`
+that is not a descendant of the immediate next sibling, the padding is not applied.
+
+
+
+### Section Header Content Slots
+
+`bit-section-header` contains the following slots to help position the content:
+
+| Slot | Description |
+| ------------ | ------------------------------- |
+| default | title text of the header |
+| `slot="end"` | placed at the end of the header |
+
+#### Default slot
+
+Anything passed to the default slot will display as part of the title. The title should be a
+`bitTypography` element, usually an `h2` styled as an `h6`.
+
+Title suffixes (typically an icon or icon button) can be added as well. A gap is automatically
+applied between the children of the default slot.
+
+
+
+#### End slot
+
+The `end` slot will typically be used for text or an icon button.
+
+
diff --git a/libs/components/src/section/section.stories.ts b/libs/components/src/section/section.stories.ts
index 65b6a67d476..0f720d1dba0 100644
--- a/libs/components/src/section/section.stories.ts
+++ b/libs/components/src/section/section.stories.ts
@@ -1,15 +1,24 @@
import { Meta, StoryObj, componentWrapperDecorator, moduleMetadata } from "@storybook/angular";
+import { CardComponent } from "../card";
+import { IconButtonModule } from "../icon-button";
+import { ItemModule } from "../item";
import { TypographyModule } from "../typography";
-import { SectionComponent } from "./section.component";
+import { SectionComponent, SectionHeaderComponent } from "./";
export default {
title: "Component Library/Section",
component: SectionComponent,
decorators: [
moduleMetadata({
- imports: [TypographyModule],
+ imports: [
+ TypographyModule,
+ SectionHeaderComponent,
+ CardComponent,
+ IconButtonModule,
+ ItemModule,
+ ],
}),
componentWrapperDecorator((story) => `${story}
`),
],
@@ -17,19 +26,149 @@ export default {
type Story = StoryObj;
-/** Sections are simple containers that apply a responsive bottom margin. They often contain a heading. */
export const Default: Story = {
- render: (args) => ({
- props: args,
- template: `
+ render: () => ({
+ template: /*html*/ `
+
Foo
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.
+
Bar
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.
`,
}),
};
+
+export const HeaderVariants: Story = {
+ render: () => ({
+ template: /*html*/ `
+
+
+ Title only
+
+
+
+
+ Title with icon button suffix
+
+
+
+ `,
+ }),
+};
+
+export const HeaderEndSlotVariants: Story = {
+ render: () => ({
+ template: /*html*/ `
+
+
+ Title with end slot text
+
+ 13
+
+
+
+ Title with end slot icon button
+
+
+
+ `,
+ }),
+};
+
+export const HeaderWithPadding: Story = {
+ render: () => ({
+ template: /*html*/ `
+
+
+
+
+ Card as immediate sibling
+
+
+
+
+ bit-section-header has padding
+
+
+
+
+
+ Card nested in immediate sibling
+
+
+
+
+
+ bit-section-header has padding
+
+
+
+
+
+
+ Item as immediate sibling
+
+
+
+
+ bit-section-header has padding
+
+
+
+
+
+ Item nested in immediate sibling
+
+
+
+
+
+ bit-section-header has padding
+
+
+
+
+ `,
+ }),
+};
+
+export const HeaderWithoutPadding: Story = {
+ render: () => ({
+ template: /*html*/ `
+
+
+
+
+ No card or item used
+
+
+
+
+
just a div, so bit-section-header has no padding
+
+
+
+
+
+ Card nested in non-immediate sibling
+
+
+
+
+ a div here
+
+
+ bit-section-header has no padding
+
+
+
+ `,
+ }),
+};