diff --git a/index.d.ts b/index.d.ts
index 47f92744c..4b37039f1 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -37,6 +37,7 @@ export {
CakeIcon,
CalendarDayIcon,
CalendarIcon,
+ CalendarListIcon,
CalendarMonthIcon,
CalendarWeekIcon,
CapacityIcon,
@@ -177,6 +178,7 @@ export {
MoneyBackIcon,
MoneyIcon,
MountainIcon,
+ MonthPicker,
MouseIcon,
Number,
numberFormat,
diff --git a/src/components/DatePicker/DatePicker.jsx b/src/components/DatePicker/DatePicker.jsx
index 0cb7aa3ee..bfd5b2532 100644
--- a/src/components/DatePicker/DatePicker.jsx
+++ b/src/components/DatePicker/DatePicker.jsx
@@ -12,6 +12,7 @@ import { Context } from "../Provider";
import { Day } from "./Day";
import { LocalizedDayPicker } from "./LocalizedDayPicker";
import { MonthYearSelector } from "./MonthYearSelector";
+import { MonthSelector } from "./MonthSelector";
import { NavbarElement } from "./NavbarElement";
import RangeDatePicker from "./RangeDatePicker";
import { RelativeDateRange } from "./RelativeDateRange";
@@ -30,8 +31,10 @@ export const DatePicker = ({
value,
getDayContent,
disabledDays = [],
+ selectedDays,
loadingDays = [],
shouldShowYearPicker = false,
+ shouldShowMonthSelector = false,
onChange,
onMonthChange,
onSubmitDateRange,
@@ -68,6 +71,7 @@ export const DatePicker = ({
const [rangeName, setRangeName] = useState("");
const isRangeVariant = variant === variants.range;
const isValidValue = value && value.from && value.to;
+ const isSelectedDaysHasValidRange = selectedDays && selectedDays.from && selectedDays.to;
useEffect(() => {
if (timezoneName && !isValidTimeZoneName(timezoneName)) {
@@ -190,19 +194,27 @@ export const DatePicker = ({
// TODO: Should be outside this component because this returns JSX
const CaptionElement = useMemo(() => {
- return shouldShowYearPicker && currentMonth
- ? ({ date }) => (
-
- )
+ return (shouldShowYearPicker || shouldShowMonthSelector) && currentMonth
+ ? ({ date }) =>
+ shouldShowMonthSelector ? (
+
+ ) : (
+
+ )
: undefined;
// Adding `handleMonthChange` causes a lot of re-renders, and closes drop-down.
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [shouldShowYearPicker, currentMonth]);
+ }, [shouldShowYearPicker, shouldShowMonthSelector, currentMonth]);
// TODO: Should be outside this component because this returns JSX
const renderDay = (date) => {
@@ -233,12 +245,20 @@ export const DatePicker = ({
);
};
- const rangeModifier = isRangeVariant && isValidValue ? { start: value.from, end: value.to } : null;
+ const rangeModifier =
+ isRangeVariant && isValidValue
+ ? { start: value.from, end: value.to }
+ : isSelectedDaysHasValidRange
+ ? { start: selectedDays.from, end: selectedDays.to }
+ : null;
// Comparing `from` and `to` dates hides a weird CSS style when you select the same date twice in a date range.
const useDateRangeStyle = isRangeVariant && isValidValue && value.from?.getTime() !== value.to?.getTime();
+ const useDateSelectedRangeStyle =
+ isSelectedDaysHasValidRange && selectedDays.from?.getTime() !== selectedDays.to?.getTime();
// Return the same value if it is already dayjs object or has range variant otherwise format it to dayJs object
- const selectedDays = value && (dayjs.isDayjs(value) || isRangeVariant ? value : now(value, timezoneName).toDate());
+ const selectedDaysValues =
+ selectedDays ?? (value && (dayjs.isDayjs(value) || isRangeVariant ? value : now(value, timezoneName).toDate()));
return (
<>
@@ -267,7 +287,7 @@ export const DatePicker = ({
handleStartMonthChange={handleStartMonthChange}
handleEndMonthChange={handleEndMonthChange}
handleTodayClick={handleTodayClick}
- selectedDays={selectedDays}
+ selectedDays={selectedDaysValues}
locale={locale ?? contextLocale}
timezoneName={timezoneName}
{...rest}
@@ -277,11 +297,12 @@ export const DatePicker = ({
className={clsx(
"ui-date-picker rounded-lg pt-3",
useDateRangeStyle ? "date-range-picker" : null,
+ useDateSelectedRangeStyle ? "date-range-picker max-w-[400px]" : null,
getDayContent ? "has-custom-content" : null,
modifiers.waitlist ? "has-custom-content" : null,
)}
todayButton="Today"
- selectedDays={selectedDays}
+ selectedDays={selectedDaysValues}
month={currentMonth}
modifiers={{ ...modifiers, ...rangeModifier }}
disabledDays={disabledDays}
@@ -319,11 +340,13 @@ export const DatePicker = ({
DatePicker.propTypes = {
variant: PropTypes.oneOf(Object.keys(variants)),
value: PropTypes.objectOf(Date),
+ selectedDays: PropTypes.objectOf(Date),
upcomingDates: PropTypes.arrayOf(Date),
onChange: PropTypes.func.isRequired,
onMonthChange: PropTypes.func,
disabledDays: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.func]),
shouldShowYearPicker: PropTypes.bool,
+ shouldShowMonthSelector: PropTypes.bool,
isDateRangeStyle: PropTypes.bool,
isRangeVariant: PropTypes.bool,
getDayContent: PropTypes.func,
diff --git a/src/components/DatePicker/DatePickerPopover.jsx b/src/components/DatePicker/DatePickerPopover.jsx
index a5cb05a45..f42f5ada1 100644
--- a/src/components/DatePicker/DatePickerPopover.jsx
+++ b/src/components/DatePicker/DatePickerPopover.jsx
@@ -6,12 +6,14 @@ import { formatDate } from "../../helpers/date";
import { Input } from "../Forms/Input";
import { Popover } from "../Popover/Popover";
import { DatePicker } from "./DatePicker";
+import { MonthPicker } from "./MonthPicker";
export const DatePickerPopover = ({
value,
variant = "single",
dateFormat = "ddd, LL",
placeholder = "Select Date",
+ pickerType = "day",
onChange,
children,
classNames = {},
@@ -79,17 +81,20 @@ export const DatePickerPopover = ({
)}
- {isVisible && (
-
- )}
+ {isVisible &&
+ (pickerType === "month" ? (
+
+ ) : (
+
+ ))}
);
diff --git a/src/components/DatePicker/MonthGrid.jsx b/src/components/DatePicker/MonthGrid.jsx
new file mode 100644
index 000000000..49951c18d
--- /dev/null
+++ b/src/components/DatePicker/MonthGrid.jsx
@@ -0,0 +1,73 @@
+import React, { useContext } from "react";
+import dayjs from "dayjs";
+import PropTypes from "prop-types";
+import clsx from "clsx";
+import { Context } from "../Provider";
+import { ChevronLeftIcon, ChevronRightIcon } from "../../icons";
+import { Button } from "../Buttons/Button";
+import { ChevronButton } from "./NavbarElement";
+
+const today = dayjs();
+
+export const MonthGrid = ({ year, value, onChange, handleYearChange, locale, handleClear, handleToday }) => {
+ const { locale: contextLocale } = useContext(Context);
+ const months = [...Array.from({ length: 12 }).keys()].map((m) =>
+ today
+ .locale(locale ?? contextLocale)
+ .month(m)
+ .format("MMM"),
+ );
+
+ const handleMonthSelect = (monthIndex) => {
+ const newDate = new Date(year, monthIndex);
+ onChange(newDate);
+ };
+
+ return (
+
+
+ handleYearChange(-1)}>
+
+
+ {year}
+ handleYearChange(1)}>
+
+
+
+
+
+ {months.map((month, index) => (
+
+ ))}
+
+
+
+
+
+
+
+ );
+};
+
+MonthGrid.propTypes = {
+ year: PropTypes.number.isRequired,
+ value: PropTypes.objectOf(Date),
+ onChange: PropTypes.func.isRequired,
+ locale: PropTypes.string,
+};
diff --git a/src/components/DatePicker/MonthPicker.jsx b/src/components/DatePicker/MonthPicker.jsx
new file mode 100644
index 000000000..490324d88
--- /dev/null
+++ b/src/components/DatePicker/MonthPicker.jsx
@@ -0,0 +1,41 @@
+import React, { useState } from "react";
+import PropTypes from "prop-types";
+import { MonthGrid } from "./MonthGrid";
+
+export const MonthPicker = ({ value, onChange, locale }) => {
+ const [year, setYear] = useState(new Date(value).getFullYear());
+
+ const handleYearChange = (offset) => {
+ setYear(year + offset);
+ };
+
+ const handleToday = () => {
+ const today = new Date();
+ setYear(today.getFullYear());
+ onChange(today);
+ };
+
+ const handleClear = () => {
+ onChange(value);
+ };
+
+ return (
+
+
+
+ );
+};
+
+MonthPicker.propTypes = {
+ value: PropTypes.objectOf(Date),
+ onChange: PropTypes.func,
+ locale: PropTypes.string,
+};
diff --git a/src/components/DatePicker/MonthSelector.jsx b/src/components/DatePicker/MonthSelector.jsx
new file mode 100644
index 000000000..c146e2ea1
--- /dev/null
+++ b/src/components/DatePicker/MonthSelector.jsx
@@ -0,0 +1,65 @@
+import PropTypes from "prop-types";
+import React, { useState } from "react";
+import dayjs from "dayjs";
+import { ChevronDownIcon } from "../../icons";
+import { Popover } from "../Popover/Popover";
+import { MonthGrid } from "./MonthGrid";
+
+export const MonthSelector = ({ date, locale, onChange, currentMonth }) => {
+ const [isVisible, setIsVisible] = useState(false);
+ const [year, setYear] = useState(new Date(currentMonth).getFullYear());
+
+ const toggleVisibility = () => {
+ setIsVisible(!isVisible);
+ };
+
+ const handleMonthSelect = (newDate) => {
+ onChange(newDate);
+ setIsVisible(false);
+ };
+
+ const handleYearChange = (offset) => {
+ setYear(year + offset);
+ };
+
+ const handleClear = () => {
+ setIsVisible(false);
+ };
+
+ const handleToday = () => {
+ onChange(new Date());
+ setIsVisible(false);
+ };
+
+ return (
+
+
+
+
+ {dayjs(date).locale(locale).format("MMMM YYYY")}
+
+
+
+
+
+
+
+
+ );
+};
+
+MonthSelector.propTypes = {
+ date: PropTypes.objectOf(Date).isRequired,
+ onChange: PropTypes.func.isRequired,
+};
diff --git a/src/components/DatePicker/NavbarElement.jsx b/src/components/DatePicker/NavbarElement.jsx
index 1cd76f6d4..bc6598e55 100644
--- a/src/components/DatePicker/NavbarElement.jsx
+++ b/src/components/DatePicker/NavbarElement.jsx
@@ -30,7 +30,7 @@ NavbarElement.propTypes = {
showPreviousButton: PropTypes.bool,
};
-const ChevronButton = ({ isVisible = true, onClick, children }) => {
+export const ChevronButton = ({ isVisible = true, onClick, children }) => {
return (