This commit is contained in:
Tizian.Breuch
2025-09-08 14:54:15 +02:00
parent dd4e2b7d9d
commit c6e9612ceb
12 changed files with 1049 additions and 1027 deletions

View File

@@ -1 +1,2 @@
<router-outlet style="height: 100vh; width: 100vw;"></router-outlet>
<router-outlet></router-outlet>
<app-snackbar-container></app-snackbar-container>

View File

@@ -1,9 +1,10 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { SnackbarContainerComponent } from './shared/snackbar/components/snackbar-container/snackbar-container.component';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
imports: [RouterOutlet,SnackbarContainerComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})

View File

@@ -1,15 +1,15 @@
<!-- FINALE demo.component.html -->
<!-- =================================================================================
FINALES & VOLLSTÄNDIGES DEMO-LAYOUT (VERSION 3.0)
================================================================================== -->
<div class="dashboard-container">
<!-- =================================================================== -->
<!-- SIDEBAR -->
<!-- 1. SIDEBAR -->
<!-- =================================================================== -->
<aside class="sidebar">
<div class="sidebar-header">
<h1 class="sidebar-title">CustomDash</h1>
</div>
<div class="sidebar-header"><h1 class="sidebar-title">CustomDash</h1></div>
<nav class="sidebar-nav">
<a href="#" class="nav-item active">
<svg
<a href="#" class="nav-item active"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
@@ -21,12 +21,11 @@
stroke-linejoin="round"
>
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
<span>Übersicht</span>
</a>
<a href="#" class="nav-item">
<svg
<polyline points="9 22 9 12 15 12 15 22"></polyline></svg
><span>Übersicht</span></a
>
<a href="#" class="nav-item"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
@@ -39,12 +38,11 @@
>
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
<span>Analysen</span>
</a>
<a href="#" class="nav-item">
<svg
<line x1="6" y1="20" x2="6" y2="14"></line></svg
><span>Analysen</span></a
>
<a href="#" class="nav-item"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
@@ -57,14 +55,13 @@
>
<path
d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
></path>
</svg>
<span>Berichte</span>
</a>
></path></svg
><span>Berichte</span></a
>
</nav>
<div class="sidebar-footer">
<a href="#" class="nav-item">
<svg
<a href="#" class="nav-item"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
@@ -77,15 +74,14 @@
>
<path
d="M10.3 21.7c.9.9 2.5.9 3.4 0l6.9-6.9c.9-.9.9-2.5 0-3.4l-6.9-6.9c-.9-.9-2.5-.9-3.4 0l-6.9 6.9c-.9.9-.9-2.5 0 3.4l6.9 6.9z"
></path>
</svg>
<span>Einstellungen</span>
</a>
></path></svg
><span>Einstellungen</span></a
>
</div>
</aside>
<!-- =================================================================== -->
<!-- HAUPTINHALT -->
<!-- 2. HAUPTINHALT -->
<!-- =================================================================== -->
<main class="main-content">
<header class="main-header">
@@ -102,14 +98,13 @@
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
<input type="text" placeholder="Dashboard durchsuchen..." />
<line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg
><input type="text" placeholder="Dashboard durchsuchen..." />
</div>
<div class="header-actions">
<div class="theme-switcher">
<label for="theme-toggle">Dark Mode</label>
<input
<label for="theme-toggle">Dark Mode</label
><input
type="checkbox"
id="theme-toggle"
class="slider-checkbox"
@@ -132,7 +127,7 @@
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -154,7 +149,7 @@
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -178,7 +173,7 @@
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -201,7 +196,7 @@
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -230,24 +225,21 @@
</div>
</section>
<!-- =================================================================== -->
<!-- Sektion: Datentabelle (Final & Korrigiert) -->
<!-- =================================================================== -->
<!-- Sektion: Datentabelle -->
<section class="card grid-col-span-2">
<h3 class="card-header">Letzte Bestellungen</h3>
<div class="table-container">
<table class="modern-table">
<thead>
<tr>
<th class="sortable">
<span>Kunde</span>
<svg
<span>Kunde</span
><svg
class="sort-icon"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -267,7 +259,6 @@
</tr>
</thead>
<tbody>
<!-- Zeile 1 -->
<tr>
<td>
<div class="user-cell">
@@ -282,7 +273,6 @@
</div>
</td>
<td><span class="mono">#10543</span></td>
<!-- HIER WURDE DIE KORREKTE STATUS-PILLE HINZUGEFÜGT -->
<td>
<span class="status-pill pill-success">Abgeschlossen</span>
</td>
@@ -293,7 +283,7 @@
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -304,14 +294,13 @@
d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</button>
<button class="btn btn-icon" data-tooltip="Bearbeiten">
</svg></button
><button class="btn btn-icon" data-tooltip="Bearbeiten">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -324,11 +313,33 @@
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
></path>
</svg></button
><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>
</td>
</tr>
<!-- Zeile 2 -->
<tr>
<td>
<div class="user-cell">
@@ -343,7 +354,6 @@
</div>
</td>
<td><span class="mono">#10542</span></td>
<!-- HIER WURDE DIE KLASSE ZU status-pill GEÄNDERT -->
<td>
<span class="status-pill pill-warning">In Bearbeitung</span>
</td>
@@ -354,7 +364,7 @@
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -365,14 +375,13 @@
d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</button>
<button class="btn btn-icon" data-tooltip="Bearbeiten">
</svg></button
><button class="btn btn-icon" data-tooltip="Bearbeiten">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -385,11 +394,33 @@
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
></path>
</svg></button
><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>
</td>
</tr>
<!-- Zeile 3 -->
<tr>
<td>
<div class="user-cell">
@@ -404,7 +435,6 @@
</div>
</td>
<td><span class="mono">#10541</span></td>
<!-- HIER WURDE DIE KLASSE ZU status-pill GEÄNDERT -->
<td><span class="status-pill pill-danger">Storniert</span></td>
<td class="text-right amount">€ 87,00</td>
<td class="actions-cell">
@@ -413,7 +443,7 @@
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -424,14 +454,13 @@
d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</button>
<button class="btn btn-icon" data-tooltip="Bearbeiten">
</svg></button
><button class="btn btn-icon" data-tooltip="Bearbeiten">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -444,65 +473,29 @@
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
></path>
</svg>
</button>
</td>
</tr>
<!-- Zeile 4 (Bonus) -->
<tr>
<td>
<div class="user-cell">
<img
src="https://i.pravatar.cc/40?u=anna"
alt="Avatar Anna Kurz"
/>
<div>
<div class="user-name">Anna Kurz</div>
<div class="user-email">anna.kurzATexample.com</div>
</div>
</div>
</td>
<td><span class="mono">#10540</span></td>
<!-- HIER WURDE DIE KLASSE ZU status-pill GEÄNDERT -->
<td><span class="status-pill pill-info">Versendet</span></td>
<td class="text-right amount">€ 214,20</td>
<td class="actions-cell">
<button class="btn btn-icon" data-tooltip="Details anzeigen">
</svg></button
><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"
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="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"
></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
</button>
<button class="btn btn-icon" data-tooltip="Bearbeiten">
<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="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
></path>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
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>
</td>
@@ -552,7 +545,7 @@
</div>
</section>
<section class="card grid-col-span-2">
<h3 class="card-header">Buttons, Chips & Badges</h3>
<h3 class="card-header">Buttons, Chips & Interaktion</h3>
<div class="card-body component-grid">
<div class="button-group">
<button class="btn btn-primary">Primary</button
@@ -564,7 +557,7 @@
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -592,7 +585,7 @@
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
ViewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -604,33 +597,33 @@
><span class="badge-dot">3</span>
</div>
</div>
<div class="menu-container" [class.open]="isMenuOpen">
<button
class="btn btn-secondary"
(click)="isMenuOpen = !isMenuOpen"
>
Menü öffnen
</button>
<div class="menu-panel card">
<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>
</section>
<section class="card grid-col-span-2">
<h3 class="card-header">Indikatoren & Feedback</h3>
<div class="card-body component-grid">
<div>
<label>Fortschritt</label>
<div class="progress-bar">
<div class="progress-bar-value" style="width: 75%"></div>
</div>
</div>
<div class="spinner-container">
<div class="progress-spinner"></div>
</div>
<div class="alert alert-success">
<strong>Erfolg!</strong> Ihre Änderungen wurden gespeichert.
</div>
<div class="alert alert-danger">
<strong>Fehler!</strong> Bitte füllen Sie alle Felder aus.
</div>
<div class="button-group" style="align-items: center">
<button class="btn btn-secondary" (click)="triggerSnackbar()">
Snackbar anzeigen
</button>
</div>
</section>
<!-- Karte: Navigation & Layout -->
<section class="card grid-col-span-2">
<h3 class="card-header">Navigation & Layout</h3>
<div class="card-body">
<div class="card-body component-grid">
<div class="tab-group">
<button class="tab-link active">Profil</button
><button class="tab-link">Konto</button
@@ -640,16 +633,20 @@
<p>Hier wäre der Inhalt für den aktiven Tab "Profil".</p>
</div>
<hr class="divider" />
<!-- HIER IST DIE WICHTIGE ÄNDERUNG FÜR DIE ANIMATION -->
<details class="expansion-panel">
<summary class="expansion-panel-header">
<div class="expansion-panel" [class.is-open]="isPanelOpen">
<!-- Das <summary> wird zu einem <button> für bessere Kontrolle & Zugänglichkeit -->
<button
type="button"
class="expansion-panel-header"
(click)="isPanelOpen = !isPanelOpen"
>
<span>Weitere Details anzeigen</span>
<svg
class="expansion-panel-icon"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
ViewBox="0 0 0 24"
fill="none"
stroke="currentColor"
stroke-width="2"
@@ -658,7 +655,9 @@
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</summary>
</button>
<!-- Der Inhalt bleibt strukturell gleich -->
<div class="expansion-panel-content-wrapper">
<div class="expansion-panel-content">
Hier steht der Inhalt, der ein- und ausgeklappt werden kann.
@@ -667,129 +666,151 @@
eget.
</div>
</div>
</details>
</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>
</section>
<section class="card grid-col-span-2">
<h3 class="card-header">Hierarchische Daten & Paginierung</h3>
<div class="card-body component-grid">
<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>
<li class="tree-node">
<details>
<summary class="tree-node-label">Ordner 2</summary>
<ul class="tree-node-children">
<li class="tree-node">
<span class="tree-node-label">Datei 2.1</span>
</li>
</ul>
</details>
</li>
</ul>
<div class="paginator">
<span class="paginator-info">1-10 von 100</span>
<div class="paginator-controls">
<button class="btn btn-icon">&lt;</button
><button class="btn btn-icon">&gt;</button>
</div>
</div>
</div>
</section>
<section class="card grid-col-span-2">
<h3 class="card-header">Ladezustände (Skeleton)</h3>
<div class="card-body component-grid">
<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>
</section>
</div>
<!-- Ende .dashboard-grid -->
</main>
</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">&times;</button>
</div>
<!-- =================================================================== -->
<!-- 3. OVERLAYS (Dynamisch, außerhalb des Haupt-Layouts) -->
<!-- =================================================================== -->
<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
class="dialog-backdrop"
*ngIf="isDialogOpen"
(click)="closeDialog()"
[@fade]
>
<div
class="dialog-container card"
(click)="$event.stopPropagation()"
[@slideIn]
>
<div class="dialog-header">
<h3 class="dialog-title">Aktion bestätigen</h3>
<button
class="btn btn-icon"
(click)="closeDialog()"
data-tooltip="Schließen"
>
&times;
</button>
</div>
<div class="dialog-content">
<div class="dialog-icon-container">
<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">
<button class="btn btn-stroked" (click)="closeDialog()">
Abbrechen</button
><button class="btn btn-danger" (click)="closeDialog()">
Ja, endgültig löschen
</button>
</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">&lt;</button>
<button class="btn btn-icon">&gt;</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>
<!-- <div class="snackbar-container-wrapper" [class.position-top]="snackbarPosition === 'top'">
<div
*ngFor="let snack of snackbars; let i = index"
class="snackbar"
[style]="getSnackbarStyle(i, snack.state)">
<button class="btn btn-icon snackbar-close-btn" (click)="closeSnackbar()" data-tooltip="Schließen">
&times;
</button>
<div class="snackbar-icon-container">icon</div>
<span class="snackbar-message">{{ snack.message }}</span>
<button class="btn btn-icon snackbar-close-btn" (click)="closeSnackbar(snack.id)">
&times;
</button>
</div>
</div> -->
</div>

View File

@@ -1,58 +1,93 @@
import { Component, Renderer2 } from '@angular/core';
import { Component, Renderer2, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { trigger, transition, style, animate } from '@angular/animations';
import { SnackbarService } from '../../../../shared/snackbar/services/snackbar.service';
@Component({
selector: 'app-demo',
standalone: true, // Hinzugefügt für moderne, eigenständige Komponenten
imports: [CommonModule],
templateUrl: './demo.component.html',
styleUrl: './demo.component.css',
animations: [
animations: [
trigger('fade', [
transition(':enter', [
style({ opacity: 0 }),
animate('300ms ease-out', style({ opacity: 1 }))
animate('300ms ease-out', style({ opacity: 1 })),
]),
transition(':leave', [
animate('300ms ease-in', style({ opacity: 0 }))
])
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 }))
animate(
'300ms ease-out',
style({ transform: 'translateY(0)', opacity: 1 })
),
]),
transition(':leave', [
animate('300ms ease-in', style({ transform: 'translateY(20px)', opacity: 0 }))
])
animate(
'300ms ease-in',
style({ transform: 'translateY(20px)', opacity: 0 })
),
]),
]),
trigger('fadeSlideIn', [
trigger('fadeSlideIn', [
transition(':enter', [
style({ bottom: '-100px', opacity: 0 }),
animate('300ms ease-out', style({ bottom: '2rem', opacity: 1 }))
animate('300ms ease-out', style({ bottom: '2rem', opacity: 1 })),
]),
transition(':leave', [
animate('300ms ease-in', style({ bottom: '-100px', opacity: 0 }))
])
])
]
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.
export class DemoComponent implements OnInit {
// ===================================================================
// 1. ZUSTANDS-EIGENSCHAFTEN DER KOMPONENTE
// ===================================================================
isDarkMode = false;
isLoading = true;
// 2. Wir "injizieren" den Renderer2. Das ist der sichere Weg in Angular,
// um das DOM (z.B. den <body>-Tag) zu manipulieren.
constructor(private renderer: Renderer2) {}
isLoading = true; // Startet im Ladezustand, um Skeleton Loader zu zeigen
isDialogOpen = false;
isSnackbarVisible = false;
isMenuOpen = false; // Hinzugefügt für das interaktive Menü
isPanelOpen = false;
snackbarMessage = '';
private snackbarTimer: any;
// Eindeutige Variablen für jedes Menü
isComponentMenuOpen = false;
// ===================================================================
// 2. DEPENDENCY INJECTION & LIFECYCLE
// ===================================================================
constructor(
private renderer: Renderer2,
private snackbarService: SnackbarService
) {}
/**
* Wird einmalig nach der Initialisierung der Komponente aufgerufen.
* Perfekt, um initiale Daten zu laden.
*/
ngOnInit(): void {
// Simuliert das Laden von Daten. Nach 2 Sekunden wird der Inhalt angezeigt.
setTimeout(() => {
this.isLoading = false;
}, 2000);
}
// ===================================================================
// 3. METHODEN FÜR DAS THEME-MANAGEMENT
// ===================================================================
// 3. Das ist die Funktion, die vom (change)-Event des Schalters aufgerufen wird.
toggleTheme(): void {
// Kehre den aktuellen Zustand um (true -> false, false -> true)
this.isDarkMode = !this.isDarkMode;
// Überprüfe den neuen Zustand und füge die Klasse 'dark-theme'
// zum <body>-Tag hinzu oder entferne sie.
if (this.isDarkMode) {
this.renderer.addClass(document.body, 'dark-theme');
} else {
@@ -60,54 +95,28 @@ export class DemoComponent {
}
}
isDialogOpen = false;
// ===================================================================
// 4. METHODEN FÜR DEN DIALOG
// ===================================================================
// 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');
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');
}
// demo.component.ts
isSnackbarVisible = false;
snackbarMessage = '';
private snackbarTimer: any; // Hier speichern wir den Timer
// ===================================================================
// FINALE, ROBUSTE METHODEN FÜR DIE SNACKBAR (VERSION 5.0)
// ===================================================================
// 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);
}
triggerSnackbar(): void {
const time = new Date().toLocaleTimeString();
this.snackbarService.show('Neues Ereignis um ' + time);
}
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;
}
}

View File

@@ -0,0 +1,17 @@
/* Stile, die NUR für den Container gelten */
.snackbar-container-wrapper {
position: fixed;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
z-index: 1050;
width: 350px;
max-width: 90vw;
height: 300px;
pointer-events: none;
}
.snackbar-container-wrapper.position-top {
bottom: auto;
top: 2rem;
}

View File

@@ -0,0 +1,8 @@
<div class="snackbar-container-wrapper" [class.position-top]="(position$ | async) === 'top'">
<app-snackbar
*ngFor="let snack of (snackbars$ | async); let i = index"
[message]="snack.message"
[style]="getSnackbarStyle(i, snack.state, (position$ | async)!)"
(close)="closeSnackbar(snack.id)">
</app-snackbar>
</div>

View File

@@ -0,0 +1,46 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Observable } from 'rxjs';
import { Snackbar, SnackbarService } from '../../services/snackbar.service';
import { SnackbarComponent } from '../snackbar/snackbar.component';
@Component({
selector: 'app-snackbar-container',
standalone: true,
imports: [CommonModule, SnackbarComponent],
templateUrl: './snackbar-container.component.html',
styleUrl: './snackbar-container.component.css'
})
export class SnackbarContainerComponent {
snackbars$: Observable<Snackbar[]>;
position$: Observable<'top' | 'bottom'>;
constructor(private snackbarService: SnackbarService) {
this.snackbars$ = this.snackbarService.snackbars$;
this.position$ = this.snackbarService.position$;
}
// Diese Methode gehört hierher, da der Container die Position aller Kinder kennen muss.
getSnackbarStyle(index: number, state: 'visible' | 'fading', position: 'top' | 'bottom'): { [key: string]: any } {
const snackbarHeight = 75;
const verticalOffset = index * snackbarHeight;
const positionProperty = position === 'bottom' ? 'bottom' : 'top';
const opacity = 1 - (index * 0.2);
const scale = 1 - (index * 0.02);
const visibleStyle = {
[positionProperty]: `${verticalOffset}px`,
'opacity': opacity,
'transform': `scale(${scale})`,
'z-index': 1000 - index
};
const fadingStyle = { ...visibleStyle, 'opacity': 0, 'transform': 'scale(0.8)' };
return state === 'visible' ? visibleStyle : fadingStyle;
}
closeSnackbar(id: number): void {
this.snackbarService.close(id);
}
}

View File

@@ -0,0 +1,46 @@
/* Stile, die NUR für eine einzelne Snackbar gelten */
:host {
position: absolute;
left: 0;
right: 0;
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem;
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);
pointer-events: auto;
transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.snackbar-icon-container {
flex-shrink: 0;
width: 32px;
height: 32px;
border-radius: 50%;
display: grid;
place-items: center;
background-color: #dcfce7;
color: #166534;
}
:host-context(body.dark-theme) .snackbar-icon-container {
background-color: #166534;
color: #dcfce7;
}
.snackbar-message {
flex-grow: 1;
font-weight: 500;
}
.snackbar-close-btn {
flex-shrink: 0;
width: 32px;
height: 32px;
font-size: 1.5rem;
color: var(--color-text-light);
}
.snackbar-close-btn:hover {
background-color: var(--color-body-bg);
}

View File

@@ -0,0 +1,10 @@
<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">{{ message }}</span>
<button class="btn btn-icon snackbar-close-btn" (click)="onClose()" data-tooltip="Schließen">
&times;
</button>

View File

@@ -0,0 +1,17 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-snackbar',
standalone: true,
imports: [],
templateUrl: './snackbar.component.html',
styleUrl: './snackbar.component.css'
})
export class SnackbarComponent {
@Input() message: string = '';
@Output() close = new EventEmitter<void>();
onClose() {
this.close.emit();
}
}

View File

@@ -0,0 +1,55 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
// Exportiere das Interface, damit es wiederverwendbar ist
export interface Snackbar {
id: number;
message: string;
state: 'visible' | 'fading';
}
@Injectable({
providedIn: 'root'
})
export class SnackbarService {
// BehaviorSubject hält den aktuellen Zustand und benachrichtigt alle "Zuhörer"
private snackbarsSubject = new BehaviorSubject<Snackbar[]>([]);
snackbars$: Observable<Snackbar[]> = this.snackbarsSubject.asObservable();
// Die Position wird ebenfalls hier als globaler Zustand verwaltet
private positionSubject = new BehaviorSubject<'top' | 'bottom'>('top');
position$: Observable<'top' | 'bottom'> = this.positionSubject.asObservable();
private nextId = 0;
// Öffentliche Methode zum Anzeigen einer Snackbar
show(message: string): void {
const newSnackbar: Snackbar = { id: this.nextId++, message, state: 'visible' };
const currentSnackbars = [newSnackbar, ...this.snackbarsSubject.value];
this.snackbarsSubject.next(currentSnackbars);
// Timer zum automatischen Schließen
setTimeout(() => this.close(newSnackbar.id), 5000);
}
// Öffentliche Methode zum Schließen einer Snackbar
close(id: number): void {
let currentSnackbars = this.snackbarsSubject.value;
const index = currentSnackbars.findIndex(s => s.id === id);
if (index > -1 && currentSnackbars[index].state === 'visible') {
currentSnackbars[index].state = 'fading';
this.snackbarsSubject.next([...currentSnackbars]);
setTimeout(() => {
currentSnackbars = this.snackbarsSubject.value.filter(s => s.id !== id);
this.snackbarsSubject.next(currentSnackbars);
}, 100); // Muss zur CSS-Dauer passen
}
}
// Öffentliche Methode zum Ändern der Position
setPosition(position: 'top' | 'bottom'): void {
this.positionSubject.next(position);
}
}

File diff suppressed because it is too large Load Diff