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:
parent
3371760779
commit
070d8556cf
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
|
@ -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>
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user