Skip to content

Commit

Permalink
Merge pull request #9907 from CitizenLabDotCo/TAN-2973-replace-old-da…
Browse files Browse the repository at this point in the history
…te-range-picker

TAN-2973 Replace old date range picker
  • Loading branch information
luucvanderzee authored Jan 7, 2025
2 parents 9768fe1 + 5dfc427 commit 24a7778
Show file tree
Hide file tree
Showing 40 changed files with 853 additions and 266 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import styled from 'styled-components';

import useLocale from 'hooks/useLocale';

import { userTimezone } from 'utils/dateUtils';

import { getLocale } from '../../_shared/locales';
import { Props } from '../typings';

Expand Down Expand Up @@ -144,7 +146,6 @@ const Calendar = ({
defaultMonth,
onUpdateRange,
}: Props) => {
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const startMonth = getStartMonth({
startMonth: _startMonth,
selectedRange,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { differenceInDays, addDays } from 'date-fns';

import { DateRange, ClosedDateRange } from '../../typings';
import { DateRange } from 'components/admin/DatePickers/_shared/typings';

import { ClosedDateRange } from '../../typings';

import { rangesValid } from './rangesValid';
import { allAreClosedDateRanges } from './utils';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { addYears } from 'date-fns';

import { DateRange } from '../../typings';
import { DateRange } from 'components/admin/DatePickers/_shared/typings';

interface GetStartMonthProps {
startMonth?: Date;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { addDays, isSameDay } from 'date-fns';

import { ClosedDateRange, DateRange } from '../../typings';
import { DateRange } from 'components/admin/DatePickers/_shared/typings';

import { ClosedDateRange } from '../../typings';

import { rangesValid } from './rangesValid';
import { isClosedDateRange } from './utils';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { differenceInDays } from 'date-fns';

import { DateRange, ClosedDateRange } from '../../typings';
import { DateRange } from 'components/admin/DatePickers/_shared/typings';

import { ClosedDateRange } from '../../typings';

import { allAreClosedDateRanges, isClosedDateRange } from './utils';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DateRange, ClosedDateRange } from '../../typings';
import { DateRange } from '../../../_shared/typings';
import { ClosedDateRange } from '../../typings';

export const isClosedDateRange = (range: DateRange): range is ClosedDateRange =>
!!range.to;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useState } from 'react';

import { DateRange } from '../_shared/typings';

import { patchDisabledRanges } from './patchDisabledRanges';
import { DateRange } from './typings';

import DatePhasePicker from '.';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { useIntl } from 'utils/cl-intl';

import InputContainer from '../_shared/InputContainer';
import sharedMessages from '../_shared/messages';
import { DateRange } from '../_shared/typings';

import messages from './messages';
import { DateRange } from './typings';

interface Props {
selectedRange: Partial<DateRange>;
Expand Down
17 changes: 6 additions & 11 deletions front/app/components/admin/DatePickers/DatePhasePicker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React, { useState } from 'react';

import { Tooltip, Box } from '@citizenlab/cl2-component-library';
import styled from 'styled-components';

import ClickOutside from 'utils/containers/clickOutside';
import ClickOutsideContainer from '../_shared/ClickOutsideContainer';

import Calendar from './Calendar';
import Input from './Input';
Expand All @@ -12,13 +11,6 @@ import { Props } from './typings';

const WIDTH = '620px';

const StyledClickOutside = styled(ClickOutside)`
div.tippy-box {
max-width: ${WIDTH} !important;
padding: 8px;
}
`;

const DatePhasePicker = ({
selectedRange,
disabledRanges = [],
Expand All @@ -35,7 +27,10 @@ const DatePhasePicker = ({
);

return (
<StyledClickOutside onClickOutside={() => setCalendarOpen(false)}>
<ClickOutsideContainer
width={WIDTH}
onClickOutside={() => setCalendarOpen(false)}
>
<Tooltip
content={
<Box width={WIDTH}>
Expand All @@ -59,7 +54,7 @@ const DatePhasePicker = ({
onClick={() => setCalendarOpen((open) => !open)}
/>
</Tooltip>
</StyledClickOutside>
</ClickOutsideContainer>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DateRange } from './typings';
import { DateRange } from '../_shared/typings';

export const isSelectedRangeOpenEnded = (
{ from, to }: Partial<DateRange>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { addDays } from 'date-fns';

import { DateRange } from './typings';
import { DateRange } from '../_shared/typings';

/**
* Utility to handle the case where the last disabled range is open,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
export type DateRange = {
from: Date;
to?: Date;
};
import { DateRange } from '../_shared/typings';

export type ClosedDateRange = {
from: Date;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from 'react';

import { Box, colors, Button } from '@citizenlab/cl2-component-library';
import { DayPicker, PropsBase } from 'react-day-picker';
import 'react-day-picker/style.css';
import styled from 'styled-components';

import useLocale from 'hooks/useLocale';

import { useIntl } from 'utils/cl-intl';

import { getEndMonth } from '../../_shared/getStartEndMonth';
import { getLocale } from '../../_shared/locales';
import { CalendarProps } from '../typings';

import messages from './messages';
import { getNextSelectionMode } from './utils/getNextSelectionMode';
import { getUpdatedRange } from './utils/getUpdatedRange';

const DayPickerStyles = styled.div`
.rdp-root {
--rdp-accent-color: ${colors.teal700};
--rdp-accent-background-color: ${colors.teal100};
}
.rdp-selected > button.rdp-day_button {
font-size: 14px;
font-weight: normal;
}
`;

const Calendar = ({
selectedRange,
startMonth: _startMonth,
endMonth: _endMonth,
defaultMonth,
disabled,
selectionMode,
numberOfMonths = 2,
onUpdateRange,
onUpdateSelectionMode,
}: CalendarProps) => {
const locale = useLocale();
const { formatMessage } = useIntl();

const startMonth = _startMonth ?? new Date(1900, 0);
const endMonth = getEndMonth({
endMonth: _endMonth,
selectedDate: selectedRange.to,
});

const handleDayClick: PropsBase['onDayClick'] = (day: Date) => {
if (!selectionMode) return; // Should not be possible in practice

const nextRange = getUpdatedRange({
selectedRange,
selectionMode,
clickedDate: day,
});

onUpdateRange(nextRange);

const nextSelectionMode = getNextSelectionMode({
selectionMode,
selectedRange: nextRange,
});

onUpdateSelectionMode(nextSelectionMode);
};

return (
<DayPickerStyles>
<DayPicker
mode="range"
numberOfMonths={numberOfMonths}
captionLayout="dropdown"
locale={getLocale(locale)}
startMonth={startMonth}
endMonth={endMonth}
defaultMonth={defaultMonth}
selected={{ from: selectedRange.from, to: selectedRange.to }}
disabled={disabled}
onDayClick={handleDayClick}
// This NOOP is necessary because otherwise the
// DayPicker will rely on its internal state to manage the selected
// range rather than being controlled by our state.
onSelect={NOOP}
footer={
<Box mt="12px" w="100%" display="flex">
{selectedRange.from && (
<Button
buttonStyle="text"
size="s"
w="auto"
pl="0px"
ml="8px"
onClick={() => {
onUpdateRange({ from: undefined, to: selectedRange.to });
}}
>
{formatMessage(messages.clearStartDate)}
</Button>
)}
{selectedRange.to && (
<Button
buttonStyle="text"
size="s"
w="auto"
pl="0px"
ml="8px"
onClick={() => {
onUpdateRange({ from: selectedRange.from, to: undefined });
}}
>
{formatMessage(messages.clearEndDate)}
</Button>
)}
</Box>
}
/>
</DayPickerStyles>
);
};

const NOOP = () => {};

export default Calendar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineMessages } from 'react-intl';

export default defineMessages({
clearStartDate: {
id: 'app.components.admin.DatePickers.DateRangePicker.Calendar.clearStartDate',
defaultMessage: 'Clear start date',
},
clearEndDate: {
id: 'app.components.admin.DatePickers.DateRangePicker.Calendar.clearEndDate',
defaultMessage: 'Clear end date',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DateRange } from 'components/admin/DatePickers/_shared/typings';

import { SelectionMode } from '../../typings';

interface GetNextSelectionModeParams {
selectedRange: Partial<DateRange>;
selectionMode: SelectionMode;
}

export const getNextSelectionMode = ({
selectedRange,
selectionMode,
}: GetNextSelectionModeParams) => {
if (selectedRange.from && !selectedRange.to) {
return 'to';
}

if (!selectedRange.from && selectedRange.to) {
return 'from';
}

return selectionMode;
};
Loading

0 comments on commit 24a7778

Please sign in to comment.