Skip to content

Commit afaf165

Browse files
authored
feat(datepicker): Allow the user to type a date into the input (#10)
* feat: Creating fake package to use parse from date-fns@next * feat(utils): Adding function to convert D and Y in the format prop to lowercase See https://date-fns.org/v2.0.0-alpha.24/docs/parse and https://git.io/fxCyr for more information regarding this breaking change * feat(datepicker): Allow the user to type a date in the input according to the format prop * docs(contributors): Adding @rallysson as a contributor
1 parent 2bdaffd commit afaf165

File tree

11 files changed

+1964
-19
lines changed

11 files changed

+1964
-19
lines changed

.all-contributorsrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@
4444
"doc",
4545
"example"
4646
]
47+
},
48+
{
49+
"login": "rallysson",
50+
"name": "Rallysson",
51+
"avatar_url": "https://avatars3.githubusercontent.com/u/12260324?v=4",
52+
"profile": "https://github.com/rallysson",
53+
"contributions": [
54+
"doc"
55+
]
4756
}
4857
]
4958
}

README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Datepickers built with [Semantic UI for React][semantic-ui-react] and [Dayzed][d
44

55
[![version][version-badge]][package]
66
[![MIT License][license-badge]][license]
7-
[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors)
7+
[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors)
88

99
## Overview
1010

@@ -69,18 +69,18 @@ More examples [here](https://react-semantic-ui-datepickers.now.sh).
6969

7070
### Own Props
7171

72-
| property | type | required | default | description |
73-
| ---------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
74-
| clearable | boolean | no | true | Allows the user to clear the value |
75-
| 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) |
76-
| keepOpenOnClear | boolean | no | false | Keeps the datepicker open (or opens it if it's closed) when the clear icon is clicked |
77-
| keepOpenOnSelect | boolean | no | false | Keeps the datepicker open when a date is selected |
78-
| locale | object | no | [en-US](https://github.com/arthurdenner/react-semantic-ui-datepickers/blob/master/src/locales/en-US.js) | Object with the labels to be used on the library PS: Feel free to submit PR's with more locales! |
79-
| onDateChange | function | yes | | Callback fired when the value changes |
80-
| type | string | no | basic | Type of input to render. Available options: 'basic' and 'range' |
81-
| filterDate | function | no | () => true | Function that receives each date and returns a boolean to indicate whether it is enabled or not |
82-
|selected | Date, arrayOf(Date) | no | | Default date selected |
83-
| pointing | string | no | 'left' | Location to render the component around input. Available options: 'left', 'right', 'top left', 'top right' |
72+
| property | type | required | default | description |
73+
| ---------------- | ------------------- | -------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
74+
| clearable | boolean | no | true | Allows the user to clear the value |
75+
| 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) |
76+
| keepOpenOnClear | boolean | no | false | Keeps the datepicker open (or opens it if it's closed) when the clear icon is clicked |
77+
| keepOpenOnSelect | boolean | no | false | Keeps the datepicker open when a date is selected |
78+
| locale | object | no | [en-US](https://github.com/arthurdenner/react-semantic-ui-datepickers/blob/master/src/locales/en-US.js) | Object with the labels to be used on the library PS: Feel free to submit PR's with more locales! |
79+
| onDateChange | function | yes | | Callback fired when the value changes |
80+
| type | string | no | basic | Type of input to render. Available options: 'basic' and 'range' |
81+
| filterDate | function | no | () => true | Function that receives each date and returns a boolean to indicate whether it is enabled or not |
82+
| selected | Date, arrayOf(Date) | no | | Default date selected |
83+
| pointing | string | no | 'left' | Location to render the component around input. Available options: 'left', 'right', 'top left', 'top right' |
8484

8585
### Form.Input Props
8686

@@ -134,8 +134,8 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
134134

135135
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
136136
<!-- prettier-ignore -->
137-
| [<img src="https://avatars0.githubusercontent.com/u/13774309?v=4" width="100px;"/><br /><sub><b>Arthur Denner</b></sub>](https://github.com/arthurdenner)<br />[💻](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=arthurdenner "Code") [🎨](#design-arthurdenner "Design") [📖](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=arthurdenner "Documentation") [💡](#example-arthurdenner "Examples") [🤔](#ideas-arthurdenner "Ideas, Planning, & Feedback") | [<img src="https://avatars2.githubusercontent.com/u/10627086?v=4" width="100px;"/><br /><sub><b>Emerson Laurentino</b></sub>](https://twitter.com/elaurent_)<br />[💻](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=emersonlaurentino "Code") [🤔](#ideas-emersonlaurentino "Ideas, Planning, & Feedback") [📖](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=emersonlaurentino "Documentation") [💡](#example-emersonlaurentino "Examples") | [<img src="https://avatars2.githubusercontent.com/u/12260334?v=4" width="100px;"/><br /><sub><b>Lucas Borges</b></sub>](https://github.com/lucasnsborges)<br />[💻](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=lucasnsborges "Code") [📖](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=lucasnsborges "Documentation") [💡](#example-lucasnsborges "Examples") |
138-
| :---: | :---: | :---: |
137+
| [<img src="https://avatars0.githubusercontent.com/u/13774309?v=4" width="100px;"/><br /><sub><b>Arthur Denner</b></sub>](https://github.com/arthurdenner)<br />[💻](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=arthurdenner "Code") [🎨](#design-arthurdenner "Design") [📖](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=arthurdenner "Documentation") [💡](#example-arthurdenner "Examples") [🤔](#ideas-arthurdenner "Ideas, Planning, & Feedback") | [<img src="https://avatars2.githubusercontent.com/u/10627086?v=4" width="100px;"/><br /><sub><b>Emerson Laurentino</b></sub>](https://twitter.com/elaurent_)<br />[💻](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=emersonlaurentino "Code") [🤔](#ideas-emersonlaurentino "Ideas, Planning, & Feedback") [📖](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=emersonlaurentino "Documentation") [💡](#example-emersonlaurentino "Examples") | [<img src="https://avatars2.githubusercontent.com/u/12260334?v=4" width="100px;"/><br /><sub><b>Lucas Borges</b></sub>](https://github.com/lucasnsborges)<br />[💻](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=lucasnsborges "Code") [📖](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=lucasnsborges "Documentation") [💡](#example-lucasnsborges "Examples") | [<img src="https://avatars3.githubusercontent.com/u/12260324?v=4" width="100px;"/><br /><sub><b>Rallysson</b></sub>](https://github.com/rallysson)<br />[📖](https://github.com/arthurdenner/react-semantic-ui-datepickers/commits?author=rallysson "Documentation") |
138+
| :---: | :---: | :---: | :---: |
139139

140140
<!-- ALL-CONTRIBUTORS-LIST:END -->
141141

date-fns-v2/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import parse from 'date-fns/parse';
2+
3+
export default { parse };

date-fns-v2/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "date-fns-v2",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"dependencies": {
7+
"date-fns": "^2.0.0-alpha.24"
8+
}
9+
}

date-fns-v2/yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
date-fns@^2.0.0-alpha.24:
6+
version "2.0.0-alpha.24"
7+
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.24.tgz#2988c137c72275af29d7d21cc53eb52b3a8c2586"
8+
integrity sha512-jpLzKHKSq0nTcZ3K5ZnTelxWmUwPepuoEaXkATwIUnc1tc+/rIooAvDMR+zdAGnwQ35eVWiyn5dikoLHjhEYeA==

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"classnames": "^2.2.5",
3535
"date-fns": "^1.29.0",
3636
"dayzed": "^2.2.0",
37+
"format-string-by-pattern": "^1.0.0",
3738
"react-fast-compare": "^2.0.1"
3839
},
3940
"devDependencies": {

src/__tests__/utils.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
isSelectable,
99
moveElementsByN,
1010
omit,
11+
parseFormatString,
1112
pick,
1213
} from '../utils';
1314

@@ -143,3 +144,11 @@ describe('formatSelectedDate', () => {
143144
);
144145
});
145146
});
147+
148+
describe('parseFormatString', () => {
149+
it('should change the case of letters D and Y', () => {
150+
expect(parseFormatString('YYYY-MM-DD')).toBe('yyyy-MM-dd');
151+
152+
expect(parseFormatString('DD/MM/yyyy')).toBe('dd/MM/yyyy');
153+
});
154+
});

src/components/datepicker.js

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import isEqual from 'react-fast-compare';
4-
import { formatSelectedDate, moveElementsByN, omit, pick } from '../utils';
4+
import isValid from 'date-fns/is_valid';
5+
import formatStringByPattern from 'format-string-by-pattern';
6+
import {
7+
formatSelectedDate,
8+
moveElementsByN,
9+
omit,
10+
parseOnBlur,
11+
pick,
12+
} from '../utils';
513
import localeEn from '../locales/en-US';
614
import BasicDatePicker from '../pickers/basic';
715
import RangeDatePicker from '../pickers/range';
@@ -96,6 +104,7 @@ class SemanticDatepicker extends React.Component {
96104
isVisible: false,
97105
selectedDate: selected || initialSelectedDate,
98106
selectedDateFormatted: formatSelectedDate(selected, format),
107+
typedValue: null,
99108
};
100109
}
101110

@@ -193,6 +202,7 @@ class SemanticDatepicker extends React.Component {
193202
const newState = {
194203
selectedDate: newDates,
195204
selectedDateFormatted: formatSelectedDate(newDates, format),
205+
typedValue: null,
196206
};
197207

198208
this.setState(newState, () => {
@@ -217,13 +227,68 @@ class SemanticDatepicker extends React.Component {
217227
isVisible: keepOpenOnSelect,
218228
selectedDate: newDate,
219229
selectedDateFormatted: formatSelectedDate(newDate, format),
230+
typedValue: null,
220231
};
221232

222233
this.setState(newState, () => {
223234
onDateChange(newDate);
224235
});
225236
};
226237

238+
handleBlur = () => {
239+
const { format } = this.props;
240+
const { typedValue } = this.state;
241+
242+
if (!typedValue) {
243+
return;
244+
}
245+
246+
const parsedValue = parseOnBlur(typedValue, format, this.isRangeInput);
247+
248+
if (this.isRangeInput) {
249+
const areDatesValid = parsedValue.every(isValid);
250+
251+
if (areDatesValid) {
252+
this.handleRangeInput(parsedValue);
253+
return;
254+
}
255+
} else {
256+
const isDateValid = isValid(parsedValue);
257+
258+
if (isDateValid) {
259+
this.handleBasicInput(parsedValue);
260+
return;
261+
}
262+
}
263+
264+
this.setState({ typedValue: null });
265+
};
266+
267+
handleChange = (evt, { value }) => {
268+
const { format, onDateChange } = this.props;
269+
const formatString = this.isRangeInput ? `${format} - ${format}` : format;
270+
271+
if (!value) {
272+
const newState = {
273+
selectedDate: this.isRangeInput ? [] : null,
274+
selectedDateFormatted: '',
275+
typedValue: null,
276+
};
277+
278+
this.setState(newState, () => {
279+
onDateChange(null);
280+
});
281+
282+
return;
283+
}
284+
285+
this.setState({
286+
selectedDate: this.isRangeInput ? [] : null,
287+
selectedDateFormatted: '',
288+
typedValue: formatStringByPattern(formatString, value),
289+
});
290+
};
291+
227292
onDateSelected = (...args) => {
228293
if (this.isRangeInput) {
229294
this.handleRangeInput(...args);
@@ -233,7 +298,12 @@ class SemanticDatepicker extends React.Component {
233298
};
234299

235300
render() {
236-
const { isVisible, selectedDate, selectedDateFormatted } = this.state;
301+
const {
302+
isVisible,
303+
selectedDate,
304+
selectedDateFormatted,
305+
typedValue,
306+
} = this.state;
237307
const { clearable, locale, pointing, filterDate } = this.props;
238308

239309
return (
@@ -247,9 +317,11 @@ class SemanticDatepicker extends React.Component {
247317
<Input
248318
{...this.inputProps}
249319
isClearIconVisible={Boolean(clearable && selectedDateFormatted)}
320+
onBlur={this.handleBlur}
321+
onChange={this.handleChange}
250322
onClear={this.resetState}
251323
onClick={this.showCalendar}
252-
value={selectedDateFormatted}
324+
value={typedValue || selectedDateFormatted}
253325
/>
254326
{isVisible && (
255327
<this.Component

src/components/input.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const CustomInput = ({
2020
/>
2121
}
2222
onClick={onClick}
23-
readOnly
2423
value={value}
2524
/>
2625
);

src/utils.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import format from 'date-fns/format';
22
import isBefore from 'date-fns/is_before';
33
import startOfDay from 'date-fns/start_of_day';
4+
import dateFnsV2 from '../date-fns-v2';
45

56
export const isSelectable = (date, minDate, maxDate) => {
67
if (
@@ -56,3 +57,28 @@ export const formatSelectedDate = (selectedDate, dateFormat) => {
5657
? selectedDate.map(date => formatDate(date, dateFormat)).join(' - ')
5758
: formatDate(selectedDate, dateFormat);
5859
};
60+
61+
export const parseFormatString = formatString =>
62+
formatString.replace(/[D, Y]/gi, a => a.toLowerCase());
63+
64+
export const parseOnBlur = (typedValue, formatString, isRangeInput) => {
65+
if (isRangeInput) {
66+
const rangeValues = typedValue.split(' - ');
67+
68+
return rangeValues
69+
.map(value =>
70+
dateFnsV2.parse(
71+
value,
72+
parseFormatString(formatString, true),
73+
new Date()
74+
)
75+
)
76+
.sort((a, b) => (a > b ? 1 : -1));
77+
}
78+
79+
return dateFnsV2.parse(
80+
typedValue,
81+
parseFormatString(formatString),
82+
new Date()
83+
);
84+
};

0 commit comments

Comments
 (0)