Skip to content

Commit c5c09ac

Browse files
committed
feat(date-picker): replace moment with vanilla JS
1 parent 52d4732 commit c5c09ac

31 files changed

+434
-167
lines changed

CHANGELOG.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Removed
11+
12+
- _[BREAKING]_ `@lumx/core/js/utils/date-picker` has been removed (replaced with internal utils we do not plan to expose yet)
13+
14+
### Changed
15+
16+
- `@lumx/core` and `@lumx/react` no long depend on `moment` or `moment-range`.
17+
1018
## [3.5.3][] - 2023-08-30
1119

1220
### Changed
@@ -1807,8 +1815,6 @@ _Failed released_
18071815
[3.5.0]: https://github.com/lumapps/design-system/tree/v3.5.0
18081816
[unreleased]: https://github.com/lumapps/design-system/compare/v3.5.1...HEAD
18091817
[3.5.1]: https://github.com/lumapps/design-system/tree/v3.5.1
1810-
1811-
1812-
[Unreleased]: https://github.com/lumapps/design-system/compare/v3.5.3...HEAD
1818+
[unreleased]: https://github.com/lumapps/design-system/compare/v3.5.3...HEAD
18131819
[3.5.3]: https://github.com/lumapps/design-system/compare/v3.5.2...v3.5.3
18141820
[3.5.2]: https://github.com/lumapps/design-system/tree/v3.5.2

packages/lumx-core/package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,5 @@
7373
"webpack": "^4.44.1",
7474
"webpack-notifier": "^1.8.0",
7575
"webpackbar": "^4.0.0"
76-
},
77-
"peerDependencies": {
78-
"moment": ">= 2",
79-
"moment-range": "^4.0.2"
8076
}
8177
}

packages/lumx-core/src/js/date-picker.ts

Lines changed: 0 additions & 71 deletions
This file was deleted.

packages/lumx-core/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ module.exports = {
143143
),
144144
},
145145

146-
externals: ['moment', 'moment-range'],
146+
externals: [],
147147

148148
bail: true,
149149
devtool: 'source-map',

packages/lumx-react/.storybook/preview.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import type { Preview } from '@storybook/react';
22
import { withStoryBlockDecorator } from './story-block/decorator';
33
import { Theme } from '@lumx/react';
44

5-
/**
6-
* Import non default language to test moment local change.
7-
*/
8-
import 'moment/dist/locale/fr';
9-
105
const preview: Preview = {
116
globalTypes: {
127
/** Add Theme switcher in the toolbar */

packages/lumx-react/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,6 @@
7979
},
8080
"peerDependencies": {
8181
"lodash": "4.17.21",
82-
"moment": ">= 2",
83-
"moment-range": "^4.0.2",
8482
"react": ">= 16.13.0",
8583
"react-dom": ">= 16.13.0"
8684
},

packages/lumx-react/src/components/date-picker/DatePicker.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import moment from 'moment';
21
import React, { forwardRef, useState } from 'react';
32
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';
45
import { CLASSNAME, COMPONENT_NAME } from './constants';
56
import { DatePickerControlled } from './DatePickerControlled';
67
import { DatePickerProps } from './types';
@@ -14,17 +15,13 @@ import { DatePickerProps } from './types';
1415
*/
1516
export const DatePicker: Comp<DatePickerProps, HTMLDivElement> = forwardRef((props, ref) => {
1617
const { defaultMonth, locale, value, onChange, ...forwardedProps } = props;
17-
let castedValue;
18-
if (value) {
19-
castedValue = moment(value);
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)) {
2421
// eslint-disable-next-line no-console
25-
console.warn(`[@lumx/react/DatePicker] Invalid date provided ${castedValue}`);
22+
console.warn(`[@lumx/react/DatePicker] Invalid date provided ${referenceDate}`);
23+
referenceDate = new Date();
2624
}
27-
const selectedDay = castedValue && castedValue.isValid() ? castedValue : moment();
2825

2926
const [monthOffset, setMonthOffset] = useState(0);
3027

@@ -36,7 +33,7 @@ export const DatePicker: Comp<DatePickerProps, HTMLDivElement> = forwardRef((pro
3633
setMonthOffset(0);
3734
};
3835

39-
const selectedMonth = moment(selectedDay).locale(locale).add(monthOffset, 'months').toDate();
36+
const selectedMonth = addMonthResetDay(referenceDate, monthOffset);
4037

4138
return (
4239
<DatePickerControlled

packages/lumx-react/src/components/date-picker/DatePickerControlled.tsx

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import React, { forwardRef } from 'react';
2-
import moment from 'moment';
32
import classNames from 'classnames';
43
import { DatePickerProps, Emphasis, IconButton, Toolbar } from '@lumx/react';
54
import { mdiChevronLeft, mdiChevronRight } from '@lumx/icons';
6-
import { getAnnotatedMonthCalendar, getWeekDays } from '@lumx/core/js/date-picker';
75
import { Comp } from '@lumx/react/utils/type';
6+
import { getMonthCalendar } from '@lumx/react/utils/date/getMonthCalendar';
7+
import { listWeekDays } from '@lumx/react/utils/date/listWeekDays';
8+
import { isSameDay } from '@lumx/react/utils/date/isSameDay';
9+
import { formatDayNumber, formatMonthYear } from '@lumx/react/utils';
810
import { CLASSNAME } from './constants';
911

1012
/**
@@ -45,13 +47,14 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
4547
todayOrSelectedDateRef,
4648
value,
4749
} = props;
48-
const days = React.useMemo(() => {
49-
return getAnnotatedMonthCalendar(locale, minDate, maxDate, moment(selectedMonth));
50-
}, [locale, minDate, maxDate, selectedMonth]);
50+
const weeks = React.useMemo(() => getMonthCalendar(locale, selectedMonth, minDate, maxDate), [
51+
locale,
52+
minDate,
53+
maxDate,
54+
selectedMonth,
55+
]);
5156

52-
const weekDays = React.useMemo(() => {
53-
return getWeekDays(locale);
54-
}, [locale]);
57+
const weekDays = React.useMemo(() => listWeekDays(locale), [locale]);
5558

5659
return (
5760
<div ref={ref} className={`${CLASSNAME}`}>
@@ -73,51 +76,44 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
7376
onClick={onPrevMonthChange}
7477
/>
7578
}
76-
label={
77-
<span className={`${CLASSNAME}__month`}>
78-
{moment(selectedMonth).locale(locale).format('MMMM YYYY')}
79-
</span>
80-
}
79+
label={<span className={`${CLASSNAME}__month`}>{formatMonthYear(selectedMonth, locale)}</span>}
8180
/>
8281
<div className={`${CLASSNAME}__calendar`}>
8382
<div className={`${CLASSNAME}__week-days ${CLASSNAME}__days-wrapper`}>
84-
{weekDays.map((weekDay) => (
85-
<div key={weekDay.unix()} className={`${CLASSNAME}__day-wrapper`}>
86-
<span className={`${CLASSNAME}__week-day`}>
87-
{weekDay.format('dddd').slice(0, 1).toLocaleUpperCase()}
88-
</span>
83+
{weekDays.map(({ letter, number }) => (
84+
<div key={number} className={`${CLASSNAME}__day-wrapper`}>
85+
<span className={`${CLASSNAME}__week-day`}>{letter.toLocaleUpperCase()}</span>
8986
</div>
9087
))}
9188
</div>
9289

9390
<div className={`${CLASSNAME}__month-days ${CLASSNAME}__days-wrapper`}>
94-
{days.map((annotatedDate) => {
95-
if (annotatedDate.isDisplayed) {
91+
{weeks.flatMap((week, weekIndex) => {
92+
return weekDays.map((weekDay, dayIndex) => {
93+
const { date, isInRange } = week[weekDay.number] || {};
94+
const key = `${weekIndex}-${dayIndex}`;
95+
const isToday = date && isSameDay(date, new Date());
96+
const isSelected = date && value && isSameDay(value, date);
97+
9698
return (
97-
<div key={annotatedDate.date.unix()} className={`${CLASSNAME}__day-wrapper`}>
98-
<button
99-
ref={
100-
(value && annotatedDate.date.isSame(value, 'day')) ||
101-
(!value && annotatedDate.isToday)
102-
? todayOrSelectedDateRef
103-
: null
104-
}
105-
className={classNames(`${CLASSNAME}__month-day`, {
106-
[`${CLASSNAME}__month-day--is-selected`]:
107-
value && annotatedDate.date.isSame(value, 'day'),
108-
[`${CLASSNAME}__month-day--is-today`]:
109-
annotatedDate.isClickable && annotatedDate.isToday,
110-
})}
111-
disabled={!annotatedDate.isClickable}
112-
type="button"
113-
onClick={() => onChange(moment(annotatedDate.date).toDate())}
114-
>
115-
<span>{annotatedDate.date.format('DD')}</span>
116-
</button>
99+
<div key={key} className={`${CLASSNAME}__day-wrapper`}>
100+
{date && (
101+
<button
102+
ref={isSelected || (!value && isToday) ? todayOrSelectedDateRef : null}
103+
className={classNames(`${CLASSNAME}__month-day`, {
104+
[`${CLASSNAME}__month-day--is-selected`]: value && isSelected,
105+
[`${CLASSNAME}__month-day--is-today`]: isInRange && isToday,
106+
})}
107+
disabled={!isInRange}
108+
type="button"
109+
onClick={() => onChange(date)}
110+
>
111+
<span>{formatDayNumber(date, locale)}</span>
112+
</button>
113+
)}
117114
</div>
118115
);
119-
}
120-
return <div key={annotatedDate.date.unix()} className={`${CLASSNAME}__day-wrapper`} />;
116+
});
121117
})}
122118
</div>
123119
</div>

packages/lumx-react/src/components/date-picker/DatePickerField.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
61
import React, { forwardRef, SyntheticEvent, useCallback, useRef, useState } from 'react';
72

3+
import { DatePicker, IconButtonProps, Placement, Popover, TextField } from '@lumx/react';
4+
import { useFocusTrap } from '@lumx/react/hooks/useFocusTrap';
85
import { useFocus } from '@lumx/react/hooks/useFocus';
96
import { Comp, GenericProps } from '@lumx/react/utils/type';
7+
import { formatDate } from '@lumx/react/utils';
108

119
/**
1210
* Defines the props of the component.
@@ -105,7 +103,7 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
105103
name={name}
106104
forceFocusStyle={isOpen}
107105
textFieldRef={anchorRef}
108-
value={value ? moment(value).locale(locale).format('LL') : ''}
106+
value={value ? formatDate(value, locale) : ''}
109107
onClick={toggleSimpleMenu}
110108
onChange={onTextFieldChange}
111109
onKeyPress={handleKeyboardNav}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)