feat: add responsive task calendar picker
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
export type CalendarDay = {
|
||||
date: Date
|
||||
value: string
|
||||
inMonth: boolean
|
||||
isToday: boolean
|
||||
isSelected: boolean
|
||||
}
|
||||
|
||||
export type AnchorRect = Pick<DOMRect, 'left' | 'top' | 'bottom' | 'width' | 'height'>
|
||||
|
||||
const DAY_MS = 86_400_000
|
||||
|
||||
export function parseLocalDate(value: string): Date | null {
|
||||
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value)
|
||||
if (!match) return null
|
||||
const year = Number(match[1])
|
||||
const month = Number(match[2]) - 1
|
||||
const day = Number(match[3])
|
||||
const date = new Date(year, month, day)
|
||||
return date.getFullYear() === year && date.getMonth() === month && date.getDate() === day ? date : null
|
||||
}
|
||||
|
||||
export function formatLocalDate(date: Date): string {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
export function addCalendarDays(date: Date, amount: number): Date {
|
||||
const next = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
||||
next.setDate(next.getDate() + amount)
|
||||
return next
|
||||
}
|
||||
|
||||
function addCalendarMonths(date: Date, amount: number): Date {
|
||||
const wantedDay = date.getDate()
|
||||
const next = new Date(date.getFullYear(), date.getMonth() + amount, 1)
|
||||
const finalDay = new Date(next.getFullYear(), next.getMonth() + 1, 0).getDate()
|
||||
next.setDate(Math.min(wantedDay, finalDay))
|
||||
return next
|
||||
}
|
||||
|
||||
export function moveCalendarFocus(date: Date, key: string): Date {
|
||||
if (key === 'ArrowLeft') return addCalendarDays(date, -1)
|
||||
if (key === 'ArrowRight') return addCalendarDays(date, 1)
|
||||
if (key === 'ArrowUp') return addCalendarDays(date, -7)
|
||||
if (key === 'ArrowDown') return addCalendarDays(date, 7)
|
||||
if (key === 'PageUp') return addCalendarMonths(date, -1)
|
||||
if (key === 'PageDown') return addCalendarMonths(date, 1)
|
||||
const mondayIndex = (date.getDay() + 6) % 7
|
||||
if (key === 'Home') return addCalendarDays(date, -mondayIndex)
|
||||
if (key === 'End') return addCalendarDays(date, 6 - mondayIndex)
|
||||
return new Date(date)
|
||||
}
|
||||
|
||||
export function buildCalendarMonth(month: Date, today: Date, selected: Date | null): CalendarDay[] {
|
||||
const first = new Date(month.getFullYear(), month.getMonth(), 1)
|
||||
const start = addCalendarDays(first, -((first.getDay() + 6) % 7))
|
||||
const todayValue = formatLocalDate(today)
|
||||
const selectedValue = selected ? formatLocalDate(selected) : ''
|
||||
return Array.from({ length: 42 }, (_, index) => {
|
||||
const date = addCalendarDays(start, index)
|
||||
const value = formatLocalDate(date)
|
||||
return { date, value, inMonth: date.getMonth() === month.getMonth(), isToday: value === todayValue, isSelected: value === selectedValue }
|
||||
})
|
||||
}
|
||||
|
||||
export function positionCalendarPopover(anchor: AnchorRect, viewportWidth: number, viewportHeight: number, height: number) {
|
||||
const width = 328
|
||||
const gap = 8
|
||||
const edge = 12
|
||||
const left = Math.min(Math.max(anchor.left, edge), viewportWidth - width - edge)
|
||||
const below = anchor.bottom + gap
|
||||
const above = anchor.top - height - gap
|
||||
const maxTop = Math.max(edge, viewportHeight - height - edge)
|
||||
const top = below + height <= viewportHeight - edge
|
||||
? below
|
||||
: above >= edge
|
||||
? above
|
||||
: Math.min(Math.max(anchor.bottom - anchor.height + gap, edge), maxTop)
|
||||
return { left, top }
|
||||
}
|
||||
|
||||
export const calendarDayDistance = (from: Date, to: Date) => Math.round((Date.UTC(to.getFullYear(), to.getMonth(), to.getDate()) - Date.UTC(from.getFullYear(), from.getMonth(), from.getDate())) / DAY_MS)
|
||||
Reference in New Issue
Block a user