feat: implement Dynamic Type with Apple text styles
Replace all custom Theme.FontSize values and hardcoded font sizes with Apple's built-in text styles (.largeTitle, .title2, .headline, .body, .subheadline, .caption, .caption2) to support accessibility scaling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -144,11 +144,11 @@ struct RegionMapSelector: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(region.shortName)
|
||||
.font(.system(size: 11, weight: isSelected ? .bold : .medium))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(isSelected ? regionColor(region) : Theme.textPrimary(colorScheme))
|
||||
|
||||
Text(states)
|
||||
.font(.system(size: 9))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ struct RegionMapSelector: View {
|
||||
HStack(spacing: Theme.Spacing.md) {
|
||||
if selectedRegions.isEmpty {
|
||||
Text("Tap map to select regions")
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
} else {
|
||||
Spacer()
|
||||
@@ -168,7 +168,7 @@ struct RegionMapSelector: View {
|
||||
selectedRegions.removeAll()
|
||||
} label: {
|
||||
Text("Clear")
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,15 +187,15 @@ struct TripCreationView: View {
|
||||
private var heroHeader: some View {
|
||||
VStack(spacing: Theme.Spacing.sm) {
|
||||
Image(systemName: "map.fill")
|
||||
.font(.system(size: 40))
|
||||
.font(.largeTitle)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
|
||||
Text("Plan Your Adventure")
|
||||
.font(.system(size: Theme.FontSize.sectionTitle, weight: .bold, design: .rounded))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text("Select your games, set your route, and hit the road")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
@@ -214,7 +214,7 @@ struct TripCreationView: View {
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
Text(viewModel.planningMode.description)
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
.padding(.top, Theme.Spacing.xs)
|
||||
}
|
||||
@@ -248,7 +248,7 @@ struct TripCreationView: View {
|
||||
HStack {
|
||||
ThemedSpinnerCompact(size: 14)
|
||||
Text("Searching...")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
.padding(.top, Theme.Spacing.xs)
|
||||
@@ -281,7 +281,7 @@ struct TripCreationView: View {
|
||||
HStack {
|
||||
ThemedSpinnerCompact(size: 14)
|
||||
Text("Searching...")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
.padding(.top, Theme.Spacing.xs)
|
||||
@@ -363,16 +363,16 @@ struct TripCreationView: View {
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
.font(.system(size: 14))
|
||||
.font(.subheadline)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(result.name)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
if !result.address.isEmpty {
|
||||
Text(result.address)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -401,7 +401,7 @@ struct TripCreationView: View {
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
ThemedSpinnerCompact(size: 20)
|
||||
Text("Loading games...")
|
||||
.font(.system(size: Theme.FontSize.body))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
@@ -425,10 +425,10 @@ struct TripCreationView: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Browse Teams & Games")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Text("\(viewModel.availableGames.count) games available")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ struct TripCreationView: View {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
Text("\(viewModel.mustSeeGameIds.count) game(s) selected")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .medium))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
@@ -460,7 +460,7 @@ struct TripCreationView: View {
|
||||
viewModel.deselectAllGames()
|
||||
} label: {
|
||||
Text("Deselect All")
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
}
|
||||
@@ -470,18 +470,18 @@ struct TripCreationView: View {
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
SportColorBar(sport: game.game.sport)
|
||||
Text("\(game.awayTeam.abbreviation) @ \(game.homeTeam.abbreviation)")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Spacer()
|
||||
Text(game.game.formattedDate)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.selectedGames.count > 3 {
|
||||
Text("+ \(viewModel.selectedGames.count - 3) more")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -550,13 +550,13 @@ struct TripCreationView: View {
|
||||
}
|
||||
|
||||
Text("Select Games")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .medium))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(viewModel.selectedGamesCount) selected")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
@@ -576,7 +576,7 @@ struct TripCreationView: View {
|
||||
// Region selector
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.xs) {
|
||||
Text("Regions")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
RegionMapSelector(
|
||||
@@ -588,12 +588,12 @@ struct TripCreationView: View {
|
||||
|
||||
if viewModel.selectedRegions.isEmpty {
|
||||
Text("Select at least one region")
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
.padding(.top, Theme.Spacing.xxs)
|
||||
} else {
|
||||
Text("Games will be found in selected regions")
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.padding(.top, Theme.Spacing.xxs)
|
||||
}
|
||||
@@ -602,7 +602,7 @@ struct TripCreationView: View {
|
||||
// Route preference
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.xs) {
|
||||
Text("Route Preference")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
Picker("Route Preference", selection: $viewModel.routePreference) {
|
||||
@@ -622,7 +622,7 @@ struct TripCreationView: View {
|
||||
|
||||
if !viewModel.allowRepeatCities {
|
||||
Text("Each city will only be visited on one day")
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.padding(.leading, 32)
|
||||
}
|
||||
@@ -640,11 +640,11 @@ struct TripCreationView: View {
|
||||
Image(systemName: "mappin.circle")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
Text("Must-Stop Locations")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .medium))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Spacer()
|
||||
Text("\(viewModel.mustStopLocations.count)")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -652,11 +652,11 @@ struct TripCreationView: View {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(location.name)
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
if let address = location.address, !address.isEmpty {
|
||||
Text(address)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -681,7 +681,7 @@ struct TripCreationView: View {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
Text("Add Location")
|
||||
}
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
}
|
||||
@@ -748,7 +748,7 @@ struct TripCreationView: View {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundStyle(.orange)
|
||||
Text(message)
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
.padding(Theme.Spacing.md)
|
||||
@@ -861,7 +861,7 @@ struct GamePickerSheet: View {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
Text("\(selectedGamesCount) game(s) selected")
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.font(.subheadline)
|
||||
Spacer()
|
||||
}
|
||||
.padding(Theme.Spacing.md)
|
||||
@@ -934,23 +934,23 @@ struct SportSection: View {
|
||||
} label: {
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
Image(systemName: sport.iconName)
|
||||
.font(.system(size: 20))
|
||||
.font(.title3)
|
||||
.foregroundStyle(sport.themeColor)
|
||||
.frame(width: 32)
|
||||
|
||||
Text(sport.rawValue)
|
||||
.font(.system(size: 17, weight: .bold))
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text("\(teams.flatMap { $0.games }.count) games")
|
||||
.font(.system(size: 13))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
|
||||
Spacer()
|
||||
|
||||
if selectedCount > 0 {
|
||||
Text("\(selectedCount)")
|
||||
.font(.system(size: 12, weight: .bold))
|
||||
.font(.caption)
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
@@ -1035,18 +1035,18 @@ struct TeamSection: View {
|
||||
}
|
||||
|
||||
Text("\(teamData.team.city) \(teamData.team.name)")
|
||||
.font(.system(size: 15, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text("\(teamData.games.count)")
|
||||
.font(.system(size: 12))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
|
||||
Spacer()
|
||||
|
||||
if selectedCount > 0 {
|
||||
Text("\(selectedCount)")
|
||||
.font(.system(size: 11, weight: .bold))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 3)
|
||||
@@ -1055,7 +1055,7 @@ struct TeamSection: View {
|
||||
}
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.rotationEffect(.degrees(isExpanded ? 90 : 0))
|
||||
}
|
||||
@@ -1072,7 +1072,7 @@ struct TeamSection: View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
// Date header
|
||||
Text(dateGroup.date)
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
.padding(.horizontal, Theme.Spacing.md)
|
||||
.padding(.top, Theme.Spacing.sm)
|
||||
@@ -1116,24 +1116,24 @@ struct GameCalendarRow: View {
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
// Selection indicator
|
||||
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
|
||||
.font(.system(size: 22))
|
||||
.font(.title3)
|
||||
.foregroundStyle(isSelected ? Theme.warmOrange : Theme.textMuted(colorScheme))
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("vs \(game.awayTeam.name)")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
HStack(spacing: Theme.Spacing.xs) {
|
||||
Text(game.game.gameTime)
|
||||
.font(.system(size: 12))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
Text("•")
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
|
||||
Text(game.stadium.name)
|
||||
.font(.system(size: 12))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.lineLimit(1)
|
||||
}
|
||||
@@ -1424,11 +1424,11 @@ struct TripOptionsView: View {
|
||||
// Hero header
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "point.topright.arrow.triangle.backward.to.point.bottomleft.scurvepath.fill")
|
||||
.font(.system(size: 40))
|
||||
.font(.largeTitle)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
|
||||
Text("\(filteredAndSortedOptions.count) of \(options.count) Routes")
|
||||
.font(.system(size: Theme.FontSize.sectionTitle, weight: .bold, design: .rounded))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
.padding(.top, Theme.Spacing.lg)
|
||||
@@ -1484,11 +1484,11 @@ struct TripOptionsView: View {
|
||||
} label: {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: sortOption.icon)
|
||||
.font(.system(size: 14))
|
||||
.font(.subheadline)
|
||||
Text(sortOption.rawValue)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.font(.subheadline)
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 12))
|
||||
.font(.caption)
|
||||
}
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.padding(.horizontal, 16)
|
||||
@@ -1535,11 +1535,11 @@ struct TripOptionsView: View {
|
||||
} label: {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: paceFilter.icon)
|
||||
.font(.system(size: 12))
|
||||
.font(.caption)
|
||||
Text(paceFilter.rawValue)
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
.font(.subheadline)
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 10))
|
||||
.font(.caption2)
|
||||
}
|
||||
.foregroundStyle(paceFilter == .all ? Theme.textPrimary(colorScheme) : Theme.warmOrange)
|
||||
.padding(.horizontal, 12)
|
||||
@@ -1556,7 +1556,7 @@ struct TripOptionsView: View {
|
||||
private var citiesPicker: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.xs) {
|
||||
Label("Max Cities", systemImage: "mappin.circle")
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
@@ -1593,7 +1593,7 @@ struct TripOptionsView: View {
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
|
||||
Text("No routes match your filters")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .medium))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
Button {
|
||||
@@ -1603,7 +1603,7 @@ struct TripOptionsView: View {
|
||||
}
|
||||
} label: {
|
||||
Text("Reset Filters")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
}
|
||||
@@ -1657,19 +1657,19 @@ struct TripOptionCard: View {
|
||||
// Vertical route display
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(uniqueCities.first ?? "")
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text("|")
|
||||
.font(.system(size: 10))
|
||||
.font(.caption2)
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 8, weight: .bold))
|
||||
.font(.caption2)
|
||||
}
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
|
||||
Text(uniqueCities.last ?? "")
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -1680,7 +1680,7 @@ struct TripOptionCard: View {
|
||||
Label("\(Int(option.totalDistanceMiles)) mi", systemImage: "car")
|
||||
}
|
||||
}
|
||||
.font(.system(size: 12))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
// Bottom row: sports with game counts
|
||||
@@ -1688,9 +1688,9 @@ struct TripOptionCard: View {
|
||||
ForEach(gamesPerSport, id: \.sport) { item in
|
||||
HStack(spacing: 3) {
|
||||
Image(systemName: item.sport.iconName)
|
||||
.font(.system(size: 9))
|
||||
.font(.caption2)
|
||||
Text("\(item.sport.rawValue.uppercased()) \(item.count)")
|
||||
.font(.system(size: 9, weight: .semibold))
|
||||
.font(.caption2)
|
||||
}
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 3)
|
||||
@@ -1711,7 +1711,7 @@ struct TripOptionCard: View {
|
||||
HStack(spacing: 4) {
|
||||
ThemedSpinnerCompact(size: 12)
|
||||
Text("Generating...")
|
||||
.font(.system(size: 11))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -1777,7 +1777,7 @@ struct ThemedSection<Content: View>: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
Text(title)
|
||||
.font(.system(size: Theme.FontSize.sectionTitle, weight: .bold, design: .rounded))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
|
||||
@@ -1804,7 +1804,7 @@ struct ThemedTextField: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.xs) {
|
||||
Text(label)
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
@@ -1813,7 +1813,7 @@ struct ThemedTextField: View {
|
||||
.frame(width: 24)
|
||||
|
||||
TextField(placeholder, text: $text)
|
||||
.font(.system(size: Theme.FontSize.body))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
.padding(Theme.Spacing.md)
|
||||
@@ -1836,7 +1836,7 @@ struct ThemedToggle: View {
|
||||
.frame(width: 24)
|
||||
|
||||
Text(label)
|
||||
.font(.system(size: Theme.FontSize.body))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
@@ -1859,7 +1859,7 @@ struct ThemedStepper: View {
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
.font(.system(size: Theme.FontSize.body))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
@@ -1877,7 +1877,7 @@ struct ThemedStepper: View {
|
||||
.disabled(value <= range.lowerBound)
|
||||
|
||||
Text("\(value)")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .bold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.frame(minWidth: 30)
|
||||
|
||||
@@ -1907,7 +1907,7 @@ struct ThemedDatePicker: View {
|
||||
Image(systemName: "calendar")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
Text(label)
|
||||
.font(.system(size: Theme.FontSize.body))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -2004,26 +2004,26 @@ struct DateRangePicker: View {
|
||||
// Start date
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("START")
|
||||
.font(.system(size: 10, weight: .semibold))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
Text(startDate.formatted(.dateTime.month(.abbreviated).day().year()))
|
||||
.font(.system(size: Theme.FontSize.body, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
// Arrow
|
||||
Image(systemName: "arrow.right")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
|
||||
// End date
|
||||
VStack(alignment: .trailing, spacing: 4) {
|
||||
Text("END")
|
||||
.font(.system(size: 10, weight: .semibold))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
Text(endDate.formatted(.dateTime.month(.abbreviated).day().year()))
|
||||
.font(.system(size: Theme.FontSize.body, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
@@ -2041,7 +2041,7 @@ struct DateRangePicker: View {
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
.frame(width: 36, height: 36)
|
||||
.background(Theme.warmOrange.opacity(0.15))
|
||||
@@ -2051,7 +2051,7 @@ struct DateRangePicker: View {
|
||||
Spacer()
|
||||
|
||||
Text(monthYearString)
|
||||
.font(.system(size: Theme.FontSize.cardTitle, weight: .bold, design: .rounded))
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
@@ -2062,7 +2062,7 @@ struct DateRangePicker: View {
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
.frame(width: 36, height: 36)
|
||||
.background(Theme.warmOrange.opacity(0.15))
|
||||
@@ -2075,7 +2075,7 @@ struct DateRangePicker: View {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(Array(daysOfWeek.enumerated()), id: \.offset) { _, day in
|
||||
Text(day)
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
@@ -2109,7 +2109,7 @@ struct DateRangePicker: View {
|
||||
Image(systemName: "calendar.badge.clock")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
Text("\(tripDuration) day\(tripDuration == 1 ? "" : "s")")
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
@@ -2247,7 +2247,7 @@ struct SportSelectionChip: View {
|
||||
}
|
||||
|
||||
Image(systemName: sport.iconName)
|
||||
.font(.system(size: 20))
|
||||
.font(.title3)
|
||||
.foregroundStyle(isSelected ? .white : sport.themeColor)
|
||||
}
|
||||
|
||||
|
||||
@@ -124,23 +124,23 @@ struct TripDetailView: View {
|
||||
.animation(.easeInOut(duration: 0.3), value: exportProgress?.percentComplete)
|
||||
|
||||
Image(systemName: "doc.fill")
|
||||
.font(.system(size: 24))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
|
||||
VStack(spacing: Theme.Spacing.xs) {
|
||||
Text("Creating PDF")
|
||||
.font(.system(size: Theme.FontSize.cardTitle, weight: .semibold))
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text(exportProgress?.currentStep ?? "Preparing...")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if let progress = exportProgress {
|
||||
Text("\(Int(progress.percentComplete * 100))%")
|
||||
.font(.system(size: Theme.FontSize.micro, weight: .medium, design: .monospaced))
|
||||
.font(.caption.monospaced())
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ struct TripDetailView: View {
|
||||
toggleSaved()
|
||||
} label: {
|
||||
Image(systemName: isSaved ? "heart.fill" : "heart")
|
||||
.font(.system(size: 22, weight: .medium))
|
||||
.font(.title3)
|
||||
.foregroundStyle(isSaved ? .red : .white)
|
||||
.padding(12)
|
||||
.background(.ultraThinMaterial)
|
||||
@@ -214,7 +214,7 @@ struct TripDetailView: View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
// Date range
|
||||
Text(trip.formattedDateRange)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
// Route preview
|
||||
@@ -226,9 +226,9 @@ struct TripDetailView: View {
|
||||
ForEach(Array(trip.uniqueSports), id: \.self) { sport in
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: sport.iconName)
|
||||
.font(.system(size: 10))
|
||||
.font(.caption2)
|
||||
Text(sport.rawValue)
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.font(.caption2)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 5)
|
||||
@@ -261,11 +261,11 @@ struct TripDetailView: View {
|
||||
VStack(spacing: Theme.Spacing.md) {
|
||||
HStack {
|
||||
Text("Trip Score")
|
||||
.font(.system(size: Theme.FontSize.cardTitle, weight: .semibold))
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Spacer()
|
||||
Text(score.scoreGrade)
|
||||
.font(.system(size: 32, weight: .bold, design: .rounded))
|
||||
.font(.largeTitle)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
.glowEffect(color: Theme.warmOrange, radius: 8)
|
||||
}
|
||||
@@ -283,10 +283,10 @@ struct TripDetailView: View {
|
||||
private func scoreItem(label: String, value: Double, color: Color) -> some View {
|
||||
VStack(spacing: 4) {
|
||||
Text(String(format: "%.0f", value))
|
||||
.font(.system(size: Theme.FontSize.cardTitle, weight: .bold))
|
||||
.font(.headline)
|
||||
.foregroundStyle(color)
|
||||
Text(label)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ struct TripDetailView: View {
|
||||
private var itinerarySection: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
|
||||
Text("Itinerary")
|
||||
.font(.system(size: Theme.FontSize.sectionTitle, weight: .bold, design: .rounded))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
ForEach(Array(itinerarySections.enumerated()), id: \.offset) { index, section in
|
||||
@@ -604,11 +604,11 @@ struct DaySection: View {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Day \(dayNumber)")
|
||||
.font(.system(size: Theme.FontSize.sectionTitle, weight: .bold, design: .rounded))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text(formattedDate)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -626,7 +626,7 @@ struct DaySection: View {
|
||||
// City label
|
||||
if let city = gameCity {
|
||||
Label(city, systemImage: "mappin")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -656,20 +656,20 @@ struct GameRow: View {
|
||||
// Sport icon and name
|
||||
HStack(spacing: 3) {
|
||||
Image(systemName: game.game.sport.iconName)
|
||||
.font(.system(size: 10))
|
||||
.font(.caption2)
|
||||
Text(game.game.sport.rawValue)
|
||||
.font(.system(size: 10, weight: .medium))
|
||||
.font(.caption2)
|
||||
}
|
||||
.foregroundStyle(game.game.sport.themeColor)
|
||||
|
||||
// Matchup
|
||||
HStack(spacing: 4) {
|
||||
Text(game.awayTeam.abbreviation)
|
||||
.font(.system(size: Theme.FontSize.body, weight: .bold))
|
||||
.font(.body)
|
||||
Text("@")
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
Text(game.homeTeam.abbreviation)
|
||||
.font(.system(size: Theme.FontSize.body, weight: .bold))
|
||||
.font(.body)
|
||||
}
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
@@ -677,9 +677,9 @@ struct GameRow: View {
|
||||
// Stadium
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "building.2")
|
||||
.font(.system(size: 10))
|
||||
.font(.caption2)
|
||||
Text(game.stadium.name)
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
}
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
@@ -688,7 +688,7 @@ struct GameRow: View {
|
||||
|
||||
// Time
|
||||
Text(game.game.gameTime)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .semibold))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
.padding(Theme.Spacing.sm)
|
||||
@@ -731,11 +731,11 @@ struct TravelSection: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Travel")
|
||||
.font(.system(size: Theme.FontSize.micro, weight: .semibold))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
|
||||
Text("\(segment.fromLocation.name) → \(segment.toLocation.name)")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .medium))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -743,10 +743,10 @@ struct TravelSection: View {
|
||||
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
Text(segment.formattedDistance)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .semibold))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Text(segment.formattedDuration)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -765,16 +765,16 @@ struct TravelSection: View {
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.foregroundStyle(.green)
|
||||
.font(.system(size: 12))
|
||||
.font(.caption)
|
||||
|
||||
Text("\(segment.evChargingStops.count) EV Charger\(segment.evChargingStops.count > 1 ? "s" : "") Along Route")
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: showEVChargers ? "chevron.up" : "chevron.down")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
.padding(.horizontal, Theme.Spacing.md)
|
||||
@@ -834,7 +834,7 @@ struct EVChargerRow: View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack(spacing: Theme.Spacing.xs) {
|
||||
Text(charger.name)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.lineLimit(1)
|
||||
|
||||
@@ -844,7 +844,7 @@ struct EVChargerRow: View {
|
||||
HStack(spacing: Theme.Spacing.xs) {
|
||||
if let address = charger.location.address {
|
||||
Text(address)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -852,7 +852,7 @@ struct EVChargerRow: View {
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
|
||||
Text("~\(charger.formattedChargeTime) charge")
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -866,7 +866,7 @@ struct EVChargerRow: View {
|
||||
private var chargerTypeBadge: some View {
|
||||
let (text, color) = chargerTypeInfo
|
||||
Text(text)
|
||||
.font(.system(size: 9, weight: .semibold))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(color)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 2)
|
||||
|
||||
Reference in New Issue
Block a user