Skip to content

Commit

Permalink
feat(DatePicker): add Readonly date picker (#12426)
Browse files Browse the repository at this point in the history
* chore: wip with split readonly

* feat: readonly date picker

* chore: remove unnecessary scss vars

* fix: cursor following review

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
lee-chase and kodiakhq[bot] authored Nov 14, 2022
1 parent 99db21a commit a95ebe2
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 54 deletions.
16 changes: 16 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2470,6 +2470,19 @@ Map {
"onOpen": Object {
"type": "func",
},
"readOnly": Object {
"args": Array [
Array [
Object {
"type": "bool",
},
Object {
"type": "array",
},
],
],
"type": "oneOfType",
},
"short": Object {
"type": "bool",
},
Expand Down Expand Up @@ -2565,6 +2578,9 @@ Map {
"placeholder": Object {
"type": "string",
},
"readOnly": Object {
"type": "bool",
},
"size": Object {
"args": Array [
Array [
Expand Down
36 changes: 36 additions & 0 deletions packages/react/src/components/DatePicker/DatePicker-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,40 @@ describe('Date picker with minDate and maxDate', () => {
expect(mockConsoleError).not.toHaveBeenCalled();
jest.restoreAllMocks();
});

it('should respect readOnly prop', () => {
const onChange = jest.fn();
const onClick = jest.fn();

render(
<DatePicker
dateFormat="m/d/Y"
onClick={onClick}
onChange={onChange}
datePickerType="range"
readOnly={true}>
<DatePickerInput
id="date-picker-input-id-start"
labelText="Start date"
/>
<DatePickerInput
id="date-picker-input-id-finish"
labelText="End date"
/>
</DatePicker>
);

// Click events should fire
const theStart = screen.getByLabelText('Start date');
userEvent.click(theStart);
expect(onClick).toHaveBeenCalledTimes(1);
const theEnd = screen.getByLabelText('End date');
userEvent.click(theEnd);
expect(onClick).toHaveBeenCalledTimes(2);

userEvent.type(theStart, '01/01/2018{tab}'); // should not be possible to type
userEvent.type(theEnd, '02/02/2018{enter}'); // should not be possible to type

expect(onChange).toHaveBeenCalledTimes(0);
});
});
91 changes: 57 additions & 34 deletions packages/react/src/components/DatePicker/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,24 @@ const carbonFlatpickrMonthSelectPlugin = (config) => (fp) => {
};

const updateCurrentMonth = () => {
const monthStr = monthToStr(
fp.currentMonth,
config.shorthand === true,
fp.l10n
);
fp.yearElements.forEach((elem) => {
const currentMonthContainer = elem.closest(
config.selectorFlatpickrMonthYearContainer
);
Array.prototype.forEach.call(
currentMonthContainer.querySelectorAll('.cur-month'),
(monthElement) => {
monthElement.textContent = monthStr;
}
if (fp.monthElements) {
const monthStr = monthToStr(
fp.currentMonth,
config.shorthand === true,
fp.l10n
);
});
fp.yearElements.forEach((elem) => {
const currentMonthContainer = elem.closest(
config.selectorFlatpickrMonthYearContainer
);
Array.prototype.forEach.call(
currentMonthContainer.querySelectorAll('.cur-month'),
(monthElement) => {
monthElement.textContent = monthStr;
}
);
});
}
};

const register = () => {
Expand Down Expand Up @@ -200,6 +202,7 @@ const DatePicker = React.forwardRef(function DatePicker(
onChange,
onClose,
onOpen,
readOnly = false,
short = false,
value,
...rest
Expand Down Expand Up @@ -235,6 +238,7 @@ const DatePicker = React.forwardRef(function DatePicker(
return React.cloneElement(child, {
datePickerType,
ref: startInputField,
readOnly,
});
}
if (
Expand All @@ -244,16 +248,19 @@ const DatePicker = React.forwardRef(function DatePicker(
return React.cloneElement(child, {
datePickerType,
ref: endInputField,
readOnly,
});
}
if (index === 0) {
return React.cloneElement(child, {
ref: startInputField,
readOnly,
});
}
if (index === 1) {
return React.cloneElement(child, {
ref: endInputField,
readOnly,
});
}
}
Expand All @@ -270,6 +277,12 @@ const DatePicker = React.forwardRef(function DatePicker(

const onHook = (_electedDates, _dateStr, instance, prefix) => {
updateClassNames(instance, prefix);
if (startInputField?.current) {
startInputField.current.readOnly = readOnly;
}
if (endInputField?.current) {
endInputField.current.readOnly = readOnly;
}
};

// Logic to determine if `enable` or `disable` will be passed down. If neither
Expand Down Expand Up @@ -328,11 +341,12 @@ const DatePicker = React.forwardRef(function DatePicker(
inputTo: endInputField.current,
}),
],
clickOpens: true,
clickOpens: !readOnly,
noCalendar: readOnly,
nextArrow: rightArrowHTML,
prevArrow: leftArrowHTML,
onChange: (...args) => {
if (savedOnChange) {
if (savedOnChange && !readOnly) {
savedOnChange(...args);
}
},
Expand Down Expand Up @@ -398,15 +412,17 @@ const DatePicker = React.forwardRef(function DatePicker(
start.addEventListener('keydown', handleArrowDown);
start.addEventListener('change', handleOnChange);

// Flatpickr's calendar dialog is not rendered in a landmark causing an
// error with IBM Equal Access Accessibility Checker so we add an aria
// role to the container div.
calendar.calendarContainer.setAttribute('role', 'application');
// IBM EAAC requires an aria-label on a role='region'
calendar.calendarContainer.setAttribute(
'aria-label',
'calendar-container'
);
if (calendar && calendar.calendarContainer) {
// Flatpickr's calendar dialog is not rendered in a landmark causing an
// error with IBM Equal Access Accessibility Checker so we add an aria
// role to the container div.
calendar.calendarContainer.setAttribute('role', 'application');
// IBM EAAC requires an aria-label on a role='region'
calendar.calendarContainer.setAttribute(
'aria-label',
'calendar-container'
);
}
}

if (end) {
Expand All @@ -432,46 +448,46 @@ const DatePicker = React.forwardRef(function DatePicker(
end.removeEventListener('change', handleOnChange);
}
};
}, [savedOnChange, savedOnClose, savedOnOpen]); //eslint-disable-line react-hooks/exhaustive-deps
}, [savedOnChange, savedOnClose, savedOnOpen, readOnly]); //eslint-disable-line react-hooks/exhaustive-deps

useEffect(() => {
if (calendarRef.current) {
if (calendarRef?.current?.set) {
calendarRef.current.set({ dateFormat });
}
}, [dateFormat]);

useEffect(() => {
if (calendarRef.current) {
if (calendarRef?.current?.set) {
calendarRef.current.set('minDate', minDate);
}
}, [minDate]);

useEffect(() => {
if (calendarRef.current) {
if (calendarRef?.current?.set) {
calendarRef.current.set('maxDate', maxDate);
}
}, [maxDate]);

useEffect(() => {
if (calendarRef.current && disable) {
if (calendarRef?.current?.set && disable) {
calendarRef.current.set('disable', disable);
}
}, [disable]);

useEffect(() => {
if (calendarRef.current && enable) {
if (calendarRef?.current?.set && enable) {
calendarRef.current.set('enable', enable);
}
}, [enable]);

useEffect(() => {
if (calendarRef.current && inline) {
if (calendarRef?.current?.set && inline) {
calendarRef.current.set('inline', inline);
}
}, [inline]);

useEffect(() => {
if (calendarRef.current) {
if (calendarRef?.current?.set) {
if (value !== undefined) {
calendarRef.current.setDate(value);
}
Expand Down Expand Up @@ -686,6 +702,13 @@ DatePicker.propTypes = {
*/
onOpen: PropTypes.func,

/**
* whether the DatePicker is to be readOnly
* if boolean applies to all inputs
* if array applies to each input in order
*/
readOnly: PropTypes.oneOfType([PropTypes.bool, PropTypes.array]),

/**
* `true` to use the short version.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,9 @@ export const RangeWithCalendarWithLayer = () => {

export const Skeleton = () => <DatePickerSkeleton range />;

export const Playground = (args) => {
export const Playground = ({ readOnly, ...args }) => {
return (
<DatePicker datePickerType="single" {...args}>
<DatePicker datePickerType="single" {...args} readOnly={readOnly}>
<DatePickerInput
placeholder="mm/dd/yyyy"
labelText="Date Picker label"
Expand Down Expand Up @@ -265,6 +265,11 @@ Playground.argTypes = {
onOpen: {
action: 'clicked',
},
readOnly: {
control: {
type: 'boolean',
},
},
value: {
table: {
disable: true,
Expand Down
35 changes: 17 additions & 18 deletions packages/react/src/components/DatePickerInput/DatePickerInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const DatePickerInput = React.forwardRef(function DatePickerInput(props, ref) {
const labelClasses = cx(`${prefix}--label`, {
[`${prefix}--visually-hidden`]: hideLabel,
[`${prefix}--label--disabled`]: disabled,
[`${prefix}--label--readonly`]: rest.readOnly,
});
const helperTextClasses = cx(`${prefix}--form__helper-text`, {
[`${prefix}--form__helper-text--disabled`]: disabled,
Expand All @@ -72,24 +73,17 @@ const DatePickerInput = React.forwardRef(function DatePickerInput(props, ref) {
[`${prefix}--date-picker--fluid--warn`]: isFluid && warn,
});

const input = invalid ? (
<input
{...rest}
{...datePickerInputProps}
disabled={disabled}
ref={ref}
data-invalid
className={inputClasses}
/>
) : (
<input
{...rest}
{...datePickerInputProps}
disabled={disabled}
className={inputClasses}
ref={ref}
/>
);
const inputProps = {
...rest,
...datePickerInputProps,
className: inputClasses,
disabled,
ref,
};
if (invalid) {
inputProps['data-invalid'] = true;
}
const input = <input {...inputProps} />;

return (
<div className={containerClasses}>
Expand Down Expand Up @@ -202,6 +196,11 @@ DatePickerInput.propTypes = {
*/
placeholder: PropTypes.string,

/**
* whether the DatePicker is to be readOnly
*/
readOnly: PropTypes.bool,

/**
* Specify the size of the Date Picker Input. Currently supports either `sm`, `md`, or `lg` as an option.
*/
Expand Down
10 changes: 10 additions & 0 deletions packages/styles/scss/components/date-picker/_date-picker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,14 @@
.#{$prefix}--date-picker__icon {
@include high-contrast-mode('icon-fill');
}

// readonly
.#{$prefix}--date-picker__input[readonly] {
background: transparent;
cursor: text;
}

.#{$prefix}--date-picker__input[readonly] + .#{$prefix}--date-picker__icon {
fill: $icon-disabled;
}
}

0 comments on commit a95ebe2

Please sign in to comment.