Skip to content

Commit 968a311

Browse files
committed
feat(inputs): Formatting input value while typing and supporting some Input's props
1 parent 5ec9aeb commit 968a311

File tree

6 files changed

+167
-56
lines changed

6 files changed

+167
-56
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"classnames": "^2.2.5",
2323
"date-fns": "^1.29.0",
2424
"dayzed": "^2.0.1",
25+
"format-string-by-pattern": "^0.3.0",
2526
"prop-types": "^15.6.1",
2627
"react": "16.2",
2728
"react-dom": "16.2",
@@ -37,7 +38,8 @@
3738
"eslintConfig": {
3839
"extends": "./node_modules/kcd-scripts/eslint.js",
3940
"rules": {
40-
"import/no-unassigned-import": 0
41+
"import/no-unassigned-import": 0,
42+
"no-console": 1
4143
}
4244
},
4345
"eslintIgnore": [

src/components/calendar/calendar.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1+
.clndr-calendar {
2+
margin: 0 !important;
3+
}
4+
15
.clndr-control {
26
display: grid;
3-
grid-template-columns: auto auto 1fr auto auto;
7+
grid-template-columns: 1fr 1fr 1fr;
48
margin-bottom: 10px;
59
align-items: center;
610
}
711

12+
.clndr-control > div {
13+
display: flex;
14+
justify-content: var(--justify, flex-start);
15+
}
16+
817
.clndr-control-month {
918
padding: 0 20px;
1019
text-align: center;

src/components/calendar/calendar.js

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { Fragment } from 'react';
22
import PropTypes from 'prop-types';
33
import { Segment } from 'semantic-ui-react';
44
import Dayzed from 'dayzed';
@@ -11,6 +11,8 @@ import './calendar.css';
1111

1212
const Calendar = ({
1313
fluid,
14+
minDate,
15+
maxDate,
1416
onDateSelected,
1517
selected,
1618
selectedClassName,
@@ -19,39 +21,57 @@ const Calendar = ({
1921
}) => (
2022
<Dayzed
2123
{...otherProps}
24+
minDate={minDate}
25+
maxDate={maxDate}
2226
onDateSelected={onDateSelected}
2327
selected={selected}
2428
render={({ calendars, getBackProps, getForwardProps, getDateProps }) =>
25-
calendars.map(calendar => (
26-
<Segment compact={!fluid} key={`${calendar.year}-${calendar.month}`}>
29+
calendars.map((calendar, calendarIdx) => (
30+
<Segment
31+
className="clndr-calendar"
32+
compact={!fluid}
33+
key={`${calendar.year}-${calendar.month}`}
34+
>
2735
<div className="clndr-control">
28-
<Button
29-
icon="angle double left"
30-
title="Last year"
31-
{...getBackProps({ calendars, offset: 12 })}
32-
/>
33-
<Button
34-
icon="angle left"
35-
style={{ marginRight: 0 }}
36-
title="Last month"
37-
{...getBackProps({ calendars })}
38-
/>
36+
<div>
37+
{calendarIdx === 0 && (
38+
<Fragment>
39+
<Button
40+
icon="angle double left"
41+
title="Last year"
42+
{...getBackProps({ calendars, offset: 12 })}
43+
/>
44+
<Button
45+
icon="angle left"
46+
style={{ marginRight: 0 }}
47+
title="Last month"
48+
{...getBackProps({ calendars })}
49+
/>
50+
</Fragment>
51+
)}
52+
</div>
3953

4054
<span className="clndr-control-month">
4155
{monthNamesShort[calendar.month]} {calendar.year}
4256
</span>
4357

44-
<Button
45-
icon="angle right"
46-
title="Next month"
47-
{...getForwardProps({ calendars })}
48-
/>
49-
<Button
50-
icon="angle double right"
51-
style={{ marginRight: 0 }}
52-
title="Next year"
53-
{...getForwardProps({ calendars, offset: 12 })}
54-
/>
58+
<div style={{ '--justify': 'flex-end' }}>
59+
{calendarIdx === calendars.length - 1 && (
60+
<Fragment>
61+
<Button
62+
icon="angle right"
63+
title="Next month"
64+
{...getForwardProps({ calendars })}
65+
/>
66+
<Button
67+
icon="angle double right"
68+
style={{ marginRight: 0 }}
69+
title="Next year"
70+
{...getForwardProps({ calendars, offset: 12 })}
71+
/>
72+
</Fragment>
73+
)}
74+
</div>
5575
</div>
5676
<div className="clndr-days">
5777
{weekdayNamesShort.map(weekday => (
@@ -62,8 +82,8 @@ const Calendar = ({
6282
</CalendarCell>
6383
))}
6484
{calendar.weeks.map(week =>
65-
week.map((dateObj, index) => {
66-
const key = `${calendar.year}-${calendar.month}-${index}`;
85+
week.map((dateObj, weekIdx) => {
86+
const key = `${calendar.year}-${calendar.month}-${weekIdx}`;
6787

6888
return dateObj ? (
6989
<CalendarCell
@@ -82,8 +102,10 @@ const Calendar = ({
82102
</div>
83103
{showToday && (
84104
<TodayButton
85-
{...getToday(selected)}
86-
{...getDateProps({ dateObj: getToday(selected) })}
105+
{...getToday(selected, minDate, maxDate)}
106+
{...getDateProps({
107+
dateObj: getToday(selected, minDate, maxDate),
108+
})}
87109
>
88110
Today
89111
</TodayButton>
@@ -96,6 +118,8 @@ const Calendar = ({
96118

97119
Calendar.propTypes = {
98120
fluid: PropTypes.bool,
121+
maxDate: PropTypes.instanceOf(Date),
122+
minDate: PropTypes.instanceOf(Date),
99123
onDateSelected: PropTypes.func,
100124
selected: PropTypes.instanceOf(Date),
101125
selectedClassName: PropTypes.string,
@@ -104,6 +128,8 @@ Calendar.propTypes = {
104128

105129
Calendar.defaultProps = {
106130
onDateSelected: () => {},
131+
maxDate: null,
132+
minDate: null,
107133
showToday: true,
108134
};
109135

src/inputs/simple.js

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,25 @@ import { Form } from 'semantic-ui-react';
44
import isEqual from 'date-fns/is_equal';
55
import isValid from 'date-fns/is_valid';
66
import parse from 'date-fns/parse';
7-
import { formatDate } from '../utils';
7+
import formatByPattern from 'format-string-by-pattern';
8+
import { formatDate, omit, pick, semanticInputProps } from '../utils';
89
import Calendar from '../components/calendar';
910
import Portal from '../components/portal';
1011

1112
class SimpleInput extends Component {
1213
static propTypes = {
1314
date: PropTypes.instanceOf(Date),
15+
fluid: PropTypes.bool,
1416
format: PropTypes.string,
15-
inputProps: PropTypes.object,
16-
onDateSelected: PropTypes.func.isRequired,
17+
onDateChange: PropTypes.func.isRequired,
18+
placeholder: PropTypes.string,
1719
};
1820

1921
static defaultProps = {
2022
date: undefined,
23+
fluid: false,
2124
format: 'YYYY-MM-DD',
22-
inputProps: {},
25+
placeholder: null,
2326
};
2427

2528
state = {
@@ -33,7 +36,7 @@ class SimpleInput extends Component {
3336
return;
3437
}
3538

36-
const { format, onDateSelected } = this.props;
39+
const { format, onDateChange } = this.props;
3740

3841
this.setState(({ selectedDate }) => {
3942
let newDate = date;
@@ -42,7 +45,7 @@ class SimpleInput extends Component {
4245
newDate = null;
4346
}
4447

45-
onDateSelected(newDate);
48+
onDateChange(newDate);
4649

4750
return {
4851
isCalendarVisible: false,
@@ -65,25 +68,36 @@ class SimpleInput extends Component {
6568
};
6669

6770
handleDateChange = (evt, { value }) => {
68-
const { format, onDateSelected } = this.props;
71+
if (!value) {
72+
this.setState({
73+
selectedDate: null,
74+
selectedDateFormatted: '',
75+
});
76+
77+
return;
78+
}
79+
80+
const { format, onDateChange } = this.props;
81+
const formatInputValue = formatByPattern(format);
82+
const formattedValue = formatInputValue(value.replace(/\D/g, ''));
6983

7084
this.setState({
7185
selectedDate: null,
72-
selectedDateFormatted: value,
86+
selectedDateFormatted: formattedValue,
7387
});
7488

75-
onDateSelected(null);
89+
onDateChange(null);
7690

77-
if (value.length === format.length) {
78-
const newDate = parse(value);
91+
if (formattedValue.length === format.length) {
92+
const newDate = parse(formattedValue, format, new Date());
7993

8094
if (isValid(newDate)) {
8195
this.setState({
82-
selectedDate: newDate,
8396
selectedDateFormatted: formatDate(newDate, format),
97+
selectedDate: newDate,
8498
});
8599

86-
onDateSelected(newDate);
100+
onDateChange(newDate);
87101
}
88102
}
89103
};
@@ -94,8 +108,21 @@ class SimpleInput extends Component {
94108
}));
95109
};
96110

111+
get dayzedProps() {
112+
return omit(semanticInputProps, this.props);
113+
}
114+
115+
get inputProps() {
116+
const props = pick(semanticInputProps, this.props);
117+
118+
return {
119+
...props,
120+
placeholder: props.placeholder || this.props.format,
121+
};
122+
}
123+
97124
render() {
98-
const { date, format, inputProps } = this.props;
125+
const { date, fluid } = this.props;
99126
const {
100127
isCalendarVisible,
101128
selectedDate,
@@ -105,18 +132,20 @@ class SimpleInput extends Component {
105132
return (
106133
<div id="test">
107134
<Form.Input
108-
icon="calendar"
135+
{...this.inputProps}
136+
fluid={fluid}
109137
onBlur={this.handleBlur}
110138
onChange={this.handleDateChange}
111139
onClick={this.showCalendar}
112-
placeholder={format}
140+
icon="calendar"
113141
value={selectedDateFormatted}
114-
{...inputProps}
115142
/>
116143
{isCalendarVisible && (
117144
<Portal query="#test">
118145
<Calendar
146+
{...this.dayzedProps}
119147
date={selectedDate || date}
148+
fluid={fluid}
120149
onDateSelected={this.onDateSelected}
121150
selected={selectedDate}
122151
/>

src/utils/index.js

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,60 @@
11
import format from 'date-fns/format';
2+
import isBefore from 'date-fns/is_before';
23
import isEqual from 'date-fns/is_equal';
3-
import startOfTheDay from 'date-fns/start_of_day';
4+
import startOfDay from 'date-fns/start_of_day';
5+
6+
export const semanticInputProps = [
7+
'disabled',
8+
'error',
9+
'icon',
10+
'iconPosition',
11+
'label',
12+
'labelPosition',
13+
'loading',
14+
'placeholder',
15+
'size',
16+
'transparent',
17+
];
18+
19+
function isSelectable(date, minDate, maxDate) {
20+
if (
21+
(minDate && isBefore(date, minDate)) ||
22+
(maxDate && isBefore(maxDate, date))
23+
) {
24+
return false;
25+
}
26+
27+
return true;
28+
}
29+
30+
export const getToday = (selected, minDate, maxDate) => {
31+
const today = new Date();
432

5-
export const getToday = selected => {
633
return {
7-
date: startOfTheDay(new Date()),
8-
selectable: true,
9-
selected: isEqual(new Date(), selected),
34+
date: startOfDay(today),
35+
selectable: isSelectable(today, minDate, maxDate),
36+
selected: isEqual(today, selected),
1037
today: true,
1138
};
1239
};
1340

1441
export const formatDate = (date, dateFormat) =>
15-
date ? format(startOfTheDay(date), dateFormat) : undefined;
42+
date ? format(startOfDay(date), dateFormat) : undefined;
43+
44+
export const omit = (keysToOmit, obj) => {
45+
const newObj = { ...obj };
46+
47+
keysToOmit.forEach(key => delete newObj[key]);
48+
49+
return newObj;
50+
};
51+
52+
export const pick = (keysToPick, obj) => {
53+
const newObj = {};
54+
55+
keysToPick.forEach(key => {
56+
newObj[key] = obj[key];
57+
});
58+
59+
return newObj;
60+
};

stories/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { storiesOf } from '@storybook/react';
33
import Simple from '../src/inputs/simple';
44

55
storiesOf('Examples', module)
6-
.add('Simple', () => <Simple onDateSelected={alert} />)
6+
.add('Simple', () => <Simple onDateChange={console.log} />)
77
.add('Simple with custom props', () => (
8-
<Simple format="DD/MM/YYYY" onDateSelected={alert} />
8+
<Simple format="DD/MM/YYYY" onDateChange={console.log} />
99
));

0 commit comments

Comments
 (0)