CalendarDayCell.swift (3470B)
1 import SwiftUI 2 3 struct CalendarDayCell: View { 4 let date: Date 5 let dayNumber: Int 6 let isEnabled: Bool 7 let isToday: Bool 8 let isSelected: Bool 9 let isInLibrary: Bool 10 let onTap: () -> Void 11 12 var body: some View { 13 Button(action: onTap) { 14 Text("\(dayNumber)") 15 .font(.body) 16 .fontWeight(isToday || isSelected ? .bold : .regular) 17 // A floor rather than a fixed height: the row lets the cell 18 // stretch when there is room and compresses it towards 36 when 19 // six weeks have to fit a fixed-height sheet. The full column 20 // width keeps the tap target comfortable either way. 21 .frame(maxWidth: .infinity, minHeight: 36, maxHeight: .infinity) 22 .foregroundStyle(foregroundColor) 23 .background { 24 ZStack { 25 // A day already in the library owns the fill, so the 26 // grey circle is never displaced by selection state. 27 if isInLibrary { 28 Circle() 29 .fill(Color(.systemFill)) 30 .frame(width: 36, height: 36) 31 } else if isSelected { 32 Circle() 33 .fill(Color.accentColor) 34 .frame(width: 36, height: 36) 35 } else if isToday { 36 Circle() 37 .fill(Color.accentColor.opacity(0.15)) 38 .frame(width: 36, height: 36) 39 } 40 // With the fill taken, selection moves to a ring drawn 41 // inside the same 36pt footprint — an outset ring would 42 // sit on the cell's edge in the narrowest column. 43 if isInLibrary, isSelected || isToday { 44 Circle() 45 .strokeBorder( 46 isSelected ? Color.accentColor : Color.accentColor.opacity(0.4), 47 lineWidth: 2.5 48 ) 49 .frame(width: 36, height: 36) 50 } 51 } 52 } 53 } 54 .buttonStyle(.plain) 55 .disabled(!isEnabled) 56 .accessibilityLabel(accessibilityLabel) 57 .accessibilityValue(accessibilityValue) 58 .accessibilityHint(isEnabled ? "Selects this puzzle date" : "") 59 .accessibilityAddTraits(isSelected ? .isSelected : []) 60 } 61 62 private var foregroundColor: Color { 63 // White reads against the accent fill, but a selected library day is 64 // filled grey and keeps the normal label colour. 65 if isSelected, !isInLibrary { return .white } 66 return isEnabled ? .primary : .secondary.opacity(0.4) 67 } 68 69 private var accessibilityLabel: String { 70 date.formatted(.dateTime.weekday(.wide).month(.wide).day().year()) 71 } 72 73 private var accessibilityValue: String { 74 var parts: [String] = [] 75 if isToday { parts.append("Today") } 76 if isSelected { parts.append("Selected") } 77 if isInLibrary { parts.append("In your library") } 78 if !isEnabled { parts.append("Unavailable") } 79 return parts.joined(separator: ", ") 80 } 81 }