Skip to content

Commit

Permalink
Merge branch 'main' into 10050-combobox-error-screenreader
Browse files Browse the repository at this point in the history
  • Loading branch information
tay1orjones committed Mar 14, 2024
2 parents 377d1d6 + 9346f3e commit cff71c6
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 18 deletions.
11 changes: 10 additions & 1 deletion packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2651,6 +2651,9 @@ Map {
"onOpen": Object {
"type": "func",
},
"parseDate": Object {
"type": "func",
},
"readOnly": Object {
"args": Array [
Array [
Expand Down Expand Up @@ -5828,6 +5831,7 @@ Map {
Object {
"args": Array [
Object {
"isRequired": true,
"type": "number",
},
],
Expand All @@ -5838,12 +5842,17 @@ Map {
Object {
"args": Array [
Object {
"text": undefined,
"text": Object {
"isRequired": true,
"type": "string",
},
"value": Object {
"isRequired": true,
"type": "number",
},
},
],
"isRequired": true,
"type": "shape",
},
],
Expand Down
121 changes: 121 additions & 0 deletions packages/react/src/components/DatePicker/DatePicker-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,127 @@ describe('DatePicker', () => {

expect(screen.getByRole('button')).toHaveClass(`${prefix}--slug__button`);
});

it('should respect parseDate prop', async () => {
const parseDate = jest.fn();
parseDate.mockReturnValueOnce(new Date('1989/01/20'));
render(
<DatePicker
onChange={() => {}}
datePickerType="single"
parseDate={parseDate}>
<DatePickerInput
id="date-picker-input-id-start"
placeholder="mm/dd/yyyy"
labelText="Date Picker label"
data-testid="input-value"
/>
</DatePicker>
);
await userEvent.type(
screen.getByLabelText('Date Picker label'),
'01/20/1989{enter}'
);
expect(parseDate).toHaveBeenCalled();
});

it('invalid date month/day is correctly parsed when using the default format', async () => {
render(
<DatePicker onChange={() => {}} datePickerType="single">
<DatePickerInput
id="date-picker-input-id-start"
placeholder="mm/dd/yyyy"
labelText="Date Picker label"
data-testid="input-value"
/>
</DatePicker>
);

expect(screen.getByLabelText('Date Picker label')).toHaveValue('');

// Invalid month
await userEvent.type(
screen.getByLabelText('Date Picker label'),
'99/20/1989{enter}'
);
expect(screen.getByLabelText('Date Picker label')).toHaveValue(
'01/20/1989'
);
await userEvent.clear(screen.getByLabelText('Date Picker label'));

// Invalid day
await userEvent.type(
screen.getByLabelText('Date Picker label'),
'01/99/1989{enter}'
);
expect(screen.getByLabelText('Date Picker label')).toHaveValue(
'01/01/1989'
);
await userEvent.clear(screen.getByLabelText('Date Picker label'));

// Invalid month and day
await userEvent.type(
screen.getByLabelText('Date Picker label'),
'99/99/1989{enter}'
);
expect(screen.getByLabelText('Date Picker label')).toHaveValue(
'01/01/1989'
);
await userEvent.clear(screen.getByLabelText('Date Picker label'));

expect(screen.getByLabelText('Date Picker label')).toHaveValue('');
});

it('invalid date month/day is parsed by flatpickr when using a custom format', async () => {
render(
<DatePicker
onChange={() => {}}
datePickerType="single"
dateFormat="d/m/Y">
<DatePickerInput
id="date-picker-input-id-start"
placeholder="mm/dd/yyyy"
labelText="Date Picker label"
data-testid="input-value"
/>
</DatePicker>
);

expect(screen.getByLabelText('Date Picker label')).toHaveValue('');

await userEvent.type(
screen.getByLabelText('Date Picker label'),
'34/34/3434{enter}'
);
// More on how this value is calculated by flatpickr:
// https://github.com/carbon-design-system/carbon/issues/15432#issuecomment-1967447677
expect(screen.getByLabelText('Date Picker label')).toHaveValue(
'03/10/3436'
);
await userEvent.clear(screen.getByLabelText('Date Picker label'));
});

it('the input is cleared when given a completely invalid date', async () => {
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
render(
<DatePicker onChange={() => {}} datePickerType="single">
<DatePickerInput
id="date-picker-input-id-start"
placeholder="mm/dd/yyyy"
labelText="Date Picker label"
data-testid="input-value"
/>
</DatePicker>
);

await userEvent.type(
screen.getByLabelText('Date Picker label'),
'a1/0a/a999{enter}'
);
expect(warn).toHaveBeenCalled();
expect(screen.getByLabelText('Date Picker label')).toHaveValue('');
warn.mockRestore();
});
});

describe('Simple date picker', () => {
Expand Down
51 changes: 51 additions & 0 deletions packages/react/src/components/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,11 @@ interface DatePickerProps {
*/
onOpen?: flatpickr.Options.Hook;

/**
* flatpickr prop passthrough. Controls how dates are parsed.
*/
parseDate?: (date: string) => Date | false;

/**
* whether the DatePicker is to be readOnly
* if boolean applies to all inputs
Expand Down Expand Up @@ -419,6 +424,7 @@ const DatePicker = React.forwardRef(function DatePicker(
readOnly = false,
short = false,
value,
parseDate: parseDateProp,
...rest
}: DatePickerProps,
ref: ForwardedRef<HTMLDivElement>
Expand Down Expand Up @@ -557,6 +563,45 @@ const DatePicker = React.forwardRef(function DatePicker(
localeData = l10n[locale];
}

/**
* parseDate is called before the date is actually set.
* It attempts to parse the input value and return a valid date string.
* Flatpickr's default parser results in odd dates when given invalid
* values, so instead here we normalize the month/day to `1` if given
* a value outside the acceptable range.
*/
let parseDate;
if (!parseDateProp && dateFormat === 'm/d/Y') {
// This function only supports the default dateFormat.
parseDate = (date) => {
// Month must be 1-12. If outside these bounds, `1` should be used.
const month =
date.split('/')[0] <= 12 && date.split('/')[0] > 0
? parseInt(date.split('/')[0])
: 1;
const year = parseInt(date.split('/')[2]);

if (month && year) {
// The month and year must be provided to be able to determine
// the number of days in the month.
const daysInMonth = new Date(year, month, 0).getDate();
// If the day does not fall within the days in the month, `1` should be used.
const day =
date.split('/')[1] <= daysInMonth && date.split('/')[1] > 0
? parseInt(date.split('/')[1])
: 1;

return new Date(`${year}/${month}/${day}`);
} else {
// With no month and year, we cannot calculate anything.
// Returning false gives flatpickr an invalid date, which will clear the input
return false;
}
};
} else if (parseDateProp) {
parseDate = parseDateProp;
}

const { current: start } = startInputField;
const { current: end } = endInputField;
const flatpickerconfig: any = {
Expand All @@ -571,6 +616,7 @@ const DatePicker = React.forwardRef(function DatePicker(
[enableOrDisable]: enableOrDisableArr,
minDate: minDate,
maxDate: maxDate,
parseDate: parseDate,
plugins: [
datePickerType === 'range'
? carbonFlatpickrRangePlugin({
Expand Down Expand Up @@ -998,6 +1044,11 @@ DatePicker.propTypes = {
*/
onOpen: PropTypes.func,

/**
* flatpickr prop passthrough. Controls how dates are parsed.
*/
parseDate: PropTypes.func,

/**
* whether the DatePicker is to be readOnly
* if boolean applies to all inputs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ import cx from 'classnames';
import SkeletonText from '../SkeletonText';
import { usePrefix } from '../../internal/usePrefix';

function PaginationSkeleton({ className, ...rest }) {
export interface PaginationSkeletonProps
extends React.HTMLAttributes<HTMLDivElement> {
/**
* Specify an optional className to add.
*/
className?: string;
}

function PaginationSkeleton({ className, ...rest }: PaginationSkeletonProps) {
const prefix = usePrefix();
return (
<div
Expand Down

0 comments on commit cff71c6

Please sign in to comment.