cdslibrary 1.2.78 → 1.2.79

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.
@@ -136,7 +136,7 @@ const styles = StyleSheet.create({
136
136
  position: 'absolute',
137
137
  left: 16,
138
138
  right: 16,
139
- opacity: 0, // No se ve, pero ocupa espacio para onLayout
139
+ opacity: 0,
140
140
  },
141
141
  paddingWrapper: {
142
142
  paddingBottom: 16,
@@ -32,19 +32,17 @@ const bottomSheetRender = ({
32
32
  const { theme } = useTheme();
33
33
  const isMobile = theme.isMobile;
34
34
 
35
- // --- 1. Estado de Animación (Reanimated SharedValues) ---
36
- const startPos = isMobile ? height : width; // Abajo en mobile, Derecha en desktop
35
+ const startPos = isMobile ? height : width;
37
36
  const translation = useSharedValue(startPos);
38
37
  const opacity = useSharedValue(0);
39
38
 
40
- // --- 2. Funciones de Cierre (Wrapper para JS Thread) ---
41
39
  const triggerOnFinish = useCallback(() => {
42
40
  if (onFinish) onFinish();
43
41
  if (primaryButtonOnPress) primaryButtonOnPress();
44
42
  }, [onFinish, primaryButtonOnPress]);
45
43
 
46
44
  const handleClose = useCallback(() => {
47
- "worklet"; // Marca para ejecutar en UI thread
45
+ "worklet";
48
46
  translation.value = withTiming(startPos, { duration: 300 }, (finished) => {
49
47
  if (finished) {
50
48
  runOnJS(triggerOnFinish)();
@@ -53,18 +51,15 @@ const bottomSheetRender = ({
53
51
  opacity.value = withTiming(0, { duration: 300 });
54
52
  }, [startPos, triggerOnFinish]);
55
53
 
56
- // --- 3. Lógica del Gesto (Solo Mobile) ---
57
54
  const pan = Gesture.Pan()
58
- .enabled(isMobile) // Desactivamos gesto en Desktop
55
+ .enabled(isMobile)
59
56
  .onUpdate((event) => {
60
- // Solo permitir arrastrar hacia abajo (valores positivos)
61
57
  if (event.translationY > 0) {
62
58
  translation.value = event.translationY;
63
59
  }
64
60
  })
65
61
  .onEnd((event) => {
66
- // Si arrastra más de 100px o hace un "flick" rápido -> CERRAR
67
- if (event.translationY > 200 || event.velocityY > 2000) {
62
+ if (event.translationY > 150 || event.velocityY > 2000) {
68
63
  handleClose();
69
64
  } else {
70
65
  translation.value = withTiming(0, {
@@ -74,10 +69,10 @@ const bottomSheetRender = ({
74
69
  }
75
70
  });
76
71
 
77
- // --- 4. Efectos de Entrada/Salida ---
72
+ const nativeGesture = Gesture.Native();
73
+
78
74
  useEffect(() => {
79
75
  if (isVisible) {
80
- // Usamos una curva Bezier suave (out-expo) que se frena al final
81
76
  translation.value = withTiming(0, {
82
77
  duration: 300,
83
78
  easing: Easing.out(Easing.exp),
@@ -86,7 +81,6 @@ const bottomSheetRender = ({
86
81
  }
87
82
  }, [isVisible]);
88
83
 
89
- // --- 5. Estilos Animados ---
90
84
  const animatedStyle = useAnimatedStyle(() => {
91
85
  return {
92
86
  opacity: opacity.value,
@@ -99,7 +93,6 @@ const bottomSheetRender = ({
99
93
  });
100
94
 
101
95
  const backdropStyle = useAnimatedStyle(() => {
102
- // Interpolamos para que el fondo se aclare mientras arrastras
103
96
  const currentY = translation.value;
104
97
  const opacityVal = interpolate(currentY, [0, height], [1, 0], Extrapolation.CLAMP);
105
98
  return {
@@ -113,62 +106,60 @@ const bottomSheetRender = ({
113
106
  style={[
114
107
  styles.overlay,
115
108
  { backgroundColor: theme.surface.special.overlay },
116
- backdropStyle // Aplicamos estilo animado aquí
109
+ backdropStyle
117
110
  ]}
118
111
  >
119
112
  <Pressable onPress={() => runOnJS(handleClose)()} style={{ flex: 1 }} />
120
113
  </Animated.View>
121
114
 
122
- <GestureDetector gesture={pan}>
123
- <Animated.View
124
- style={[
125
- isMobile ? styles.container.typeBottomSheet : styles.container.typeDrawer,
126
- animatedStyle, // Inyectamos la animación de Reanimated
127
- {
128
- backgroundColor: theme.surface.neutral.primary,
129
- // Mantenemos tu lógica exacta de padding y gaps
130
- paddingTop: (hasClose ? theme.space['2xl'] : theme.space.xl),
131
- gap: theme.space.md,
132
- ...(isMobile ? {
133
- borderTopLeftRadius: theme.radius.lg,
134
- borderTopRightRadius: theme.radius.lg,
135
- } : {
136
- borderBottomLeftRadius: theme.radius.lg,
137
- borderTopLeftRadius: theme.radius.lg,
138
- paddingBottom: theme.space.md,
139
- })
140
- }
141
- ]}
142
- >
143
- {/* --- NUEVO: La línea visual (Handle) ---
144
- Se agrega position absolute para NO afectar tus paddings/espacios originales */}
145
- {isMobile && (
115
+ <Animated.View
116
+ style={[
117
+ isMobile ? styles.container.typeBottomSheet : styles.container.typeDrawer,
118
+ animatedStyle,
119
+ {
120
+ backgroundColor: theme.surface.neutral.primary,
121
+ paddingTop: (hasClose ? theme.space['2xl'] : theme.space.xl),
122
+ gap: theme.space.md, // Mantengo tu gap original
123
+ ...(isMobile ? {
124
+ borderTopLeftRadius: theme.radius.lg,
125
+ borderTopRightRadius: theme.radius.lg,
126
+ } : {
127
+ borderBottomLeftRadius: theme.radius.lg,
128
+ borderTopLeftRadius: theme.radius.lg,
129
+ paddingBottom: theme.space.md,
130
+ })
131
+ }
132
+ ]}
133
+ >
134
+ {isMobile && (
135
+ <GestureDetector gesture={pan}>
146
136
  <View style={styles.handleBarContainer}>
147
137
  <View style={[styles.handleBarLine, { backgroundColor: theme.outline.neutral.tertiary }]} />
148
138
  </View>
149
- )}
139
+ </GestureDetector>
140
+ )}
150
141
 
151
- {hasClose && (
152
- <MaterialIcons
153
- name="close"
154
- size={theme.typography.icon.lg}
155
- color={theme.text.neutral.primary}
156
- // Usamos runOnJS porque onPress espera una función normal
157
- onPress={() => runOnJS(handleClose)()}
158
- style={{ position: "absolute", right: theme.space.md, top: theme.space.md, zIndex: 10 }}
159
- />
160
- )}
142
+ {hasClose && (
143
+ <MaterialIcons
144
+ name="close"
145
+ size={theme.typography.icon.lg}
146
+ color={theme.text.neutral.primary}
147
+ onPress={() => runOnJS(handleClose)()}
148
+ style={{ position: "absolute", right: theme.space.md, top: theme.space.md, zIndex: 10 }}
149
+ />
150
+ )}
161
151
 
162
- {!!title && <Text style={[theme.typography.h3, { marginHorizontal: theme.space.md }]}>{title}</Text>}
152
+ {!!title && <Text style={[theme.typography.h3, { marginHorizontal: theme.space.md }]}>{title}</Text>}
163
153
 
164
- <View style={[styles.scrollWrapper,]}>
154
+ <View style={styles.scrollWrapper}>
155
+ <GestureDetector gesture={Gesture.Simultaneous(nativeGesture)}>
165
156
  <ScrollView
166
157
  ref={ref}
167
158
  style={styles.scrollArea}
168
- contentContainerStyle={[styles.scrollContent, { paddingBottom: theme.space.xl }]}
159
+ contentContainerStyle={[styles.scrollContent, { paddingBottom: theme.space.xl }]} // Tu padding original
169
160
  showsVerticalScrollIndicator={true}
170
161
  bounces={true}
171
- alwaysBounceVertical={false}
162
+ nestedScrollEnabled={true}
172
163
  scrollEventThrottle={16}
173
164
  >
174
165
  {!!description && (
@@ -178,33 +169,32 @@ const bottomSheetRender = ({
178
169
  )}
179
170
  {customSlot}
180
171
  </ScrollView>
181
- <LinearGradient
182
- colors={['transparent', theme.surface.neutral.primary]}
183
- style={[styles.fadeGradient, {pointerEvents: 'none'}]}
172
+ </GestureDetector>
173
+ <LinearGradient
174
+ colors={['transparent', theme.surface.neutral.primary]}
175
+ style={[styles.fadeGradient, { pointerEvents: 'none' }]}
176
+ />
177
+ </View>
178
+
179
+ {type !== "informative" && (
180
+ <View style={[isMobile ? styles.actionsContainer.typeBottomSheet : styles.actionsContainer.typeDrawer, { paddingHorizontal: theme.space.md, paddingVertical: theme.space.lg }]}>
181
+ <CDSButton
182
+ label={primaryButtonLabel}
183
+ onPress={() => runOnJS(handleClose)()}
184
184
  />
185
+ {secondaryButtonLabel && (<CDSButton
186
+ label={secondaryButtonLabel}
187
+ type="ghost"
188
+ onPress={() => runOnJS(handleClose)()}
189
+ />)}
185
190
  </View>
186
-
187
- {type !== "informative" && (
188
- <View style={[isMobile ? styles.actionsContainer.typeBottomSheet : styles.actionsContainer.typeDrawer, { paddingHorizontal: theme.space.md, paddingVertical: theme.space.lg }]}>
189
- <CDSButton
190
- label={primaryButtonLabel}
191
- onPress={() => runOnJS(handleClose)()}
192
- />
193
- {secondaryButtonLabel && (<CDSButton
194
- label={secondaryButtonLabel}
195
- type="ghost"
196
- onPress={() => runOnJS(handleClose)()}
197
- />)}
198
- </View>
199
- )}
200
- </Animated.View>
201
- </GestureDetector>
191
+ )}
192
+ </Animated.View>
202
193
  </>
203
194
  );
204
195
  };
205
196
 
206
197
  const styles = StyleSheet.create({
207
- // --- Estilos originales intactos ---
208
198
  overlay: {
209
199
  position: "absolute",
210
200
  top: 0,
@@ -215,13 +205,15 @@ const styles = StyleSheet.create({
215
205
  },
216
206
  container: {
217
207
  typeBottomSheet: {
218
- maxHeight: '95%',
208
+ flexShrink: 1,
209
+ maxHeight: height * 0.83,
219
210
  width: "100%",
220
211
  position: "absolute",
221
212
  bottom: 0,
222
213
  left: 0,
223
214
  right: 0,
224
215
  zIndex: 99,
216
+ ...Platform.select({web:{maxHeight: '95%'}})
225
217
  },
226
218
  typeDrawer: {
227
219
  position: "absolute",
@@ -236,15 +228,11 @@ const styles = StyleSheet.create({
236
228
  },
237
229
  },
238
230
  scrollWrapper: {
239
- overflow: 'visible',
240
- flex: 1,
241
- position: 'relative',
231
+ flexShrink: 1, // CLAVE: Permite encogerse si el contenido es mucho
242
232
  width: '100%',
243
- paddingBottom: 1
244
233
  },
245
234
  scrollArea: {
246
- flex: 1,
247
- width: '100%',
235
+ width: '100%', // Quitamos el flex: 1 de aquí para que sea dinámico
248
236
  },
249
237
  scrollContent: {
250
238
  gap: 16,
@@ -269,13 +257,15 @@ const styles = StyleSheet.create({
269
257
  marginTop: 'auto',
270
258
  },
271
259
  },
272
- // --- NUEVOS ESTILOS PARA EL HANDLE ---
273
260
  handleBarContainer: {
274
261
  position: 'absolute',
275
- top: 8, // Un poco de margen superior
276
- width: '100%',
262
+ top: 0,
263
+ left: 0,
264
+ right: 0,
265
+ height: 30,
277
266
  alignItems: 'center',
278
- zIndex: 1, // Debajo del botón de cerrar
267
+ justifyContent: 'center',
268
+ zIndex: 20,
279
269
  },
280
270
  handleBarLine: {
281
271
  width: 40,
@@ -45,11 +45,13 @@ export const CDSButtonGroup = ({ options, onSelect, label = 'Label', selected =
45
45
  const styles = StyleSheet.create({
46
46
  container: {
47
47
  width: '100%',
48
+
48
49
  },
49
50
 
50
51
  actionsContainer: {
51
52
  flexDirection: 'row',
52
53
  alignItems: 'center',
53
54
  justifyContent: 'center',
55
+ width: '100%'
54
56
  },
55
57
  });
@@ -9,13 +9,11 @@ import Animated, {
9
9
  } from "react-native-reanimated";
10
10
  import { useTheme } from "../context/CDSThemeContext";
11
11
 
12
- export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeText, animationTrigger }) => {
12
+ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeText, animationTrigger, returnKeyType, onFocus }) => {
13
13
  const { theme } = useTheme();
14
14
  const [isFocused, setIsFocused] = useState(false);
15
15
 
16
- // Valor animado para el "flash" (0 a 1)
17
16
  const flashValue = useSharedValue(0);
18
- // Ref para detectar si el cambio vino del teclado o del código
19
17
  const isInternalChange = useRef(false);
20
18
 
21
19
  useEffect(() => {
@@ -29,7 +27,7 @@ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeTe
29
27
  }, [value, animationTrigger]);
30
28
 
31
29
  const handleTextChange = (inputText) => {
32
- isInternalChange.current = true; // Marcamos que el cambio es del usuario
30
+ isInternalChange.current = true;
33
31
 
34
32
  if (keyboard === "numeric" || keyboard === "decimal-pad") {
35
33
  let cleaned = inputText.replace(',', '.').replace(/[^0-9.]/g, "");
@@ -41,28 +39,31 @@ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeTe
41
39
  }
42
40
  };
43
41
 
44
- // Estilo animado para el borde y el fondo
45
42
  const animatedBoxStyle = useAnimatedStyle(() => {
46
43
  const borderColor = interpolateColor(
47
44
  flashValue.value,
48
45
  [0, 1],
49
46
  [
50
47
  isFocused ? theme.outline.neutral.focus : value ? theme.outline.neutral.tertiaryVariant : theme.outline.neutral.primary,
51
- theme.outline.neutral.focus// Color del "brillo" (usamos el color de focus)
48
+ theme.outline.neutral.focus
52
49
  ]
53
50
  );
54
51
 
55
52
  return {
56
53
  borderColor: borderColor,
57
- transform: [{ scale: 1 + flashValue.value * 0.02 }], // Pequeño pulso
58
- borderWidth: 1 + flashValue.value * 1,
54
+ // Quitamos el scale momentáneamente para asegurar que el layout no brinque
55
+ // transform: [{ scale: 1 + flashValue.value * 0.01 }],
56
+ borderWidth: 1 + (flashValue.value * 1),
59
57
  };
60
58
  });
61
59
 
62
60
  return (
63
- <View style={[styles.container, { gap: theme.space.sm }]}>
61
+ <View style={styles.container}>
64
62
  {label && (
65
- <Text style={[theme.typography.label, { color: theme.text.neutral.primary }]}>
63
+ <Text style={[
64
+ theme.typography.label,
65
+ { color: theme.text.neutral.primary, marginBottom: theme.space.xs }
66
+ ]}>
66
67
  {label}
67
68
  </Text>
68
69
  )}
@@ -79,11 +80,13 @@ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeTe
79
80
  style={[
80
81
  styles.textBox,
81
82
  theme.typography.inputText.value,
82
- { pointerEvents: (type === 'readOnly' ? 'none' : 'auto'),
83
+ {
83
84
  paddingHorizontal: theme.space.sm,
84
- color: (type === 'readOnly' ? theme.text.neutral.secondary :theme.text.neutral.primary),
85
- // @ts-ignore
86
- outlineStyle: 'none',
85
+ color: type === 'readOnly' ? theme.text.neutral.secondary : theme.text.neutral.primary,
86
+ // Fix para web:
87
+ ...Platform.select({
88
+ web: { outlineStyle: 'none' }
89
+ })
87
90
  },
88
91
  ]}
89
92
  placeholder={placeholder}
@@ -93,9 +96,10 @@ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeTe
93
96
  value={value}
94
97
  editable={type !== 'readOnly'}
95
98
  secureTextEntry={type === "password"}
96
- onFocus={() => setIsFocused(true)}
99
+ onFocus={() => {setIsFocused(true); onFocus}}
97
100
  onBlur={() => setIsFocused(false)}
98
101
  underlineColorAndroid="transparent"
102
+ returnKeyType={returnKeyType}
99
103
  />
100
104
  </Animated.View>
101
105
  </View>
@@ -103,14 +107,20 @@ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeTe
103
107
  };
104
108
 
105
109
  const styles = StyleSheet.create({
106
- container: { width: "100%" },
110
+ container: {
111
+ width: "100%",
112
+ alignSelf: 'stretch',
113
+ },
107
114
  wrapper: {
115
+ width: '100%',
108
116
  height: 48,
117
+ flexDirection: 'row', // Asegura que el TextInput interno tenga un eje claro
118
+ alignItems: 'center',
109
119
  borderWidth: 1,
110
- justifyContent: 'center',
111
120
  },
112
121
  textBox: {
113
- flex: 1,
122
+ flex: 1, // Esto es lo más importante: ocupa todo el espacio sobrante
114
123
  height: '100%',
124
+ width: '100%',
115
125
  },
116
126
  });
@@ -1,11 +1,11 @@
1
1
  import React, { useEffect, useState } from "react";
2
- import { View, StyleSheet, Modal, Text } from "react-native";
2
+ import { View, StyleSheet, Modal, Text, Platform } from "react-native";
3
3
  import LottieView from "lottie-react-native";
4
4
  import Animated, {
5
5
  useSharedValue,
6
6
  useAnimatedStyle,
7
7
  withTiming,
8
- withDelay, // 👈 Importamos withDelay
8
+ withDelay,
9
9
  runOnJS
10
10
  } from "react-native-reanimated";
11
11
  import { useTheme } from "../context/CDSThemeContext";
@@ -18,11 +18,8 @@ export const CDSLoader = ({ visible = false, message }) => {
18
18
  useEffect(() => {
19
19
  if (visible) {
20
20
  setShouldRender(true);
21
- // Entrada rápida para que no se sienta lag al empezar
22
21
  opacity.value = withTiming(1, { duration: 300 });
23
22
  } else {
24
- // 1. Espera 800ms antes de hacer nada (Sostenido)
25
- // 2. Desvanece lentamente en 1200ms
26
23
  opacity.value = withDelay(
27
24
  1000,
28
25
  withTiming(0, { duration: 1200 }, (isFinished) => {
@@ -38,26 +35,38 @@ export const CDSLoader = ({ visible = false, message }) => {
38
35
  opacity: opacity.value,
39
36
  }));
40
37
 
41
- if (!shouldRender && !visible) return null;
38
+ if (!shouldRender) return null;
39
+
42
40
  return (
43
- <Modal transparent visible={shouldRender} animationType="none">
44
- <Animated.View style={[styles.overlay, animatedStyle,
45
- { backgroundColor: theme.surface.special.overlay },]}>
46
- <View
47
- style={[
48
- styles.loaderContainer,
49
- ]}
50
- >
41
+ <Modal transparent visible={true} animationType="none">
42
+ <Animated.View style={[
43
+ styles.overlay,
44
+ animatedStyle,
45
+ { backgroundColor: theme.surface.special.overlay }
46
+ ]}>
47
+ <View style={styles.loaderContainer}>
51
48
  <LottieView
52
49
  autoPlay
53
50
  loop
54
51
  source={require("../assets/animations/animationLoaderWhite.json")}
52
+ // CLAVE: Definimos un tamaño que funcione en ambos,
53
+ // pero limitado para que en Web no explote
54
+ style={styles.lottie}
55
55
  resizeMode="contain"
56
56
  />
57
- {message && <Text
58
- style={[theme.typography.semiBold.md, { textAlign: 'center', width:'100%', color: theme.text.special.loader }]}>
59
- {message}
60
- </Text>}
57
+ {message && (
58
+ <Text style={[
59
+ theme.typography.semiBold.md,
60
+ {
61
+ textAlign: 'center',
62
+ color: theme.text.special.loader,
63
+ marginTop: 10,
64
+ paddingHorizontal: 20
65
+ }
66
+ ]}>
67
+ {message}
68
+ </Text>
69
+ )}
61
70
  </View>
62
71
  </Animated.View>
63
72
  </Modal>
@@ -66,19 +75,26 @@ export const CDSLoader = ({ visible = false, message }) => {
66
75
 
67
76
  const styles = StyleSheet.create({
68
77
  overlay: {
69
- height: '100%',
70
- width: '100%',
78
+ flex: 1,
71
79
  justifyContent: "center",
72
80
  alignItems: "center",
73
- zIndex: 1000,
74
81
  },
75
82
  loaderContainer: {
76
- padding: 20,
77
- // O si usas fijos, asegúrate que sean mayores al estilo del Lottie
78
- minWidth: 180,
79
- minHeight: 180,
80
- maxWidth: 400,
83
+ // Evita que el contenedor se estire en pantallas grandes (Web)
84
+ width: '100%',
85
+ maxWidth: 300,
81
86
  justifyContent: "center",
82
87
  alignItems: "center",
83
88
  },
89
+ lottie: {
90
+ // Tamaño controlado para móvil y Web
91
+ width: 150,
92
+ height: 150,
93
+ // En Web a veces necesita una alineación extra
94
+ ...Platform.select({
95
+ web: {
96
+ alignSelf: 'center',
97
+ }
98
+ })
99
+ },
84
100
  });
@@ -1,56 +1,76 @@
1
-
2
- import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
3
-
4
-
1
+ import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
5
2
  import { MaterialIcons } from "@expo/vector-icons";
6
3
  import { useTheme } from "../context/CDSThemeContext";
7
4
  import { useNavigation } from "@react-navigation/native";
8
5
 
9
6
  export const CDSNavBar = ({
10
-
11
7
  type,
12
8
  isExpanded,
13
9
  hasClose,
14
10
  title,
15
- hasBack, }) => {
16
-
17
- const { theme, isDarkMode } = useTheme();
18
- const isMobile = theme.isMobile
19
-
11
+ hasBack,
12
+ }) => {
13
+ const { theme, isDarkMode } = useTheme();
20
14
  const navigation = useNavigation();
21
15
 
22
16
  const handleBackPress = () => {
23
- if (hasBack) {
24
- navigation.goBack();
25
- }
17
+ if (hasBack) navigation.goBack();
26
18
  };
27
19
 
28
20
  const handleClosePress = () => {
29
- if (hasClose) {
30
- navigation.goBack();
31
- }
21
+ if (hasClose) navigation.goBack();
32
22
  };
33
23
 
34
24
  return (
35
- <View style={[styles.container, { backgroundColor: theme.surface.neutral.primary, borderColor: theme.outline.brand.primary }]}>
36
- <TouchableOpacity onPress={handleBackPress}>
37
- <MaterialIcons name="arrow-back-ios" size={theme.typography.icon.md} color={theme.text.neutral.primary} style={{ cursor: hasBack ? 'pointer' : 'default', opacity: hasBack ? 1 : 0 }} />
25
+ <View style={[styles.container, {
26
+ backgroundColor: theme.surface.neutral.primary,
27
+ borderBottomColor: theme.outline.neutral.primary // Usando tu variable de tema
28
+ }]}>
29
+
30
+ {/* Contenedor del título o logo (Capa absoluta para centrado perfecto) */}
31
+ <View style={styles.centerContainer}>
32
+ {type === 'title' ? (
33
+ <Text
34
+ numberOfLines={1}
35
+ style={[styles.title, theme.typography.semiBold.lg, { color: theme.text.neutral.primary }]}
36
+ >
37
+ {title || 'title'}
38
+ </Text>
39
+ ) : (
40
+ <Image
41
+ source={
42
+ isDarkMode
43
+ ? require("../assets/images/logoMonteTW.png")
44
+ : require("../assets/images/logoMonte.png")
45
+ }
46
+ style={styles.logo}
47
+ resizeMode="contain"
48
+ />
49
+ )}
50
+ </View>
51
+
52
+ {/* Icono Izquierdo */}
53
+ <TouchableOpacity onPress={handleBackPress} disabled={!hasBack}>
54
+ <MaterialIcons
55
+ name="arrow-back-ios"
56
+ size={theme.typography.icon.md}
57
+ color={theme.text.neutral.primary}
58
+ style={{ opacity: hasBack ? 1 : 0 }}
59
+ />
38
60
  </TouchableOpacity>
39
- {type === 'title' ? <Text style={[styles.title, theme.typography.semiBold.lg]}>{title || 'title'}</Text> : <Image
40
- source={
41
- isDarkMode
42
- ? require("../assets/images/logoMonteTW.png")
43
- : require("../assets/images/logoMonte.png")
44
- }
45
- style={styles.logo}
46
- resizeMode="contain"
47
- />}
48
- <TouchableOpacity onPress={handleClosePress}>
49
- <MaterialIcons name="close" size={theme.typography.icon.md} color={theme.text.neutral.primary} style={{ cursor: hasClose ? 'pointer' : 'default', opacity: hasClose ? 1 : 0 }} />
61
+
62
+ {/* Icono Derecho */}
63
+ <TouchableOpacity onPress={handleClosePress} disabled={!hasClose}>
64
+ <MaterialIcons
65
+ name="close"
66
+ size={theme.typography.icon.md}
67
+ color={theme.text.neutral.primary}
68
+ style={{ opacity: hasClose ? 1 : 0 }}
69
+ />
50
70
  </TouchableOpacity>
51
71
  </View>
52
- )
53
- }
72
+ );
73
+ };
54
74
 
55
75
  const styles = StyleSheet.create({
56
76
  container: {
@@ -61,18 +81,23 @@ const styles = StyleSheet.create({
61
81
  justifyContent: 'space-between',
62
82
  paddingHorizontal: 16,
63
83
  borderBottomWidth: 1,
64
- borderBottomColor: '#E0E0E0',
65
84
  },
66
-
85
+ centerContainer: {
86
+ // Esta capa flota detrás de los botones y ocupa todo el ancho
87
+ position: 'absolute',
88
+ left: 0,
89
+ right: 0,
90
+ top: 0,
91
+ bottom: 0,
92
+ justifyContent: 'center',
93
+ alignItems: 'center',
94
+ paddingHorizontal: 48, // Evita que el texto choque con los iconos
95
+ },
67
96
  title: {
68
- width: '100%',
69
97
  textAlign: 'center',
70
98
  },
71
-
72
99
  logo: {
73
- flex: 1,
74
- height: '80%',
75
- resizeMode: 'contain',
100
+ height: '60%',
101
+ width: 150, // Ajusta según tu logo
76
102
  },
77
-
78
- });
103
+ });
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useRef, useEffect } from "react";
2
2
  import {
3
- View, Text, TouchableOpacity, StyleSheet, FlatList,
3
+ View, Text, TouchableOpacity, StyleSheet, ScrollView,
4
4
  Animated, Pressable, Platform
5
5
  } from "react-native";
6
6
  import { MaterialIcons } from "@expo/vector-icons";
@@ -81,7 +81,6 @@ export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null
81
81
  if (onSelect) onSelect(item.value);
82
82
  };
83
83
 
84
- // Estructura idéntica a CardFeedback para mantener consistencia
85
84
  const animatedLayout = {
86
85
  maxHeight: animatedValue.interpolate({
87
86
  inputRange: [0, 1],
@@ -125,7 +124,7 @@ export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null
125
124
  <Animated.View
126
125
  style={[
127
126
  styles.dropdownInline,
128
- animatedLayout, // Aplicamos el objeto de animación aquí
127
+ animatedLayout,
129
128
  {
130
129
  backgroundColor: theme.surface.neutral.primary,
131
130
  borderRadius: theme.radius.sm,
@@ -134,19 +133,17 @@ export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null
134
133
  }
135
134
  ]}
136
135
  >
137
- <FlatList
138
- data={options}
139
- keyExtractor={(item) => item.value.toString()}
140
- renderItem={({ item }) => (
136
+ <ScrollView bounces={false} nestedScrollEnabled={true}>
137
+ {options.map((item, index) => (
141
138
  <SelectOption
139
+ key={item.value?.toString() || index.toString()}
142
140
  item={item}
143
141
  isSelected={selectedItem?.value === item.value}
144
142
  onSelect={handleSelect}
145
143
  theme={theme}
146
144
  />
147
- )}
148
- nestedScrollEnabled={true}
149
- />
145
+ ))}
146
+ </ScrollView>
150
147
  </Animated.View>
151
148
  )}
152
149
  </View>
@@ -155,7 +152,7 @@ export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null
155
152
 
156
153
  const styles = StyleSheet.create({
157
154
  mainContainer: { width: '100%' },
158
- inputContainer: { height: 48, borderWidth: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
155
+ inputContainer: { width: '100%', height: 48, borderWidth: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
159
156
  dropdownInline: {
160
157
  borderWidth: 1,
161
158
  },
@@ -1,41 +1,51 @@
1
1
  import React from 'react';
2
- import { View, Text, StyleSheet, ScrollView, useWindowDimensions } from 'react-native';
2
+ import { View, Text, StyleSheet, ScrollView, useWindowDimensions, Platform } from 'react-native';
3
3
  import { useTheme } from '../context/CDSThemeContext';
4
4
 
5
5
  export const CDSTable = ({ columns, data, breakpoint = 500 }) => {
6
6
  const { theme } = useTheme();
7
7
  const { width: screenWidth } = useWindowDimensions();
8
8
 
9
- // Aquí definimos que "Mobile" es cualquier pantalla menor al breakpoint
10
9
  const isSmallScreen = screenWidth < breakpoint;
11
10
 
12
11
  const TableContent = (
13
12
  <View style={[
14
13
  styles.tableContainer,
15
- {
16
- borderColor: theme.outline.neutral.primary,
14
+ {
15
+ borderColor: theme.outline.neutral.primary,
17
16
  borderRadius: theme.radius.md,
18
- // Si es pantalla pequeña, ancho libre para que crezca a la derecha.
19
- // Si es pantalla grande, ocupa el 100% del padre.
20
- width: isSmallScreen ? undefined : '100%',
17
+ // Asegura el 100% en desktop y auto-ajuste en móvil
18
+ width: isSmallScreen ? 'auto' : '100%',
19
+ minWidth: '100%',
21
20
  }
22
21
  ]}>
23
22
  {/* Encabezado */}
24
23
  <View style={[
25
- styles.row,
26
- styles.header,
27
- { backgroundColor: theme.surface.neutral.primaryVariant, borderBottomColor: theme.outline.neutral.primary }
24
+ styles.row,
25
+ styles.header,
26
+ {
27
+ backgroundColor: theme.surface.neutral.primaryVariant,
28
+ borderBottomColor: theme.outline.neutral.primary
29
+ }
28
30
  ]}>
29
31
  {columns.map((col, index) => (
30
- <View
32
+ <View
31
33
  key={`header-${index}`}
32
34
  style={[
33
- styles.cell,
35
+ styles.cell,
34
36
  { padding: theme.space.sm },
35
- col.width ? { width: col.width } : { flex: 1 }
37
+ col.width ? { width: col.width } : { flex: 1 },
38
+ // Alinea el contenedor según la propiedad align de la columna
39
+ col.align === 'right' ? { alignItems: 'flex-end' } : { alignItems: 'flex-start' }
36
40
  ]}
37
41
  >
38
- <Text style={[styles.headerText, theme.typography.semiBold.md]}>
42
+ <Text style={[
43
+ theme.typography.semiBold.md,
44
+ {
45
+ color: theme.text.neutral.primary,
46
+ textAlign: col.align || 'left'
47
+ }
48
+ ]}>
39
49
  {col.title}
40
50
  </Text>
41
51
  </View>
@@ -53,15 +63,25 @@ export const CDSTable = ({ columns, data, breakpoint = 500 }) => {
53
63
  ]}
54
64
  >
55
65
  {columns.map((col, colIndex) => (
56
- <View
66
+ <View
57
67
  key={`cell-${rowIndex}-${colIndex}`}
58
68
  style={[
59
- styles.cell,
69
+ styles.cell,
60
70
  { padding: theme.space.sm },
61
- col.width ? { width: col.width } : { flex: 1 }
71
+ col.width ? { width: col.width } : { flex: 1 },
72
+ // Alinea el contenedor para móvil (Expo Go)
73
+ col.align === 'right' ? { alignItems: 'flex-end' } : { alignItems: 'flex-start' }
62
74
  ]}
63
75
  >
64
- <Text style={[styles.cellText, theme.typography.regular.md]}>
76
+ <Text style={[
77
+ theme.typography.regular.md,
78
+ {
79
+ color: theme.text.neutral.secondary,
80
+ textAlign: col.align || 'left',
81
+ // Tabular-nums asegura que los números tengan el mismo ancho (perfecto para precios)
82
+ fontVariant: col.align === 'right' ? ['tabular-nums'] : [],
83
+ }
84
+ ]}>
65
85
  {item[col.key]}
66
86
  </Text>
67
87
  </View>
@@ -73,11 +93,10 @@ export const CDSTable = ({ columns, data, breakpoint = 500 }) => {
73
93
 
74
94
  if (isSmallScreen) {
75
95
  return (
76
- <View style={styles.scrollWrapper}>
77
- <ScrollView
78
- horizontal
79
- showsHorizontalScrollIndicator={true}
80
- // Importante: No ponemos alignItems: 'flex-start' aquí para que no muerda el final
96
+ <View style={styles.scrollWrapper}>
97
+ <ScrollView
98
+ horizontal
99
+ showsHorizontalScrollIndicator={false}
81
100
  contentContainerStyle={styles.scrollContent}
82
101
  >
83
102
  {TableContent}
@@ -98,9 +117,7 @@ const styles = StyleSheet.create({
98
117
  width: '100%',
99
118
  },
100
119
  scrollContent: {
101
- // Asegura que la tabla pueda expandirse horizontalmente sin restricciones
102
- minWidth: '100%',
103
- paddingRight: 20, // Espacio extra al final del scroll para que la última columna no pegue
120
+ flexGrow: 1,
104
121
  },
105
122
  fullWidthContainer: {
106
123
  width: '100%',
@@ -111,19 +128,15 @@ const styles = StyleSheet.create({
111
128
  },
112
129
  row: {
113
130
  flexDirection: 'row',
131
+ width: '100%',
132
+ // Importante para que el fondo del header no se corte en scroll horizontal
133
+ minWidth: '100%',
114
134
  },
115
135
  header: {
116
136
  borderBottomWidth: 1,
117
137
  },
118
138
  cell: {
119
139
  justifyContent: 'center',
120
- // Ancho mínimo por columna para que siempre sea legible en scroll
121
- minWidth: 100,
122
- },
123
- headerText: {
124
- textAlign: 'center',
125
- },
126
- cellText: {
127
- textAlign: 'center',
140
+ minWidth: 100, // Un poco más de aire para evitar saltos de línea agresivos
128
141
  },
129
142
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cdslibrary",
3
3
  "license": "0BSD",
4
- "version": "1.2.78",
4
+ "version": "1.2.79",
5
5
  "main": "index.js",
6
6
  "author": "Nat Viramontes",
7
7
  "description": "A library of components for the CDS project",