diff --git a/src/app/app.component.html b/src/app/app.component.html index 0680b43..66364fa 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1 +1 @@ - + diff --git a/src/app/app.config.ts b/src/app/app.config.ts index a9af518..11f35e5 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,9 +1,15 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; +import { provideAnimations } from '@angular/platform-browser/animations'; // Importieren import { routes } from './app.routes'; import { provideClientHydration, withEventReplay } from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideClientHydration(withEventReplay())] -}; + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideClientHydration(withEventReplay()), + provideAnimations() // Hier hinzufügen + ] +}; \ No newline at end of file diff --git a/src/app/features/demo/components/demo/demo.component.html b/src/app/features/demo/components/demo/demo.component.html index eb89925..328545b 100644 --- a/src/app/features/demo/components/demo/demo.component.html +++ b/src/app/features/demo/components/demo/demo.component.html @@ -674,3 +674,122 @@ + + + +
+ + +
+ +
+

Aktion bestätigen

+ + +
+ +
+
+ + +
+

Sind Sie sicher, dass Sie die Bestellung #10543 löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.

+
+ +
+ + + +
+ +
+
+ + + + + +
+
+
+
+
+
+
+ +
+
Profil
+
+
2
Konto
+
+
3
Abschluss
+
+ +
+ 1-10 von 100 +
+ + +
+
+ + + +
+
...
+
+ + + + + + + + + + +
+ + +
+ +
+ + {{ snackbarMessage }} + + + +
+ + + diff --git a/src/app/features/demo/components/demo/demo.component.ts b/src/app/features/demo/components/demo/demo.component.ts index cfca3bf..d40b56c 100644 --- a/src/app/features/demo/components/demo/demo.component.ts +++ b/src/app/features/demo/components/demo/demo.component.ts @@ -1,17 +1,47 @@ import { Component, Renderer2 } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { trigger, state, style, transition, animate } from '@angular/animations'; @Component({ selector: 'app-demo', - imports: [], + imports: [CommonModule], 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 { - // 1. Eine Eigenschaft, die den aktuellen Zustand des Dark Mode speichert. // Sie ist an [checked]="isDarkMode" in der HTML gebunden. isDarkMode = false; - + isLoading = true; // 2. Wir "injizieren" den Renderer2. Das ist der sichere Weg in Angular, // um das DOM (z.B. den -Tag) zu manipulieren. constructor(private renderer: Renderer2) {} @@ -29,4 +59,55 @@ export class DemoComponent { this.renderer.removeClass(document.body, 'dark-theme'); } } -} \ No newline at end of file + + 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; +} +} diff --git a/src/app/features/demo/demo.routes.ts b/src/app/features/demo/demo.routes.ts index 72684df..7baf5d9 100644 --- a/src/app/features/demo/demo.routes.ts +++ b/src/app/features/demo/demo.routes.ts @@ -4,11 +4,19 @@ import { DemoComponent } from './components/demo/demo.component'; export const DEMO_ROUTES: Routes = [ { + // Diese Route ist relativ zu '/demo'. + // Ein leerer Pfad leitet direkt weiter zu '/demo/1'. path: '', + redirectTo: '1', + pathMatch: 'full' + }, + { + // Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal. + path: '1', component: DemoComponent, - children: [ - { path: '', redirectTo: '1', pathMatch: 'full' }, - { path: '1', component: DemoComponent, title: 'Demo' }, - ] + title: 'Demo' } + // Hier könntest du weitere Routen wie '2', '3' etc. hinzufügen, + // die andere Komponenten laden. + // { path: '2', component: AnotherDemoComponent }, ]; \ No newline at end of file diff --git a/src/app/modules.ts b/src/app/modules.ts index bf64cde..7cd79bf 100644 --- a/src/app/modules.ts +++ b/src/app/modules.ts @@ -1,21 +1,9 @@ // src/app/material.ts 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 = [ CommonModule, - MatButtonModule, - MatCardModule, - MatIconModule, - MatTableModule, - MatTabsModule, - MatToolbarModule, ]; \ No newline at end of file diff --git a/src/styles.css b/src/styles.css index aa5ab60..930f9ff 100644 --- a/src/styles.css +++ b/src/styles.css @@ -831,3 +831,448 @@ body.dark-theme .pill-info { 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); +} \ No newline at end of file