feat(wizard): add all wizard step components
- DatesStep: date range selection with duration indicator - RegionsStep: geographic region selection - RoutePreferenceStep: direct/scenic/balanced preference - RepeatCitiesStep: unique vs repeat city visits - MustStopsStep: optional must-stop locations - ReviewStep: summary and plan button Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
98
SportsTime/Features/Trip/Views/Wizard/Steps/DatesStep.swift
Normal file
98
SportsTime/Features/Trip/Views/Wizard/Steps/DatesStep.swift
Normal file
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// DatesStep.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Step 3 of the trip wizard - select travel dates.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DatesStep: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Binding var startDate: Date
|
||||
@Binding var endDate: Date
|
||||
@Binding var hasSetDates: Bool
|
||||
let onDatesChanged: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
|
||||
StepHeader(
|
||||
title: "When would you like to travel?",
|
||||
subtitle: "Pick your trip dates"
|
||||
)
|
||||
|
||||
VStack(spacing: Theme.Spacing.md) {
|
||||
DatePicker(
|
||||
"Start Date",
|
||||
selection: $startDate,
|
||||
in: Date()...,
|
||||
displayedComponents: .date
|
||||
)
|
||||
.datePickerStyle(.compact)
|
||||
.onChange(of: startDate) { _, newValue in
|
||||
// Ensure end date is after start date
|
||||
if endDate < newValue {
|
||||
endDate = newValue.addingTimeInterval(86400)
|
||||
}
|
||||
hasSetDates = true
|
||||
onDatesChanged()
|
||||
}
|
||||
|
||||
DatePicker(
|
||||
"End Date",
|
||||
selection: $endDate,
|
||||
in: startDate...,
|
||||
displayedComponents: .date
|
||||
)
|
||||
.datePickerStyle(.compact)
|
||||
.onChange(of: endDate) { _, _ in
|
||||
hasSetDates = true
|
||||
onDatesChanged()
|
||||
}
|
||||
}
|
||||
.padding(Theme.Spacing.sm)
|
||||
.background(Theme.cardBackgroundElevated(colorScheme))
|
||||
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
|
||||
|
||||
// Trip duration indicator
|
||||
HStack {
|
||||
Image(systemName: "calendar.badge.clock")
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
Text(durationText)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
}
|
||||
.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 var durationText: String {
|
||||
let days = Calendar.current.dateComponents([.day], from: startDate, to: endDate).day ?? 0
|
||||
if days == 0 {
|
||||
return "Same day trip"
|
||||
} else if days == 1 {
|
||||
return "1 day trip"
|
||||
} else {
|
||||
return "\(days) day trip"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
DatesStep(
|
||||
startDate: .constant(Date()),
|
||||
endDate: .constant(Date().addingTimeInterval(86400 * 5)),
|
||||
hasSetDates: .constant(true),
|
||||
onDatesChanged: {}
|
||||
)
|
||||
.padding()
|
||||
.themedBackground()
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// MustStopsStep.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Step 7 of the trip wizard - add must-stop locations.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MustStopsStep: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Binding var mustStopLocations: [LocationInput]
|
||||
@State private var showLocationSearch = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
|
||||
StepHeader(
|
||||
title: "Any must-stop locations?",
|
||||
subtitle: "Optional - add cities you want to visit"
|
||||
)
|
||||
|
||||
if !mustStopLocations.isEmpty {
|
||||
VStack(spacing: Theme.Spacing.xs) {
|
||||
ForEach(mustStopLocations, id: \.name) { location in
|
||||
HStack {
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
|
||||
Text(location.name)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
mustStopLocations.removeAll { $0.name == location.name }
|
||||
} label: {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
.padding(Theme.Spacing.sm)
|
||||
.background(Theme.cardBackgroundElevated(colorScheme))
|
||||
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
showLocationSearch = true
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
Text(mustStopLocations.isEmpty ? "Add a location" : "Add another")
|
||||
}
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
|
||||
Text("Skip this step if you don't have specific cities in mind")
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
.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)
|
||||
}
|
||||
.sheet(isPresented: $showLocationSearch) {
|
||||
LocationSearchSheet(inputType: .mustStop) { location in
|
||||
mustStopLocations.append(location)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
MustStopsStep(mustStopLocations: .constant([]))
|
||||
.padding()
|
||||
.themedBackground()
|
||||
}
|
||||
104
SportsTime/Features/Trip/Views/Wizard/Steps/RegionsStep.swift
Normal file
104
SportsTime/Features/Trip/Views/Wizard/Steps/RegionsStep.swift
Normal file
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// RegionsStep.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Step 4 of the trip wizard - select geographic regions.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RegionsStep: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Binding var selectedRegions: Set<Region>
|
||||
|
||||
private let columns = [
|
||||
GridItem(.flexible()),
|
||||
GridItem(.flexible())
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
|
||||
StepHeader(
|
||||
title: "Where do you want to go?",
|
||||
subtitle: "Select one or more regions"
|
||||
)
|
||||
|
||||
LazyVGrid(columns: columns, spacing: Theme.Spacing.sm) {
|
||||
ForEach(Region.allCases.filter { $0 != .crossCountry }) { region in
|
||||
RegionCard(
|
||||
region: region,
|
||||
isSelected: selectedRegions.contains(region),
|
||||
onTap: { toggleRegion(region) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if !selectedRegions.isEmpty {
|
||||
HStack {
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
Text("\(selectedRegions.count) region\(selectedRegions.count == 1 ? "" : "s") selected")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
.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 toggleRegion(_ region: Region) {
|
||||
if selectedRegions.contains(region) {
|
||||
selectedRegions.remove(region)
|
||||
} else {
|
||||
selectedRegions.insert(region)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Region Card
|
||||
|
||||
private struct RegionCard: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
let region: Region
|
||||
let isSelected: Bool
|
||||
let onTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
VStack(spacing: Theme.Spacing.xs) {
|
||||
Image(systemName: region.iconName)
|
||||
.font(.title)
|
||||
.foregroundStyle(isSelected ? Theme.warmOrange : Theme.textSecondary(colorScheme))
|
||||
|
||||
Text(region.shortName)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
.foregroundStyle(isSelected ? Theme.warmOrange : Theme.textPrimary(colorScheme))
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, Theme.Spacing.md)
|
||||
.background(isSelected ? Theme.warmOrange.opacity(0.1) : Color.clear)
|
||||
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
|
||||
.stroke(isSelected ? Theme.warmOrange : Theme.textMuted(colorScheme).opacity(0.3), lineWidth: isSelected ? 2 : 1)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
RegionsStep(selectedRegions: .constant([.east, .central]))
|
||||
.padding()
|
||||
.themedBackground()
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// RepeatCitiesStep.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Step 6 of the trip wizard - allow repeat city visits.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RepeatCitiesStep: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Binding var allowRepeatCities: Bool
|
||||
@Binding var hasSetRepeatCities: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
|
||||
StepHeader(
|
||||
title: "Visit cities more than once?",
|
||||
subtitle: "Some trips work better with return visits"
|
||||
)
|
||||
|
||||
HStack(spacing: Theme.Spacing.md) {
|
||||
OptionButton(
|
||||
title: "No, unique cities only",
|
||||
icon: "arrow.right",
|
||||
isSelected: hasSetRepeatCities && !allowRepeatCities,
|
||||
onTap: {
|
||||
allowRepeatCities = false
|
||||
hasSetRepeatCities = true
|
||||
}
|
||||
)
|
||||
|
||||
OptionButton(
|
||||
title: "Yes, allow repeats",
|
||||
icon: "arrow.triangle.2.circlepath",
|
||||
isSelected: hasSetRepeatCities && allowRepeatCities,
|
||||
onTap: {
|
||||
allowRepeatCities = true
|
||||
hasSetRepeatCities = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Option Button
|
||||
|
||||
private struct OptionButton: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
let title: String
|
||||
let icon: String
|
||||
let isSelected: Bool
|
||||
let onTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
VStack(spacing: Theme.Spacing.sm) {
|
||||
Image(systemName: icon)
|
||||
.font(.title2)
|
||||
.foregroundStyle(isSelected ? Theme.warmOrange : Theme.textSecondary(colorScheme))
|
||||
|
||||
Text(title)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
.foregroundStyle(isSelected ? Theme.warmOrange : Theme.textPrimary(colorScheme))
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(Theme.Spacing.md)
|
||||
.background(isSelected ? Theme.warmOrange.opacity(0.1) : Color.clear)
|
||||
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
|
||||
.stroke(isSelected ? Theme.warmOrange : Theme.textMuted(colorScheme).opacity(0.3), lineWidth: isSelected ? 2 : 1)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
RepeatCitiesStep(
|
||||
allowRepeatCities: .constant(false),
|
||||
hasSetRepeatCities: .constant(true)
|
||||
)
|
||||
.padding()
|
||||
.themedBackground()
|
||||
}
|
||||
120
SportsTime/Features/Trip/Views/Wizard/Steps/ReviewStep.swift
Normal file
120
SportsTime/Features/Trip/Views/Wizard/Steps/ReviewStep.swift
Normal file
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// ReviewStep.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Step 8 of the trip wizard - review and submit.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ReviewStep: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
let planningMode: PlanningMode
|
||||
let selectedSports: Set<Sport>
|
||||
let startDate: Date
|
||||
let endDate: Date
|
||||
let selectedRegions: Set<Region>
|
||||
let routePreference: RoutePreference
|
||||
let allowRepeatCities: Bool
|
||||
let mustStopLocations: [LocationInput]
|
||||
let isPlanning: Bool
|
||||
let onPlan: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
|
||||
StepHeader(
|
||||
title: "Ready to plan your trip!",
|
||||
subtitle: "Review your selections"
|
||||
)
|
||||
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
ReviewRow(label: "Mode", value: planningMode.displayName)
|
||||
ReviewRow(label: "Sports", value: selectedSports.map(\.rawValue).sorted().joined(separator: ", "))
|
||||
ReviewRow(label: "Dates", value: dateRangeText)
|
||||
ReviewRow(label: "Regions", value: selectedRegions.map(\.shortName).sorted().joined(separator: ", "))
|
||||
ReviewRow(label: "Route", value: routePreference.displayName)
|
||||
ReviewRow(label: "Repeat cities", value: allowRepeatCities ? "Yes" : "No")
|
||||
|
||||
if !mustStopLocations.isEmpty {
|
||||
ReviewRow(label: "Must-stops", value: mustStopLocations.map(\.name).joined(separator: ", "))
|
||||
}
|
||||
}
|
||||
.padding(Theme.Spacing.sm)
|
||||
.background(Theme.cardBackgroundElevated(colorScheme))
|
||||
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
|
||||
|
||||
Button(action: onPlan) {
|
||||
HStack {
|
||||
if isPlanning {
|
||||
ProgressView()
|
||||
.scaleEffect(0.8)
|
||||
.tint(.white)
|
||||
}
|
||||
Text(isPlanning ? "Planning..." : "Plan My Trip")
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(Theme.Spacing.md)
|
||||
.background(Theme.warmOrange)
|
||||
.foregroundStyle(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large))
|
||||
}
|
||||
.disabled(isPlanning)
|
||||
}
|
||||
.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 var dateRangeText: String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
return "\(formatter.string(from: startDate)) - \(formatter.string(from: endDate))"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Review Row
|
||||
|
||||
private struct ReviewRow: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
let label: String
|
||||
let value: String
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
Text(label)
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.frame(width: 80, alignment: .leading)
|
||||
|
||||
Text(value)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
ReviewStep(
|
||||
planningMode: .dateRange,
|
||||
selectedSports: [.mlb, .nba],
|
||||
startDate: Date(),
|
||||
endDate: Date().addingTimeInterval(86400 * 7),
|
||||
selectedRegions: [.east, .central],
|
||||
routePreference: .balanced,
|
||||
allowRepeatCities: false,
|
||||
mustStopLocations: [],
|
||||
isPlanning: false,
|
||||
onPlan: {}
|
||||
)
|
||||
.padding()
|
||||
.themedBackground()
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// RoutePreferenceStep.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Step 5 of the trip wizard - select route preference.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RoutePreferenceStep: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Binding var routePreference: RoutePreference
|
||||
@Binding var hasSetRoutePreference: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
|
||||
StepHeader(
|
||||
title: "What's your route preference?",
|
||||
subtitle: "Balance efficiency vs. exploration"
|
||||
)
|
||||
|
||||
VStack(spacing: Theme.Spacing.sm) {
|
||||
ForEach(RoutePreference.allCases) { preference in
|
||||
RoutePreferenceCard(
|
||||
preference: preference,
|
||||
isSelected: routePreference == preference,
|
||||
onTap: {
|
||||
routePreference = preference
|
||||
hasSetRoutePreference = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Route Preference Card
|
||||
|
||||
private struct RoutePreferenceCard: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
let preference: RoutePreference
|
||||
let isSelected: Bool
|
||||
let onTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
HStack(spacing: Theme.Spacing.md) {
|
||||
Image(systemName: preference.iconName)
|
||||
.font(.title2)
|
||||
.foregroundStyle(isSelected ? Theme.warmOrange : Theme.textSecondary(colorScheme))
|
||||
.frame(width: 32)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(preference.displayName)
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text(preference.descriptionText)
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
}
|
||||
.padding(Theme.Spacing.md)
|
||||
.background(isSelected ? Theme.warmOrange.opacity(0.1) : Color.clear)
|
||||
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
|
||||
.stroke(isSelected ? Theme.warmOrange : Theme.textMuted(colorScheme).opacity(0.3), lineWidth: isSelected ? 2 : 1)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RoutePreference Extensions
|
||||
|
||||
extension RoutePreference {
|
||||
var iconName: String {
|
||||
switch self {
|
||||
case .direct: return "bolt.fill"
|
||||
case .scenic: return "binoculars.fill"
|
||||
case .balanced: return "scale.3d"
|
||||
}
|
||||
}
|
||||
|
||||
var descriptionText: String {
|
||||
switch self {
|
||||
case .direct: return "Minimize driving time between games"
|
||||
case .scenic: return "Prioritize interesting stops and routes"
|
||||
case .balanced: return "Mix of efficiency and exploration"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
RoutePreferenceStep(
|
||||
routePreference: .constant(.balanced),
|
||||
hasSetRoutePreference: .constant(true)
|
||||
)
|
||||
.padding()
|
||||
.themedBackground()
|
||||
}
|
||||
Reference in New Issue
Block a user