// // TeamPickerView.swift // SportsTime // // Multi-select team picker grid for Team-First planning mode. // Users select multiple teams (>=2) and the app finds optimal trip windows. // import SwiftUI struct TeamPickerView: View { @Environment(\.colorScheme) private var colorScheme let sport: Sport @Binding var selectedTeamIds: Set @State private var searchText = "" private let columns = [ GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) ] private var teams: [Team] { let allTeams = AppDataProvider.shared.teams .filter { $0.sport == sport } .sorted { $0.fullName < $1.fullName } if searchText.isEmpty { return allTeams } return allTeams.filter { $0.fullName.localizedCaseInsensitiveContains(searchText) || $0.city.localizedCaseInsensitiveContains(searchText) || $0.abbreviation.localizedCaseInsensitiveContains(searchText) } } var body: some View { VStack(spacing: Theme.Spacing.md) { // Search bar HStack { Image(systemName: "magnifyingglass") .foregroundStyle(Theme.textMuted(colorScheme)) .accessibilityHidden(true) TextField("Search teams...", text: $searchText) .textFieldStyle(.plain) if !searchText.isEmpty { Button { searchText = "" } label: { Image(systemName: "xmark.circle.fill") .foregroundStyle(Theme.textMuted(colorScheme)) } .minimumHitTarget() .accessibilityLabel("Clear search") } } .padding(Theme.Spacing.sm) .background(Theme.cardBackgroundElevated(colorScheme)) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)) // Selection count badge if !selectedTeamIds.isEmpty { HStack { Text("\(selectedTeamIds.count) team\(selectedTeamIds.count == 1 ? "" : "s") selected") .font(.caption) .fontWeight(.medium) .foregroundStyle(.white) .padding(.horizontal, Theme.Spacing.sm) .padding(.vertical, Theme.Spacing.xs) .background(Theme.warmOrange) .clipShape(Capsule()) Spacer() Button("Clear all") { Theme.Animation.withMotion(Theme.Animation.spring) { selectedTeamIds.removeAll() } } .font(.caption) .foregroundStyle(Theme.warmOrange) } } // Team grid ScrollView { LazyVGrid(columns: columns, spacing: Theme.Spacing.sm) { ForEach(teams) { team in TeamCard( team: team, isSelected: selectedTeamIds.contains(team.id), onTap: { toggleTeam(team) } ) } } .padding(.bottom, Theme.Spacing.lg) } } } private func toggleTeam(_ team: Team) { Theme.Animation.withMotion(Theme.Animation.spring) { if selectedTeamIds.contains(team.id) { selectedTeamIds.remove(team.id) } else { selectedTeamIds.insert(team.id) } } } } // MARK: - Team Card private struct TeamCard: View { @Environment(\.colorScheme) private var colorScheme let team: Team let isSelected: Bool let onTap: () -> Void var body: some View { Button(action: onTap) { VStack(spacing: Theme.Spacing.xs) { // Team color circle with checkmark overlay ZStack { Circle() .fill(teamColor) .frame(width: 44, height: 44) if isSelected { Circle() .fill(Color.black.opacity(0.3)) .frame(width: 44, height: 44) Image(systemName: "checkmark") .font(.title3) .fontWeight(.bold) .foregroundStyle(.white) .accessibilityHidden(true) } } // Team name Text(team.name) .font(.caption) .fontWeight(isSelected ? .semibold : .regular) .foregroundStyle(isSelected ? Theme.warmOrange : Theme.textPrimary(colorScheme)) .lineLimit(1) .minimumScaleFactor(0.8) // City Text(team.city) .font(.caption2) .foregroundStyle(Theme.textMuted(colorScheme)) .lineLimit(1) } .frame(maxWidth: .infinity) .padding(.vertical, Theme.Spacing.sm) .background(isSelected ? Theme.warmOrange.opacity(0.15) : Color.clear) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)) .contentShape(Rectangle()) .overlay( RoundedRectangle(cornerRadius: Theme.CornerRadius.medium) .stroke(isSelected ? Theme.warmOrange : Theme.textMuted(colorScheme).opacity(0.3), lineWidth: isSelected ? 2 : 1) ) } .buttonStyle(.plain) .accessibilityValue(isSelected ? "Selected" : "Not selected") .accessibilityAddTraits(isSelected ? .isSelected : []) } private var teamColor: Color { if let hex = team.primaryColor { return Color(hex: hex) } return team.sport.themeColor } } // MARK: - Preview #Preview { TeamPickerView( sport: .mlb, selectedTeamIds: .constant(["team_mlb_bos", "team_mlb_nyy"]) ) .padding() .themedBackground() }