Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct EventFormCompactDateField: View {
let accessibilityIdentifier: String
@State private var text: String
@State private var showsCalendar = false
@State private var pickerMonth = Date()
@FocusState private var isFocused: Bool

init(date: Binding<Date>, accessibilityIdentifier: String) {
Expand Down Expand Up @@ -47,28 +48,38 @@ struct EventFormCompactDateField: View {
showsCalendar = true
}
.popover(isPresented: $showsCalendar, arrowEdge: .bottom) {
DatePicker(
"Pick date",
selection: Binding(
get: { date },
set: { newDate in
date = newDate
text = EventFormDateFormat.formatLabel(for: newDate)
}
),
displayedComponents: [.date]
// Web `react-datepicker`: the same compact month grid as the sidebar.
SidebarMonthPicker(
displayedMonth: $pickerMonth,
selectedDate: date,
onSelectDate: { picked in
date = Self.keepingTime(of: date, onDayOf: picked)
text = EventFormDateFormat.formatLabel(for: date)
showsCalendar = false
},
today: Date(),
daysAreClickable: true
)
.datePickerStyle(.graphical)
.labelsHidden()
.frame(width: 252)
.padding(12)
.background(theme.surfacePanelColor)
.clipShape(RoundedRectangle(cornerRadius: NativeWebLayoutTokens.radiusDefault))
.shadow(color: .black.opacity(0.08), radius: 4, y: 2)
.accessibilityElement(children: .contain)
.accessibilityIdentifier("compass-event-form-date-picker")
.onAppear { pickerMonth = date }
}
}

/// Picking a day changes the calendar day only; the time of day stays.
static func keepingTime(of original: Date, onDayOf picked: Date) -> Date {
let calendar = EffectiveTimeZone.calendar
var parts = calendar.dateComponents([.year, .month, .day], from: picked)
let time = calendar.dateComponents([.hour, .minute, .second], from: original)
parts.hour = time.hour
parts.minute = time.minute
parts.second = time.second
return calendar.date(from: parts) ?? picked
}

private func commitTypedDate() {
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ struct EventFormTimeField: View {
.font(.rubik(NativeWebLayoutTokens.fontSizeBody))
.foregroundStyle(theme.textColor)
.lineLimit(1)
PhosphorIconView(icon: .caretUpDown, size: 12)
}
.padding(.horizontal, 8)
.frame(minWidth: 72, idealWidth: 76, maxWidth: 88)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ public struct SidebarMonthPicker: View {
public let focusedDate: Date?
/// The calendar's clock (`referenceNow`), so fixtures and pinned zones agree on today.
public let today: Date
/// Web sidebar days are keyboard targets only; the form's date popover
/// reuses this picker with clickable days.
public let daysAreClickable: Bool
private let trailingHeader: AnyView?

public init(
Expand All @@ -20,6 +23,7 @@ public struct SidebarMonthPicker: View {
focusedDate: Date? = nil,
visibleDayCount: Int = 1,
today: Date = Date(),
daysAreClickable: Bool = false,
trailingHeader: (() -> some View)? = nil
) {
_displayedMonth = displayedMonth
Expand All @@ -28,13 +32,32 @@ public struct SidebarMonthPicker: View {
self.visibleDayCount = visibleDayCount
self.focusedDate = focusedDate
self.today = today
self.daysAreClickable = daysAreClickable
if let trailingHeader {
self.trailingHeader = AnyView(trailingHeader())
} else {
self.trailingHeader = nil
}
}

/// A picker with no trailing header content.
public init(
displayedMonth: Binding<Date>,
selectedDate: Date,
onSelectDate: @escaping (Date) -> Void,
today: Date = Date(),
daysAreClickable: Bool = false
) {
_displayedMonth = displayedMonth
self.selectedDate = selectedDate
self.onSelectDate = onSelectDate
self.visibleDayCount = 1
self.focusedDate = nil
self.today = today
self.daysAreClickable = daysAreClickable
self.trailingHeader = nil
}

public var body: some View {
VStack(alignment: .leading, spacing: 8) {
ViewThatFits(in: .horizontal) {
Expand Down Expand Up @@ -153,7 +176,7 @@ public struct SidebarMonthPicker: View {
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.allowsHitTesting(false)
.allowsHitTesting(daysAreClickable)
.accessibilityLabel(dayAccessibilityLabel(day))
.accessibilityAddTraits(isInRange(day, from: selectedDate) ? .isSelected : [])
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import CompassKit
import XCTest
@testable import CompassUI

final class EventFormDatePickTests: XCTestCase {
private var savedZone = "UTC"

override func setUp() {
savedZone = EffectiveTimeZone.identifier
EffectiveTimeZone.identifier = "America/Denver"
}

override func tearDown() {
EffectiveTimeZone.identifier = savedZone
}

private func date(_ day: Int, hour: Int, minute: Int) -> Date {
var parts = DateComponents()
parts.year = 2026
parts.month = 6
parts.day = day
parts.hour = hour
parts.minute = minute
return EffectiveTimeZone.calendar.date(from: parts)!
}

func testPickingADayKeepsTheTimeOfDay() {
let original = date(10, hour: 15, minute: 45)
let picked = date(22, hour: 0, minute: 0)
let result = EventFormCompactDateField.keepingTime(of: original, onDayOf: picked)
XCTAssertEqual(result, date(22, hour: 15, minute: 45))
}

func testPickingTheSameDayChangesNothing() {
let original = date(10, hour: 9, minute: 15)
XCTAssertEqual(
EventFormCompactDateField.keepingTime(of: original, onDayOf: date(10, hour: 0, minute: 0)),
original)
}
}
Loading