add tab groups and tenent detail components aswell as active tab directive

This commit is contained in:
Christian Werner 2025-10-29 20:51:14 +01:00
parent 2645de2e9f
commit ec1773909b
11 changed files with 10024 additions and 0 deletions

9767
src/angular/frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
import { ActiveTabDirective } from './active-tab.directive';
describe('ActiveTabDirective', () => {
it('should create an instance', () => {
const directive = new ActiveTabDirective();
expect(directive).toBeTruthy();
});
});

View File

@ -0,0 +1,12 @@
import {Directive, input, TemplateRef} from '@angular/core';
@Directive({
selector: 'ng-template[tabId]'
})
export class ActiveTabDirective {
public tabId = input.required<string>()
constructor(public template: TemplateRef<any>) {}
}

View File

@ -0,0 +1,14 @@
<div class="tabs">
@for (tab of tabs(); track tab.id) {
<button
class="tab secondary"
[ngClass]="{'active': tab.id == activeTabId()}"
(click)="changeTab($event, tab.id)"
>
{{tab.name}}
</button>
}
</div>
<div class="tab-content">
<ng-content></ng-content>
</div>

View File

@ -0,0 +1,35 @@
:host {
.tab-content {
margin-top: 1rem;
}
.tabs {
--_tabGroup_bgColor : var(--neutral-80);
--_tab_bgColor: var(--secondary-30);
--_tab_hoverColor: var(--secondary-30);
display: inline-flex;
gap: 1ch;
background-color: var(--_tabGroup_bgColor);
border-radius: 1000px;
.tab {
padding: .5rem 1rem;
border-radius: 1000px;
background-color: unset;
color: var(--neutral-10);
&.active {
color: var(--neutral-90);
background-color: var(--_tab_bgColor);
}
&:hover {
box-shadow: 0 0 0 1px var(--_tab_hoverColor) inset;
}
}
}
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TabGroupComponent } from './tab-group.component';
describe('TabGroupComponent', () => {
let component: TabGroupComponent;
let fixture: ComponentFixture<TabGroupComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TabGroupComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TabGroupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,80 @@
import {
AfterViewInit,
Component,
ContentChildren,
input,
model,
OnInit,
QueryList,
ViewContainerRef
} from '@angular/core';
import {NgClass} from '@angular/common';
import {ActiveTabDirective} from './active-tab.directive';
import {takeUntilDestroyed, toObservable} from '@angular/core/rxjs-interop';
export interface TabGroup {
id: string;
name: string;
}
@Component({
selector: 'app-tab-group',
imports: [
NgClass
],
templateUrl: './tab-group.component.html',
styleUrl: './tab-group.component.scss',
standalone: true
})
export class TabGroupComponent implements OnInit, AfterViewInit {
public tabs = input.required<TabGroup[]>();
public activeTabId = model<string | null>();
@ContentChildren(ActiveTabDirective) tabTemplates!: QueryList<ActiveTabDirective>;
constructor(private vcr: ViewContainerRef) {
toObservable(this.activeTabId)
.pipe(takeUntilDestroyed())
.subscribe(id => {
if (id)
this.renderTab(id);
})
}
ngOnInit(): void {
if (!this.activeTabId() && this.tabs().length > 0) {
this.activeTabId.set(this.tabs()[0]!.id);
}
}
ngAfterViewInit() {
const activeTabId = this.activeTabId();
if (activeTabId)
this.renderTab(activeTabId);
}
changeTab($event: PointerEvent, id: string) {
$event.preventDefault();
if (id == this.activeTabId()) {
return;
}
this.activeTabId.update(() => id);
}
private renderTab(id: string) {
if (!this.tabTemplates) return;
this.vcr.clear();
const tab = this.tabTemplates.find(t => t.tabId() === this.activeTabId());
if (tab) {
const tabOption = this.tabs().find(x => x.id == id);
this.vcr.createEmbeddedView(tab.template, {$implicit: tabOption});
}
}
}

View File

@ -0,0 +1,23 @@
<app-tab-group [tabs]="tabs">
<ng-template tabId="subjects" let-tab>
{{tab.name}} - content
</ng-template>
<ng-template tabId="apps" let-tab>
{{tab.name}} - content
</ng-template>
<ng-template tabId="groups" let-tab>
{{tab.name}} - content
</ng-template>
<ng-template tabId="roles" let-tab>
{{tab.name}} - content
</ng-template>
<ng-template tabId="authorities" let-tab>
{{tab.name}} - content
</ng-template>
</app-tab-group>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TenantDetailComponent } from './tenant-detail.component';
describe('TenantDetailComponent', () => {
let component: TenantDetailComponent;
let fixture: ComponentFixture<TenantDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TenantDetailComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TenantDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,39 @@
import {Component} from '@angular/core';
import {TabGroup, TabGroupComponent} from '../../tab-group/tab-group.component';
import {ActiveTabDirective} from '../../tab-group/active-tab.directive';
@Component({
selector: 'app-tenant-detail',
imports: [
TabGroupComponent,
ActiveTabDirective
],
templateUrl: './tenant-detail.component.html',
styleUrl: './tenant-detail.component.scss',
})
export class TenantDetailComponent {
protected tabs = [
{
id: "subjects",
name: "Users"
} satisfies TabGroup,
{
id: "apps",
name: "Apps"
} satisfies TabGroup,
{
id: "groups",
name: "Groups"
} satisfies TabGroup,
{
id: "roles",
name: "Roles"
} satisfies TabGroup,
{
id: "authorities",
name: "Authorities"
} satisfies TabGroup
]
}