From 50f8fbf56b485f49dcb31aa64c19b6c69077f2ab Mon Sep 17 00:00:00 2001 From: Will Martin Date: Sat, 12 Jul 2025 23:21:34 -0400 Subject: [PATCH] [CL-724] Vault list item keyboard nav dialog (#15591) * add focus-visible-within util * wip: add grid a11y dialog to vault * add story and i18n --- apps/browser/src/_locales/en/messages.json | 12 +++++ ...tem-group-navigation-dialog.component.html | 26 ++++++++++ ...-item-group-navigation-dialog.component.ts | 10 ++++ ...lt-item-group-navigation-dialog.service.ts | 37 +++++++++++++++ ...lt-item-group-navigation-dialog.stories.ts | 38 +++++++++++++++ .../vault-list-items-container.component.html | 6 +-- .../vault-list-items-container.component.ts | 9 ++++ .../src/platform/state/state-definitions.ts | 4 ++ libs/components/src/a11y/index.ts | 3 ++ .../src/utils/focus-visible-within.ts | 47 +++++++++++++++++++ libs/components/src/utils/index.ts | 1 + 11 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.component.html create mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.component.ts create mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.service.ts create mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.stories.ts create mode 100644 libs/components/src/utils/focus-visible-within.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index b6a8d1834b4..9aed702017d 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5419,5 +5419,17 @@ "wasmNotSupported": { "message": "WebAssembly is not supported on your browser or is not enabled. WebAssembly is required to use the Bitwarden app.", "description": "'WebAssembly' is a technical term and should not be translated." + }, + "keyboardNavigationBehavior": { + "message": "Keyboard navigation behavior" + }, + "keyboardNavigationBehaviorUpDown": { + "message": "Use up/down arrow keys (↑ ↓) to move between items" + }, + "keyboardNavigationBehaviorRightLeft": { + "message": "Use right/left arrow keys (→ ←) to move between item actions" + }, + "keyboardNavigationBehaviorTab": { + "message": "Use tab key (↹) to jump to the next focusable section on the page" } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.component.html new file mode 100644 index 00000000000..3f017427ad8 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.component.html @@ -0,0 +1,26 @@ + + + + + + {{ "keyboardNavigationBehavior" | i18n }} + + +
    +
  • + {{ "keyboardNavigationBehaviorUpDown" | i18n }} +
  • +
  • + {{ "keyboardNavigationBehaviorRightLeft" | i18n }} +
  • +
  • + {{ "keyboardNavigationBehaviorTab" | i18n }} +
  • +
+
+ + + +
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.component.ts new file mode 100644 index 00000000000..04e058f9368 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.component.ts @@ -0,0 +1,10 @@ +import { Component } from "@angular/core"; + +import { ButtonModule, DialogModule, TypographyModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +@Component({ + imports: [DialogModule, ButtonModule, TypographyModule, I18nPipe], + templateUrl: "vault-item-group-navigation-dialog.component.html", +}) +export class VaultItemGroupNavigationDialogComponent {} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.service.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.service.ts new file mode 100644 index 00000000000..ab2f96bd2e8 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.service.ts @@ -0,0 +1,37 @@ +import { inject, Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { + KeyDefinition, + StateProvider, + VAULT_ITEM_GROUP_NAVIGATION_DIALOG, +} from "@bitwarden/common/platform/state"; +import { DialogService } from "@bitwarden/components"; + +import { VaultItemGroupNavigationDialogComponent } from "./vault-item-group-navigation-dialog.component"; + +const VAULT_ITEM_GROUP_NAVIGATION_DIALOG_SHOWN = new KeyDefinition( + VAULT_ITEM_GROUP_NAVIGATION_DIALOG, + "dialogShown", + { + deserializer: (obj) => obj, + }, +); + +@Injectable({ + providedIn: "root", +}) +export class VaultItemGroupNavigationDialogService { + private dialogService = inject(DialogService); + private shownState = inject(StateProvider).getGlobal(VAULT_ITEM_GROUP_NAVIGATION_DIALOG_SHOWN); + + /** Opens the dialog if it hasn't been opened before. */ + async openOnce() { + if (await firstValueFrom(this.shownState.state$)) { + return; + } + const dialogRef = this.dialogService.open(VaultItemGroupNavigationDialogComponent); + await this.shownState.update(() => true); + return dialogRef; + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.stories.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.stories.ts new file mode 100644 index 00000000000..689cf46e25d --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/navigation-dialog/vault-item-group-navigation-dialog.stories.ts @@ -0,0 +1,38 @@ +import { provideNoopAnimations } from "@angular/platform-browser/animations"; +import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nMockService } from "@bitwarden/components"; + +import { VaultItemGroupNavigationDialogComponent } from "./vault-item-group-navigation-dialog.component"; + +export default { + title: "Browser/Vault/Item Group Navigation Dialog", + component: VaultItemGroupNavigationDialogComponent, + decorators: [ + moduleMetadata({ + imports: [], + providers: [ + { + provide: I18nService, + useFactory: () => + new I18nMockService({ + keyboardNavigationBehavior: "Keyboard navigation behavior", + keyboardNavigationBehaviorUpDown: + "Use up/down arrow keys (↑ ↓) to move between items", + keyboardNavigationBehaviorRightLeft: + "Use right/left arrow keys (→ ←) to move between item actions", + keyboardNavigationBehaviorTab: + "Use tab key (↹) to jump to the next focusable section on the page", + gotIt: "Got it", + }), + }, + ], + }), + applicationConfig({ + providers: [provideNoopAnimations()], + }), + ], +} as Meta; + +export const Default: StoryObj = {}; 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 adb672f33df..ab732c003a6 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 @@ -33,9 +33,7 @@ -

- {{ title }} -

+

foobar