Skip to content

Commit

Permalink
refactor: make the calendar API more standalone
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Feb 16, 2025
1 parent 987be85 commit c7244b5
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 39 deletions.
72 changes: 43 additions & 29 deletions packages/core/src/useCalendar/useCalendar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('useCalendar', () => {
await render({
setup() {
const { pickerProps, gridProps, buttonProps, gridLabelProps, nextButtonProps, previousButtonProps } =
useCalendar();
useCalendar({ label: 'Calendar' });

return {
pickerProps,
Expand Down Expand Up @@ -47,7 +47,7 @@ describe('useCalendar', () => {
test('opens calendar when button is clicked', async () => {
await render({
setup() {
const { buttonProps, isOpen } = useCalendar();
const { buttonProps, isOpen } = useCalendar({ label: 'Calendar' });

return {
buttonProps,
Expand All @@ -71,7 +71,7 @@ describe('useCalendar', () => {
test('closes calendar when Escape is pressed', async () => {
await render({
setup() {
const { pickerProps, isOpen, buttonProps } = useCalendar();
const { pickerProps, isOpen, buttonProps } = useCalendar({ label: 'Calendar' });

return {
pickerProps,
Expand All @@ -97,7 +97,7 @@ describe('useCalendar', () => {
test('closes calendar when Tab is pressed', async () => {
await render({
setup() {
const { pickerProps, isOpen, buttonProps, gridProps } = useCalendar();
const { pickerProps, isOpen, buttonProps, gridProps } = useCalendar({ label: 'Calendar' });

return {
pickerProps,
Expand Down Expand Up @@ -125,8 +125,8 @@ describe('useCalendar', () => {
});

describe('date selection', () => {
test('calls onDaySelected when a date is selected', async () => {
const onDaySelected = vi.fn();
test('calls onUpdateModelValue when a date is selected', async () => {
const onUpdateModelValue = vi.fn();
const currentDate = now('UTC');

await render({
Expand All @@ -135,8 +135,9 @@ describe('useCalendar', () => {
},
setup() {
const { pickerProps, buttonProps } = useCalendar({
onDaySelected,
currentDate,
label: 'Calendar',
onUpdateModelValue,
modelValue: currentDate,
});

return {
Expand All @@ -158,7 +159,7 @@ describe('useCalendar', () => {
await flush();
await fireEvent.click(screen.getByText('Select Date'));
await flush();
expect(onDaySelected).toHaveBeenCalledWith(currentDate);
expect(onUpdateModelValue).toHaveBeenCalledWith(currentDate);
});

test('uses provided calendar type', async () => {
Expand All @@ -167,6 +168,7 @@ describe('useCalendar', () => {
await render({
setup() {
const { selectedDate } = useCalendar({
label: 'Calendar',
calendar,
});

Expand All @@ -186,7 +188,7 @@ describe('useCalendar', () => {
});

test('handles Enter key on calendar cell', async () => {
const onDaySelected = vi.fn();
const onUpdateModelValue = vi.fn();
const currentDate = now('UTC');

await render({
Expand All @@ -195,8 +197,9 @@ describe('useCalendar', () => {
},
setup() {
const { pickerProps, buttonProps, isOpen, focusedDate } = useCalendar({
onDaySelected,
currentDate,
label: 'Calendar',
onUpdateModelValue,
modelValue: currentDate,
});

return {
Expand Down Expand Up @@ -229,19 +232,20 @@ describe('useCalendar', () => {

// Test Enter key selects the date
await fireEvent.keyDown(cell, { code: 'Enter' });
expect(onDaySelected).toHaveBeenCalledWith(currentDate);
expect(onUpdateModelValue).toHaveBeenCalledWith(currentDate);
expect(screen.queryByText(currentDate.toString())).not.toBeInTheDocument(); // Calendar should close after selection
});

test('handles Enter key in different panels', async () => {
const onDaySelected = vi.fn();
const onUpdateModelValue = vi.fn();
const currentDate = now('UTC');

await render({
setup() {
const { pickerProps, buttonProps, isOpen, focusedDate, gridLabelProps, currentPanel } = useCalendar({
onDaySelected,
currentDate,
label: 'Calendar',
onUpdateModelValue,
modelValue: currentDate,
});

return {
Expand Down Expand Up @@ -272,7 +276,7 @@ describe('useCalendar', () => {

// Test Enter in day panel
await fireEvent.keyDown(calendar, { code: 'Enter' });
expect(onDaySelected).toHaveBeenCalledWith(currentDate);
expect(onUpdateModelValue).toHaveBeenCalledWith(currentDate);

// Switch to month panel
await fireEvent.click(panelLabel);
Expand All @@ -291,7 +295,7 @@ describe('useCalendar', () => {
test('switches between day, month, and year panels', async () => {
await render({
setup() {
const { gridLabelProps, currentPanel } = useCalendar();
const { gridLabelProps, currentPanel } = useCalendar({ label: 'Calendar' });

return {
gridLabelProps,
Expand All @@ -317,12 +321,13 @@ describe('useCalendar', () => {
});

test('navigates to next/previous panels', async () => {
const onDaySelected = vi.fn();
const onUpdateModelValue = vi.fn();

await render({
setup() {
const { nextButtonProps, previousButtonProps, currentPanel } = useCalendar({
onDaySelected,
label: 'Calendar',
onUpdateModelValue,
});

return {
Expand Down Expand Up @@ -362,7 +367,8 @@ describe('useCalendar', () => {
isOpen,
buttonProps,
} = useCalendar({
currentDate,
label: 'Calendar',
modelValue: currentDate,
});

return {
Expand Down Expand Up @@ -426,7 +432,8 @@ describe('useCalendar', () => {
isOpen,
buttonProps,
} = useCalendar({
currentDate,
label: 'Calendar',
modelValue: currentDate,
});

return {
Expand Down Expand Up @@ -489,7 +496,8 @@ describe('useCalendar', () => {
await render({
setup() {
const { pickerProps, isOpen, buttonProps, selectedDate, focusedDate } = useCalendar({
currentDate,
label: 'Calendar',
modelValue: currentDate,
});

return {
Expand Down Expand Up @@ -555,7 +563,8 @@ describe('useCalendar', () => {
await render({
setup() {
const { pickerProps, isOpen, buttonProps, selectedDate, focusedDate, gridLabelProps } = useCalendar({
currentDate,
label: 'Calendar',
modelValue: currentDate,
});

return {
Expand Down Expand Up @@ -627,7 +636,8 @@ describe('useCalendar', () => {
await render({
setup() {
const { pickerProps, isOpen, buttonProps, selectedDate, focusedDate, gridLabelProps } = useCalendar({
currentDate,
label: 'Calendar',
modelValue: currentDate,
});

return {
Expand Down Expand Up @@ -700,7 +710,8 @@ describe('useCalendar', () => {
await render({
setup() {
const { pickerProps, isOpen, buttonProps, selectedDate, focusedDate } = useCalendar({
currentDate,
label: 'Calendar',
modelValue: currentDate,
minDate,
maxDate,
});
Expand Down Expand Up @@ -743,7 +754,8 @@ describe('useCalendar', () => {
await render({
setup() {
const { pickerProps, isOpen, buttonProps, focusedDate } = useCalendar({
currentDate,
label: 'Calendar',
modelValue: currentDate,
});

return {
Expand Down Expand Up @@ -796,7 +808,8 @@ describe('useCalendar', () => {
await render({
setup() {
const { pickerProps, isOpen, buttonProps, focusedDate, gridLabelProps } = useCalendar({
currentDate,
label: 'Calendar',
modelValue: currentDate,
});

return {
Expand Down Expand Up @@ -841,7 +854,8 @@ describe('useCalendar', () => {
await render({
setup() {
const { pickerProps, isOpen, buttonProps, focusedDate, gridLabelProps } = useCalendar({
currentDate,
label: 'Calendar',
modelValue: currentDate,
});

return {
Expand Down
29 changes: 22 additions & 7 deletions packages/core/src/useCalendar/useCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ import { CalendarPanel, useCalendarPanel } from './useCalendarPanel';
import { Calendar, ZonedDateTime, now, toCalendar } from '@internationalized/date';

export interface CalendarProps {
/**
* The field name of the calendar.
*/
name?: string;

/**
* The label for the calendar.
*/
label: string;

/**
* The locale to use for the calendar.
*/
Expand All @@ -21,12 +31,12 @@ export interface CalendarProps {
/**
* The current date to use for the calendar.
*/
currentDate?: ZonedDateTime;
modelValue?: ZonedDateTime;

/**
* The callback to call when a day is selected.
* The initial value to use for the calendar.
*/
onDaySelected?: (day: ZonedDateTime) => void;
value?: ZonedDateTime;

/**
* The calendar type to use for the calendar, e.g. `gregory`, `islamic-umalqura`, etc.
Expand Down Expand Up @@ -77,10 +87,15 @@ export interface CalendarProps {
* The available panels to switch to and from in the calendar.
*/
allowedPanels?: CalendarPanelType[];

/**
* The callback to call when the calendar value is updated.
*/
onUpdateModelValue?: (value: ZonedDateTime) => void;
}

export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> = {}) {
const props = normalizeProps(_props, ['onDaySelected']);
export function useCalendar(_props: Reactivify<CalendarProps, 'onUpdateModelValue'>) {
const props = normalizeProps(_props, ['onUpdateModelValue']);
const calendarId = useUniqId(FieldTypePrefixes.Calendar);
const gridId = `${calendarId}-g`;
const pickerEl = ref<HTMLElement>();
Expand All @@ -90,7 +105,7 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
calendar: () => toValue(props.calendar),
});

const selectedDate = computed(() => toValue(props.currentDate) ?? toCalendar(now(toValue(timeZone)), calendar.value));
const selectedDate = computed(() => toValue(props.modelValue) ?? toCalendar(now(toValue(timeZone)), calendar.value));
const focusedDay = shallowRef<ZonedDateTime>();
const { isOpen } = usePopoverController(pickerEl, { disabled: props.disabled });

Expand All @@ -110,7 +125,7 @@ export function useCalendar(_props: Reactivify<CalendarProps, 'onDaySelected'> =
getSelectedDate: () => selectedDate.value,
getFocusedDate: getFocusedOrSelected,
setDate: (date: ZonedDateTime, panel?: CalendarPanelType) => {
props.onDaySelected?.(date);
props.onUpdateModelValue?.(date);
if (panel) {
switchPanel(panel);
} else if (currentPanel.value.type === 'weeks') {
Expand Down
8 changes: 5 additions & 3 deletions packages/core/src/useDateTimeField/useDateTimeField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,13 @@ export function useDateTimeField(_props: Reactivify<DateTimeFieldProps, 'schema'
errorMessage: field.errorMessage,
});

const calendarProps: Reactivify<CalendarProps, 'onDaySelected'> = {
const calendarProps: Reactivify<CalendarProps, 'onUpdateModelValue'> = {
label: props.label,
locale: () => locale.value,
currentDate: temporalValue,
name: undefined,
modelValue: temporalValue,
calendar: calendar,
onDaySelected: onValueChange,
onUpdateModelValue: onValueChange,
minDate: () => (isTemporalPartial(minDate.value) ? undefined : minDate.value),
maxDate: () => (isTemporalPartial(maxDate.value) ? undefined : maxDate.value),
};
Expand Down

0 comments on commit c7244b5

Please sign in to comment.