demo
This commit is contained in:
@@ -1 +1 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet style="height: 100vh; width: 100vw;"></router-outlet>
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
|
import { provideAnimations } from '@angular/platform-browser/animations'; // Importieren
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideClientHydration(withEventReplay())]
|
providers: [
|
||||||
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
|
provideRouter(routes),
|
||||||
|
provideClientHydration(withEventReplay()),
|
||||||
|
provideAnimations() // Hier hinzufügen
|
||||||
|
]
|
||||||
};
|
};
|
||||||
@@ -674,3 +674,122 @@
|
|||||||
<!-- Ende .dashboard-grid -->
|
<!-- Ende .dashboard-grid -->
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- =================================================================== -->
|
||||||
|
<!-- DYNAMISCHER DIALOG (Standardmäßig unsichtbar) -->
|
||||||
|
<!-- =================================================================== -->
|
||||||
|
<div
|
||||||
|
class="dialog-backdrop"
|
||||||
|
*ngIf="isDialogOpen"
|
||||||
|
(click)="closeDialog()"
|
||||||
|
[@fade]> <!-- Angular Animation -->
|
||||||
|
|
||||||
|
<!-- (click.stop) verhindert, dass der Klick auf den Container den Backdrop schließt -->
|
||||||
|
<div class="dialog-container card" (click)="$event.stopPropagation()" [@slideIn]>
|
||||||
|
|
||||||
|
<div class="dialog-header">
|
||||||
|
<h3 class="dialog-title">Aktion bestätigen</h3>
|
||||||
|
<!-- (click) ruft die closeDialog Methode auf -->
|
||||||
|
<button class="btn btn-icon" (click)="closeDialog()" data-tooltip="Schließen">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dialog-content">
|
||||||
|
<div class="dialog-icon-container">
|
||||||
|
<!-- Ein Icon, um die Art des Dialogs zu verdeutlichen -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
|
||||||
|
</div>
|
||||||
|
<p>Sind Sie sicher, dass Sie die Bestellung <strong>#10543</strong> löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dialog-actions">
|
||||||
|
<!-- (click) ruft die closeDialog Methode auf -->
|
||||||
|
<button class="btn btn-stroked" (click)="closeDialog()">Abbrechen</button>
|
||||||
|
<button class="btn btn-danger" (click)="closeDialog()">Ja, endgültig löschen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="menu-container">
|
||||||
|
<button class="btn btn-icon">...</button> <!-- Der Auslöser -->
|
||||||
|
|
||||||
|
<div class="menu-panel card"> <!-- Das Panel, das erscheint -->
|
||||||
|
<a href="#" class="menu-item">Bearbeiten</a>
|
||||||
|
<a href="#" class="menu-item">Kopieren</a>
|
||||||
|
<hr class="divider">
|
||||||
|
<a href="#" class="menu-item menu-item-danger">Löschen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="skeleton-card card">
|
||||||
|
<div class="skeleton-item skeleton-avatar"></div>
|
||||||
|
<div class="skeleton-content">
|
||||||
|
<div class="skeleton-item skeleton-line"></div>
|
||||||
|
<div class="skeleton-item skeleton-line" style="width: 70%;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stepper">
|
||||||
|
<div class="step step-complete"><div class="step-icon">✓</div><span>Profil</span></div>
|
||||||
|
<div class="step-connector"></div>
|
||||||
|
<div class="step step-active"><div class="step-icon">2</div><span>Konto</span></div>
|
||||||
|
<div class="step-connector"></div>
|
||||||
|
<div class="step"><div class="step-icon">3</div><span>Abschluss</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="paginator">
|
||||||
|
<span class="paginator-info">1-10 von 100</span>
|
||||||
|
<div class="paginator-controls">
|
||||||
|
<button class="btn btn-icon"><</button>
|
||||||
|
<button class="btn btn-icon">></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="tree">
|
||||||
|
<li class="tree-node">
|
||||||
|
<details open>
|
||||||
|
<summary class="tree-node-label">Ordner 1</summary>
|
||||||
|
<ul class="tree-node-children">
|
||||||
|
<li class="tree-node"><span class="tree-node-label">Datei 1.1</span></li>
|
||||||
|
<li class="tree-node"><span class="tree-node-label">Datei 1.2</span></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div *ngIf="isLoading; else content">
|
||||||
|
<div class="skeleton-card">...</div>
|
||||||
|
</div>
|
||||||
|
<ng-template #content>
|
||||||
|
<!-- Echte Daten hier -->
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<button class="btn btn-icon btn-icon-danger" data-tooltip="Löschen" (click)="openDialog()">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- =================================================================== -->
|
||||||
|
<!-- DYNAMISCHE SNACKBAR (Standardmäßig unsichtbar) -->
|
||||||
|
<!-- =================================================================== -->
|
||||||
|
<div
|
||||||
|
class="snackbar"
|
||||||
|
*ngIf="isSnackbarVisible"
|
||||||
|
[@fadeSlideIn]> <!-- Angular Animation -->
|
||||||
|
|
||||||
|
<!-- Das Icon ist optional, wertet die Snackbar aber auf -->
|
||||||
|
<div class="snackbar-icon-container">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="snackbar-message">{{ snackbarMessage }}</span>
|
||||||
|
|
||||||
|
<button class="btn btn-icon snackbar-close-btn" (click)="closeSnackbar()" data-tooltip="Schließen">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,47 @@
|
|||||||
import { Component, Renderer2 } from '@angular/core';
|
import { Component, Renderer2 } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-demo',
|
selector: 'app-demo',
|
||||||
imports: [],
|
imports: [CommonModule],
|
||||||
templateUrl: './demo.component.html',
|
templateUrl: './demo.component.html',
|
||||||
styleUrl: './demo.component.css'
|
styleUrl: './demo.component.css',
|
||||||
|
animations: [
|
||||||
|
trigger('fade', [
|
||||||
|
transition(':enter', [
|
||||||
|
style({ opacity: 0 }),
|
||||||
|
animate('300ms ease-out', style({ opacity: 1 }))
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
animate('300ms ease-in', style({ opacity: 0 }))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
trigger('slideIn', [
|
||||||
|
transition(':enter', [
|
||||||
|
style({ transform: 'translateY(20px)', opacity: 0 }),
|
||||||
|
animate('300ms ease-out', style({ transform: 'translateY(0)', opacity: 1 }))
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
animate('300ms ease-in', style({ transform: 'translateY(20px)', opacity: 0 }))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
trigger('fadeSlideIn', [
|
||||||
|
transition(':enter', [
|
||||||
|
style({ bottom: '-100px', opacity: 0 }),
|
||||||
|
animate('300ms ease-out', style({ bottom: '2rem', opacity: 1 }))
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
animate('300ms ease-in', style({ bottom: '-100px', opacity: 0 }))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class DemoComponent {
|
export class DemoComponent {
|
||||||
|
|
||||||
// 1. Eine Eigenschaft, die den aktuellen Zustand des Dark Mode speichert.
|
// 1. Eine Eigenschaft, die den aktuellen Zustand des Dark Mode speichert.
|
||||||
// Sie ist an [checked]="isDarkMode" in der HTML gebunden.
|
// Sie ist an [checked]="isDarkMode" in der HTML gebunden.
|
||||||
isDarkMode = false;
|
isDarkMode = false;
|
||||||
|
isLoading = true;
|
||||||
// 2. Wir "injizieren" den Renderer2. Das ist der sichere Weg in Angular,
|
// 2. Wir "injizieren" den Renderer2. Das ist der sichere Weg in Angular,
|
||||||
// um das DOM (z.B. den <body>-Tag) zu manipulieren.
|
// um das DOM (z.B. den <body>-Tag) zu manipulieren.
|
||||||
constructor(private renderer: Renderer2) {}
|
constructor(private renderer: Renderer2) {}
|
||||||
@@ -29,4 +59,55 @@ export class DemoComponent {
|
|||||||
this.renderer.removeClass(document.body, 'dark-theme');
|
this.renderer.removeClass(document.body, 'dark-theme');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDialogOpen = false;
|
||||||
|
|
||||||
|
// 2. Eine Methode, um den Dialog zu öffnen
|
||||||
|
openDialog(): void {
|
||||||
|
this.isDialogOpen = true;
|
||||||
|
// Optional: Verhindert das Scrollen der Seite im Hintergrund
|
||||||
|
this.renderer.addClass(document.body, 'no-scroll');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Eine Methode, um den Dialog zu schließen
|
||||||
|
closeDialog(): void {
|
||||||
|
this.isDialogOpen = false;
|
||||||
|
// Optional: Gibt das Scrollen der Seite wieder frei
|
||||||
|
this.renderer.removeClass(document.body, 'no-scroll');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
isSnackbarVisible = false;
|
||||||
|
snackbarMessage = '';
|
||||||
|
private snackbarTimer: any; // Hier speichern wir den Timer
|
||||||
|
|
||||||
|
// 2. Eine Methode, um die Snackbar anzuzeigen
|
||||||
|
// Wir übergeben eine Nachricht, um sie wiederverwendbar zu machen
|
||||||
|
showSnackbar(message: string): void {
|
||||||
|
// Falls bereits eine Snackbar sichtbar ist, schließen wir sie zuerst
|
||||||
|
if (this.isSnackbarVisible) {
|
||||||
|
this.closeSnackbar();
|
||||||
|
// Kurze Verzögerung, damit die Animation sauber neustartet
|
||||||
|
setTimeout(() => this.displaySnackbar(message), 300);
|
||||||
|
} else {
|
||||||
|
this.displaySnackbar(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private displaySnackbar(message: string): void {
|
||||||
|
this.snackbarMessage = message;
|
||||||
|
this.isSnackbarVisible = true;
|
||||||
|
|
||||||
|
// Setze einen Timer, um die Snackbar nach 5 Sekunden automatisch zu schließen
|
||||||
|
this.snackbarTimer = setTimeout(() => {
|
||||||
|
this.closeSnackbar();
|
||||||
|
}, 5000); // 5000 Millisekunden = 5 Sekunden
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Eine Methode, um die Snackbar zu schließen (manuell oder durch Timer)
|
||||||
|
closeSnackbar(): void {
|
||||||
|
// Lösche den Timer, falls der Benutzer manuell schließt
|
||||||
|
clearTimeout(this.snackbarTimer);
|
||||||
|
this.isSnackbarVisible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,19 @@ import { DemoComponent } from './components/demo/demo.component';
|
|||||||
|
|
||||||
export const DEMO_ROUTES: Routes = [
|
export const DEMO_ROUTES: Routes = [
|
||||||
{
|
{
|
||||||
|
// Diese Route ist relativ zu '/demo'.
|
||||||
|
// Ein leerer Pfad leitet direkt weiter zu '/demo/1'.
|
||||||
path: '',
|
path: '',
|
||||||
|
redirectTo: '1',
|
||||||
|
pathMatch: 'full'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal.
|
||||||
|
path: '1',
|
||||||
component: DemoComponent,
|
component: DemoComponent,
|
||||||
children: [
|
title: 'Demo'
|
||||||
{ path: '', redirectTo: '1', pathMatch: 'full' },
|
|
||||||
{ path: '1', component: DemoComponent, title: 'Demo' },
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
// Hier könntest du weitere Routen wie '2', '3' etc. hinzufügen,
|
||||||
|
// die andere Komponenten laden.
|
||||||
|
// { path: '2', component: AnotherDemoComponent },
|
||||||
];
|
];
|
||||||
@@ -1,21 +1,9 @@
|
|||||||
// src/app/material.ts
|
// src/app/material.ts
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatCardModule } from '@angular/material/card';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { MatTableModule } from '@angular/material/table';
|
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
||||||
// ... füge hier alle Material-Module hinzu, die du häufig verwendest
|
|
||||||
|
|
||||||
export const MATERIAL_MODULES = [
|
export const MATERIAL_MODULES = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|
||||||
MatButtonModule,
|
|
||||||
MatCardModule,
|
|
||||||
MatIconModule,
|
|
||||||
MatTableModule,
|
|
||||||
MatTabsModule,
|
|
||||||
MatToolbarModule,
|
|
||||||
];
|
];
|
||||||
445
src/styles.css
445
src/styles.css
@@ -831,3 +831,448 @@ body.dark-theme .pill-info {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* =================================================================================
|
||||||
|
* 10. ERWEITERTE UI-KOMPONENTEN-BIBLIOTHEK (TEIL 2)
|
||||||
|
* ================================================================================= */
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* 10.1 DIALOG / MODAL
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
.dialog-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 1; /* Für Animationen anpassen */
|
||||||
|
transition: opacity var(--transition-speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px; /* Standardbreite für Dialoge */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: var(--box-shadow-md);
|
||||||
|
/* Animation: von leicht unten nach oben sliden */
|
||||||
|
transform: translateY(0);
|
||||||
|
transition: transform var(--transition-speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
background-color: var(--color-body-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* 10.2 SNACKBAR / TOAST
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
@keyframes snackbar-in {
|
||||||
|
from { transform: translate(-50%, 100px); opacity: 0; }
|
||||||
|
to { transform: translate(-50%, 0); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.snackbar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 500px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
background-color: #2c3e50; /* Typischerweise dunkel */
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: var(--box-shadow-md);
|
||||||
|
z-index: 1050;
|
||||||
|
animation: snackbar-in 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-theme .snackbar {
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.snackbar-action {
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
body.dark-theme .snackbar-action {
|
||||||
|
color: #60a5fa; /* Helleres Blau für Dark Mode */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* 10.3 MENU / DROPDOWN
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
.menu-container {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px); /* Etwas Abstand zum Auslöser */
|
||||||
|
right: 0;
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
z-index: 900;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
box-shadow: var(--box-shadow-md);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: opacity var(--transition-speed), transform var(--transition-speed);
|
||||||
|
}
|
||||||
|
/* Zustand, wenn das Menü offen ist (z.B. durch eine JS-Klasse) */
|
||||||
|
.menu-container.open .menu-panel {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: block;
|
||||||
|
padding: 0.6rem 1.25rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: background-color 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background-color: var(--color-body-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item-danger {
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* 10.4 SKELETON LOADER
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
@keyframes skeleton-shimmer {
|
||||||
|
0% { background-position: -200px 0; }
|
||||||
|
100% { background-position: calc(200px + 100%) 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-item {
|
||||||
|
background-color: var(--color-border);
|
||||||
|
background-image: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--color-border) 0px,
|
||||||
|
var(--color-body-bg) 40px,
|
||||||
|
var(--color-border) 80px
|
||||||
|
);
|
||||||
|
background-size: 200px 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
animation: skeleton-shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.skeleton-avatar {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.skeleton-content {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
.skeleton-line {
|
||||||
|
width: 100%;
|
||||||
|
height: 1rem;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* 10.5 STEPPER
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
.stepper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all var(--transition-speed);
|
||||||
|
}
|
||||||
|
.step.step-active {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
.step.step-active .step-icon {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
.step.step-complete {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
.step.step-complete .step-icon {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-connector {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 2px;
|
||||||
|
background-color: var(--color-border);
|
||||||
|
margin: 0 1rem;
|
||||||
|
transform: translateY(-12px); /* Auf Höhe der Icons ausrichten */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* 10.6 PAGINATOR
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
.paginator {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
.paginator-info {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.paginator-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
/* 10.7 TREE
|
||||||
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
|
.tree {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.4rem 0.5rem;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tree-node-label:hover {
|
||||||
|
background-color: var(--color-body-bg);
|
||||||
|
}
|
||||||
|
.tree-node details > summary::-webkit-details-marker {
|
||||||
|
display: none; /* Icon entfernen */
|
||||||
|
}
|
||||||
|
.tree-node details > summary {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.tree-node details > summary::before { /* Chevron-Icon */
|
||||||
|
content: '▶';
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
transition: transform 0.2s ease-out;
|
||||||
|
}
|
||||||
|
.tree-node details[open] > summary::before {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-children {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 1.75rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/* Verbindungs-Linie */
|
||||||
|
.tree-node-children::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 8px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
background-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================================
|
||||||
|
* 11. VERBESSERTES DIALOG-STYLING & HELFER-KLASSEN
|
||||||
|
* ================================================================================= */
|
||||||
|
|
||||||
|
/* Verhindert das Scrollen des Body, wenn ein Overlay aktiv ist */
|
||||||
|
body.no-scroll {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Neuer Button-Typ für gefährliche Aktionen */
|
||||||
|
.btn-icon-danger:hover {
|
||||||
|
background-color: #fee2e2; /* Rötlicher Hover-Effekt */
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
body.dark-theme .btn-icon-danger:hover {
|
||||||
|
background-color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verbessertes Dialog-Styling */
|
||||||
|
.dialog-backdrop {
|
||||||
|
/* ... bestehende Regeln (position: fixed, z-index etc.) bleiben gleich ... */
|
||||||
|
backdrop-filter: blur(4px); /* Moderner Blur-Effekt */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-container {
|
||||||
|
/* ... bestehende Regeln (max-width, display: flex etc.) bleiben gleich ... */
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon-Styling innerhalb des Dialogs */
|
||||||
|
.dialog-content {
|
||||||
|
text-align: center; /* Zentriert den Inhalt für einen besseren Look */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-icon-container {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
margin: 0 auto 1rem auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background-color: #fee2e2; /* Roter Hintergrund für das Icon */
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
body.dark-theme .dialog-icon-container {
|
||||||
|
background-color: #7f1d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content p {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
.dialog-content strong {
|
||||||
|
color: var(--color-text);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =================================================================================
|
||||||
|
* 12. VERBESSERTES SNACKBAR-STYLING
|
||||||
|
* ================================================================================= */
|
||||||
|
|
||||||
|
.snackbar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%); /* Startposition für Animation */
|
||||||
|
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 500px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.75rem; /* Etwas weniger Padding, da der Button eigenes hat */
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
|
||||||
|
background-color: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
box-shadow: var(--box-shadow-md);
|
||||||
|
z-index: 1050;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Der @keyframes-Block wird nicht mehr benötigt, da wir Angular Animations nutzen */
|
||||||
|
|
||||||
|
.snackbar-icon-container {
|
||||||
|
flex-shrink: 0; /* Verhindert, dass das Icon schrumpft */
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background-color: #dcfce7; /* Success-Farbe */
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
body.dark-theme .snackbar-icon-container {
|
||||||
|
background-color: #166534;
|
||||||
|
color: #dcfce7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snackbar-message {
|
||||||
|
flex-grow: 1; /* Nimmt den verfügbaren Platz ein */
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snackbar-close-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 1.5rem; /* Größeres "x" */
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
.snackbar-close-btn:hover {
|
||||||
|
background-color: var(--color-body-bg);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user