mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 01:33:33 +00:00
[EC 456] Component Library Content Switching Tabs (#3452)
* [EC-456] Rename bitTabItem -> bitTab * [EC-456] Use templateRefs or text for tab label content * [EC-456] Add bit-tab-nav-bar component * [EC-456] Finish content tab switching and nav tabs * [EC-456] Undo accidental eslintrc.json change * [EC-456] Fix directive/component selector naming convention * [EC-456] Cleanup unnecessary InjectionTokens and simplify template label property * [EC-456] Cleanup one more unnecessary InjectionToken * [EC-456] Cleanup tab styles to better match Figma. Add internal tab header component for styling header background according to Figma. * [EC-456] Add sub-folders for nav, content, and shared tab components/directives * [EC-456] Code/style cleanup * [EC-456] Remove underscore from protected members * [EC-456] Cleanup tab stories and forgotten any type. * [EC-456] Fix dark theme story tab content text color * [EC-456] Add missing padding to tab header * [EC-456] Add tab content padding to align with tab headers * [EC-456] Move bottom tab border to header to span entire content area * [EC-456] Force text-main tab label color * [EC-456] Undo text-main change
This commit is contained in:
13
libs/components/src/tabs/tab-nav-bar/tab-link.component.html
Normal file
13
libs/components/src/tabs/tab-nav-bar/tab-link.component.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<a
|
||||
bitTabListItem
|
||||
[routerLink]="disabled ? null : route"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
[active]="rla.isActive"
|
||||
[disabled]="disabled"
|
||||
[attr.aria-disabled]="disabled"
|
||||
ariaCurrentWhenActive="page"
|
||||
role="link"
|
||||
>
|
||||
<ng-content></ng-content>
|
||||
</a>
|
||||
51
libs/components/src/tabs/tab-nav-bar/tab-link.component.ts
Normal file
51
libs/components/src/tabs/tab-nav-bar/tab-link.component.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { FocusableOption } from "@angular/cdk/a11y";
|
||||
import { AfterViewInit, Component, HostListener, Input, OnDestroy, ViewChild } from "@angular/core";
|
||||
import { RouterLinkActive } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { TabListItemDirective } from "../shared/tab-list-item.directive";
|
||||
|
||||
import { TabNavBarComponent } from "./tab-nav-bar.component";
|
||||
|
||||
@Component({
|
||||
selector: "bit-tab-link",
|
||||
templateUrl: "tab-link.component.html",
|
||||
})
|
||||
export class TabLinkComponent implements FocusableOption, AfterViewInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@ViewChild(TabListItemDirective) tabItem: TabListItemDirective;
|
||||
@ViewChild("rla") routerLinkActive: RouterLinkActive;
|
||||
|
||||
@Input() route: string;
|
||||
@Input() disabled = false;
|
||||
|
||||
@HostListener("keydown", ["$event"]) onKeyDown(event: KeyboardEvent) {
|
||||
if (event.code === "Space") {
|
||||
this.tabItem.click();
|
||||
}
|
||||
}
|
||||
|
||||
get active() {
|
||||
return this.routerLinkActive?.isActive ?? false;
|
||||
}
|
||||
|
||||
constructor(private _tabNavBar: TabNavBarComponent) {}
|
||||
|
||||
focus(): void {
|
||||
this.tabItem.focus();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
// The active state of tab links are tracked via the routerLinkActive directive
|
||||
// We need to watch for changes to tell the parent nav group when the tab is active
|
||||
this.routerLinkActive.isActiveChange
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((_) => this._tabNavBar.updateActiveLink());
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<bit-tab-header>
|
||||
<nav bitTabListContainer [attr.aria-label]="label" (keydown)="keyManager.onKeydown($event)">
|
||||
<ng-content></ng-content>
|
||||
</nav>
|
||||
</bit-tab-header>
|
||||
@@ -0,0 +1,43 @@
|
||||
import { FocusKeyManager } from "@angular/cdk/a11y";
|
||||
import {
|
||||
AfterContentInit,
|
||||
Component,
|
||||
ContentChildren,
|
||||
forwardRef,
|
||||
Input,
|
||||
QueryList,
|
||||
} from "@angular/core";
|
||||
|
||||
import { TabLinkComponent } from "./tab-link.component";
|
||||
|
||||
@Component({
|
||||
selector: "bit-tab-nav-bar",
|
||||
templateUrl: "tab-nav-bar.component.html",
|
||||
})
|
||||
export class TabNavBarComponent implements AfterContentInit {
|
||||
@ContentChildren(forwardRef(() => TabLinkComponent)) tabLabels: QueryList<TabLinkComponent>;
|
||||
@Input() label = "";
|
||||
|
||||
/**
|
||||
* Focus key manager for keeping tab controls accessible.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tablist_role#keyboard_interactions
|
||||
*/
|
||||
keyManager: FocusKeyManager<TabLinkComponent>;
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
this.keyManager = new FocusKeyManager(this.tabLabels)
|
||||
.withHorizontalOrientation("ltr")
|
||||
.withWrap()
|
||||
.withHomeAndEnd();
|
||||
}
|
||||
|
||||
updateActiveLink() {
|
||||
// Keep the keyManager in sync with active tabs
|
||||
const items = this.tabLabels.toArray();
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].active) {
|
||||
this.keyManager.updateActiveItem(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user