@widergy/mobile-ui 2.16.0 → 2.17.1

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.
@@ -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
- <View style={styles.header}>
126
- <View style={styles.headerTopRow}>
127
- <UTFieldLabel
128
- required={required}
129
- style={styles.title}
130
- variant="subtitle1"
131
- weight="medium"
132
- dataTestId={dataTestId ? `${dataTestId}.${titleTestId}` : undefined}
133
- {...titleProps}
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
- {buttonText}
146
- </UTButton>
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
- {description && (
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,3 @@
1
+ export const CALENDAR_LOCALE = 'es';
2
+ export const PICKER_ITEM_HEIGHT = 40;
3
+ export const WEEK_KEY_FORMAT = 'YYYY-MM-DD';
@@ -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,8 @@
1
+ import { StyleSheet } from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ otherMonthCell: {
5
+ flex: 1,
6
+ height: 36
7
+ }
8
+ });
@@ -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;
@@ -0,0 +1,135 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Keyboard } from 'react-native';
3
+ import dayjs from 'dayjs';
4
+ import customParseFormat from 'dayjs/plugin/customParseFormat';
5
+
6
+ import { COMPONENT_KEYS } from '../UTBaseInputField/constants';
7
+
8
+ import { DEFAULT_PLACEHOLDER, OUTPUT_LABEL_MASK, VARIANTS } from './constants';
9
+ import UTDatePickerLayout from './layout';
10
+ import { propTypes } from './proptypes';
11
+ import { dateMatchesFormat, getFinalDate } from './utils';
12
+
13
+ dayjs.extend(customParseFormat);
14
+
15
+ const UTDatePicker = ({
16
+ modalProps,
17
+ clearable = true,
18
+ CustomIcon,
19
+ disabled,
20
+ error = '',
21
+ helpText,
22
+ onChange = () => {},
23
+ placeholder,
24
+ range,
25
+ readOnly = false,
26
+ required,
27
+ style,
28
+ title,
29
+ value = '',
30
+ variant = VARIANTS.select
31
+ }) => {
32
+ const { maxDate, minDate } = range || {};
33
+
34
+ const [pickedDate, setPickedDate] = useState(null);
35
+ const [isOpen, setIsOpen] = useState(false);
36
+
37
+ useEffect(() => {
38
+ if (!value && pickedDate?.isValid()) setPickedDate(null);
39
+ else if (dateMatchesFormat(value, OUTPUT_LABEL_MASK)) setPickedDate(dayjs(value, OUTPUT_LABEL_MASK));
40
+ else setPickedDate(null);
41
+ }, [value]);
42
+
43
+ const openCalendar = () => {
44
+ if (disabled || readOnly) return;
45
+ Keyboard.dismiss();
46
+ setIsOpen(true);
47
+ };
48
+
49
+ const handleClose = () => {
50
+ setIsOpen(false);
51
+ };
52
+
53
+ const handleDaySelect = date => {
54
+ const finalDate = getFinalDate({ date, maxDate, minDate });
55
+ setPickedDate(finalDate);
56
+ if (finalDate.isValid()) onChange(finalDate.format(OUTPUT_LABEL_MASK));
57
+ else onChange('');
58
+ setIsOpen(false);
59
+ };
60
+
61
+ const handleClear = () => {
62
+ setPickedDate(null);
63
+ onChange('');
64
+ };
65
+
66
+ const displayValue = pickedDate?.isValid() ? pickedDate.format(OUTPUT_LABEL_MASK) : '';
67
+
68
+ const hasError = !!error;
69
+ const hasValue = !!pickedDate?.isValid();
70
+ const displayPlaceholder = placeholder || DEFAULT_PLACEHOLDER;
71
+ const errorMessage = error;
72
+ const isTransparent = variant === VARIANTS.picker;
73
+
74
+ const showClearButton = clearable && hasValue && !disabled && !readOnly;
75
+ const showCalendarIcon = !readOnly && !disabled;
76
+
77
+ const calendarAction = {
78
+ colorTheme: hasError ? 'error' : isOpen ? 'primary' : 'gray',
79
+ Icon: CustomIcon || 'IconCalendarEvent',
80
+ onPress: openCalendar,
81
+ size: 'small',
82
+ variant: 'text'
83
+ };
84
+
85
+ const clearAction = {
86
+ colorTheme: 'secondary',
87
+ Icon: 'IconX',
88
+ onPress: handleClear,
89
+ size: 'small',
90
+ variant: 'text'
91
+ };
92
+
93
+ const rightAdornments = showCalendarIcon
94
+ ? [
95
+ {
96
+ name: COMPONENT_KEYS.ACTION,
97
+ props: { action: showClearButton ? clearAction : calendarAction }
98
+ }
99
+ ]
100
+ : [];
101
+
102
+ const inputStyle = isTransparent
103
+ ? { container: { backgroundColor: 'transparent', borderColor: 'transparent' } }
104
+ : { container: { alignItems: 'stretch', paddingHorizontal: 12 } };
105
+
106
+ return (
107
+ <UTDatePickerLayout
108
+ disabled={disabled}
109
+ displayPlaceholder={displayPlaceholder}
110
+ displayValue={displayValue}
111
+ errorMessage={errorMessage}
112
+ handleClose={handleClose}
113
+ handleDaySelect={handleDaySelect}
114
+ hasError={hasError}
115
+ helpText={helpText}
116
+ inputStyle={inputStyle}
117
+ isOpen={isOpen}
118
+ maxDate={maxDate}
119
+ minDate={minDate}
120
+ modalProps={modalProps}
121
+ onPress={openCalendar}
122
+ pickedDate={pickedDate}
123
+ readOnly={readOnly}
124
+ required={required}
125
+ rightAdornments={rightAdornments}
126
+ style={style}
127
+ title={title}
128
+ variant={variant}
129
+ />
130
+ );
131
+ };
132
+
133
+ UTDatePicker.propTypes = propTypes;
134
+
135
+ export default UTDatePicker;