feat: add WCAG AA accessibility app-wide, fix CloudKit container config, remove debug logs

- Add VoiceOver labels, hints, and element grouping across all 60+ views
- Add Reduce Motion support (Theme.Animation.prefersReducedMotion) to all animations
- Replace fixed font sizes with semantic Dynamic Type styles
- Hide decorative elements from VoiceOver with .accessibilityHidden(true)
- Add .minimumHitTarget() modifier ensuring 44pt touch targets
- Add AccessibilityAnnouncer utility for VoiceOver announcements
- Improve color contrast values in Theme.swift for WCAG AA compliance
- Extract CloudKitContainerConfig for explicit container identity
- Remove PostHog debug console log from AnalyticsManager

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-11 09:27:23 -06:00
parent e9c15d70b1
commit d63d311cab
77 changed files with 982 additions and 263 deletions

View File

@@ -25,6 +25,7 @@ struct DateRangePicker: View {
private let calendar = Calendar.current
private let daysOfWeek = ["S", "M", "T", "W", "T", "F", "S"]
private let daysOfWeekFull = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
private var monthYearString: String {
let formatter = DateFormatter()
@@ -96,13 +97,13 @@ struct DateRangePicker: View {
if isDemoMode && !hasAppliedDemoSelection {
hasAppliedDemoSelection = true
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay) {
withAnimation(.easeInOut(duration: 0.3)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
// Navigate to demo month
displayedMonth = DemoConfig.demoStartDate
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay + 0.5) {
withAnimation(.easeInOut(duration: 0.3)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
startDate = DemoConfig.demoStartDate
endDate = DemoConfig.demoEndDate
selectionState = .complete
@@ -119,7 +120,7 @@ struct DateRangePicker: View {
let newYear = calendar.component(.year, from: newValue)
if oldMonth != newMonth || oldYear != newYear {
withAnimation(.easeInOut(duration: 0.2)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
displayedMonth = calendar.startOfDay(for: newValue)
}
}
@@ -148,6 +149,7 @@ struct DateRangePicker: View {
Image(systemName: "arrow.right")
.font(.subheadline)
.foregroundStyle(Theme.textMuted(colorScheme))
.accessibilityHidden(true)
// End date
VStack(alignment: .trailing, spacing: 4) {
@@ -168,17 +170,18 @@ struct DateRangePicker: View {
private var monthNavigation: some View {
HStack {
Button {
withAnimation(.easeInOut(duration: 0.2)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
displayedMonth = calendar.date(byAdding: .month, value: -1, to: displayedMonth) ?? displayedMonth
}
} label: {
Image(systemName: "chevron.left")
.font(.body)
.foregroundStyle(Theme.warmOrange)
.frame(width: 36, height: 36)
.frame(minWidth: 44, minHeight: 44)
.background(Theme.warmOrange.opacity(0.15))
.clipShape(Circle())
}
.accessibilityLabel("Previous month")
.accessibilityIdentifier("wizard.dates.previousMonth")
Spacer()
@@ -191,28 +194,30 @@ struct DateRangePicker: View {
Spacer()
Button {
withAnimation(.easeInOut(duration: 0.2)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
displayedMonth = calendar.date(byAdding: .month, value: 1, to: displayedMonth) ?? displayedMonth
}
} label: {
Image(systemName: "chevron.right")
.font(.body)
.foregroundStyle(Theme.warmOrange)
.frame(width: 36, height: 36)
.frame(minWidth: 44, minHeight: 44)
.background(Theme.warmOrange.opacity(0.15))
.clipShape(Circle())
}
.accessibilityLabel("Next month")
.accessibilityIdentifier("wizard.dates.nextMonth")
}
}
private var daysOfWeekHeader: some View {
HStack(spacing: 0) {
ForEach(Array(daysOfWeek.enumerated()), id: \.offset) { _, day in
ForEach(Array(daysOfWeek.enumerated()), id: \.offset) { index, day in
Text(day)
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
.frame(maxWidth: .infinity)
.accessibilityLabel(daysOfWeekFull[index])
}
}
}
@@ -243,6 +248,7 @@ struct DateRangePicker: View {
HStack(spacing: Theme.Spacing.xs) {
Image(systemName: "calendar.badge.clock")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
Text("\(tripDuration) day\(tripDuration == 1 ? "" : "s")")
.font(.subheadline)
.foregroundStyle(Theme.textSecondary(colorScheme))
@@ -348,7 +354,7 @@ struct DayCell: View {
}
Text(dayNumber)
.font(.system(size: 14, weight: (isStart || isEnd) ? .bold : .medium))
.font(.subheadline)
.foregroundStyle(
isPast ? Theme.textMuted(colorScheme).opacity(0.5) :
(isStart || isEnd) ? .white :

View File

@@ -123,26 +123,53 @@ struct GamePickerStep: View {
.fontWeight(.medium)
.foregroundStyle(Theme.textSecondary(colorScheme))
Button {
if isEnabled { onTap() }
} label: {
HStack {
Image(systemName: icon)
.foregroundStyle(isEnabled ? Theme.warmOrange : Theme.textMuted(colorScheme))
if let value = value {
HStack(spacing: Theme.Spacing.sm) {
Button {
if isEnabled { onTap() }
} label: {
HStack {
Image(systemName: icon)
.foregroundStyle(isEnabled ? Theme.warmOrange : Theme.textMuted(colorScheme))
.accessibilityHidden(true)
if let value = value {
Text(value)
.font(.subheadline)
.foregroundStyle(Theme.textPrimary(colorScheme))
.lineLimit(1)
Text(value)
.font(.subheadline)
.foregroundStyle(Theme.textPrimary(colorScheme))
.lineLimit(1)
Spacer()
Button(action: onClear) {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(Theme.textMuted(colorScheme))
Spacer()
}
} else {
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.disabled(!isEnabled)
Button(action: onClear) {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(Theme.textMuted(colorScheme))
}
.buttonStyle(.plain)
.minimumHitTarget()
.accessibilityLabel("Clear \(label.lowercased()) selection")
}
.padding(Theme.Spacing.md)
.background(Theme.cardBackgroundElevated(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(Theme.warmOrange, lineWidth: 2)
)
.opacity(isEnabled ? 1 : 0.5)
} else {
Button {
if isEnabled { onTap() }
} label: {
HStack {
Image(systemName: icon)
.foregroundStyle(isEnabled ? Theme.warmOrange : Theme.textMuted(colorScheme))
.accessibilityHidden(true)
Text(placeholder)
.font(.subheadline)
.foregroundStyle(Theme.textMuted(colorScheme))
@@ -152,19 +179,20 @@ struct GamePickerStep: View {
Image(systemName: "chevron.right")
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
.accessibilityHidden(true)
}
.padding(Theme.Spacing.md)
.background(Theme.cardBackgroundElevated(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(Theme.textMuted(colorScheme).opacity(0.3), lineWidth: 1)
)
.opacity(isEnabled ? 1 : 0.5)
}
.padding(Theme.Spacing.md)
.background(Theme.cardBackgroundElevated(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(value != nil ? Theme.warmOrange : Theme.textMuted(colorScheme).opacity(0.3), lineWidth: value != nil ? 2 : 1)
)
.opacity(isEnabled ? 1 : 0.5)
.buttonStyle(.plain)
.disabled(!isEnabled)
}
.buttonStyle(.plain)
.disabled(!isEnabled)
}
}
@@ -177,6 +205,7 @@ struct GamePickerStep: View {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
Text("\(selectedGameIds.count) game\(selectedGameIds.count == 1 ? "" : "s") selected")
.font(.subheadline)
.fontWeight(.medium)
@@ -201,6 +230,8 @@ struct GamePickerStep: View {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(Theme.textMuted(colorScheme))
}
.minimumHitTarget()
.accessibilityLabel("Remove \(game.matchupDescription)")
}
.padding(Theme.Spacing.sm)
.background(Theme.cardBackgroundElevated(colorScheme))
@@ -236,6 +267,7 @@ struct GamePickerStep: View {
HStack {
Image(systemName: "calendar")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
Text("Trip Date Range")
.font(.subheadline)
.fontWeight(.medium)
@@ -353,15 +385,18 @@ private struct SportsPickerSheet: View {
if selectedSports.contains(sport) {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
} else {
Image(systemName: "circle")
.foregroundStyle(Theme.textMuted(colorScheme))
.accessibilityHidden(true)
}
}
.padding(.vertical, Theme.Spacing.xs)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityAddTraits(selectedSports.contains(sport) ? .isSelected : [])
}
}
.listStyle(.plain)
@@ -451,15 +486,18 @@ private struct TeamsPickerSheet: View {
if selectedTeamIds.contains(team.id) {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
} else {
Image(systemName: "circle")
.foregroundStyle(Theme.textMuted(colorScheme))
.accessibilityHidden(true)
}
}
.padding(.vertical, Theme.Spacing.xs)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityAddTraits(selectedTeamIds.contains(team.id) ? .isSelected : [])
}
} header: {
HStack(spacing: Theme.Spacing.xs) {
@@ -555,15 +593,19 @@ private struct GamesPickerSheet: View {
if selectedGameIds.contains(game.id) {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
} else {
Image(systemName: "circle")
.foregroundStyle(Theme.textMuted(colorScheme))
.accessibilityHidden(true)
}
}
.padding(.vertical, Theme.Spacing.xs)
.contentShape(Rectangle())
.accessibilityElement(children: .combine)
}
.buttonStyle(.plain)
.accessibilityAddTraits(selectedGameIds.contains(game.id) ? .isSelected : [])
}
} header: {
Text(date, style: .date)

View File

@@ -48,6 +48,7 @@ struct LocationSearchSheet: View {
HStack {
Image(systemName: "magnifyingglass")
.foregroundStyle(.secondary)
.accessibilityHidden(true)
TextField("Search cities, addresses, places...", text: $searchText)
.textFieldStyle(.plain)
.autocorrectionDisabled()
@@ -61,6 +62,8 @@ struct LocationSearchSheet: View {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.secondary)
}
.minimumHitTarget()
.accessibilityLabel("Clear search")
}
}
.padding()
@@ -85,6 +88,7 @@ struct LocationSearchSheet: View {
Image(systemName: "mappin.circle.fill")
.foregroundStyle(.red)
.font(.title2)
.accessibilityHidden(true)
VStack(alignment: .leading) {
Text(result.name)
.foregroundStyle(.primary)
@@ -97,6 +101,7 @@ struct LocationSearchSheet: View {
Spacer()
Image(systemName: "plus.circle")
.foregroundStyle(.blue)
.accessibilityHidden(true)
}
}
.buttonStyle(.plain)

View File

@@ -49,6 +49,7 @@ struct LocationsStep: View {
HStack(spacing: Theme.Spacing.sm) {
Image(systemName: "arrow.triangle.2.circlepath")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
Text("Round trip (return to start)")
.font(.subheadline)
.foregroundStyle(Theme.textPrimary(colorScheme))
@@ -107,6 +108,7 @@ struct LocationsStep: View {
HStack {
Image(systemName: "mappin.circle.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 2) {
Text(location.name)
@@ -128,6 +130,8 @@ struct LocationsStep: View {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(Theme.textMuted(colorScheme))
}
.minimumHitTarget()
.accessibilityLabel("Clear location")
}
.padding(Theme.Spacing.sm)
.background(Theme.cardBackgroundElevated(colorScheme))
@@ -138,6 +142,7 @@ struct LocationsStep: View {
HStack {
Image(systemName: "plus.circle")
.foregroundStyle(Theme.warmOrange)
.accessibilityLabel("Add location")
Text(placeholder)
.font(.subheadline)
@@ -148,6 +153,7 @@ struct LocationsStep: View {
Image(systemName: "chevron.right")
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
.accessibilityHidden(true)
}
.padding(Theme.Spacing.sm)
.background(Theme.cardBackgroundElevated(colorScheme))

View File

@@ -25,6 +25,7 @@ struct MustStopsStep: View {
HStack {
Image(systemName: "mappin.circle.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
Text(location.name)
.font(.subheadline)
@@ -38,6 +39,8 @@ struct MustStopsStep: View {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(Theme.textMuted(colorScheme))
}
.minimumHitTarget()
.accessibilityLabel("Remove location")
}
.padding(Theme.Spacing.sm)
.background(Theme.cardBackgroundElevated(colorScheme))
@@ -56,6 +59,7 @@ struct MustStopsStep: View {
.font(.subheadline)
.foregroundStyle(Theme.warmOrange)
}
.accessibilityLabel("Add must-see location")
Text("Skip this step if you don't have specific cities in mind")
.font(.caption)

View File

@@ -39,7 +39,7 @@ struct PlanningModeStep: View {
.onAppear {
if isDemoMode && selection == nil {
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay) {
withAnimation(.easeInOut(duration: 0.3)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
selection = DemoConfig.demoPlanningMode
}
}
@@ -63,6 +63,7 @@ private struct WizardModeCard: View {
.font(.title2)
.foregroundStyle(isSelected ? Theme.warmOrange : Theme.textSecondary(colorScheme))
.frame(width: 32)
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 2) {
Text(mode.displayName)
@@ -79,6 +80,7 @@ private struct WizardModeCard: View {
if isSelected {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
}
}
.padding(Theme.Spacing.md)
@@ -89,7 +91,11 @@ private struct WizardModeCard: View {
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(isSelected ? Theme.warmOrange : Theme.textMuted(colorScheme).opacity(0.3), lineWidth: isSelected ? 2 : 1)
)
.accessibilityElement(children: .combine)
}
.accessibilityLabel("\(mode.displayName): \(mode.description)")
.accessibilityValue(isSelected ? "Selected" : "Not selected")
.accessibilityAddTraits(isSelected ? .isSelected : [])
.accessibilityIdentifier("wizard.planningMode.\(mode.rawValue)")
.buttonStyle(.plain)
}

View File

@@ -72,6 +72,7 @@ private struct OptionButton: View {
Image(systemName: icon)
.font(.title2)
.foregroundStyle(isSelected ? Theme.warmOrange : Theme.textSecondary(colorScheme))
.accessibilityHidden(true)
Text(title)
.font(.caption)
@@ -90,6 +91,8 @@ private struct OptionButton: View {
)
}
.buttonStyle(.plain)
.accessibilityValue(isSelected ? "Selected" : "Not selected")
.accessibilityAddTraits(isSelected ? .isSelected : [])
}
}

View File

@@ -63,6 +63,7 @@ struct ReviewStep: View {
HStack(spacing: Theme.Spacing.xs) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(.orange)
.accessibilityHidden(true)
Text("Complete all required fields to continue")
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
@@ -85,6 +86,7 @@ struct ReviewStep: View {
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large))
}
.accessibilityIdentifier("wizard.planTripButton")
.accessibilityHint("Creates trip itinerary based on your selections")
.disabled(!canPlanTrip || isPlanning)
}
.padding(Theme.Spacing.lg)
@@ -155,6 +157,7 @@ private struct ReviewRow: View {
Image(systemName: "exclamationmark.circle.fill")
.font(.caption)
.foregroundStyle(.red)
.accessibilityHidden(true)
}
}
}

View File

@@ -63,6 +63,7 @@ private struct RoutePreferenceCard: View {
.font(.title2)
.foregroundStyle(isSelected ? Theme.warmOrange : Theme.textSecondary(colorScheme))
.frame(width: 32)
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 2) {
Text(preference.displayName)
@@ -79,6 +80,7 @@ private struct RoutePreferenceCard: View {
if isSelected {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
}
}
.padding(Theme.Spacing.md)
@@ -89,7 +91,11 @@ private struct RoutePreferenceCard: View {
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(isSelected ? Theme.warmOrange : Theme.textMuted(colorScheme).opacity(0.3), lineWidth: isSelected ? 2 : 1)
)
.accessibilityElement(children: .combine)
}
.accessibilityLabel(preference.displayName)
.accessibilityValue(isSelected ? "Selected" : "Not selected")
.accessibilityAddTraits(isSelected ? .isSelected : [])
.buttonStyle(.plain)
}
}

View File

@@ -60,7 +60,7 @@ struct SportsStep: View {
if isDemoMode && !hasAppliedDemoSelection && selectedSports.isEmpty {
hasAppliedDemoSelection = true
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay) {
withAnimation(.easeInOut(duration: 0.3)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
_ = selectedSports.insert(DemoConfig.demoSport)
}
}
@@ -92,6 +92,7 @@ private struct SportCard: View {
Image(systemName: sport.iconName)
.font(.title2)
.foregroundStyle(cardColor)
.accessibilityHidden(true)
Text(sport.rawValue)
.font(.caption)
@@ -111,7 +112,15 @@ private struct SportCard: View {
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(borderColor, lineWidth: isSelected ? 2 : 1)
)
.accessibilityElement(children: .combine)
}
.accessibilityLabel(sport.rawValue)
.accessibilityValue(
isAvailable
? (isSelected ? "Selected" : "Not selected")
: "Unavailable"
)
.accessibilityAddTraits(isSelected ? .isSelected : [])
.accessibilityIdentifier("wizard.sports.\(sport.rawValue.lowercased())")
.buttonStyle(.plain)
.opacity(isAvailable ? 1.0 : 0.5)

View File

@@ -28,41 +28,60 @@ struct TeamPickerStep: View {
subtitle: "See their home and away games"
)
// Selection button
Button {
showTeamPicker = true
} label: {
HStack {
if let team = selectedTeam {
// Show selected team
Circle()
.fill(team.primaryColor.map { Color(hex: $0) } ?? team.sport.themeColor)
.frame(width: 24, height: 24)
if let team = selectedTeam {
HStack(spacing: Theme.Spacing.sm) {
Button {
showTeamPicker = true
} label: {
HStack {
Circle()
.fill(team.primaryColor.map { Color(hex: $0) } ?? team.sport.themeColor)
.frame(width: 24, height: 24)
VStack(alignment: .leading, spacing: 2) {
Text(team.fullName)
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(Theme.textPrimary(colorScheme))
VStack(alignment: .leading, spacing: 2) {
Text(team.fullName)
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(Theme.textPrimary(colorScheme))
Text(team.sport.rawValue)
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
Text(team.sport.rawValue)
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
}
Spacer()
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
Spacer()
Button {
selectedTeamId = nil
selectedSport = nil
} label: {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(Theme.textMuted(colorScheme))
}
} else {
// Empty state
Button {
selectedTeamId = nil
selectedSport = nil
} label: {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(Theme.textMuted(colorScheme))
}
.buttonStyle(.plain)
.minimumHitTarget()
.accessibilityLabel("Clear team selection")
}
.padding(Theme.Spacing.md)
.background(Theme.cardBackgroundElevated(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(Theme.warmOrange, lineWidth: 2)
)
} else {
// Selection button
Button {
showTeamPicker = true
} label: {
HStack {
Image(systemName: "person.2.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
Text("Select a team")
.font(.subheadline)
@@ -73,17 +92,18 @@ struct TeamPickerStep: View {
Image(systemName: "chevron.right")
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
.accessibilityHidden(true)
}
.padding(Theme.Spacing.md)
.background(Theme.cardBackgroundElevated(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(Theme.textMuted(colorScheme).opacity(0.3), lineWidth: 1)
)
}
.padding(Theme.Spacing.md)
.background(Theme.cardBackgroundElevated(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(selectedTeam != nil ? Theme.warmOrange : Theme.textMuted(colorScheme).opacity(0.3), lineWidth: selectedTeam != nil ? 2 : 1)
)
.buttonStyle(.plain)
}
.buttonStyle(.plain)
}
.padding(Theme.Spacing.lg)
.background(Theme.cardBackground(colorScheme))
@@ -214,11 +234,14 @@ private struct TeamListView: View {
if selectedTeamId == team.id {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
}
}
.padding(.vertical, Theme.Spacing.xs)
.accessibilityElement(children: .combine)
}
.buttonStyle(.plain)
.accessibilityAddTraits(selectedTeamId == team.id ? .isSelected : [])
}
}
.listStyle(.plain)