@space-uy/pulsar-ui 0.2.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.
Files changed (155) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +148 -0
  3. package/lib/module/components/Accordion.js +242 -0
  4. package/lib/module/components/Accordion.js.map +1 -0
  5. package/lib/module/components/BottomSheet.js +183 -0
  6. package/lib/module/components/BottomSheet.js.map +1 -0
  7. package/lib/module/components/Button.js +64 -0
  8. package/lib/module/components/Button.js.map +1 -0
  9. package/lib/module/components/ButtonContainer.js +118 -0
  10. package/lib/module/components/ButtonContainer.js.map +1 -0
  11. package/lib/module/components/CalendarPicker.js +374 -0
  12. package/lib/module/components/CalendarPicker.js.map +1 -0
  13. package/lib/module/components/Card.js +43 -0
  14. package/lib/module/components/Card.js.map +1 -0
  15. package/lib/module/components/Checkbox.js +122 -0
  16. package/lib/module/components/Checkbox.js.map +1 -0
  17. package/lib/module/components/Chip.js +50 -0
  18. package/lib/module/components/Chip.js.map +1 -0
  19. package/lib/module/components/CopyToClipboard.js +98 -0
  20. package/lib/module/components/CopyToClipboard.js.map +1 -0
  21. package/lib/module/components/Dialog.js +232 -0
  22. package/lib/module/components/Dialog.js.map +1 -0
  23. package/lib/module/components/Header.js +94 -0
  24. package/lib/module/components/Header.js.map +1 -0
  25. package/lib/module/components/Icon.js +22 -0
  26. package/lib/module/components/Icon.js.map +1 -0
  27. package/lib/module/components/IconButton.js +57 -0
  28. package/lib/module/components/IconButton.js.map +1 -0
  29. package/lib/module/components/Input.js +111 -0
  30. package/lib/module/components/Input.js.map +1 -0
  31. package/lib/module/components/InputContainer.js +104 -0
  32. package/lib/module/components/InputContainer.js.map +1 -0
  33. package/lib/module/components/LoadingIndicator.js +62 -0
  34. package/lib/module/components/LoadingIndicator.js.map +1 -0
  35. package/lib/module/components/OtpInput.js +85 -0
  36. package/lib/module/components/OtpInput.js.map +1 -0
  37. package/lib/module/components/OtpInputContainer.js +148 -0
  38. package/lib/module/components/OtpInputContainer.js.map +1 -0
  39. package/lib/module/components/Select.js +189 -0
  40. package/lib/module/components/Select.js.map +1 -0
  41. package/lib/module/components/Switch.js +74 -0
  42. package/lib/module/components/Switch.js.map +1 -0
  43. package/lib/module/components/Tabs.js +99 -0
  44. package/lib/module/components/Tabs.js.map +1 -0
  45. package/lib/module/components/Text.js +66 -0
  46. package/lib/module/components/Text.js.map +1 -0
  47. package/lib/module/components/TextArea.js +106 -0
  48. package/lib/module/components/TextArea.js.map +1 -0
  49. package/lib/module/hooks/useTheme.js +20 -0
  50. package/lib/module/hooks/useTheme.js.map +1 -0
  51. package/lib/module/index.js +27 -0
  52. package/lib/module/index.js.map +1 -0
  53. package/lib/module/package.json +1 -0
  54. package/lib/module/store/themeStore.js +50 -0
  55. package/lib/module/store/themeStore.js.map +1 -0
  56. package/lib/module/theme/colors.js +25 -0
  57. package/lib/module/theme/colors.js.map +1 -0
  58. package/lib/module/theme/meassures.js +10 -0
  59. package/lib/module/theme/meassures.js.map +1 -0
  60. package/lib/module/utils/stringUtils.js +12 -0
  61. package/lib/module/utils/stringUtils.js.map +1 -0
  62. package/lib/module/utils/uiUtils.js +63 -0
  63. package/lib/module/utils/uiUtils.js.map +1 -0
  64. package/lib/typescript/package.json +1 -0
  65. package/lib/typescript/src/components/Accordion.d.ts +22 -0
  66. package/lib/typescript/src/components/Accordion.d.ts.map +1 -0
  67. package/lib/typescript/src/components/BottomSheet.d.ts +13 -0
  68. package/lib/typescript/src/components/BottomSheet.d.ts.map +1 -0
  69. package/lib/typescript/src/components/Button.d.ts +16 -0
  70. package/lib/typescript/src/components/Button.d.ts.map +1 -0
  71. package/lib/typescript/src/components/ButtonContainer.d.ts +30 -0
  72. package/lib/typescript/src/components/ButtonContainer.d.ts.map +1 -0
  73. package/lib/typescript/src/components/CalendarPicker.d.ts +19 -0
  74. package/lib/typescript/src/components/CalendarPicker.d.ts.map +1 -0
  75. package/lib/typescript/src/components/Card.d.ts +7 -0
  76. package/lib/typescript/src/components/Card.d.ts.map +1 -0
  77. package/lib/typescript/src/components/Checkbox.d.ts +11 -0
  78. package/lib/typescript/src/components/Checkbox.d.ts.map +1 -0
  79. package/lib/typescript/src/components/Chip.d.ts +9 -0
  80. package/lib/typescript/src/components/Chip.d.ts.map +1 -0
  81. package/lib/typescript/src/components/CopyToClipboard.d.ts +12 -0
  82. package/lib/typescript/src/components/CopyToClipboard.d.ts.map +1 -0
  83. package/lib/typescript/src/components/Dialog.d.ts +40 -0
  84. package/lib/typescript/src/components/Dialog.d.ts.map +1 -0
  85. package/lib/typescript/src/components/Header.d.ts +18 -0
  86. package/lib/typescript/src/components/Header.d.ts.map +1 -0
  87. package/lib/typescript/src/components/Icon.d.ts +12 -0
  88. package/lib/typescript/src/components/Icon.d.ts.map +1 -0
  89. package/lib/typescript/src/components/IconButton.d.ts +13 -0
  90. package/lib/typescript/src/components/IconButton.d.ts.map +1 -0
  91. package/lib/typescript/src/components/Input.d.ts +17 -0
  92. package/lib/typescript/src/components/Input.d.ts.map +1 -0
  93. package/lib/typescript/src/components/InputContainer.d.ts +22 -0
  94. package/lib/typescript/src/components/InputContainer.d.ts.map +1 -0
  95. package/lib/typescript/src/components/LoadingIndicator.d.ts +9 -0
  96. package/lib/typescript/src/components/LoadingIndicator.d.ts.map +1 -0
  97. package/lib/typescript/src/components/OtpInput.d.ts +3 -0
  98. package/lib/typescript/src/components/OtpInput.d.ts.map +1 -0
  99. package/lib/typescript/src/components/OtpInputContainer.d.ts +17 -0
  100. package/lib/typescript/src/components/OtpInputContainer.d.ts.map +1 -0
  101. package/lib/typescript/src/components/Select.d.ts +20 -0
  102. package/lib/typescript/src/components/Select.d.ts.map +1 -0
  103. package/lib/typescript/src/components/Switch.d.ts +10 -0
  104. package/lib/typescript/src/components/Switch.d.ts.map +1 -0
  105. package/lib/typescript/src/components/Tabs.d.ts +14 -0
  106. package/lib/typescript/src/components/Tabs.d.ts.map +1 -0
  107. package/lib/typescript/src/components/Text.d.ts +7 -0
  108. package/lib/typescript/src/components/Text.d.ts.map +1 -0
  109. package/lib/typescript/src/components/TextArea.d.ts +16 -0
  110. package/lib/typescript/src/components/TextArea.d.ts.map +1 -0
  111. package/lib/typescript/src/hooks/useTheme.d.ts +9 -0
  112. package/lib/typescript/src/hooks/useTheme.d.ts.map +1 -0
  113. package/lib/typescript/src/index.d.ts +27 -0
  114. package/lib/typescript/src/index.d.ts.map +1 -0
  115. package/lib/typescript/src/store/themeStore.d.ts +32 -0
  116. package/lib/typescript/src/store/themeStore.d.ts.map +1 -0
  117. package/lib/typescript/src/theme/colors.d.ts +14 -0
  118. package/lib/typescript/src/theme/colors.d.ts.map +1 -0
  119. package/lib/typescript/src/theme/meassures.d.ts +9 -0
  120. package/lib/typescript/src/theme/meassures.d.ts.map +1 -0
  121. package/lib/typescript/src/utils/stringUtils.d.ts +7 -0
  122. package/lib/typescript/src/utils/stringUtils.d.ts.map +1 -0
  123. package/lib/typescript/src/utils/uiUtils.d.ts +21 -0
  124. package/lib/typescript/src/utils/uiUtils.d.ts.map +1 -0
  125. package/package.json +173 -0
  126. package/src/components/Accordion.tsx +284 -0
  127. package/src/components/BottomSheet.tsx +259 -0
  128. package/src/components/Button.tsx +85 -0
  129. package/src/components/ButtonContainer.tsx +161 -0
  130. package/src/components/CalendarPicker.tsx +428 -0
  131. package/src/components/Card.tsx +55 -0
  132. package/src/components/Checkbox.tsx +160 -0
  133. package/src/components/Chip.tsx +58 -0
  134. package/src/components/CopyToClipboard.tsx +108 -0
  135. package/src/components/Dialog.tsx +263 -0
  136. package/src/components/Header.tsx +100 -0
  137. package/src/components/Icon.tsx +27 -0
  138. package/src/components/IconButton.tsx +71 -0
  139. package/src/components/Input.tsx +144 -0
  140. package/src/components/InputContainer.tsx +134 -0
  141. package/src/components/LoadingIndicator.tsx +78 -0
  142. package/src/components/OtpInput.tsx +109 -0
  143. package/src/components/OtpInputContainer.tsx +196 -0
  144. package/src/components/Select.tsx +219 -0
  145. package/src/components/Switch.tsx +104 -0
  146. package/src/components/Tabs.tsx +117 -0
  147. package/src/components/Text.tsx +64 -0
  148. package/src/components/TextArea.tsx +141 -0
  149. package/src/hooks/useTheme.tsx +23 -0
  150. package/src/index.tsx +38 -0
  151. package/src/store/themeStore.ts +57 -0
  152. package/src/theme/colors.ts +35 -0
  153. package/src/theme/meassures.ts +7 -0
  154. package/src/utils/stringUtils.ts +16 -0
  155. package/src/utils/uiUtils.ts +70 -0
@@ -0,0 +1,161 @@
1
+ import React, { useEffect, useMemo } from 'react';
2
+ import {
3
+ Platform,
4
+ Pressable,
5
+ StyleSheet,
6
+ type GestureResponderEvent,
7
+ type PressableProps,
8
+ type StyleProp,
9
+ type ViewStyle,
10
+ } from 'react-native';
11
+ import Animated, {
12
+ interpolate,
13
+ interpolateColor,
14
+ useAnimatedStyle,
15
+ useSharedValue,
16
+ withTiming,
17
+ type AnimatedStyle,
18
+ } from 'react-native-reanimated';
19
+
20
+ import useTheme from '../hooks/useTheme';
21
+
22
+ import { convertHexToRgba } from '../utils/uiUtils';
23
+ import meassures from '../theme/meassures';
24
+
25
+ export enum ButtonVariant {
26
+ flat = 'flat',
27
+ outline = 'outline',
28
+ transparent = 'transparent',
29
+ destructive = 'destructive',
30
+ }
31
+
32
+ export enum ButtonSize {
33
+ small = 'small',
34
+ medium = 'medium',
35
+ large = 'large',
36
+ }
37
+
38
+ export type ButtonColors = {
39
+ backgroundColor: string;
40
+ borderColor?: string;
41
+ textColor: string;
42
+ pressedBackgroundColor: string;
43
+ };
44
+
45
+ type Props = Omit<PressableProps, 'children'> & {
46
+ variant?: keyof typeof ButtonVariant;
47
+ size?: keyof typeof ButtonSize;
48
+ loading?: boolean;
49
+ renderContent: (colors: ButtonColors) => React.ReactNode;
50
+ contentContainerStyle?: StyleProp<AnimatedStyle<StyleProp<ViewStyle>>>;
51
+ };
52
+
53
+ export default function ButtonContainer({
54
+ disabled,
55
+ variant = 'flat',
56
+ size = 'large',
57
+ loading = false,
58
+ renderContent,
59
+ onPressIn,
60
+ onPressOut,
61
+ contentContainerStyle,
62
+ ...rest
63
+ }: Props) {
64
+ const { theme, colors } = useTheme();
65
+ const pressed = useSharedValue(0);
66
+ const enabled = useSharedValue(disabled ? 0 : 1);
67
+
68
+ const height = useMemo(() => meassures.button[size], [size]);
69
+
70
+ const buttonColors: ButtonColors = useMemo(() => {
71
+ return {
72
+ outline: {
73
+ backgroundColor: 'transparent',
74
+ borderColor: colors.border,
75
+ textColor: colors.foreground,
76
+ pressedBackgroundColor: convertHexToRgba(colors.foreground, 0.1),
77
+ },
78
+ transparent: {
79
+ backgroundColor: 'transparent',
80
+ textColor: colors.foreground,
81
+ pressedBackgroundColor: convertHexToRgba(colors.foreground, 0.1),
82
+ },
83
+ destructive: {
84
+ backgroundColor: colors.destructive,
85
+ textColor: colors.foregroundOnDestructive,
86
+ pressedBackgroundColor: convertHexToRgba(colors.destructive, 0.8),
87
+ },
88
+ flat: {
89
+ backgroundColor: colors.primary,
90
+ textColor: colors.altForeground,
91
+ pressedBackgroundColor: convertHexToRgba(colors.foreground, 0.8),
92
+ },
93
+ }[variant];
94
+ }, [variant, colors]);
95
+
96
+ const animatedStyle = useAnimatedStyle(() => {
97
+ return {
98
+ backgroundColor: interpolateColor(
99
+ pressed.value,
100
+ [0, 1],
101
+ [buttonColors.backgroundColor, buttonColors.pressedBackgroundColor]
102
+ ),
103
+ opacity: interpolate(enabled.value, [0, 1], [0.3, 1]),
104
+ };
105
+ });
106
+
107
+ const borderWidth = useMemo(() => (variant === 'outline' ? 1 : 0), [variant]);
108
+
109
+ const updatePressAnimation = (value: number) => {
110
+ pressed.value = withTiming(value, { duration: 300 });
111
+ };
112
+
113
+ useEffect(() => {
114
+ enabled.value = withTiming(disabled ? 0 : 1, { duration: 300 });
115
+ }, [disabled, enabled]);
116
+
117
+ const handlePressIn = (e: GestureResponderEvent) => {
118
+ if (!loading && !disabled) {
119
+ onPressIn?.(e);
120
+ Platform.OS !== 'web' && updatePressAnimation(1);
121
+ }
122
+ };
123
+
124
+ const handlePressOut = (e: GestureResponderEvent) => {
125
+ if (!loading && !disabled) {
126
+ onPressOut?.(e);
127
+ Platform.OS !== 'web' && updatePressAnimation(0);
128
+ }
129
+ };
130
+
131
+ return (
132
+ <Pressable
133
+ {...rest}
134
+ onPressIn={handlePressIn}
135
+ onPressOut={handlePressOut}
136
+ onHoverIn={() => updatePressAnimation(1)}
137
+ onHoverOut={() => updatePressAnimation(0)}
138
+ disabled={disabled || loading}
139
+ >
140
+ <Animated.View
141
+ style={[
142
+ styles.contentContainer,
143
+ {
144
+ height,
145
+ borderRadius: theme.roundness,
146
+ borderWidth,
147
+ borderColor: buttonColors.borderColor,
148
+ },
149
+ animatedStyle,
150
+ contentContainerStyle,
151
+ ]}
152
+ >
153
+ {renderContent(buttonColors)}
154
+ </Animated.View>
155
+ </Pressable>
156
+ );
157
+ }
158
+
159
+ const styles = StyleSheet.create({
160
+ contentContainer: { justifyContent: 'center' },
161
+ });
@@ -0,0 +1,428 @@
1
+ import { useMemo, useRef, useState, useCallback } from 'react';
2
+ import { add, format, isBefore, addMonths, subMonths } from 'date-fns';
3
+ import {
4
+ useWindowDimensions,
5
+ View,
6
+ StyleSheet,
7
+ type StyleProp,
8
+ type ViewStyle,
9
+ } from 'react-native';
10
+ import { Calendar } from 'react-native-calendars';
11
+ import type { DateData, MarkedDates } from 'react-native-calendars/src/types';
12
+ import Animated, {
13
+ useSharedValue,
14
+ withTiming,
15
+ useAnimatedStyle,
16
+ } from 'react-native-reanimated';
17
+
18
+ import InputContainer from './InputContainer';
19
+ import BottomSheet, { type BottomSheetProps } from './BottomSheet';
20
+ import Text from './Text';
21
+ import Icon from './Icon';
22
+ import Button from './Button';
23
+ import IconButton from './IconButton';
24
+
25
+ import useTheme from '../hooks/useTheme';
26
+
27
+ import { convertDateToISOString } from '../utils/stringUtils';
28
+ import { convertHexToRgba } from '../utils/uiUtils';
29
+
30
+ type DateRange = { fromDate?: string; toDate?: string };
31
+
32
+ type Props = {
33
+ style?: StyleProp<ViewStyle>;
34
+ value?: DateRange;
35
+ onChange: (dateRange: DateRange) => void;
36
+ placeholder?: string;
37
+ label?: string;
38
+ disabled?: boolean;
39
+ error?: boolean;
40
+ hint?: string;
41
+ title?: string;
42
+ };
43
+
44
+ // Helper function to safely parse ISO date string to Date object
45
+ const parseISODateString = (isoString: string): Date | null => {
46
+ try {
47
+ const datePart = isoString.split('T')[0];
48
+ if (!datePart) return null;
49
+
50
+ const dateArray = datePart.split('-').map(Number);
51
+ if (dateArray.length !== 3) return null;
52
+
53
+ const [year, month, day] = dateArray;
54
+ if (!year || !month || !day) return null;
55
+
56
+ return new Date(year, month - 1, day);
57
+ } catch {
58
+ return null;
59
+ }
60
+ };
61
+
62
+ const CalendarPicker = ({
63
+ style,
64
+ value,
65
+ onChange,
66
+ placeholder = 'Seleccionar fechas',
67
+ label,
68
+ disabled = false,
69
+ error,
70
+ hint,
71
+ }: Props) => {
72
+ const [fromDate, setFromDate] = useState<string | undefined>(
73
+ value?.fromDate ? format(new Date(value.fromDate), 'yyyy-MM-dd') : undefined
74
+ );
75
+ const [toDate, setToDate] = useState<string | undefined>(
76
+ value?.toDate ? format(new Date(value.toDate), 'yyyy-MM-dd') : undefined
77
+ );
78
+ const [isOpen, setIsOpen] = useState(false);
79
+ const [currentMonth, setCurrentMonth] = useState(new Date());
80
+ const rotation = useSharedValue(0);
81
+ const { width } = useWindowDimensions();
82
+ const { theme, colors } = useTheme();
83
+ const bottomSheetRef = useRef<BottomSheetProps>(null);
84
+
85
+ const getMiddleDays = (
86
+ startDate: string | undefined,
87
+ endDate: string | undefined
88
+ ) => {
89
+ if (!startDate || !endDate) return {};
90
+ const markedDates: MarkedDates = {};
91
+ const sDate = new Date(startDate);
92
+ const eDate = new Date(endDate);
93
+ let currentDate = add(sDate, { days: 1 });
94
+ while (isBefore(currentDate, eDate)) {
95
+ markedDates[format(add(currentDate, { days: 1 }), 'yyyy-MM-dd')] = {
96
+ color: convertHexToRgba(colors.border, 0.5),
97
+ textColor: colors.foreground,
98
+ };
99
+ currentDate = add(currentDate, { days: 1 });
100
+ }
101
+ return markedDates;
102
+ };
103
+
104
+ const handlePress = useCallback(() => {
105
+ if (disabled) return;
106
+ setFromDate(
107
+ value?.fromDate
108
+ ? format(new Date(value.fromDate), 'yyyy-MM-dd')
109
+ : undefined
110
+ );
111
+ setToDate(
112
+ value?.toDate ? format(new Date(value.toDate), 'yyyy-MM-dd') : undefined
113
+ );
114
+ setIsOpen(true);
115
+ rotation.value = withTiming(1, { duration: 200 });
116
+ bottomSheetRef.current?.show();
117
+ }, [disabled, rotation, value?.fromDate, value?.toDate]);
118
+
119
+ const handleClose = useCallback(() => {
120
+ rotation.value = withTiming(0, { duration: 200 });
121
+ setIsOpen(false);
122
+ setFromDate(
123
+ value?.fromDate
124
+ ? format(new Date(value.fromDate), 'yyyy-MM-dd')
125
+ : undefined
126
+ );
127
+ setToDate(
128
+ value?.toDate ? format(new Date(value.toDate), 'yyyy-MM-dd') : undefined
129
+ );
130
+ bottomSheetRef.current?.hide();
131
+ }, [rotation, value?.fromDate, value?.toDate]);
132
+
133
+ const handleDatePress = (selectedDate: DateData) => {
134
+ if (!fromDate || (!!fromDate && !!toDate)) {
135
+ setFromDate(selectedDate.dateString);
136
+ !!toDate && setToDate(undefined);
137
+ } else {
138
+ if (isBefore(new Date(selectedDate.dateString), new Date(fromDate))) {
139
+ setToDate(fromDate);
140
+ setFromDate(selectedDate.dateString);
141
+ } else {
142
+ setToDate(selectedDate.dateString);
143
+ }
144
+ }
145
+ };
146
+
147
+ const handleConfirm = () => {
148
+ onChange({
149
+ fromDate: fromDate ? convertDateToISOString(fromDate) : undefined,
150
+ toDate: toDate ? convertDateToISOString(toDate) : undefined,
151
+ });
152
+ handleClose();
153
+ };
154
+
155
+ const handleClear = () => {
156
+ setFromDate(undefined);
157
+ setToDate(undefined);
158
+ };
159
+
160
+ const handlePrevMonth = () => {
161
+ setCurrentMonth((prev) => subMonths(prev, 1));
162
+ };
163
+
164
+ const handleNextMonth = () => {
165
+ setCurrentMonth((prev) => addMonths(prev, 1));
166
+ };
167
+
168
+ const handleMonthChange = (month: DateData) => {
169
+ setCurrentMonth(new Date(month.dateString));
170
+ };
171
+
172
+ const formatMonthYear = (date: Date) => {
173
+ return format(date, 'MMMM yyyy');
174
+ };
175
+
176
+ const animatedStyle = useAnimatedStyle(() => ({
177
+ transform: [{ rotate: `${rotation.value * 180}deg` }],
178
+ }));
179
+
180
+ const getInputTextColor = useCallback(() => {
181
+ if (disabled) return convertHexToRgba(colors.foreground, 0.5);
182
+ return colors.foreground;
183
+ }, [colors.foreground, disabled]);
184
+
185
+ const getDisplayValue = () => {
186
+ if (!value?.fromDate && !value?.toDate) return null;
187
+
188
+ if (value?.fromDate && !value?.toDate) {
189
+ const date = parseISODateString(value.fromDate);
190
+ return date ? format(date, 'dd/MM/yyyy') : null;
191
+ }
192
+
193
+ if (value?.fromDate && value?.toDate) {
194
+ const _fromDate = parseISODateString(value.fromDate);
195
+ const _toDate = parseISODateString(value.toDate);
196
+
197
+ if (!_fromDate || !_toDate) return null;
198
+
199
+ return `${format(_fromDate, 'dd/MM/yyyy')} - ${format(_toDate, 'dd/MM/yyyy')}`;
200
+ }
201
+
202
+ return null;
203
+ };
204
+
205
+ const displayValue = getDisplayValue();
206
+ const isButtonDisabled = useMemo(() => {
207
+ if (!fromDate || !toDate) return true;
208
+
209
+ const normalizedFromDate = fromDate
210
+ ? convertDateToISOString(fromDate)
211
+ : undefined;
212
+ const normalizedToDate = toDate
213
+ ? convertDateToISOString(toDate)
214
+ : undefined;
215
+
216
+ return (
217
+ normalizedFromDate === value?.fromDate &&
218
+ normalizedToDate === value?.toDate
219
+ );
220
+ }, [fromDate, toDate, value?.fromDate, value?.toDate]);
221
+
222
+ const isSingleDateSelection = fromDate && toDate && fromDate === toDate;
223
+
224
+ const getMarkedDates = () => {
225
+ if (isSingleDateSelection) {
226
+ return {
227
+ [fromDate!]: {
228
+ startingDay: true,
229
+ endingDay: true,
230
+ color: colors.primary,
231
+ textColor: colors.background,
232
+ customContainerStyle: { borderRadius: theme.roundness },
233
+ },
234
+ };
235
+ } else {
236
+ return {
237
+ [fromDate ?? '']: {
238
+ startingDay: true,
239
+ color: colors.primary,
240
+ textColor: colors.background,
241
+ customContainerStyle: { borderRadius: theme.roundness },
242
+ },
243
+ [toDate ?? '']: {
244
+ endingDay: true,
245
+ color: colors.primary,
246
+ textColor: colors.background,
247
+ customContainerStyle: { borderRadius: theme.roundness },
248
+ },
249
+ ...getMiddleDays(fromDate, toDate),
250
+ };
251
+ }
252
+ };
253
+
254
+ return (
255
+ <>
256
+ <View style={style}>
257
+ <InputContainer
258
+ onPress={handlePress}
259
+ disabled={disabled}
260
+ focused={isOpen}
261
+ error={error}
262
+ hint={hint}
263
+ label={label}
264
+ >
265
+ <Icon
266
+ style={styles.icon}
267
+ name="Calendar"
268
+ size={18}
269
+ color={convertHexToRgba(colors.foreground, 0.5)}
270
+ />
271
+ <Text
272
+ variant="pm"
273
+ style={[
274
+ styles.selectText,
275
+ {
276
+ color: displayValue
277
+ ? getInputTextColor()
278
+ : convertHexToRgba(colors.foreground, 0.5),
279
+ fontFamily: theme.fonts.regular,
280
+ },
281
+ ]}
282
+ numberOfLines={1}
283
+ >
284
+ {displayValue ?? placeholder}
285
+ </Text>
286
+ <Animated.View style={animatedStyle}>
287
+ <Icon name="ChevronDown" size={16} color={colors.foreground} />
288
+ </Animated.View>
289
+ </InputContainer>
290
+ </View>
291
+ <BottomSheet ref={bottomSheetRef} onBackdropPress={handleClose}>
292
+ <View style={styles.calendarContainer}>
293
+ <View style={styles.customHeader}>
294
+ <Text
295
+ variant="h4"
296
+ style={[styles.monthTitle, { color: colors.foreground }]}
297
+ >
298
+ {formatMonthYear(currentMonth)}
299
+ </Text>
300
+ <View style={styles.navigationButtons}>
301
+ <IconButton
302
+ onPress={handlePrevMonth}
303
+ iconName="ChevronLeft"
304
+ // size="small"
305
+ variant="outline"
306
+ />
307
+ <IconButton
308
+ onPress={handleNextMonth}
309
+ iconName="ChevronRight"
310
+ // size="small"
311
+ variant="outline"
312
+ />
313
+ </View>
314
+ </View>
315
+
316
+ <View
317
+ style={[
318
+ styles.calendarContent,
319
+ {
320
+ backgroundColor: convertHexToRgba(colors.border, 0.3),
321
+ borderRadius: theme.roundness,
322
+ },
323
+ ]}
324
+ >
325
+ <Calendar
326
+ key={format(currentMonth, 'yyyy-MM')}
327
+ current={format(currentMonth, 'yyyy-MM-dd')}
328
+ onDayPress={handleDatePress}
329
+ onMonthChange={handleMonthChange}
330
+ disableAllTouchEventsForDisabledDays={true}
331
+ markedDates={getMarkedDates()}
332
+ showSixWeeks={true}
333
+ hideExtraDays={false}
334
+ markingType="period"
335
+ hideArrows={true}
336
+ hideHeader={true}
337
+ hideDayNames={false}
338
+ disableArrowLeft={true}
339
+ disableArrowRight={true}
340
+ renderHeader={() => null}
341
+ theme={{
342
+ 'calendarBackground': 'transparent',
343
+ 'backgroundColor': 'transparent',
344
+ 'textDayFontFamily': theme.fonts.regular,
345
+ 'textDayFontSize': 12,
346
+ 'dayTextColor': colors.foreground,
347
+ 'todayTextColor': colors.primary,
348
+ 'textMonthFontFamily': theme.fonts.medium,
349
+ 'monthTextColor': colors.foreground,
350
+ 'textMonthFontSize': 14,
351
+ 'textDayHeaderFontFamily': theme.fonts.medium,
352
+ 'textDayHeaderFontSize': 12,
353
+ 'textSectionTitleColor': convertHexToRgba(
354
+ colors.foreground,
355
+ 0.7
356
+ ),
357
+ 'textDisabledColor': convertHexToRgba(colors.foreground, 0.3),
358
+ 'arrowColor': colors.foreground,
359
+ 'stylesheet.calendar.header': {
360
+ header: {
361
+ height: 0,
362
+ opacity: 0,
363
+ display: 'none',
364
+ },
365
+ },
366
+ }}
367
+ calendarWidth={width - 32}
368
+ />
369
+ </View>
370
+ </View>
371
+ <View
372
+ style={[
373
+ styles.buttonContainer,
374
+ { paddingBottom: theme.insets.bottom + 16 },
375
+ ]}
376
+ >
377
+ <View style={styles.buttonRow}>
378
+ {toDate && fromDate && (
379
+ <Button
380
+ text="Limpiar"
381
+ variant="outline"
382
+ onPress={handleClear}
383
+ style={styles.secondaryButton}
384
+ />
385
+ )}
386
+ <Button
387
+ text="Confirmar"
388
+ onPress={handleConfirm}
389
+ disabled={isButtonDisabled}
390
+ style={styles.primaryButton}
391
+ />
392
+ </View>
393
+ </View>
394
+ </BottomSheet>
395
+ </>
396
+ );
397
+ };
398
+
399
+ const styles = StyleSheet.create({
400
+ icon: { marginEnd: 8 },
401
+ selectText: { flex: 1 },
402
+ calendarContainer: {
403
+ backgroundColor: 'transparent',
404
+ marginHorizontal: 16,
405
+ borderRadius: 8,
406
+ overflow: 'hidden',
407
+ marginTop: 16,
408
+ },
409
+ customHeader: {
410
+ flexDirection: 'row',
411
+ justifyContent: 'space-between',
412
+ alignItems: 'center',
413
+ marginBottom: 16,
414
+ },
415
+ monthTitle: { textTransform: 'capitalize' },
416
+ navigationButtons: {
417
+ flexDirection: 'row',
418
+ alignItems: 'center',
419
+ gap: 8,
420
+ },
421
+ buttonContainer: { marginHorizontal: 16, marginTop: 16 },
422
+ buttonRow: { flexDirection: 'row', gap: 12 },
423
+ secondaryButton: { flex: 1 },
424
+ primaryButton: { flex: 2 },
425
+ calendarContent: { padding: 8, overflow: 'hidden' },
426
+ });
427
+
428
+ export default CalendarPicker;
@@ -0,0 +1,55 @@
1
+ import { useMemo, type PropsWithChildren } from 'react';
2
+ import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native';
3
+
4
+ import useTheme from '../hooks/useTheme';
5
+ import { convertHexToRgba } from '../utils/uiUtils';
6
+
7
+ export default function Card({
8
+ children,
9
+ style,
10
+ variant = 'default',
11
+ }: PropsWithChildren<{
12
+ style?: StyleProp<ViewStyle>;
13
+ variant?: 'default' | 'alternative' | 'tinted';
14
+ }>) {
15
+ const { theme, colors } = useTheme();
16
+
17
+ const themeStyle = useMemo(() => {
18
+ return {
19
+ backgroundColor:
20
+ variant === 'default'
21
+ ? colors.background
22
+ : convertHexToRgba(colors.border, 0.5),
23
+ borderRadius: theme.roundness,
24
+ borderColor: variant === 'default' ? colors.border : 'transparent',
25
+ boxShadow:
26
+ variant === 'default' ? '0px 0px 1px 1px rgba(0, 0, 0, 0.05)' : 'none',
27
+ };
28
+ }, [colors, theme, variant]);
29
+
30
+ return (
31
+ <View
32
+ style={[
33
+ styles.card,
34
+ themeStyle,
35
+ variant === 'default' && styles.withBorder,
36
+ style,
37
+ ]}
38
+ >
39
+ {variant === 'tinted' && (
40
+ <View
41
+ style={[
42
+ StyleSheet.absoluteFill,
43
+ { backgroundColor: convertHexToRgba(colors.primary, 0.1) },
44
+ ]}
45
+ />
46
+ )}
47
+ {children}
48
+ </View>
49
+ );
50
+ }
51
+
52
+ const styles = StyleSheet.create({
53
+ card: { padding: 16, overflow: 'hidden' },
54
+ withBorder: { borderWidth: 1 },
55
+ });