diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index 942891ba061..678494cd9f8 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -1,10 +1,12 @@ -import { DialogModule, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { DIALOG_DATA, DialogModule, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; import { Meta, moduleMetadata, Story } from "@storybook/angular"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { ButtonModule } from "../button"; +import { IconButtonModule } from "../icon-button"; +import { SharedModule } from "../shared"; import { I18nMockService } from "../utils/i18n-mock.service"; import { DialogService } from "./dialog.service"; @@ -35,7 +37,7 @@ class StoryDialogComponent { @Component({ selector: "story-dialog-content", template: ` - + Dialog Title Dialog body text goes here. @@ -68,7 +70,7 @@ export default { DialogTitleContainerDirective, StoryDialogContentComponent, ], - imports: [ButtonModule, DialogModule], + imports: [SharedModule, ButtonModule, DialogModule, IconButtonModule], providers: [ DialogService, { diff --git a/libs/components/src/tabs/tab-group/tab-group.component.html b/libs/components/src/tabs/tab-group/tab-group.component.html index dbe71fb4b99..071f5c2259f 100644 --- a/libs/components/src/tabs/tab-group/tab-group.component.html +++ b/libs/components/src/tabs/tab-group/tab-group.component.html @@ -34,7 +34,7 @@ role="tabpanel" *ngFor="let tab of tabs; let i = index" [id]="getTabContentId(i)" - [attr.tabindex]="selectedIndex === i ? 0 : -1" + [attr.tabindex]="tab.contentTabIndex" [attr.labeledby]="getTabLabelId(i)" [active]="tab.isActive" [content]="tab.content" diff --git a/libs/components/src/tabs/tab-group/tab.component.ts b/libs/components/src/tabs/tab-group/tab.component.ts index 05e61ffacc2..9a3a9380031 100644 --- a/libs/components/src/tabs/tab-group/tab.component.ts +++ b/libs/components/src/tabs/tab-group/tab.component.ts @@ -20,9 +20,18 @@ import { TabLabelDirective } from "./tab-label.directive"; }) export class TabComponent implements OnInit { @Input() disabled = false; - @Input("label") textLabel = ""; + /** + * Optional tabIndex for the tabPanel that contains this tab's content. + * + * If the tabpanel does not contain any focusable elements or the first element with content is not focusable, + * this should be set to 0 to include it in the tab sequence of the page. + * + * @remarks See note 4 of https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/ + */ + @Input() contentTabIndex: number | undefined; + @ViewChild(TemplateRef, { static: true }) implicitContent: TemplateRef; @ContentChild(TabLabelDirective) templateLabel: TabLabelDirective; diff --git a/libs/components/src/tabs/tabs.stories.ts b/libs/components/src/tabs/tabs.stories.ts index a520b50e17a..32c7e950e73 100644 --- a/libs/components/src/tabs/tabs.stories.ts +++ b/libs/components/src/tabs/tabs.stories.ts @@ -3,6 +3,9 @@ import { Component } from "@angular/core"; import { RouterModule } from "@angular/router"; import { Meta, moduleMetadata, Story } from "@storybook/angular"; +import { ButtonModule } from "../button"; +import { FormFieldModule } from "../form-field"; + import { TabGroupComponent } from "./tab-group/tab-group.component"; import { TabsModule } from "./tabs.module"; @@ -44,6 +47,8 @@ export default { imports: [ CommonModule, TabsModule, + ButtonModule, + FormFieldModule, RouterModule.forRoot( [ { path: "", redirectTo: "active", pathMatch: "full" }, @@ -125,3 +130,32 @@ const PreserveContentTabGroupTemplate: Story = (args: any) => }); export const PreserveContentTabs = PreserveContentTabGroupTemplate.bind({}); + +const KeyboardNavTabGroupTemplate: Story = (args: any) => ({ + props: args, + template: ` + + +

+ You can navigate through all tab labels, form inputs, and the button that is outside the tab group via + the keyboard. +

+ + First Input + + + + Second Input + + +
+ + +

This tab has no focusable content, but the panel should still be focusable

+
+
+ +`, +}); + +export const KeyboardNavigation = KeyboardNavTabGroupTemplate.bind({});