From 659e699c3372aed752a4972eb2d0bad19c8ee228 Mon Sep 17 00:00:00 2001 From: Stefan Mogeritsch Date: Tue, 31 Mar 2026 10:57:05 +0200 Subject: [PATCH] feat(design-system): add `MsDialogShell` and `MsConfirmDialog` components, and update roadmap - Introduced `MsDialogShell` for a standardized modal dialog framework. - Added `MsConfirmDialog` as a utility for common confirmation dialogs. - Updated `Frontend_Komponenten_Roadmap.md` to mark `MsDialogShell` as complete. Signed-off-by: Stefan Mogeritsch --- .../Frontend_Komponenten_Roadmap.md | 2 +- .../core/designsystem/components/MsButton.kt | 10 +- .../designsystem/components/MsDialogShell.kt | 121 ++++++++++++++++++ 3 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsDialogShell.kt diff --git a/docs/01_Architecture/Frontend_Komponenten_Roadmap.md b/docs/01_Architecture/Frontend_Komponenten_Roadmap.md index 0de8595f..468725b3 100644 --- a/docs/01_Architecture/Frontend_Komponenten_Roadmap.md +++ b/docs/01_Architecture/Frontend_Komponenten_Roadmap.md @@ -53,7 +53,7 @@ Hier bringen wir alles zusammen, bevor das finale Routing implementiert wird. * [x] **`MsMasterDetailLayout`:** Standard-Layout für alle Stammdaten-Screens (Liste & Editor). * [x] **`MsActionToolbar`:** Einheitliche Platzierung von Hauptaktionen (Neu, Speichern, Drucken). -* [ ] **`MsDialogShell`:** Standardisierter Rahmen für Modale und Bestätigungsdialoge. +* [x] **`MsDialogShell`:** Standardisierter Rahmen für Modale und Bestätigungsdialoge. --- diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsButton.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsButton.kt index 6e48f5e0..4a8f718a 100644 --- a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsButton.kt +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsButton.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp enum class ButtonVariant { @@ -25,7 +26,8 @@ fun MsButton( size: ButtonSize = ButtonSize.MEDIUM, enabled: Boolean = true, isLoading: Boolean = false, - fullWidth: Boolean = false + fullWidth: Boolean = false, + containerColor: Color? = null ) { val buttonModifier = modifier.then( if (fullWidth) Modifier.fillMaxWidth() else Modifier @@ -41,7 +43,8 @@ fun MsButton( ButtonVariant.PRIMARY -> Button( onClick = onClick, modifier = buttonModifier, - enabled = enabled && !isLoading + enabled = enabled && !isLoading, + colors = if (containerColor != null) ButtonDefaults.buttonColors(containerColor = containerColor) else ButtonDefaults.buttonColors() ) { ButtonContent(text = text, isLoading = isLoading) } @@ -49,7 +52,8 @@ fun MsButton( ButtonVariant.SECONDARY -> FilledTonalButton( onClick = onClick, modifier = buttonModifier, - enabled = enabled && !isLoading + enabled = enabled && !isLoading, + colors = if (containerColor != null) ButtonDefaults.filledTonalButtonColors(containerColor = containerColor) else ButtonDefaults.filledTonalButtonColors() ) { ButtonContent(text = text, isLoading = isLoading) } diff --git a/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsDialogShell.kt b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsDialogShell.kt new file mode 100644 index 00000000..a15c46a6 --- /dev/null +++ b/frontend/core/design-system/src/commonMain/kotlin/at/mocode/frontend/core/designsystem/components/MsDialogShell.kt @@ -0,0 +1,121 @@ +package at.mocode.frontend.core.designsystem.components + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import at.mocode.frontend.core.designsystem.theme.Dimens + +/** + * Ein einheitlicher Rahmen für modale Dialoge. + * + * @param title Die Überschrift des Dialogs. + * @param onDismissRequest Callback, wenn der Dialog geschlossen werden soll (z.B. Klick außerhalb). + * @param confirmButton Die primäre Aktion (z.B. OK, Speichern). + * @param dismissButton Die sekundäre Aktion (z.B. Abbrechen). + * @param modifier Modifier für das Surface des Dialogs. + * @param content Der eigentliche Inhalt des Dialogs. + */ +@Composable +fun MsDialogShell( + title: String, + onDismissRequest: () -> Unit, + confirmButton: @Composable () -> Unit, + modifier: Modifier = Modifier, + dismissButton: @Composable (() -> Unit)? = null, + content: @Composable ColumnScope.() -> Unit +) { + Dialog(onDismissRequest = onDismissRequest) { + Surface( + modifier = modifier + .fillMaxWidth(0.9f) + .wrapContentHeight(), + shape = MaterialTheme.shapes.medium, + tonalElevation = 6.dp + ) { + Column( + modifier = Modifier.padding(Dimens.SpacingM) + ) { + // --- 1. Titel-Bereich --- + Text( + text = title, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = Dimens.SpacingM) + ) + + // --- 2. Content-Bereich --- + Column( + modifier = Modifier + .fillMaxWidth() + .weight(weight = 1f, fill = false) + ) { + content() + } + + Spacer(modifier = Modifier.height(Dimens.SpacingM)) + + // --- 3. Button-Leiste --- + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + if (dismissButton != null) { + dismissButton() + Spacer(modifier = Modifier.width(Dimens.SpacingS)) + } + confirmButton() + } + } + } + } +} + +/** + * Hilfs-Funktion für einen Standard-Bestätigungsdialog. + */ +@Composable +fun MsConfirmDialog( + title: String, + message: String, + onConfirm: () -> Unit, + onDismiss: () -> Unit, + confirmText: String = "Bestätigen", + dismissText: String = "Abbrechen", + isDestructive: Boolean = false +) { + MsDialogShell( + title = title, + onDismissRequest = onDismiss, + confirmButton = { + MsButton( + text = confirmText, + onClick = onConfirm, + variant = if (isDestructive) ButtonVariant.PRIMARY else ButtonVariant.PRIMARY, + // Bei destruktiven Aktionen könnten wir hier später eine rote Farbe erzwingen + containerColor = if (isDestructive) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary, + size = ButtonSize.SMALL + ) + }, + dismissButton = { + MsButton( + text = dismissText, + onClick = onDismiss, + variant = ButtonVariant.TEXT, + size = ButtonSize.SMALL + ) + } + ) { + Text( + text = message, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } +}