diff --git a/frontend/core/wizard/build.gradle.kts b/frontend/core/wizard/build.gradle.kts index 5195ea67..1e0adb95 100644 --- a/frontend/core/wizard/build.gradle.kts +++ b/frontend/core/wizard/build.gradle.kts @@ -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 { diff --git a/frontend/core/wizard/src/commonMain/kotlin/at/mocode/frontend/core/wizard/ui/WizardScaffold.kt b/frontend/core/wizard/src/commonMain/kotlin/at/mocode/frontend/core/wizard/ui/WizardScaffold.kt new file mode 100644 index 00000000..2519b22e --- /dev/null +++ b/frontend/core/wizard/src/commonMain/kotlin/at/mocode/frontend/core/wizard/ui/WizardScaffold.kt @@ -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, + 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) + } + } + } + } + } +} diff --git a/frontend/core/wizard/src/jvmMain/kotlin/at/mocode/frontend/core/wizard/ui/WizardHotkeys.kt b/frontend/core/wizard/src/jvmMain/kotlin/at/mocode/frontend/core/wizard/ui/WizardHotkeys.kt new file mode 100644 index 00000000..7c16c852 --- /dev/null +++ b/frontend/core/wizard/src/jvmMain/kotlin/at/mocode/frontend/core/wizard/ui/WizardHotkeys.kt @@ -0,0 +1,36 @@ +package at.mocode.frontend.core.wizard.ui + +import androidx.compose.runtime.Composable + +/** + * Desktop-spezifische Variante. Hotkeys werden in einem Folge‑Inkrement ergänzt, + * sobald die Compose‑API-Version projektweit abgestimmt ist. + */ +@Composable +fun WizardScaffoldWithHotkeys( + steps: List, + 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 + ) +}