@startlibs/form 1.0.11 → 1.1.0
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/lib/AutoComplete.mjs +550 -0
- package/lib/AutoComplete.mjs.map +1 -0
- package/lib/Checkbox.mjs +76 -0
- package/lib/Checkbox.mjs.map +1 -0
- package/lib/Combobox.mjs +544 -0
- package/lib/Combobox.mjs.map +1 -0
- package/lib/ComboboxWithCustom.mjs +77 -0
- package/lib/ComboboxWithCustom.mjs.map +1 -0
- package/lib/DatePicker.mjs +625 -0
- package/lib/DatePicker.mjs.map +1 -0
- package/lib/EditableBox.mjs +496 -0
- package/lib/EditableBox.mjs.map +1 -0
- package/lib/Errors.mjs +176 -0
- package/lib/Errors.mjs.map +1 -0
- package/lib/ExpandableBox.mjs +209 -0
- package/lib/ExpandableBox.mjs.map +1 -0
- package/lib/Field.mjs +65 -0
- package/lib/Field.mjs.map +1 -0
- package/lib/FieldBox.mjs +80 -0
- package/lib/FieldBox.mjs.map +1 -0
- package/lib/Form.mjs +322 -0
- package/lib/Form.mjs.map +1 -0
- package/lib/FormValue.mjs +111 -0
- package/lib/FormValue.mjs.map +1 -0
- package/lib/InputboxGroup.mjs +104 -0
- package/lib/InputboxGroup.mjs.map +1 -0
- package/lib/Radiobox.mjs +67 -0
- package/lib/Radiobox.mjs.map +1 -0
- package/lib/RichText.mjs +634 -0
- package/lib/RichText.mjs.map +1 -0
- package/lib/TextInput.mjs +359 -0
- package/lib/TextInput.mjs.map +1 -0
- package/lib/alt/checkbox/SimpleCheckbox.mjs +82 -0
- package/lib/alt/checkbox/SimpleCheckbox.mjs.map +1 -0
- package/lib/alt/checkbox/ToggleCheckbox.mjs +68 -0
- package/lib/alt/checkbox/ToggleCheckbox.mjs.map +1 -0
- package/lib/alt/radiobox/CleanTabRadiobox.mjs +44 -0
- package/lib/alt/radiobox/CleanTabRadiobox.mjs.map +1 -0
- package/lib/alt/radiobox/IconRadioBox.mjs +52 -0
- package/lib/alt/radiobox/IconRadioBox.mjs.map +1 -0
- package/lib/alt/radiobox/SimpleRadiobox.mjs +61 -0
- package/lib/alt/radiobox/SimpleRadiobox.mjs.map +1 -0
- package/lib/alt/radiobox/TabRadiobox.mjs +54 -0
- package/lib/alt/radiobox/TabRadiobox.mjs.map +1 -0
- package/lib/enhanceInput.mjs +131 -0
- package/lib/enhanceInput.mjs.map +1 -0
- package/lib/index.cjs +5345 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.mjs +28 -0
- package/lib/index.mjs.map +1 -0
- package/lib/useConfirmDialog.mjs +117 -0
- package/lib/useConfirmDialog.mjs.map +1 -0
- package/lib/useDraftConfirmation.mjs +56 -0
- package/lib/useDraftConfirmation.mjs.map +1 -0
- package/lib/validation.mjs +91 -0
- package/lib/validation.mjs.map +1 -0
- package/lib/withFormImageInput.mjs +281 -0
- package/lib/withFormImageInput.mjs.map +1 -0
- package/package.json +44 -25
- package/src/AutoComplete.jsx +642 -0
- package/src/Checkbox.jsx +45 -0
- package/src/Combobox.jsx +606 -0
- package/src/ComboboxWithCustom.jsx +68 -0
- package/src/DatePicker.jsx +733 -0
- package/src/EditableBox.jsx +502 -0
- package/src/Errors.jsx +174 -0
- package/src/ExpandableBox.jsx +207 -0
- package/src/Field.jsx +76 -0
- package/src/FieldBox.jsx +136 -0
- package/src/Form.jsx +291 -0
- package/src/FormValue.jsx +67 -0
- package/src/IconRadioBoxOldHubt.jsx +93 -0
- package/src/InputboxGroup.jsx +121 -0
- package/src/Radiobox.jsx +33 -0
- package/src/RichText.jsx +812 -0
- package/src/TextInput.jsx +519 -0
- package/src/alt/checkbox/SimpleCheckbox.jsx +162 -0
- package/src/alt/checkbox/ToggleCheckbox.jsx +143 -0
- package/src/alt/radiobox/CleanTabRadiobox.jsx +46 -0
- package/src/alt/radiobox/IconRadioBox.jsx +93 -0
- package/src/alt/radiobox/SimpleRadiobox.jsx +153 -0
- package/src/alt/radiobox/TabRadiobox.jsx +73 -0
- package/src/enhanceInput.jsx +107 -0
- package/src/index.js +27 -0
- package/src/useConfirmDialog.jsx +105 -0
- package/src/useDraftConfirmation.jsx +60 -0
- package/src/validation.js +92 -0
- package/src/withFormImageInput.jsx +297 -0
- package/.babelrc +0 -28
- package/lib/index.js +0 -6255
- package/rollup.config.js +0 -43
- package/yarn-error.log +0 -1607
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
import React, {forwardRef, useContext, useEffect, useRef, useState} from 'react'
|
|
2
|
+
import styled, {css} from 'styled-components'
|
|
3
|
+
import _ from 'lodash/fp'
|
|
4
|
+
import {StartlibsContext, useEnsureRef, usePopupToggle, usePrevious} from '@startlibs/core'
|
|
5
|
+
import {formatDate, formatDateToPattern, getColor, getNumberInputType, ifProp, media, parseDate} from '@startlibs/utils'
|
|
6
|
+
import {Icon} from '@startlibs/components'
|
|
7
|
+
import {Field} from './Field'
|
|
8
|
+
import {TextInput} from './TextInput'
|
|
9
|
+
import {enhanceInput, useInput} from './enhanceInput'
|
|
10
|
+
import {darken} from 'polished'
|
|
11
|
+
import {callIfFunction} from '@startlibs/utils'
|
|
12
|
+
|
|
13
|
+
export const CALENDAR_LEVEL_DAY = 'day'
|
|
14
|
+
export const CALENDAR_LEVEL_MONTH = 'month'
|
|
15
|
+
export const CALENDAR_LEVEL_YEAR = 'year'
|
|
16
|
+
|
|
17
|
+
const LEVELS = [CALENDAR_LEVEL_DAY, CALENDAR_LEVEL_MONTH, CALENDAR_LEVEL_YEAR]
|
|
18
|
+
|
|
19
|
+
const DatePickerContainer = styled.div`
|
|
20
|
+
width: 20rem;
|
|
21
|
+
position: absolute;
|
|
22
|
+
z-index: 100;
|
|
23
|
+
background-color: white;
|
|
24
|
+
border-radius: 5px;
|
|
25
|
+
border: 1px solid ${getColor('gray210')};
|
|
26
|
+
user-select: none;
|
|
27
|
+
box-shadow: 0 0 4px 0 rgba(0,0,0,0.2);
|
|
28
|
+
font-size: 13px;
|
|
29
|
+
text-align: center;
|
|
30
|
+
animation: 0.2s datePickerFadeInDown ease-out;
|
|
31
|
+
display: none;
|
|
32
|
+
@supports (flex-wrap: wrap) {
|
|
33
|
+
display: block;
|
|
34
|
+
}
|
|
35
|
+
input {
|
|
36
|
+
padding-right: 2.25rem;
|
|
37
|
+
${media.mobile`
|
|
38
|
+
width: 2.5rem;
|
|
39
|
+
`}
|
|
40
|
+
}
|
|
41
|
+
@keyframes datePickerFadeInDown {
|
|
42
|
+
from {
|
|
43
|
+
opacity: 0;
|
|
44
|
+
transform: translateY(-10px);
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
}
|
|
47
|
+
to {
|
|
48
|
+
opacity: 1;
|
|
49
|
+
transform: none;
|
|
50
|
+
pointer-events: none;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
`
|
|
54
|
+
|
|
55
|
+
const CalendarHeader = styled.div`
|
|
56
|
+
display: flex;
|
|
57
|
+
padding: 0.5rem;
|
|
58
|
+
align-items: center;
|
|
59
|
+
`
|
|
60
|
+
|
|
61
|
+
const CalendarContentContainer = styled.div`
|
|
62
|
+
padding: 0 0.5rem 0.5rem 0.5rem;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-wrap: wrap;
|
|
65
|
+
justify-content: space-around;
|
|
66
|
+
`
|
|
67
|
+
|
|
68
|
+
const WeekHeader = styled(CalendarContentContainer)`
|
|
69
|
+
font-size: 12px;
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
cursor: default;
|
|
72
|
+
span {
|
|
73
|
+
width: 2.5rem;
|
|
74
|
+
}
|
|
75
|
+
`
|
|
76
|
+
|
|
77
|
+
const CalendarClickable = styled.a`
|
|
78
|
+
:hover, :focus {
|
|
79
|
+
background-color: rgba(0,0,0,0.1);
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
}
|
|
82
|
+
`
|
|
83
|
+
|
|
84
|
+
const TitleButton = styled(CalendarClickable)`
|
|
85
|
+
font-size: 16px;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
flex-grow: 1;
|
|
88
|
+
border-radius: 4px;
|
|
89
|
+
line-height: 3rem;
|
|
90
|
+
`
|
|
91
|
+
|
|
92
|
+
const NavigationButton = styled(CalendarClickable)`
|
|
93
|
+
font-size: 21px;
|
|
94
|
+
width: 3rem;
|
|
95
|
+
text-align: center;
|
|
96
|
+
border-radius: 4px;
|
|
97
|
+
height: 3rem;
|
|
98
|
+
${Icon} {
|
|
99
|
+
line-height: 3rem;
|
|
100
|
+
}
|
|
101
|
+
`
|
|
102
|
+
|
|
103
|
+
const CalendarContent = styled(CalendarClickable)`
|
|
104
|
+
line-height: 2.25rem;
|
|
105
|
+
flex-grow: 1;
|
|
106
|
+
display: inline-block;
|
|
107
|
+
border-radius: 5px;
|
|
108
|
+
margin-top: 0.25rem;
|
|
109
|
+
margin-left: 0.25rem;
|
|
110
|
+
${ifProp('isEdge', css`color: rgba(0,0,0,0.4);`)}
|
|
111
|
+
${ifProp('isSelected', css`box-shadow: inset 0 0 0 1px ${getColor('gray210')};`)}
|
|
112
|
+
${ifProp('isDisabled',
|
|
113
|
+
`pointer-events: none;
|
|
114
|
+
opacity: 0.5;`
|
|
115
|
+
)}
|
|
116
|
+
${ifProp('isCurrent', css`
|
|
117
|
+
background: ${getColor('main')};
|
|
118
|
+
color: #FFF;
|
|
119
|
+
:hover {
|
|
120
|
+
background-color: ${props => darken(0.075, getColor('main')(props))};
|
|
121
|
+
}
|
|
122
|
+
${ifProp('isSelected', 'box-shadow: none;')}
|
|
123
|
+
`)}
|
|
124
|
+
`
|
|
125
|
+
|
|
126
|
+
const DayItem = styled(CalendarContent)`
|
|
127
|
+
${props => props.index % 7 === 0 && 'margin-left:0;'}
|
|
128
|
+
flex-basis: 12%;
|
|
129
|
+
`
|
|
130
|
+
const MonthItem = styled(CalendarContent)`
|
|
131
|
+
${props => props.index % 3 === 0 && 'margin-left:0;'}
|
|
132
|
+
flex-basis: 30%;
|
|
133
|
+
`
|
|
134
|
+
const YearItem = styled(CalendarContent)`
|
|
135
|
+
${props => props.index % 3 === 0 && 'margin-left:0;'}
|
|
136
|
+
flex-basis: 30%;
|
|
137
|
+
`
|
|
138
|
+
|
|
139
|
+
const DatePickerInputIcon = styled(Icon)`
|
|
140
|
+
position: absolute;
|
|
141
|
+
outline: none;
|
|
142
|
+
bottom: 0;
|
|
143
|
+
font-size: 21px;
|
|
144
|
+
line-height: 3rem;
|
|
145
|
+
right: 0;
|
|
146
|
+
width: 2rem;
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
color: ${getColor('gray150')};
|
|
149
|
+
:hover {
|
|
150
|
+
color: ${getColor('main')};
|
|
151
|
+
}
|
|
152
|
+
display: none;
|
|
153
|
+
@supports (flex-wrap: wrap) {
|
|
154
|
+
display: block;
|
|
155
|
+
}
|
|
156
|
+
${props => props.disabled && 'pointer-events: none;'}
|
|
157
|
+
${media.mobile`
|
|
158
|
+
line-height: 42px;
|
|
159
|
+
width: 2.25rem;
|
|
160
|
+
`}
|
|
161
|
+
`
|
|
162
|
+
|
|
163
|
+
const ClearButton = styled(Icon)`
|
|
164
|
+
color: ${getColor('gray90')};
|
|
165
|
+
font-size: 12px;
|
|
166
|
+
position: absolute;
|
|
167
|
+
right: 40px;
|
|
168
|
+
bottom: 0;
|
|
169
|
+
transform: translateY(-50%);
|
|
170
|
+
cursor: pointer;
|
|
171
|
+
width: 1.5rem;
|
|
172
|
+
line-height: 1.5rem;
|
|
173
|
+
text-align: center;
|
|
174
|
+
border-radius: 50%;
|
|
175
|
+
background: rgba(0,0,0,0.1);
|
|
176
|
+
:hover {
|
|
177
|
+
background: #f7d3d4;
|
|
178
|
+
color: ${getColor('alert')};
|
|
179
|
+
}
|
|
180
|
+
`
|
|
181
|
+
|
|
182
|
+
const InputAndIconWrapper = styled.div`
|
|
183
|
+
position: relative;
|
|
184
|
+
`
|
|
185
|
+
|
|
186
|
+
const ShortcutsWrapper = styled.div`
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-wrap: wrap;
|
|
189
|
+
gap: 0.5rem;
|
|
190
|
+
padding: 0.5rem 0.25rem 0;
|
|
191
|
+
color: ${getColor('gray150')};
|
|
192
|
+
|
|
193
|
+
.link {
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
font-weight: 600;
|
|
196
|
+
text-decoration: underline;
|
|
197
|
+
|
|
198
|
+
${media.mobile`
|
|
199
|
+
font-size: 13px;
|
|
200
|
+
`}
|
|
201
|
+
|
|
202
|
+
:hover {
|
|
203
|
+
color: ${getColor('main')};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
${ifProp('disabled', css`
|
|
207
|
+
pointer-events: none;
|
|
208
|
+
opacity: 0.5;
|
|
209
|
+
`)}
|
|
210
|
+
}
|
|
211
|
+
`
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
const KeyMapper = (level) => {
|
|
215
|
+
const upDownSpacing = level === CALENDAR_LEVEL_DAY ? 7 : 3
|
|
216
|
+
return {
|
|
217
|
+
'ArrowLeft': -1,
|
|
218
|
+
'ArrowRight': 1,
|
|
219
|
+
'ArrowUp': -upDownSpacing,
|
|
220
|
+
'ArrowDown': upDownSpacing
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const getUTCDate = (year,month = 0,date = 1,hour = 0,min =0 ,sec =0 ,ms = 0) => new Date(Date.UTC(year,month,date,hour,min,sec,ms))
|
|
225
|
+
const getUTCDateBefore = (year,month = 0,date = 1,hour = 0,min =0 ,sec =0 ,ms = -1) => new Date(Date.UTC(year,month,date,hour,min,sec,ms))
|
|
226
|
+
|
|
227
|
+
export const DatePicker = forwardRef((
|
|
228
|
+
{
|
|
229
|
+
format = 'dd/MM/yyyy',
|
|
230
|
+
min = new Date(now().setUTCFullYear(now().getUTCFullYear() - 100)),
|
|
231
|
+
max = new Date(now().setUTCFullYear(now().getUTCFullYear() + 100)),
|
|
232
|
+
weekStartsMonday = false,
|
|
233
|
+
label, mandatory, tabIndex, helpText,
|
|
234
|
+
path,
|
|
235
|
+
timezoneOffset = 0,
|
|
236
|
+
placeholder,
|
|
237
|
+
onBlur,
|
|
238
|
+
raw,
|
|
239
|
+
disabled,
|
|
240
|
+
level,
|
|
241
|
+
daysFromNowShortcuts, // Uma array de números representando atalhos de dias a partir de agora, e.g. [0, 1, 3]
|
|
242
|
+
monthsFromNowShortcuts, // Uma array de números representando atalhos de meses a partir de agora, e.g. [0, 1, 3]
|
|
243
|
+
...rest
|
|
244
|
+
},
|
|
245
|
+
maybeInputRef
|
|
246
|
+
) => {
|
|
247
|
+
|
|
248
|
+
const inputRef = useEnsureRef(maybeInputRef)
|
|
249
|
+
|
|
250
|
+
const [formValue, setUnoffsetedFormValue, {hasErrors}] = useInput({path,...rest},raw)
|
|
251
|
+
const setFormValue = (newV) => setUnoffsetedFormValue(_.isFinite(newV) && newV !== -1 ? newV - (timezoneOffset * 36e5) : newV)
|
|
252
|
+
|
|
253
|
+
const popup = usePopupToggle()
|
|
254
|
+
const calendarElement = useRef()
|
|
255
|
+
|
|
256
|
+
const messages = useContext(StartlibsContext).dateMessages
|
|
257
|
+
|
|
258
|
+
const time = Number(formValue)
|
|
259
|
+
const offsetedTime = time + (timezoneOffset * 36e5)
|
|
260
|
+
const [textValue, setRawTextValue] = useState(!isNaN(time) ? formatDate(new Date(offsetedTime), format, messages) : '')
|
|
261
|
+
const prevValue = usePrevious(textValue)
|
|
262
|
+
const [hasFormatErrors, setHasFormatErrors] = useState(false)
|
|
263
|
+
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
if (textValue && prevValue && textValue.length > prevValue.length+1) {
|
|
266
|
+
setTimeout(()=>inputRef.current.setSelectionRange(inputRef.current.value.length+1,inputRef.current.value.length+1),1);
|
|
267
|
+
}
|
|
268
|
+
},[textValue])
|
|
269
|
+
|
|
270
|
+
const isShortcutsEnabled = !!daysFromNowShortcuts && daysFromNowShortcuts.length > 0 || !!monthsFromNowShortcuts && monthsFromNowShortcuts.length > 0;
|
|
271
|
+
|
|
272
|
+
const setDateToNDaysFromNow = (daysFromNow) => {
|
|
273
|
+
const date = new Date(now())
|
|
274
|
+
date.setUTCDate(date.getUTCDate() + daysFromNow)
|
|
275
|
+
setFormValue(date.getTime())
|
|
276
|
+
updateText(date)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const setDateToNMonthsFromNow = (monthsFromNow) => {
|
|
280
|
+
const date = new Date(now())
|
|
281
|
+
date.setUTCMonth(date.getUTCMonth() + monthsFromNow)
|
|
282
|
+
setFormValue(date.getTime())
|
|
283
|
+
updateText(date)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const setTextValue = (textValue) => {
|
|
287
|
+
const value = formValue === -1 || isNaN(formValue) ? now() : new Date(Number(formValue))
|
|
288
|
+
const date = parseDate(textValue, value, format, messages)
|
|
289
|
+
if (isNaN(date.getTime())) {
|
|
290
|
+
const nextMonth = new Date(value)
|
|
291
|
+
const closestLeapYear = new Date(value)
|
|
292
|
+
nextMonth.setUTCMonth(value.getUTCMonth() + 1)
|
|
293
|
+
closestLeapYear.setUTCFullYear(getClosestNextLeapYear(value.getUTCFullYear()))
|
|
294
|
+
const nextMonthDate = parseDate(textValue, nextMonth, format, messages)
|
|
295
|
+
const closestLeapYearDate = parseDate(textValue, closestLeapYear, format, messages)
|
|
296
|
+
setRawTextValue(textValue)
|
|
297
|
+
setHasFormatErrors(isNaN(nextMonthDate.getTime()) && isNaN(closestLeapYearDate))
|
|
298
|
+
} else if (!inRange({min,max},date)) {
|
|
299
|
+
setRawTextValue(textValue)
|
|
300
|
+
setHasFormatErrors(true)
|
|
301
|
+
setFormValue(-1)
|
|
302
|
+
} else {
|
|
303
|
+
setRawTextValue(textValue)
|
|
304
|
+
setHasFormatErrors(false)
|
|
305
|
+
setFormValue(date.getTime())
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const updateText = (date) => {
|
|
310
|
+
setTextValue(formatDate(date, format, messages))
|
|
311
|
+
setHasFormatErrors(false)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const setValue = (value) => {
|
|
315
|
+
setFormValue(value)
|
|
316
|
+
updateText(new Date(value))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const confirmValue = () => {
|
|
320
|
+
popup.close()
|
|
321
|
+
if (!textValue) {
|
|
322
|
+
setFormValue(undefined)
|
|
323
|
+
setHasFormatErrors(false)
|
|
324
|
+
} else {
|
|
325
|
+
const date = parseDate(textValue, new Date(formValue), format, messages, true)
|
|
326
|
+
if (isNaN(date.getTime())) {
|
|
327
|
+
setHasFormatErrors(true)
|
|
328
|
+
setFormValue(-1)
|
|
329
|
+
} else {
|
|
330
|
+
updateText(date)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const _onBlur = (e) => {
|
|
336
|
+
const currentTarget = e.currentTarget
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
if (!currentTarget.contains(document.activeElement)) {
|
|
339
|
+
confirmValue()
|
|
340
|
+
}
|
|
341
|
+
callIfFunction(onBlur)
|
|
342
|
+
}, 1)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const onKeyDown = (e) => {
|
|
346
|
+
const isConfirm = e.key === 'Enter'
|
|
347
|
+
if (Object.keys(KeyMapper()).indexOf(e.key) >= 0 || isConfirm) {
|
|
348
|
+
if (popup.isOpen) {
|
|
349
|
+
e.preventDefault()
|
|
350
|
+
calendarElement.current.keyDown(e)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (e.key === 'Escape') {
|
|
354
|
+
e.stopPropagation()
|
|
355
|
+
popup.close()
|
|
356
|
+
const date = new Date(formValue)
|
|
357
|
+
if (!isNaN(date.getTime()))
|
|
358
|
+
updateText(date)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const refocus = (e) => inputRef.current.focus()
|
|
363
|
+
|
|
364
|
+
const onClick = e => {
|
|
365
|
+
if (disabled) return;
|
|
366
|
+
e.stopPropagation()
|
|
367
|
+
refocus()
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const iconClick = e => {
|
|
371
|
+
e.stopPropagation()
|
|
372
|
+
if (popup.isOpen) {
|
|
373
|
+
refocus()
|
|
374
|
+
popup.close()
|
|
375
|
+
} else {
|
|
376
|
+
refocus()
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const stopFocusPropagation = (e) => e.stopPropagation()
|
|
381
|
+
|
|
382
|
+
// const calendarValue = new Date(!isNaN(time) && formValue !== -1 ? offsetedTime : now())
|
|
383
|
+
const calendarValue = new Date(!isNaN(time) && formValue !== -1 ? offsetedTime : nowTimeZoneAdjusted(timezoneOffset))
|
|
384
|
+
calendarValue.setUTCHours(0, 0, 0)
|
|
385
|
+
return <Field helpText={helpText} label={label} mandatory={mandatory} onClick={onClick} onFocus={popup.open} onBlur={_onBlur}>
|
|
386
|
+
<InputAndIconWrapper>
|
|
387
|
+
<TextInput
|
|
388
|
+
raw
|
|
389
|
+
disabled={disabled}
|
|
390
|
+
type={getNumberInputType()}
|
|
391
|
+
tabIndex={tabIndex}
|
|
392
|
+
ref={inputRef}
|
|
393
|
+
constraint={formatDateToPattern(format)}
|
|
394
|
+
value={textValue}
|
|
395
|
+
setValue={setTextValue}
|
|
396
|
+
placeholder={placeholder}
|
|
397
|
+
hasErrors={hasErrors || hasFormatErrors}
|
|
398
|
+
onKeyDown={onKeyDown}
|
|
399
|
+
/>
|
|
400
|
+
<DatePickerInputIcon tabIndex={-1} onFocus={stopFocusPropagation} disabled={disabled} onClick={iconClick} icon="calendar" className={isShortcutsEnabled ? "hasShortcuts" : ""} />
|
|
401
|
+
</InputAndIconWrapper>
|
|
402
|
+
|
|
403
|
+
{(daysFromNowShortcuts?.length > 0 || monthsFromNowShortcuts?.length > 0) ?
|
|
404
|
+
<ShortcutsWrapper>
|
|
405
|
+
<span>Em:</span>
|
|
406
|
+
{daysFromNowShortcuts?.map((daysFromNow) => (
|
|
407
|
+
<span class="link"
|
|
408
|
+
key={`days-from-now-${daysFromNow}`}
|
|
409
|
+
onClick={() => setDateToNDaysFromNow(daysFromNow)}
|
|
410
|
+
disabled={disabled}
|
|
411
|
+
>
|
|
412
|
+
{daysFromNow} dia{daysFromNow > 1 ? 's' : ''}
|
|
413
|
+
</span>
|
|
414
|
+
))}
|
|
415
|
+
|
|
416
|
+
{monthsFromNowShortcuts?.map((monthsFromNow) => (
|
|
417
|
+
<span class="link"
|
|
418
|
+
key={`months-from-now${monthsFromNow}`}
|
|
419
|
+
onClick={() => setDateToNMonthsFromNow(monthsFromNow)}
|
|
420
|
+
disabled={disabled}
|
|
421
|
+
>
|
|
422
|
+
{monthsFromNow} {monthsFromNow > 1 ? 'meses' : 'mês'}
|
|
423
|
+
</span>
|
|
424
|
+
))}
|
|
425
|
+
</ShortcutsWrapper>
|
|
426
|
+
: null}
|
|
427
|
+
|
|
428
|
+
{
|
|
429
|
+
popup.isOpen &&
|
|
430
|
+
<DatePickerCalendar
|
|
431
|
+
raw
|
|
432
|
+
min={min}
|
|
433
|
+
max={max}
|
|
434
|
+
level={level}
|
|
435
|
+
weekStartsMonday={weekStartsMonday}
|
|
436
|
+
messages={messages}
|
|
437
|
+
ref={calendarElement}
|
|
438
|
+
updateTransientValue={updateText}
|
|
439
|
+
refocus={refocus}
|
|
440
|
+
onSelectDay={popup.toggle}
|
|
441
|
+
setValue={setValue}
|
|
442
|
+
value={calendarValue.getTime()}
|
|
443
|
+
/>
|
|
444
|
+
}
|
|
445
|
+
</Field>
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
const defaultProps = {
|
|
450
|
+
format: 'dd/MM/yyyy',
|
|
451
|
+
min: new Date(now().setUTCFullYear(now().getUTCFullYear() - 100)),
|
|
452
|
+
max: new Date(now().setUTCFullYear(now().getUTCFullYear() + 100)),
|
|
453
|
+
weekStartsMonday: false
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
class RawDatePickerCalendar extends React.Component {
|
|
459
|
+
|
|
460
|
+
static defaultProps = defaultProps
|
|
461
|
+
|
|
462
|
+
constructor(props) {
|
|
463
|
+
super(props)
|
|
464
|
+
this.state = {level: this.props.level || CALENDAR_LEVEL_DAY, currentDate: new Date(props.value || now())}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
element
|
|
468
|
+
|
|
469
|
+
componentDidUpdate(prevProps) {
|
|
470
|
+
if (prevProps.value !== this.props.value) {
|
|
471
|
+
this.setState({
|
|
472
|
+
currentDate: new Date(this.props.value)
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
setCurrentDatePlus = (n, type) => () => {
|
|
478
|
+
const date = new Date(this.state.currentDate.getTime())
|
|
479
|
+
switch (type) {
|
|
480
|
+
case CALENDAR_LEVEL_DAY:
|
|
481
|
+
date.setUTCDate(date.getUTCDate() + n)
|
|
482
|
+
break
|
|
483
|
+
case CALENDAR_LEVEL_MONTH:
|
|
484
|
+
date.setUTCMonth(date.getUTCMonth() + n)
|
|
485
|
+
break
|
|
486
|
+
case CALENDAR_LEVEL_YEAR:
|
|
487
|
+
date.setUTCFullYear(date.getUTCFullYear() + n)
|
|
488
|
+
break
|
|
489
|
+
default:
|
|
490
|
+
break
|
|
491
|
+
}
|
|
492
|
+
this.props.updateTransientValue && this.props.updateTransientValue(date)
|
|
493
|
+
this.setState({currentDate: date})
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
up = () => {
|
|
497
|
+
this.setState({level: LEVELS[LEVELS.indexOf(this.state.level) + 1]})
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
keyDown = (e) => {
|
|
502
|
+
const {level, currentDate} = this.state
|
|
503
|
+
const keyMapper = KeyMapper(level)
|
|
504
|
+
if (keyMapper[e.key]) {
|
|
505
|
+
e.preventDefault()
|
|
506
|
+
this.setCurrentDatePlus(keyMapper[e.key], level)()
|
|
507
|
+
}
|
|
508
|
+
if (e.key === 'Enter') {
|
|
509
|
+
e.stopPropagation()
|
|
510
|
+
e.preventDefault()
|
|
511
|
+
this.handleChange(currentDate)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
handleChange = (date) => {
|
|
516
|
+
this.props.setValue(date.getTime())
|
|
517
|
+
if (this.state.level === CALENDAR_LEVEL_DAY && this.props.onSelectDay) {
|
|
518
|
+
this.props.onSelectDay()
|
|
519
|
+
}
|
|
520
|
+
this.setState({level: LEVELS[Math.max(LEVELS.indexOf(this.state.level) - 1, 0)]})
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
getView = () => {
|
|
524
|
+
switch (this.state.level) {
|
|
525
|
+
case CALENDAR_LEVEL_DAY:
|
|
526
|
+
return <Calendar {...this.props} {...this.state} handleChange={this.handleChange}
|
|
527
|
+
changeCurrentDate={this.setCurrentDatePlus} up={this.up}/>
|
|
528
|
+
case CALENDAR_LEVEL_MONTH:
|
|
529
|
+
return <Months {...this.props} {...this.state} handleChange={this.handleChange}
|
|
530
|
+
changeCurrentDate={this.setCurrentDatePlus} up={this.up}/>
|
|
531
|
+
case CALENDAR_LEVEL_YEAR:
|
|
532
|
+
return <Years {...this.props} {...this.state} handleChange={this.handleChange}
|
|
533
|
+
changeCurrentDate={this.setCurrentDatePlus}/>
|
|
534
|
+
default:
|
|
535
|
+
return null
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
setRef = (e) => {
|
|
540
|
+
this.element = e
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
render() {
|
|
544
|
+
return (
|
|
545
|
+
<div ref={this.setRef} tabIndex={-1} onFocus={this.props.refocus} onKeyDown={this.keyDown}>
|
|
546
|
+
{this.getView()}
|
|
547
|
+
</div>
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const DatePickerCalendar = enhanceInput(RawDatePickerCalendar)
|
|
553
|
+
|
|
554
|
+
function now() {
|
|
555
|
+
const dt = new Date()
|
|
556
|
+
dt.setUTCHours(0, 0, 0, 0)
|
|
557
|
+
return dt
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function nowTimeZoneAdjusted(timezoneOffset){
|
|
561
|
+
const timezoneOffsetMilliseconds = timezoneOffset * 3600000; // 3600000 milliseconds in an hour
|
|
562
|
+
const dt = new Date()
|
|
563
|
+
dt.setTime(dt.getTime() + timezoneOffsetMilliseconds);
|
|
564
|
+
dt.setUTCHours(0, 0, 0, 0)
|
|
565
|
+
return dt;
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
function inRange(context, date) {
|
|
569
|
+
return context.min <= date && context.max >= date
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const mapDays = (currentDate, dayOffset, fn) => {
|
|
573
|
+
const month = []
|
|
574
|
+
const iter = new Date(currentDate)
|
|
575
|
+
iter.setUTCDate(1)
|
|
576
|
+
let startDay = dayOffset + iter.getUTCDate() - iter.getUTCDay()
|
|
577
|
+
if (startDay > 1 && startDay < 10) {
|
|
578
|
+
startDay = startDay - 7
|
|
579
|
+
}
|
|
580
|
+
iter.setUTCDate(startDay)
|
|
581
|
+
for (let week = 0; week < 6; week++) {
|
|
582
|
+
for (let day = week * 7; day < (week * 7) + 7; day++) {
|
|
583
|
+
month.push(fn(iter, startDay + day, day))
|
|
584
|
+
iter.setUTCDate(iter.getUTCDate() + 1)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return month
|
|
588
|
+
}
|
|
589
|
+
const Calendar = ({messages, max, min, value, currentDate, weekStartsMonday, changeCurrentDate, handleChange, up}) => {
|
|
590
|
+
const currentMonth = currentDate.getUTCMonth()
|
|
591
|
+
const dayOffset = weekStartsMonday ? 1 : 0
|
|
592
|
+
const today = now().getTime()
|
|
593
|
+
const handleDateChange = (e) => {
|
|
594
|
+
const date = new Date(currentDate)
|
|
595
|
+
handleChange(new Date(date.setUTCDate(e.currentTarget.getAttribute('data-day'))))
|
|
596
|
+
}
|
|
597
|
+
const previousMonth = getUTCDateBefore(currentDate.getUTCFullYear(),currentDate.getUTCMonth())
|
|
598
|
+
const nextMonth = getUTCDate(currentDate.getUTCFullYear(),currentDate.getUTCMonth()+1)
|
|
599
|
+
return (
|
|
600
|
+
<DatePickerContainer>
|
|
601
|
+
<CalendarHeader>
|
|
602
|
+
<NavigationButton disabled={!inRange({min,max},previousMonth)} onClick={changeCurrentDate(-1, CALENDAR_LEVEL_MONTH)}>
|
|
603
|
+
<Icon icon="arrow-left-line"/>
|
|
604
|
+
</NavigationButton>
|
|
605
|
+
<TitleButton onClick={up}>{messages('month', currentMonth)} {currentDate.getUTCFullYear()}</TitleButton>
|
|
606
|
+
<NavigationButton disabled={!inRange({min,max},nextMonth)} onClick={changeCurrentDate(1, CALENDAR_LEVEL_MONTH)}>
|
|
607
|
+
<Icon icon="arrow-right-line"/>
|
|
608
|
+
</NavigationButton>
|
|
609
|
+
</CalendarHeader>
|
|
610
|
+
<WeekHeader>
|
|
611
|
+
{[0, 1, 2, 3, 4, 5, 6].map((day) =>
|
|
612
|
+
<span key={day}>{messages('day', (day + dayOffset) % 7).slice(0, 3)}</span>
|
|
613
|
+
)}
|
|
614
|
+
</WeekHeader>
|
|
615
|
+
<CalendarContentContainer>
|
|
616
|
+
|
|
617
|
+
{mapDays(currentDate, dayOffset, (date, day, index) =>
|
|
618
|
+
<DayItem
|
|
619
|
+
index={index}
|
|
620
|
+
key={date.getTime()}
|
|
621
|
+
onClick={handleDateChange}
|
|
622
|
+
data-day={day}
|
|
623
|
+
isEdge={date.getUTCMonth() !== currentMonth}
|
|
624
|
+
isCurrent={date.getTime() === currentDate.getTime()}
|
|
625
|
+
isSelected={date.getTime() === value}
|
|
626
|
+
isDisabled={!inRange({max, min}, date)}
|
|
627
|
+
isToday={date.getTime() === today}
|
|
628
|
+
>
|
|
629
|
+
{date.getUTCDate()}
|
|
630
|
+
</DayItem>
|
|
631
|
+
)}
|
|
632
|
+
</CalendarContentContainer>
|
|
633
|
+
</DatePickerContainer>
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const getBeginDate = (year,month) => new Date(year,month,1,0,0,0,0)
|
|
638
|
+
|
|
639
|
+
const Months = ({currentDate, messages, value, changeCurrentDate, handleChange, up, min, max}) => {
|
|
640
|
+
const currentMonth = currentDate.getUTCMonth()
|
|
641
|
+
const sameYear = currentDate.getUTCFullYear() === new Date(value).getUTCFullYear()
|
|
642
|
+
const selectedMonth = sameYear ? currentDate.getUTCMonth() : undefined
|
|
643
|
+
const handleMonthChange = (e) => {
|
|
644
|
+
const date = new Date(currentDate)
|
|
645
|
+
handleChange(new Date(date.setUTCMonth(e.currentTarget.getAttribute('data-month'))))
|
|
646
|
+
}
|
|
647
|
+
return (
|
|
648
|
+
<DatePickerContainer>
|
|
649
|
+
<CalendarHeader>
|
|
650
|
+
<NavigationButton disabled={!inRange({min,max},getUTCDateBefore(currentDate.getUTCFullYear()))} onClick={changeCurrentDate(-1, CALENDAR_LEVEL_YEAR)}>
|
|
651
|
+
<Icon icon="arrow-left-line"/>
|
|
652
|
+
</NavigationButton>
|
|
653
|
+
<TitleButton onClick={up}>{currentDate.getUTCFullYear()}</TitleButton>
|
|
654
|
+
<NavigationButton disabled={!inRange({min,max},getUTCDate(currentDate.getUTCFullYear()+1))} onClick={changeCurrentDate(1, CALENDAR_LEVEL_YEAR)}>
|
|
655
|
+
<Icon icon="arrow-right-line"/>
|
|
656
|
+
</NavigationButton>
|
|
657
|
+
</CalendarHeader>
|
|
658
|
+
<CalendarContentContainer>
|
|
659
|
+
{[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map((month) =>
|
|
660
|
+
<MonthItem
|
|
661
|
+
disabled={!inRange({min,max},getUTCDate(currentDate.getUTCFullYear(),month)) && !inRange({min,max},getUTCDateBefore(currentDate.getUTCFullYear(),month+1))}
|
|
662
|
+
onClick={handleMonthChange}
|
|
663
|
+
key={month}
|
|
664
|
+
data-month={month}
|
|
665
|
+
index={month}
|
|
666
|
+
isCurrent={currentMonth === month}
|
|
667
|
+
isSelected={selectedMonth === month}
|
|
668
|
+
>
|
|
669
|
+
{messages('month', month).slice(0, 3)}
|
|
670
|
+
</MonthItem>
|
|
671
|
+
)}
|
|
672
|
+
</CalendarContentContainer>
|
|
673
|
+
</DatePickerContainer>
|
|
674
|
+
)
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Given the specified context, produces an HTML string
|
|
678
|
+
// representing months of the year.
|
|
679
|
+
|
|
680
|
+
function mapYears(currentDate, fn) {
|
|
681
|
+
const result = []
|
|
682
|
+
const year = currentDate.getUTCFullYear()
|
|
683
|
+
for (let i = 0; i < (4 * 3); i++) {
|
|
684
|
+
result.push(fn(year - (year % 10) - 1 + i, i < 1 || i > 10, i))
|
|
685
|
+
}
|
|
686
|
+
return result
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const Years = ({value, currentDate, changeCurrentDate, handleChange, min, max}) => {
|
|
690
|
+
const currentYear = currentDate.getUTCFullYear()
|
|
691
|
+
const selectedYear = new Date(value).getUTCFullYear()
|
|
692
|
+
const handleYearChange = (e) => {
|
|
693
|
+
const date = new Date(currentDate)
|
|
694
|
+
handleChange(new Date(date.setUTCFullYear(e.currentTarget.getAttribute('data-year'))))
|
|
695
|
+
}
|
|
696
|
+
return (
|
|
697
|
+
<DatePickerContainer>
|
|
698
|
+
<CalendarHeader>
|
|
699
|
+
<NavigationButton disabled={!inRange({min,max},getUTCDateBefore(currentDate.getUTCFullYear()))} onClick={changeCurrentDate(-10, CALENDAR_LEVEL_YEAR)}>
|
|
700
|
+
<Icon icon="arrow-left-line"/>
|
|
701
|
+
</NavigationButton>
|
|
702
|
+
<TitleButton>{currentYear - (currentYear % 10)}-{currentYear - (currentYear % 10) + 9}</TitleButton>
|
|
703
|
+
<NavigationButton disabled={!inRange({min,max},getUTCDate(currentDate.getUTCFullYear()+10))} onClick={changeCurrentDate(10, CALENDAR_LEVEL_YEAR)}>
|
|
704
|
+
<Icon icon="arrow-right-line"/>
|
|
705
|
+
</NavigationButton>
|
|
706
|
+
</CalendarHeader>
|
|
707
|
+
<CalendarContentContainer>
|
|
708
|
+
{mapYears(currentDate, (year, outside, i) =>
|
|
709
|
+
<YearItem
|
|
710
|
+
key={year}
|
|
711
|
+
onClick={handleYearChange}
|
|
712
|
+
disabled={!inRange({min,max},getUTCDate(year)) && !inRange({min,max},getUTCDateBefore(year+1))}
|
|
713
|
+
data-year={year}
|
|
714
|
+
index={i}
|
|
715
|
+
isCurrent={year === currentYear}
|
|
716
|
+
isSelected={year === selectedYear}
|
|
717
|
+
isEdge={outside}
|
|
718
|
+
>
|
|
719
|
+
{year}
|
|
720
|
+
</YearItem>
|
|
721
|
+
)}
|
|
722
|
+
</CalendarContentContainer>
|
|
723
|
+
</DatePickerContainer>
|
|
724
|
+
)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function getClosestNextLeapYear(year) {
|
|
728
|
+
let y = isNaN(year) ? now().getUTCFullYear() : year
|
|
729
|
+
while (!((y % 100 === 0) ? (y % 400 === 0) : (y % 4 === 0))) {
|
|
730
|
+
y++
|
|
731
|
+
}
|
|
732
|
+
return y
|
|
733
|
+
}
|