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: add selectionType prop to Calendar component #1563

Merged
merged 30 commits into from
May 22, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c4004e6
feat: add selectionType prop to Calendar component
wildergd May 19, 2020
b3b267a
Merge branch 'master' into feat-add-selection-type-prop-to-calendar-c…
wildergd May 19, 2020
b26ac84
fix: code cleanup
wildergd May 19, 2020
fa21005
fix: fix render when selection type is range
wildergd May 19, 2020
6130642
style: fix range highlight
wildergd May 19, 2020
841c89e
Merge branch 'master' into feat-add-selection-type-prop-to-calendar-c…
wildergd May 19, 2020
0dd7000
Merge branch 'master' into feat-add-selection-type-prop-to-calendar-c…
wildergd May 20, 2020
7eb649e
style: fix range highlight
wildergd May 20, 2020
1b4fc06
style: fix range highlight when range start date is the same as range…
wildergd May 20, 2020
6cefaa6
fix: fix range highlight when hovered
wildergd May 20, 2020
17a5bf4
style: fix styles
wildergd May 20, 2020
1c73c93
Merge branch 'master' into feat-add-selection-type-prop-to-calendar-c…
wildergd May 20, 2020
f396a8e
fix: fix comments
wildergd May 20, 2020
f7b783b
Merge branch 'master' into feat-add-selection-type-prop-to-calendar-c…
wildergd May 20, 2020
b3b3bc9
fix: some code enhancements
wildergd May 21, 2020
f5d2aa5
Merge branch 'master' into feat-add-selection-type-prop-to-calendar-c…
TahimiLeonBravo May 21, 2020
f62c06d
fix: fix index.d.ts
wildergd May 21, 2020
0bbd384
Merge branch 'master' into feat-add-selection-type-prop-to-calendar-c…
wildergd May 21, 2020
4d33c7f
fix: issues in comments
wildergd May 22, 2020
9366b4a
Merge branch 'master' into feat-add-selection-type-prop-to-calendar-c…
wildergd May 22, 2020
bc587ad
fix: fix buildNewRangeFromValue helper
wildergd May 22, 2020
ed48674
styles: range color
May 22, 2020
364fab0
Merge branch 'feat-add-selection-type-prop-to-calendar-component' of …
May 22, 2020
5a10a9b
styles: hover flicking
May 22, 2020
71923b2
fix: indentation
May 22, 2020
2e3077e
style: fix highlight background when range starts on saturday and end…
wildergd May 22, 2020
4839b77
fix: fix range selection when selected end date is previous to range …
wildergd May 22, 2020
f941c3b
Merge branch 'master' into feat-add-selection-type-prop-to-calendar-c…
maxxgreene May 22, 2020
335cf2f
Merge branch 'feat-add-selection-type-prop-to-calendar-component' of …
wildergd May 22, 2020
746feaa
fix: fix comments
wildergd May 22, 2020
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
3 changes: 2 additions & 1 deletion src/components/Calendar/context.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';

export const { Provider, Consumer } = React.createContext();
export const CalendarContext = React.createContext();
export const { Provider, Consumer } = CalendarContext;
63 changes: 50 additions & 13 deletions src/components/Calendar/day.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,36 @@ import { Consumer } from './context';
import StyledDay from './styled/day';
import StyledDayAdjacent from './styled/dayAdjacent';
import StyledDayButton from './styled/dayButton';
import StyledRangeHighlight from './styled/rangeHighlight';
import { isSameDay, compareDates } from './helpers';
import { useRangeStartDate, useRangeEndDate } from './hooks';

function DayComponent(props) {
const { date, firstDayMonth, isSelected, minDate, maxDate, onChange } = props;
const { useAutoFocus, focusedDate, privateKeyDown, privateOnFocus, privateOnBlur } = props;
const {
date,
firstDayMonth,
isSelected,
minDate,
maxDate,
onChange,
isWithinRange,
isFirstDayOfWeek,
isLastDayOfWeek,
useAutoFocus,
focusedDate,
currentRange,
privateKeyDown,
privateOnFocus,
privateOnBlur,
privateOnHover,
} = props;
const day = date.getDate();
const isAdjacentDate = date.getMonth() !== firstDayMonth.getMonth();
const isDisabled = compareDates(date, maxDate) > 0 || compareDates(date, minDate) < 0;
const tabIndex = isSameDay(focusedDate, date) ? 0 : -1;
const buttonRef = useRef();
const isRangeStartDate = useRangeStartDate(date, currentRange);
const isRangeEndDate = useRangeEndDate(date, currentRange);

useEffect(() => {
if (!useAutoFocus || !buttonRef.current || tabIndex === -1) return;
Expand All @@ -31,18 +51,29 @@ function DayComponent(props) {

return (
<StyledDay role="gridcell">
<StyledDayButton
ref={buttonRef}
tabIndex={tabIndex}
onClick={() => onChange(new Date(date))}
isSelected={isSelected}
data-selected={isSelected}
onKeyDown={privateKeyDown}
onFocus={privateOnFocus}
onBlur={privateOnBlur}
<StyledRangeHighlight
isVisible={isWithinRange && !(isRangeStartDate && isRangeEndDate)}
isFirstInRange={isRangeStartDate}
isLastInRange={isRangeEndDate}
isFirstDayOfWeek={isFirstDayOfWeek}
isLastDayOfWeek={isLastDayOfWeek}
>
{day}
</StyledDayButton>
<StyledDayButton
ref={buttonRef}
tabIndex={tabIndex}
onClick={() => onChange(new Date(date))}
onMouseEnter={() => privateOnHover(new Date(date))}
wildergd marked this conversation as resolved.
Show resolved Hide resolved
isSelected={isSelected}
isHovered={!isSelected && isRangeEndDate}
data-selected={isSelected}
onKeyDown={privateKeyDown}
onFocus={privateOnFocus}
onBlur={privateOnBlur}
isWithinRange={isWithinRange}
>
{day}
</StyledDayButton>
</StyledRangeHighlight>
</StyledDay>
);
}
Expand All @@ -57,6 +88,9 @@ Day.propTypes = {
minDate: PropTypes.instanceOf(Date),
maxDate: PropTypes.instanceOf(Date),
isSelected: PropTypes.bool,
isWithinRange: PropTypes.bool,
isFirstDayOfWeek: PropTypes.bool,
isLastDayOfWeek: PropTypes.bool,
onChange: PropTypes.func,
};

Expand All @@ -66,5 +100,8 @@ Day.defaultProps = {
minDate: undefined,
maxDate: undefined,
isSelected: false,
isWithinRange: false,
isFirstDayOfWeek: false,
isLastDayOfWeek: false,
onChange: () => {},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import buildNewRangeFromValue from '../buildNewRangeFromValue';

describe('buildNewRangeFromValue', () => {
it('should return array with single date', () => {
const value = new Date();
const ranges = [undefined, null, []];
ranges.forEach(range => {
expect(buildNewRangeFromValue(value, range)).toEqual([value]);
});
});
it('should return array with two dates', () => {
const date1 = new Date(2019, 2, 1);
const date2 = new Date(2019, 21, 1);
const range = [date1];
expect(buildNewRangeFromValue(date2, range)).toEqual([date1, date2]);
});
it('should return array with two dates and date3 as first date', () => {
const date1 = new Date(2019, 0, 2);
const date2 = new Date(2019, 0, 21);
const date3 = new Date(2019, 0, 15);
const range = [date1, date2];
expect(buildNewRangeFromValue(date3, range)).toEqual([date3, date2]);
});
});
17 changes: 17 additions & 0 deletions src/components/Calendar/helpers/__test__/isDateWithinRange.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import isDateWithinRange from '../isDateWithinRange';

describe('isDateWithinRange', () => {
it('should return false', () => {
const range = [new Date(2019, 2, 1, 0, 0, 0, 600), new Date(2019, 15, 1, 23, 12, 34, 600)];
expect(isDateWithinRange(null, range)).toBe(false);
expect(isDateWithinRange(undefined, range)).toBe(false);
expect(isDateWithinRange(new Date(2019, 1, 1), range)).toBe(false);
expect(isDateWithinRange(new Date(2019, 18, 1), range)).toBe(false);
});
it('should return true', () => {
const range = [new Date(2019, 2, 1), new Date(2019, 15, 1)];
expect(isDateWithinRange(new Date(2019, 2, 1), range)).toBe(true);
expect(isDateWithinRange(new Date(2019, 13, 1), range)).toBe(true);
expect(isDateWithinRange(new Date(2019, 15, 1), range)).toBe(true);
});
});
16 changes: 16 additions & 0 deletions src/components/Calendar/helpers/__test__/isEmptyRange.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import isEmptyRange from '../isEmptyRange';

describe('isEmptyRange', () => {
it('should return true', () => {
const ranges = [null, undefined, [], {}, 0, [undefined], [undefined, undefined]];
ranges.forEach(range => {
expect(isEmptyRange(range)).toBe(true);
});
});
it('should return false', () => {
const ranges = [[1], [1, 2]];
ranges.forEach(range => {
expect(isEmptyRange(range)).toBe(false);
});
});
});
44 changes: 44 additions & 0 deletions src/components/Calendar/helpers/__test__/isSameDatesRange.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import isSameDatesRange from '../isSameDatesRange';

describe('isSameMonth', () => {
it('should return true', () => {
const range1 = [
null,
undefined,
[],
[new Date(2019, 1, 1)],
[new Date(2019, 1, 1), new Date(2019, 1, 10)],
];
const range2 = [
null,
undefined,
[],
[new Date(2019, 1, 1)],
[new Date(2019, 1, 1), new Date(2019, 1, 10)],
];

range1.forEach((value, index) => {
expect(isSameDatesRange(value, range2[index])).toBe(true);
});
});
it('should return false', () => {
const range1 = [
null,
undefined,
[],
[new Date(2019, 1, 1)],
[new Date(2019, 1, 1), new Date(2019, 1, 10)],
];
const range2 = [
undefined,
[],
null,
[new Date(2019, 1, 1), new Date(2019, 1, 10)],
[new Date(2019, 1, 1)],
];

range1.forEach((value, index) => {
expect(isSameDatesRange(value, range2[index])).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import shouldDateBeSelected from '../shouldDateBeSelected';

describe('shouldDateBeSelected', () => {
it('should return true when selectionType is single and date is the same as value', () => {
const date = new Date(2019, 1, 1);
const value = new Date(2019, 1, 1);
const range = [new Date(2019, 1, 1), new Date(2019, 1, 15)];
expect(shouldDateBeSelected(date, value, 'single', undefined)).toBe(true);
expect(shouldDateBeSelected(date, value, 'single', null)).toBe(true);
expect(shouldDateBeSelected(date, value, 'single', [])).toBe(true);
expect(shouldDateBeSelected(date, value, 'single', range)).toBe(true);
});
it('should return true when selectionType is range and date is same as range boundaries', () => {
const date1 = new Date(2019, 1, 1);
const date2 = new Date(2019, 1, 15);
const value = new Date(2019, 1, 1);
const range = [new Date(2019, 1, 1), new Date(2019, 1, 15)];
expect(shouldDateBeSelected(date1, undefined, 'range', range)).toBe(true);
expect(shouldDateBeSelected(date1, null, 'range', range)).toBe(true);
expect(shouldDateBeSelected(date1, value, 'range', range)).toBe(true);
expect(shouldDateBeSelected(date2, value, 'range', range)).toBe(true);
});
it('should return false when selectionType is single and date is not the same as value', () => {
const date = new Date(2019, 1, 12);
const value = new Date(2019, 1, 1);
const range = [new Date(2019, 1, 1), new Date(2019, 1, 15)];
expect(shouldDateBeSelected(date, value, 'single', undefined)).toBe(false);
expect(shouldDateBeSelected(date, value, 'single', null)).toBe(false);
expect(shouldDateBeSelected(date, value, 'single', [])).toBe(false);
expect(shouldDateBeSelected(date, value, 'single', range)).toBe(false);
});
it('should return false when selectionType is range and date is not the same as range boundaries', () => {
const date1 = new Date(2019, 1, 2);
const date2 = new Date(2019, 1, 12);
const value = new Date(2019, 1, 1);
const range = [new Date(2019, 1, 1), new Date(2019, 1, 15)];
expect(shouldDateBeSelected(date1, undefined, 'range', range)).toBe(false);
expect(shouldDateBeSelected(date1, null, 'range', range)).toBe(false);
expect(shouldDateBeSelected(date1, value, 'range', range)).toBe(false);
expect(shouldDateBeSelected(date2, value, 'range', range)).toBe(false);
});
});
27 changes: 27 additions & 0 deletions src/components/Calendar/helpers/buildNewRangeFromValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import isDateWithinRange from './isDateWithinRange';
import compareDates from './compareDates';

let rangeIndex = 0;
wildergd marked this conversation as resolved.
Show resolved Hide resolved

export default function buildNewRangeFromValue(value, currentRange) {
if (!currentRange || currentRange.length === 0) return [value];

const [rangeStart, rangeEnd] = currentRange;

if (!rangeEnd) {
if (compareDates(value, rangeStart) >= 0) return [rangeStart, value];
return [value, rangeStart];
}

if (isDateWithinRange(value, currentRange)) {
if (rangeIndex === 0) {
rangeIndex = 1;
return [value, rangeEnd];
}

rangeIndex = 0;
return [rangeStart, value];
}

return [value];
}
63 changes: 24 additions & 39 deletions src/components/Calendar/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,24 @@
import addDays from './addDays';
import addMonths from './addMonths';
import formatDate from './formatDate';
import getFirstDayMonth from './getFirstDayMonth';
import getFormattedMonth from './getFormattedMonth';
import getLastDayMonth from './getLastDayMonth';
import getYearsRange from './getYearsRange';
import isSameDay from './isSameDay';
import normalizeDate from './normalizeDate';
import getFormattedDayName from './getFormattedDayName';
import compareDates from './compareDates';
import isSameMonth from './isSameMonth';
import isSameYear from './isSameYear';
import getSign from './getSign';
import getCalendarBounds from './getCalendarBounds';
import isDateBelowLimit from './isDateBelowLimit';
import isDateBeyondLimit from './isDateBeyondLimit';
import getNextFocusedDate from './getNextFocusedDate';

export {
addDays,
addMonths,
formatDate,
getFirstDayMonth,
getFormattedMonth,
getLastDayMonth,
getYearsRange,
isSameDay,
normalizeDate,
getFormattedDayName,
compareDates,
isSameMonth,
isSameYear,
getSign,
getCalendarBounds,
isDateBelowLimit,
isDateBeyondLimit,
getNextFocusedDate,
};
export { default as addDays } from './addDays';
export { default as addMonths } from './addMonths';
export { default as formatDate } from './formatDate';
export { default as getFirstDayMonth } from './getFirstDayMonth';
export { default as getFormattedMonth } from './getFormattedMonth';
export { default as getLastDayMonth } from './getLastDayMonth';
export { default as getYearsRange } from './getYearsRange';
export { default as isSameDay } from './isSameDay';
export { default as normalizeDate } from './normalizeDate';
export { default as normalizeRange } from './normalizeRange';
export { default as getFormattedDayName } from './getFormattedDayName';
export { default as compareDates } from './compareDates';
export { default as isSameMonth } from './isSameMonth';
export { default as isSameYear } from './isSameYear';
export { default as getSign } from './getSign';
export { default as getCalendarBounds } from './getCalendarBounds';
export { default as isDateBelowLimit } from './isDateBelowLimit';
export { default as isDateBeyondLimit } from './isDateBeyondLimit';
export { default as getNextFocusedDate } from './getNextFocusedDate';
export { default as isDateWithinRange } from './isDateWithinRange';
export { default as buildNewRangeFromValue } from './buildNewRangeFromValue';
export { default as shouldDateBeSelected } from './shouldDateBeSelected';
export { default as isSameDatesRange } from './isSameDatesRange';
export { default as isEmptyRange } from './isEmptyRange';
7 changes: 7 additions & 0 deletions src/components/Calendar/helpers/isDateWithinRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import compareDates from './compareDates';

export default function isDateWithinRange(date, range) {
if (!date || !Array.isArray(range) || range.length <= 1) return false;
wildergd marked this conversation as resolved.
Show resolved Hide resolved
const [rangeStart, rangeEnd] = range;
return compareDates(date, rangeStart) >= 0 && compareDates(date, rangeEnd) <= 0;
}
6 changes: 6 additions & 0 deletions src/components/Calendar/helpers/isEmptyRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default function isEmptyRange(range) {
if ([null, undefined, [], {}].includes(range)) return true;
if (!Array.isArray(range) || range.length === 0) return true;
const nullFilteredRange = range.filter(item => !!item);
return nullFilteredRange.length === 0;
}
3 changes: 3 additions & 0 deletions src/components/Calendar/helpers/isSameDatesRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function isSameDatesRange(range1, range2) {
return JSON.stringify(range1) === JSON.stringify(range2);
}
7 changes: 7 additions & 0 deletions src/components/Calendar/helpers/normalizeRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import normalizeDate from './normalizeDate';

export default function normalizeRange(range) {
if (!range) return [];
if (!Array.isArray(range)) return [normalizeDate(range)];
return range.map(date => normalizeDate(date));
}
8 changes: 8 additions & 0 deletions src/components/Calendar/helpers/shouldDateBeSelected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import isSameDay from './isSameDay';

export default function shouldDateBeSelected(date, currentValue, selectionType, currentRange) {
if (selectionType === 'single') return isSameDay(date, currentValue);
if (!Array.isArray(currentRange) || currentRange.length === 0) return false;
const [rangeStart, rangeEnd] = currentRange;
return isSameDay(date, rangeStart) || isSameDay(date, rangeEnd);
}
6 changes: 6 additions & 0 deletions src/components/Calendar/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { default as useCurrentDateFromValue } from './useCurrentDateFromValue';
export { default as useRangeFromValue } from './useRangeFromValue';
export { default as useDaysBuilder } from './useDaysBuilder';
export { default as useWeeksBuilder } from './useWeeksBuilder';
export { default as useRangeStartDate } from './useRangeStartDate';
export { default as useRangeEndDate } from './useRangeEndDate';
9 changes: 9 additions & 0 deletions src/components/Calendar/hooks/useCurrentDateFromValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useMemo } from 'react';

export default function useCurrentDateFromValue(value) {
return useMemo(() => {
if (!Array.isArray(value)) return value;
const [rangeStart, rangeEnd] = value;
return rangeStart || rangeEnd;
}, [value]);
}
Loading