1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-28 12:45:45 +01:00

[CL-179][PM-6011] open parent bit-nav-groups when bit-nav-item becomes active (#7801)

* add routing to nav-group stories

* open parent nav group when active
This commit is contained in:
Will Martin 2024-02-08 10:35:13 -05:00 committed by GitHub
parent 3371760779
commit 070d8556cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 69 additions and 35 deletions

View File

@ -3,29 +3,25 @@ import {
Component, Component,
ContentChildren, ContentChildren,
EventEmitter, EventEmitter,
forwardRef,
Input, Input,
Optional,
Output, Output,
QueryList, QueryList,
SkipSelf,
} from "@angular/core"; } from "@angular/core";
import { NavBaseComponent } from "./nav-base.component"; import { NavBaseComponent } from "./nav-base.component";
import { NavItemComponent } from "./nav-item.component";
@Component({ @Component({
selector: "bit-nav-group", selector: "bit-nav-group",
templateUrl: "./nav-group.component.html", templateUrl: "./nav-group.component.html",
providers: [{ provide: NavBaseComponent, useExisting: NavGroupComponent }],
}) })
export class NavGroupComponent extends NavBaseComponent implements AfterContentInit { export class NavGroupComponent extends NavBaseComponent implements AfterContentInit {
@ContentChildren(forwardRef(() => NavGroupComponent), { @ContentChildren(NavBaseComponent, {
descendants: true, descendants: true,
}) })
nestedGroups!: QueryList<NavGroupComponent>; nestedNavComponents!: QueryList<NavBaseComponent>;
@ContentChildren(NavItemComponent, {
descendants: true,
})
nestedItems!: QueryList<NavItemComponent>;
/** The parent nav item should not show active styles when open. */ /** The parent nav item should not show active styles when open. */
protected get parentHideActiveStyles(): boolean { protected get parentHideActiveStyles(): boolean {
@ -51,10 +47,19 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI
@Output() @Output()
openChange = new EventEmitter<boolean>(); openChange = new EventEmitter<boolean>();
constructor(@Optional() @SkipSelf() private parentNavGroup: NavGroupComponent) {
super();
}
setOpen(isOpen: boolean) {
this.open = isOpen;
this.openChange.emit(this.open);
this.open && this.parentNavGroup?.setOpen(this.open);
}
protected toggle(event?: MouseEvent) { protected toggle(event?: MouseEvent) {
event?.stopPropagation(); event?.stopPropagation();
this.open = !this.open; this.setOpen(!this.open);
this.openChange.emit(this.open);
} }
/** /**
@ -64,7 +69,7 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI
if (this.variant !== "tree") { if (this.variant !== "tree") {
return; return;
} }
[...this.nestedGroups, ...this.nestedItems].forEach((navGroupOrItem) => { [...this.nestedNavComponents].forEach((navGroupOrItem) => {
navGroupOrItem.treeDepth += 1; navGroupOrItem.treeDepth += 1;
}); });
} }

View File

@ -1,5 +1,6 @@
import { RouterTestingModule } from "@angular/router/testing"; import { Component, importProvidersFrom } from "@angular/core";
import { StoryObj, Meta, moduleMetadata } from "@storybook/angular"; import { RouterModule } from "@angular/router";
import { StoryObj, Meta, moduleMetadata, applicationConfig } from "@storybook/angular";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -9,12 +10,18 @@ import { I18nMockService } from "../utils/i18n-mock.service";
import { NavGroupComponent } from "./nav-group.component"; import { NavGroupComponent } from "./nav-group.component";
import { NavigationModule } from "./navigation.module"; import { NavigationModule } from "./navigation.module";
@Component({
standalone: true,
template: "",
})
class DummyContentComponent {}
export default { export default {
title: "Component Library/Nav/Nav Group", title: "Component Library/Nav/Nav Group",
component: NavGroupComponent, component: NavGroupComponent,
decorators: [ decorators: [
moduleMetadata({ moduleMetadata({
imports: [SharedModule, RouterTestingModule, NavigationModule], imports: [SharedModule, RouterModule, NavigationModule, DummyContentComponent],
providers: [ providers: [
{ {
provide: I18nService, provide: I18nService,
@ -27,6 +34,19 @@ export default {
}, },
], ],
}), }),
applicationConfig({
providers: [
importProvidersFrom(
RouterModule.forRoot(
[
{ path: "", redirectTo: "a", pathMatch: "full" },
{ path: "**", component: DummyContentComponent },
],
{ useHash: true },
),
),
],
}),
], ],
parameters: { parameters: {
design: { design: {
@ -40,10 +60,10 @@ export const Default: StoryObj<NavGroupComponent> = {
render: (args) => ({ render: (args) => ({
props: args, props: args,
template: ` template: `
<bit-nav-group text="Hello World (Anchor)" [route]="['']" icon="bwi-filter" [open]="true"> <bit-nav-group text="Hello World (Anchor)" [route]="['a']" icon="bwi-filter">
<bit-nav-item text="Child A" route="#" icon="bwi-filter"></bit-nav-item> <bit-nav-item text="Child A" route="a" icon="bwi-filter"></bit-nav-item>
<bit-nav-item text="Child B" route="#"></bit-nav-item> <bit-nav-item text="Child B" route="b"></bit-nav-item>
<bit-nav-item text="Child C" route="#" icon="bwi-filter"></bit-nav-item> <bit-nav-item text="Child C" route="c" icon="bwi-filter"></bit-nav-item>
</bit-nav-group> </bit-nav-group>
<bit-nav-group text="Lorem Ipsum (Button)" icon="bwi-filter"> <bit-nav-group text="Lorem Ipsum (Button)" icon="bwi-filter">
<bit-nav-item text="Child A" icon="bwi-filter"></bit-nav-item> <bit-nav-item text="Child A" icon="bwi-filter"></bit-nav-item>
@ -59,19 +79,19 @@ export const Tree: StoryObj<NavGroupComponent> = {
props: args, props: args,
template: ` template: `
<bit-nav-group text="Tree example" icon="bwi-collection" [open]="true"> <bit-nav-group text="Tree example" icon="bwi-collection" [open]="true">
<bit-nav-group text="Level 1 - with children (empty)" route="#" icon="bwi-collection" variant="tree"></bit-nav-group> <bit-nav-group text="Level 1 - with children (empty)" route="t1" icon="bwi-collection" variant="tree"></bit-nav-group>
<bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item> <bit-nav-item text="Level 1 - no children" route="t2" icon="bwi-collection" variant="tree"></bit-nav-item>
<bit-nav-group text="Level 1 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true"> <bit-nav-group text="Level 1 - with children" route="t3" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-group text="Level 2 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true"> <bit-nav-group text="Level 2 - with children" route="t4" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-item text="Level 3 - no children, no icon" route="#" variant="tree"></bit-nav-item> <bit-nav-item text="Level 3 - no children, no icon" route="t5" variant="tree"></bit-nav-item>
<bit-nav-group text="Level 3 - with children" route="#" icon="bwi-collection" variant="tree" [open]="true"> <bit-nav-group text="Level 3 - with children" route="t6" icon="bwi-collection" variant="tree" [open]="true">
<bit-nav-item text="Level 4 - no children, no icon" route="#" variant="tree"></bit-nav-item> <bit-nav-item text="Level 4 - no children, no icon" route="t7" variant="tree"></bit-nav-item>
</bit-nav-group> </bit-nav-group>
</bit-nav-group> </bit-nav-group>
<bit-nav-group text="Level 2 - with children (empty)" route="#" icon="bwi-collection" variant="tree" [open]="true"></bit-nav-group> <bit-nav-group text="Level 2 - with children (empty)" route="t8" icon="bwi-collection" variant="tree" [open]="true"></bit-nav-group>
<bit-nav-item text="Level 2 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item> <bit-nav-item text="Level 2 - no children" route="t9" icon="bwi-collection" variant="tree"></bit-nav-item>
</bit-nav-group> </bit-nav-group>
<bit-nav-item text="Level 1 - no children" route="#" icon="bwi-collection" variant="tree"></bit-nav-item> <bit-nav-item text="Level 1 - no children" route="t10" icon="bwi-collection" variant="tree"></bit-nav-item>
</bit-nav-group> </bit-nav-group>
`, `,
}), }),

View File

@ -55,7 +55,7 @@
routerLinkActive routerLinkActive
[routerLinkActiveOptions]="rlaOptions" [routerLinkActiveOptions]="rlaOptions"
[ariaCurrentWhenActive]="'page'" [ariaCurrentWhenActive]="'page'"
(isActiveChange)="setActive($event)" (isActiveChange)="setIsActive($event)"
(click)="mainContentClicked.emit()" (click)="mainContentClicked.emit()"
> >
<ng-container *ngTemplateOutlet="anchorAndButtonContent"></ng-container> <ng-container *ngTemplateOutlet="anchorAndButtonContent"></ng-container>

View File

@ -1,23 +1,28 @@
import { Component, HostListener, Input } from "@angular/core"; import { Component, HostListener, Input, Optional } from "@angular/core";
import { IsActiveMatchOptions } from "@angular/router"; import { IsActiveMatchOptions } from "@angular/router";
import { BehaviorSubject, map } from "rxjs"; import { BehaviorSubject, map } from "rxjs";
import { NavBaseComponent } from "./nav-base.component"; import { NavBaseComponent } from "./nav-base.component";
import { NavGroupComponent } from "./nav-group.component";
@Component({ @Component({
selector: "bit-nav-item", selector: "bit-nav-item",
templateUrl: "./nav-item.component.html", templateUrl: "./nav-item.component.html",
providers: [{ provide: NavBaseComponent, useExisting: NavItemComponent }],
}) })
export class NavItemComponent extends NavBaseComponent { export class NavItemComponent extends NavBaseComponent {
/** /**
* Is `true` if `to` matches the current route * Is `true` if `to` matches the current route
*/ */
private _active = false; private _isActive = false;
protected setActive(isActive: boolean) { protected setIsActive(isActive: boolean) {
this._active = isActive; this._isActive = isActive;
if (this._isActive && this.parentNavGroup) {
this.parentNavGroup.setOpen(true);
}
} }
protected get showActiveStyles() { protected get showActiveStyles() {
return this._active && !this.hideActiveStyles; return this._isActive && !this.hideActiveStyles;
} }
protected rlaOptions: IsActiveMatchOptions = { protected rlaOptions: IsActiveMatchOptions = {
paths: "subset", paths: "subset",
@ -54,4 +59,8 @@ export class NavItemComponent extends NavBaseComponent {
onFocusOut() { onFocusOut() {
this.focusVisibleWithin$.next(false); this.focusVisibleWithin$.next(false);
} }
constructor(@Optional() private parentNavGroup: NavGroupComponent) {
super();
}
} }