@widergy/mobile-ui 2.17.0 → 2.18.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/CHANGELOG.md +24 -2
- package/lib/components/UTBottomSheet/index.js +35 -32
- package/lib/components/UTBottomSheet/styles.js +4 -3
- package/lib/components/UTDatePicker/README.md +73 -0
- package/lib/components/UTDatePicker/components/Calendar/constants.js +3 -0
- package/lib/components/UTDatePicker/components/Calendar/index.js +197 -0
- package/lib/components/UTDatePicker/components/Day/index.js +44 -0
- package/lib/components/UTDatePicker/components/Day/styles.js +8 -0
- package/lib/components/UTDatePicker/components/PickerColumn/index.js +57 -0
- package/lib/components/UTDatePicker/constants.js +48 -0
- package/lib/components/UTDatePicker/index.js +135 -0
- package/lib/components/UTDatePicker/layout.js +108 -0
- package/lib/components/UTDatePicker/proptypes.js +20 -0
- package/lib/components/UTDatePicker/styles.js +63 -0
- package/lib/components/UTDatePicker/theme.js +18 -0
- package/lib/components/UTDatePicker/utils.js +52 -0
- package/lib/components/UTLoading/README.md +109 -31
- package/lib/components/UTLoading/index.js +9 -9
- package/lib/components/UTLoading/{components → versions/V0/components}/AnimatedCircles/index.js +1 -1
- package/lib/components/UTLoading/{components → versions/V0/components}/AnimatedCircles/styles.js +16 -13
- package/lib/components/UTLoading/{components → versions/V0/components}/Spinner/index.js +4 -4
- package/lib/components/UTLoading/{components → versions/V0/components}/Spinner/styles.js +0 -3
- package/lib/components/UTLoading/versions/V0/index.js +18 -0
- package/lib/components/UTLoading/versions/V1/components/LoaderWithLogo/index.js +84 -0
- package/lib/components/UTLoading/versions/V1/components/LoaderWithLogo/theme.js +45 -0
- package/lib/components/UTLoading/versions/V1/components/Spinner/constants.js +5 -0
- package/lib/components/UTLoading/versions/V1/components/Spinner/index.js +143 -0
- package/lib/components/UTLoading/versions/V1/components/Spinner/theme.js +23 -0
- package/lib/components/UTLoading/versions/V1/index.js +33 -0
- package/lib/constants/testIds.js +19 -0
- package/lib/index.js +1 -0
- package/package.json +5 -4
- /package/lib/components/UTLoading/{components → versions/V0/components}/AnimatedCircles/constants.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
|
-
# [2.
|
|
1
|
+
# [2.18.0](https://github.com/widergy/mobile-ui/compare/v2.17.1...v2.18.0) (2026-06-07)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* fix ([81b84c4](https://github.com/widergy/mobile-ui/commit/81b84c4f673b19883b8030102a31d3ef3da3968b))
|
|
7
|
+
* fix ([d86eab3](https://github.com/widergy/mobile-ui/commit/d86eab32c35d07cdafa14a24467d61311511c8a6))
|
|
8
|
+
* fix ([1bafddb](https://github.com/widergy/mobile-ui/commit/1bafddbd5d45c300cfcba13f9b16a3edcc877332))
|
|
9
|
+
* fixes ([424a166](https://github.com/widergy/mobile-ui/commit/424a1667af7df6a84524452b6e83d264b03013d4))
|
|
10
|
+
* fixes ([edc2cda](https://github.com/widergy/mobile-ui/commit/edc2cdaa5a543e529c1549d6e0a4d03cf5efc0dc))
|
|
2
11
|
|
|
3
12
|
|
|
4
13
|
### Features
|
|
5
14
|
|
|
6
|
-
* [
|
|
15
|
+
* [CX-2399] add V1 to UTLoading with new Spinner and LoaderWithLogo ([20c95f7](https://github.com/widergy/mobile-ui/commit/20c95f791209175e6c3bb4218af8689e1a9979ee))
|
|
16
|
+
|
|
17
|
+
## [2.17.1](https://github.com/widergy/mobile-ui/compare/v2.17.0...v2.17.1) (2026-06-05)
|
|
18
|
+
|
|
19
|
+
### Novedades y Mejoras
|
|
20
|
+
|
|
21
|
+
* Se incorporó un nuevo selector de fechas para mobile con calendario visual y posibilidad de ingresar la fecha manualmente. Permite configurar rangos de fechas permitidas, diferentes tamaños y modos de visualización, además de soportar estados como deshabilitado, solo lectura y campos obligatorios con mensajes de ayuda o error. [#507](https://github.com/widergy/Energy-UI-Mobile/pull/507) [DIS-1103](https://widergy.atlassian.net/browse/DIS-1103)
|
|
22
|
+
* Mejoras internas de la plataforma. [`7e0a800`](https://github.com/widergy/Energy-UI-Mobile/commit/7e0a800)
|
|
23
|
+
|
|
24
|
+
# [2.17.0](https://github.com/widergy/mobile-ui/compare/v2.16.0...v2.17.0) (2026-06-03)
|
|
25
|
+
|
|
26
|
+
### Novedades y Mejoras
|
|
27
|
+
|
|
28
|
+
* Se amplió el componente de banner con nuevas opciones de presentación: categoría encima del título, texto de ayuda debajo de la descripción, disposición del ícono y los botones en fila o columna, un segundo botón opcional y la posibilidad de cerrarlo desde una esquina. [#505](https://github.com/widergy/Energy-UI-Mobile/pull/505) [DIS-1102](https://widergy.atlassian.net/browse/DIS-1102)
|
|
7
29
|
|
|
8
30
|
# [2.16.0](https://github.com/widergy/mobile-ui/compare/v2.15.0...v2.16.0) (2026-05-15)
|
|
9
31
|
|
|
@@ -96,6 +96,7 @@ const UTBottomSheet = ({
|
|
|
96
96
|
}, [height]);
|
|
97
97
|
|
|
98
98
|
const ChildrenContainer = scrolleable ? ScrollView : View;
|
|
99
|
+
const hasHeader = !!(title || buttonText || description);
|
|
99
100
|
|
|
100
101
|
const actionAlignment =
|
|
101
102
|
actionAlignment_ || [primaryAction, secondaryAction, tertiaryAction].filter(Boolean).length === 2
|
|
@@ -118,44 +119,46 @@ const UTBottomSheet = ({
|
|
|
118
119
|
>
|
|
119
120
|
<Animated.View style={[styles.animatedContainer(theme), { height: modalHeight }, pan.getLayout()]}>
|
|
120
121
|
<SafeAreaView onLayout={onLayout} style={styles.content(adjustableHeight)}>
|
|
121
|
-
<View style={styles.dragHandle}>
|
|
122
|
+
<View style={styles.dragHandle(hasHeader)}>
|
|
122
123
|
<View style={styles.handleIndicator(theme)} />
|
|
123
124
|
</View>
|
|
124
125
|
<View style={styles.content(adjustableHeight)}>
|
|
125
|
-
|
|
126
|
-
<View style={styles.
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{title}
|
|
136
|
-
</UTFieldLabel>
|
|
137
|
-
{buttonText && (
|
|
138
|
-
<UTButton
|
|
139
|
-
colorTheme="primary"
|
|
140
|
-
onPress={onButtonPress ?? onClose}
|
|
141
|
-
variant="semitransparent"
|
|
142
|
-
dataTestId={dataTestId ? `${dataTestId}.${closeButton}` : undefined}
|
|
143
|
-
{...onCloseProps}
|
|
126
|
+
{hasHeader && (
|
|
127
|
+
<View style={styles.header}>
|
|
128
|
+
<View style={styles.headerTopRow}>
|
|
129
|
+
<UTFieldLabel
|
|
130
|
+
required={required}
|
|
131
|
+
style={styles.title}
|
|
132
|
+
variant="subtitle1"
|
|
133
|
+
weight="medium"
|
|
134
|
+
dataTestId={dataTestId ? `${dataTestId}.${titleTestId}` : undefined}
|
|
135
|
+
{...titleProps}
|
|
144
136
|
>
|
|
145
|
-
{
|
|
146
|
-
</
|
|
137
|
+
{title}
|
|
138
|
+
</UTFieldLabel>
|
|
139
|
+
{buttonText && (
|
|
140
|
+
<UTButton
|
|
141
|
+
colorTheme="primary"
|
|
142
|
+
onPress={onButtonPress ?? onClose}
|
|
143
|
+
variant="semitransparent"
|
|
144
|
+
dataTestId={dataTestId ? `${dataTestId}.${closeButton}` : undefined}
|
|
145
|
+
{...onCloseProps}
|
|
146
|
+
>
|
|
147
|
+
{buttonText}
|
|
148
|
+
</UTButton>
|
|
149
|
+
)}
|
|
150
|
+
</View>
|
|
151
|
+
{description && (
|
|
152
|
+
<UTLabel
|
|
153
|
+
colorTheme="gray"
|
|
154
|
+
variant="small"
|
|
155
|
+
dataTestId={dataTestId ? `${dataTestId}.${descriptionTestId}` : undefined}
|
|
156
|
+
>
|
|
157
|
+
{description}
|
|
158
|
+
</UTLabel>
|
|
147
159
|
)}
|
|
148
160
|
</View>
|
|
149
|
-
|
|
150
|
-
<UTLabel
|
|
151
|
-
colorTheme="gray"
|
|
152
|
-
variant="small"
|
|
153
|
-
dataTestId={dataTestId ? `${dataTestId}.${descriptionTestId}` : undefined}
|
|
154
|
-
>
|
|
155
|
-
{description}
|
|
156
|
-
</UTLabel>
|
|
157
|
-
)}
|
|
158
|
-
</View>
|
|
161
|
+
)}
|
|
159
162
|
<ChildrenContainer
|
|
160
163
|
keyboardShouldPersistTaps="handled"
|
|
161
164
|
style={styles.childrenContainer(adjustableHeight, withBodyPadding)}
|
|
@@ -42,11 +42,12 @@ const styles = StyleSheet.create({
|
|
|
42
42
|
content: adjustableHeight => ({
|
|
43
43
|
flexGrow: adjustableHeight ? 0 : 1
|
|
44
44
|
}),
|
|
45
|
-
dragHandle: {
|
|
45
|
+
dragHandle: hasHeader => ({
|
|
46
46
|
alignItems: 'center',
|
|
47
47
|
backgroundColor: 'transparent',
|
|
48
|
-
padding: 16
|
|
49
|
-
|
|
48
|
+
padding: 16,
|
|
49
|
+
paddingBottom: hasHeader ? 16 : 0
|
|
50
|
+
}),
|
|
50
51
|
handleIndicator: theme => ({
|
|
51
52
|
backgroundColor: theme.Palette.light['04'],
|
|
52
53
|
borderRadius: 2.5,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# UTDatePicker
|
|
2
|
+
|
|
3
|
+
Selector de fechas con calendario modal y soporte de entrada manual. Adaptación mobile del componente `UTDatePicker` de Energy-UI web (V1).
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Nombre | Tipo | Default | Descripción |
|
|
8
|
+
|-------------|---------------------------|---------------|-----------------------------------------------------------------------|
|
|
9
|
+
| clearable | `bool` | `true` | Muestra botón para limpiar la fecha seleccionada |
|
|
10
|
+
| CustomIcon | `elementType` | — | Ícono personalizado para el botón del calendario |
|
|
11
|
+
| disabled | `bool` | — | Deshabilita toda interacción |
|
|
12
|
+
| error | `string` | `''` | Mensaje de error externo |
|
|
13
|
+
| helpText | `string` | — | Texto de ayuda debajo del campo |
|
|
14
|
+
| modalProps | `object` | — | Props adicionales para el Modal de React Native (equivalente mobile de `popoverProps` en web) |
|
|
15
|
+
| onChange | `func` | `() => {}` | Callback al seleccionar una fecha. Recibe string en formato `DD/MM/YYYY` |
|
|
16
|
+
| placeholder | `string` | `'DD/MM/AAAA'`| Texto placeholder del campo |
|
|
17
|
+
| range | `{ maxDate, minDate }` | — | Restricción de rango. Fechas en formato `DD/MM/YYYY` |
|
|
18
|
+
| readOnly | `bool` | `false` | Modo solo lectura |
|
|
19
|
+
| required | `bool` | — | Marca el título como requerido |
|
|
20
|
+
| size | `'sm' \| 'md'` | `'sm'` | Tamaño del campo de entrada |
|
|
21
|
+
| style | `{ root: ViewStyle }` | — | Estilos personalizados para el contenedor raíz |
|
|
22
|
+
| title | `string` | — | Etiqueta superior del campo |
|
|
23
|
+
| value | `string` | `''` | Fecha seleccionada en formato `DD/MM/YYYY` |
|
|
24
|
+
| variant | `'select' \| 'picker'` | `'select'` | Variante visual del campo. `'picker'` usa fondo transparente sin borde |
|
|
25
|
+
|
|
26
|
+
## Variantes
|
|
27
|
+
|
|
28
|
+
### `variant='select'` (default)
|
|
29
|
+
Campo con borde y fondo blanco. Muestra el mensaje de error debajo del campo.
|
|
30
|
+
|
|
31
|
+
### `variant='picker'`
|
|
32
|
+
Fondo transparente sin borde. El error no se muestra debajo (se maneja externamente).
|
|
33
|
+
|
|
34
|
+
## Adaptaciones mobile vs web
|
|
35
|
+
|
|
36
|
+
- El calendario se abre mediante el ícono de calendario (no al hacer foco en el input, como en web).
|
|
37
|
+
- El `Popover` de MUI fue reemplazado por un `Modal` de React Native. Usar `modalProps` en lugar de `popoverProps`.
|
|
38
|
+
- Los estilos usan `StyleSheet` y el sistema de theming de la librería (no CSS Modules).
|
|
39
|
+
- La entrada de texto usa teclado numérico (`type='number'`).
|
|
40
|
+
|
|
41
|
+
## Ejemplos
|
|
42
|
+
|
|
43
|
+
```jsx
|
|
44
|
+
// Básico
|
|
45
|
+
<UTDatePicker
|
|
46
|
+
onChange={date => console.log(date)}
|
|
47
|
+
title="Fecha de nacimiento"
|
|
48
|
+
value={selectedDate}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
// Con rango y error
|
|
52
|
+
<UTDatePicker
|
|
53
|
+
error="La fecha no es válida"
|
|
54
|
+
onChange={setDate}
|
|
55
|
+
range={{ minDate: '01/01/2020', maxDate: '31/12/2030' }}
|
|
56
|
+
title="Seleccioná una fecha"
|
|
57
|
+
value={date}
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
// Variante picker (transparente)
|
|
61
|
+
<UTDatePicker
|
|
62
|
+
onChange={setDate}
|
|
63
|
+
value={date}
|
|
64
|
+
variant="picker"
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
// Solo lectura
|
|
68
|
+
<UTDatePicker
|
|
69
|
+
readOnly
|
|
70
|
+
title="Fecha registrada"
|
|
71
|
+
value="15/06/2024"
|
|
72
|
+
/>
|
|
73
|
+
```
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import 'dayjs/locale/es';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { func, object, string } from 'prop-types';
|
|
6
|
+
|
|
7
|
+
import UTButton from '../../../UTButton';
|
|
8
|
+
import UTLabel from '../../../UTLabel';
|
|
9
|
+
import { TEST_IDS } from '../../../../constants/testIds';
|
|
10
|
+
import {
|
|
11
|
+
CALENDAR_VIEWS,
|
|
12
|
+
DAY_UNIT,
|
|
13
|
+
MONTH_LABELS,
|
|
14
|
+
MONTH_UNIT,
|
|
15
|
+
MONTH_YEAR_FORMAT,
|
|
16
|
+
OUTPUT_LABEL_MASK,
|
|
17
|
+
WEEKDAYS_LABELS
|
|
18
|
+
} from '../../constants';
|
|
19
|
+
import styles from '../../styles';
|
|
20
|
+
import { getWeeks, getYearRange } from '../../utils';
|
|
21
|
+
import Day from '../Day';
|
|
22
|
+
import PickerColumn from '../PickerColumn';
|
|
23
|
+
|
|
24
|
+
import { CALENDAR_LOCALE, PICKER_ITEM_HEIGHT, WEEK_KEY_FORMAT } from './constants';
|
|
25
|
+
|
|
26
|
+
const { UTDatePicker: datePickerTestIds } = TEST_IDS;
|
|
27
|
+
|
|
28
|
+
const Calendar = ({ dataTestId, maxDate, minDate, onDaySelect, pickedDate }) => {
|
|
29
|
+
const monthScrollRef = useRef(null);
|
|
30
|
+
const yearScrollRef = useRef(null);
|
|
31
|
+
const [currentMonth, setCurrentMonth] = useState(pickedDate?.isValid() ? pickedDate : dayjs());
|
|
32
|
+
const [viewMode, setViewMode] = useState(CALENDAR_VIEWS.calendar);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (pickedDate?.isValid()) setCurrentMonth(pickedDate);
|
|
36
|
+
}, [pickedDate]);
|
|
37
|
+
|
|
38
|
+
const weeks = getWeeks(currentMonth);
|
|
39
|
+
const years = useMemo(() => getYearRange(minDate, maxDate), [minDate, maxDate]);
|
|
40
|
+
const selectedYear = pickedDate?.isValid() ? pickedDate.year() : null;
|
|
41
|
+
const selectedMonth = pickedDate?.isValid() ? pickedDate.month() : null;
|
|
42
|
+
|
|
43
|
+
const visibleMonths = useMemo(() => {
|
|
44
|
+
const viewedYear = currentMonth.year();
|
|
45
|
+
const parsedMin = minDate ? dayjs(minDate, OUTPUT_LABEL_MASK) : null;
|
|
46
|
+
const parsedMax = maxDate ? dayjs(maxDate, OUTPUT_LABEL_MASK) : null;
|
|
47
|
+
const minMonthBound = parsedMin?.year() === viewedYear ? parsedMin.month() : 0;
|
|
48
|
+
const maxMonthBound = parsedMax?.year() === viewedYear ? parsedMax.month() : 11;
|
|
49
|
+
return MONTH_LABELS.map((label, index) => ({ index, label })).filter(
|
|
50
|
+
({ index }) => index >= minMonthBound && index <= maxMonthBound
|
|
51
|
+
);
|
|
52
|
+
}, [currentMonth, minDate, maxDate]);
|
|
53
|
+
|
|
54
|
+
const handlePrevMonth = () => setCurrentMonth(prev => prev.subtract(1, MONTH_UNIT));
|
|
55
|
+
const handleNextMonth = () => setCurrentMonth(prev => prev.add(1, MONTH_UNIT));
|
|
56
|
+
|
|
57
|
+
const handleMonthSelect = month => {
|
|
58
|
+
setCurrentMonth(prev => prev.month(month));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleYearSelect = year => {
|
|
62
|
+
setCurrentMonth(prev => prev.year(year));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const isInYearView = viewMode === CALENDAR_VIEWS.year;
|
|
66
|
+
const rawMonthLabel = currentMonth.locale(CALENDAR_LOCALE).format(MONTH_YEAR_FORMAT);
|
|
67
|
+
const monthYearLabel = rawMonthLabel.charAt(0).toUpperCase() + rawMonthLabel.slice(1);
|
|
68
|
+
|
|
69
|
+
const toggleViewMode = () =>
|
|
70
|
+
setViewMode(prev => (prev === CALENDAR_VIEWS.calendar ? CALENDAR_VIEWS.year : CALENDAR_VIEWS.calendar));
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!isInYearView) return;
|
|
74
|
+
const targetMonth = selectedMonth ?? currentMonth.month();
|
|
75
|
+
const monthScrollPos = visibleMonths.findIndex(m => m.index === targetMonth);
|
|
76
|
+
const yearIdx = years.findIndex(y => y === (selectedYear ?? currentMonth.year()));
|
|
77
|
+
const id = setTimeout(() => {
|
|
78
|
+
if (monthScrollPos >= 0) {
|
|
79
|
+
monthScrollRef.current?.scrollTo({ animated: false, y: monthScrollPos * PICKER_ITEM_HEIGHT });
|
|
80
|
+
}
|
|
81
|
+
if (yearIdx >= 0) yearScrollRef.current?.scrollTo({ animated: false, y: yearIdx * PICKER_ITEM_HEIGHT });
|
|
82
|
+
}, 50);
|
|
83
|
+
return () => clearTimeout(id);
|
|
84
|
+
}, [isInYearView, visibleMonths, years, selectedMonth, selectedYear, currentMonth]);
|
|
85
|
+
|
|
86
|
+
const isDisabled = date => {
|
|
87
|
+
if (minDate && date.isBefore(dayjs(minDate, OUTPUT_LABEL_MASK), DAY_UNIT)) return true;
|
|
88
|
+
if (maxDate && date.isAfter(dayjs(maxDate, OUTPUT_LABEL_MASK), DAY_UNIT)) return true;
|
|
89
|
+
return false;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const monthItems = visibleMonths.map(({ index, label }) => ({
|
|
93
|
+
key: label,
|
|
94
|
+
label,
|
|
95
|
+
onPress: () => handleMonthSelect(index),
|
|
96
|
+
selected: index === currentMonth.month(),
|
|
97
|
+
testID: `${datePickerTestIds.monthItem}.${label}`
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
const yearItems = years.map(year => ({
|
|
101
|
+
key: year,
|
|
102
|
+
label: String(year),
|
|
103
|
+
onPress: () => handleYearSelect(year),
|
|
104
|
+
selected: year === currentMonth.year(),
|
|
105
|
+
testID: `${datePickerTestIds.yearItem}.${year}`
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<View style={styles.calendarRoot} testID={dataTestId || datePickerTestIds.calendar}>
|
|
110
|
+
<View style={styles.nav}>
|
|
111
|
+
{isInYearView ? (
|
|
112
|
+
<View style={styles.navSpacer} />
|
|
113
|
+
) : (
|
|
114
|
+
<UTButton
|
|
115
|
+
colorTheme="secondary"
|
|
116
|
+
dataTestId={datePickerTestIds.calendarPrevBtn}
|
|
117
|
+
Icon="IconChevronLeft"
|
|
118
|
+
onPress={handlePrevMonth}
|
|
119
|
+
size="small"
|
|
120
|
+
variant="text"
|
|
121
|
+
/>
|
|
122
|
+
)}
|
|
123
|
+
|
|
124
|
+
<UTButton
|
|
125
|
+
colorTheme="secondary"
|
|
126
|
+
dataTestId={datePickerTestIds.calendarMonthYear}
|
|
127
|
+
Icon={isInYearView ? 'IconChevronUp' : 'IconChevronDown'}
|
|
128
|
+
iconPlacement="right"
|
|
129
|
+
onPress={toggleViewMode}
|
|
130
|
+
size="small"
|
|
131
|
+
style={{ root: styles.navLabel }}
|
|
132
|
+
variant="text"
|
|
133
|
+
>
|
|
134
|
+
{monthYearLabel}
|
|
135
|
+
</UTButton>
|
|
136
|
+
|
|
137
|
+
{isInYearView ? (
|
|
138
|
+
<View style={styles.navSpacer} />
|
|
139
|
+
) : (
|
|
140
|
+
<UTButton
|
|
141
|
+
colorTheme="secondary"
|
|
142
|
+
dataTestId={datePickerTestIds.calendarNextBtn}
|
|
143
|
+
Icon="IconChevronRight"
|
|
144
|
+
onPress={handleNextMonth}
|
|
145
|
+
size="small"
|
|
146
|
+
variant="text"
|
|
147
|
+
/>
|
|
148
|
+
)}
|
|
149
|
+
</View>
|
|
150
|
+
|
|
151
|
+
{isInYearView ? (
|
|
152
|
+
<View style={styles.pickerContainer}>
|
|
153
|
+
<PickerColumn items={monthItems} scrollRef={monthScrollRef} testID={datePickerTestIds.monthView} />
|
|
154
|
+
<PickerColumn items={yearItems} scrollRef={yearScrollRef} testID={datePickerTestIds.yearView} />
|
|
155
|
+
</View>
|
|
156
|
+
) : (
|
|
157
|
+
<View style={styles.month}>
|
|
158
|
+
<View style={styles.week}>
|
|
159
|
+
{WEEKDAYS_LABELS.map(label => (
|
|
160
|
+
<UTLabel colorTheme="gray" key={label} style={styles.dayCell} variant="small" weight="medium">
|
|
161
|
+
{label}
|
|
162
|
+
</UTLabel>
|
|
163
|
+
))}
|
|
164
|
+
</View>
|
|
165
|
+
|
|
166
|
+
{weeks.map(week => (
|
|
167
|
+
<View key={week[0].format(WEEK_KEY_FORMAT)} style={styles.week}>
|
|
168
|
+
{week.map(date => {
|
|
169
|
+
const dayInCurrentMonth = date.month() === currentMonth.month();
|
|
170
|
+
return (
|
|
171
|
+
<Day
|
|
172
|
+
date={date}
|
|
173
|
+
dayInCurrentMonth={dayInCurrentMonth}
|
|
174
|
+
isDisabled={isDisabled(date)}
|
|
175
|
+
key={date.format(WEEK_KEY_FORMAT)}
|
|
176
|
+
onClick={onDaySelect}
|
|
177
|
+
pickedDate={pickedDate}
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
})}
|
|
181
|
+
</View>
|
|
182
|
+
))}
|
|
183
|
+
</View>
|
|
184
|
+
)}
|
|
185
|
+
</View>
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
Calendar.propTypes = {
|
|
190
|
+
dataTestId: string,
|
|
191
|
+
maxDate: string,
|
|
192
|
+
minDate: string,
|
|
193
|
+
onDaySelect: func,
|
|
194
|
+
pickedDate: object
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export default Calendar;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { bool, func, object } from 'prop-types';
|
|
5
|
+
|
|
6
|
+
import { useTheme } from '../../../../theming';
|
|
7
|
+
import UTButton from '../../../UTButton';
|
|
8
|
+
import { DAY_NUMBER_FORMAT } from '../../constants';
|
|
9
|
+
import { getDayCellButtonStyle } from '../../theme';
|
|
10
|
+
import { isSelected } from '../../utils';
|
|
11
|
+
|
|
12
|
+
import styles from './styles';
|
|
13
|
+
|
|
14
|
+
const Day = ({ date, dayInCurrentMonth, isDisabled, onClick, pickedDate }) => {
|
|
15
|
+
const theme = useTheme();
|
|
16
|
+
const selected = isSelected(date, pickedDate);
|
|
17
|
+
|
|
18
|
+
if (!dayInCurrentMonth) return <View style={styles.otherMonthCell} />;
|
|
19
|
+
|
|
20
|
+
const buttonStyle = getDayCellButtonStyle(selected, theme);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<UTButton
|
|
24
|
+
colorTheme={selected ? 'primary' : 'secondary'}
|
|
25
|
+
disabled={isDisabled}
|
|
26
|
+
onPress={() => onClick(date)}
|
|
27
|
+
size="small"
|
|
28
|
+
style={buttonStyle}
|
|
29
|
+
variant={selected ? 'filled' : 'text'}
|
|
30
|
+
>
|
|
31
|
+
{dayjs(date).format(DAY_NUMBER_FORMAT)}
|
|
32
|
+
</UTButton>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
Day.propTypes = {
|
|
37
|
+
date: object,
|
|
38
|
+
dayInCurrentMonth: bool,
|
|
39
|
+
isDisabled: bool,
|
|
40
|
+
onClick: func,
|
|
41
|
+
pickedDate: object
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default memo(Day);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScrollView, View } from 'react-native';
|
|
3
|
+
import { arrayOf, bool, func, number, object, oneOfType, shape, string } from 'prop-types';
|
|
4
|
+
|
|
5
|
+
import { useTheme } from '../../../../theming';
|
|
6
|
+
import UTButton from '../../../UTButton';
|
|
7
|
+
import { getYearItemStyle } from '../../theme';
|
|
8
|
+
import styles from '../../styles';
|
|
9
|
+
|
|
10
|
+
const PickerColumn = ({ items, scrollRef, testID }) => {
|
|
11
|
+
const theme = useTheme();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<View style={styles.pickerColumn}>
|
|
15
|
+
<ScrollView
|
|
16
|
+
contentContainerStyle={styles.pickerScrollContent}
|
|
17
|
+
ref={scrollRef}
|
|
18
|
+
showsVerticalScrollIndicator={false}
|
|
19
|
+
style={styles.pickerScroll}
|
|
20
|
+
testID={testID}
|
|
21
|
+
>
|
|
22
|
+
{items.map(({ key, label, onPress, selected, testID: itemTestID }) => {
|
|
23
|
+
const itemTheme = getYearItemStyle(selected, theme);
|
|
24
|
+
return (
|
|
25
|
+
<UTButton
|
|
26
|
+
colorTheme={selected ? 'primary' : 'secondary'}
|
|
27
|
+
dataTestId={itemTestID}
|
|
28
|
+
key={key}
|
|
29
|
+
onPress={onPress}
|
|
30
|
+
size="small"
|
|
31
|
+
style={{ root: { ...styles.yearItem, ...itemTheme } }}
|
|
32
|
+
variant={selected ? 'filled' : 'text'}
|
|
33
|
+
>
|
|
34
|
+
{label}
|
|
35
|
+
</UTButton>
|
|
36
|
+
);
|
|
37
|
+
})}
|
|
38
|
+
</ScrollView>
|
|
39
|
+
</View>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
PickerColumn.propTypes = {
|
|
44
|
+
items: arrayOf(
|
|
45
|
+
shape({
|
|
46
|
+
key: oneOfType([number, string]),
|
|
47
|
+
label: string,
|
|
48
|
+
onPress: func,
|
|
49
|
+
selected: bool,
|
|
50
|
+
testID: string
|
|
51
|
+
})
|
|
52
|
+
),
|
|
53
|
+
scrollRef: object,
|
|
54
|
+
testID: string
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default PickerColumn;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const OUTPUT_LABEL_MASK = 'DD/MM/YYYY';
|
|
2
|
+
|
|
3
|
+
export const WEEKDAYS_LABELS = ['Dom', 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab'];
|
|
4
|
+
|
|
5
|
+
export const MONTH_YEAR_FORMAT = 'MMMM YYYY';
|
|
6
|
+
|
|
7
|
+
export const DAY_NUMBER_FORMAT = 'DD';
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_PLACEHOLDER = 'DD/MM/AAAA';
|
|
10
|
+
|
|
11
|
+
export const TYPING_ERROR_MESSAGE = 'Fecha inválida';
|
|
12
|
+
|
|
13
|
+
export const VARIANTS = {
|
|
14
|
+
picker: 'picker',
|
|
15
|
+
select: 'select'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const SIZES = {
|
|
19
|
+
md: 'md',
|
|
20
|
+
sm: 'sm'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const DAY_UNIT = 'day';
|
|
24
|
+
export const MONTH_UNIT = 'month';
|
|
25
|
+
|
|
26
|
+
export const CALENDAR_VIEWS = {
|
|
27
|
+
calendar: 'calendar',
|
|
28
|
+
year: 'year'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const MONTH_LABELS = [
|
|
32
|
+
'Ene',
|
|
33
|
+
'Feb',
|
|
34
|
+
'Mar',
|
|
35
|
+
'Abr',
|
|
36
|
+
'May',
|
|
37
|
+
'Jun',
|
|
38
|
+
'Jul',
|
|
39
|
+
'Ago',
|
|
40
|
+
'Sep',
|
|
41
|
+
'Oct',
|
|
42
|
+
'Nov',
|
|
43
|
+
'Dic'
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
export const YEAR_RANGE_BEFORE = 50;
|
|
47
|
+
export const YEAR_RANGE_AFTER = 10;
|
|
48
|
+
export const YEARS_PER_ROW = 3;
|