feat(design-system): add MsEnumDropdown component and update roadmap

- Introduced a reusable `MsEnumDropdown` component for selecting enum values with customizable options, labels, and error handling.
- Updated `Frontend_Komponenten_Roadmap.md` to reflect progress in Phase 3, marking `MsEnumDropdown` as completed.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-31 10:40:44 +02:00
parent 5980fbe14f
commit 340f341594
2 changed files with 94 additions and 2 deletions
@@ -39,11 +39,11 @@ Turniermanagement bedeutet Arbeit mit Listen. Wir benötigen mächtige, aber kom
* [x] Filter-Chips für schnelle Status-Wechsel.
* [x] Anzeige der Trefferanzahl (Result Count).
## Phase 3: Formular- & Eingabe-System (Die Datenerfassung) [ZUKUNFT]
## Phase 3: Formular- & Eingabe-System (Die Datenerfassung) 🔵 [IN ARBEIT]
Eingabe von Stammdaten muss schnell und fehlerfrei erfolgen.
* [ ] **`MsEnumDropdown`:** Automatisches Mapping von Backend-Enums (ÖTO) auf UI-Auswahl.
* [x] **`MsEnumDropdown`:** Automatisches Mapping von Backend-Enums (ÖTO) auf UI-Auswahl.
* [ ] **`MsValidationWrapper`:** Konsistente Anzeige von Fehlern (z.B. "Lizenz für diese Klasse nicht ausreichend").
* [ ] **`MsSearchableSelect`:** Für die Verknüpfung von Reitern/Pferden (Autocomplete).
@@ -0,0 +1,92 @@
package at.mocode.frontend.core.designsystem.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
/**
* Ein generisches Dropdown zur Auswahl von Enum-Werten.
*
* @param label Das Label über dem Dropdown.
* @param options Alle verfügbaren Enum-Optionen (z.B. SparteE.values()).
* @param selectedOption Der aktuell gewählte Wert.
* @param onOptionSelected Callback bei Auswahl einer Option.
* @param optionLabel Transformation des Enums in einen lesbaren Text (Standard: toString()).
* @param modifier Modifier für die gesamte Komponente.
* @param enabled Ob das Dropdown bearbeitbar ist.
* @param isError Ob ein Fehler vorliegt.
* @param errorMessage Die anzuzeigende Fehlermeldung.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun <T : Enum<T>> MsEnumDropdown(
label: String,
options: Array<T>,
selectedOption: T?,
onOptionSelected: (T) -> Unit,
modifier: Modifier = Modifier,
optionLabel: (T) -> String = { it.name },
enabled: Boolean = true,
isError: Boolean = false,
errorMessage: String? = null
) {
var expanded by remember { mutableStateOf(false) }
Column(modifier = modifier) {
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { if (enabled) expanded = it }
) {
OutlinedTextField(
value = selectedOption?.let { optionLabel(it) } ?: "",
onValueChange = {},
readOnly = true,
label = { Text(label, style = MaterialTheme.typography.bodySmall) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
modifier = Modifier
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, enabled)
.fillMaxWidth(),
isError = isError,
enabled = enabled,
singleLine = true,
textStyle = MaterialTheme.typography.bodyMedium,
shape = MaterialTheme.shapes.small
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
options.forEach { option ->
DropdownMenuItem(
text = {
Text(
text = optionLabel(option),
style = MaterialTheme.typography.bodyMedium
)
},
onClick = {
onOptionSelected(option)
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding
)
}
}
}
if (isError && errorMessage != null) {
Text(
text = errorMessage,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.labelSmall,
modifier = Modifier.padding(start = 16.dp, top = 4.dp)
)
}
}
}