This commit is contained in:
Tizian.Breuch
2025-10-29 14:04:14 +01:00
parent 1585129e1f
commit b1b1c3173b
20 changed files with 1346 additions and 497 deletions

View File

@@ -23,7 +23,7 @@
padding: 0.85rem 1rem;
border: 1px solid var(--color-border);
border-radius: var(--border-radius-md);
/* Aussehen & Typografie */
background-color: var(--color-surface);
color: var(--color-text);
@@ -46,14 +46,18 @@
position: absolute;
top: 50%; /* Vertikal zentrieren (Schritt 1) */
left: 1rem; /* Linken Abstand wie beim Input-Padding halten */
transform: translateY(-50%); /* Vertikal zentrieren (Schritt 2 - Feinjustierung) */
transform: translateY(
-50%
); /* Vertikal zentrieren (Schritt 2 - Feinjustierung) */
border-radius: 4px;
/* Aussehen & Typografie */
color: var(--color-text-light);
background-color: var(--color-surface); /* WICHTIG: Überdeckt die Input-Linie beim Schweben */
background-color: var(
--color-surface
); /* WICHTIG: Überdeckt die Input-Linie beim Schweben */
padding: 0 0.25rem;
/* Verhalten */
transition: all 0.2s ease-out; /* Animiert alle Änderungen (top, font-size, color) */
pointer-events: none; /* Erlaubt Klicks "durch" das Label auf das Input-Feld darunter */
@@ -75,7 +79,9 @@
.form-input:focus ~ .form-label,
.form-input:not(:placeholder-shown) ~ .form-label {
top: 0;
transform: translateY(-50%); /* Bleibt für die Zentrierung auf der Border-Linie wichtig */
transform: translateY(
-50%
); /* Bleibt für die Zentrierung auf der Border-Linie wichtig */
font-size: 0.8rem;
color: var(--color-primary);
}
@@ -87,12 +93,12 @@
* und die richtige Farbe bekommt.
*/
.form-input:-webkit-autofill,
.form-input:-webkit-autofill:hover,
.form-input:-webkit-autofill:focus,
.form-input:-webkit-autofill:hover,
.form-input:-webkit-autofill:focus,
.form-input:-webkit-autofill:active {
/* OPTIONAL: Überschreibt den unschönen gelben/blauen Autofill-Hintergrund */
-webkit-box-shadow: 0 0 0 30px var(--color-surface) inset !important;
-webkit-text-fill-color: var(--color-text) !important;
/* OPTIONAL: Überschreibt den unschönen gelben/blauen Autofill-Hintergrund */
-webkit-box-shadow: 0 0 0 30px var(--color-surface) inset !important;
-webkit-text-fill-color: var(--color-text) !important;
}
.form-input:-webkit-autofill ~ .form-label {
@@ -100,4 +106,18 @@
transform: translateY(-50%);
font-size: 0.8rem;
color: var(--color-primary);
}
}
.required-indicator {
color: var(--color-danger);
font-weight: bold;
margin-left: 2px;
}
/* Styling für die Fehlermeldung */
.error-message {
color: var(--color-danger);
font-size: 0.875rem;
margin-top: 0.25rem;
padding-left: 0.25rem;
}

View File

@@ -1,13 +1,26 @@
<div class="form-field">
<input
[type]="type"
class="form-input"
[id]="controlId"
placeholder=" "
[disabled]="disabled"
[(ngModel)]="value"
(ngModelChange)="onChange($event)"
(blur)="onTouched()">
<label [for]="controlId" class="form-label">{{ label }}</label>
<!-- /src/app/shared/components/form/form-field/form-field.component.html -->
<div class="form-field-wrapper">
<div class="form-field">
<input
[type]="type"
class="form-input"
[id]="controlId"
placeholder=" "
[disabled]="disabled"
[(ngModel)]="value"
(ngModelChange)="onChange($event)"
(blur)="onTouched()">
<label [for]="controlId" class="form-label">
{{ label }}
<!-- Der Indikator wird jetzt nur bei Bedarf angezeigt -->
<span *ngIf="isRequired" class="required-indicator">*</span>
</label>
</div>
<!-- Anzeige für Validierungsfehler -->
<div *ngIf="showErrors && errorMessage" class="error-message">
{{ errorMessage }}
</div>
</div>

View File

@@ -2,7 +2,7 @@
import { Component, Input, forwardRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AbstractControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { AbstractControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule, Validators } from '@angular/forms'; // Validators importieren
@Component({
selector: 'app-form-field',
@@ -10,10 +10,10 @@ import { AbstractControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule }
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule // <-- WICHTIG: Hinzufügen, um mit AbstractControl zu arbeiten
ReactiveFormsModule
],
templateUrl: './form-field.component.html',
styleUrl: './form-field.component.css',
styleUrls: ['./form-field.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@@ -23,44 +23,42 @@ import { AbstractControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule }
],
})
export class FormFieldComponent {
// --- KORREKTUR: Erweitere die erlaubten Typen ---
@Input() type: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' = 'text';
@Input() label: string = '';
// Neuer Input, um das FormControl für die Fehleranzeige zu erhalten
@Input() control?: AbstractControl;
@Input() showErrors = true; // Standardmäßig Fehler anzeigen
@Input() control?: AbstractControl | null;
@Input() showErrors = true;
controlId = `form-field-${Math.random().toString(36).substring(2, 9)}`;
// --- Eigenschaften & Methoden für ControlValueAccessor ---
value: string | number = '';
onChange: (value: any) => void = () => {};
onTouched: () => void = () => {};
disabled = false;
writeValue(value: any): void {
this.value = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
// NEU: Getter, der automatisch prüft, ob das Feld ein Pflichtfeld ist.
get isRequired(): boolean {
if (!this.control) {
return false;
}
// hasValidator prüft, ob ein bestimmter Validator auf dem Control gesetzt ist.
return this.control.hasValidator(Validators.required);
}
// Hilfsfunktion für das Template, um Fehler zu finden
get errorMessage(): string | null {
if (!this.control || !this.control.errors || (!this.control.touched && !this.control.dirty)) {
return null;
}
if (this.control.hasError('required')) return 'Dieses Feld ist erforderlich.';
if (this.control.hasError('email')) return 'Bitte geben Sie eine gültige E-Mail-Adresse ein.';
if (this.control.hasError('min')) return `Der Wert muss mindestens ${this.control.errors['min'].min} sein.`;
// ... weitere Fehlermeldungen hier
const errors = this.control.errors;
if (errors['required']) return 'Dieses Feld ist erforderlich.';
if (errors['email']) return 'Bitte geben Sie eine gültige E-Mail-Adresse ein.';
if (errors['min']) return `Der Wert muss mindestens ${errors['min'].min} sein.`;
if (errors['max']) return `Der Wert darf maximal ${errors['max'].max} sein.`;
if (errors['minlength']) return `Mindestens ${errors['minlength'].requiredLength} Zeichen erforderlich.`;
return 'Ungültige Eingabe.';
}
writeValue(value: any): void { this.value = value; }
registerOnChange(fn: any): void { this.onChange = fn; }
registerOnTouched(fn: any): void { this.onTouched = fn; }
setDisabledState?(isDisabled: boolean): void { this.disabled = isDisabled; }
}

View File

@@ -76,4 +76,18 @@ border-radius: 4px;
top: 0;
font-size: 0.8rem;
color: var(--color-primary);
}
}
.required-indicator {
color: var(--color-danger);
font-weight: bold;
margin-left: 2px;
}
/* Styling für die Fehlermeldung */
.error-message {
color: var(--color-danger);
font-size: 0.875rem;
margin-top: 0.25rem;
padding-left: 0.25rem;
}

View File

@@ -1,13 +1,24 @@
<div class="form-field">
<textarea
class="form-input"
[id]="controlId"
placeholder=" "
[rows]="rows"
[(ngModel)]="value"
(ngModelChange)="onChange($event)"
(blur)="onTouched()"
[disabled]="disabled"></textarea>
<!-- /src/app/shared/components/form/form-textarea/form-textarea.component.html -->
<div class="form-field-wrapper">
<div class="form-field">
<textarea
class="form-input"
[id]="controlId"
[rows]="rows"
placeholder=" "
[disabled]="disabled"
[(ngModel)]="value"
(ngModelChange)="onChange($event)"
(blur)="onTouched()"></textarea>
<label [for]="controlId" class="form-label">
{{ label }}
<span *ngIf="isRequired" class="required-indicator">*</span>
</label>
</div>
<label [for]="controlId" class="form-label">{{ label }}</label>
<div *ngIf="showErrors && errorMessage" class="error-message">
{{ errorMessage }}
</div>
</div>

View File

@@ -1,38 +1,54 @@
// /src/app/shared/components/form/form-textarea/form-textarea.component.ts
import { Component, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, ReactiveFormsModule, FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { AbstractControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-form-textarea',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
FormsModule // Wichtig für [(ngModel)]
],
imports: [ CommonModule, FormsModule, ReactiveFormsModule ],
templateUrl: './form-textarea.component.html',
styleUrl: './form-textarea.component.css',
styleUrls: ['./form-textarea.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FormTextareaComponent),
multi: true
}
]
multi: true,
},
],
})
export class FormTextareaComponent implements ControlValueAccessor {
export class FormTextareaComponent {
@Input() label: string = '';
@Input() rows = 3; // Standardanzahl der Zeilen
@Input() rows: number = 4;
// NEU: Hinzufügen des 'control' Inputs, genau wie in form-field
@Input() control?: AbstractControl | null;
@Input() showErrors = true;
// Eindeutige ID für die Verknüpfung
controlId = `form-textarea-${Math.random().toString(36).substring(2)}`;
// --- Logik für ControlValueAccessor ---
controlId = `form-textarea-${Math.random().toString(36).substring(2, 9)}`;
value: string = '';
onChange: (value: any) => void = () => {};
onTouched: () => void = () => {};
disabled = false;
get isRequired(): boolean {
if (!this.control) return false;
return this.control.hasValidator(Validators.required);
}
get errorMessage(): string | null {
if (!this.control || !this.control.errors || (!this.control.touched && !this.control.dirty)) {
return null;
}
const errors = this.control.errors;
if (errors['required']) return 'Dieses Feld ist erforderlich.';
if (errors['minlength']) return `Mindestens ${errors['minlength'].requiredLength} Zeichen erforderlich.`;
return 'Ungültige Eingabe.';
}
writeValue(value: any): void { this.value = value; }
registerOnChange(fn: any): void { this.onChange = fn; }
registerOnTouched(fn: any): void { this.onTouched = fn; }