Skip to content

Commit

Permalink
feat(RangeDatePickerV2): adjusted selecting
Browse files Browse the repository at this point in the history
  • Loading branch information
marcinsawicki committed Jan 8, 2025
1 parent b8099d9 commit 91b70d3
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,7 @@ $base-class: 'date-picker';
}
}
}

.#{$base-class}__range-start.#{$base-class}__range-end {
border-radius: var(--radius-3);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { ReactElement, useCallback, useEffect, useMemo, useRef } from 'react';

import { isAfter, isSameDay, differenceInCalendarDays } from 'date-fns';
import {
isAfter,
isSameDay,
differenceInCalendarDays,
isBefore,
} from 'date-fns';

import {
calculateDatePickerMonth,
Expand All @@ -27,6 +32,7 @@ export const RangeDatePicker = ({
onChange,
onRangeSelect,
onSelect,
onCustomTempDateRangeChange,
children,
}: IRangeDatePickerProps): ReactElement => {
const prevSelectedItem = useRef<string | null>(
Expand Down Expand Up @@ -150,6 +156,13 @@ export const RangeDatePicker = ({
onChange?.(optionsHash[selectedItem]);
}, [onChange, state.selectedItem, options]);

useEffect(() => {
onCustomTempDateRangeChange?.({
from: state.temporaryFrom,
to: state.temporaryTo,
});
}, [state.temporaryFrom, state.temporaryTo]);

const handleOnSelect = useCallback(() => {
const { from, to } = state;

Expand All @@ -163,15 +176,27 @@ export const RangeDatePicker = ({

const handleDayMouseEnter = useCallback(
(day: Date) => {
const { from, to } = state;
const isInRange = toMonth
? differenceInCalendarDays(toMonth, day) >= 0
: true;

if (!isSelectingFirstDay(state.from, state.to) && isInRange) {
dispatch({
type: RangeDatePickerAction.NEW_TEMPORARY_TO_VALUE,
payload: { date: day },
});
if (!isSelectingFirstDay(from, to) && isInRange) {
if (from && isBefore(day, from)) {
dispatch({
type: RangeDatePickerAction.NEW_TEMPORARY_FROM_VALUE,
payload: { date: day },
});
dispatch({
type: RangeDatePickerAction.NEW_TEMPORARY_TO_VALUE,
payload: { date: from },
});
} else {
dispatch({
type: RangeDatePickerAction.NEW_TEMPORARY_TO_VALUE,
payload: { date: day },
});
}
}
},
[toMonth, state.from, state.to]
Expand All @@ -190,6 +215,10 @@ export const RangeDatePicker = ({
type: RangeDatePickerAction.SELECT_FIRST_DAY,
payload: { date: day },
});
dispatch({
type: RangeDatePickerAction.NEW_TEMPORARY_FROM_VALUE,
payload: { date: day },
});
} else if (
(from && isSameDay(day, from)) ||
(from && isAfter(day, from))
Expand Down Expand Up @@ -245,6 +274,7 @@ export const RangeDatePicker = ({
currentMonth,
from,
selectedItem,
temporaryFrom,
temporaryTo,
to,
customTempFrom,
Expand All @@ -255,8 +285,8 @@ export const RangeDatePicker = ({
}, [options, selectedItem]);

const selectedDays = useMemo(() => {
return { from, to: temporaryTo };
}, [from, temporaryTo]);
return { from: temporaryFrom || from, to: temporaryTo };
}, [from, temporaryFrom, temporaryTo]);

const customSelectedDays = useMemo(() => {
if (!customTempFrom || !customTempTo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ $base-class: 'range-date-picker';

&__trigger {
display: flex;
position: relative;
transition: border-color var(--transition-duration-fast-2) ease-in-out;
border: 1px solid var(--border-basic-primary);
border-radius: var(--radius-3);
Expand All @@ -17,17 +18,30 @@ $base-class: 'range-date-picker';
&:hover {
border-color: var(--border-basic-hover);
cursor: pointer;
}

&__label {
flex: 1;
margin: 0;
.#{$base-class}__trigger__right-node--active {
.#{$base-class}__trigger__right-node__icon {
display: none;
}

.#{$base-class}__trigger__right-node__button {
display: inline-flex;
}
}
}

&__right-node {
display: flex;
align-items: center;
margin-left: var(--spacing-2);

&__button {
display: none;
position: absolute;
top: auto;
right: 6px;
bottom: auto;
}
}

&--active,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { FC, useState, useCallback } from 'react';

import { Check, Calendar } from '@livechat/design-system-icons';
import { Check, Calendar, Close } from '@livechat/design-system-icons';
import cx from 'clsx';
import { format, startOfToday } from 'date-fns';
import { startOfToday } from 'date-fns';
import { DateRange } from 'react-day-picker';

import { Button } from '../../Button';
import { DatePicker, RangeDatePicker } from '../../DatePicker';
import { Icon } from '../../Icon';
import { Popover } from '../../Popover';
import { Text } from '../../Typography';

import { RangeDatePickerV2Label } from './components/RangeDatePickerV2Label';
import { OPTIONS } from './helpers';
import { IRangeDatePickerV2Props, RANGE_DATE_PICKER_OPTION_ID } from './types';

Expand All @@ -22,21 +23,35 @@ const todayDate = startOfToday();
export const RangeDatePickerV2: FC<IRangeDatePickerV2Props> = ({
selectedId,
onRangeSelect,
...props
}) => {
const [date, setDate] = useState<DateRange | undefined>();
const { initialFromDate, initialToDate, ...restProps } = props;
const [date, setDate] = useState<DateRange | undefined>({
from: initialFromDate,
to: initialToDate,
});
const [tempDate, setTempDate] = useState<DateRange | undefined>();
const [isVisible, setIsVisible] = useState(false);
const [currentSelectedId, setCurrentSelectedId] = useState<
RANGE_DATE_PICKER_OPTION_ID | undefined
>(selectedId);

const handleOnOptionMouseEnter = useCallback(({ from, to }: DateRange) => {
setDate({ from, to });
}, []);
const handleRangeSelecting = (range: DateRange | null) => {
setDate(range ? range : undefined);
onRangeSelect(range);
setTempDate(undefined);
setCurrentSelectedId(undefined);
};

const handleClosing = () => {
setTempDate(undefined);
setIsVisible(false);
};

const handleOnOptionClick = useCallback(
({ from, to }: DateRange, id: RANGE_DATE_PICKER_OPTION_ID) => {
onRangeSelect({ from, to });
setIsVisible(false);
handleRangeSelecting({ from, to });
handleClosing();

if (!selectedId) {
setCurrentSelectedId(id);
Expand All @@ -50,28 +65,16 @@ export const RangeDatePickerV2: FC<IRangeDatePickerV2Props> = ({
return;
}

setDate(range);
onRangeSelect(range);
setIsVisible(false);
handleRangeSelecting(range);
handleClosing();
}, []);

const getTriggerLavel = useCallback(() => {
if (date?.from && date?.to) {
return `${format(date.from, 'dd-MM-yy')} - ${format(
date.to,
'dd-MM-yy'
)}`;
}

return 'DD-MM-YYYY - DD-MM-YYYY';
}, [date]);

return (
<div className={styles[baseClass]}>
<Popover
isVisible={isVisible}
onOpen={() => setIsVisible(true)}
onClose={() => setIsVisible(false)}
onClose={handleClosing}
className={styles[`${baseClass}__popover`]}
placement="bottom-start"
triggerRenderer={() => (
Expand All @@ -80,11 +83,28 @@ export const RangeDatePickerV2: FC<IRangeDatePickerV2Props> = ({
[styles[`${baseClass}__trigger--active`]]: isVisible,
})}
>
<Text className={styles[`${baseClass}__trigger__label`]}>
{getTriggerLavel()}
</Text>
<div className={styles[`${baseClass}__trigger__right-node`]}>
<Icon source={Calendar} />
<RangeDatePickerV2Label tempDate={tempDate} date={date} />
<div
className={cx(
styles[`${baseClass}__trigger__right-node`],
date && styles[`${baseClass}__trigger__right-node--active`]
)}
>
<Icon
source={Calendar}
className={styles[`${baseClass}__trigger__right-node__icon`]}
/>
{date && (
<Button
size="xcompact"
kind="plain"
icon={<Icon size="xsmall" source={Close} />}
className={
styles[`${baseClass}__trigger__right-node__button`]
}
onClick={() => handleRangeSelecting(null)}
/>
)}
</div>
</div>
)}
Expand All @@ -104,8 +124,8 @@ export const RangeDatePickerV2: FC<IRangeDatePickerV2Props> = ({
}
)}
onClick={() => handleOnOptionClick(option.value, option.id)}
onMouseEnter={() => handleOnOptionMouseEnter(option.value)}
onMouseLeave={() => setDate(undefined)}
onMouseEnter={() => setTempDate(option.value)}
onMouseLeave={() => setTempDate(undefined)}
>
{option.label}
{currentSelectedId === option.id && (
Expand All @@ -125,9 +145,11 @@ export const RangeDatePickerV2: FC<IRangeDatePickerV2Props> = ({
<div className={styles[`${baseClass}__date-picker__callendar`]}>
<RangeDatePicker
onRangeSelect={handleOnRangeSelect}
customTempFromDate={date?.from}
customTempToDate={date?.to}
customTempFromDate={tempDate?.from || date?.from}
customTempToDate={tempDate?.to || date?.to}
onCustomTempDateRangeChange={setTempDate}
initialToDate={todayDate}
{...restProps}
>
{({ datepicker }) => <DatePicker {...datepicker} />}
</RangeDatePicker>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
$base-class: 'range-date-picker-v2-label';

.#{$base-class} {
flex: 1;
margin: 0;
color: var(--content-basic-secondary);

&--selected {
color: var(--content-basic-primary);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { FC } from 'react';

import cx from 'clsx';
import { format } from 'date-fns';
import { DateRange } from 'react-day-picker';

import { Text } from '../../../Typography';

import styles from './RangeDatePickerV2Label.module.scss';

const baseClass = 'range-date-picker-v2-label';

interface IRangeDatePickerV2LabelProps {
tempDate?: DateRange;
date?: DateRange;
}

export const RangeDatePickerV2Label: FC<IRangeDatePickerV2LabelProps> = ({
tempDate,
date,
}) => {
const getStartDate = () => {
if (tempDate?.from) {
return `${format(tempDate.from, 'dd-MM-yy')}`;
}

if (date?.from) {
return `${format(date.from, 'dd-MM-yy')}`;
}

return 'Start date';
};

const getEndDate = () => {
if (tempDate?.to) {
return `${format(tempDate.to, 'dd-MM-yy')}`;
}

if (date?.to) {
return `${format(date.to, 'dd-MM-yy')}`;
}

return 'End date';
};

return (
<Text
className={cx(styles[`${baseClass}`], {
[styles[`${baseClass}--selected`]]: date?.from && date?.to,
})}
>
{getStartDate()} - {getEndDate()}
</Text>
);
};
8 changes: 8 additions & 0 deletions packages/react-components/src/components/DatePicker/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ export const useRangeDatePickerState = (
temporaryTo: void 0,
currentMonth: initialCurrentMonth,
};
case RangeDatePickerAction.NEW_TEMPORARY_FROM_VALUE:
return {
...state,
temporaryFrom: action.payload.date,
};
case RangeDatePickerAction.NEW_TEMPORARY_TO_VALUE:
return {
...state,
Expand All @@ -59,7 +64,10 @@ export const useRangeDatePickerState = (
...state,
from: action.payload.date,
to: void 0,
temporaryFrom: void 0,
temporaryTo: void 0,
customTempFrom: void 0,
customTempTo: void 0,
};
case RangeDatePickerAction.SELECT_SECOND_DAY_AS_FROM:
return {
Expand Down
Loading

0 comments on commit 91b70d3

Please sign in to comment.