cdslibrary 1.2.88 → 1.2.89
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/components/CDSBottomSheet.jsx +64 -26
- package/components/CDSButton.jsx +4 -33
- package/components/CDSButtonGroup.jsx +1 -1
- package/components/CDSCarousel.jsx +1 -1
- package/components/CDSImageButtonGroup.jsx +2 -2
- package/components/CDSInput.jsx +29 -10
- package/components/CDSLoader.jsx +2 -2
- package/package.json +1 -1
|
@@ -26,8 +26,10 @@ const bottomSheetRender = ({
|
|
|
26
26
|
customSlot,
|
|
27
27
|
primaryButtonLabel = "Aceptar",
|
|
28
28
|
primaryButtonOnPress,
|
|
29
|
+
secondaryButtonOnPress,
|
|
29
30
|
secondaryButtonLabel,
|
|
30
31
|
onFinish,
|
|
32
|
+
closeOnSecondaryPress = true,
|
|
31
33
|
}, ref) => {
|
|
32
34
|
const { theme } = useTheme();
|
|
33
35
|
const isMobile = theme.isMobile;
|
|
@@ -36,16 +38,28 @@ const bottomSheetRender = ({
|
|
|
36
38
|
const translation = useSharedValue(startPos);
|
|
37
39
|
const opacity = useSharedValue(0);
|
|
38
40
|
|
|
39
|
-
const
|
|
41
|
+
const [contentHeight, setContentHeight] = React.useState(0);
|
|
42
|
+
const [layoutHeight, setLayoutHeight] = React.useState(0);
|
|
43
|
+
|
|
44
|
+
// Comprobamos si el contenido desborda el contenedor
|
|
45
|
+
const isScrollable = contentHeight > layoutHeight;
|
|
46
|
+
|
|
47
|
+
const triggerOnFinish = useCallback((wasActionAlreadyDone = false) => {
|
|
40
48
|
if (onFinish) onFinish();
|
|
41
|
-
if (primaryButtonOnPress) primaryButtonOnPress();
|
|
42
|
-
}, [onFinish, primaryButtonOnPress]);
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
// SI NO se ha ejecutado la acción (se cerró por X, gesto o backdrop)
|
|
51
|
+
// Y SI NO hay botón secundario (es un modal de botón único)
|
|
52
|
+
if (!wasActionAlreadyDone && !secondaryButtonLabel && primaryButtonOnPress) {
|
|
53
|
+
primaryButtonOnPress();
|
|
54
|
+
}
|
|
55
|
+
}, [onFinish, secondaryButtonLabel, primaryButtonOnPress]);
|
|
56
|
+
|
|
57
|
+
// 2. handleClose recibe el parámetro y lo pasa al JS
|
|
58
|
+
const handleClose = useCallback((wasActionAlreadyDone = false) => {
|
|
45
59
|
"worklet";
|
|
46
60
|
translation.value = withTiming(startPos, { duration: 300 }, (finished) => {
|
|
47
61
|
if (finished) {
|
|
48
|
-
runOnJS(triggerOnFinish)();
|
|
62
|
+
runOnJS(triggerOnFinish)(wasActionAlreadyDone);
|
|
49
63
|
}
|
|
50
64
|
});
|
|
51
65
|
opacity.value = withTiming(0, { duration: 300 });
|
|
@@ -86,7 +100,7 @@ const bottomSheetRender = ({
|
|
|
86
100
|
}
|
|
87
101
|
}, [isVisible, startPos]); // Asegúrate de incluir startPos
|
|
88
102
|
|
|
89
|
-
const animatedStyle = useAnimatedStyle(() => {
|
|
103
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
90
104
|
return {
|
|
91
105
|
opacity: opacity.value,
|
|
92
106
|
// Ocultamos el layout completamente si terminó de salir
|
|
@@ -120,11 +134,11 @@ const animatedStyle = useAnimatedStyle(() => {
|
|
|
120
134
|
<Animated.View
|
|
121
135
|
style={[
|
|
122
136
|
styles.overlay,
|
|
123
|
-
{pointerEvents: isVisible ? "auto" : "none", backgroundColor: theme.surface.special.overlay },
|
|
137
|
+
{ pointerEvents: isVisible ? "auto" : "none", backgroundColor: theme.surface.special.overlay },
|
|
124
138
|
backdropStyle
|
|
125
139
|
]}
|
|
126
140
|
>
|
|
127
|
-
<Pressable onPress={() => runOnJS(handleClose)()} style={{ flex: 1 }} />
|
|
141
|
+
<Pressable onPress={() => runOnJS(handleClose)(false)} style={{ flex: 1 }} />
|
|
128
142
|
</Animated.View>
|
|
129
143
|
<Animated.View
|
|
130
144
|
style={[
|
|
@@ -141,7 +155,6 @@ const animatedStyle = useAnimatedStyle(() => {
|
|
|
141
155
|
} : {
|
|
142
156
|
borderBottomLeftRadius: theme.radius.lg,
|
|
143
157
|
borderTopLeftRadius: theme.radius.lg,
|
|
144
|
-
paddingBottom: theme.space.md,
|
|
145
158
|
})
|
|
146
159
|
}
|
|
147
160
|
]}
|
|
@@ -166,42 +179,65 @@ const animatedStyle = useAnimatedStyle(() => {
|
|
|
166
179
|
|
|
167
180
|
{!!title && <Text style={[theme.typography.h3, { marginHorizontal: theme.space.md }]}>{title}</Text>}
|
|
168
181
|
|
|
169
|
-
<View style={styles.scrollWrapper}>
|
|
182
|
+
<View style={styles.scrollWrapper} onLayout={(e) => setLayoutHeight(e.nativeEvent.layout.height)}>
|
|
170
183
|
<GestureDetector gesture={Gesture.Simultaneous(nativeGesture)}>
|
|
171
184
|
<ScrollView
|
|
172
185
|
ref={ref}
|
|
173
186
|
style={styles.scrollArea}
|
|
174
|
-
|
|
187
|
+
scrollEnabled={isScrollable} // Desactiva el scroll si no es necesario
|
|
188
|
+
onContentSizeChange={(_, h) => setContentHeight(h)}
|
|
189
|
+
contentContainerStyle={[styles.scrollContent, {}]} // Tu padding original
|
|
175
190
|
showsVerticalScrollIndicator={true}
|
|
176
191
|
bounces={true}
|
|
177
192
|
nestedScrollEnabled={true}
|
|
178
193
|
scrollEventThrottle={16}
|
|
179
194
|
>
|
|
180
195
|
{!!description && (
|
|
181
|
-
<Text style={[theme.typography.regular.md, {
|
|
196
|
+
<Text style={[theme.typography.regular.md, { paddingHorizontal: theme.space.md }]}>
|
|
182
197
|
{description}
|
|
183
198
|
</Text>
|
|
184
199
|
)}
|
|
185
200
|
{customSlot}
|
|
186
201
|
</ScrollView>
|
|
187
202
|
</GestureDetector>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
{isScrollable && (
|
|
204
|
+
<LinearGradient
|
|
205
|
+
colors={['transparent', theme.surface.neutral.primary]}
|
|
206
|
+
style={[styles.fadeGradient, {
|
|
207
|
+
pointerEvents: 'none',
|
|
208
|
+
marginLeft: !isMobile && theme.space.md,
|
|
209
|
+
borderBottomLeftRadius: !isMobile && 2
|
|
210
|
+
}]}
|
|
211
|
+
/>
|
|
212
|
+
)}
|
|
192
213
|
</View>
|
|
193
214
|
|
|
194
215
|
{type !== "informative" && (
|
|
195
|
-
<View style={[isMobile ? styles.actionsContainer.typeBottomSheet : styles.actionsContainer.typeDrawer, {
|
|
216
|
+
<View style={[isMobile ? styles.actionsContainer.typeBottomSheet : styles.actionsContainer.typeDrawer, { margin: theme.space.md }]}>
|
|
217
|
+
{/* BOTÓN PRIMARIO */}
|
|
196
218
|
<CDSButton
|
|
197
219
|
label={primaryButtonLabel}
|
|
198
|
-
onPress={() =>
|
|
220
|
+
onPress={() => {
|
|
221
|
+
if (primaryButtonOnPress) primaryButtonOnPress();
|
|
222
|
+
runOnJS(handleClose)(true); // <--- TRUE: Acción ya hecha
|
|
223
|
+
}}
|
|
199
224
|
/>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
225
|
+
|
|
226
|
+
{/* BOTÓN SECUNDARIO (si existe) */}
|
|
227
|
+
{secondaryButtonLabel && (
|
|
228
|
+
<CDSButton
|
|
229
|
+
label={secondaryButtonLabel}
|
|
230
|
+
type="ghost"
|
|
231
|
+
onPress={() => {
|
|
232
|
+
if (secondaryButtonOnPress) secondaryButtonOnPress();
|
|
233
|
+
|
|
234
|
+
// Solo cerramos si no nos dijeron lo contrario
|
|
235
|
+
if (closeOnSecondaryPress) {
|
|
236
|
+
runOnJS(handleClose)(true);
|
|
237
|
+
}
|
|
238
|
+
}}
|
|
239
|
+
/>
|
|
240
|
+
)}
|
|
205
241
|
</View>
|
|
206
242
|
)}
|
|
207
243
|
</Animated.View>
|
|
@@ -249,27 +285,29 @@ const styles = StyleSheet.create({
|
|
|
249
285
|
width: '100%',
|
|
250
286
|
},
|
|
251
287
|
scrollArea: {
|
|
288
|
+
|
|
252
289
|
width: '100%',
|
|
253
290
|
},
|
|
254
291
|
scrollContent: {
|
|
255
292
|
gap: 16,
|
|
293
|
+
|
|
256
294
|
},
|
|
257
295
|
fadeGradient: {
|
|
258
296
|
position: 'absolute',
|
|
259
297
|
bottom: 0,
|
|
260
298
|
left: 0,
|
|
261
299
|
right: 0,
|
|
262
|
-
height:
|
|
300
|
+
height: 32,
|
|
263
301
|
},
|
|
264
302
|
actionsContainer: {
|
|
265
303
|
typeBottomSheet: {
|
|
266
304
|
flexDirection: "column",
|
|
267
|
-
width: "
|
|
305
|
+
width: "auto",
|
|
268
306
|
gap: 8,
|
|
269
307
|
},
|
|
270
308
|
typeDrawer: {
|
|
271
309
|
flexDirection: "row-reverse",
|
|
272
|
-
width: "
|
|
310
|
+
width: "auto",
|
|
273
311
|
gap: 8,
|
|
274
312
|
marginTop: 'auto',
|
|
275
313
|
},
|
package/components/CDSButton.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useRef, useEffect } from "react";
|
|
2
|
-
import { Text, StyleSheet, TouchableOpacity, Animated } from "react-native";
|
|
2
|
+
import { Text, StyleSheet, TouchableOpacity, Animated, View } from "react-native";
|
|
3
3
|
import { MaterialIcons } from "@expo/vector-icons";
|
|
4
4
|
import { useTheme } from "../context/CDSThemeContext";
|
|
5
5
|
|
|
@@ -15,34 +15,6 @@ export const CDSButton = ({
|
|
|
15
15
|
const { theme } = useTheme();
|
|
16
16
|
const isMobile = theme.isMobile;
|
|
17
17
|
|
|
18
|
-
// 1. Referencia de animación
|
|
19
|
-
const animatedValue = useRef(new Animated.Value(0)).current;
|
|
20
|
-
|
|
21
|
-
// 2. Disparar entrada al montar
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
Animated.timing(animatedValue, {
|
|
24
|
-
toValue: 1,
|
|
25
|
-
duration: 400,
|
|
26
|
-
useNativeDriver: false // Para manejar maxHeight y otros layouts
|
|
27
|
-
}).start();
|
|
28
|
-
}, []);
|
|
29
|
-
|
|
30
|
-
// 3. Estructura de animatedLayout unificada
|
|
31
|
-
const animatedLayout = {
|
|
32
|
-
opacity: animatedValue,
|
|
33
|
-
transform: [{
|
|
34
|
-
scale: animatedValue.interpolate({
|
|
35
|
-
inputRange: [0, 1],
|
|
36
|
-
outputRange: [0.95, 1]
|
|
37
|
-
})
|
|
38
|
-
}],
|
|
39
|
-
// Permite que el botón aparezca empujando suavemente
|
|
40
|
-
maxHeight: animatedValue.interpolate({
|
|
41
|
-
inputRange: [0, 1],
|
|
42
|
-
outputRange: [0, (variant === "fill" || isMobile ? 48 : 56)]
|
|
43
|
-
}),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
18
|
const backgroundColor =
|
|
47
19
|
type === "disabled"
|
|
48
20
|
? theme.surface.action.disabled
|
|
@@ -58,11 +30,10 @@ export const CDSButton = ({
|
|
|
58
30
|
: theme.text.neutral.contrast;
|
|
59
31
|
|
|
60
32
|
return (
|
|
61
|
-
<
|
|
62
|
-
animatedLayout,
|
|
33
|
+
<View style={[
|
|
63
34
|
{
|
|
64
35
|
overflow: 'hidden',
|
|
65
|
-
alignSelf: (variant === "fill" || isMobile) ? "stretch" : "
|
|
36
|
+
alignSelf: (variant === "fill" || isMobile) ? "stretch" : "",
|
|
66
37
|
flexGrow: (variant === "fill" || isMobile ? 1 : 0),
|
|
67
38
|
}
|
|
68
39
|
]}>
|
|
@@ -113,7 +84,7 @@ export const CDSButton = ({
|
|
|
113
84
|
/>
|
|
114
85
|
)}
|
|
115
86
|
</TouchableOpacity>
|
|
116
|
-
</
|
|
87
|
+
</View>
|
|
117
88
|
);
|
|
118
89
|
};
|
|
119
90
|
|
|
@@ -24,7 +24,7 @@ export const CDSButtonGroup = ({ options, onSelect, label = 'Label', selected =
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<View style={[styles.container, { gap: theme.space.sm }]}>
|
|
27
|
+
<View style={[styles.container, { gap: theme.space.sm, }]}>
|
|
28
28
|
<Text style={[theme.typography.label]}>{label}</Text>
|
|
29
29
|
<View style={[styles.actionsContainer, { gap: theme.space.sm }]}>
|
|
30
30
|
{options.map((option, index) => (
|
|
@@ -29,7 +29,7 @@ export const CDSCarousel = ({ activeIndex, scrollViewRef, onScroll, images }) =>
|
|
|
29
29
|
onLayout={onLayout}
|
|
30
30
|
>
|
|
31
31
|
{images.map((image, index) => (
|
|
32
|
-
<View key={index} style={{ width: scrollViewWidth, justifyContent: 'center', alignItems: 'center',
|
|
32
|
+
<View key={index} style={{ width: scrollViewWidth, justifyContent: 'center', alignItems: 'center',}}>
|
|
33
33
|
<Image
|
|
34
34
|
source={image}
|
|
35
35
|
style={{
|
|
@@ -35,7 +35,7 @@ export const CDSImageButtonGroup = ({ array, onSelect, isCentered }) => {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
return (
|
|
38
|
-
<
|
|
38
|
+
<View style={[
|
|
39
39
|
animatedLayout,
|
|
40
40
|
{ width: '100%' } // Asegura que el contenedor ocupe todo el ancho disponible
|
|
41
41
|
]}>
|
|
@@ -65,7 +65,7 @@ export const CDSImageButtonGroup = ({ array, onSelect, isCentered }) => {
|
|
|
65
65
|
/>
|
|
66
66
|
))}
|
|
67
67
|
</ScrollView>
|
|
68
|
-
</
|
|
68
|
+
</View>
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
71
|
|
package/components/CDSInput.jsx
CHANGED
|
@@ -23,11 +23,33 @@ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeTe
|
|
|
23
23
|
const isReadOnly = type === 'readOnly';
|
|
24
24
|
|
|
25
25
|
// --- Colores de seguridad ---
|
|
26
|
-
const colorBase = theme?.text?.neutral?.primary
|
|
27
|
-
const colorReadOnlyText = theme?.text?.neutral?.secondary
|
|
28
|
-
const colorFocus = theme?.
|
|
29
|
-
const colorTertiary = theme?.outline?.neutral?.tertiaryVariant
|
|
30
|
-
const colorPrimaryOutline = theme?.outline?.neutral?.primary
|
|
26
|
+
const colorBase = theme?.text?.neutral?.primary
|
|
27
|
+
const colorReadOnlyText = theme?.text?.neutral?.secondary
|
|
28
|
+
const colorFocus = theme?.outline?.neutral?.focus
|
|
29
|
+
const colorTertiary = theme?.outline?.neutral?.tertiaryVariant
|
|
30
|
+
const colorPrimaryOutline = theme?.outline?.neutral?.primary
|
|
31
|
+
|
|
32
|
+
const handleTextChange = (text) => {
|
|
33
|
+
let filteredText = text;
|
|
34
|
+
|
|
35
|
+
if (keyboard === 'numeric') {
|
|
36
|
+
// Elimina cualquier cosa que no sea un número del 0 al 9
|
|
37
|
+
filteredText = text.replace(/[^0-9]/g, '');
|
|
38
|
+
}
|
|
39
|
+
else if (keyboard === 'decimal-pad') {
|
|
40
|
+
// 1. Permite solo números y puntos
|
|
41
|
+
filteredText = text.replace(/[^0-9.]/g, '');
|
|
42
|
+
|
|
43
|
+
// 2. Evita que haya más de un punto decimal
|
|
44
|
+
const parts = filteredText.split('.');
|
|
45
|
+
if (parts.length > 2) {
|
|
46
|
+
filteredText = parts[0] + '.' + parts.slice(1).join('');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isInternalChange.current = true;
|
|
51
|
+
onChangeText && onChangeText(filteredText);
|
|
52
|
+
};
|
|
31
53
|
|
|
32
54
|
useEffect(() => {
|
|
33
55
|
// ELIMINAMOS la restricción de !isReadOnly para que se anime siempre
|
|
@@ -58,7 +80,7 @@ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeTe
|
|
|
58
80
|
return {
|
|
59
81
|
borderColor,
|
|
60
82
|
borderWidth: 1 + (flashValue.value * 2),
|
|
61
|
-
transform: [{ scale: 1 + (flashValue.value * 0.
|
|
83
|
+
transform: [{ scale: 1 + (flashValue.value * 0.01) }]
|
|
62
84
|
};
|
|
63
85
|
});
|
|
64
86
|
|
|
@@ -109,10 +131,7 @@ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeTe
|
|
|
109
131
|
placeholder={placeholder}
|
|
110
132
|
placeholderTextColor={theme.text.neutral.placeholder}
|
|
111
133
|
keyboardType={keyboard}
|
|
112
|
-
onChangeText={
|
|
113
|
-
isInternalChange.current = true;
|
|
114
|
-
onChangeText && onChangeText(txt);
|
|
115
|
-
}}
|
|
134
|
+
onChangeText={handleTextChange}
|
|
116
135
|
value={value}
|
|
117
136
|
editable={!isReadOnly}
|
|
118
137
|
secureTextEntry={type === "password"}
|
package/components/CDSLoader.jsx
CHANGED
|
@@ -21,8 +21,8 @@ export const CDSLoader = ({ visible = false, message }) => {
|
|
|
21
21
|
opacity.value = withTiming(1, { duration: 300 });
|
|
22
22
|
} else {
|
|
23
23
|
opacity.value = withDelay(
|
|
24
|
-
|
|
25
|
-
withTiming(0, { duration:
|
|
24
|
+
200,
|
|
25
|
+
withTiming(0, { duration: 200 }, (isFinished) => {
|
|
26
26
|
if (isFinished) {
|
|
27
27
|
runOnJS(setShouldRender)(false);
|
|
28
28
|
}
|