From 002dfbd872e13ff0e0816f14a0365966394e6708 Mon Sep 17 00:00:00 2001 From: Trey t Date: Mon, 12 Jan 2026 20:47:11 -0600 Subject: [PATCH] feat(wizard): add SportsStep with availability graying Co-Authored-By: Claude Opus 4.5 --- .../Trip/Views/Wizard/Steps/SportsStep.swift | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 SportsTime/Features/Trip/Views/Wizard/Steps/SportsStep.swift diff --git a/SportsTime/Features/Trip/Views/Wizard/Steps/SportsStep.swift b/SportsTime/Features/Trip/Views/Wizard/Steps/SportsStep.swift new file mode 100644 index 0000000..4f28619 --- /dev/null +++ b/SportsTime/Features/Trip/Views/Wizard/Steps/SportsStep.swift @@ -0,0 +1,142 @@ +// +// SportsStep.swift +// SportsTime +// +// Step 2 of the trip wizard - select sports leagues. +// + +import SwiftUI + +struct SportsStep: View { + @Environment(\.colorScheme) private var colorScheme + @Binding var selectedSports: Set + let sportAvailability: [Sport: Bool] + let isLoading: Bool + let canSelectSport: (Sport) -> Bool + + private let columns = [ + GridItem(.flexible()), + GridItem(.flexible()), + GridItem(.flexible()) + ] + + var body: some View { + VStack(alignment: .leading, spacing: Theme.Spacing.md) { + StepHeader( + title: "Which sports interest you?", + subtitle: "Select one or more leagues" + ) + + if isLoading { + HStack { + ProgressView() + .scaleEffect(0.8) + Text("Checking game availability...") + .font(.caption) + .foregroundStyle(Theme.textMuted(colorScheme)) + } + .padding(.vertical, Theme.Spacing.sm) + } + + LazyVGrid(columns: columns, spacing: Theme.Spacing.sm) { + ForEach(Sport.supported, id: \.self) { sport in + SportCard( + sport: sport, + isSelected: selectedSports.contains(sport), + isAvailable: canSelectSport(sport), + onTap: { + if canSelectSport(sport) { + toggleSport(sport) + } + } + ) + } + } + } + .padding(Theme.Spacing.lg) + .background(Theme.cardBackground(colorScheme)) + .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large)) + .overlay { + RoundedRectangle(cornerRadius: Theme.CornerRadius.large) + .stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1) + } + } + + private func toggleSport(_ sport: Sport) { + if selectedSports.contains(sport) { + selectedSports.remove(sport) + } else { + selectedSports.insert(sport) + } + } +} + +// MARK: - Sport Card + +private struct SportCard: View { + @Environment(\.colorScheme) private var colorScheme + let sport: Sport + let isSelected: Bool + let isAvailable: Bool + let onTap: () -> Void + + var body: some View { + Button(action: onTap) { + VStack(spacing: Theme.Spacing.xs) { + Image(systemName: sport.iconName) + .font(.title2) + .foregroundStyle(cardColor) + + Text(sport.rawValue) + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(cardColor) + + if !isAvailable { + Text("No games") + .font(.caption2) + .foregroundStyle(Theme.textMuted(colorScheme)) + } + } + .frame(maxWidth: .infinity) + .padding(.vertical, Theme.Spacing.md) + .background(backgroundColor) + .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)) + .overlay( + RoundedRectangle(cornerRadius: Theme.CornerRadius.medium) + .stroke(borderColor, lineWidth: isSelected ? 2 : 1) + ) + } + .buttonStyle(.plain) + .opacity(isAvailable ? 1.0 : 0.5) + .disabled(!isAvailable) + } + + private var cardColor: Color { + if !isAvailable { return Theme.textMuted(colorScheme) } + return isSelected ? sport.themeColor : Theme.textSecondary(colorScheme) + } + + private var backgroundColor: Color { + if !isAvailable { return Theme.textMuted(colorScheme).opacity(0.1) } + return isSelected ? sport.themeColor.opacity(0.15) : Color.clear + } + + private var borderColor: Color { + if !isAvailable { return Theme.textMuted(colorScheme).opacity(0.3) } + return isSelected ? sport.themeColor : Theme.textMuted(colorScheme).opacity(0.3) + } +} + +// MARK: - Preview + +#Preview { + SportsStep( + selectedSports: .constant([.mlb]), + sportAvailability: [.mlb: true, .nba: true, .wnba: false], + isLoading: false, + canSelectSport: { sport in sport != .wnba } + ) + .padding() + .themedBackground() +}