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 <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-31 10:57:05 +02:00
parent 96c9abb264
commit 659e699c33
3 changed files with 129 additions and 4 deletions
@@ -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.
---
@@ -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)
}
@@ -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
)
}
}