diginet-core-ui 1.4.24 → 1.4.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/form-control/date-input/DateField.js +189 -0
- package/components/form-control/date-input/index.js +300 -0
- package/components/form-control/date-input/useControlled.js +14 -0
- package/components/form-control/date-input/useDateInputState.js +132 -0
- package/components/form-control/date-input/useIsFocused.js +20 -0
- package/components/form-control/date-input/useKeyboardInputEvent.js +45 -0
- package/components/form-control/date-input/utils.js +286 -0
- package/components/form-control/date-picker/index.js +144 -422
- package/components/form-control/input-base/UncontrolledInputBase.js +451 -0
- package/components/index.js +1 -0
- package/package.json +1 -1
- package/readme.md +4 -0
- package/theme/settings.js +14 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { isValid } from 'date-fns';
|
|
2
|
+
import { useReducer } from 'react';
|
|
3
|
+
import { modifyDate } from "./utils";
|
|
4
|
+
export const patternMap = {
|
|
5
|
+
Y: 'year',
|
|
6
|
+
y: 'year',
|
|
7
|
+
M: 'month',
|
|
8
|
+
D: 'day',
|
|
9
|
+
d: 'day',
|
|
10
|
+
H: 'hour',
|
|
11
|
+
h: 'hour',
|
|
12
|
+
m: 'minute',
|
|
13
|
+
s: 'second',
|
|
14
|
+
a: 'meridian'
|
|
15
|
+
};
|
|
16
|
+
export class DateField {
|
|
17
|
+
constructor(format, value) {
|
|
18
|
+
this.format = format;
|
|
19
|
+
this.patternArray = [];
|
|
20
|
+
this.year = null;
|
|
21
|
+
this.month = null;
|
|
22
|
+
this.day = null;
|
|
23
|
+
this.hour = null;
|
|
24
|
+
this.minute = null;
|
|
25
|
+
this.second = null;
|
|
26
|
+
const formatArray = format.match(new RegExp('([Y|y|D|d|M|H|h|m|s|a])+', 'ig')) || [];
|
|
27
|
+
this.patternArray = formatArray.map(pattern => {
|
|
28
|
+
return {
|
|
29
|
+
pattern,
|
|
30
|
+
key: patternMap[pattern[0]]
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
if (value && isValid(value)) {
|
|
34
|
+
this.year = value.getFullYear();
|
|
35
|
+
this.month = value.getMonth() + 1;
|
|
36
|
+
this.day = value.getDate();
|
|
37
|
+
this.hour = value.getHours();
|
|
38
|
+
this.minute = value.getMinutes();
|
|
39
|
+
this.second = value.getSeconds();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Pad a number with zeros to the left.
|
|
46
|
+
*/
|
|
47
|
+
function padNumber(number, length) {
|
|
48
|
+
let numberString = String(number);
|
|
49
|
+
if (numberString.length >= length) {
|
|
50
|
+
return numberString;
|
|
51
|
+
}
|
|
52
|
+
const paddingCount = length - numberString.length;
|
|
53
|
+
for (let i = 0; i < paddingCount; i++) {
|
|
54
|
+
numberString = '0' + numberString;
|
|
55
|
+
}
|
|
56
|
+
return numberString;
|
|
57
|
+
}
|
|
58
|
+
export const useDateField = (format, localize, date) => {
|
|
59
|
+
const [dateField, dispatch] = useReducer((state, action) => {
|
|
60
|
+
switch (action.type) {
|
|
61
|
+
case 'setYear':
|
|
62
|
+
return {
|
|
63
|
+
...state,
|
|
64
|
+
year: action.value
|
|
65
|
+
};
|
|
66
|
+
case 'setMonth':
|
|
67
|
+
return {
|
|
68
|
+
...state,
|
|
69
|
+
month: action.value
|
|
70
|
+
};
|
|
71
|
+
case 'setDay':
|
|
72
|
+
return {
|
|
73
|
+
...state,
|
|
74
|
+
day: action.value
|
|
75
|
+
};
|
|
76
|
+
case 'setHour':
|
|
77
|
+
return {
|
|
78
|
+
...state,
|
|
79
|
+
hour: action.value
|
|
80
|
+
};
|
|
81
|
+
case 'setMinute':
|
|
82
|
+
return {
|
|
83
|
+
...state,
|
|
84
|
+
minute: action.value
|
|
85
|
+
};
|
|
86
|
+
case 'setSecond':
|
|
87
|
+
return {
|
|
88
|
+
...state,
|
|
89
|
+
second: action.value
|
|
90
|
+
};
|
|
91
|
+
case 'setNewDate':
|
|
92
|
+
return new DateField(format, action.value);
|
|
93
|
+
default:
|
|
94
|
+
return state;
|
|
95
|
+
}
|
|
96
|
+
}, new DateField(format, date));
|
|
97
|
+
const toDateString = () => {
|
|
98
|
+
let str = format;
|
|
99
|
+
dateField.patternArray.forEach(item => {
|
|
100
|
+
const {
|
|
101
|
+
key,
|
|
102
|
+
pattern
|
|
103
|
+
} = item;
|
|
104
|
+
const hour = dateField.hour;
|
|
105
|
+
let value = dateField[key];
|
|
106
|
+
if (value !== null) {
|
|
107
|
+
if (pattern === 'MMM' && typeof value === 'number') {
|
|
108
|
+
// value = localize?.month(value - 1, { width: 'abbreviated' });
|
|
109
|
+
value = localize === null || localize === void 0 ? void 0 : localize.months[['notFull']][value - 1];
|
|
110
|
+
} else if (pattern === 'MMMM' && typeof value === 'number') {
|
|
111
|
+
// value = localize?.month(value - 1, { width: 'wide' });
|
|
112
|
+
value = localize === null || localize === void 0 ? void 0 : localize.months[['full']][value - 1];
|
|
113
|
+
} else if (pattern === 'aa') {
|
|
114
|
+
if (typeof hour === 'number') {
|
|
115
|
+
value = hour > 12 ? 'PM' : 'AM';
|
|
116
|
+
} else {
|
|
117
|
+
value = 'aa';
|
|
118
|
+
}
|
|
119
|
+
} else if (pattern === 'hh' && typeof value === 'number') {
|
|
120
|
+
value = value === 0 ? 12 : value > 12 ? value - 12 : value;
|
|
121
|
+
}
|
|
122
|
+
if (typeof value === 'number') {
|
|
123
|
+
value = padNumber(value, pattern.length);
|
|
124
|
+
}
|
|
125
|
+
str = str.replace(pattern, value);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return str;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Check if the field value is valid.
|
|
132
|
+
const validFieldValue = (type, value) => {
|
|
133
|
+
var _format$match;
|
|
134
|
+
let isValid = true;
|
|
135
|
+
(_format$match = format.match(new RegExp('([y|d|M|H|h|m|s])+', 'ig'))) === null || _format$match === void 0 ? void 0 : _format$match.forEach(pattern => {
|
|
136
|
+
const key = patternMap[pattern[0]];
|
|
137
|
+
const fieldValue = type === key ? value : dateField[key];
|
|
138
|
+
if (fieldValue === null) {
|
|
139
|
+
isValid = false;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return isValid;
|
|
144
|
+
};
|
|
145
|
+
const isEmptyValue = (type, value) => {
|
|
146
|
+
var _format$match2;
|
|
147
|
+
const checkValueArray = (_format$match2 = format.match(new RegExp('([y|d|M|H|h|m|s])+', 'ig'))) === null || _format$match2 === void 0 ? void 0 : _format$match2.map(pattern => {
|
|
148
|
+
const key = patternMap[pattern[0]];
|
|
149
|
+
const fieldValue = type === key ? value : dateField[key];
|
|
150
|
+
return fieldValue !== null;
|
|
151
|
+
});
|
|
152
|
+
return checkValueArray === null || checkValueArray === void 0 ? void 0 : checkValueArray.every(item => item === false);
|
|
153
|
+
};
|
|
154
|
+
const toDate = (type, value) => {
|
|
155
|
+
const {
|
|
156
|
+
year,
|
|
157
|
+
month,
|
|
158
|
+
day,
|
|
159
|
+
hour,
|
|
160
|
+
minute,
|
|
161
|
+
second
|
|
162
|
+
} = dateField;
|
|
163
|
+
const date = new Date(year || 0, typeof month === 'number' ? month - 1 : 0,
|
|
164
|
+
// The default day is 1 when the value is null, otherwise it becomes the last day of the month.
|
|
165
|
+
day || 1, hour || 0, minute || 0, second || 0);
|
|
166
|
+
if (typeof type === 'undefined' || typeof value === 'undefined') {
|
|
167
|
+
return date;
|
|
168
|
+
}
|
|
169
|
+
if (value === null || !validFieldValue(type, value)) {
|
|
170
|
+
if (isEmptyValue(type, value)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return new Date('');
|
|
174
|
+
}
|
|
175
|
+
if (type === 'meridian' && typeof hour === 'number') {
|
|
176
|
+
const newHour = hour > 12 ? hour - 12 : hour + 12;
|
|
177
|
+
type = 'hour';
|
|
178
|
+
value = newHour;
|
|
179
|
+
}
|
|
180
|
+
return modifyDate(date, type, value);
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
dateField,
|
|
184
|
+
dispatch,
|
|
185
|
+
toDate,
|
|
186
|
+
toDateString,
|
|
187
|
+
isEmptyValue
|
|
188
|
+
};
|
|
189
|
+
};
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/** @jsxRuntime classic */
|
|
2
|
+
/** @jsx jsx */
|
|
3
|
+
import { css, jsx } from '@emotion/core';
|
|
4
|
+
import { HelperText, Label } from "../..";
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
import { forwardRef, memo, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
7
|
+
import { cursorNotAllowed, displayBlock, positionRelative } from "../../../styles/general";
|
|
8
|
+
import useThemeProps from "../../../theme/utils/useThemeProps";
|
|
9
|
+
import { classNames, refType as ref } from "../../../utils";
|
|
10
|
+
import useIsFocused from "./useIsFocused";
|
|
11
|
+
import useKeyboardInputEvent from "./useKeyboardInputEvent";
|
|
12
|
+
import { getInputSelectedState, isFieldFullValue, useEventCallback, useInputSelection, validateDateTime } from "./utils";
|
|
13
|
+
import { getGlobal } from "../../../global";
|
|
14
|
+
import locale from "../../../locale";
|
|
15
|
+
import useDateInputState from "./useDateInputState";
|
|
16
|
+
import useControlled from "./useControlled";
|
|
17
|
+
import UncontrolledInputBase from "../input-base/UncontrolledInputBase";
|
|
18
|
+
const DateInput = /*#__PURE__*/memo( /*#__PURE__*/forwardRef((inProps, reference) => {
|
|
19
|
+
if (!reference) reference = useRef(null);
|
|
20
|
+
|
|
21
|
+
// props priority: `inProps` > `themeDefaultProps`
|
|
22
|
+
const props = useThemeProps({
|
|
23
|
+
props: inProps,
|
|
24
|
+
name: 'DateInput'
|
|
25
|
+
});
|
|
26
|
+
const {
|
|
27
|
+
action = {},
|
|
28
|
+
className,
|
|
29
|
+
defaultValue,
|
|
30
|
+
disabled,
|
|
31
|
+
endIcon,
|
|
32
|
+
endIconProps,
|
|
33
|
+
error,
|
|
34
|
+
format: formatStr,
|
|
35
|
+
inputProps,
|
|
36
|
+
helperTextProps,
|
|
37
|
+
inputRef = useRef(null),
|
|
38
|
+
inputStyle,
|
|
39
|
+
label,
|
|
40
|
+
labelProps,
|
|
41
|
+
onBlur,
|
|
42
|
+
onChange,
|
|
43
|
+
onFocus,
|
|
44
|
+
onKeyDown,
|
|
45
|
+
placeholder,
|
|
46
|
+
readOnly,
|
|
47
|
+
required,
|
|
48
|
+
startIcon,
|
|
49
|
+
startIconProps,
|
|
50
|
+
style,
|
|
51
|
+
value: valueProp,
|
|
52
|
+
viewType,
|
|
53
|
+
...other
|
|
54
|
+
} = props;
|
|
55
|
+
const ref = useRef(null);
|
|
56
|
+
const [selectedState, setSelectedState] = useState({
|
|
57
|
+
selectedPattern: 'y',
|
|
58
|
+
selectionStart: 0,
|
|
59
|
+
selectionEnd: 0
|
|
60
|
+
});
|
|
61
|
+
const isError = !!error;
|
|
62
|
+
const isEnabled = !readOnly && !disabled;
|
|
63
|
+
const _DateInputRoot = DateInputRoot();
|
|
64
|
+
useImperativeHandle(reference, () => {
|
|
65
|
+
const currentRef = ref.current || {};
|
|
66
|
+
currentRef.element = ref.current;
|
|
67
|
+
const _instance = {
|
|
68
|
+
...action
|
|
69
|
+
}; // methods
|
|
70
|
+
_instance.__proto__ = {}; // hidden methods
|
|
71
|
+
currentRef.instance = _instance;
|
|
72
|
+
return currentRef;
|
|
73
|
+
});
|
|
74
|
+
const dateLocale = locale.get();
|
|
75
|
+
const [value, setValue, isControlled] = useControlled(valueProp, defaultValue);
|
|
76
|
+
const {
|
|
77
|
+
dateField,
|
|
78
|
+
setDateOffset,
|
|
79
|
+
setDateField,
|
|
80
|
+
getDateField,
|
|
81
|
+
toDateString,
|
|
82
|
+
isEmptyValue
|
|
83
|
+
} = useDateInputState({
|
|
84
|
+
formatStr,
|
|
85
|
+
locale: dateLocale,
|
|
86
|
+
date: value,
|
|
87
|
+
isControlledDate: isControlled
|
|
88
|
+
});
|
|
89
|
+
const dateString = toDateString();
|
|
90
|
+
const keyPressOptions = useMemo(() => ({
|
|
91
|
+
formatStr,
|
|
92
|
+
localize: getGlobal(dateLocale),
|
|
93
|
+
selectedMonth: dateField.month,
|
|
94
|
+
dateString
|
|
95
|
+
}), [dateField, dateString, formatStr, dateLocale]);
|
|
96
|
+
const handleChange = useEventCallback((value, event) => {
|
|
97
|
+
if (onChange) {
|
|
98
|
+
onChange(value, event);
|
|
99
|
+
}
|
|
100
|
+
setValue(value);
|
|
101
|
+
});
|
|
102
|
+
const setSelectionRange = useInputSelection(inputRef);
|
|
103
|
+
const onSegmentChange = useEventCallback((event, nextDirection) => {
|
|
104
|
+
const input = event.target;
|
|
105
|
+
const key = event.key;
|
|
106
|
+
const direction = nextDirection || (key === 'ArrowRight' ? 'right' : 'left');
|
|
107
|
+
const state = getInputSelectedState({
|
|
108
|
+
...keyPressOptions,
|
|
109
|
+
input,
|
|
110
|
+
direction
|
|
111
|
+
});
|
|
112
|
+
setSelectionRange(state.selectionStart, state.selectionEnd);
|
|
113
|
+
setSelectedState(state);
|
|
114
|
+
});
|
|
115
|
+
const onSegmentValueChange = useEventCallback(event => {
|
|
116
|
+
const input = event.target;
|
|
117
|
+
const key = event.key;
|
|
118
|
+
const offset = key === 'ArrowUp' ? 1 : -1;
|
|
119
|
+
const state = getInputSelectedState({
|
|
120
|
+
...keyPressOptions,
|
|
121
|
+
input,
|
|
122
|
+
valueOffset: offset
|
|
123
|
+
});
|
|
124
|
+
setSelectedState(state);
|
|
125
|
+
setDateOffset(state.selectedPattern, offset, date => handleChange(date, event));
|
|
126
|
+
setSelectionRange(state.selectionStart, state.selectionEnd);
|
|
127
|
+
});
|
|
128
|
+
const onSegmentValueChangeWithNumericKeys = useEventCallback(event => {
|
|
129
|
+
const input = event.target;
|
|
130
|
+
const key = event.key;
|
|
131
|
+
const pattern = selectedState.selectedPattern;
|
|
132
|
+
if (!pattern) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const field = getDateField(pattern);
|
|
136
|
+
const value = parseInt(key, 10);
|
|
137
|
+
const padValue = parseInt(`${field.value || ''}${key}`, 10);
|
|
138
|
+
let newValue = value;
|
|
139
|
+
|
|
140
|
+
// Check if the value entered by the user is a valid date
|
|
141
|
+
if (validateDateTime(field.name, padValue)) {
|
|
142
|
+
newValue = padValue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// if (pattern === 'M') {
|
|
146
|
+
// // Month cannot be less than 1.
|
|
147
|
+
// newValue = Math.max(1, newValue);
|
|
148
|
+
// }
|
|
149
|
+
|
|
150
|
+
setDateField(pattern, newValue, date => handleChange(date, event));
|
|
151
|
+
|
|
152
|
+
// The currently selected month will be retained as a parameter of getInputSelectedState,
|
|
153
|
+
// but if the user enters a month, the month value will be replaced with the value entered by the user.
|
|
154
|
+
const selectedMonth = pattern === 'M' ? newValue : dateField.month;
|
|
155
|
+
const nextState = getInputSelectedState({
|
|
156
|
+
...keyPressOptions,
|
|
157
|
+
input,
|
|
158
|
+
selectedMonth
|
|
159
|
+
});
|
|
160
|
+
setSelectedState(nextState);
|
|
161
|
+
setSelectionRange(nextState.selectionStart, nextState.selectionEnd);
|
|
162
|
+
// If the field is full value, move the cursor to the next field
|
|
163
|
+
if ((isFieldFullValue(formatStr, newValue, pattern) || field.value === 0) && input.selectionEnd !== input.value.length) {
|
|
164
|
+
onSegmentChange(event, 'right');
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
const onSegmentValueRemove = useEventCallback(event => {
|
|
168
|
+
const input = event.target;
|
|
169
|
+
if (selectedState.selectedPattern) {
|
|
170
|
+
const nextState = getInputSelectedState({
|
|
171
|
+
...keyPressOptions,
|
|
172
|
+
input,
|
|
173
|
+
valueOffset: null
|
|
174
|
+
});
|
|
175
|
+
setSelectedState(nextState);
|
|
176
|
+
setSelectionRange(nextState.selectionStart, nextState.selectionEnd);
|
|
177
|
+
setDateField(selectedState.selectedPattern, null, date => handleChange(date, event));
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
const handleClick = useEventCallback(event => {
|
|
181
|
+
const input = event.target;
|
|
182
|
+
const state = getInputSelectedState({
|
|
183
|
+
...keyPressOptions,
|
|
184
|
+
input
|
|
185
|
+
});
|
|
186
|
+
setSelectedState(state);
|
|
187
|
+
setSelectionRange(state.selectionStart, state.selectionEnd);
|
|
188
|
+
});
|
|
189
|
+
const onKeyboardInput = useKeyboardInputEvent({
|
|
190
|
+
onSegmentChange,
|
|
191
|
+
onSegmentValueChange,
|
|
192
|
+
onSegmentValueChangeWithNumericKeys,
|
|
193
|
+
onSegmentValueRemove,
|
|
194
|
+
onKeyDown
|
|
195
|
+
});
|
|
196
|
+
const [focused, focusEventProps] = useIsFocused({
|
|
197
|
+
onBlur,
|
|
198
|
+
onFocus
|
|
199
|
+
});
|
|
200
|
+
const renderedValue = useMemo(() => {
|
|
201
|
+
if (!isEmptyValue()) {
|
|
202
|
+
return dateString;
|
|
203
|
+
}
|
|
204
|
+
return !focused ? '' : dateString;
|
|
205
|
+
}, [dateString, focused, isEmptyValue]);
|
|
206
|
+
return jsx("div", {
|
|
207
|
+
ref: ref,
|
|
208
|
+
css: _DateInputRoot,
|
|
209
|
+
className: classNames('DGN-UI-DateInput', className, disabled && 'disabled'),
|
|
210
|
+
style: style,
|
|
211
|
+
...other
|
|
212
|
+
}, !!label && jsx(Label, {
|
|
213
|
+
required: required,
|
|
214
|
+
...labelProps,
|
|
215
|
+
disabled: disabled
|
|
216
|
+
}, label), jsx(UncontrolledInputBase, {
|
|
217
|
+
autoComplete: "off",
|
|
218
|
+
autoCorrect: "off",
|
|
219
|
+
disabled: disabled,
|
|
220
|
+
endIcon: endIcon,
|
|
221
|
+
endIconProps: endIconProps,
|
|
222
|
+
inputMode: focused ? 'numeric' : 'text',
|
|
223
|
+
inputProps: inputProps,
|
|
224
|
+
inputRef: inputRef,
|
|
225
|
+
inputStyle: inputStyle,
|
|
226
|
+
onClick: handleClick,
|
|
227
|
+
onKeyDown: isEnabled ? onKeyboardInput : null,
|
|
228
|
+
placeholder: placeholder || formatStr,
|
|
229
|
+
readOnly: readOnly,
|
|
230
|
+
spellCheck: false,
|
|
231
|
+
startIcon: startIcon,
|
|
232
|
+
startIconProps: startIconProps,
|
|
233
|
+
value: renderedValue,
|
|
234
|
+
viewType: viewType,
|
|
235
|
+
...focusEventProps
|
|
236
|
+
}), isError && jsx(HelperText, {
|
|
237
|
+
...helperTextProps,
|
|
238
|
+
disabled: disabled
|
|
239
|
+
}, error));
|
|
240
|
+
}));
|
|
241
|
+
const DateInputRoot = () => css`
|
|
242
|
+
${displayBlock};
|
|
243
|
+
${positionRelative};
|
|
244
|
+
&.disabled {
|
|
245
|
+
${cursorNotAllowed};
|
|
246
|
+
}
|
|
247
|
+
`;
|
|
248
|
+
DateInput.propTypes = {
|
|
249
|
+
/** Class for component. */
|
|
250
|
+
className: PropTypes.string,
|
|
251
|
+
/** The default value. Use when the component is not controlled. */
|
|
252
|
+
defaultValue: PropTypes.instanceOf(Date),
|
|
253
|
+
/** If `true`, the component is disabled.*/
|
|
254
|
+
disabled: PropTypes.bool,
|
|
255
|
+
/** [Icon](https://core.diginet.com.vn/ui/?path=/story/icon-basic--basic) or element placed to the right of input. */
|
|
256
|
+
endIcon: PropTypes.any,
|
|
257
|
+
/** Props of end icon in InputBase component. */
|
|
258
|
+
endIconProps: PropTypes.object,
|
|
259
|
+
/** Error displayed below input. */
|
|
260
|
+
error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
|
|
261
|
+
/** Format of the date when rendered in the input. */
|
|
262
|
+
format: PropTypes.string,
|
|
263
|
+
/** [Props](https://core.diginet.com.vn/ui/?path=/story/form-control-text-helpertext) of helper text. */
|
|
264
|
+
helperTextProps: PropTypes.object,
|
|
265
|
+
/** [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attributes) applied to the input element. */
|
|
266
|
+
inputProps: PropTypes.object,
|
|
267
|
+
/** Pass a ref to the input element. */
|
|
268
|
+
inputRef: ref,
|
|
269
|
+
/** Style inline of input element. */
|
|
270
|
+
inputStyle: PropTypes.object,
|
|
271
|
+
/** The label of the component. */
|
|
272
|
+
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
|
273
|
+
/** [Props](https://core.diginet.com.vn/ui/?path=/docs/form-control-text-label--simple) of label. */
|
|
274
|
+
labelProps: PropTypes.object,
|
|
275
|
+
/** Callback fired when the input is blurred. */
|
|
276
|
+
onBlur: PropTypes.func,
|
|
277
|
+
/** Callback fired when the value is changed. */
|
|
278
|
+
onChange: PropTypes.func,
|
|
279
|
+
/** Callback fired when the input is focused. */
|
|
280
|
+
onFocus: PropTypes.func,
|
|
281
|
+
/** Callback fired when pressing a key. */
|
|
282
|
+
onKeyDown: PropTypes.func,
|
|
283
|
+
/** The short hint displayed in the input before the user enters a value. */
|
|
284
|
+
placeholder: PropTypes.string,
|
|
285
|
+
/** If `true`, the component is readOnly. */
|
|
286
|
+
readOnly: PropTypes.bool,
|
|
287
|
+
/** If `true`, the input element is required. */
|
|
288
|
+
required: PropTypes.bool,
|
|
289
|
+
/** [Icon](https://core.diginet.com.vn/ui/?path=/story/icon-basic--basic) or element placed to the left of input. */
|
|
290
|
+
startIcon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
|
291
|
+
/** Props of start icon in InputBase component. */
|
|
292
|
+
startIconProps: PropTypes.object,
|
|
293
|
+
/** Style inline of component. */
|
|
294
|
+
style: PropTypes.object,
|
|
295
|
+
/** The value of the input element, required for a controlled component. */
|
|
296
|
+
value: PropTypes.instanceOf(Date),
|
|
297
|
+
/** The variant to use. */
|
|
298
|
+
viewType: PropTypes.oneOf(['underlined', 'outlined', 'none'])
|
|
299
|
+
};
|
|
300
|
+
export default DateInput;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useRef, useState, useCallback } from 'react';
|
|
2
|
+
function useControlled(controlledValue, defaultValue) {
|
|
3
|
+
const controlledRef = useRef(false);
|
|
4
|
+
controlledRef.current = controlledValue !== undefined;
|
|
5
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
6
|
+
const value = controlledRef.current ? controlledValue : uncontrolledValue;
|
|
7
|
+
const setValue = useCallback(nextValue => {
|
|
8
|
+
if (!controlledRef.current) {
|
|
9
|
+
setUncontrolledValue(nextValue);
|
|
10
|
+
}
|
|
11
|
+
}, [controlledRef]);
|
|
12
|
+
return [value, setValue, controlledRef.current];
|
|
13
|
+
}
|
|
14
|
+
export default useControlled;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
// import startCase from 'lodash/startCase';
|
|
3
|
+
import { addDays, addHours, addMinutes, addMonths, addSeconds, addYears, format, isLastDayOfMonth, isValid, lastDayOfMonth } from 'date-fns';
|
|
4
|
+
import { getGlobal } from "../../../global";
|
|
5
|
+
import { capitalize } from "../../../utils";
|
|
6
|
+
import { patternMap, useDateField } from "./DateField";
|
|
7
|
+
import { lowerCaseDayYear } from "./utils";
|
|
8
|
+
import { enUS, vi } from 'date-fns/locale';
|
|
9
|
+
export function useDateInputState({
|
|
10
|
+
formatStr,
|
|
11
|
+
locale,
|
|
12
|
+
date,
|
|
13
|
+
isControlledDate
|
|
14
|
+
}) {
|
|
15
|
+
const {
|
|
16
|
+
dateField,
|
|
17
|
+
dispatch,
|
|
18
|
+
toDateString,
|
|
19
|
+
toDate,
|
|
20
|
+
isEmptyValue
|
|
21
|
+
} = useDateField(formatStr, getGlobal(locale), date);
|
|
22
|
+
const setDateOffset = (pattern, offset, callback) => {
|
|
23
|
+
const currentDate = new Date();
|
|
24
|
+
const year = dateField.year || currentDate.getFullYear();
|
|
25
|
+
const month = dateField.month ? dateField.month - 1 : currentDate.getMonth();
|
|
26
|
+
const day = dateField.day || 0;
|
|
27
|
+
const hour = dateField.hour || 0;
|
|
28
|
+
const minute = dateField.minute || 0;
|
|
29
|
+
const second = dateField.second || 0;
|
|
30
|
+
let actionName;
|
|
31
|
+
let value;
|
|
32
|
+
switch (pattern) {
|
|
33
|
+
case 'Y':
|
|
34
|
+
case 'y':
|
|
35
|
+
actionName = 'setYear';
|
|
36
|
+
value = addYears(new Date(year, 0), offset).getFullYear();
|
|
37
|
+
break;
|
|
38
|
+
case 'M':
|
|
39
|
+
actionName = 'setMonth';
|
|
40
|
+
value = addMonths(new Date(year, month), offset).getMonth() + 1;
|
|
41
|
+
break;
|
|
42
|
+
case 'D':
|
|
43
|
+
case 'd':
|
|
44
|
+
actionName = 'setDay';
|
|
45
|
+
// eslint-disable-next-line no-case-declarations
|
|
46
|
+
const prevDate = new Date(year, month, day);
|
|
47
|
+
value = addDays(prevDate, offset).getDate();
|
|
48
|
+
if (offset > 0) {
|
|
49
|
+
value = isLastDayOfMonth(prevDate) ? 1 : value;
|
|
50
|
+
} else {
|
|
51
|
+
value = prevDate.getDate() === 1 ? lastDayOfMonth(prevDate).getDate() : value;
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
case 'H':
|
|
55
|
+
case 'h':
|
|
56
|
+
actionName = 'setHour';
|
|
57
|
+
value = addHours(new Date(year, month, day, hour), offset).getHours();
|
|
58
|
+
break;
|
|
59
|
+
case 'm':
|
|
60
|
+
actionName = 'setMinute';
|
|
61
|
+
value = addMinutes(new Date(year, month, day, hour, minute), offset).getMinutes();
|
|
62
|
+
break;
|
|
63
|
+
case 's':
|
|
64
|
+
actionName = 'setSecond';
|
|
65
|
+
value = addSeconds(new Date(year, month, day, hour, minute, second), offset).getSeconds();
|
|
66
|
+
break;
|
|
67
|
+
case 'a':
|
|
68
|
+
actionName = 'setHour';
|
|
69
|
+
value = hour >= 12 ? hour - 12 : hour + 12;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
if (actionName && value) {
|
|
73
|
+
dispatch({
|
|
74
|
+
type: actionName,
|
|
75
|
+
value
|
|
76
|
+
});
|
|
77
|
+
const field = patternMap[pattern];
|
|
78
|
+
callback === null || callback === void 0 ? void 0 : callback(toDate(field, value));
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const setDateField = (pattern, value, callback) => {
|
|
82
|
+
const field = patternMap[pattern];
|
|
83
|
+
// const actionName = `set${startCase(field)}`;
|
|
84
|
+
const actionName = `set${capitalize(field)}`;
|
|
85
|
+
dispatch({
|
|
86
|
+
type: actionName,
|
|
87
|
+
value
|
|
88
|
+
});
|
|
89
|
+
callback === null || callback === void 0 ? void 0 : callback(toDate(field, value));
|
|
90
|
+
};
|
|
91
|
+
const getDateField = pattern => {
|
|
92
|
+
const fieldName = patternMap[pattern];
|
|
93
|
+
return {
|
|
94
|
+
name: fieldName,
|
|
95
|
+
value: dateField[fieldName]
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
const toControlledDateString = () => {
|
|
99
|
+
if (date && isValid(date)) {
|
|
100
|
+
// return format(date, formatStr, { locale });
|
|
101
|
+
return format(date, lowerCaseDayYear(formatStr), {
|
|
102
|
+
locale: locale === 'vi' ? vi : enUS
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// if date is not valid, return uncontrolled date string
|
|
106
|
+
return toDateString();
|
|
107
|
+
};
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (isControlledDate) {
|
|
110
|
+
if (date && isValid(date)) {
|
|
111
|
+
dispatch({
|
|
112
|
+
type: 'setNewDate',
|
|
113
|
+
value: date
|
|
114
|
+
});
|
|
115
|
+
} else if (date === null) {
|
|
116
|
+
dispatch({
|
|
117
|
+
type: 'setNewDate',
|
|
118
|
+
value: null
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}, [date, dispatch, isControlledDate]);
|
|
123
|
+
return {
|
|
124
|
+
dateField,
|
|
125
|
+
setDateOffset,
|
|
126
|
+
setDateField,
|
|
127
|
+
getDateField,
|
|
128
|
+
toDateString: isControlledDate ? toControlledDateString : toDateString,
|
|
129
|
+
isEmptyValue
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export default useDateInputState;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
function useIsFocused({
|
|
3
|
+
onFocus: onFocusProp,
|
|
4
|
+
onBlur: onBlurProp
|
|
5
|
+
}) {
|
|
6
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
7
|
+
const onFocus = useCallback(event => {
|
|
8
|
+
setIsFocused(true);
|
|
9
|
+
onFocusProp === null || onFocusProp === void 0 ? void 0 : onFocusProp(event);
|
|
10
|
+
}, [onFocusProp]);
|
|
11
|
+
const onBlur = useCallback(event => {
|
|
12
|
+
setIsFocused(false);
|
|
13
|
+
onBlurProp === null || onBlurProp === void 0 ? void 0 : onBlurProp(event);
|
|
14
|
+
}, [onBlurProp]);
|
|
15
|
+
return [isFocused, {
|
|
16
|
+
onFocus,
|
|
17
|
+
onBlur
|
|
18
|
+
}];
|
|
19
|
+
}
|
|
20
|
+
export default useIsFocused;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
function useKeyboardInputEvent({
|
|
2
|
+
onSegmentChange,
|
|
3
|
+
onSegmentValueChange,
|
|
4
|
+
onSegmentValueChangeWithNumericKeys,
|
|
5
|
+
onSegmentValueRemove,
|
|
6
|
+
onKeyDown
|
|
7
|
+
}) {
|
|
8
|
+
return event => {
|
|
9
|
+
var _key$match;
|
|
10
|
+
const {
|
|
11
|
+
key,
|
|
12
|
+
ctrlKey,
|
|
13
|
+
metaKey
|
|
14
|
+
} = event;
|
|
15
|
+
switch (key) {
|
|
16
|
+
case 'ArrowRight':
|
|
17
|
+
case 'ArrowLeft':
|
|
18
|
+
onSegmentChange === null || onSegmentChange === void 0 ? void 0 : onSegmentChange(event);
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
break;
|
|
21
|
+
case 'ArrowUp':
|
|
22
|
+
case 'ArrowDown':
|
|
23
|
+
onSegmentValueChange === null || onSegmentValueChange === void 0 ? void 0 : onSegmentValueChange(event);
|
|
24
|
+
event.preventDefault();
|
|
25
|
+
break;
|
|
26
|
+
case 'Backspace':
|
|
27
|
+
case 'Delete':
|
|
28
|
+
onSegmentValueRemove === null || onSegmentValueRemove === void 0 ? void 0 : onSegmentValueRemove(event);
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
break;
|
|
31
|
+
case (_key$match = key.match(/\d/)) === null || _key$match === void 0 ? void 0 : _key$match.input:
|
|
32
|
+
// Allow numeric keys to be entered
|
|
33
|
+
onSegmentValueChangeWithNumericKeys === null || onSegmentValueChangeWithNumericKeys === void 0 ? void 0 : onSegmentValueChangeWithNumericKeys(event);
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
// case key.match(/[a-z]/)?.[0]:
|
|
38
|
+
// Prevent letters from being entered
|
|
39
|
+
if (!ctrlKey && !metaKey) event.preventDefault();
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export default useKeyboardInputEvent;
|