refactor: Standardisiere Formular-Layout (Label oben, volle Breite, linksbündig)
CSS (frontend/src/app.css): - .form-row: flex-direction column, volle Breite, margin-bottom 16px - .form-label: display block, font-weight 600, linksbündig - .form-input: width 100%, text-align left (statt right 90px), padding 10px 12px - textarea.form-input: resize vertical, min-height 80px - select.form-input: cursor pointer - .form-row--inline: Neue Variante für Ausnahmen (kurze Werte + Einheit) Dokumentation (.claude/rules/CODING_RULES.md): - Neue Regel Frontend §6: Formular-Standard (VERBINDLICH ab 2026-04-22) - Code-Beispiele für Standard-Layout (input, textarea, select) - Klare Regeln: Label = <label>, Pflichtfelder mit *, volle Breite, linksbündig - Inline-Variante nur für Ausnahmen (Zahlen mit Einheit) WICHTIG: Bestehende Formulare (Exercises, Clubs, Skills, TrainingPlanning) nutzen bereits größtenteils diese Struktur und profitieren automatisch von den CSS-Änderungen. Vorteile: - Konsistente UX über alle Formulare - Mobile-friendly (volle Breite, kein horizontal scrolling) - Bessere Lesbarkeit (Label als Überschrift) - Einfacher wartbar (ein Standard für alle) Nächster Schritt: Bestehende Formulare testen, ggf. kleine Anpassungen
This commit is contained in:
parent
7f156ba085
commit
cbb783222c
|
|
@ -78,6 +78,62 @@ style={{color: 'var(--accent)'}}
|
||||||
style={{color: '#1D9E75'}}
|
style={{color: '#1D9E75'}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 6. Formular-Standard (VERBINDLICH ab 2026-04-22)
|
||||||
|
**Alle neuen Formulare verwenden den Standard-Stil:**
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// ✅ Standard: Label oben, volle Breite, linksbündig
|
||||||
|
<div className="form-row">
|
||||||
|
<label className="form-label">Feldname *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-input"
|
||||||
|
value={formData.field}
|
||||||
|
onChange={(e) => updateFormField('field', e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// ✅ Textarea
|
||||||
|
<div className="form-row">
|
||||||
|
<label className="form-label">Beschreibung</label>
|
||||||
|
<textarea
|
||||||
|
className="form-input"
|
||||||
|
rows={3}
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) => updateFormField('description', e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// ✅ Select
|
||||||
|
<div className="form-row">
|
||||||
|
<label className="form-label">Status</label>
|
||||||
|
<select
|
||||||
|
className="form-input"
|
||||||
|
value={formData.status}
|
||||||
|
onChange={(e) => updateFormField('status', e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="active">Aktiv</option>
|
||||||
|
<option value="inactive">Inaktiv</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// ❌ NICHT: Inline-Layout mit Label links
|
||||||
|
// Nur für Ausnahmen (kurze Werte mit Einheit) nutzen:
|
||||||
|
<div className="form-row--inline">
|
||||||
|
<label className="form-label">Dauer</label>
|
||||||
|
<input type="number" className="form-input" value={duration} />
|
||||||
|
<span className="form-unit">min</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regeln:**
|
||||||
|
- Label ist `<label>` mit Klasse `.form-label` (nicht `<div>`)
|
||||||
|
- Input/Textarea/Select nutzen `.form-input`
|
||||||
|
- Volle Breite (100%), linksbündig
|
||||||
|
- Pflichtfelder mit `*` im Label kennzeichnen
|
||||||
|
- Inline-Variante (`.form-row--inline`) nur für Ausnahmen (Zahlen mit Einheit)
|
||||||
|
|
||||||
## Git & Deployment
|
## Git & Deployment
|
||||||
|
|
||||||
### 1. Nie direkt auf main pushen
|
### 1. Nie direkt auf main pushen
|
||||||
|
|
|
||||||
|
|
@ -113,25 +113,93 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
|
||||||
.delta-pos { color: var(--accent); }
|
.delta-pos { color: var(--accent); }
|
||||||
.delta-neg { color: var(--danger); }
|
.delta-neg { color: var(--danger); }
|
||||||
|
|
||||||
/* Form */
|
/* Form - STANDARD: Label oben, volle Breite, linksbündig */
|
||||||
.form-section { margin-bottom: 20px; }
|
.form-section { margin-bottom: 20px; }
|
||||||
.form-section-title {
|
.form-section-title {
|
||||||
font-size: 13px; font-weight: 600; color: var(--text3);
|
font-size: 13px; font-weight: 600; color: var(--text3);
|
||||||
text-transform: uppercase; letter-spacing: 0.05em;
|
text-transform: uppercase; letter-spacing: 0.05em;
|
||||||
margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
|
margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.form-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--border); }
|
|
||||||
.form-row:last-child { border-bottom: none; }
|
/* Standard Form Row: Label oben, Input darunter, volle Breite */
|
||||||
.form-label { flex: 1; font-size: 14px; color: var(--text1); }
|
.form-row {
|
||||||
.form-sub { font-size: 11px; color: var(--text3); display: block; margin-top: 1px; }
|
display: flex;
|
||||||
.form-input {
|
flex-direction: column;
|
||||||
width: 90px; padding: 7px 10px; text-align: right;
|
align-items: stretch;
|
||||||
font-family: var(--font); font-size: 15px; font-weight: 500; color: var(--text1);
|
gap: 8px;
|
||||||
background: var(--surface2); border: 1.5px solid var(--border2);
|
padding: 0;
|
||||||
border-radius: 8px; transition: border-color 0.15s;
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text1);
|
||||||
|
text-align: left;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-sub {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text3);
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-family: var(--font);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text1);
|
||||||
|
background: var(--surface2);
|
||||||
|
border: 1.5px solid var(--border2);
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Textarea spezifisch */
|
||||||
|
textarea.form-input {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select spezifisch */
|
||||||
|
select.form-input {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline-Variante für Ausnahmen (z.B. kurze Werte mit Einheit) */
|
||||||
|
.form-row--inline {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row--inline .form-label {
|
||||||
|
flex: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row--inline .form-input {
|
||||||
|
width: 90px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-unit {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text3);
|
||||||
|
width: 24px;
|
||||||
}
|
}
|
||||||
.form-input:focus { outline: none; border-color: var(--accent); }
|
|
||||||
.form-unit { font-size: 12px; color: var(--text3); width: 24px; }
|
|
||||||
|
|
||||||
/* Einstellungen Profil: Label als Überschrift oben, volle Breite, linksbündig */
|
/* Einstellungen Profil: Label als Überschrift oben, volle Breite, linksbündig */
|
||||||
.settings-page__field {
|
.settings-page__field {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user