Skip to content

Commit 67e7235

Browse files
authored
Merge pull request #1013 from lumapps/feat/remove-moment
feat(date-picker): replace moment with vanilla JS
2 parents 52d4732 + 9fa47d6 commit 67e7235

27 files changed

+579
-72
lines changed

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Changed
11+
12+
- `@lumx/react` no long depend on `moment` or `moment-range` to generate the date picker.
13+
- Deprecated `@lumx/core/js/date-picker` functions that **will be removed in the next major version** along with `moment` and `moment-range`.
14+
- DatePicker & DatePickerField: `locale` prop is now optional (uses browser locale by default)
15+
1016
## [3.5.3][] - 2023-08-30
1117

1218
### Changed
@@ -1807,8 +1813,6 @@ _Failed released_
18071813
[3.5.0]: https://github.com/lumapps/design-system/tree/v3.5.0
18081814
[unreleased]: https://github.com/lumapps/design-system/compare/v3.5.1...HEAD
18091815
[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
1816+
[unreleased]: https://github.com/lumapps/design-system/compare/v3.5.3...HEAD
18131817
[3.5.3]: https://github.com/lumapps/design-system/compare/v3.5.2...v3.5.3
18141818
[3.5.2]: https://github.com/lumapps/design-system/tree/v3.5.2

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ interface AnnotatedDate {
1616
/**
1717
* Get the list of days in a week based on locale.
1818
*
19+
* @deprecated will be removed in next major version along with the removal of moment (no replacement planned)
20+
*
1921
* @param locale The locale using to generate the order of days in a week.
2022
* @return The list of days in a week based on locale.
2123
*/
@@ -26,6 +28,8 @@ export function getWeekDays(locale: string): Moment[] {
2628
/**
2729
* Get month calendar based on locale and start date.
2830
*
31+
* @deprecated will be removed in next major version along with the removal of moment (no replacement planned)
32+
*
2933
* @param locale The locale using to generate the order of days in a week.
3034
* @param selectedMonth The selected month.
3135
* @return The list of days in a week based on locale.
@@ -44,6 +48,8 @@ export function getMonthCalendar(locale: string, selectedMonth?: Moment): Moment
4448
* Get month calendar based on locale and start date.
4549
* Each day is annotated to know if they are displayed and/or clickable.
4650
*
51+
* @deprecated will be removed in next major version along with the removal of moment (no replacement planned)
52+
*
4753
* @param locale The locale using to generate the order of days in a week.
4854
* @param minDate The first selectable date.
4955
* @param maxDate The last selectable date.

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
},
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
};

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: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
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 { 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';
811
import { CLASSNAME } from './constants';
912

1013
/**
@@ -33,7 +36,7 @@ const COMPONENT_NAME = 'DatePickerControlled';
3336
*/
3437
export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElement> = forwardRef((props, ref) => {
3538
const {
36-
locale,
39+
locale = getCurrentLocale(),
3740
maxDate,
3841
minDate,
3942
nextButtonProps,
@@ -45,14 +48,11 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
4548
todayOrSelectedDateRef,
4649
value,
4750
} = props;
48-
const days = React.useMemo(() => {
49-
return getAnnotatedMonthCalendar(locale, minDate, maxDate, moment(selectedMonth));
51+
const { weeks, weekDays } = React.useMemo(() => {
52+
const localeObj = parseLocale(locale) as Locale;
53+
return getMonthCalendar(localeObj, selectedMonth, minDate, maxDate);
5054
}, [locale, minDate, maxDate, selectedMonth]);
5155

52-
const weekDays = React.useMemo(() => {
53-
return getWeekDays(locale);
54-
}, [locale]);
55-
5656
return (
5757
<div ref={ref} className={`${CLASSNAME}`}>
5858
<Toolbar
@@ -75,49 +75,46 @@ export const DatePickerControlled: Comp<DatePickerControlledProps, HTMLDivElemen
7575
}
7676
label={
7777
<span className={`${CLASSNAME}__month`}>
78-
{moment(selectedMonth).locale(locale).format('MMMM YYYY')}
78+
{selectedMonth.toLocaleDateString(locale, { year: 'numeric', month: 'long' })}
7979
</span>
8080
}
8181
/>
8282
<div className={`${CLASSNAME}__calendar`}>
8383
<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>
84+
{weekDays.map(({ letter, number }) => (
85+
<div key={number} className={`${CLASSNAME}__day-wrapper`}>
86+
<span className={`${CLASSNAME}__week-day`}>{letter.toLocaleUpperCase()}</span>
8987
</div>
9088
))}
9189
</div>
9290

9391
<div className={`${CLASSNAME}__month-days ${CLASSNAME}__days-wrapper`}>
94-
{days.map((annotatedDate) => {
95-
if (annotatedDate.isDisplayed) {
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+
9699
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>
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+
)}
117115
</div>
118116
);
119-
}
120-
return <div key={annotatedDate.date.unix()} className={`${CLASSNAME}__day-wrapper`} />;
117+
});
121118
})}
122119
</div>
123120
</div>

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export default {
88
component: DatePickerField,
99
args: {
1010
...DatePickerField.defaultProps,
11-
locale: 'fr',
1211
'nextButtonProps.label': 'Next month',
1312
'previousButtonProps.label': 'Previous month',
1413
},

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

Lines changed: 9 additions & 8 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 { getCurrentLocale } from '@lumx/react/utils/locale/getCurrentLocale';
108

119
/**
1210
* Defines the props of the component.
@@ -17,7 +15,7 @@ export interface DatePickerFieldProps extends GenericProps {
1715
/** Whether the component is disabled or not. */
1816
isDisabled?: boolean;
1917
/** Locale (language or region) to use. */
20-
locale: string;
18+
locale?: string;
2119
/** Date after which dates can't be selected. */
2220
maxDate?: Date;
2321
/** Date before which dates can't be selected. */
@@ -52,7 +50,7 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
5250
defaultMonth,
5351
disabled,
5452
isDisabled = disabled,
55-
locale,
53+
locale = getCurrentLocale(),
5654
maxDate,
5755
minDate,
5856
name,
@@ -97,6 +95,9 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
9795
onClose();
9896
};
9997

98+
// Format date for text field
99+
const textFieldValue = value?.toLocaleDateString(locale, { year: 'numeric', month: 'long', day: 'numeric' }) || '';
100+
100101
return (
101102
<>
102103
<TextField
@@ -105,7 +106,7 @@ export const DatePickerField: Comp<DatePickerFieldProps, HTMLDivElement> = forwa
105106
name={name}
106107
forceFocusStyle={isOpen}
107108
textFieldRef={anchorRef}
108-
value={value ? moment(value).locale(locale).format('LL') : ''}
109+
value={textFieldValue}
109110
onClick={toggleSimpleMenu}
110111
onChange={onTextFieldChange}
111112
onKeyPress={handleKeyboardNav}

packages/lumx-react/src/components/date-picker/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export interface DatePickerProps extends GenericProps {
99
/** Default month. */
1010
defaultMonth?: Date;
1111
/** Locale (language or region) to use. */
12-
locale: string;
12+
locale?: string;
1313
/** Date after which dates can't be selected. */
1414
maxDate?: Date;
1515
/** Date before which dates can't be selected. */

0 commit comments

Comments
 (0)