Skip to content

Commit c026caa

Browse files
arthurdenneruchtiDenis Chumachenko
authored
feat(datepicker): add formatOptions prop (#661)
* added formatOptions prop (#631) * added format options for date-fns, necessary to support locales * fixed the property Co-authored-by: Denis Chumachenko <[email protected]> * fix: update dependencies and types * docs: update README Co-authored-by: Denis <[email protected]> Co-authored-by: Denis Chumachenko <[email protected]>
1 parent c663207 commit c026caa

File tree

7 files changed

+83
-45
lines changed

7 files changed

+83
-45
lines changed

README.md

+23-22
Original file line numberDiff line numberDiff line change
@@ -73,28 +73,29 @@ More examples [here](https://react-semantic-ui-datepickers.now.sh).
7373

7474
### Own Props
7575

76-
| property | type | required | default | description |
77-
| -------------------- | ----------------------------------- | -------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78-
| allowOnlyNumbers | boolean | no | true | Allows the user enter only numbers |
79-
| autoComplete | string | no | -- | Specifies if the input should have autocomplete enabled |
80-
| clearIcon | SemanticICONS \| React.ReactElement | no | 'close' | An [icon from semantic-ui-react](https://react.semantic-ui.com/elements/icon/) or a custom component. The custom component will get two props: `data-testid` and `onClick`. |
81-
| clearOnSameDateClick | boolean | no | true | Controls whether the datepicker's state resets if the same date is selected in succession. |
82-
| clearable | boolean | no | true | Allows the user to clear the value |
83-
| datePickerOnly | boolean | no | false | Allows the date to be selected only via the date picker and disables the text input |
84-
| filterDate | function | no | (date) => true | Function that receives each date and returns a boolean to indicate whether it is enabled or not |
85-
| format | string | no | 'YYYY-MM-DD' | Specifies how the date will be formatted using the [date-fns' format](https://date-fns.org/v1.29.0/docs/format) |
86-
| icon | SemanticICONS \| React.ReactElement | no | 'calendar' | An [icon from semantic-ui-react](https://react.semantic-ui.com/elements/icon/) or a custom component. The custom component will get two props: `data-testid` and `onClick`. |
87-
| inline | boolean | no | false | Uses an inline variant, without the input and the features related to it, e.g. clearable datepicker |
88-
| keepOpenOnClear | boolean | no | false | Keeps the datepicker open (or opens it if it's closed) when the clear icon is clicked |
89-
| keepOpenOnSelect | boolean | no | false | Keeps the datepicker open when a date is selected |
90-
| locale | string | no | 'en-US' | Filename of the locale to be used. PS: Feel free to submit PR's with more locales! |
91-
| onBlur | function | no | (event) => {} | Callback fired when the input loses focus |
92-
| onFocus | function | no | (event) => {} | Callback fired when the input gets focused focus |
93-
| onChange | function | no | (event, data) => {} | Callback fired when the value changes |
94-
| pointing | string | no | 'left' | Location to render the component around input. Available options: 'left', 'right', 'top left', 'top right' |
95-
| showToday | boolean | no | true | Hides the "Today" button if false |
96-
| type | string | no | basic | Type of input to render. Available options: 'basic' and 'range' |
97-
| value | Date\|Date[] | no | -- | The value of the datepicker |
76+
| property | type | required | default | description |
77+
| -------------------- | ------------------------------------------------------------- | -------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78+
| allowOnlyNumbers | boolean | no | true | Allows the user enter only numbers |
79+
| autoComplete | string | no | -- | Specifies if the input should have autocomplete enabled |
80+
| clearIcon | SemanticICONS \| React.ReactElement | no | 'close' | An [icon from semantic-ui-react](https://react.semantic-ui.com/elements/icon/) or a custom component. The custom component will get two props: `data-testid` and `onClick`. |
81+
| clearOnSameDateClick | boolean | no | true | Controls whether the datepicker's state resets if the same date is selected in succession. |
82+
| clearable | boolean | no | true | Allows the user to clear the value |
83+
| datePickerOnly | boolean | no | false | Allows the date to be selected only via the date picker and disables the text input |
84+
| filterDate | function | no | (date) => true | Function that receives each date and returns a boolean to indicate whether it is enabled or not |
85+
| format | string | no | 'YYYY-MM-DD' | Specifies how to format the date using the [date-fns' format](https://date-fns.org/v2.24.0/docs/format) |
86+
| formatOptions | [options](https://date-fns.org/v2.24.0/docs/format#arguments) | no | -- | Specifies the options to format the date using the [date-fns' format](https://date-fns.org/v2.24.0/docs/format) |
87+
| icon | SemanticICONS \| React.ReactElement | no | 'calendar' | An [icon from semantic-ui-react](https://react.semantic-ui.com/elements/icon/) or a custom component. The custom component will get two props: `data-testid` and `onClick`. |
88+
| inline | boolean | no | false | Uses an inline variant, without the input and the features related to it, e.g. clearable datepicker |
89+
| keepOpenOnClear | boolean | no | false | Keeps the datepicker open (or opens it if it's closed) when the clear icon is clicked |
90+
| keepOpenOnSelect | boolean | no | false | Keeps the datepicker open when a date is selected |
91+
| locale | string | no | 'en-US' | Filename of the locale to be used. PS: Feel free to submit PR's with more locales! |
92+
| onBlur | function | no | (event) => {} | Callback fired when the input loses focus |
93+
| onFocus | function | no | (event) => {} | Callback fired when the input gets focused focus |
94+
| onChange | function | no | (event, data) => {} | Callback fired when the value changes |
95+
| pointing | string | no | 'left' | Location to render the component around input. Available options: 'left', 'right', 'top left', 'top right' |
96+
| showToday | boolean | no | true | Hides the "Today" button if false |
97+
| type | string | no | basic | Type of input to render. Available options: 'basic' and 'range' |
98+
| value | Date\|Date[] | no | -- | The value of the datepicker |
9899

99100
### Form.Input Props
100101

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"babel-jest": "27.2.4",
5757
"babel-loader": "8.2.2",
5858
"cssnano": "4.1.11",
59-
"eslint-plugin-prettier": "4.0.0",
59+
"eslint-plugin-prettier": "3.4.1",
6060
"husky": "7.0.2",
6161
"jest-transform-css": "2.1.0",
6262
"prettier": "2.4.1",

src/__tests__/utils.test.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import parseISO from 'date-fns/parseISO';
22
import startOfDay from 'date-fns/startOfDay';
3-
import localeEn from '../locales/en-US.json';
3+
import ruLocale from 'date-fns/locale/ru';
4+
import enLocale from '../locales/en-US.json';
45
import {
56
formatDate,
67
formatSelectedDate,
@@ -24,11 +25,11 @@ const june28 = parseISO('2018-06-28');
2425

2526
describe('moveElementsByN', () => {
2627
it('should return the same array if `n` is zero', () => {
27-
expect(moveElementsByN(0, localeEn.weekdays)).toEqual(localeEn.weekdays);
28+
expect(moveElementsByN(0, enLocale.weekdays)).toEqual(enLocale.weekdays);
2829
});
2930

3031
it('should return the correct array if `n` is different than zero', () => {
31-
expect(moveElementsByN(3, localeEn.weekdays)).toEqual([
32+
expect(moveElementsByN(3, enLocale.weekdays)).toEqual([
3233
'Wednesday',
3334
'Thursday',
3435
'Friday',
@@ -38,7 +39,7 @@ describe('moveElementsByN', () => {
3839
'Tuesday',
3940
]);
4041

41-
expect(moveElementsByN(5, localeEn.weekdays)).toEqual([
42+
expect(moveElementsByN(5, enLocale.weekdays)).toEqual([
4243
'Friday',
4344
'Saturday',
4445
'Sunday',
@@ -75,9 +76,15 @@ describe('formatDate', () => {
7576
expect(formatDate(null, 'DD/MM/YYYY')).toBe(undefined);
7677
});
7778

78-
it('should format correctly if a valid date is given', () => {
79+
it('should format correctly if a valid date and a locale are not given', () => {
7980
expect(formatDate(dateTest, 'DD/MM/YYYY')).toBe('21/06/2018');
8081
});
82+
83+
it('should format correctly if a valid date and a locale are given', () => {
84+
expect(formatDate(dateTest, 'EEE, d MMM YYYY', { locale: ruLocale })).toBe(
85+
'чтв, 4 июнь 2018'
86+
);
87+
});
8188
});
8289

8390
describe('isSelectable', () => {
@@ -150,6 +157,14 @@ describe('formatSelectedDate', () => {
150157
'14/06/2018 - 20/06/2018'
151158
);
152159
});
160+
161+
it('should return the correct result if a valid array of dates is given', () => {
162+
expect(
163+
formatSelectedDate([june14, june20], 'EEE, d MMM YYYY', {
164+
locale: ruLocale,
165+
})
166+
).toBe('чтв, 4 июнь 2018 - срд, 3 июнь 2018');
167+
});
153168
});
154169

155170
describe('parseFormatString', () => {

src/index.tsx

+16-7
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,14 @@ class SemanticDatepicker extends React.Component<
126126
}
127127

128128
get initialState() {
129-
const { format, value } = this.props;
129+
const { format, value, formatOptions } = this.props;
130130
const initialSelectedDate = this.isRangeInput ? [] : null;
131131

132132
return {
133133
isVisible: false,
134134
locale: this.locale,
135135
selectedDate: value || initialSelectedDate,
136-
selectedDateFormatted: formatSelectedDate(value, format),
136+
selectedDateFormatted: formatSelectedDate(value, format, formatOptions),
137137
typedValue: null,
138138
};
139139
}
@@ -264,7 +264,7 @@ class SemanticDatepicker extends React.Component<
264264
};
265265

266266
handleRangeInput = (newDates, event) => {
267-
const { format, keepOpenOnSelect, onChange } = this.props;
267+
const { format, keepOpenOnSelect, onChange, formatOptions } = this.props;
268268

269269
if (!newDates || !newDates.length) {
270270
this.resetState(event);
@@ -274,7 +274,11 @@ class SemanticDatepicker extends React.Component<
274274

275275
const newState = {
276276
selectedDate: newDates,
277-
selectedDateFormatted: formatSelectedDate(newDates, format),
277+
selectedDateFormatted: formatSelectedDate(
278+
newDates,
279+
format,
280+
formatOptions
281+
),
278282
typedValue: null,
279283
};
280284

@@ -288,8 +292,13 @@ class SemanticDatepicker extends React.Component<
288292
};
289293

290294
handleBasicInput = (newDate, event) => {
291-
const { format, keepOpenOnSelect, onChange, clearOnSameDateClick } =
292-
this.props;
295+
const {
296+
format,
297+
keepOpenOnSelect,
298+
onChange,
299+
clearOnSameDateClick,
300+
formatOptions,
301+
} = this.props;
293302

294303
if (!newDate) {
295304
// if clearOnSameDateClick is true (this is the default case)
@@ -313,7 +322,7 @@ class SemanticDatepicker extends React.Component<
313322
const newState = {
314323
isVisible: keepOpenOnSelect,
315324
selectedDate: newDate,
316-
selectedDateFormatted: formatSelectedDate(newDate, format),
325+
selectedDateFormatted: formatSelectedDate(newDate, format, formatOptions),
317326
typedValue: null,
318327
};
319328

src/types/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import format from 'date-fns/format';
12
import { Props as DayzedProps, RenderProps } from 'dayzed';
23
import { FormInputProps, SemanticICONS } from 'semantic-ui-react';
34

@@ -59,6 +60,8 @@ export type PickedFormInputProps = Pick<
5960
| 'readOnly'
6061
>;
6162

63+
export type FnsFormatOptions = Parameters<typeof format>[2];
64+
6265
export type SemanticDatepickerProps = PickedDayzedProps &
6366
PickedFormInputProps & {
6467
allowOnlyNumbers: boolean;
@@ -68,6 +71,7 @@ export type SemanticDatepickerProps = PickedDayzedProps &
6871
clearIcon?: SemanticICONS | React.ReactElement;
6972
filterDate: (date: Date) => boolean;
7073
format: string;
74+
formatOptions?: FnsFormatOptions;
7175
keepOpenOnClear: boolean;
7276
keepOpenOnSelect: boolean;
7377
icon?: SemanticICONS | React.ReactElement;

src/utils.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import isBefore from 'date-fns/isBefore';
44
import parse from 'date-fns/parse';
55
import startOfDay from 'date-fns/startOfDay';
66
import { DateObj } from 'dayzed';
7-
import { Object } from './types';
7+
import { Object, FnsFormatOptions } from './types';
88

99
export const keys = {
1010
enter: 13,
@@ -36,8 +36,14 @@ export const getToday = (minDate?: Date, maxDate?: Date): DateObj => {
3636
};
3737
};
3838

39-
export const formatDate = (date: Date | null, dateFormat: string) =>
40-
date ? format(startOfDay(date), convertTokens(dateFormat)) : undefined;
39+
export const formatDate = (
40+
date: Date | null,
41+
dateFormat: string,
42+
formatOptions?: FnsFormatOptions
43+
) =>
44+
date
45+
? format(startOfDay(date), convertTokens(dateFormat), formatOptions)
46+
: undefined;
4147

4248
export const omit = (keysToOmit: string[], obj: Object) => {
4349
const newObj = { ...obj };
@@ -62,15 +68,18 @@ export const moveElementsByN = <T>(n: number, arr: T[]) =>
6268

6369
export const formatSelectedDate = (
6470
selectedDate: Date | Date[] | null | undefined,
65-
dateFormat: string
71+
dateFormat: string,
72+
formatOptions?: FnsFormatOptions
6673
) => {
6774
if (!selectedDate) {
6875
return '';
6976
}
7077

7178
return Array.isArray(selectedDate)
72-
? selectedDate.map((date) => formatDate(date, dateFormat)).join(' - ')
73-
: formatDate(selectedDate, dateFormat);
79+
? selectedDate
80+
.map((date) => formatDate(date, dateFormat, formatOptions))
81+
.join(' - ')
82+
: formatDate(selectedDate, dateFormat, formatOptions);
7483
};
7584

7685
export const parseFormatString = (formatString: string) =>

0 commit comments

Comments
 (0)