oficina / calendar Public

Clone
git clone https://amadan.net/oficina/calendar

Plain git — no account needed to clone.

Download

Download this file

1import SwiftUI
2
3struct CalendarMonthView: View {
4 @ObservedObject var model: CalendarModel
5 @Environment(\.controlSize) private var controlSize
6 private var compact: Bool {
7 #if os(macOS)
8 controlSize == .small
9 #else
10 false
11 #endif
12 }
13 @Environment(\.dynamicTypeSize) private var typeSize
14 private let accent = Color(red: 0.78, green: 0.22, blue: 0.29)
15 var body: some View { VStack(spacing: 0) { monthHeader; monthGrid } }
16 private var monthHeader: some View {
17 ViewThatFits(in: .horizontal) {
18 HStack {
19 Text(model.display(model.monthStart, template: "MMMM yyyy")).font(.title2.bold())
20 Spacer()
21 monthControls
22 }
23 VStack(alignment: .leading) {
24 Text(model.display(model.monthStart, template: "MMMM yyyy")).font(.title2.bold())
25 monthControls
26 }
27 }.padding(.vertical, compact ? 4 : 16)
28 }
29 private var monthControls: some View {
30 HStack {
31 Button("Previous month", systemImage: "chevron.left") { move(-1) }.labelStyle(.iconOnly).frame(minWidth: compact ? 28 : 44, minHeight: compact ? 28 : 44)
32 Button("Today") { model.date = Date() }.frame(minHeight: compact ? 28 : 44)
33 Button("Next month", systemImage: "chevron.right") { move(1) }.labelStyle(.iconOnly).frame(minWidth: compact ? 28 : 44, minHeight: compact ? 28 : 44)
34 }.buttonStyle(.borderless)
35 }
36 private func move(_ amount: Int) { model.date = model.calendar.date(byAdding: .month, value: amount, to: model.date) ?? model.date }
37 private var monthGrid: some View {
38 Grid(horizontalSpacing: 4, verticalSpacing: 4) {
39 GridRow {
40 ForEach(Array(model.weekdayLabels.enumerated()), id: \.offset) { _, label in
41 Text(typeSize.isAccessibilitySize ? String(label.prefix(1)) : label)
42 .font(.caption).foregroundStyle(.secondary).accessibilityHidden(true)
43 }
44 }
45 ForEach(0..<6) { week in
46 GridRow {
47 ForEach(Array(model.days.dropFirst(week * 7).prefix(7)), id: \.self) { day in
48 dayButton(day)
49 }
50 }
51 }
52 }.padding(.bottom, 12)
53 }
54 private func dayButton(_ day: Date) -> some View {
55 let selected = model.calendar.isDate(day, inSameDayAs: model.date)
56 let today = model.calendar.isDate(day, inSameDayAs: Date())
57 let count = model.dayEvents(day).count
58 let current = model.calendar.isDate(day, equalTo: model.monthStart, toGranularity: .month)
59 return Button { model.date = day } label: {
60 VStack(spacing: 3) {
61 Text("\(model.calendar.component(.day, from: day))")
62 .font(.body.monospacedDigit()).bold(today).lineLimit(1).minimumScaleFactor(0.7)
63 Circle().fill(count == 0 ? .clear : selected ? .white : accent).frame(width: 4, height: 4)
64 }.frame(maxWidth: .infinity, minHeight: compact ? 28 : 44)
65 .foregroundStyle(selected ? .white : current ? Color.primary : Color.secondary)
66 .background(selected ? accent : .clear, in: RoundedRectangle(cornerRadius: 9))
67 .overlay { RoundedRectangle(cornerRadius: 9).strokeBorder(today ? accent : .clear, lineWidth: 2) }
68 }.buttonStyle(.plain)
69 .accessibilityLabel(model.display(day, template: "EEEE d MMMM yyyy"))
70 .accessibilityValue("\(today ? "Today. " : "")\(count) events")
71 .accessibilityAddTraits(selected ? .isSelected : [])
72 }
73}
74