mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
✨ feat(shell-tabs): scaffold shell-tabs library with tab components
Adds new shell-tabs library with ShellTabsComponent and ShellTabItemComponent for managing application tabs in the shell header area.
This commit is contained in:
7
libs/shell/tabs/README.md
Normal file
7
libs/shell/tabs/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# shell-tabs
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test shell-tabs` to execute the unit tests.
|
||||
34
libs/shell/tabs/eslint.config.cjs
Normal file
34
libs/shell/tabs/eslint.config.cjs
Normal file
@@ -0,0 +1,34 @@
|
||||
const nx = require('@nx/eslint-plugin');
|
||||
const baseConfig = require('../../../eslint.config.js');
|
||||
|
||||
module.exports = [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'shell',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'shell',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
20
libs/shell/tabs/project.json
Normal file
20
libs/shell/tabs/project.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "shell-tabs",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/shell/tabs/src",
|
||||
"prefix": "shell",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/vite:test",
|
||||
"outputs": ["{options.reportsDirectory}"],
|
||||
"options": {
|
||||
"reportsDirectory": "../../../coverage/libs/shell/tabs"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
1
libs/shell/tabs/src/index.ts
Normal file
1
libs/shell/tabs/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './lib/shell-tabs.component';
|
||||
@@ -0,0 +1,23 @@
|
||||
:host {
|
||||
@apply block opacity-40;
|
||||
}
|
||||
|
||||
:host.active {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply h-14;
|
||||
}
|
||||
|
||||
a.compact {
|
||||
@apply h-[1.625rem] items-center;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply size-6;
|
||||
}
|
||||
|
||||
a.compact button {
|
||||
@apply size-[1.625rem];
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<!-- <a
|
||||
class="w-full flex flex-col min-w-[10rem] max-w-[11.5rem] px-3 pt-2 items-start gap-2 rounded-t-2xl bg-isa-neutral-200 overflow-hidden transition-[height] duration-200"
|
||||
[class.h-14]="!compact()"
|
||||
[class.h-8]="compact()"
|
||||
[routerLink]="route().url"
|
||||
[title]="route().title"
|
||||
>
|
||||
<div class="isa-text-caption-bold truncate w-full pr-6">
|
||||
{{ tab().name }}
|
||||
</div>
|
||||
@if (!compact()) {
|
||||
<div class="isa-text-caption-regular truncate w-full">
|
||||
{{ tab().subtitle }}
|
||||
</div>
|
||||
}
|
||||
</a>
|
||||
<button
|
||||
class="absolute top-2 right-3"
|
||||
(click)="$event.stopPropagation(); close()"
|
||||
>
|
||||
<ng-icon
|
||||
name="isaActionClose"
|
||||
[size]="compact() ? '.75rem' : '1.5rem'"
|
||||
></ng-icon>
|
||||
</button> -->
|
||||
|
||||
<a
|
||||
class="w-full flex flex-row min-w-[10rem] max-w-[11.5rem] px-3 pt-2 items-start gap-2 rounded-t-2xl bg-isa-neutral-200 overflow-hidden transition-[height] duration-200"
|
||||
[class.compact]="compact()"
|
||||
[routerLink]="route().url"
|
||||
[title]="route().title"
|
||||
>
|
||||
<!-- <div class="grow-0"></div> -->
|
||||
<div class="grow">
|
||||
<div class="isa-text-caption-bold truncate w-full">
|
||||
{{ tab().name }}
|
||||
</div>
|
||||
@if (!compact()) {
|
||||
<div class="isa-text-caption-regular truncate w-full">
|
||||
{{ tab().subtitle }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button
|
||||
(click)="$event.stopPropagation(); close()"
|
||||
class="grow-0 transition-all duration-200"
|
||||
>
|
||||
<ng-icon
|
||||
name="isaActionClose"
|
||||
[size]="compact() ? '.75rem' : '1.5rem'"
|
||||
></ng-icon>
|
||||
</button>
|
||||
</a>
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
input,
|
||||
inject,
|
||||
computed,
|
||||
} from '@angular/core';
|
||||
import { Tab } from '@isa/core/tabs';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionClose } from '@isa/icons';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { RouterLinkWithHref } from '@angular/router';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'shell-tab-item',
|
||||
templateUrl: './shell-tab-item.component.html',
|
||||
styleUrls: ['./shell-tab-item.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [NgIcon, RouterLinkWithHref],
|
||||
providers: [provideIcons({ isaActionClose })],
|
||||
host: {
|
||||
'[class.active]': 'active()',
|
||||
},
|
||||
})
|
||||
export class ShellTabItemComponent {
|
||||
#router = inject(Router);
|
||||
#tabService = inject(TabService);
|
||||
|
||||
tab = input.required<Tab>();
|
||||
|
||||
compact = input<boolean>();
|
||||
|
||||
active = computed(() => {
|
||||
const activeId = this.#tabService.activatedTabId();
|
||||
return activeId === this.tab().id;
|
||||
});
|
||||
|
||||
route = computed(() => {
|
||||
const tab = this.tab();
|
||||
return tab.location.locations[tab.location.current];
|
||||
});
|
||||
|
||||
async close() {
|
||||
await this.#router.navigateByUrl('/'); // Navigate away before closing to avoid errors
|
||||
this.#tabService.removeTab(this.tab().id);
|
||||
}
|
||||
}
|
||||
3
libs/shell/tabs/src/lib/shell-tabs.component.css
Normal file
3
libs/shell/tabs/src/lib/shell-tabs.component.css
Normal file
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply block;
|
||||
}
|
||||
5
libs/shell/tabs/src/lib/shell-tabs.component.html
Normal file
5
libs/shell/tabs/src/lib/shell-tabs.component.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<ui-carousel>
|
||||
@for (tab of tabs(); track tab.id) {
|
||||
<shell-tab-item [tab]="tab" [compact]="compact()" />
|
||||
}
|
||||
</ui-carousel>
|
||||
43
libs/shell/tabs/src/lib/shell-tabs.component.ts
Normal file
43
libs/shell/tabs/src/lib/shell-tabs.component.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Component, inject, computed, signal, ElementRef } from '@angular/core';
|
||||
import { ShellTabItemComponent } from './components/shell-tab-item.component';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { CarouselComponent } from '@isa/ui/carousel';
|
||||
|
||||
const PROXIMITY_THRESHOLD_PX = 50;
|
||||
|
||||
@Component({
|
||||
selector: 'shell-tabs',
|
||||
imports: [ShellTabItemComponent, CarouselComponent],
|
||||
templateUrl: './shell-tabs.component.html',
|
||||
styleUrl: './shell-tabs.component.css',
|
||||
host: {
|
||||
'(document:mousemove)': 'onMouseMove($event)',
|
||||
},
|
||||
})
|
||||
export class ShellTabsComponent {
|
||||
#tabService = inject(TabService);
|
||||
#elementRef = inject(ElementRef);
|
||||
|
||||
readonly tabs = this.#tabService.entities;
|
||||
|
||||
#isNear = signal(false);
|
||||
|
||||
compact = computed(() => !this.#isNear());
|
||||
|
||||
onMouseMove(event: MouseEvent): void {
|
||||
const rect = this.#elementRef.nativeElement.getBoundingClientRect();
|
||||
const mouseY = event.clientY;
|
||||
const mouseX = event.clientX;
|
||||
|
||||
const isWithinX = mouseX >= rect.left && mouseX <= rect.right;
|
||||
const distanceY =
|
||||
mouseY < rect.top
|
||||
? rect.top - mouseY
|
||||
: mouseY > rect.bottom
|
||||
? mouseY - rect.bottom
|
||||
: 0;
|
||||
|
||||
const isNear = isWithinX && distanceY <= PROXIMITY_THRESHOLD_PX;
|
||||
this.#isNear.set(isNear);
|
||||
}
|
||||
}
|
||||
13
libs/shell/tabs/src/test-setup.ts
Normal file
13
libs/shell/tabs/src/test-setup.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import '@angular/compiler';
|
||||
import '@analogjs/vitest-angular/setup-zone';
|
||||
|
||||
import {
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting,
|
||||
} from '@angular/platform-browser/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting(),
|
||||
);
|
||||
30
libs/shell/tabs/tsconfig.json
Normal file
30
libs/shell/tabs/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "preserve"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"typeCheckHostBindings": true,
|
||||
"strictTemplates": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
27
libs/shell/tabs/tsconfig.lib.json
Normal file
27
libs/shell/tabs/tsconfig.lib.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/test-setup.ts",
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
29
libs/shell/tabs/tsconfig.spec.json
Normal file
29
libs/shell/tabs/tsconfig.spec.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"types": [
|
||||
"vitest/globals",
|
||||
"vitest/importMeta",
|
||||
"vite/client",
|
||||
"node",
|
||||
"vitest"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"vite.config.mts",
|
||||
"vitest.config.ts",
|
||||
"vitest.config.mts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
],
|
||||
"files": ["src/test-setup.ts"]
|
||||
}
|
||||
27
libs/shell/tabs/vite.config.mts
Normal file
27
libs/shell/tabs/vite.config.mts
Normal file
@@ -0,0 +1,27 @@
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../../node_modules/.vite/libs/shell/tabs',
|
||||
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: ['default'],
|
||||
coverage: {
|
||||
reportsDirectory: '../../../coverage/libs/shell/tabs',
|
||||
provider: 'v8' as const,
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -150,6 +150,7 @@
|
||||
"@isa/shell/layout": ["libs/shell/layout/src/index.ts"],
|
||||
"@isa/shell/navigation": ["libs/shell/navigation/src/index.ts"],
|
||||
"@isa/shell/notifications": ["libs/shell/notifications/src/index.ts"],
|
||||
"@isa/shell/tabs": ["libs/shell/tabs/src/index.ts"],
|
||||
"@isa/ui/bullet-list": ["libs/ui/bullet-list/src/index.ts"],
|
||||
"@isa/ui/buttons": ["libs/ui/buttons/src/index.ts"],
|
||||
"@isa/ui/carousel": ["libs/ui/carousel/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user