diff --git a/frontend/src/app.css b/frontend/src/app.css index a43cfc8..e19d511 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -1186,56 +1186,203 @@ a.analysis-split__nav-item { .form-action-bar__inner { display: flex; flex-wrap: wrap; - align-items: center; - justify-content: space-between; + align-items: stretch; + justify-content: flex-start; gap: 8px; max-width: min(1100px, 100%); margin: 0 auto; -} -.form-action-bar__spacer { - flex: 0 0 auto; min-width: 0; } .form-action-bar__primary-group { display: flex; flex-wrap: wrap; - align-items: center; + align-items: stretch; justify-content: flex-end; gap: 8px; margin-left: auto; + min-width: 0; + flex: 1 1 auto; } .form-action-bar__btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; min-height: 44px; padding: 8px 14px; font-size: 0.88rem; font-weight: 600; white-space: nowrap; + min-width: 0; } .form-action-bar__btn--cancel { min-width: 5.5rem; } +.form-action-bar__icon { + flex-shrink: 0; + display: none; +} +.form-action-bar__text--short { + display: none; +} +.form-action-bar__saving { + font-size: 0.82rem; + letter-spacing: 0.02em; +} +/* Form-Modale: Overlay + Panel (Desktop zentriert, Mobile Vollbild) */ +.modal-overlay--form { + position: fixed; + inset: 0; + z-index: 1000; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; + overflow: hidden; + box-sizing: border-box; +} +.modal-overlay--form.modal-overlay--raised { + z-index: 1100; +} .modal-panel--form { display: flex; flex-direction: column; - max-height: min(92vh, 100%); + background: var(--surface); + border-radius: 12px; + padding: clamp(12px, 3vw, 2rem); + width: 100%; + max-width: min(1100px, 100%); + max-height: min(92vh, 100dvh); overflow: hidden; min-height: 0; + min-width: 0; + box-sizing: border-box; +} +.modal-panel--form.modal-panel--narrow { + max-width: min(560px, 100%); +} +.modal-panel--form.card { + /* card-Klasse nur für Desktop-Rahmen; Mobile überschreibt unten */ +} +.modal-panel__title { + margin: 0 0 1rem; + flex-shrink: 0; + font-size: clamp(1.05rem, 4vw, 1.25rem); + line-height: 1.25; + min-width: 0; +} +.modal-panel__intro { + font-size: 0.88rem; + color: var(--text2); + line-height: 1.45; + margin: 0 0 1rem; + flex-shrink: 0; + min-width: 0; } .modal-form-shell { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; + min-width: 0; overflow: hidden; } .modal-form-shell__body { flex: 1 1 auto; min-height: 0; + min-width: 0; + overflow-x: hidden; overflow-y: auto; + overscroll-behavior-x: none; -webkit-overflow-scrolling: touch; padding-bottom: 4px; } +.modal-panel--form > *, +.modal-form-shell__body > * { + max-width: 100%; + min-width: 0; +} + +@media (max-width: 639px) { + .modal-overlay--form { + padding: 0; + align-items: stretch; + } + .modal-panel--form { + max-width: 100%; + max-height: 100dvh; + height: 100dvh; + border-radius: 0; + padding: 12px; + padding-top: max(12px, env(safe-area-inset-top, 0px)); + padding-bottom: max(0px, env(safe-area-inset-bottom, 0px)); + } + .modal-panel--form.card { + border: none; + border-radius: 0; + padding: 12px; + padding-top: max(12px, env(safe-area-inset-top, 0px)); + padding-bottom: max(0px, env(safe-area-inset-bottom, 0px)); + } + .modal-panel__title { + margin-bottom: 0.75rem; + font-size: 1.05rem; + } + .modal-panel__intro { + margin-bottom: 0.75rem; + font-size: 0.85rem; + } + + .form-action-bar { + padding: 5px 6px; + padding-bottom: max(5px, env(safe-area-inset-bottom, 0px)); + } + .form-action-bar__inner { + flex-wrap: nowrap; + gap: 6px; + max-width: none; + } + .form-action-bar__primary-group { + flex: 1 1 0; + flex-wrap: nowrap; + gap: 6px; + margin-left: 0; + min-width: 0; + } + .form-action-bar__btn { + min-height: 38px; + padding: 6px 8px; + font-size: 0.75rem; + gap: 4px; + flex: 1 1 0; + } + .form-action-bar__btn--cancel { + flex: 0 0 38px; + width: 38px; + min-width: 38px; + max-width: 38px; + padding: 0; + } + .form-action-bar__btn--cancel .form-action-bar__text--long, + .form-action-bar__btn--cancel .form-action-bar__text--short { + display: none !important; + } + .form-action-bar__icon { + display: inline; + } + .form-action-bar__text--long { + display: none; + } + .form-action-bar__text--short { + display: inline; + } + .form-action-bar--single-primary .form-action-bar__primary-group .form-action-bar__btn { + flex: 1 1 auto; + } +} + .page-form-shell { display: flex; flex-direction: column; diff --git a/frontend/src/components/FormActionBar.jsx b/frontend/src/components/FormActionBar.jsx index 00614eb..e177b94 100644 --- a/frontend/src/components/FormActionBar.jsx +++ b/frontend/src/components/FormActionBar.jsx @@ -1,13 +1,32 @@ +import { Check, Save, X } from 'lucide-react' + /** * Feste Aktionsleiste für Formulare/Modale: Speichern, Speichern & schließen, Abbrechen. * Bleibt sichtbar (sticky), während der Formularinhalt scrollt. */ +function ActionLabel({ Icon, long, short, saving, savingShort = '…' }) { + if (saving) { + return {savingShort} + } + return ( + <> + {Icon ? : null} + {long} + {short != null && short !== '' ? ( + {short} + ) : null} + + ) +} + export default function FormActionBar({ placement = 'bottom', variant = 'default', saving = false, saveLabel, + saveShortLabel, saveAndCloseLabel = 'Speichern & schließen', + saveAndCloseShortLabel, cancelLabel = 'Abbrechen', onSave, onSaveAndClose, @@ -20,6 +39,8 @@ export default function FormActionBar({ primaryIsSaveOnly = false, }) { const labelSave = saveLabel ?? (isNew ? 'Anlegen' : 'Speichern') + const shortSave = saveShortLabel ?? (isNew ? 'Neu' : 'Sichern') + const shortClose = saveAndCloseShortLabel ?? 'Fertig' const showSaveBtn = showSave && (Boolean(onSave) || Boolean(formId)) const showCloseBtn = showSaveAndClose && (Boolean(onSaveAndClose) || Boolean(formId)) const showCancelBtn = showCancel && Boolean(onCancel) @@ -33,9 +54,13 @@ export default function FormActionBar({ primaryIsSaveOnly ? ' btn-secondary' : ' btn-primary' }` + const primaryCount = (showSaveBtn ? 1 : 0) + (showCloseBtn ? 1 : 0) + return (
@@ -46,12 +71,12 @@ export default function FormActionBar({ className="btn btn-secondary form-action-bar__btn form-action-bar__btn--cancel" onClick={onCancel} disabled={saving} + aria-label={cancelLabel} + title={cancelLabel} > - {cancelLabel} + - ) : ( - - )} + ) : null}
{showSaveBtn ? ( ) : null} {showCloseBtn ? ( @@ -71,8 +97,14 @@ export default function FormActionBar({ className={closeBtnClass} disabled={saving} onClick={onSaveAndClose || undefined} + title={saveAndCloseLabel} > - {saving ? 'Speichern…' : saveAndCloseLabel} + ) : null}
diff --git a/frontend/src/components/planning/SaveExercisesAsModuleModal.jsx b/frontend/src/components/planning/SaveExercisesAsModuleModal.jsx index 387ce47..a2d58a9 100644 --- a/frontend/src/components/planning/SaveExercisesAsModuleModal.jsx +++ b/frontend/src/components/planning/SaveExercisesAsModuleModal.jsx @@ -147,30 +147,10 @@ export default function SaveExercisesAsModuleModal({ if (!open) return null return ( -
-
-

Übungen als Trainingsmodul

-

+

+
+

Übungen als Trainingsmodul

+

Es werden die gespeicherten Übungspositionen der Einheit vom{' '} {unitLabel || '…'} verwendet. Speichere die Planung vorher, wenn du den aktuellen Stand brauchst. @@ -296,6 +276,7 @@ export default function SaveExercisesAsModuleModal({ saving={submitting} showSave={false} saveAndCloseLabel="Modul anlegen" + saveAndCloseShortLabel="Anlegen" onCancel={onClose} /> diff --git a/frontend/src/components/planning/TrainingPlanningUnitFormModal.jsx b/frontend/src/components/planning/TrainingPlanningUnitFormModal.jsx index 14f3032..b15004a 100644 --- a/frontend/src/components/planning/TrainingPlanningUnitFormModal.jsx +++ b/frontend/src/components/planning/TrainingPlanningUnitFormModal.jsx @@ -57,37 +57,9 @@ export default function TrainingPlanningUnitFormModal({ const formId = 'planning-unit-form' return ( -

-
-

+
+
+

{editingUnit ? 'Trainingseinheit bearbeiten' : 'Neue Trainingseinheit'}

diff --git a/frontend/src/components/planning/TrainingPublishToFrameworkModal.jsx b/frontend/src/components/planning/TrainingPublishToFrameworkModal.jsx index 6208799..3789013 100644 --- a/frontend/src/components/planning/TrainingPublishToFrameworkModal.jsx +++ b/frontend/src/components/planning/TrainingPublishToFrameworkModal.jsx @@ -192,30 +192,10 @@ export default function TrainingPublishToFrameworkModal({ if (!open) return null return ( -
-
-

Ablauf ins Rahmenprogramm übernehmen

-

+

+
+

Ablauf ins Rahmenprogramm übernehmen

+

Es wird der zuletzt gespeicherte Ablauf dieser Einheit aus der Datenbank übernommen. Nicht gespeicherte Änderungen im Formular sind nicht enthalten — bitte vorher die Einheit speichern.

@@ -415,6 +395,7 @@ export default function TrainingPublishToFrameworkModal({ saving={submitting} showSave={false} saveAndCloseLabel="In Rahmen übernehmen" + saveAndCloseShortLabel="Übernehmen" onCancel={resetAndClose} />