styling
This commit is contained in:
@@ -1 +1,2 @@
|
||||
<router-outlet style="height: 100vh; width: 100vw;"></router-outlet>
|
||||
<router-outlet></router-outlet>
|
||||
<app-snackbar-container></app-snackbar-container>
|
||||
@@ -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'
|
||||
})
|
||||
|
||||
@@ -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"><</button
|
||||
><button class="btn btn-icon">></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">×</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"
|
||||
>
|
||||
×
|
||||
</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"><</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>
|
||||
<!-- <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">
|
||||
×
|
||||
</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)">
|
||||
×
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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">
|
||||
×
|
||||
</button>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
55
src/app/shared/snackbar/services/snackbar.service.ts
Normal file
55
src/app/shared/snackbar/services/snackbar.service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user