Skip to content
Open
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
7 changes: 3 additions & 4 deletions packages/react-date-picker/src/DatePicker.css
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,14 @@
cursor: pointer;
}

.react-date-picker__button:disabled .react-date-picker__button__icon {
stroke: #6d6d6d;
}
.react-date-picker__button:enabled:hover .react-date-picker__button__icon,
.react-date-picker__button:enabled:focus .react-date-picker__button__icon {
stroke: #0078d7;
}

.react-date-picker__button:disabled .react-date-picker__button__icon {
stroke: #6d6d6d;
}

.react-date-picker__button svg {
display: inherit;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/react-date-picker/src/DatePicker.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ describe('DatePicker', () => {

it('renders clear icon when given clearIcon as a React element', async () => {
function ClearIcon() {
return <>❌</>;
return <span>❌</span>;
}

await render(<DatePicker {...defaultProps} clearIcon={<ClearIcon />} />);
Expand All @@ -218,7 +218,7 @@ describe('DatePicker', () => {

it('renders clear icon when given clearIcon as a function', async () => {
function ClearIcon() {
return <>❌</>;
return <span>❌</span>;
}

await render(<DatePicker {...defaultProps} clearIcon={ClearIcon} />);
Expand Down Expand Up @@ -258,7 +258,7 @@ describe('DatePicker', () => {

it('renders calendar icon when given calendarIcon as a React element', async () => {
function CalendarIcon() {
return <>📅</>;
return <span>📅</span>;
}

await render(<DatePicker {...defaultProps} calendarIcon={<CalendarIcon />} />);
Expand All @@ -270,7 +270,7 @@ describe('DatePicker', () => {

it('renders calendar icon when given calendarIcon as a function', async () => {
function CalendarIcon() {
return <>📅</>;
return <span>📅</span>;
}

await render(<DatePicker {...defaultProps} calendarIcon={CalendarIcon} />);
Expand Down
72 changes: 53 additions & 19 deletions packages/react-date-picker/src/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createElement, useCallback, useEffect, useMemo, useRef, useState } from
import { createPortal } from 'react-dom';
import clsx from 'clsx';
import makeEventProps from 'make-event-props';
import Calendar from 'react-calendar';
import Calendar, { type OnArgs } from 'react-calendar';
import Fit from 'react-fit';

import DateInput from './DateInput.js';
Expand All @@ -17,6 +17,8 @@ import type {
OpenReason,
Value,
} from './shared/types.js';
import type { View } from 'react-calendar/src/shared/types.js';
import { normalizeToDate } from './shared/utils.js';

const baseClassName = 'react-date-picker';
const outsideActionEvents = ['mousedown', 'focusin', 'touchstart'] as const;
Expand Down Expand Up @@ -92,7 +94,7 @@ export type DatePickerProps = {
*/
calendarProps?: CalendarProps;
/**
* Class name(s) that will be added along with `"react-date-picker"` to the main React-Date-Picker `<div>` element.
* Class name(s) that will be added along with `'react-date-picker'` to the main React-Date-Picker `<div>` element.
*
* @example 'class1 class2'
* @example ['class1', 'class2 class3']
Expand Down Expand Up @@ -190,7 +192,7 @@ export type DatePickerProps = {
*/
maxDate?: Date;
/**
* The most detailed calendar view that the user shall see. View defined here also becomes the one on which clicking an item in the calendar will select a date and pass it to onChange. Can be `"month"`, `"year"`, `"decade"` or `"century"`.
* The most detailed calendar view that the user shall see. View defined here also becomes the one on which clicking an item in the calendar will select a date and pass it to onChange. Can be `'month'`, `'year'`, `'decade'` or `'century'`.
*
* @default 'month'
* @example 'year'
Expand Down Expand Up @@ -240,7 +242,7 @@ export type DatePickerProps = {
*/
onCalendarOpen?: () => void;
/**
* Function called when the user picks a valid date. If any of the fields were excluded using custom `format`, `new Date(y, 0, 1, 0, 0, 0)`, where `y` is the current year, is going to serve as a "base".
* Function called when the user picks a valid date. If any of the fields were excluded using custom `format`, `new Date(y, 0, 1, 0, 0, 0)`, where `y` is the current year, is going to serve as a 'base'.
*
* @example (value) => alert('New date is: ', value)
*/
Expand Down Expand Up @@ -278,20 +280,20 @@ export type DatePickerProps = {
*/
required?: boolean;
/**
* Which dates shall be passed by the calendar to the onChange function and onClick{Period} functions. Can be `"start"`, `"end"` or `"range"`. The latter will cause an array with start and end values to be passed.
* Which dates shall be passed by the calendar to the onChange function and onClick{Period} functions. Can be `'start'`, `'end'` or `'range'`. The latter will cause an array with start and end values to be passed.
*
* @default 'start'
* @example 'range'
*/
returnValue?: 'start' | 'end' | 'range';
/**
* Function called before the calendar closes. `reason` can be `"buttonClick"`, `"escape"`, `"outsideAction"`, or `"select"`. If it returns `false`, the calendar will not close.
* Function called before the calendar closes. `reason` can be `'buttonClick'`, `'escape'`, `'outsideAction'`, or `'select'`. If it returns `false`, the calendar will not close.
*
* @example ({ reason }) => reason !== 'outsideAction'
*/
shouldCloseCalendar?: (props: { reason: CloseReason }) => boolean;
/**
* Function called before the calendar opens. `reason` can be `"buttonClick"` or `"focus"`. If it returns `false`, the calendar will not open.
* Function called before the calendar opens. `reason` can be `'buttonClick'` or `'focus'`. If it returns `false`, the calendar will not open.
*
* @example ({ reason }) => reason !== 'focus'
*/
Expand Down Expand Up @@ -369,26 +371,35 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement {
} = props;

const [isOpen, setIsOpen] = useState<boolean | null>(isOpenProps);
const [internalActiveStartDate, setInternalActiveStartDate] = useState<Date | null>(null);
const [internalView, setInternalView] = useState<View>('month');
const wrapper = useRef<HTMLDivElement>(null);
const calendarWrapper = useRef<HTMLDivElement>(null);

useEffect(() => {
setIsOpen(isOpenProps);
}, [isOpenProps]);

function openCalendar({ reason }: { reason: OpenReason }) {
if (shouldOpenCalendar) {
if (!shouldOpenCalendar({ reason })) {
return;
// made this callback and preventing it to re render
const openCalendar = useCallback(
({ reason }: { reason: OpenReason }) => {
if (shouldOpenCalendar) {
if (!shouldOpenCalendar({ reason })) {
return;
}
}
}
const startDate = normalizeToDate(value);
setInternalActiveStartDate(startDate);
setInternalView(maxDetail === 'month' ? 'month' : maxDetail);

setIsOpen(true);
setIsOpen(true);

if (onCalendarOpen) {
onCalendarOpen();
}
}
if (onCalendarOpen) {
onCalendarOpen();
}
},
[value, onCalendarOpen, shouldOpenCalendar, maxDetail],
);

const closeCalendar = useCallback(
({ reason }: { reason: CloseReason }) => {
Expand All @@ -407,6 +418,28 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement {
[onCalendarClose, shouldCloseCalendar],
);

// user to get to the month view everytime , even though they drilled up to decade/century and closed the calender without selecting date
function onActiveStartDateChange({ action, activeStartDate, view }: OnArgs) {
let nextDate = activeStartDate;
switch (action) {
case 'drillUp':
case 'drillDown':
case 'next':
case 'prev':
case 'next2':
case 'prev2':
case 'onChange':
nextDate = activeStartDate;
break;

default:
nextDate = activeStartDate;
}

setInternalActiveStartDate(nextDate);
setInternalView(view);
}

function toggleCalendar() {
if (isOpen) {
closeCalendar({ reason: 'buttonClick' });
Expand Down Expand Up @@ -551,7 +584,6 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement {
<button
aria-label={clearAriaLabel}
className={`${baseClassName}__clear-button ${baseClassName}__button`}
data-testid="clear-button"
disabled={disabled}
onClick={clear}
onFocus={stopPropagation}
Expand All @@ -565,7 +597,6 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement {
aria-expanded={isOpen || false}
aria-label={calendarAriaLabel}
className={`${baseClassName}__calendar-button ${baseClassName}__button`}
data-testid="calendar-button"
disabled={disabled}
onClick={toggleCalendar}
onFocus={stopPropagation}
Expand Down Expand Up @@ -596,6 +627,9 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement {
minDate={minDate}
onChange={(value) => onChange(value)}
value={value}
activeStartDate={internalActiveStartDate ?? undefined}
onActiveStartDateChange={onActiveStartDateChange}
view={internalView}
{...calendarProps}
/>
);
Expand Down
7 changes: 7 additions & 0 deletions packages/react-date-picker/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ export type RangeType = 'century' | 'decade' | 'year' | 'month' | 'day';
type ValuePiece = Date | null;

export type Value = ValuePiece | Range<ValuePiece>;

export type ActiveStartDateChangeArgs = {
action: 'prev' | 'prev2' | 'next' | 'next2' | 'drillUp' | 'drillDown' | 'onChange';
activeStartDate: Date;
value: Date | Date[] | null;
view: 'month' | 'year' | 'decade' | 'century';
};
14 changes: 14 additions & 0 deletions packages/react-date-picker/src/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,17 @@ export function safeMin(...args: unknown[]): number {
export function safeMax(...args: unknown[]): number {
return Math.max(...args.filter(isValidNumber));
}

// date validation , if user provided or need to put default date i.e. today
export function normalizeToDate<T>(v: T): Date {
if (!v) return new Date(); // no value → today

if (v instanceof Date) return v;

if (typeof v === 'string') return new Date(v);

// range value: pick start date
if (Array.isArray(v) && v[0] instanceof Date) return v[0];

return new Date();
}