Skip to content

Commit

Permalink
feature: Make the calendar icon of the calendar clickable
Browse files Browse the repository at this point in the history
Make the calendar icon clickable that helps to toggle the open status of the calendar.  Added new test cases to validate the new feature and updated the existing test case of checking the calendar icon class to make the new feature.

Closes #4091
  • Loading branch information
Balaji Sridharan committed Dec 18, 2023
1 parent 804b7ca commit 102ef5b
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 17 deletions.
5 changes: 4 additions & 1 deletion src/calendar_icon.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import PropTypes from "prop-types";

const CalendarIcon = ({ icon, className = "" }) => {
const CalendarIcon = ({ icon, className = "", onClick }) => {
const defaultClass = "react-datepicker__calendar-icon";

if (React.isValidElement(icon)) {
Expand All @@ -15,6 +15,7 @@ const CalendarIcon = ({ icon, className = "" }) => {
<i
className={`${defaultClass} ${icon} ${className}`}
aria-hidden="true"
onClick={onClick}
/>
);
}
Expand All @@ -25,6 +26,7 @@ const CalendarIcon = ({ icon, className = "" }) => {
className={`${defaultClass} ${className}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
onClick={onClick}
>
<path d="M96 32V64H48C21.5 64 0 85.5 0 112v48H448V112c0-26.5-21.5-48-48-48H352V32c0-17.7-14.3-32-32-32s-32 14.3-32 32V64H160V32c0-17.7-14.3-32-32-32S96 14.3 96 32zM448 192H0V464c0 26.5 21.5 48 48 48H400c26.5 0 48-21.5 48-48V192z" />
</svg>
Expand All @@ -34,6 +36,7 @@ const CalendarIcon = ({ icon, className = "" }) => {
CalendarIcon.propTypes = {
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
className: PropTypes.string,
onClick: PropTypes.func,
};

export default CalendarIcon;
42 changes: 27 additions & 15 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,10 @@ export default class DatePicker extends React.Component {
this.props.openToDate
? this.props.openToDate
: this.props.selectsEnd && this.props.startDate
? this.props.startDate
: this.props.selectsStart && this.props.endDate
? this.props.endDate
: newDate();
? this.props.startDate
: this.props.selectsStart && this.props.endDate
? this.props.endDate
: newDate();

// Convert the date from string format to standard Date format
modifyHolidays = () =>
Expand All @@ -384,8 +384,8 @@ export default class DatePicker extends React.Component {
minDate && isBefore(defaultPreSelection, startOfDay(minDate))
? minDate
: maxDate && isAfter(defaultPreSelection, endOfDay(maxDate))
? maxDate
: defaultPreSelection;
? maxDate
: defaultPreSelection;
return {
open: this.props.startOpen || false,
preventFocus: false,
Expand Down Expand Up @@ -713,6 +713,10 @@ export default class DatePicker extends React.Component {
}
};

toggleCalendar = () => {
this.setOpen(!this.state.open);
};

handleTimeChange = (time) => {
const selected = this.props.selected
? this.props.selected
Expand Down Expand Up @@ -1175,14 +1179,14 @@ export default class DatePicker extends React.Component {
typeof this.props.value === "string"
? this.props.value
: typeof this.state.inputValue === "string"
? this.state.inputValue
: this.props.selectsRange
? safeDateRangeFormat(
this.props.startDate,
this.props.endDate,
this.props,
)
: safeDateFormat(this.props.selected, this.props);
? this.state.inputValue
: this.props.selectsRange
? safeDateRangeFormat(
this.props.startDate,
this.props.endDate,
this.props,
)
: safeDateFormat(this.props.selected, this.props);

return React.cloneElement(customInput, {
[customInputRef]: (input) => {
Expand Down Expand Up @@ -1250,14 +1254,22 @@ export default class DatePicker extends React.Component {

renderInputContainer() {
const { showIcon, icon, calendarIconClassname } = this.props;
const { open } = this.state;

return (
<div
className={`react-datepicker__input-container${
showIcon ? " react-datepicker__view-calendar-icon" : ""
}`}
>
{showIcon && (
<CalendarIcon icon={icon} className={calendarIconClassname} />
<CalendarIcon
icon={icon}
className={`${calendarIconClassname} ${
open && "react-datepicker-ignore-onclickoutside"
}`}
onClick={this.toggleCalendar}
/>
)}
{this.state.isRenderAriaLiveMessage && this.renderAriaLiveRegion()}
{this.renderDateInput()}
Expand Down
31 changes: 31 additions & 0 deletions test/calendar_icon.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import { mount } from "enzyme";
import { render, fireEvent } from "@testing-library/react";
import CalendarIcon from "../src/calendar_icon";
import { IconParkSolidApplication } from "./helper_components/calendar_icon";

Expand All @@ -12,6 +13,14 @@ afterAll(() => {
});

describe("CalendarIcon", () => {
let onClickMock;
beforeEach(() => {
onClickMock = jest.fn();
});
afterEach(() => {
onClickMock.mockClear();
});

it("renders a custom SVG icon when provided", () => {
const wrapper = mount(
<CalendarIcon showIcon icon={<IconParkSolidApplication />} />,
Expand All @@ -30,4 +39,26 @@ describe("CalendarIcon", () => {
const wrapper = mount(<CalendarIcon showIcon />);
expect(wrapper.find("svg.react-datepicker__calendar-icon")).toHaveLength(1);
});

it("should fire onClick event when the icon is clicked", () => {
const { container } = render(
<CalendarIcon showIcon onClick={onClickMock} />,
);

const icon = container.querySelector("svg.react-datepicker__calendar-icon");
fireEvent.click(icon);

expect(onClickMock).toHaveBeenCalledTimes(1);
});

it("should fire onClick event on the click of font-awesome icon when provided", () => {
const { container } = render(
<CalendarIcon showIcon icon="fa-example-icon" onClick={onClickMock} />,
);

const icon = container.querySelector("i.fa-example-icon");
fireEvent.click(icon);

expect(onClickMock).toHaveBeenCalledTimes(1);
});
});
34 changes: 33 additions & 1 deletion test/datepicker_test.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,38 @@ describe("DatePicker", () => {
);
});

it("should not apply the react-datepicker-ignore-onclickoutside class to calendar icon when closed", () => {
const { container } = render(
<DatePicker selected={utils.newDate("2023-12-17")} showIcon />,
);

const calendarIcon = container.querySelector(
".react-datepicker__calendar-icon",
);
expect(
calendarIcon.classList.contains("react-datepicker-ignore-onclickoutside"),
).toBe(false);
});

it("should apply the react-datepicker-ignore-onclickoutside class to calendar icon when open", () => {
const { container } = render(
<DatePicker selected={utils.newDate("2023-12-17")} showIcon />,
);

let calendarIcon = container.querySelector(
"svg.react-datepicker__calendar-icon",
);
fireEvent.click(calendarIcon);

calendarIcon = container.querySelector(
"svg.react-datepicker__calendar-icon",
);

expect(
calendarIcon.classList.contains("react-datepicker-ignore-onclickoutside"),
).toBe(true);
});

it("should set the type attribute on the clear button to button", () => {
var datePicker = TestUtils.renderIntoDocument(
<DatePicker selected={utils.newDate("2015-12-15")} isClearable />,
Expand Down Expand Up @@ -2301,7 +2333,7 @@ describe("DatePicker", () => {
datePicker,
"react-datepicker__calendar-icon",
).getAttribute("class");
expect(showIconClass).toMatch(/^react-datepicker__calendar-icon\s?$/);
expect(showIconClass).toContain("react-datepicker__calendar-icon");
});

describe("Year picker", () => {
Expand Down

0 comments on commit 102ef5b

Please sign in to comment.