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:
@@ -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 :
|
||||
|
||||
Reference in New Issue
Block a user