From 42f10d7b7b00bd232c523facfc1bc8398b9476fc Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 10:38:29 +0600 Subject: [PATCH 01/17] fix: Badge text overflowing --- src/components/badge/badge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/badge/badge.tsx b/src/components/badge/badge.tsx index fc5a02d3..1f070bff 100644 --- a/src/components/badge/badge.tsx +++ b/src/components/badge/badge.tsx @@ -159,7 +159,7 @@ const Badge = forwardRef( { icon } ) : null } - { label } + { label } { closable && ( Date: Sat, 4 Jan 2025 10:41:30 +0600 Subject: [PATCH 02/17] wip: Add hover effect to the range selection --- .../datepicker/datepicker-component.tsx | 331 ++++++++++++------ 1 file changed, 232 insertions(+), 99 deletions(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index c3ab1588..0fef3aee 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -86,6 +86,13 @@ interface CustomDayButtonProps { range_end: boolean; }; onSelect: ( date: Date ) => void; + onMouseEnter?: ( event: React.MouseEvent ) => void; + onMouseLeave?: ( event: React.MouseEvent ) => void; + onClick?: ( event: React.MouseEvent ) => void; + onKeyDown?: ( event: React.KeyboardEvent ) => void; + onFocus?: ( event: React.FocusEvent ) => void; + onBlur?: ( event: React.FocusEvent ) => void; + children: ReactNode; } interface DayProps extends CustomDayButtonProps { @@ -326,7 +333,7 @@ const DatePickerComponent = ( { const CustomDayButton = ( { day, modifiers, - onSelect, + ...customDayProps }: CustomDayButtonProps ) => { const { selected: isSelected, @@ -339,11 +346,11 @@ const DatePickerComponent = ( { } = modifiers; const isPartOfRange = isRangeStart || isRangeEnd || isRangeMiddle; - const handleClick = () => ! isDisabled && onSelect( day.date ); + // const handleClick = () => ! isDisabled && onSelect( day.date ); const today = new Date(); const rangeEnd = ( - selectedDates as { from: Date | null; to: Date | null } + selectedDates as TDateRange )?.to; const isThisMonth = @@ -382,15 +389,52 @@ const DatePickerComponent = ( { : '' ); + + const handleHover = ( event: React.MouseEvent ) => { + if ( typeof customDayProps.onMouseEnter === 'function' ) { + customDayProps.onMouseEnter( event ); + } + event.currentTarget.setAttribute( 'data-hover', 'true' ); + } + const handleLeave = ( event: React.MouseEvent ) => { + if ( typeof customDayProps.onMouseLeave === 'function' ) { + customDayProps.onMouseLeave( event ); + } + event.currentTarget.setAttribute( 'data-hover', 'false' ); + } + const handleClick = ( event: React.MouseEvent ) => { + if ( typeof customDayProps.onClick === 'function' ) { + customDayProps.onClick( event ); + } + } + + console.log('isSelected', isSelected); + return ( - ) ) } + ))} - ) } + )} - { showMonthSelect && ! showYearSelect && ( + {showMonthSelect && !showYearSelect && (
- { Array.from( { length: 12 }, ( _, monthIndex ) => ( + {Array.from({ length: 12 }, (_, monthIndex) => ( - ) ) } + ))}
- ) } + )} - { ! showMonthSelect && ! showYearSelect && ( - - ) } + {!showMonthSelect && !showYearSelect && ( + + )} ); } - const MonthSelectors = ( { weekdays }: { weekdays: string[] } ) => { + const MonthSelectors = ({ weekdays }: { weekdays: string[] }) => { return (
- { weekdays.map( ( weekday, weekdayIndex ) => ( + {weekdays.map((weekday, weekdayIndex) => ( - ) ) } + ))}
); }; - const CustomDayButton = ( { + const CustomDayButton = ({ day, modifiers, ...customDayProps - }: CustomDayButtonProps ) => { + }: CustomDayButtonProps) => { const { selected: isSelected, today: isToday, @@ -349,22 +349,20 @@ const DatePickerComponent = ( { // const handleClick = () => ! isDisabled && onSelect( day.date ); const today = new Date(); - const rangeEnd = ( - selectedDates as TDateRange - )?.to; + const rangeEnd = (selectedDates as TDateRange)?.to; const isThisMonth = - format( day.displayMonth, 'yyyy-MM' ) === format( today, 'yyyy-MM' ); + format(day.displayMonth, 'yyyy-MM') === format(today, 'yyyy-MM'); const isRangeEndInCurrentMonth = rangeEnd && - format( rangeEnd, 'yyyy-MM' ) === format( day.date, 'yyyy-MM' ); - const previousMonth = subMonths( today, 1 ); + format(rangeEnd, 'yyyy-MM') === format(day.date, 'yyyy-MM'); + const previousMonth = subMonths(today, 1); const isPreviousMonth = - format( day.date, 'yyyy-MM' ) === format( previousMonth, 'yyyy-MM' ); + format(day.date, 'yyyy-MM') === format(previousMonth, 'yyyy-MM'); const shouldShowDay = isThisMonth || isRangeEndInCurrentMonth || isPartOfRange; - const showOutsideDates = ! showOutsideDays && isOutside; + const showOutsideDates = !showOutsideDays && isOutside; // Common class for disabled outside days const disabledOutsideClass = @@ -373,71 +371,72 @@ const DatePickerComponent = ( { const buttonClasses = cn( 'h-10 w-10 flex items-center justify-center transition text-text-secondary relative text-sm', 'border-none rounded', - ( isSelected || isPartOfRange ) && ( ! isOutside || isPreviousMonth ) + (isSelected || isPartOfRange) && (!isOutside || isPreviousMonth) ? 'bg-background-brand text-text-on-color' : 'bg-transparent hover:bg-button-tertiary-hover', - isRangeMiddle && shouldShowDay && ( ! isOutside || isPartOfRange ) + isRangeMiddle && shouldShowDay && (!isOutside || isPartOfRange) ? 'bg-brand-background-50 text-text-secondary rounded-none' : '', isDisabled ? 'opacity-50 cursor-not-allowed text-text-disabled' : 'cursor-pointer', - ( isOutside && ! isPartOfRange ) || - ( ! shouldShowDay && isOutside ) || - ( isOutside && ! isPreviousMonth ) + (isOutside && !isPartOfRange) || + (!shouldShowDay && isOutside) || + (isOutside && !isPreviousMonth) ? disabledOutsideClass : '' ); - - const handleHover = ( event: React.MouseEvent ) => { - if ( typeof customDayProps.onMouseEnter === 'function' ) { - customDayProps.onMouseEnter( event ); + const handleHover = (event: React.MouseEvent) => { + if (typeof customDayProps.onMouseEnter === 'function') { + customDayProps.onMouseEnter(event); } - event.currentTarget.setAttribute( 'data-hover', 'true' ); - } - const handleLeave = ( event: React.MouseEvent ) => { - if ( typeof customDayProps.onMouseLeave === 'function' ) { - customDayProps.onMouseLeave( event ); + event.currentTarget.setAttribute('data-hover', 'true'); + }; + const handleLeave = (event: React.MouseEvent) => { + if (typeof customDayProps.onMouseLeave === 'function') { + customDayProps.onMouseLeave(event); } - event.currentTarget.setAttribute( 'data-hover', 'false' ); - } - const handleClick = ( event: React.MouseEvent ) => { - if ( typeof customDayProps.onClick === 'function' ) { - customDayProps.onClick( event ); + event.currentTarget.setAttribute('data-hover', 'false'); + }; + const handleClick = (event: React.MouseEvent) => { + if (typeof customDayProps.onClick === 'function') { + customDayProps.onClick(event); } - } + }; console.log('isSelected', isSelected); return ( ); }; @@ -455,17 +454,17 @@ const DatePickerComponent = ( { // ); // }; - const CustomMonths = ( { monthGridProps, onSelect }: CustomMonthsProps ) => { + const CustomMonths = ({ monthGridProps, onSelect }: CustomMonthsProps) => { return (
- { ( + {( monthGridProps as { children: React.ReactElement[]; } - ).children[ 1 ].props.children.map( - ( month: React.ReactElement, index: number ) => ( + ).children[1].props.children.map( + (month: React.ReactElement, index: number) => (
{/* { ( @@ -489,51 +488,45 @@ const DatePickerComponent = ( { {month}
) - ) } + )}
); }; - const handleSelect = ( selectedDate: Date, trigger, mod ) => { - // console.log( 'selectedDate:\t', selectedDate, '\ntrigger:\t', trigger, '\nmod:\t', mod ); + const handleSelect = ( + selectedDate: Date | TDateRange | Date[], + trigger: Date + ) => { if (mode === 'range') { - // if ( - // !(selectedDates as TDateRange)?.from || - // ((selectedDates as TDateRange)?.from && - // (selectedDates as TDateRange)?.to) - // ) { - // setSelectedDates({ from: selectedDate, to: null }); - // } else { - // setSelectedDates({ - // from: (selectedDates as TDateRange).from, - // to: selectedDate?.to, - // }); - // } - if ( (selectedDates?.from && selectedDates?.to) || (! selectedDates?.from && ! selectedDates?.to) ) { - setSelectedDates( { from: trigger, to: null } ); - return + const currentSelectedValue = selectedDates as TDateRange; + if ( + (currentSelectedValue?.from && currentSelectedValue?.to) || + (!currentSelectedValue?.from && !currentSelectedValue?.to) + ) { + setSelectedDates({ from: trigger, to: null }); + return; } - setSelectedDates( selectedDate ) + setSelectedDates(selectedDate); } 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); } }; @@ -553,7 +546,7 @@ const DatePickerComponent = ( { isFooter ? 'rounded-b-none' : 'rounded-bl-md rounded-br-md' ); - console.log( 'selectedDates', selectedDates ); + console.log('selectedDates', selectedDates); return ( <> @@ -596,10 +589,10 @@ const DatePickerComponent = ( { CustomMonthCaption as unknown as CustomComponents['MonthCaption'], DayButton: CustomDayButton as unknown as CustomComponents['DayButton'], - Day: ( singleDayProps ) => { + Day: (singleDayProps) => { return (
); }, - Weekdays: () => (<>), + Weekdays: () => <>, Week: (weekProps: any) => { return (
{weekProps.children}
- ) + ); }, Months: (monthsProps) => ( <> @@ -728,7 +721,7 @@ const DatePickerComponent = ( { // Select the buttons between the current button and the button with aria-selected="true" (inclusive) // eslint-disable-next-line no-plusplus for (let i = start; i <= end; i++) { - if (! buttons[i]?.disabled) { + if (!buttons[i]?.disabled) { selectedButtons.push(buttons[i]); } } From 0e2ab01372c00d3316853d2f7a2e2a71264e73d5 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 11:18:13 +0600 Subject: [PATCH 04/17] Removed commented code and fixed type --- .../datepicker/datepicker-component.tsx | 82 +++---------------- 1 file changed, 10 insertions(+), 72 deletions(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index ede43212..6d672c15 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -10,7 +10,6 @@ import { format, subMonths } from 'date-fns'; import { cn } from '@/utilities/functions'; import Button from '../button'; import { currentTimeDot, formatWeekdayName, generateYearRange } from './utils'; -import { JSX } from 'react/jsx-runtime'; export type TDateRange = { from: Date | null; to: Date | null }; @@ -52,21 +51,12 @@ export interface DatePickerProps { variant?: 'normal' | 'dualdate' | 'presets'; /** Defines the alignment of the date picker: horizontal or vertical. */ alignment?: 'horizontal' | 'vertical'; - // /** Callback when the date picker loses focus. */ - // onBlur?: ( event: React.FocusEvent ) => void; - // /** Callback when the selected date changes. */ - // onChange?: ( date: Date | Date[] | { from: Date; to: Date } | null ) => void; /** The number of months to display. */ numberOfMonths?: number; /** Footer content to be displayed at the bottom of the date picker. */ footer?: ReactNode; } -interface CustomMonthsProps { - monthGridProps: React.ComponentProps<'table'>; - onSelect: (date: Date) => void; -} - interface CustomMonthCaptionProps { goToMonth: (date: Date) => void; nextMonth: Date; @@ -95,10 +85,6 @@ interface CustomDayButtonProps { children: ReactNode; } -interface DayProps extends CustomDayButtonProps { - className?: string; -} - const DatePickerComponent = ({ width, className: outerClassName, // Renamed to avoid shadowing @@ -109,8 +95,6 @@ const DatePickerComponent = ({ mode = 'single', variant = 'normal', alignment = 'horizontal', - // onBlur, - // onChange, numberOfMonths, ...props }: DatePickerProps) => { @@ -346,7 +330,6 @@ const DatePickerComponent = ({ } = modifiers; const isPartOfRange = isRangeStart || isRangeEnd || isRangeMiddle; - // const handleClick = () => ! isDisabled && onSelect( day.date ); const today = new Date(); const rangeEnd = (selectedDates as TDateRange)?.to; @@ -409,7 +392,6 @@ const DatePickerComponent = ({ return (
), - // Day(dayProps) { - // console.log('Day', dayProps); - // return ( - //
- // ) - // }, MonthGrid: (monthGridProps) => !showMonthSelect && !showYearSelect ? ( ) : ( <> ), }} - // eslint-disable-next-line @typescript-eslint/no-explicit-any {...props} onDayMouseEnter={(_, __, event) => { if (mode !== 'range') { @@ -683,7 +621,7 @@ const DatePickerComponent = ({ document.querySelectorAll('[data-hover]') ); - resetButtons.forEach((item: any) => { + resetButtons.forEach((item: Element) => { item.setAttribute('data-hover', 'false'); }); return; @@ -692,14 +630,14 @@ const DatePickerComponent = ({ // Get the current target button const currentButton = event.target as HTMLButtonElement; - // Find the closest ancestor element - const tbody = currentButton.closest( + // Find the closest ancestor container element + const datesContainer = currentButton.closest( '.bsf-force-ui-month-weeks' ) as any; - // Find all buttons within the element - const buttons = Array.from( - tbody.querySelectorAll('button') + // Find all buttons within the container element + const buttons: HTMLButtonElement[] = Array.from( + datesContainer.querySelectorAll('button') ); // Find the index of the current button in the buttons array @@ -712,7 +650,7 @@ const DatePickerComponent = ({ ); // Create an array to store the selected buttons - const selectedButtons = [] as any[]; + const selectedButtons: HTMLButtonElement[] = []; // Determine the range of buttons to select const start = Math.min(currentIndex, selectedIndex); @@ -726,7 +664,7 @@ const DatePickerComponent = ({ } } - buttons.forEach((item: any) => { + buttons.forEach((item: HTMLButtonElement) => { // run over all buttons and set data-hover true to those who in range item.setAttribute( 'data-hover', From 46b5f9f6f10bf57a11684402b446d41fa90a6e9a Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 12:06:41 +0600 Subject: [PATCH 05/17] fix: variable type --- .../datepicker/datepicker-component.tsx | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index 6d672c15..5ffb21fe 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -3,6 +3,7 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'; import { DayPicker, useDayPicker, + type MonthGridProps, type CustomComponents, type PropsRangeRequired, } from 'react-day-picker'; @@ -11,7 +12,7 @@ import { cn } from '@/utilities/functions'; import Button from '../button'; import { currentTimeDot, formatWeekdayName, generateYearRange } from './utils'; -export type TDateRange = { from: Date | null; to: Date | null }; +export type TDateRange = { from: Date | undefined; to: Date | undefined }; export interface DatePickerProps { /** The width of the date picker. */ @@ -98,7 +99,7 @@ const DatePickerComponent = ({ numberOfMonths, ...props }: DatePickerProps) => { - // check footer is a valide compoenent + // check footer is a valid component. const isFooter = React.isValidElement(props.footer) || typeof props.footer === 'function'; @@ -109,13 +110,13 @@ const DatePickerComponent = ({ selectedYear - (selectedYear % 24) ); - if (selectedDates === null || selectedDates === undefined) { + if (selectedDates === undefined ) { if (mode === 'multiple') { selectedDates = []; } else if (mode === 'range') { - selectedDates = { from: null, to: null }; + selectedDates = { from: undefined, to: undefined }; } else { - selectedDates = null; + selectedDates = undefined; } } @@ -421,7 +422,7 @@ const DatePickerComponent = ({ ); }; - const CustomMonths = ( monthGridProps ) => { + const CustomMonths = ( monthGridProps: MonthGridProps ) => { return (
{( @@ -452,7 +453,7 @@ const DatePickerComponent = ({ (currentSelectedValue?.from && currentSelectedValue?.to) || (!currentSelectedValue?.from && !currentSelectedValue?.to) ) { - setSelectedDates({ from: trigger, to: null }); + setSelectedDates({ from: trigger, to: undefined }); return; } setSelectedDates(selectedDate); @@ -495,20 +496,18 @@ const DatePickerComponent = ({ isFooter ? 'rounded-b-none' : 'rounded-bl-md rounded-br-md' ); - console.log('selectedDates', selectedDates); - return ( <> { 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 @@ -597,13 +596,12 @@ const DatePickerComponent = ({ ), MonthGrid: (monthGridProps) => !showMonthSelect && !showYearSelect ? ( - + ) : ( <> ), }} + {...(mode === 'range' && { required: true })} {...props} onDayMouseEnter={(_, __, event) => { if (mode !== 'range') { From 43fd5f9dd37a90ed6ade988927b6e02f21747b27 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 12:54:54 +0600 Subject: [PATCH 06/17] Updated the TS config to show complete error message --- tsconfig.app.json | 1 + tsconfig.node.json | 1 + 2 files changed, 2 insertions(+) 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", From 48e93dcbd92ea24cc2f16011cb3f8da2397740a4 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 12:55:07 +0600 Subject: [PATCH 07/17] fix: Type errors --- .../datepicker/datepicker-component.tsx | 6 +++--- src/components/datepicker/datepicker.tsx | 16 ++++++++-------- src/components/datepicker/utils.tsx | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index 5ffb21fe..0920c75f 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -5,7 +5,6 @@ import { useDayPicker, type MonthGridProps, type CustomComponents, - type PropsRangeRequired, } from 'react-day-picker'; import { format, subMonths } from 'date-fns'; import { cn } from '@/utilities/functions'; @@ -41,9 +40,9 @@ export interface DatePickerProps { day?: string; }; /** The selected dates. */ - selectedDates?: Date | Date[] | TDateRange | null; + selectedDates?: Date | Date[] | TDateRange | undefined; /** Sets the selected dates. */ - setSelectedDates: (dates: Date | Date[] | TDateRange | null) => void; + setSelectedDates: (dates: Date | Date[] | TDateRange | undefined) => void; /** Show or hide days outside of the current month. */ showOutsideDays?: boolean; /** Defines the selection selectionType of the date picker: single, range, or multiple dates. */ @@ -498,6 +497,7 @@ const DatePickerComponent = ({ return ( <> + {/* @ts-ignore */} { diff --git a/src/components/datepicker/datepicker.tsx b/src/components/datepicker/datepicker.tsx index be85af79..cceb54f9 100644 --- a/src/components/datepicker/datepicker.tsx +++ b/src/components/datepicker/datepicker.tsx @@ -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,7 @@ export interface DatePickerProps { /** Show or hide the footer. */ isFooter?: boolean; /** Selected date value. */ - selected?: Date | Date[] | TDateRange | null; + selected?: Date | Date[] | TDateRange | undefined; } const DatePicker = ( { @@ -53,7 +53,7 @@ const DatePicker = ( { ...props }: DatePickerProps ) => { const [ selectedDates, setSelectedDates ] = useState< - TDateRange | Date | Date[] | null + TDateRange | Date | Date[] | undefined >( () => { if ( ! selected ) { return getDefaultSelectedValue( selectionType ); @@ -75,7 +75,7 @@ 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 ); @@ -129,7 +129,7 @@ const DatePicker = ( { const handleCancelClick = () => { setSelectedDates( - selectionType === 'multiple' ? [] : { from: null, to: null } + selectionType === 'multiple' ? [] : { from: undefined, to: undefined } ); if ( onCancel ) { onCancel(); @@ -153,7 +153,7 @@ const DatePicker = ( { showOutsideDays={ showOutsideDays } setSelectedDates={ handleSelect as ( - dates: Date | Date[] | TDateRange | null + dates: Date | Date[] | TDateRange | undefined ) => void } footer={ @@ -184,7 +184,7 @@ const DatePicker = ( { selectedDates={ selectedDates } setSelectedDates={ handleSelect as ( - dates: Date | Date[] | TDateRange | null + dates: Date | Date[] | TDateRange | undefined ) => void } showOutsideDays={ showOutsideDays } @@ -226,7 +226,7 @@ const DatePicker = ( { selectedDates={ selectedDates } setSelectedDates={ handleSelect as ( - dates: Date | Date[] | TDateRange | null + dates: Date | Date[] | TDateRange | undefined ) => void } variant={ variant } 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; }; From 4d6fd107d25739806dd394bb65458406d25b0658 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 13:15:29 +0600 Subject: [PATCH 08/17] chore: Lint --- .../datepicker/datepicker-component.tsx | 446 +++++++++--------- src/components/datepicker/datepicker.tsx | 8 +- 2 files changed, 228 insertions(+), 226 deletions(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index 0920c75f..5d853f28 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -42,7 +42,7 @@ export interface DatePickerProps { /** The selected dates. */ selectedDates?: Date | Date[] | TDateRange | undefined; /** Sets the selected dates. */ - setSelectedDates: (dates: Date | Date[] | TDateRange | undefined) => void; + setSelectedDates: ( dates: Date | Date[] | TDateRange | undefined ) => void; /** Show or hide days outside of the current month. */ showOutsideDays?: boolean; /** Defines the selection selectionType of the date picker: single, range, or multiple dates. */ @@ -58,7 +58,7 @@ export interface DatePickerProps { } interface CustomMonthCaptionProps { - goToMonth: (date: Date) => void; + goToMonth: ( date: Date ) => void; nextMonth: Date; previousMonth: Date; calendarMonth: { date: Date }; @@ -75,17 +75,17 @@ interface CustomDayButtonProps { range_start: boolean; range_end: boolean; }; - onSelect: (date: Date) => void; - onMouseEnter?: (event: React.MouseEvent) => void; - onMouseLeave?: (event: React.MouseEvent) => void; - onClick?: (event: React.MouseEvent) => void; - onKeyDown?: (event: React.KeyboardEvent) => void; - onFocus?: (event: React.FocusEvent) => void; - onBlur?: (event: React.FocusEvent) => void; + onSelect: ( date: Date ) => void; + onMouseEnter?: ( event: React.MouseEvent ) => void; + onMouseLeave?: ( event: React.MouseEvent ) => void; + onClick?: ( event: React.MouseEvent ) => void; + onKeyDown?: ( event: React.KeyboardEvent ) => void; + onFocus?: ( event: React.FocusEvent ) => void; + onBlur?: ( event: React.FocusEvent ) => void; children: ReactNode; } -const DatePickerComponent = ({ +const DatePickerComponent = ( { width, className: outerClassName, // Renamed to avoid shadowing classNames, @@ -97,79 +97,79 @@ const DatePickerComponent = ({ alignment = 'horizontal', numberOfMonths, ...props -}: DatePickerProps) => { +}: DatePickerProps ) => { // check footer is a valid component. const isFooter = - React.isValidElement(props.footer) || + React.isValidElement( props.footer ) || typeof props.footer === 'function'; - const [showMonthSelect, setShowMonthSelect] = useState(false); - const [showYearSelect, setShowYearSelect] = useState(false); // New state for year selection - const [selectedYear, setSelectedYear] = useState(new Date().getFullYear()); - const [yearRangeStart, setYearRangeStart] = useState( - selectedYear - (selectedYear % 24) + const [ showMonthSelect, setShowMonthSelect ] = useState( false ); + const [ showYearSelect, setShowYearSelect ] = useState( false ); // New state for year selection + const [ selectedYear, setSelectedYear ] = useState( new Date().getFullYear() ); + const [ yearRangeStart, setYearRangeStart ] = useState( + selectedYear - ( selectedYear % 24 ) ); - if (selectedDates === undefined ) { - if (mode === 'multiple') { + if ( selectedDates === undefined ) { + if ( mode === 'multiple' ) { selectedDates = []; - } else if (mode === 'range') { + } else if ( mode === 'range' ) { selectedDates = { from: undefined, to: undefined }; } else { selectedDates = undefined; } } - function CustomMonthCaption(customMonthProps: CustomMonthCaptionProps) { + function CustomMonthCaption( customMonthProps: CustomMonthCaptionProps ) { const { goToMonth, nextMonth, previousMonth } = useDayPicker(); const yearFormatted = format( customMonthProps.calendarMonth.date, 'yyyy' ); - const month = format(customMonthProps.calendarMonth.date, 'MMMM'); + const month = format( customMonthProps.calendarMonth.date, 'MMMM' ); - const startOfWeek = new Date(customMonthProps.calendarMonth.date); - startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay()); + const startOfWeek = new Date( customMonthProps.calendarMonth.date ); + startOfWeek.setDate( startOfWeek.getDate() - startOfWeek.getDay() ); - const weekdays = Array.from({ length: 7 }, (_, i) => { - const date = new Date(startOfWeek); - date.setDate(startOfWeek.getDate() + i); - return formatWeekdayName(date); - }); + const weekdays = Array.from( { length: 7 }, ( _, i ) => { + const date = new Date( startOfWeek ); + date.setDate( startOfWeek.getDate() + i ); + return formatWeekdayName( date ); + } ); const handlePrevButtonClick = () => { - if (showYearSelect) { - setYearRangeStart(yearRangeStart - 24); - } else if (showMonthSelect) { + if ( showYearSelect ) { + setYearRangeStart( yearRangeStart - 24 ); + } else if ( showMonthSelect ) { const prevYear = new Date( selectedYear - 1, customMonthProps.calendarMonth.date.getMonth() ); - setSelectedYear(prevYear.getFullYear()); - goToMonth(prevYear); + setSelectedYear( prevYear.getFullYear() ); + goToMonth( prevYear ); } else { - goToMonth(previousMonth!); + goToMonth( previousMonth! ); } }; const handleNextButtonClick = () => { - if (showYearSelect) { - setYearRangeStart(yearRangeStart + 24); - } else if (showMonthSelect) { + if ( showYearSelect ) { + setYearRangeStart( yearRangeStart + 24 ); + } else if ( showMonthSelect ) { const nextYear = new Date( selectedYear + 1, customMonthProps.calendarMonth.date.getMonth() ); - setSelectedYear(nextYear.getFullYear()); - goToMonth(nextYear); + setSelectedYear( nextYear.getFullYear() ); + goToMonth( nextYear ); } else { - goToMonth(nextMonth!); + goToMonth( nextMonth! ); } }; - const handleYearClick = (yearValue: number) => { - setSelectedYear(yearValue); - setShowYearSelect(false); - setShowMonthSelect(true); + const handleYearClick = ( yearValue: number ) => { + setSelectedYear( yearValue ); + setShowYearSelect( false ); + setShowMonthSelect( true ); goToMonth( new Date( yearValue, @@ -179,12 +179,12 @@ const DatePickerComponent = ({ }; let displayText; - if (showYearSelect) { - displayText = `${yearRangeStart} - ${yearRangeStart + 23}`; - } else if (showMonthSelect) { + if ( showYearSelect ) { + displayText = `${ yearRangeStart } - ${ yearRangeStart + 23 }`; + } else if ( showMonthSelect ) { displayText = yearFormatted; } else { - displayText = `${month} ${yearFormatted}`; + displayText = `${ month } ${ yearFormatted }`; } return ( @@ -192,7 +192,7 @@ const DatePickerComponent = ({
- {showYearSelect && ( + { showYearSelect && (
- {generateYearRange(yearRangeStart).map((yearValue) => ( + { generateYearRange( yearRangeStart ).map( ( yearValue ) => ( - ))} + ) ) }
- )} + ) } - {showMonthSelect && !showYearSelect && ( + { showMonthSelect && ! showYearSelect && (
- {Array.from({ length: 12 }, (_, monthIndex) => ( + { Array.from( { length: 12 }, ( _, monthIndex ) => ( - ))} + ) ) }
- )} + ) } - {!showMonthSelect && !showYearSelect && ( - - )} + { ! showMonthSelect && ! showYearSelect && ( + + ) } ); } - const MonthSelectors = ({ weekdays }: { weekdays: string[] }) => { + const MonthSelectors = ( { weekdays }: { weekdays: string[] } ) => { return (
- {weekdays.map((weekday, weekdayIndex) => ( + { weekdays.map( ( weekday, weekdayIndex ) => ( - ))} + ) ) }
); }; - const CustomDayButton = ({ + const CustomDayButton = ( { day, modifiers, ...customDayProps - }: CustomDayButtonProps) => { + }: CustomDayButtonProps ) => { const { selected: isSelected, today: isToday, @@ -332,20 +332,20 @@ const DatePickerComponent = ({ const isPartOfRange = isRangeStart || isRangeEnd || isRangeMiddle; const today = new Date(); - const rangeEnd = (selectedDates as TDateRange)?.to; + const rangeEnd = ( selectedDates as TDateRange )?.to; const isThisMonth = - format(day.displayMonth, 'yyyy-MM') === format(today, 'yyyy-MM'); + format( day.displayMonth, 'yyyy-MM' ) === format( today, 'yyyy-MM' ); const isRangeEndInCurrentMonth = rangeEnd && - format(rangeEnd, 'yyyy-MM') === format(day.date, 'yyyy-MM'); - const previousMonth = subMonths(today, 1); + format( rangeEnd, 'yyyy-MM' ) === format( day.date, 'yyyy-MM' ); + const previousMonth = subMonths( today, 1 ); const isPreviousMonth = - format(day.date, 'yyyy-MM') === format(previousMonth, 'yyyy-MM'); + format( day.date, 'yyyy-MM' ) === format( previousMonth, 'yyyy-MM' ); const shouldShowDay = isThisMonth || isRangeEndInCurrentMonth || isPartOfRange; - const showOutsideDates = !showOutsideDays && isOutside; + const showOutsideDates = ! showOutsideDays && isOutside; // Common class for disabled outside days const disabledOutsideClass = @@ -354,45 +354,43 @@ const DatePickerComponent = ({ const buttonClasses = cn( 'h-10 w-10 flex items-center justify-center transition text-text-secondary relative text-sm', 'border-none rounded', - (isSelected || isPartOfRange) && (!isOutside || isPreviousMonth) + ( isSelected || isPartOfRange ) && ( ! isOutside || isPreviousMonth ) ? 'bg-background-brand text-text-on-color' : 'bg-transparent hover:bg-button-tertiary-hover', - isRangeMiddle && shouldShowDay && (!isOutside || isPartOfRange) + isRangeMiddle && shouldShowDay && ( ! isOutside || isPartOfRange ) ? 'bg-brand-background-50 text-text-secondary rounded-none' : '', isDisabled ? 'opacity-50 cursor-not-allowed text-text-disabled' : 'cursor-pointer', - (isOutside && !isPartOfRange) || - (!shouldShowDay && isOutside) || - (isOutside && !isPreviousMonth) + ( isOutside && ! isPartOfRange ) || + ( ! shouldShowDay && isOutside ) || + ( isOutside && ! isPreviousMonth ) ? disabledOutsideClass : '' ); - const handleHover = (event: React.MouseEvent) => { - if (typeof customDayProps.onMouseEnter === 'function') { - customDayProps.onMouseEnter(event); + const handleHover = ( event: React.MouseEvent ) => { + if ( typeof customDayProps.onMouseEnter === 'function' ) { + customDayProps.onMouseEnter( event ); } - event.currentTarget.setAttribute('data-hover', 'true'); + event.currentTarget.setAttribute( 'data-hover', 'true' ); }; - const handleLeave = (event: React.MouseEvent) => { - if (typeof customDayProps.onMouseLeave === 'function') { - customDayProps.onMouseLeave(event); + const handleLeave = ( event: React.MouseEvent ) => { + if ( typeof customDayProps.onMouseLeave === 'function' ) { + customDayProps.onMouseLeave( event ); } - event.currentTarget.setAttribute('data-hover', 'false'); + event.currentTarget.setAttribute( 'data-hover', 'false' ); }; - const handleClick = (event: React.MouseEvent) => { - if (typeof customDayProps.onClick === 'function') { - customDayProps.onClick(event); + const handleClick = ( event: React.MouseEvent ) => { + if ( typeof customDayProps.onClick === 'function' ) { + customDayProps.onClick( event ); } }; - console.log('isSelected', isSelected); - return ( ); }; @@ -424,20 +422,20 @@ const DatePickerComponent = ({ const CustomMonths = ( monthGridProps: MonthGridProps ) => { return (
- {( + { ( monthGridProps as { children: React.ReactElement[]; } - ).children[1].props.children.map( - (month: React.ReactElement, index: number) => ( + ).children[ 1 ].props.children.map( + ( month: React.ReactElement, index: number ) => (
- {month} + { month }
) - )} + ) }
); }; @@ -446,36 +444,36 @@ const DatePickerComponent = ({ selectedDate: Date | TDateRange | Date[], trigger: Date ) => { - if (mode === 'range') { + if ( mode === 'range' ) { const currentSelectedValue = selectedDates as TDateRange; if ( - (currentSelectedValue?.from && currentSelectedValue?.to) || - (!currentSelectedValue?.from && !currentSelectedValue?.to) + ( currentSelectedValue?.from && currentSelectedValue?.to ) || + ( ! currentSelectedValue?.from && ! currentSelectedValue?.to ) ) { - setSelectedDates({ from: trigger, to: undefined }); + setSelectedDates( { from: trigger, to: undefined } ); return; } - setSelectedDates(selectedDate); - } else if (mode === 'multiple') { + setSelectedDates( selectedDate ); + } else if ( mode === 'multiple' ) { if ( - (selectedDates as Date[])!.some( - (date) => - format(date, 'yyyy-MM-dd') === - format(trigger, 'yyyy-MM-dd') + ( selectedDates as Date[] )!.some( + ( date ) => + format( date, 'yyyy-MM-dd' ) === + format( trigger, 'yyyy-MM-dd' ) ) ) { setSelectedDates( - (selectedDates as Date[])!.filter( - (date) => - format(date, 'yyyy-MM-dd') !== - format(trigger, 'yyyy-MM-dd') + ( selectedDates as Date[] )!.filter( + ( date ) => + format( date, 'yyyy-MM-dd' ) !== + format( trigger, 'yyyy-MM-dd' ) ) ); } else { - setSelectedDates([...(selectedDates as Date[]), trigger]); + setSelectedDates( [ ...( selectedDates as Date[] ), trigger ] ); } - } else if (mode === 'single') { - setSelectedDates(selectedDate); + } else if ( mode === 'single' ) { + setSelectedDates( selectedDate ); } }; @@ -497,26 +495,26 @@ const DatePickerComponent = ({ return ( <> - {/* @ts-ignore */} + { /* @ts-expect-error: Type mismatch due to mode type. */ } { - if (mode === 'range') { + mode={ mode } + selected={ ( () => { + if ( mode === 'range' ) { return selectedDates as TDateRange; } - if (mode === 'multiple') { + if ( mode === 'multiple' ) { return selectedDates as Date[]; } return selectedDates as Date | undefined; - })()} - onSelect={handleSelect} + } )() } + onSelect={ handleSelect } hideNavigation captionLayout="label" - className={cn(outerClassName)} // Using renamed className - formatters={{ + className={ cn( outerClassName ) } // Using renamed className + formatters={ { formatWeekdayName, - }} - classNames={{ + } } + classNames={ { months: monthsClassName, month: 'flex flex-col p-2 gap-1 text-center w-full', caption: 'relative flex justify-center items-center', @@ -527,84 +525,84 @@ const DatePickerComponent = ({ row: 'flex w-full mt-2', cell: 'h-10 w-10 text-center text-sm p-0 relative', ...classNames, - }} - numberOfMonths={numberOfMonths} - components={{ + } } + numberOfMonths={ numberOfMonths } + components={ { MonthCaption: CustomMonthCaption as unknown as CustomComponents['MonthCaption'], DayButton: CustomDayButton as unknown as CustomComponents['DayButton'], - Day: (singleDayProps) => { + Day: ( singleDayProps ) => { return (
); }, Weekdays: () => <>, - Week: (weekProps: any) => { + Week: ( weekProps ) => { return (
- {weekProps.children} + { weekProps.children }
); }, - Months: (monthsProps) => ( + Months: ( monthsProps ) => ( <>
- {( + { ( monthsProps as { children: React.ReactElement[]; } - )?.children?.map((months, monthIndex) => { - if (!months) { + )?.children?.map( ( months, monthIndex ) => { + if ( ! months ) { return null; } return ( - - {( + + { ( months as unknown as React.ReactElement[] - ).map((month, innerMonthIndex) => ( + ).map( ( month, innerMonthIndex ) => ( - {innerMonthIndex > 0 && ( + { innerMonthIndex > 0 && (
- )} - {month} + ) } + { month }
- ))} + ) ) }
); - })} + } ) }
), - MonthGrid: (monthGridProps) => - !showMonthSelect && !showYearSelect ? ( - + MonthGrid: ( monthGridProps ) => + ! showMonthSelect && ! showYearSelect ? ( + ) : ( <> ), - }} - {...(mode === 'range' && { required: true })} - {...props} - onDayMouseEnter={(_, __, event) => { - if (mode !== 'range') { + } } + { ...( mode === 'range' && { required: true } ) } + { ...props } + onDayMouseEnter={ ( _, __, event ) => { + if ( mode !== 'range' ) { return; } // if more then 1 selected then no need of hover effect @@ -612,16 +610,16 @@ const DatePickerComponent = ({ // Reset data-hover if more then 1 selected or if none are selected if ( - (selected?.from && selected?.to) || - (!selected?.from && !selected?.to) + ( selected?.from && selected?.to ) || + ( ! selected?.from && ! selected?.to ) ) { const resetButtons = Array.from( - document.querySelectorAll('[data-hover]') + document.querySelectorAll( '[data-hover]' ) ); - resetButtons.forEach((item: Element) => { - item.setAttribute('data-hover', 'false'); - }); + resetButtons.forEach( ( item: Element ) => { + item.setAttribute( 'data-hover', 'false' ); + } ); return; } @@ -631,45 +629,45 @@ const DatePickerComponent = ({ // Find the closest ancestor container element const datesContainer = currentButton.closest( '.bsf-force-ui-month-weeks' - ) as any; + ) as Element; // Find all buttons within the container element const buttons: HTMLButtonElement[] = Array.from( - datesContainer.querySelectorAll('button') + datesContainer.querySelectorAll( 'button' ) ); // Find the index of the current button in the buttons array - const currentIndex = buttons.indexOf(currentButton); + const currentIndex = buttons.indexOf( currentButton ); - // Find the index of the button with aria-selected="true" + // Find the index of the button with data-selected="true" const selectedIndex = buttons.findIndex( - (button: any) => - button.getAttribute('aria-selected') === 'true' + ( 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); + const start = Math.min( currentIndex, selectedIndex ); + const end = Math.max( currentIndex, selectedIndex ); - // Select the buttons between the current button and the button with aria-selected="true" (inclusive) + // Select the buttons between the current button and the button with data-selected="true" (inclusive) // eslint-disable-next-line no-plusplus - for (let i = start; i <= end; i++) { - if (!buttons[i]?.disabled) { - selectedButtons.push(buttons[i]); + for ( let i = start; i <= end; i++ ) { + if ( ! buttons[ i ]?.disabled ) { + selectedButtons.push( buttons[ i ] ); } } - buttons.forEach((item: HTMLButtonElement) => { + 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' + selectedButtons.includes( item ) ? 'true' : 'false' ); - }); - }} + } ); + } } /> ); diff --git a/src/components/datepicker/datepicker.tsx b/src/components/datepicker/datepicker.tsx index cceb54f9..90f82e45 100644 --- a/src/components/datepicker/datepicker.tsx +++ b/src/components/datepicker/datepicker.tsx @@ -75,7 +75,9 @@ const DatePicker = ( { return getDefaultSelectedValue( selectionType ); } ); - const handleSelect = ( selectedDate: Date | Date[] | TDateRange | undefined ) => { + const handleSelect = ( + selectedDate: Date | Date[] | TDateRange | undefined + ) => { setSelectedDates( selectedDate ); if ( onDateSelect ) { onDateSelect( selectedDate ); @@ -129,7 +131,9 @@ const DatePicker = ( { const handleCancelClick = () => { setSelectedDates( - selectionType === 'multiple' ? [] : { from: undefined, to: undefined } + selectionType === 'multiple' + ? [] + : { from: undefined, to: undefined } ); if ( onCancel ) { onCancel(); From f16652f70a2d92327bb60296c1ce283bfb94a26d Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 13:18:50 +0600 Subject: [PATCH 09/17] Set required to false --- src/components/datepicker/datepicker-component.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index 5d853f28..29335387 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -599,7 +599,7 @@ const DatePickerComponent = ( { <> ), } } - { ...( mode === 'range' && { required: true } ) } + { ...( mode === 'range' && { required: false } ) } { ...props } onDayMouseEnter={ ( _, __, event ) => { if ( mode !== 'range' ) { From 3ad12e18e79892cee988d409f62cff0d8e2956f9 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 13:29:05 +0600 Subject: [PATCH 10/17] imprv: Range selection behaviour --- .../datepicker/datepicker-component.tsx | 62 +++++++++++++++---- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index 29335387..bbc040eb 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -5,8 +5,9 @@ import { useDayPicker, type MonthGridProps, type CustomComponents, + type OnSelectHandler, } from 'react-day-picker'; -import { format, subMonths } from 'date-fns'; +import { format, isEqual, subMonths } from 'date-fns'; import { cn } from '@/utilities/functions'; import Button from '../button'; import { currentTimeDot, formatWeekdayName, generateYearRange } from './utils'; @@ -440,20 +441,42 @@ const DatePickerComponent = ( { ); }; - const handleSelect = ( - selectedDate: Date | TDateRange | Date[], - trigger: Date - ) => { + const handleSelect: OnSelectHandler< + Date | Date[] | TDateRange | undefined + > = ( selectedDate, trigger ) => { if ( mode === 'range' ) { const currentSelectedValue = selectedDates as TDateRange; if ( - ( currentSelectedValue?.from && currentSelectedValue?.to ) || - ( ! currentSelectedValue?.from && ! currentSelectedValue?.to ) + ( ! currentSelectedValue?.from && ! currentSelectedValue?.to ) || + ( currentSelectedValue?.from && currentSelectedValue?.to ) ) { + 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; } - setSelectedDates( selectedDate ); + if ( currentSelectedValue?.from && ! currentSelectedValue?.to ) { + if ( trigger < currentSelectedValue.from ) { + setSelectedDates( { + from: trigger, + to: currentSelectedValue.from, + } ); + return; + } + setSelectedDates( { + from: currentSelectedValue.from, + to: trigger, + } ); + return; + } + setSelectedDates( selectedDate as TDateRange ); } else if ( mode === 'multiple' ) { if ( ( selectedDates as Date[] )!.some( @@ -473,7 +496,7 @@ const DatePickerComponent = ( { setSelectedDates( [ ...( selectedDates as Date[] ), trigger ] ); } } else if ( mode === 'single' ) { - setSelectedDates( selectedDate ); + setSelectedDates( selectedDate as Date ); } }; @@ -495,7 +518,6 @@ const DatePickerComponent = ( { return ( <> - { /* @ts-expect-error: Type mismatch due to mode type. */ } { @@ -533,14 +555,27 @@ const DatePickerComponent = ( { DayButton: CustomDayButton as unknown as CustomComponents['DayButton'], Day: ( singleDayProps ) => { + 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: () => <>, @@ -599,7 +634,8 @@ const DatePickerComponent = ( { <> ), } } - { ...( mode === 'range' && { required: false } ) } + /* eslint-disable @typescript-eslint/no-explicit-any */ + { ...( ( mode === 'range' ? { required: false } : {} ) as any ) } { ...props } onDayMouseEnter={ ( _, __, event ) => { if ( mode !== 'range' ) { From 881d828a01dce440cff3f4f987bc6edfcd969ff6 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 15:37:08 +0600 Subject: [PATCH 11/17] Added disable prop --- .../datepicker/datepicker-component.tsx | 4 ++++ src/components/datepicker/datepicker.tsx | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index bbc040eb..12c160c5 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -56,6 +56,8 @@ export interface DatePickerProps { numberOfMonths?: number; /** Footer content to be displayed at the bottom of the date picker. */ footer?: ReactNode; + /** Additional props to be passed to the date picker. */ + [key: string]: unknown; } interface CustomMonthCaptionProps { @@ -97,6 +99,7 @@ const DatePickerComponent = ( { variant = 'normal', alignment = 'horizontal', numberOfMonths, + disabled, ...props }: DatePickerProps ) => { // check footer is a valid component. @@ -704,6 +707,7 @@ const DatePickerComponent = ( { ); } ); } } + disabled={ disabled } /> ); diff --git a/src/components/datepicker/datepicker.tsx b/src/components/datepicker/datepicker.tsx index 90f82e45..2f17d4ca 100644 --- a/src/components/datepicker/datepicker.tsx +++ b/src/components/datepicker/datepicker.tsx @@ -12,6 +12,7 @@ import { subMonths, } 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. */ @@ -36,6 +37,16 @@ export interface DatePickerProps { isFooter?: boolean; /** Selected date value. */ 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,6 +61,7 @@ const DatePicker = ( { showOutsideDays = true, isFooter = true, selected, + disabled, ...props }: DatePickerProps ) => { const [ selectedDates, setSelectedDates ] = useState< @@ -175,6 +187,7 @@ const DatePicker = ( {
) } + disabled={ disabled } /> ); } @@ -204,6 +217,7 @@ const DatePicker = ( {
} + disabled={ disabled } { ...props } /> ); @@ -250,6 +264,7 @@ const DatePicker = ( {
} + disabled={ disabled } /> ); From 23a27a5161454c5d861868dfaa378e8ab7afc15f Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 15:47:11 +0600 Subject: [PATCH 12/17] fix: Hover effect not working properly for dualdate and presets variant --- src/components/datepicker/datepicker-component.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index 12c160c5..4b9fa8c5 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -667,7 +667,7 @@ const DatePickerComponent = ( { // Find the closest ancestor container element const datesContainer = currentButton.closest( - '.bsf-force-ui-month-weeks' + '.bsf-force-ui-date-picker-month' ) as Element; // Find all buttons within the container element From d91773a6317279479a1d356a243bba033e2a4586 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sat, 4 Jan 2025 23:46:20 +0600 Subject: [PATCH 13/17] fix: Applying invalid color class names --- .../datepicker/datepicker-component.tsx | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index 4b9fa8c5..943dc450 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -358,10 +358,10 @@ const DatePickerComponent = ( { const buttonClasses = cn( 'h-10 w-10 flex items-center justify-center transition text-text-secondary relative text-sm', 'border-none rounded', - ( isSelected || isPartOfRange ) && ( ! isOutside || isPreviousMonth ) + ( isSelected || isPartOfRange ) && ( ! isOutside ) ? 'bg-background-brand text-text-on-color' : 'bg-transparent hover:bg-button-tertiary-hover', - isRangeMiddle && shouldShowDay && ( ! isOutside || isPartOfRange ) + isRangeMiddle && shouldShowDay && ( ! isOutside ) ? 'bg-brand-background-50 text-text-secondary rounded-none' : '', isDisabled @@ -369,7 +369,7 @@ const DatePickerComponent = ( { : 'cursor-pointer', ( isOutside && ! isPartOfRange ) || ( ! shouldShowDay && isOutside ) || - ( isOutside && ! isPreviousMonth ) + ( isOutside && ! isPreviousMonth ) || isOutside ? disabledOutsideClass : '' ); @@ -412,6 +412,7 @@ const DatePickerComponent = ( { onMouseLeave={ handleLeave } aria-label={ format( day.date, 'EEEE, MMMM do, yyyy' ) } data-selected={ isSelected } + data-day={ format( day.date, 'yyyy-MM-dd' ) } > { ( ! showOutsideDates || ( isPartOfRange && shouldShowDay ) ) && customDayProps.children } @@ -666,9 +667,22 @@ const DatePickerComponent = ( { const currentButton = event.target as HTMLButtonElement; // Find the closest ancestor container element - const datesContainer = currentButton.closest( - '.bsf-force-ui-date-picker-month' - ) as 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( @@ -692,7 +706,6 @@ const DatePickerComponent = ( { const end = Math.max( currentIndex, selectedIndex ); // Select the buttons between the current button and the button with data-selected="true" (inclusive) - // eslint-disable-next-line no-plusplus for ( let i = start; i <= end; i++ ) { if ( ! buttons[ i ]?.disabled ) { selectedButtons.push( buttons[ i ] ); From 848c238eaeab40a4462a06d0644b30ca124f5c62 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sun, 5 Jan 2025 00:27:16 +0600 Subject: [PATCH 14/17] fix: Hover logic for previous months --- .../datepicker/datepicker-component.tsx | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index 943dc450..968dd3ab 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -7,7 +7,7 @@ import { type CustomComponents, type OnSelectHandler, } from 'react-day-picker'; -import { format, isEqual, subMonths } from 'date-fns'; +import { format, isAfter, isBefore, isEqual, subMonths } from 'date-fns'; import { cn } from '@/utilities/functions'; import Button from '../button'; import { currentTimeDot, formatWeekdayName, generateYearRange } from './utils'; @@ -416,7 +416,6 @@ const DatePickerComponent = ( { > { ( ! showOutsideDates || ( isPartOfRange && shouldShowDay ) ) && customDayProps.children } - { /* { customDayProps.children } */ } { isToday && shouldShowDay && ( ) } @@ -665,6 +664,17 @@ const DatePickerComponent = ( { // 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 @@ -689,6 +699,28 @@ const DatePickerComponent = ( { 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 ); From c3e68f0b27418eb2393fbafeffc141cb27e28f6b Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Sun, 5 Jan 2025 00:32:51 +0600 Subject: [PATCH 15/17] chore: Lint --- src/components/datepicker/datepicker-component.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/datepicker/datepicker-component.tsx b/src/components/datepicker/datepicker-component.tsx index 968dd3ab..58319a9a 100644 --- a/src/components/datepicker/datepicker-component.tsx +++ b/src/components/datepicker/datepicker-component.tsx @@ -358,10 +358,10 @@ const DatePickerComponent = ( { const buttonClasses = cn( 'h-10 w-10 flex items-center justify-center transition text-text-secondary relative text-sm', 'border-none rounded', - ( isSelected || isPartOfRange ) && ( ! isOutside ) + ( isSelected || isPartOfRange ) && ! isOutside ? 'bg-background-brand text-text-on-color' : 'bg-transparent hover:bg-button-tertiary-hover', - isRangeMiddle && shouldShowDay && ( ! isOutside ) + isRangeMiddle && shouldShowDay && ! isOutside ? 'bg-brand-background-50 text-text-secondary rounded-none' : '', isDisabled @@ -369,7 +369,8 @@ const DatePickerComponent = ( { : 'cursor-pointer', ( isOutside && ! isPartOfRange ) || ( ! shouldShowDay && isOutside ) || - ( isOutside && ! isPreviousMonth ) || isOutside + ( isOutside && ! isPreviousMonth ) || + isOutside ? disabledOutsideClass : '' ); @@ -665,7 +666,9 @@ const DatePickerComponent = ( { // 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! ); + const currentButtonDate = new Date( + currentButton.dataset.day! + ); // Check if the current button is before or after the selected range const isCurrentButtonBefore = isBefore( currentButtonDate, From 10d7e3ab8a852bca6af2b49d58324cebae6dfa7a Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Mon, 6 Jan 2025 11:20:26 +0600 Subject: [PATCH 16/17] Updated date picker presets --- src/components/datepicker/datepicker.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/datepicker/datepicker.tsx b/src/components/datepicker/datepicker.tsx index 2f17d4ca..e2b65426 100644 --- a/src/components/datepicker/datepicker.tsx +++ b/src/components/datepicker/datepicker.tsx @@ -8,8 +8,7 @@ import { endOfWeek, startOfMonth, endOfMonth, - subWeeks, - subMonths, + subDays, } from 'date-fns'; import { getDefaultSelectedValue } from './utils'; import { type PropsBase } from 'react-day-picker'; @@ -113,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(), }, }, { @@ -127,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(), }, }, ]; From 3961e0fe2810a47ef8ea544f229ffc7cf93a1a86 Mon Sep 17 00:00:00 2001 From: Jaied Al Sabid <87969327+jaieds@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:19:05 +0600 Subject: [PATCH 17/17] Updated the change log --- README.md | 4 ++-- changelog.txt | 3 +++ package.json | 2 +- version.json | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 426fc923..9bed5786 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Using Force UI as a dependency in package.json - ```json "dependencies": { - "@bsf/force-ui": "git+https://github.com/brainstormforce/force-ui#1.3.4" + "@bsf/force-ui": "git+https://github.com/brainstormforce/force-ui#1.3.5" } ``` @@ -28,7 +28,7 @@ npm install Or you can directly run the following command to install the package - ```bash -npm i -S @bsf/force-ui@git+https://github.com/brainstormforce/force-ui.git#1.3.4 +npm i -S @bsf/force-ui@git+https://github.com/brainstormforce/force-ui.git#1.3.5 ```
diff --git a/changelog.txt b/changelog.txt index 1052b23c..557363ee 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +Version 1.3.5 - 6th January, 2025 +- Improvement: Display a light blue color on hover when selecting a date range. + Version 1.3.4 - 31th December, 2024 - Improvement - Enhanced the UI of the Table and Line chart component for responsive design. - Improvement - Added option group to the Select component. diff --git a/package.json b/package.json index ca50f9b9..480f2431 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bsf/force-ui", - "version": "1.3.4", + "version": "1.3.5", "description": "Library of components for the BSF project", "main": "./dist/force-ui.js", "module": "./dist/force-ui.js", 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" }