refactor: extract reusable SportSelectorGrid component

Create unified sport selector grid used across Home (Quick Start),
Trip Creation, and Progress views. Removes duplicate button implementations
and ensures consistent grid layout with centered bottom row.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-11 10:38:10 -06:00
parent a292b5c20c
commit 475f444288
4 changed files with 321 additions and 211 deletions

View File

@@ -133,16 +133,14 @@ struct ProgressTabView: View {
// MARK: - League Selector
private var leagueSelector: some View {
HStack(spacing: Theme.Spacing.sm) {
ForEach(Sport.supported) { sport in
LeagueSelectorButton(
sport: sport,
isSelected: viewModel.selectedSport == sport,
progress: progressForSport(sport)
) {
withAnimation(Theme.Animation.spring) {
viewModel.selectSport(sport)
}
SportSelectorGrid { sport in
SportProgressButton(
sport: sport,
isSelected: viewModel.selectedSport == sport,
progress: progressForSport(sport)
) {
withAnimation(Theme.Animation.spring) {
viewModel.selectSport(sport)
}
}
}
@@ -392,54 +390,6 @@ struct ProgressTabView: View {
// MARK: - Supporting Views
struct LeagueSelectorButton: View {
let sport: Sport
let isSelected: Bool
let progress: Double
let action: () -> Void
@Environment(\.colorScheme) private var colorScheme
var body: some View {
Button(action: action) {
VStack(spacing: Theme.Spacing.xs) {
ZStack {
// Background circle with progress
Circle()
.stroke(sport.themeColor.opacity(0.2), lineWidth: 3)
.frame(width: 50, height: 50)
Circle()
.trim(from: 0, to: progress)
.stroke(sport.themeColor, style: StrokeStyle(lineWidth: 3, lineCap: .round))
.frame(width: 50, height: 50)
.rotationEffect(.degrees(-90))
// Sport icon
Image(systemName: sport.iconName)
.font(.title2)
.foregroundStyle(isSelected ? sport.themeColor : Theme.textMuted(colorScheme))
}
Text(sport.rawValue)
.font(.caption)
.foregroundStyle(isSelected ? Theme.textPrimary(colorScheme) : Theme.textMuted(colorScheme))
}
.frame(maxWidth: .infinity)
.padding(.vertical, Theme.Spacing.sm)
.background(isSelected ? Theme.cardBackground(colorScheme) : Color.clear)
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
.overlay {
if isSelected {
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(sport.themeColor, lineWidth: 2)
}
}
}
.buttonStyle(.plain)
}
}
struct ProgressStatPill: View {
let icon: String
let value: String