Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add year month selector to calendar component #84

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
129 changes: 120 additions & 9 deletions src/hooks/internal/useNavigation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Temporal } from '@js-temporal/polyfill'
import { Dispatch, SetStateAction, useMemo } from 'react'
import { PickerOptions } from '../../types'
import {
PickerOptionsWithResolvedCalendar,
SupportedCalendar,
} from '../../types'
import localisationHelpers from '../../utils/localisationHelpers'

export type UseNavigationReturnType = {
Expand All @@ -10,6 +13,7 @@ export type UseNavigationReturnType = {
}
currYear: {
label: string | number
value: string | number
}
nextYear: {
label: string | number
Expand All @@ -26,11 +30,61 @@ export type UseNavigationReturnType = {
label: string | undefined
navigateTo: () => void
}
months: Array<{
label: string
value: number
}>
navigateToMonth: (month: number) => void
navigateToYear: (year: number) => void
}

type Month = {
value: number
dateForLabel?: Temporal.PlainDate
}

const getAvailableMonths = (calendarSystem = 'iso8601'): Month[] => {
try {
const calendar = new Temporal.Calendar(calendarSystem)
const referenceDate = calendar.dateFromFields({
year: 2000,
month: 1,
day: 1,
})

const months: Month[] = []
let monthIndex = 1

while (monthIndex <= 13) {
try {
const date = calendar.dateFromFields({
year: referenceDate.year,
month: monthIndex,
day: 1,
})
const monthInfo = calendar.monthsInYear(date)
if (monthIndex > monthInfo) {
break
}
months.push({
value: monthIndex,
dateForLabel: date,
})
monthIndex++
} catch (e) {
break
}
}
return months
} catch (e) {
return getAvailableMonths('iso8601')
}
}

type UseNavigationHook = (
firstZdtOfVisibleMonth: Temporal.ZonedDateTime,
setFirstZdtOfVisibleMonth: Dispatch<SetStateAction<Temporal.ZonedDateTime>>,
localeOptions: PickerOptions
localeOptions: PickerOptionsWithResolvedCalendar
) => UseNavigationReturnType
/**
* internal hook used by useDatePicker to build the navigation of the calendar
Expand Down Expand Up @@ -62,7 +116,9 @@ export const useNavigation: UseNavigationHook = (

const options = {
locale: localeOptions.locale,
calendar: localeOptions.calendar,
calendar: localeOptions.calendar.id as
| SupportedCalendar
| undefined,
numberingSystem: localeOptions.numberingSystem,
}

Expand All @@ -76,53 +132,108 @@ export const useNavigation: UseNavigationHook = (
month: 'long' as const,
}

const monthsData = getAvailableMonths(options.calendar)
const months = monthsData
.map((month) => ({
value: month.value,
label:
(month.dateForLabel &&
localisationHelpers.localiseMonth(
month.dateForLabel.toZonedDateTime({
timeZone: firstZdtOfVisibleMonth.timeZone,
}),
{ ...localeOptions, calendar: options.calendar },
monthFormat
)) ||
'',
}))
.filter((month): month is { label: string; value: number } =>
Boolean(month.label)
)

const navigateToMonth = (monthNum: number) => {
try {
setFirstZdtOfVisibleMonth(
firstZdtOfVisibleMonth.with({ month: monthNum, day: 1 })
)
} catch (e) {
console.error('Invalid month navigation:', e)
}
}

const navigateToYear = (year: number) => {
try {
setFirstZdtOfVisibleMonth(firstZdtOfVisibleMonth.with({ year }))
} catch (e) {
console.error('Invalid year navigation:', e)
}
}

return {
prevYear: {
label: localisationHelpers.localiseYear(
prevYear,
localeOptions,
{ ...localeOptions, calendar: options.calendar },
yearNumericFormat
),
navigateTo: () => setFirstZdtOfVisibleMonth(prevYear),
},
currYear: {
label: localisationHelpers.localiseYear(
firstZdtOfVisibleMonth,
localeOptions,
{ ...localeOptions, calendar: options.calendar },
yearNumericFormat
),
value:
options.calendar === 'ethiopic'
? firstZdtOfVisibleMonth.eraYear ??
String(
localisationHelpers.localiseYear(
firstZdtOfVisibleMonth,
{
...localeOptions,
calendar: options.calendar,
},
yearNumericFormat
)
// Ethiopic years - when localised to English - add the era (i.e. 2015 ERA1)
).split(' ')[0]
: firstZdtOfVisibleMonth.year,
},
nextYear: {
label: localisationHelpers.localiseYear(
nextYear,
localeOptions,
{ ...localeOptions, calendar: options.calendar },
yearNumericFormat
),
navigateTo: () => setFirstZdtOfVisibleMonth(nextYear),
},
prevMonth: {
label: localisationHelpers.localiseMonth(
prevMonth,
localeOptions,
{ ...localeOptions, calendar: options.calendar },
monthFormat
),
navigateTo: () => setFirstZdtOfVisibleMonth(prevMonth),
},
currMonth: {
label: localisationHelpers.localiseMonth(
firstZdtOfVisibleMonth,
localeOptions,
{ ...localeOptions, calendar: options.calendar },
monthFormat
),
},
nextMonth: {
label: localisationHelpers.localiseMonth(
nextMonth,
localeOptions,
{ ...localeOptions, calendar: options.calendar },
monthFormat
),
navigateTo: () => setFirstZdtOfVisibleMonth(nextMonth),
},
months,
navigateToMonth,
navigateToYear,
}
}, [firstZdtOfVisibleMonth, localeOptions, setFirstZdtOfVisibleMonth])
}
8 changes: 5 additions & 3 deletions src/hooks/internal/useWeekDayLabels.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Temporal } from '@js-temporal/polyfill'
import { useMemo } from 'react'
import { PickerOptions } from '../../types'
import { PickerOptionsWithResolvedCalendar } from '../../types'
import localisationHelpers from '../../utils/localisationHelpers'

export const useWeekDayLabels = (localeOptions: PickerOptions) =>
export const useWeekDayLabels = (
localeOptions: PickerOptionsWithResolvedCalendar
) =>
useMemo(() => {
if (!localeOptions.calendar) {
throw new Error('a calendar must be provided to useWeekDayLabels')
Expand All @@ -29,7 +31,7 @@ export const useWeekDayLabels = (localeOptions: PickerOptions) =>

const getWeekDayString: (
date: Temporal.ZonedDateTime,
localeOptions: PickerOptions
localeOptions: PickerOptionsWithResolvedCalendar
) => string = (date, localeOptions) => {
return localisationHelpers.localiseWeekDayLabel(date, localeOptions)
}
4 changes: 2 additions & 2 deletions src/hooks/useDatePicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const useDatePicker: UseDatePickerHookType = ({
const localeOptions = useMemo(
() => ({
locale: resolvedOptions.locale,
calendar: temporalCalendar as unknown as SupportedCalendar,
calendar: temporalCalendar,
timeZone: temporalTimeZone,
weekDayFormat: resolvedOptions.weekDayFormat,
numberingSystem: resolvedOptions.numberingSystem,
Expand Down Expand Up @@ -191,7 +191,7 @@ export const useDatePicker: UseDatePickerHookType = ({
dateValue: formatDate(weekDayZdt, undefined, format),
label: localisationHelpers.localiseWeekLabel(
weekDayZdt.withCalendar(localeOptions.calendar),
localeOptions
{...localeOptions, calendar: resolvedOptions.calendar}
),
onClick: () => selectDate(weekDayZdt),
isSelected: selectedDateZdt
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export type WeekDayFormat = 'narrow' | 'short' | 'long'

export type PickerOptions = Partial<ResolvedLocaleOptions>

export type PickerOptionsWithResolvedCalendar = Omit<PickerOptions, 'calendar'> & {
calendar: Temporal.CalendarProtocol
}

export type ResolvedLocaleOptions = {
calendar: SupportedCalendar
locale: string
Expand Down
8 changes: 6 additions & 2 deletions src/utils/localisationHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
customCalendars,
CustomCalendarTypes,
} from '../custom-calendars'
import { PickerOptions, SupportedCalendar } from '../types'
import {
PickerOptions,
PickerOptionsWithResolvedCalendar,
SupportedCalendar,
} from '../types'
import { formatDate, isCustomCalendar } from './helpers'

const getPartialLocaleMatch: (
Expand Down Expand Up @@ -137,7 +141,7 @@ const localiseMonth = (

export const localiseWeekDayLabel = (
zdt: Temporal.ZonedDateTime,
localeOptions: PickerOptions
localeOptions: PickerOptionsWithResolvedCalendar
) => {
if (!localeOptions.calendar) {
throw new Error('no calendar provided to localise function')
Expand Down
Loading