Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(RangeDatePickerV2): new variant of component #1464

Merged
merged 18 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2ebf49f
feat(DatePicker): lib update and related changes
marcinsawicki Dec 17, 2024
1abb2fe
feat(DatePicker): fixes
marcinsawicki Dec 18, 2024
ae83adb
feat(DatePicker): fixes
marcinsawicki Dec 19, 2024
b8099d9
feat(RangeDatePickerV2): new variant of component
marcinsawicki Jan 7, 2025
91b70d3
feat(RangeDatePickerV2): adjusted selecting
marcinsawicki Jan 8, 2025
5cbadc5
feat(RangeDatePickerV2): fixes and unit test
marcinsawicki Jan 9, 2025
e35bcb9
feat(RangeDatePickerV2): moved to seperate folder to prevent circual …
marcinsawicki Jan 9, 2025
59192b9
feat(RangeDatePickerV2): merge with main and conflicts resolve
marcinsawicki Jan 9, 2025
69cdb4d
feat(RangeDatePickerV2): keyboard control
marcinsawicki Jan 10, 2025
20a20bb
feat(RangeDatePickerV2): changes after review
marcinsawicki Jan 10, 2025
a9819f6
feat(RangeDatePickerV2): test fix
marcinsawicki Jan 10, 2025
ddb2971
feat(RangeDatePickerV2): keyboard control fix
marcinsawicki Jan 10, 2025
e5cdf82
feat(RangeDatePickerV2): merge with main
marcinsawicki Jan 14, 2025
ebac4b9
feat(RangeDatePickerV2): set the default month if date range is selected
marcinsawicki Jan 14, 2025
c3411e6
feat(RangeDatePickerV2): change callendars number on smaller view
marcinsawicki Jan 16, 2025
66f9c5f
Merge branch 'main' into feature/range-date-picker-v2
marcinsawicki Jan 16, 2025
c6d0afc
feat(RangeDatePickerV2): tests update
marcinsawicki Jan 16, 2025
1ae0e3e
Merge branch 'feature/range-date-picker-v2' of github.com:livechat/de…
marcinsawicki Jan 16, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ $base-class: 'date-picker';
background-color: var(--surface-primary-hover);
cursor: pointer;
}

button {
font-size: 13px;
}
}

&__day-button {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,42 @@ export const RangeDatePicker = ({
initialSelectedItemKey,
initialFromDate,
initialToDate,
customTempFromDate,
customTempToDate,
toMonth,
today,
onChange,
onRangeSelect,
onSelect,
onCustomTempDateRangeChange,
children,
}: IRangeDatePickerProps): ReactElement => {
const prevSelectedItem = useRef<string | null>(
initialSelectedItemKey || null
);
const [state, dispatch] = useRangeDatePickerState({
options,
initialSelectedItemKey,
initialFromDate,
initialToDate,
toMonth,
onChange,
options,
initialSelectedItemKey,
customTempFromDate,
customTempToDate,
children,
});
onChange,
onRangeSelect,
} as IRangeDatePickerProps);

// handle custom temp date range change (range date picker v2)
useEffect(() => {
dispatch({
type: RangeDatePickerAction.SET_CUSTOM_TEMP_RANGE,
payload: {
customTempFrom: customTempFromDate,
customTempTo: customTempToDate,
},
});
}, [customTempFromDate, customTempToDate]);

// handle initialFromDate change
useEffect(() => {
Expand Down Expand Up @@ -94,16 +113,29 @@ export const RangeDatePicker = ({
});
}, [state.from, state.to, state.selectedItem, options, onChange]);

// call onRangeSelect when valid dates selected in new date range picker v2
useEffect(() => {
const { from, to } = state;
if (!(from && to) || !onRangeSelect) {
return;
}

onRangeSelect?.({
from: from,
to: to,
});
}, [state.from, state.to, onRangeSelect]);
Comment on lines +116 to +127
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prevent duplicate range selection callbacks

onRangeSelect could be called multiple times with the same values.

  useEffect(() => {
    const { from, to } = state;
    if (!(from && to) || !onRangeSelect) {
      return;
    }
+   
+   if (prevRange.current?.from === from && prevRange.current?.to === to) {
+     return;
+   }
+   
+   prevRange.current = { from, to };

    onRangeSelect?.({
      from: from,
      to: to,
    });
  }, [state.from, state.to, onRangeSelect]);

Committable suggestion skipped: line range outside the PR's diff.


// handle selected option change
useEffect(() => {
const { selectedItem } = state;

if (selectedItem === prevSelectedItem.current) {
if (selectedItem === prevSelectedItem.current || !options) {
return;
}

if (!selectedItem) {
onChange(null);
onChange?.(null);

return;
}
Expand All @@ -122,9 +154,16 @@ export const RangeDatePicker = ({
{}
);

onChange(optionsHash[selectedItem]);
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 Down Expand Up @@ -240,8 +279,16 @@ export const RangeDatePicker = ({
}, []);

const getRangeDatePickerApi = (): IRangeDatePickerChildrenPayload => {
const { currentMonth, from, selectedItem, temporaryFrom, temporaryTo, to } =
state;
const {
currentMonth,
from,
selectedItem,
temporaryFrom,
temporaryTo,
to,
customTempFrom,
customTempTo,
} = state;
const selectedOption = useMemo(() => {
return getSelectedOption(selectedItem, options);
}, [options, selectedItem]);
Expand All @@ -250,6 +297,14 @@ export const RangeDatePicker = ({
return { from: temporaryFrom || from, to: temporaryTo };
}, [from, temporaryFrom, temporaryTo]);

const customSelectedDays = useMemo(() => {
if (!customTempFrom || !customTempTo) {
return undefined;
}

return { from: customTempFrom, to: customTempTo };
}, [customTempFrom, customTempTo]);

const disabledDays = useMemo(() => {
return toMonth ? { after: toMonth } : void 0;
}, [toMonth]);
Expand All @@ -268,9 +323,10 @@ export const RangeDatePicker = ({
month: currentMonth,
numberOfMonths: 2,
onDayClick: handleDayClick,
selected: selectedDays,
selected: customSelectedDays || selectedDays,
endMonth: toMonth,
disabled: disabledDays,
today,
onDayMouseEnter: handleDayMouseEnter,
onMonthChange: handleMonthChange,
onSelect: handleOnSelect,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ export const calculateDatePickerMonth = (

export const getSelectedOption = (
itemId: string | null,
options: IRangeDatePickerOption[]
options?: IRangeDatePickerOption[]
): IRangeDatePickerOption | undefined => {
const selectedOption = options.find((item) => {
const selectedOption = options?.find((item) => {
return item.id === itemId;
});

Expand Down
11 changes: 11 additions & 0 deletions packages/react-components/src/components/DatePicker/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const useRangeDatePickerState = (
to: undefined,
temporaryTo: undefined,
currentMonth: initialCurrentMonth,
customTempFrom: undefined,
customTempTo: undefined,
};

const initialState = {
Expand Down Expand Up @@ -62,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 Expand Up @@ -92,6 +97,12 @@ export const useRangeDatePickerState = (
...state,
to: action.payload.date,
};
case RangeDatePickerAction.SET_CUSTOM_TEMP_RANGE:
return {
...state,
customTempFrom: action.payload.customTempFrom,
customTempTo: action.payload.customTempTo,
};
marcinsawicki marked this conversation as resolved.
Show resolved Hide resolved
default:
return state;
}
Expand Down
35 changes: 33 additions & 2 deletions packages/react-components/src/components/DatePicker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export enum RangeDatePickerAction {
SELECT_SECOND_DAY_AS_FROM = 'SELECT_SECOND_DAY_AS_FROM',
SELECT_SECOND_DAY_AS_TO = 'SELECT_SECOND_DAY_AS_TO',
CURRENT_MONTH_CHANGE = 'CURRENT_MONTH_CHANGE',
SET_CUSTOM_TEMP_RANGE = 'SET_CUSTOM_TEMP_RANGE',
}

export type IRangeDatePickerReducerAction =
Expand All @@ -49,6 +50,13 @@ export type IRangeDatePickerReducerAction =
payload: {
date?: Date;
};
}
| {
type: RangeDatePickerAction.SET_CUSTOM_TEMP_RANGE;
payload: {
customTempFrom?: Date;
customTempTo?: Date;
};
};

export interface IRangeDatePickerState {
Expand All @@ -58,6 +66,8 @@ export interface IRangeDatePickerState {
temporaryFrom?: Date;
temporaryTo?: Date;
currentMonth: Date;
customTempFrom?: Date;
customTempTo?: Date;
}

export type RangeDatePickerReducer = Reducer<
Expand Down Expand Up @@ -88,13 +98,34 @@ export interface IRangeDatePickerChildrenPayload {
selectedOption?: IRangeDatePickerOption;
}

export interface IRangeDatePickerProps {
export interface IRangeDatePickerV1CoreProps {
onChange: (selected: IRangeDatePickerOption | null) => void;
options: IRangeDatePickerOption[];
initialSelectedItemKey?: string;
onRangeSelect?: never;
onCustomTempDateRangeChange?: never;
customTempFromDate?: never;
customTempToDate?: never;
}

export interface IRangeDatePickerV2CoreProps {
onRangeSelect: (selected: DateRange | null) => void;
onCustomTempDateRangeChange: (date: DateRange | undefined) => void;
customTempFromDate?: Date;
customTempToDate?: Date;
options?: never;
onChange?: never;
initialSelectedItemKey?: never;
}

export interface IRangeDatePickerCoreProps {
today?: Date;
initialFromDate?: Date;
initialToDate?: Date;
toMonth?: Date;
onChange: (selected: IRangeDatePickerOption | null) => void;
onSelect?: (selected: DateRange | null) => void;
children(payload: IRangeDatePickerChildrenPayload): ReactElement;
}

export type IRangeDatePickerProps = IRangeDatePickerCoreProps &
(IRangeDatePickerV1CoreProps | IRangeDatePickerV2CoreProps);
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Canvas, ArgTypes, Meta, Title } from '@storybook/blocks';

import * as RangeDatePickerV2 from './RangeDatePickerV2.stories';

<Meta of={RangeDatePickerV2} />

<Title>RangeDatePickerV2</Title>

[Intro](#Intro) | [Component API](#ComponentAPI)

## Intro <a id="Intro" />

RangeDatePickerV2 is a component that allows you to select a date range using a calendar by selecting the start and end date, or using predefined options. The whole thing is displayed after interacting with a trigger that displays the selected dates, which makes it much easier to use the component in places where every free space counts.

Comment on lines +11 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance documentation with usage guidelines and accessibility info.

Add sections for:

  • Accessibility features and keyboard navigation
  • Best practices and common pitfalls
  • Integration examples with form libraries

<Canvas of={RangeDatePickerV2.Default} sourceState="none" />

#### Example implementation

```jsx
import { RangeDatePickerV2 } from '@livechat/design-system-react-components';

<RangeDatePickerV2
initialFromDate={new Date()}
initialToDate={new Date()}
onRangeSelect={({ from, to }) => {}}
/>
```

## Component API <a id="ComponentAPI" />

<ArgTypes of={RangeDatePickerV2.Default} sort="requiredFirst" />
Loading
Loading