+
{ (
monthGridProps as {
children: React.ReactElement[];
@@ -424,24 +437,7 @@ const DatePickerComponent = ( {
key={ index }
className="flex flex-row justify-between"
>
- { (
- month as React.ReactElement
- ).props.children[ 1 ].map(
- (
- week: {
- props: JSX.IntrinsicAttributes &
- CustomDayButtonProps;
- },
- weekIndex: React.Key | null | undefined
- ) => (
-
-
-
- )
- ) }
+ { month }
)
) }
@@ -449,40 +445,62 @@ const DatePickerComponent = ( {
);
};
- const handleSelect = ( selectedDate: Date ) => {
+ const handleSelect: OnSelectHandler<
+ Date | Date[] | TDateRange | undefined
+ > = ( selectedDate, trigger ) => {
if ( mode === 'range' ) {
+ const currentSelectedValue = selectedDates as TDateRange;
if (
- ! ( selectedDates as TDateRange )?.from ||
- ( ( selectedDates as TDateRange )?.from &&
- ( selectedDates as TDateRange )?.to )
+ ( ! currentSelectedValue?.from && ! currentSelectedValue?.to ) ||
+ ( currentSelectedValue?.from && currentSelectedValue?.to )
) {
- setSelectedDates( { from: selectedDate, to: null } );
- } else {
+ if (
+ ( currentSelectedValue.from &&
+ isEqual( trigger, currentSelectedValue?.from ) ) ||
+ ( currentSelectedValue.to &&
+ isEqual( trigger, currentSelectedValue?.to ) )
+ ) {
+ setSelectedDates( { from: undefined, to: undefined } );
+ return;
+ }
+ setSelectedDates( { from: trigger, to: undefined } );
+ return;
+ }
+ if ( currentSelectedValue?.from && ! currentSelectedValue?.to ) {
+ if ( trigger < currentSelectedValue.from ) {
+ setSelectedDates( {
+ from: trigger,
+ to: currentSelectedValue.from,
+ } );
+ return;
+ }
setSelectedDates( {
- from: ( selectedDates as TDateRange ).from,
- to: selectedDate,
+ from: currentSelectedValue.from,
+ to: trigger,
} );
+ return;
}
+ setSelectedDates( selectedDate as TDateRange );
} else if ( mode === 'multiple' ) {
if (
( selectedDates as Date[] )!.some(
( date ) =>
format( date, 'yyyy-MM-dd' ) ===
- format( selectedDate, 'yyyy-MM-dd' )
+ format( trigger, 'yyyy-MM-dd' )
)
) {
setSelectedDates(
( selectedDates as Date[] )!.filter(
( date ) =>
format( date, 'yyyy-MM-dd' ) !==
- format( selectedDate, 'yyyy-MM-dd' )
+ format( trigger, 'yyyy-MM-dd' )
)
);
} else {
- setSelectedDates( [ ...( selectedDates as Date[] ), selectedDate ] );
+ setSelectedDates( [ ...( selectedDates as Date[] ), trigger ] );
}
} else if ( mode === 'single' ) {
- setSelectedDates( [ selectedDate ] );
+ setSelectedDates( selectedDate as Date );
}
};
@@ -508,12 +526,12 @@ const DatePickerComponent = ( {
mode={ mode }
selected={ ( () => {
if ( mode === 'range' ) {
- return selectedDates as PropsRangeRequired['selected'];
+ return selectedDates as TDateRange;
}
if ( mode === 'multiple' ) {
return selectedDates as Date[];
}
- return selectedDates as Date;
+ return selectedDates as Date | undefined;
} )() }
onSelect={ handleSelect }
hideNavigation
@@ -522,7 +540,6 @@ const DatePickerComponent = ( {
formatters={ {
formatWeekdayName,
} }
- // showHead={false}
classNames={ {
months: monthsClassName,
month: 'flex flex-col p-2 gap-1 text-center w-full',
@@ -533,40 +550,59 @@ const DatePickerComponent = ( {
'text-muted-foreground rounded-md w-10 font-normal text-sm',
row: 'flex w-full mt-2',
cell: 'h-10 w-10 text-center text-sm p-0 relative',
- day: 'h-10 w-10 p-0 font-normal bg-background-primary text-current',
...classNames,
} }
numberOfMonths={ numberOfMonths }
components={ {
MonthCaption:
CustomMonthCaption as unknown as CustomComponents['MonthCaption'],
+ DayButton:
+ CustomDayButton as unknown as CustomComponents['DayButton'],
Day: ( singleDayProps ) => {
- const modifiers = {
- selected:
- singleDayProps.modifiers.selected || false,
- today: singleDayProps.modifiers.today || false,
- disabled:
- singleDayProps.modifiers.disabled || false,
- outside: singleDayProps.modifiers.outside || false,
- range_middle:
- singleDayProps.modifiers.range_middle || false,
- range_start:
- singleDayProps.modifiers.range_start || false,
- range_end:
- singleDayProps.modifiers.range_end || false,
- };
+ const dataAttributes = Object.entries(
+ singleDayProps
+ ).reduce(
+ ( acc: { [key: string]: unknown }, [ key, value ] ) => {
+ if ( key.startsWith( 'data-' ) ) {
+ acc[ key ] = value;
+ }
+ return acc;
+ },
+ {}
+ );
return (
-
+
+ { singleDayProps.children }
+
);
},
Weekdays: () => <>>,
+ Week: ( weekProps ) => {
+ return (
+
+ { weekProps.children }
+
+ );
+ },
Months: ( monthsProps ) => (
<>
-
+
{ (
monthsProps as {
children: React.ReactElement[];
@@ -597,17 +633,129 @@ const DatePickerComponent = ( {
),
MonthGrid: ( monthGridProps ) =>
! showMonthSelect && ! showYearSelect ? (
-
+
) : (
<>>
),
} }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- { ...( ( mode === 'range' ? { required: true } : {} ) as any ) }
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ { ...( ( mode === 'range' ? { required: false } : {} ) as any ) }
{ ...props }
+ onDayMouseEnter={ ( _, __, event ) => {
+ if ( mode !== 'range' ) {
+ return;
+ }
+ // if more then 1 selected then no need of hover effect
+ const selected = selectedDates as TDateRange;
+
+ // Reset data-hover if more then 1 selected or if none are selected
+ if (
+ ( selected?.from && selected?.to ) ||
+ ( ! selected?.from && ! selected?.to )
+ ) {
+ const resetButtons = Array.from(
+ document.querySelectorAll( '[data-hover]' )
+ );
+
+ resetButtons.forEach( ( item: Element ) => {
+ item.setAttribute( 'data-hover', 'false' );
+ } );
+ return;
+ }
+
+ // Get the current target button
+ const currentButton = event.target as HTMLButtonElement;
+ // Get the date of the current button
+ const currentButtonDate = new Date(
+ currentButton.dataset.day!
+ );
+ // Check if the current button is before or after the selected range
+ const isCurrentButtonBefore = isBefore(
+ currentButtonDate,
+ selected.from!
+ );
+ const isCurrentButtonAfter = isAfter(
+ currentButtonDate,
+ selected.to!
+ );
+
+ // Find the closest ancestor container element
+ // Selector based on the variant of the date picker
+ let datesContainer: Element | undefined;
+ switch ( variant ) {
+ case 'dualdate':
+ case 'presets':
+ datesContainer = currentButton.closest(
+ '.bsf-force-ui-date-picker-month'
+ ) as Element;
+ break;
+ case 'normal':
+ default:
+ datesContainer = currentButton.closest(
+ '.bsf-force-ui-month-weeks'
+ ) as Element;
+ break;
+ }
+
+ // Find all buttons within the container element
+ const buttons: HTMLButtonElement[] = Array.from(
+ datesContainer.querySelectorAll( 'button' )
+ );
+
+ // Sort if the current button is before or after the selected range
+ if ( isCurrentButtonAfter ) {
+ buttons.sort( ( a, b ) =>
+ isAfter(
+ new Date( a.dataset.day! ),
+ new Date( b.dataset.day! )
+ )
+ ? -1
+ : 1
+ );
+ }
+ if ( isCurrentButtonBefore ) {
+ buttons.sort( ( a, b ) =>
+ isBefore(
+ new Date( a.dataset.day! ),
+ new Date( b.dataset.day! )
+ )
+ ? 1
+ : -1
+ );
+ }
+
+ // Find the index of the current button in the buttons array
+ const currentIndex = buttons.indexOf( currentButton );
+
+ // Find the index of the button with data-selected="true"
+ const selectedIndex = buttons.findIndex(
+ ( button: Element ) =>
+ button.getAttribute( 'data-selected' ) === 'true'
+ );
+
+ // Create an array to store the selected buttons
+ const selectedButtons: HTMLButtonElement[] = [];
+
+ // Determine the range of buttons to select
+ const start = Math.min( currentIndex, selectedIndex );
+ const end = Math.max( currentIndex, selectedIndex );
+
+ // Select the buttons between the current button and the button with data-selected="true" (inclusive)
+ for ( let i = start; i <= end; i++ ) {
+ if ( ! buttons[ i ]?.disabled ) {
+ selectedButtons.push( buttons[ i ] );
+ }
+ }
+
+ buttons.forEach( ( item: HTMLButtonElement ) => {
+ // run over all buttons and set data-hover true to those who in range
+ item.setAttribute(
+ 'data-hover',
+ selectedButtons.includes( item ) ? 'true' : 'false'
+ );
+ } );
+ } }
+ disabled={ disabled }
/>
>
);
diff --git a/src/components/datepicker/datepicker.tsx b/src/components/datepicker/datepicker.tsx
index be85af79..e2b65426 100644
--- a/src/components/datepicker/datepicker.tsx
+++ b/src/components/datepicker/datepicker.tsx
@@ -8,10 +8,10 @@ import {
endOfWeek,
startOfMonth,
endOfMonth,
- subWeeks,
- subMonths,
+ subDays,
} from 'date-fns';
import { getDefaultSelectedValue } from './utils';
+import { type PropsBase } from 'react-day-picker';
export interface DatePickerProps {
/** Defines the selection selectionType of the date picker: single, range, or multiple dates. */
@@ -25,7 +25,7 @@ export interface DatePickerProps {
/** Callback function to be executed when the apply button is clicked. */
onApply?: ( selectedDates: Date | { from: Date; to: Date } | Date[] ) => void;
/** Callback function to be executed when a date is selected. */
- onDateSelect?: ( date: Date | Date[] | TDateRange | null ) => void;
+ onDateSelect?: ( date: Date | Date[] | TDateRange | undefined ) => void;
/** Text displayed on the Apply button. */
applyButtonText?: string;
/** Text displayed on the Cancel button. */
@@ -35,7 +35,17 @@ export interface DatePickerProps {
/** Show or hide the footer. */
isFooter?: boolean;
/** Selected date value. */
- selected?: Date | Date[] | TDateRange | null;
+ selected?: Date | Date[] | TDateRange | undefined;
+ /**
+ * Disable the date picker based on the condition.
+ * Example:
+ * To disable future dates, set the condition as:
+ * ```jsx
+ * disabled={{ after: new Date(), before:"" }}
+ * ```
+ * @default undefined
+ */
+ disabled?: PropsBase['disabled'];
}
const DatePicker = ( {
@@ -50,10 +60,11 @@ const DatePicker = ( {
showOutsideDays = true,
isFooter = true,
selected,
+ disabled,
...props
}: DatePickerProps ) => {
const [ selectedDates, setSelectedDates ] = useState<
- TDateRange | Date | Date[] | null
+ TDateRange | Date | Date[] | undefined
>( () => {
if ( ! selected ) {
return getDefaultSelectedValue( selectionType );
@@ -75,7 +86,9 @@ const DatePicker = ( {
return getDefaultSelectedValue( selectionType );
} );
- const handleSelect = ( selectedDate: Date | Date[] | TDateRange | null ) => {
+ const handleSelect = (
+ selectedDate: Date | Date[] | TDateRange | undefined
+ ) => {
setSelectedDates( selectedDate );
if ( onDateSelect ) {
onDateSelect( selectedDate );
@@ -99,10 +112,10 @@ const DatePicker = ( {
},
},
{
- label: 'Last Week',
+ label: 'Last 7 Days',
range: {
- from: startOfWeek( subWeeks( new Date(), 1 ), { weekStartsOn: 1 } ),
- to: endOfWeek( subWeeks( new Date(), 1 ), { weekStartsOn: 1 } ),
+ from: subDays( new Date(), 6 ),
+ to: new Date(),
},
},
{
@@ -113,10 +126,10 @@ const DatePicker = ( {
},
},
{
- label: 'Last Month',
+ label: 'Last 30 Days',
range: {
- from: startOfMonth( subMonths( new Date(), 1 ) ),
- to: endOfMonth( subMonths( new Date(), 1 ) ),
+ from: subDays( new Date(), 29 ),
+ to: new Date(),
},
},
];
@@ -129,7 +142,9 @@ const DatePicker = ( {
const handleCancelClick = () => {
setSelectedDates(
- selectionType === 'multiple' ? [] : { from: null, to: null }
+ selectionType === 'multiple'
+ ? []
+ : { from: undefined, to: undefined }
);
if ( onCancel ) {
onCancel();
@@ -153,7 +168,7 @@ const DatePicker = ( {
showOutsideDays={ showOutsideDays }
setSelectedDates={
handleSelect as (
- dates: Date | Date[] | TDateRange | null
+ dates: Date | Date[] | TDateRange | undefined
) => void
}
footer={
@@ -171,6 +186,7 @@ const DatePicker = ( {
)
}
+ disabled={ disabled }
/>
);
}
@@ -184,7 +200,7 @@ const DatePicker = ( {
selectedDates={ selectedDates }
setSelectedDates={
handleSelect as (
- dates: Date | Date[] | TDateRange | null
+ dates: Date | Date[] | TDateRange | undefined
) => void
}
showOutsideDays={ showOutsideDays }
@@ -200,6 +216,7 @@ const DatePicker = ( {
}
+ disabled={ disabled }
{ ...props }
/>
);
@@ -226,7 +243,7 @@ const DatePicker = ( {
selectedDates={ selectedDates }
setSelectedDates={
handleSelect as (
- dates: Date | Date[] | TDateRange | null
+ dates: Date | Date[] | TDateRange | undefined
) => void
}
variant={ variant }
@@ -246,6 +263,7 @@ const DatePicker = ( {
}
+ disabled={ disabled }
/>
);
diff --git a/src/components/datepicker/utils.tsx b/src/components/datepicker/utils.tsx
index 9965e7f9..dcf04e0c 100644
--- a/src/components/datepicker/utils.tsx
+++ b/src/components/datepicker/utils.tsx
@@ -19,7 +19,7 @@ export const getDefaultSelectedValue = ( type: string ) => {
return [];
}
if ( type === 'range' ) {
- return { from: null, to: null };
+ return { from: undefined, to: undefined };
}
- return null;
+ return undefined;
};
diff --git a/tsconfig.app.json b/tsconfig.app.json
index a075de67..0080a32f 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -6,6 +6,7 @@
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
+ "noErrorTruncation": true,
/* Bundler mode */
"moduleResolution": "bundler",
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 567f1dcc..56e46c4e 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -5,6 +5,7 @@
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
+ "noErrorTruncation": true,
/* Bundler mode */
"moduleResolution": "bundler",
diff --git a/version.json b/version.json
index 3917e46c..7dd2216d 100644
--- a/version.json
+++ b/version.json
@@ -1,3 +1,3 @@
{
- "force-ui": "1.3.4"
+ "force-ui": "1.3.5"
}