feat: implementiere WizardScaffold und Hotkey-Integration mittels Compose

Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
2026-04-21 17:56:53 +02:00
parent 19ba044ec0
commit 91a8c38b25
3 changed files with 154 additions and 0 deletions
+9
View File
@@ -5,6 +5,8 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.kotlinSerialization)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
group = "at.mocode.frontend.core"
@@ -29,6 +31,11 @@ kotlin {
implementation(projects.frontend.core.domain)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
// Compose (for WizardScaffold UI in commonMain)
implementation(compose.ui)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
}
commonTest.dependencies {
@@ -38,6 +45,8 @@ kotlin {
jvmMain.dependencies {
implementation(projects.frontend.core.navigation)
implementation(projects.frontend.core.domain)
implementation(compose.ui)
implementation(compose.materialIconsExtended)
}
jvmTest.dependencies {
@@ -0,0 +1,109 @@
package at.mocode.frontend.core.wizard.ui
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
data class WizardStepUi(
val id: String,
val title: String,
val subtitle: String? = null,
val isSkipped: Boolean = false,
val enabled: Boolean = true
)
@Composable
fun WizardScaffold(
steps: List<WizardStepUi>,
currentIndex: Int,
canBack: Boolean,
canNext: Boolean,
onBack: () -> Unit,
onNext: () -> Unit,
onSaveDraft: (() -> Unit)? = null,
nextLabel: String = "Weiter",
backLabel: String = "Zurück",
finishLabel: String = "Fertig",
content: @Composable () -> Unit
) {
val isLast = currentIndex >= steps.lastIndex && steps.isNotEmpty()
Surface(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// Header: Breadcrumb / Step-Leiste
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.padding(bottom = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
steps.forEachIndexed { idx, meta ->
val color = when {
idx == currentIndex -> MaterialTheme.colorScheme.primary
meta.isSkipped -> MaterialTheme.colorScheme.secondary
else -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "${idx + 1}.",
color = color,
style = MaterialTheme.typography.labelMedium,
fontWeight = if (idx == currentIndex) FontWeight.Bold else FontWeight.Normal
)
Text(
text = " ${meta.title}" + if (meta.isSkipped) " (übersprungen)" else "",
color = color,
style = MaterialTheme.typography.labelMedium
)
}
if (idx != steps.lastIndex) {
Text(text = " ", color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f))
}
}
}
// Content
Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
content()
}
Spacer(Modifier.height(12.dp))
// Footer: Actions
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(verticalAlignment = Alignment.CenterVertically) {
OutlinedButton(onClick = onBack, enabled = canBack) {
Text(backLabel)
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
if (onSaveDraft != null) {
OutlinedButton(onClick = onSaveDraft) {
Text("Zwischenspeichern (Alt+S)")
}
Spacer(Modifier.padding(horizontal = 6.dp))
}
Button(onClick = onNext, enabled = canNext) {
Text(if (isLast) finishLabel else nextLabel)
}
}
}
}
}
}
@@ -0,0 +1,36 @@
package at.mocode.frontend.core.wizard.ui
import androidx.compose.runtime.Composable
/**
* Desktop-spezifische Variante. Hotkeys werden in einem FolgeInkrement ergänzt,
* sobald die ComposeAPI-Version projektweit abgestimmt ist.
*/
@Composable
fun WizardScaffoldWithHotkeys(
steps: List<WizardStepUi>,
currentIndex: Int,
canBack: Boolean,
canNext: Boolean,
onBack: () -> Unit,
onNext: () -> Unit,
onSaveDraft: (() -> Unit)? = null,
nextLabel: String = "Weiter",
backLabel: String = "Zurück",
finishLabel: String = "Fertig",
content: @Composable () -> Unit
) {
WizardScaffold(
steps = steps,
currentIndex = currentIndex,
canBack = canBack,
canNext = canNext,
onBack = onBack,
onNext = onNext,
onSaveDraft = onSaveDraft,
nextLabel = nextLabel,
backLabel = backLabel,
finishLabel = finishLabel,
content = content
)
}