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

fix(TimePicker-compat): use null instead of undefined when there's no selected time #29592

Merged
merged 1 commit into from
Oct 19, 2023
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 @@ -24,7 +24,7 @@ export type TimePickerProps = Omit<ComboboxProps, 'children' | 'defaultSelectedO
endHour?: Hour;
increment?: number;
dateAnchor?: Date;
selectedTime?: Date;
selectedTime?: Date | null;
defaultSelectedTime?: Date;
onTimeSelect?: (event: TimeSelectionEvents, data: TimeSelectionData) => void;
formatDateToTimeString?: (date: Date) => string;
Expand All @@ -41,7 +41,7 @@ export type TimePickerState = ComboboxState & Required<Pick<TimePickerProps, 'fr

// @public (undocumented)
export type TimeSelectionData = {
selectedTime: Date | undefined;
selectedTime: Date | null;
selectedTimeText: string | undefined;
error: TimePickerErrorType | undefined;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export type TimePickerOption = {
export type TimePickerErrorType = 'invalid-input' | 'out-of-bounds';

export type TimeStringValidationResult = {
date?: Date;
date: Date | null;
error?: TimePickerErrorType;
};

Expand All @@ -65,7 +65,7 @@ export type TimeSelectionData = {
/**
* The Date object associated with the selected option. For freeform TimePicker it can also be the Date object parsed from the user input.
*/
selectedTime: Date | undefined;
selectedTime: Date | null;
/**
* The display text for the selected option. For freeform TimePicker it can also be the value in user input.
*/
Expand Down Expand Up @@ -125,7 +125,7 @@ export type TimePickerProps = Omit<
/**
* Currently selected time in the TimePicker.
*/
selectedTime?: Date;
selectedTime?: Date | null;

/**
* Default selected time in the TimePicker, for uncontrolled scenarios.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {

describe('Time Utilities', () => {
describe('dateToKey', () => {
it('should return empty string for undefined date', () => {
expect(dateToKey()).toBe('');
it('should return empty string for null date', () => {
expect(dateToKey(null)).toBe('');
});

it('should return "invalid" for invalid dates', () => {
Expand All @@ -26,12 +26,12 @@ describe('Time Utilities', () => {
});

describe('keyToDate', () => {
it('should return undefined for empty string', () => {
expect(keyToDate('')).toBeUndefined();
it('should return null for empty string', () => {
expect(keyToDate('')).toBeNull();
});

it('should return undefined for "invalid" string', () => {
expect(keyToDate('invalid')).toBeUndefined();
it('should return null for "invalid" string', () => {
expect(keyToDate('invalid')).toBeNull();
});

it('should return date for valid ISO string', () => {
Expand All @@ -49,8 +49,8 @@ describe('Time Utilities', () => {
expect(revertedDate?.getTime()).toEqual(originalDate.getTime());
});

it('should be inverses of each other for undefined date', () => {
const originalDate = undefined;
it('should be inverses of each other for null date', () => {
const originalDate = null;
const key = dateToKey(originalDate);
const revertedDate = keyToDate(key);

Expand All @@ -62,7 +62,7 @@ describe('Time Utilities', () => {
const key = dateToKey(originalDate);
const revertedDate = keyToDate(key);

expect(revertedDate).toBeUndefined();
expect(revertedDate).toBeNull();
});
});

Expand Down Expand Up @@ -165,13 +165,13 @@ describe('Time Utilities', () => {

it('returns an error when no time string is provided', () => {
const result = getDateFromTimeString(undefined, dateStartAnchor, dateEndAnchor, {});
expect(result.date).toBeUndefined();
expect(result.date).toBeNull();
expect(result.error).toBe('invalid-input');
});

it('returns an error for an invalid time string', () => {
const result = getDateFromTimeString('25:30', dateStartAnchor, dateEndAnchor, {});
expect(result.date).toBeUndefined();
expect(result.date).toBeNull();
expect(result.error).toBe('invalid-input');
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function isValidDate(date: Date): boolean {
/**
* Converts a Date object to a string key.
*/
export function dateToKey(date?: Date): string {
export function dateToKey(date: Date | null): string {
if (!date) {
return '';
}
Expand All @@ -21,12 +21,12 @@ export function dateToKey(date?: Date): string {
* Converts a string key back to a Date object.
* Returns undefined for keys that don't represent valid dates.
*/
export function keyToDate(key: string): Date | undefined {
export function keyToDate(key: string): Date | null {
if (key === '' || key === 'invalid') {
return undefined;
return null;
}
const date = new Date(key);
return isValidDate(date) ? date : undefined;
return isValidDate(date) ? date : null;
}

/**
Expand Down Expand Up @@ -157,7 +157,7 @@ export function getDateFromTimeString(
timeFormatOptions: TimeFormatOptions,
): TimeStringValidationResult {
if (!time) {
return { error: 'invalid-input' };
return { date: null, error: 'invalid-input' };
}

const { hour12, showSeconds } = timeFormatOptions;
Expand All @@ -171,12 +171,12 @@ export function getDateFromTimeString(
: REGEX_HIDE_SECONDS_HOUR_24;

if (!regex.test(time)) {
return { error: 'invalid-input' };
return { date: null, error: 'invalid-input' };
}

const timeParts = /^(\d\d?):(\d\d):?(\d\d)? ?([ap]m)?/i.exec(time);
if (!timeParts) {
return { error: 'invalid-input' };
return { date: null, error: 'invalid-input' };
}

const [, selectedHours, minutes, seconds, amPm] = timeParts;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ export const useTimePicker_unstable = (props: TimePickerProps, ref: React.Ref<HT
dateAnchor: dateAnchorInProps,
defaultSelectedTime: defaultSelectedTimeInProps,
endHour = 24,
formatDateToTimeString,
hour12 = false,
increment = 30,
formatDateToTimeString,
onTimeSelect,
validateFreeFormTime: validateFreeFormTimeInProps,
selectedTime: selectedTimeInProps,
showSeconds = false,
startHour = 0,
validateFreeFormTime: validateFreeFormTimeInProps,
...rest
} = props;
const { freeform = false } = rest;
Expand Down Expand Up @@ -64,10 +64,10 @@ export const useTimePicker_unstable = (props: TimePickerProps, ref: React.Ref<HT
[dateStartAnchor, dateEndAnchor, increment, dateToText],
);

const [selectedTime, setSelectedTime] = useControllableState<Date | undefined>({
const [selectedTime, setSelectedTime] = useControllableState<Date | null>({
state: selectedTimeInProps,
defaultState: defaultSelectedTimeInProps,
initialState: undefined,
initialState: null,
});

const [submittedText, setSubmittedText] = React.useState<string | undefined>(undefined);
Expand Down Expand Up @@ -142,7 +142,7 @@ const useStableDateAnchor = (providedDate: Date | undefined, startHour: Hour, en
const [fallbackDateAnchor] = React.useState(() => new Date());

// Convert the Date object to a stable key representation. This ensures that the memoization remains stable when a new Date object representing the same date is passed in.
const dateAnchorKey = dateToKey(providedDate);
const dateAnchorKey = dateToKey(providedDate ?? null);
const dateAnchor = React.useMemo(
() => keyToDate(dateAnchorKey) ?? fallbackDateAnchor,
[dateAnchorKey, fallbackDateAnchor],
Expand Down