Aggiunge UI test sul simulatore (hub, lingue, wizard, diretta) e identificatori di accessibilità per poterli ripetere su collaudo e produzione. Co-authored-by: Cursor <cursoragent@cursor.com>
172 lines
5.4 KiB
Swift
172 lines
5.4 KiB
Swift
import SwiftUI
|
|
|
|
func wizardStepTitle(_ step: Int) -> String {
|
|
let titles = [
|
|
L10n.t("wizard.step.title.match"),
|
|
L10n.t("wizard.step.title.transmission"),
|
|
L10n.t("wizard.step.title.network")
|
|
]
|
|
return titles[min(max(step, 1), 3) - 1]
|
|
}
|
|
|
|
struct WizardStepIndicator: View {
|
|
let currentStep: Int
|
|
|
|
var body: some View {
|
|
HStack(spacing: 6) {
|
|
ForEach(1...3, id: \.self) { step in
|
|
RoundedRectangle(cornerRadius: 2)
|
|
.fill(step <= currentStep ? MatchColors.primaryRed : MatchColors.surfaceElevated)
|
|
.frame(height: 4)
|
|
}
|
|
}
|
|
.padding(.horizontal, 20)
|
|
.padding(.vertical, 12)
|
|
}
|
|
}
|
|
|
|
struct WizardFooterButtons: View {
|
|
var showBack: Bool = true
|
|
let onBack: () -> Void
|
|
let forwardLabel: String
|
|
var forwardLoading: Bool = false
|
|
let onForward: () -> Void
|
|
|
|
var body: some View {
|
|
HStack(spacing: 12) {
|
|
if showBack {
|
|
MatchSecondaryButton(label: L10n.t("wizard.action.back"), action: onBack)
|
|
.frame(maxWidth: .infinity)
|
|
.accessibilityIdentifier("wizard.back")
|
|
}
|
|
MatchPrimaryButton(label: forwardLabel, action: onForward, loading: forwardLoading)
|
|
.frame(maxWidth: .infinity)
|
|
.accessibilityIdentifier("wizard.next")
|
|
}
|
|
}
|
|
}
|
|
|
|
struct WizardOutlinedField: View {
|
|
let label: String
|
|
@Binding var text: String
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text(label)
|
|
.font(MatchTypography.bodyMedium)
|
|
.foregroundStyle(MatchColors.textSecondary)
|
|
TextField("", text: $text)
|
|
.padding(12)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 8)
|
|
.stroke(MatchColors.outline, lineWidth: 1)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct WizardReadOnlyField: View {
|
|
let label: String
|
|
let value: String
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(label).font(MatchTypography.bodyMedium)
|
|
Text(value).font(MatchTypography.titleMedium)
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding(14)
|
|
.background(MatchColors.surfaceElevated, in: RoundedRectangle(cornerRadius: 10))
|
|
}
|
|
}
|
|
|
|
struct WizardChoiceButton: View {
|
|
let label: String
|
|
let selected: Bool
|
|
let action: () -> Void
|
|
|
|
var body: some View {
|
|
Group {
|
|
if selected {
|
|
MatchPrimaryButton(label: label, action: action)
|
|
} else {
|
|
MatchSecondaryButton(label: label, action: action)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct MetricCard: View {
|
|
let label: String
|
|
let value: String
|
|
var highlight: Bool = false
|
|
|
|
var body: some View {
|
|
VStack(spacing: 4) {
|
|
Text(label)
|
|
.font(MatchTypography.bodyMedium)
|
|
.foregroundStyle(MatchColors.textSecondary)
|
|
Text(value)
|
|
.font(MatchTypography.titleMedium)
|
|
.foregroundStyle(highlight ? MatchColors.successGreen : .white)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(12)
|
|
.background(
|
|
highlight ? MatchColors.successGreen.opacity(0.12) : MatchColors.surfaceElevated,
|
|
in: RoundedRectangle(cornerRadius: 10)
|
|
)
|
|
}
|
|
}
|
|
|
|
struct WizardPlatformCard: View {
|
|
let title: String
|
|
let subtitle: String?
|
|
let selected: Bool
|
|
var enabled: Bool = true
|
|
var badge: String?
|
|
let onClick: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: onClick) {
|
|
HStack(alignment: .center, spacing: 12) {
|
|
Text(selected ? "●" : "○")
|
|
.foregroundStyle(selected ? MatchColors.primaryRed : MatchColors.textSecondary)
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(title)
|
|
.font(MatchTypography.titleMedium)
|
|
.foregroundStyle(.white.opacity(enabled ? 1 : 0.55))
|
|
if let subtitle {
|
|
Text(subtitle)
|
|
.font(MatchTypography.bodyMedium)
|
|
.foregroundStyle(MatchColors.textSecondary.opacity(enabled ? 1 : 0.55))
|
|
}
|
|
}
|
|
Spacer(minLength: 8)
|
|
if let badge {
|
|
Text(badge)
|
|
.font(MatchTypography.labelLarge)
|
|
.foregroundStyle(MatchColors.textSecondary)
|
|
.padding(.horizontal, 8)
|
|
.padding(.vertical, 4)
|
|
.background(MatchColors.surface, in: RoundedRectangle(cornerRadius: 6))
|
|
}
|
|
}
|
|
.padding(14)
|
|
.background(
|
|
selected ? MatchColors.primaryRed.opacity(0.15) : MatchColors.surfaceElevated,
|
|
in: RoundedRectangle(cornerRadius: 10)
|
|
)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 10)
|
|
.stroke(selected ? MatchColors.primaryRed : .clear, lineWidth: 1)
|
|
)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.disabled(!enabled)
|
|
.opacity(enabled ? 1 : 0.55)
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
}
|