Skip to content
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
3 changes: 3 additions & 0 deletions docs-site/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
<meta name="keywords" content="React, HTML, CSS, JavaScript, JSX, date, datepicker">
<title>React Datepicker crafted by HackerOne</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"
integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>

<body>
Expand Down
10 changes: 10 additions & 0 deletions docs-site/src/components/Examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ import selectsRangeWithDisabledDates from "../../examples/selectsRangeWithDisabl
import CalendarStartDay from "../../examples/calendarStartDay";
import ExternalForm from "../../examples/externalForm";
import CalendarIcon from "../../examples/calendarIcon";
import CalendarIconExternal from "../../examples/calendarIconExternal";
import CalendarIconSvgIcon from "../../examples/calendarIconSvgIcon";

import "./style.scss";
import "react-datepicker/dist/react-datepicker.css";
Expand All @@ -118,6 +120,14 @@ export default class exampleComponents extends React.Component {
title: "Calendar Icon",
component: CalendarIcon,
},
{
title: "Calendar Icon using React Svg Component",
component: CalendarIconSvgIcon,
},
{
title: "Calendar Icon using External Lib",
component: CalendarIconExternal,
},
{
title: "Calendar container",
component: CalendarContainer,
Expand Down
11 changes: 11 additions & 0 deletions docs-site/src/examples/calendarIconExternal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
() => {
const [startDate, setStartDate] = useState(new Date());
return (
<DatePicker
showIcon
selected={startDate}
onChange={(date) => setStartDate(date)}
icon="fa fa-calendar"
/>
);
};
33 changes: 33 additions & 0 deletions docs-site/src/examples/calendarIconSvgIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
() => {
const [startDate, setStartDate] = useState(new Date());
return (
<DatePicker
showIcon
selected={startDate}
onChange={(date) => setStartDate(date)}
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
>
<mask id="ipSApplication0">
<g fill="none" stroke="#fff" strokeLinejoin="round" strokeWidth="4">
<path strokeLinecap="round" d="M40.04 22v20h-32V22"></path>
<path
fill="#fff"
d="M5.842 13.777C4.312 17.737 7.263 22 11.51 22c3.314 0 6.019-2.686 6.019-6a6 6 0 0 0 6 6h1.018a6 6 0 0 0 6-6c0 3.314 2.706 6 6.02 6c4.248 0 7.201-4.265 5.67-8.228L39.234 6H8.845l-3.003 7.777Z"
></path>
</g>
</mask>
<path
fill="currentColor"
d="M0 0h48v48H0z"
mask="url(#ipSApplication0)"
></path>
</svg>
}
/>
);
};
7 changes: 5 additions & 2 deletions docs/datepicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ General datepicker component.
| `injectTimes` | `array` | | |
| `inline` | `bool` | | |
| `isClearable` | `bool` | | |
| `showIcon` | `bool` | | |
| `locale` | `string` | | |
| `maxDate` | `instanceOf(Date)` | | |
| `maxTime` | `instanceOf(Date)` | | |
Expand All @@ -69,7 +68,8 @@ General datepicker component.
| `peekNextMonth` | `bool` | | |
| `placeholderText` | `string` | | |
| `popperClassName` | `string` | | |
| `popperContainer` | `func` | | |
| `popperContainer` | `func` | |
| |
| `popperModifiers` | `object` | | |
| `popperPlacement` | `enumpopperPlacementPositions` | | |
| `preventOpenOnFocus` | `bool` | false | When this is true, the datepicker will not automatically open when the date field is focussed |
Expand Down Expand Up @@ -103,3 +103,6 @@ General datepicker component.
| |
| `yearItemNumber` | `number` | `12` | |
| `yearDropdownItemNumber` | `number` | | |
| `icon` | `string` or `element` | | Allows using a custom calendar icon. Accepts a string (icon class name) or a React component (e.g., custom SVG). If a string is passed, an `<i>` element is rendered with that string as its class name. If a React component is passed, it is rendered as-is. |
| `calendarIconClassName` | `string` | | Accepts a string that will be added as an additional CSS class to the calendar icon, allowing further styling customization. |
| `showIcon` | `bool` | `true` | Determines whether the calendar icon is displayed. Set to `true` to display the icon, and `false` to hide it. If `icon` prop is also provided, the custom icon will be displayed when `showIcon` is `true`. |
43 changes: 43 additions & 0 deletions src/calendar_icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import PropTypes from "prop-types";

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

if (React.isValidElement(icon)) {
return React.cloneElement(icon, {
className: `${icon.props.className || ""} ${defaultClass} ${className}`,
});
}

if (typeof icon === "string") {
return (
<i
className={`${defaultClass} ${icon} ${className}`}
aria-hidden="true"
/>
);
}

// Default SVG Icon
return (
<svg
className="react-datepicker__calendar-icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<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>
);
};

CalendarIcon.propTypes = {
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
className: PropTypes.string,
};

CalendarIcon.defaultProps = {
className: "",
};

export default CalendarIcon;
13 changes: 5 additions & 8 deletions src/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import PropTypes from "prop-types";
import Calendar from "./calendar";
import CalendarIcon from "./calendar_icon";
import Portal from "./portal";
import PopperComponent, { popperPlacementPositions } from "./popper_component";
import classnames from "classnames";
Expand Down Expand Up @@ -181,6 +182,8 @@ export default class DatePicker extends React.Component {
inline: PropTypes.bool,
isClearable: PropTypes.bool,
showIcon: PropTypes.bool,
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
calendarIconClassname: PropTypes.string,
locale: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({ locale: PropTypes.object }),
Expand Down Expand Up @@ -1212,21 +1215,15 @@ export default class DatePicker extends React.Component {
};

renderInputContainer() {
const { showIcon } = this.props;
const { showIcon, icon, calendarIconClassname } = this.props;
return (
<div
className={`react-datepicker__input-container${
showIcon ? " react-datepicker__view-calendar-icon" : ""
}`}
>
{showIcon && (
<svg
className="react-datepicker__calendar-icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<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>
<CalendarIcon icon={icon} className={calendarIconClassname} />
)}
{this.state.isRenderAriaLiveMessage && this.renderAriaLiveRegion()}
{this.renderDateInput()}
Expand Down
33 changes: 33 additions & 0 deletions test/calendar_icon.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";
import { mount } from "enzyme";
import CalendarIcon from "../src/calendar_icon";
import { IconParkSolidApplication } from "./helper_components/calendar_icon";

beforeAll(() => {
jest.spyOn(console, "error").mockImplementation(() => {});
});

afterAll(() => {
console.error.mockRestore();
});

describe("CalendarIcon", () => {
it("renders a custom SVG icon when provided", () => {
const wrapper = mount(
<CalendarIcon showIcon icon={<IconParkSolidApplication />} />,
);
expect(
wrapper.find('[data-testid="icon-park-solid-application"]'),
).toHaveLength(1);
});

it("renders a FontAwesome icon when provided", () => {
const wrapper = mount(<CalendarIcon showIcon icon="fa-example-icon" />);
expect(wrapper.find("i.fa-example-icon")).toHaveLength(1);
});

it("does not render an icon when none is provided", () => {
const wrapper = mount(<CalendarIcon showIcon />);
expect(wrapper.find("svg.react-datepicker__calendar-icon")).toHaveLength(1);
});
});
29 changes: 29 additions & 0 deletions test/helper_components/calendar_icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";

export function IconParkSolidApplication(props) {
return (
<svg
data-testid="icon-park-solid-application"
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 48 48"
{...props}
>
<mask id="ipSApplication0">
<g fill="none" stroke="#fff" strokeLinejoin="round" strokeWidth="4">
<path strokeLinecap="round" d="M40.04 22v20h-32V22" />
<path
fill="#fff"
d="M5.842 13.777C4.312 17.737 7.263 22 11.51 22c3.314 0 6.019-2.686 6.019-6a6 6 0 0 0 6 6h1.018a6 6 0 0 0 6-6c0 3.314 2.706 6 6.02 6c4.248 0 7.201-4.265 5.67-8.228L39.234 6H8.845l-3.003 7.777Z"
/>
</g>
</mask>
<path
fill="currentColor"
d="M0 0h48v48H0z"
mask="url(#ipSApplication0)"
/>
</svg>
);
}