@lumx/react 3.5.4-alpha-optimize-lumx-react-bundle.0 → 3.5.4-alpha-remove-moment.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +2 -2
- package/index.js +232 -82
- package/index.js.map +1 -1
- package/package.json +3 -5
- package/src/components/date-picker/DatePicker.stories.tsx +38 -0
- package/src/components/date-picker/DatePicker.tsx +8 -11
- package/src/components/date-picker/DatePickerControlled.tsx +36 -39
- package/src/components/date-picker/DatePickerField.stories.tsx +0 -1
- package/src/components/date-picker/DatePickerField.tsx +9 -8
- package/src/components/date-picker/types.ts +1 -1
- package/src/utils/date/addMonthResetDay.test.ts +13 -0
- package/src/utils/date/addMonthResetDay.ts +9 -0
- package/src/utils/date/getFirstDayOfWeek.test.ts +20 -0
- package/src/utils/date/getFirstDayOfWeek.ts +58 -0
- package/src/utils/date/getMonthCalendar.test.ts +123 -0
- package/src/utils/date/getMonthCalendar.ts +52 -0
- package/src/utils/date/getWeekDays.test.ts +48 -0
- package/src/utils/date/getWeekDays.ts +32 -0
- package/src/utils/date/isDateValid.test.ts +15 -0
- package/src/utils/date/isDateValid.ts +4 -0
- package/src/utils/date/isSameDay.test.ts +37 -0
- package/src/utils/date/isSameDay.ts +11 -0
- package/src/utils/locale/getCurrentLocale.ts +4 -0
- package/src/utils/locale/parseLocale.test.ts +17 -0
- package/src/utils/locale/parseLocale.ts +23 -0
- package/src/utils/locale/types.ts +8 -0
package/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@juggle/resize-observer": "^3.2.0",
|
|
10
|
-
"@lumx/core": "^3.5.4-alpha-
|
|
11
|
-
"@lumx/icons": "^3.5.4-alpha-
|
|
10
|
+
"@lumx/core": "^3.5.4-alpha-remove-moment.2",
|
|
11
|
+
"@lumx/icons": "^3.5.4-alpha-remove-moment.2",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.3.2",
|
|
@@ -79,8 +79,6 @@
|
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
81
|
"lodash": "4.17.21",
|
|
82
|
-
"moment": ">= 2",
|
|
83
|
-
"moment-range": "^4.0.2",
|
|
84
82
|
"react": ">= 16.13.0",
|
|
85
83
|
"react-dom": ">= 16.13.0"
|
|
86
84
|
},
|
|
@@ -117,5 +115,5 @@
|
|
|
117
115
|
"build:storybook": "storybook build"
|
|
118
116
|
},
|
|
119
117
|
"sideEffects": false,
|
|
120
|
-
"version": "3.5.4-alpha-
|
|
118
|
+
"version": "3.5.4-alpha-remove-moment.2"
|
|
121
119
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DatePicker, GridColumn } from '@lumx/react';
|
|
2
|
+
import { withValueOnChange } from '@lumx/react/stories/decorators/withValueOnChange';
|
|
3
|
+
import { withNestedProps } from '@lumx/react/stories/decorators/withNestedProps';
|
|
4
|
+
import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
|
|
5
|
+
import { withWrapper } from '@lumx/react/stories/decorators/withWrapper';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'LumX components/date-picker/DatePicker',
|
|
9
|
+
component: DatePicker,
|
|
10
|
+
argTypes: {
|
|
11
|
+
onChange: { action: true },
|
|
12
|
+
},
|
|
13
|
+
decorators: [withValueOnChange(), withNestedProps()],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default date picker
|
|
18
|
+
*/
|
|
19
|
+
export const Default = {
|
|
20
|
+
args: {
|
|
21
|
+
defaultMonth: new Date('2023-02'),
|
|
22
|
+
'nextButtonProps.label': 'Next month',
|
|
23
|
+
'previousButtonProps.label': 'Previous month',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Demonstrate variations based on the given locale code
|
|
29
|
+
*/
|
|
30
|
+
export const LocalesVariations = {
|
|
31
|
+
...Default,
|
|
32
|
+
decorators: [
|
|
33
|
+
withCombinations({
|
|
34
|
+
combinations: { sections: { key: 'locale', options: ['fr', 'en-US', 'ar', 'zh-HK', 'ar-eg'] } },
|
|
35
|
+
}),
|
|
36
|
+
withWrapper({ maxColumns: 5, itemMinWidth: 300 }, GridColumn),
|
|
37
|
+
],
|
|
38
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import moment from 'moment';
|
|
2
1
|
import React, { forwardRef, useState } from 'react';
|
|
3
2
|
import { Comp } from '@lumx/react/utils/type';
|
|
3
|
+
import { addMonthResetDay } from '@lumx/react/utils/date/addMonthResetDay';
|
|
4
|
+
import { isDateValid } from '@lumx/react/utils/date/isDateValid';
|
|
4
5
|
import { CLASSNAME, COMPONENT_NAME } from './constants';
|
|
5
6
|
import { DatePickerControlled } from './DatePickerControlled';
|
|
6
7
|
import { DatePickerProps } from './types';
|
|
@@ -14,17 +15,13 @@ import { DatePickerProps } from './types';
|
|
|
14
15
|
*/
|
|
15
16
|
export const DatePicker: Comp<DatePickerProps, HTMLDivElement> = forwardRef((props, ref) => {
|
|
16
17
|
const { defaultMonth, locale, value, onChange, ...forwardedProps } = props;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} else if (defaultMonth) {
|
|
21
|
-
castedValue = moment(defaultMonth);
|
|
22
|
-
}
|
|
23
|
-
if (castedValue && !castedValue.isValid()) {
|
|
18
|
+
|
|
19
|
+
let referenceDate = value || defaultMonth || new Date();
|
|
20
|
+
if (!isDateValid(referenceDate)) {
|
|
24
21
|
// eslint-disable-next-line no-console
|
|
25
|
-
console.warn(`[@lumx/react/DatePicker] Invalid date provided ${
|
|
22
|
+
console.warn(`[@lumx/react/DatePicker] Invalid date provided ${referenceDate}`);
|
|
23
|
+
referenceDate = new Date();
|
|
26
24
|
}
|
|
27
|
-
const selectedDay = castedValue && castedValue.isValid() ? castedValue : moment();
|
|
28
25
|
|
|
29
26
|
const [monthOffset, setMonthOffset] = useState(0);
|
|
30
27
|
|
|
@@ -36,7 +33,7 @@ export const DatePicker: Comp<DatePickerProps, HTMLDivElement> = forwardRef((pro
|
|
|
36
33
|
setMonthOffset(0);
|
|
37
34
|
};
|
|
38
35
|
|
|
39
|
-
const selectedMonth =
|
|
36
|
+
const selectedMonth = addMonthResetDay(referenceDate, monthOffset);
|
|
40
37
|
|
|
41
38
|
return (
|
|
42
39
|
<DatePickerControlled
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react';
|
|
2
|
-
import moment from 'moment';
|
|
3
2
|
import classNames from 'classnames';
|
|
4
3
|
import { DatePickerProps, Emphasis, IconButton, Toolbar } from '@lumx/react';
|
|
5
4
|
import { mdiChevronLeft, mdiChevronRight } from '@lumx/icons';
|
|
6
|
-
import { getAnnotatedMonthCalendar, getWeekDays } from '@lumx/core/js/date-picker';
|
|
7
5
|
import { Comp } from '@lumx/react/utils/type';
|
|
6
|
+
import { getMonthCalendar } from '@lumx/react/utils/date/getMonthCalendar';
|
|
7
|
+
import { isSameDay } from '@lumx/react/utils/date/isSameDay';
|
|
8
|
+
import { getCurrentLocale } from '@lumx/react/utils/locale/getCurrentLocale';
|
|
9
|
+
import { parseLocale } from '@lumx/react/utils/locale/parseLocale';
|
|
10
|
+
import { Locale } from '@lumx/react/utils/locale/types';
|
|
8
11
|
import { CLASSNAME } from './constants';
|
|
9
12
|
|
|
10
13
|
/**
|
|
@@ -33,7 +36,7 @@ const COMPONENT_NAME = 'DatePickerControlled';
|
|
|
33
36
|
*/
|
|
34
37
|
export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElement> = forwardRef((props, ref) => {
|
|
35
38
|
const {
|
|
36
|
-
locale,
|
|
39
|
+
locale = getCurrentLocale(),
|
|
37
40
|
maxDate,
|
|
38
41
|
minDate,
|
|
39
42
|
nextButtonProps,
|
|
@@ -45,14 +48,11 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
|
|
|
45
48
|
todayOrSelectedDateRef,
|
|
46
49
|
value,
|
|
47
50
|
} = props;
|
|
48
|
-
const
|
|
49
|
-
|
|
51
|
+
const { weeks, weekDays } = React.useMemo(() => {
|
|
52
|
+
const localeObj = parseLocale(locale) as Locale;
|
|
53
|
+
return getMonthCalendar(localeObj, selectedMonth, minDate, maxDate);
|
|
50
54
|
}, [locale, minDate, maxDate, selectedMonth]);
|
|
51
55
|
|
|
52
|
-
const weekDays = React.useMemo(() => {
|
|
53
|
-
return getWeekDays(locale);
|
|
54
|
-
}, [locale]);
|
|
55
|
-
|
|
56
56
|
return (
|
|
57
57
|
<div ref={ref} className={`${CLASSNAME}`}>
|
|
58
58
|
<Toolbar
|
|
@@ -75,49 +75,46 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
|
|
|
75
75
|
}
|
|
76
76
|
label={
|
|
77
77
|
<span className={`${CLASSNAME}__month`}>
|
|
78
|
-
{
|
|
78
|
+
{selectedMonth.toLocaleDateString(locale, { year: 'numeric', month: 'long' })}
|
|
79
79
|
</span>
|
|
80
80
|
}
|
|
81
81
|
/>
|
|
82
82
|
<div className={`${CLASSNAME}__calendar`}>
|
|
83
83
|
<div className={`${CLASSNAME}__week-days ${CLASSNAME}__days-wrapper`}>
|
|
84
|
-
{weekDays.map((
|
|
85
|
-
<div key={
|
|
86
|
-
<span className={`${CLASSNAME}__week-day`}>
|
|
87
|
-
{weekDay.format('dddd').slice(0, 1).toLocaleUpperCase()}
|
|
88
|
-
</span>
|
|
84
|
+
{weekDays.map(({ letter, number }) => (
|
|
85
|
+
<div key={number} className={`${CLASSNAME}__day-wrapper`}>
|
|
86
|
+
<span className={`${CLASSNAME}__week-day`}>{letter.toLocaleUpperCase()}</span>
|
|
89
87
|
</div>
|
|
90
88
|
))}
|
|
91
89
|
</div>
|
|
92
90
|
|
|
93
91
|
<div className={`${CLASSNAME}__month-days ${CLASSNAME}__days-wrapper`}>
|
|
94
|
-
{
|
|
95
|
-
|
|
92
|
+
{weeks.flatMap((week, weekIndex) => {
|
|
93
|
+
return weekDays.map((weekDay, dayIndex) => {
|
|
94
|
+
const { date, isOutOfRange } = week[weekDay.number] || {};
|
|
95
|
+
const key = `${weekIndex}-${dayIndex}`;
|
|
96
|
+
const isToday = !isOutOfRange && date && isSameDay(date, new Date());
|
|
97
|
+
const isSelected = date && value && isSameDay(value, date);
|
|
98
|
+
|
|
96
99
|
return (
|
|
97
|
-
<div key={
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
(value &&
|
|
101
|
-
(
|
|
102
|
-
|
|
103
|
-
:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
type="button"
|
|
113
|
-
onClick={() => onChange(moment(annotatedDate.date).toDate())}
|
|
114
|
-
>
|
|
115
|
-
<span>{annotatedDate.date.format('DD')}</span>
|
|
116
|
-
</button>
|
|
100
|
+
<div key={key} className={`${CLASSNAME}__day-wrapper`}>
|
|
101
|
+
{date && (
|
|
102
|
+
<button
|
|
103
|
+
ref={isSelected || (!value && isToday) ? todayOrSelectedDateRef : null}
|
|
104
|
+
className={classNames(`${CLASSNAME}__month-day`, {
|
|
105
|
+
[`${CLASSNAME}__month-day--is-selected`]: isSelected,
|
|
106
|
+
[`${CLASSNAME}__month-day--is-today`]: isToday,
|
|
107
|
+
})}
|
|
108
|
+
disabled={isOutOfRange}
|
|
109
|
+
type="button"
|
|
110
|
+
onClick={() => onChange(date)}
|
|
111
|
+
>
|
|
112
|
+
<span>{date.toLocaleDateString(locale, { day: 'numeric' })}</span>
|
|
113
|
+
</button>
|
|
114
|
+
)}
|
|
117
115
|
</div>
|
|
118
116
|
);
|
|
119
|
-
}
|
|
120
|
-
return <div key={annotatedDate.date.unix()} className={`${CLASSNAME}__day-wrapper`} />;
|
|
117
|
+
});
|
|
121
118
|
})}
|
|
122
119
|
</div>
|
|
123
120
|
</div>
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { DatePicker, Placement, Popover, TextField, IconButtonProps } from '@lumx/react';
|
|
2
|
-
import { useFocusTrap } from '@lumx/react/hooks/useFocusTrap';
|
|
3
|
-
|
|
4
|
-
import moment from 'moment';
|
|
5
|
-
|
|
6
1
|
import React, { forwardRef, SyntheticEvent, useCallback, useRef, useState } from 'react';
|
|
7
2
|
|
|
3
|
+
import { DatePicker, IconButtonProps, Placement, Popover, TextField } from '@lumx/react';
|
|
4
|
+
import { useFocusTrap } from '@lumx/react/hooks/useFocusTrap';
|
|
8
5
|
import { useFocus } from '@lumx/react/hooks/useFocus';
|
|
9
6
|
import { Comp, GenericProps } from '@lumx/react/utils/type';
|
|
7
|
+
import { getCurrentLocale } from '@lumx/react/utils/locale/getCurrentLocale';
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
* Defines the props of the component.
|
|
@@ -17,7 +15,7 @@ export interface DatePickerFieldProps extends GenericProps {
|
|
|
17
15
|
/** Whether the component is disabled or not. */
|
|
18
16
|
isDisabled?: boolean;
|
|
19
17
|
/** Locale (language or region) to use. */
|
|
20
|
-
locale
|
|
18
|
+
locale?: string;
|
|
21
19
|
/** Date after which dates can't be selected. */
|
|
22
20
|
maxDate?: Date;
|
|
23
21
|
/** Date before which dates can't be selected. */
|
|
@@ -52,7 +50,7 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
|
|
|
52
50
|
defaultMonth,
|
|
53
51
|
disabled,
|
|
54
52
|
isDisabled = disabled,
|
|
55
|
-
locale,
|
|
53
|
+
locale = getCurrentLocale(),
|
|
56
54
|
maxDate,
|
|
57
55
|
minDate,
|
|
58
56
|
name,
|
|
@@ -97,6 +95,9 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
|
|
|
97
95
|
onClose();
|
|
98
96
|
};
|
|
99
97
|
|
|
98
|
+
// Format date for text field
|
|
99
|
+
const textFieldValue = value?.toLocaleDateString(locale, { year: 'numeric', month: 'long', day: 'numeric' }) || '';
|
|
100
|
+
|
|
100
101
|
return (
|
|
101
102
|
<>
|
|
102
103
|
<TextField
|
|
@@ -105,7 +106,7 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
|
|
|
105
106
|
name={name}
|
|
106
107
|
forceFocusStyle={isOpen}
|
|
107
108
|
textFieldRef={anchorRef}
|
|
108
|
-
value={
|
|
109
|
+
value={textFieldValue}
|
|
109
110
|
onClick={toggleSimpleMenu}
|
|
110
111
|
onChange={onTextFieldChange}
|
|
111
112
|
onKeyPress={handleKeyboardNav}
|
|
@@ -9,7 +9,7 @@ export interface DatePickerProps extends GenericProps {
|
|
|
9
9
|
/** Default month. */
|
|
10
10
|
defaultMonth?: Date;
|
|
11
11
|
/** Locale (language or region) to use. */
|
|
12
|
-
locale
|
|
12
|
+
locale?: string;
|
|
13
13
|
/** Date after which dates can't be selected. */
|
|
14
14
|
maxDate?: Date;
|
|
15
15
|
/** Date before which dates can't be selected. */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { addMonthResetDay } from '@lumx/react/utils/date/addMonthResetDay';
|
|
2
|
+
|
|
3
|
+
describe(addMonthResetDay.name, () => {
|
|
4
|
+
it('should add month to date', () => {
|
|
5
|
+
const actual = addMonthResetDay(new Date('2017-01-30'), 1);
|
|
6
|
+
expect(actual).toEqual(new Date('2017-02-01'));
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should remove months to date', () => {
|
|
10
|
+
const actual = addMonthResetDay(new Date('2017-01-30'), -2);
|
|
11
|
+
expect(actual).toEqual(new Date('2016-11-01'));
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add a number of months from a date while resetting the day to prevent month length mismatches.
|
|
3
|
+
*/
|
|
4
|
+
export function addMonthResetDay(date: Date, monthOffset: number) {
|
|
5
|
+
const newDate = new Date(date.getTime());
|
|
6
|
+
newDate.setDate(1);
|
|
7
|
+
newDate.setMonth(date.getMonth() + monthOffset);
|
|
8
|
+
return newDate;
|
|
9
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Locale } from '@lumx/react/utils/locale/types';
|
|
2
|
+
import { parseLocale } from '../locale/parseLocale';
|
|
3
|
+
import { getFirstDayOfWeek } from './getFirstDayOfWeek';
|
|
4
|
+
|
|
5
|
+
describe(getFirstDayOfWeek.name, () => {
|
|
6
|
+
it('should return for a valid locales', () => {
|
|
7
|
+
expect(getFirstDayOfWeek(parseLocale('fa-ir') as Locale)).toBe(6);
|
|
8
|
+
expect(getFirstDayOfWeek(parseLocale('ar-ma') as Locale)).toBe(1);
|
|
9
|
+
expect(getFirstDayOfWeek(parseLocale('ar') as Locale)).toBe(6);
|
|
10
|
+
expect(getFirstDayOfWeek(parseLocale('ar-eg') as Locale)).toBe(0);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should return for the lang locale if available', () => {
|
|
14
|
+
// Test for a specific locale and its root locale
|
|
15
|
+
const localeWithRoot = parseLocale('es-ES') as Locale; // Spanish (Spain) with root locale es
|
|
16
|
+
const expectedFirstDay = getFirstDayOfWeek(parseLocale('es') as Locale); // First day for root locale 'es'
|
|
17
|
+
|
|
18
|
+
expect(getFirstDayOfWeek(localeWithRoot)).toBe(expectedFirstDay);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Locale } from '@lumx/react/utils/locale/types';
|
|
2
|
+
|
|
3
|
+
/** Get first day of week for locale from the browser API */
|
|
4
|
+
export const getFromBrowser = (locale: Locale): number | undefined => {
|
|
5
|
+
try {
|
|
6
|
+
const localeMetadata = new Intl.Locale(locale.code) as any;
|
|
7
|
+
const { firstDay } = localeMetadata.getWeekInfo?.() || localeMetadata.weekInfo;
|
|
8
|
+
// Sunday is represented as `0` in Date.getDay()
|
|
9
|
+
if (firstDay === 7) return 0;
|
|
10
|
+
return firstDay;
|
|
11
|
+
} catch (e) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** List first day for each locale (could be removed when all browser implement Locale weekInfo) */
|
|
17
|
+
const FIRST_DAY_FOR_LOCALES = [
|
|
18
|
+
{
|
|
19
|
+
// Locales with Sunday as the first day of the week
|
|
20
|
+
localeRX: /^(af|ar-(dz|eg|sa)|bn|cy|en-(ca|us|za)|fr-ca|gd|he|hi|ja|km|ko|pt-br|te|th|ug|zh-hk)$/i,
|
|
21
|
+
firstDay: 0,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
// Locales with Monday as the first day of the week
|
|
25
|
+
localeRX: /^(ar-(ma|tn)|az|be|bg|bs|ca|cs|da|de|el|en-(au|gb|ie|in|nz)|eo|es|et|eu|fi|fr|fy|gl|gu|hr|ht|hu|hy|id|is|it|ka|kk|kn|lb|lt|lv|mk|mn|ms|mt|nb|nl|nn|oc|pl|pt|ro|ru|sk|sl|sq|sr|sv|ta|tr|uk|uz|vi|zh-(cn|tw))$/i,
|
|
26
|
+
firstDay: 1,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
// Locales with Saturday as the first day of the week
|
|
30
|
+
localeRX: /^(ar|fa-ir)$/i,
|
|
31
|
+
firstDay: 6,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** Find first day of week for locale from the constant */
|
|
36
|
+
const getFromConstant = (locale: Locale, searchBy: keyof Locale = 'code'): number | undefined => {
|
|
37
|
+
// Search for locale (lang + region)
|
|
38
|
+
for (const { localeRX, firstDay } of FIRST_DAY_FOR_LOCALES) {
|
|
39
|
+
if (localeRX.test(locale[searchBy] as string)) return firstDay;
|
|
40
|
+
}
|
|
41
|
+
// Fallback search for locale lang
|
|
42
|
+
if (locale.code !== locale.language) {
|
|
43
|
+
return getFromConstant(locale, 'language');
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get first day of the week for the given locale code (language + region).
|
|
50
|
+
*/
|
|
51
|
+
export const getFirstDayOfWeek = (locale: Locale): number | undefined => {
|
|
52
|
+
// Get from browser API
|
|
53
|
+
const firstDay = getFromBrowser(locale);
|
|
54
|
+
if (firstDay !== undefined) return firstDay;
|
|
55
|
+
|
|
56
|
+
// Get from constant
|
|
57
|
+
return getFromConstant(locale);
|
|
58
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { parseLocale } from '@lumx/react/utils/locale/parseLocale';
|
|
2
|
+
import { Locale } from '@lumx/react/utils/locale/types';
|
|
3
|
+
import { getMonthCalendar } from './getMonthCalendar';
|
|
4
|
+
|
|
5
|
+
describe(getMonthCalendar.name, () => {
|
|
6
|
+
it('should generate calendar', () => {
|
|
7
|
+
const referenceDate = new Date('2017-02-03');
|
|
8
|
+
const french = parseLocale('fr') as Locale;
|
|
9
|
+
const month = getMonthCalendar(french, referenceDate);
|
|
10
|
+
|
|
11
|
+
expect(month).toEqual({
|
|
12
|
+
weekDays: [
|
|
13
|
+
{ letter: 'L', number: 1 },
|
|
14
|
+
{ letter: 'M', number: 2 },
|
|
15
|
+
{ letter: 'M', number: 3 },
|
|
16
|
+
{ letter: 'J', number: 4 },
|
|
17
|
+
{ letter: 'V', number: 5 },
|
|
18
|
+
{ letter: 'S', number: 6 },
|
|
19
|
+
{ letter: 'D', number: 0 },
|
|
20
|
+
],
|
|
21
|
+
weeks: [
|
|
22
|
+
{
|
|
23
|
+
'3': { date: new Date('2017-02-01') },
|
|
24
|
+
'4': { date: new Date('2017-02-02') },
|
|
25
|
+
'5': { date: new Date('2017-02-03') },
|
|
26
|
+
'6': { date: new Date('2017-02-04') },
|
|
27
|
+
'0': { date: new Date('2017-02-05') },
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
'1': { date: new Date('2017-02-06') },
|
|
31
|
+
'2': { date: new Date('2017-02-07') },
|
|
32
|
+
'3': { date: new Date('2017-02-08') },
|
|
33
|
+
'4': { date: new Date('2017-02-09') },
|
|
34
|
+
'5': { date: new Date('2017-02-10') },
|
|
35
|
+
'6': { date: new Date('2017-02-11') },
|
|
36
|
+
'0': { date: new Date('2017-02-12') },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
'1': { date: new Date('2017-02-13') },
|
|
40
|
+
'2': { date: new Date('2017-02-14') },
|
|
41
|
+
'3': { date: new Date('2017-02-15') },
|
|
42
|
+
'4': { date: new Date('2017-02-16') },
|
|
43
|
+
'5': { date: new Date('2017-02-17') },
|
|
44
|
+
'6': { date: new Date('2017-02-18') },
|
|
45
|
+
'0': { date: new Date('2017-02-19') },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
'1': { date: new Date('2017-02-20') },
|
|
49
|
+
'2': { date: new Date('2017-02-21') },
|
|
50
|
+
'3': { date: new Date('2017-02-22') },
|
|
51
|
+
'4': { date: new Date('2017-02-23') },
|
|
52
|
+
'5': { date: new Date('2017-02-24') },
|
|
53
|
+
'6': { date: new Date('2017-02-25') },
|
|
54
|
+
'0': { date: new Date('2017-02-26') },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
'1': { date: new Date('2017-02-27') },
|
|
58
|
+
'2': { date: new Date('2017-02-28') },
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should generate calendar with sunday as start of week and mark dates in range', () => {
|
|
65
|
+
const referenceDate = new Date('2017-02-03');
|
|
66
|
+
const minDate = new Date('2017-02-06');
|
|
67
|
+
const maxDate = new Date('2017-02-10');
|
|
68
|
+
const englishUS = parseLocale('en-US') as Locale;
|
|
69
|
+
const month = getMonthCalendar(englishUS, referenceDate, minDate, maxDate);
|
|
70
|
+
|
|
71
|
+
expect(month).toEqual({
|
|
72
|
+
weekDays: [
|
|
73
|
+
{ letter: 'S', number: 0 },
|
|
74
|
+
{ letter: 'M', number: 1 },
|
|
75
|
+
{ letter: 'T', number: 2 },
|
|
76
|
+
{ letter: 'W', number: 3 },
|
|
77
|
+
{ letter: 'T', number: 4 },
|
|
78
|
+
{ letter: 'F', number: 5 },
|
|
79
|
+
{ letter: 'S', number: 6 },
|
|
80
|
+
],
|
|
81
|
+
weeks: [
|
|
82
|
+
{
|
|
83
|
+
'3': { date: new Date('2017-02-01'), isOutOfRange: true },
|
|
84
|
+
'4': { date: new Date('2017-02-02'), isOutOfRange: true },
|
|
85
|
+
'5': { date: new Date('2017-02-03'), isOutOfRange: true },
|
|
86
|
+
'6': { date: new Date('2017-02-04'), isOutOfRange: true },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
'0': { date: new Date('2017-02-05'), isOutOfRange: true },
|
|
90
|
+
'1': { date: new Date('2017-02-06'), isOutOfRange: true },
|
|
91
|
+
'2': { date: new Date('2017-02-07') },
|
|
92
|
+
'3': { date: new Date('2017-02-08') },
|
|
93
|
+
'4': { date: new Date('2017-02-09') },
|
|
94
|
+
'5': { date: new Date('2017-02-10'), isOutOfRange: true },
|
|
95
|
+
'6': { date: new Date('2017-02-11'), isOutOfRange: true },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
'0': { date: new Date('2017-02-12'), isOutOfRange: true },
|
|
99
|
+
'1': { date: new Date('2017-02-13'), isOutOfRange: true },
|
|
100
|
+
'2': { date: new Date('2017-02-14'), isOutOfRange: true },
|
|
101
|
+
'3': { date: new Date('2017-02-15'), isOutOfRange: true },
|
|
102
|
+
'4': { date: new Date('2017-02-16'), isOutOfRange: true },
|
|
103
|
+
'5': { date: new Date('2017-02-17'), isOutOfRange: true },
|
|
104
|
+
'6': { date: new Date('2017-02-18'), isOutOfRange: true },
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
'0': { date: new Date('2017-02-19'), isOutOfRange: true },
|
|
108
|
+
'1': { date: new Date('2017-02-20'), isOutOfRange: true },
|
|
109
|
+
'2': { date: new Date('2017-02-21'), isOutOfRange: true },
|
|
110
|
+
'3': { date: new Date('2017-02-22'), isOutOfRange: true },
|
|
111
|
+
'4': { date: new Date('2017-02-23'), isOutOfRange: true },
|
|
112
|
+
'5': { date: new Date('2017-02-24'), isOutOfRange: true },
|
|
113
|
+
'6': { date: new Date('2017-02-25'), isOutOfRange: true },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
'0': { date: new Date('2017-02-26'), isOutOfRange: true },
|
|
117
|
+
'1': { date: new Date('2017-02-27'), isOutOfRange: true },
|
|
118
|
+
'2': { date: new Date('2017-02-28'), isOutOfRange: true },
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import last from 'lodash/last';
|
|
2
|
+
|
|
3
|
+
import { getWeekDays, WeekDayInfo } from '@lumx/react/utils/date/getWeekDays';
|
|
4
|
+
import { Locale } from '@lumx/react/utils/locale/types';
|
|
5
|
+
|
|
6
|
+
type AnnotatedDay = { date: Date; isOutOfRange?: boolean };
|
|
7
|
+
type AnnotatedWeek = Partial<Record<number, AnnotatedDay>>;
|
|
8
|
+
|
|
9
|
+
interface MonthCalendar {
|
|
10
|
+
weekDays: Array<WeekDayInfo>;
|
|
11
|
+
weeks: Array<AnnotatedWeek>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get month calendar.
|
|
16
|
+
* A list of weeks with days indexed by week day number
|
|
17
|
+
*/
|
|
18
|
+
export const getMonthCalendar = (
|
|
19
|
+
locale: Locale,
|
|
20
|
+
referenceDate = new Date(),
|
|
21
|
+
rangeMinDate?: Date,
|
|
22
|
+
rangeMaxDate?: Date,
|
|
23
|
+
): MonthCalendar => {
|
|
24
|
+
const month = referenceDate.getMonth();
|
|
25
|
+
const iterDate = new Date(referenceDate.getTime());
|
|
26
|
+
iterDate.setDate(1);
|
|
27
|
+
|
|
28
|
+
const weekDays = getWeekDays(locale);
|
|
29
|
+
const lastDayOfWeek = last(weekDays) as WeekDayInfo;
|
|
30
|
+
|
|
31
|
+
const weeks: Array<AnnotatedWeek> = [];
|
|
32
|
+
let week: AnnotatedWeek = {};
|
|
33
|
+
while (iterDate.getMonth() === month) {
|
|
34
|
+
const weekDayNumber = iterDate.getDay();
|
|
35
|
+
const day: AnnotatedDay = { date: new Date(iterDate.getTime()) };
|
|
36
|
+
|
|
37
|
+
// If a range is specified, check if the day is out of range.
|
|
38
|
+
if ((rangeMinDate && iterDate <= rangeMinDate) || (rangeMaxDate && iterDate >= rangeMaxDate)) {
|
|
39
|
+
day.isOutOfRange = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
week[weekDayNumber] = day;
|
|
43
|
+
if (weekDayNumber === lastDayOfWeek.number) {
|
|
44
|
+
weeks.push(week);
|
|
45
|
+
week = {};
|
|
46
|
+
}
|
|
47
|
+
iterDate.setDate(iterDate.getDate() + 1);
|
|
48
|
+
}
|
|
49
|
+
if (Object.keys(week).length) weeks.push(week);
|
|
50
|
+
|
|
51
|
+
return { weeks, weekDays };
|
|
52
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { parseLocale } from '@lumx/react/utils/locale/parseLocale';
|
|
2
|
+
import { Locale } from '@lumx/react/utils/locale/types';
|
|
3
|
+
import { getWeekDays } from './getWeekDays';
|
|
4
|
+
|
|
5
|
+
describe(getWeekDays.name, () => {
|
|
6
|
+
const french = parseLocale('fr') as Locale;
|
|
7
|
+
const englishUS = parseLocale('en-us') as Locale;
|
|
8
|
+
const farsi = parseLocale('fa-ir') as Locale;
|
|
9
|
+
|
|
10
|
+
it('should list french week days', () => {
|
|
11
|
+
const weekDays = getWeekDays(french);
|
|
12
|
+
expect(weekDays).toEqual([
|
|
13
|
+
{ letter: 'L', number: 1 },
|
|
14
|
+
{ letter: 'M', number: 2 },
|
|
15
|
+
{ letter: 'M', number: 3 },
|
|
16
|
+
{ letter: 'J', number: 4 },
|
|
17
|
+
{ letter: 'V', number: 5 },
|
|
18
|
+
{ letter: 'S', number: 6 },
|
|
19
|
+
{ letter: 'D', number: 0 },
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should list US week days', () => {
|
|
24
|
+
const weekDays = getWeekDays(englishUS);
|
|
25
|
+
expect(weekDays).toEqual([
|
|
26
|
+
{ letter: 'S', number: 0 },
|
|
27
|
+
{ letter: 'M', number: 1 },
|
|
28
|
+
{ letter: 'T', number: 2 },
|
|
29
|
+
{ letter: 'W', number: 3 },
|
|
30
|
+
{ letter: 'T', number: 4 },
|
|
31
|
+
{ letter: 'F', number: 5 },
|
|
32
|
+
{ letter: 'S', number: 6 },
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should list fa-ir week days', () => {
|
|
37
|
+
const weekDays = getWeekDays(farsi);
|
|
38
|
+
expect(weekDays).toEqual([
|
|
39
|
+
{ letter: 'ش', number: 6 },
|
|
40
|
+
{ letter: 'ی', number: 0 },
|
|
41
|
+
{ letter: 'د', number: 1 },
|
|
42
|
+
{ letter: 'س', number: 2 },
|
|
43
|
+
{ letter: 'چ', number: 3 },
|
|
44
|
+
{ letter: 'پ', number: 4 },
|
|
45
|
+
{ letter: 'ج', number: 5 },
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
});
|