cdslibrary 1.2.48 → 1.2.50
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 +1 -1
- package/components/CDSButton.jsx +2 -4
- package/components/CDSCardFeedback.jsx +86 -91
- package/components/CDSImageButton.jsx +9 -5
- package/components/CDSImageButtonGroup.jsx +57 -14
- package/components/CDSNavBar.jsx +5 -8
- package/components/CDSOffer.jsx +3 -5
- package/components/CDSSelect.jsx +40 -31
- package/package.json +1 -1
- package/tokens/CDSsemanticTextStyles.js +1 -3
|
@@ -6,7 +6,6 @@ import { useTheme } from "../context/CDSThemeContext";
|
|
|
6
6
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
7
7
|
|
|
8
8
|
const { height, width } = Dimensions.get("window");
|
|
9
|
-
const isMobile = width <= 878;
|
|
10
9
|
|
|
11
10
|
const bottomSheetRender = ({
|
|
12
11
|
type,
|
|
@@ -21,6 +20,7 @@ const bottomSheetRender = ({
|
|
|
21
20
|
onFinish,
|
|
22
21
|
}, ref) => {
|
|
23
22
|
const { theme } = useTheme();
|
|
23
|
+
const isMobile = theme.isMobile
|
|
24
24
|
const [modalVisible, setModalVisible] = useState(isVisible);
|
|
25
25
|
const [modalOpacity] = useState(new Animated.Value(0));
|
|
26
26
|
const [slidePosition] = useState(new Animated.Value(isMobile ? height : width));
|
package/components/CDSButton.jsx
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Text, StyleSheet, TouchableOpacity } from "react-native";
|
|
3
|
-
import { Dimensions, Platform } from "react-native";
|
|
4
3
|
import { MaterialIcons } from "@expo/vector-icons";
|
|
5
4
|
import { useTheme } from "../context/CDSThemeContext";
|
|
6
5
|
|
|
7
|
-
const { width } = Dimensions.get("window");
|
|
8
|
-
const isMobile = width <= 768
|
|
9
6
|
|
|
10
7
|
export const CDSButton = ({
|
|
11
8
|
label,
|
|
@@ -17,7 +14,8 @@ export const CDSButton = ({
|
|
|
17
14
|
flexend,
|
|
18
15
|
}) => {
|
|
19
16
|
const { theme } = useTheme();
|
|
20
|
-
|
|
17
|
+
const isMobile = theme.isMobile
|
|
18
|
+
|
|
21
19
|
const backgroundColor =
|
|
22
20
|
type === "disabled"
|
|
23
21
|
? theme.surface.action.disabled
|
|
@@ -1,132 +1,127 @@
|
|
|
1
|
-
import { useState, useRef } from "react";
|
|
2
|
-
import { View, Text, StyleSheet, TouchableOpacity, Platform, Animated
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import React, { useState, useRef } from "react";
|
|
2
|
+
import { View, Text, StyleSheet, TouchableOpacity, Platform, Animated } from "react-native";
|
|
5
3
|
import { MaterialIcons } from "@expo/vector-icons";
|
|
6
4
|
import { useTheme } from "../context/CDSThemeContext";
|
|
7
5
|
|
|
8
6
|
export const CDSCardFeedback = ({
|
|
9
|
-
|
|
10
7
|
style,
|
|
11
8
|
title,
|
|
12
9
|
description,
|
|
13
10
|
secondAction,
|
|
14
11
|
onPressSecondAction,
|
|
15
|
-
onClose
|
|
12
|
+
onClose
|
|
13
|
+
}) => {
|
|
16
14
|
const { theme } = useTheme();
|
|
17
|
-
const [isVisible, setIsVisible] = useState(true);
|
|
15
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
18
16
|
const fadeAnim = useRef(new Animated.Value(1)).current;
|
|
19
|
-
// 4. Si isVisible es falso, no renderizamos nada (devolvemos null)
|
|
20
17
|
|
|
21
18
|
const handleClose = () => {
|
|
22
|
-
//
|
|
19
|
+
// IMPORTANTE: useNativeDriver debe ser false para animar maxHeight y padding
|
|
23
20
|
Animated.timing(fadeAnim, {
|
|
24
21
|
toValue: 0,
|
|
25
22
|
duration: 300,
|
|
26
|
-
useNativeDriver:
|
|
23
|
+
useNativeDriver: false
|
|
27
24
|
}).start(() => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
if (!isVisible) return null;
|
|
25
|
+
setIsVisible(false);
|
|
26
|
+
if (onClose) onClose();
|
|
27
|
+
});
|
|
28
|
+
};
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
const maxHeight = fadeAnim.interpolate({
|
|
39
|
-
inputRange: [0, 1],
|
|
40
|
-
outputRange: [0, 1000] // 500 o un valor lo suficientemente alto para tu card
|
|
41
|
-
});
|
|
30
|
+
if (!isVisible) return null;
|
|
42
31
|
|
|
43
32
|
const feedbackStyles = {
|
|
44
|
-
success: {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
bg: theme.surface.feedback.warning,
|
|
52
|
-
border: theme.outline.feedback.warning,
|
|
53
|
-
text: theme.text.feedback.warning,
|
|
54
|
-
icon: 'warning',
|
|
55
|
-
},
|
|
56
|
-
error: {
|
|
57
|
-
bg: theme.surface.feedback.error,
|
|
58
|
-
border: theme.outline.feedback.error,
|
|
59
|
-
text: theme.text.feedback.error,
|
|
60
|
-
icon: 'error',
|
|
61
|
-
},
|
|
62
|
-
neutral: {
|
|
63
|
-
bg: theme.surface.neutral.primaryVariant,
|
|
64
|
-
border: theme.outline.tertiary,
|
|
65
|
-
text: theme.text.neutral.primary,
|
|
66
|
-
icon: 'lightbulb',
|
|
67
|
-
},
|
|
68
|
-
info: {
|
|
69
|
-
bg: theme.surface.feedback.info,
|
|
70
|
-
border: theme.outline.feedback.info,
|
|
71
|
-
text: theme.text.feedback.info,
|
|
72
|
-
icon: 'info',
|
|
73
|
-
}
|
|
74
|
-
}
|
|
33
|
+
success: { bg: theme.surface.feedback.success, border: theme.outline.feedback.success, text: theme.text.feedback.success, icon: 'check-circle' },
|
|
34
|
+
warning: { bg: theme.surface.feedback.warning, border: theme.outline.feedback.warning, text: theme.text.feedback.warning, icon: 'warning' },
|
|
35
|
+
error: { bg: theme.surface.feedback.error, border: theme.outline.feedback.error, text: theme.text.feedback.error, icon: 'error' },
|
|
36
|
+
neutral: { bg: theme.surface.neutral.primaryVariant, border: theme.outline.tertiary, text: theme.text.neutral.primary, icon: 'lightbulb' },
|
|
37
|
+
info: { bg: theme.surface.feedback.info, border: theme.outline.feedback.info, text: theme.text.feedback.info, icon: 'info' }
|
|
38
|
+
};
|
|
39
|
+
|
|
75
40
|
const currentStyle = feedbackStyles[style] || feedbackStyles.info;
|
|
76
41
|
|
|
42
|
+
// Interpolamos todos los valores físicos para que colapsen a 0
|
|
43
|
+
const animatedLayout = {
|
|
44
|
+
maxHeight: fadeAnim.interpolate({
|
|
45
|
+
inputRange: [0, 1],
|
|
46
|
+
outputRange: [0, 600] // Suficiente para el contenido
|
|
47
|
+
}),
|
|
48
|
+
opacity: fadeAnim,
|
|
49
|
+
// Animamos el padding y el margen para que no queden espacios vacíos al cerrar
|
|
50
|
+
paddingVertical: fadeAnim.interpolate({
|
|
51
|
+
inputRange: [0, 1],
|
|
52
|
+
outputRange: [0, theme.space.md]
|
|
53
|
+
}),
|
|
54
|
+
marginBottom: fadeAnim.interpolate({
|
|
55
|
+
inputRange: [0, 1],
|
|
56
|
+
outputRange: [0, theme.space.sm]
|
|
57
|
+
}),
|
|
58
|
+
borderWidth: 1,
|
|
59
|
+
|
|
60
|
+
};
|
|
61
|
+
|
|
77
62
|
return (
|
|
78
|
-
<Animated.View style={[
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
63
|
+
<Animated.View style={[
|
|
64
|
+
styles.container,
|
|
65
|
+
animatedLayout,
|
|
66
|
+
{
|
|
67
|
+
backgroundColor: currentStyle.bg,
|
|
68
|
+
borderColor: currentStyle.border,
|
|
69
|
+
borderRadius: theme.radius.md,
|
|
70
|
+
paddingHorizontal: theme.space.md,
|
|
71
|
+
gap: theme.space.sm,
|
|
72
|
+
overflow: 'hidden', // Evita que el texto se salga mientras se achica
|
|
73
|
+
}
|
|
74
|
+
]}>
|
|
75
|
+
{title && (
|
|
76
|
+
<View style={[styles.titleContainer, { gap: theme.space.sm }]}>
|
|
77
|
+
<MaterialIcons
|
|
78
|
+
name={currentStyle.icon}
|
|
79
|
+
size={theme.typography.icon.md}
|
|
80
|
+
color={currentStyle.text}
|
|
81
|
+
/>
|
|
82
|
+
<Text style={[styles.title, theme.typography.bold.lg, { color: currentStyle.text }]}>
|
|
83
|
+
{title}
|
|
84
|
+
</Text>
|
|
85
|
+
</View>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{description && (
|
|
89
|
+
<Text style={[styles.description, theme.typography.regular.md, { color: currentStyle.text }]}>
|
|
90
|
+
{description}
|
|
91
|
+
</Text>
|
|
92
|
+
)}
|
|
94
93
|
|
|
95
|
-
{title && <View style={[styles.titleContainer, { gap: theme.space.sm }]}>
|
|
96
|
-
<MaterialIcons
|
|
97
|
-
name={currentStyle.icon}
|
|
98
|
-
size={theme.typography.icon.md}
|
|
99
|
-
color={currentStyle.text}
|
|
100
|
-
/>
|
|
101
|
-
<Text style={[styles.title, theme.typography.bold.lg, { color: currentStyle.text }]}>{title}</Text>
|
|
102
|
-
</View>}
|
|
103
|
-
{description && <Text style={[styles.description, theme.typography.regular.md, { color: currentStyle.text }]}>{description}</Text>}
|
|
104
94
|
<View style={[styles.actionsContainer, { gap: theme.space.sm }]}>
|
|
105
|
-
{secondAction &&
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
95
|
+
{secondAction && (
|
|
96
|
+
<TouchableOpacity onPress={onPressSecondAction}>
|
|
97
|
+
<Text style={[theme.typography.bold.sm, { color: currentStyle.text }]}>
|
|
98
|
+
{secondAction}
|
|
99
|
+
</Text>
|
|
100
|
+
</TouchableOpacity>
|
|
101
|
+
)}
|
|
102
|
+
<TouchableOpacity onPress={handleClose}>
|
|
103
|
+
<Text style={[theme.typography.bold.sm, { color: currentStyle.text }]}>
|
|
104
|
+
{'Cerrar'}
|
|
105
|
+
</Text>
|
|
106
|
+
</TouchableOpacity>
|
|
107
|
+
</View>
|
|
109
108
|
</Animated.View>
|
|
110
|
-
)
|
|
111
|
-
}
|
|
109
|
+
);
|
|
110
|
+
};
|
|
112
111
|
|
|
113
112
|
const styles = StyleSheet.create({
|
|
114
113
|
container: {
|
|
115
114
|
width: '100%',
|
|
116
115
|
justifyContent: 'space-between',
|
|
117
|
-
borderWidth: 1,
|
|
118
116
|
},
|
|
119
|
-
|
|
120
117
|
titleContainer: {
|
|
121
118
|
flexDirection: 'row',
|
|
122
119
|
alignItems: 'center',
|
|
123
120
|
},
|
|
124
|
-
description: {
|
|
125
|
-
},
|
|
121
|
+
description: {},
|
|
126
122
|
actionsContainer: {
|
|
127
123
|
flexDirection: 'row',
|
|
128
124
|
width: '100%',
|
|
129
125
|
justifyContent: 'flex-end',
|
|
130
|
-
|
|
131
126
|
},
|
|
132
|
-
});
|
|
127
|
+
});
|
|
@@ -8,6 +8,7 @@ import { useTheme } from "../context/CDSThemeContext";
|
|
|
8
8
|
export const CDSImageButton = ({ object, isActive, onPress }) => { // 👈 Añadimos isActive
|
|
9
9
|
const { theme } = useTheme();
|
|
10
10
|
|
|
11
|
+
console.log(theme.typography)
|
|
11
12
|
return (
|
|
12
13
|
<TouchableOpacity
|
|
13
14
|
onPress={onPress}
|
|
@@ -22,7 +23,7 @@ export const CDSImageButton = ({ object, isActive, onPress }) => { // 👈 Añad
|
|
|
22
23
|
// borderColor: isActive
|
|
23
24
|
// ? theme.outline.neutral.primary // Borde de color de marca si está activo
|
|
24
25
|
// : theme.outline.neutral.primary,
|
|
25
|
-
borderRadius: theme.radius.
|
|
26
|
+
borderRadius: theme.radius.md,
|
|
26
27
|
// Si está activo, quitamos la sombra o usamos una más pequeña (md)
|
|
27
28
|
...(!isActive && theme.shadows.lg),
|
|
28
29
|
// Si quieres que se vea "hundido", podrías bajar la escala
|
|
@@ -34,11 +35,13 @@ export const CDSImageButton = ({ object, isActive, onPress }) => { // 👈 Añad
|
|
|
34
35
|
source={object.image}
|
|
35
36
|
resizeMode="cover"
|
|
36
37
|
style={{
|
|
38
|
+
flexGrow: 1,
|
|
37
39
|
width: "100%",
|
|
38
|
-
height:
|
|
40
|
+
height: 80,
|
|
41
|
+
borderRadius: theme.radius.sm,
|
|
39
42
|
}}
|
|
40
43
|
/>
|
|
41
|
-
<Text style={
|
|
44
|
+
<Text style={isActive ? theme.typography.semiBold.sm : theme.typography.regular.sm }>
|
|
42
45
|
{object.label}
|
|
43
46
|
</Text>
|
|
44
47
|
</TouchableOpacity>
|
|
@@ -52,8 +55,9 @@ const styles = StyleSheet.create ({
|
|
|
52
55
|
mainContainer:{
|
|
53
56
|
alignItems: 'center',
|
|
54
57
|
flexGrow: 1,
|
|
55
|
-
maxWidth:
|
|
56
|
-
|
|
58
|
+
maxWidth: 160,
|
|
59
|
+
minWidth: 120,
|
|
60
|
+
height: 'auto'
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
})
|
|
@@ -1,28 +1,59 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { View, Animated, StyleSheet, ScrollView } from 'react-native';
|
|
2
3
|
import { CDSImageButton } from './CDSImageButton';
|
|
3
|
-
import {
|
|
4
|
+
import { useTheme } from "../context/CDSThemeContext";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
export const CDSImageButtonGroup = ({ array, onSelect, isCentered }) => {
|
|
7
|
+
const { theme } = useTheme();
|
|
8
|
+
const isMobile = theme.isMobile
|
|
7
9
|
const [selectedId, setSelectedId] = useState(null);
|
|
10
|
+
const animatedValue = useRef(new Animated.Value(0)).current;
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
Animated.timing(animatedValue, {
|
|
14
|
+
toValue: 1,
|
|
15
|
+
duration: 400,
|
|
16
|
+
useNativeDriver: false
|
|
17
|
+
}).start();
|
|
18
|
+
}, []);
|
|
8
19
|
|
|
9
20
|
const handlePress = (id) => {
|
|
10
21
|
const nextId = id === selectedId ? null : id;
|
|
11
22
|
setSelectedId(nextId);
|
|
12
|
-
|
|
13
|
-
// Avisamos al padre qué objeto se seleccionó (o null si se deseleccionó)
|
|
14
23
|
if (onSelect) {
|
|
15
24
|
const selectedItem = array.find(item => item.id === nextId);
|
|
16
25
|
onSelect(selectedItem || null);
|
|
17
26
|
}
|
|
18
27
|
};
|
|
19
28
|
|
|
29
|
+
const animatedLayout = {
|
|
30
|
+
maxHeight: animatedValue.interpolate({
|
|
31
|
+
inputRange: [0, 1],
|
|
32
|
+
outputRange: [0, 250]
|
|
33
|
+
}),
|
|
34
|
+
opacity: animatedValue,
|
|
35
|
+
};
|
|
36
|
+
|
|
20
37
|
return (
|
|
21
|
-
<View style={
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
<Animated.View style={[
|
|
39
|
+
animatedLayout,
|
|
40
|
+
{ width: '100%' } // Asegura que el contenedor ocupe todo el ancho disponible
|
|
41
|
+
]}>
|
|
42
|
+
<ScrollView
|
|
43
|
+
horizontal={true}
|
|
44
|
+
showsHorizontalScrollIndicator={false}
|
|
45
|
+
style={styles.scrollView}
|
|
46
|
+
contentContainerStyle={[
|
|
47
|
+
styles.scrollContent,
|
|
48
|
+
{
|
|
49
|
+
justifyContent: (isCentered ? 'center' : 'flex-start'),
|
|
50
|
+
|
|
51
|
+
gap: theme.space.sm,
|
|
52
|
+
padding: theme.space.md,
|
|
53
|
+
width: (isMobile ? 'auto' : '100%')
|
|
54
|
+
}
|
|
55
|
+
]}
|
|
56
|
+
>
|
|
26
57
|
{array.map((item) => (
|
|
27
58
|
<CDSImageButton
|
|
28
59
|
key={item.id}
|
|
@@ -31,7 +62,19 @@ export const CDSImageButtonGroup = ({ array, onSelect }) => {
|
|
|
31
62
|
onPress={() => handlePress(item.id)}
|
|
32
63
|
/>
|
|
33
64
|
))}
|
|
34
|
-
</
|
|
35
|
-
</View>
|
|
65
|
+
</ScrollView>
|
|
66
|
+
</Animated.View>
|
|
36
67
|
);
|
|
37
|
-
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const styles = StyleSheet.create({
|
|
71
|
+
scrollView: {
|
|
72
|
+
width: '100%',
|
|
73
|
+
overflow: 'visible',
|
|
74
|
+
},
|
|
75
|
+
scrollContent: {
|
|
76
|
+
width: '100%',
|
|
77
|
+
flexDirection: 'row',
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
}
|
|
80
|
+
});
|
package/components/CDSNavBar.jsx
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
|
|
2
|
-
import { View, Text,
|
|
2
|
+
import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
import { MaterialIcons } from "@expo/vector-icons";
|
|
6
6
|
import { useTheme } from "../context/CDSThemeContext";
|
|
7
|
-
import { getSemanticTextStyles } from "../tokens/CDSsemanticTextStyles";
|
|
8
7
|
import { useNavigation } from "@react-navigation/native";
|
|
9
8
|
|
|
10
|
-
const { height, width } = Dimensions.get("window");
|
|
11
|
-
|
|
12
|
-
const isMobile = width <= 878;
|
|
13
9
|
export const CDSNavBar = ({
|
|
14
|
-
|
|
10
|
+
|
|
15
11
|
type,
|
|
16
12
|
isExpanded,
|
|
17
13
|
hasClose,
|
|
18
14
|
title,
|
|
19
15
|
hasBack, }) => {
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
|
|
17
|
+
const { theme, isDarkMode } = useTheme();
|
|
18
|
+
const isMobile = theme.isMobile
|
|
22
19
|
|
|
23
20
|
const navigation = useNavigation();
|
|
24
21
|
|
package/components/CDSOffer.jsx
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
|
|
2
2
|
import { View, Text, StyleSheet, } from "react-native";
|
|
3
3
|
import { useTheme } from "../context/CDSThemeContext";
|
|
4
|
-
import { Dimensions } from "react-native";
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
const { width } = Dimensions.get("window");
|
|
8
|
-
const isMobile = width <= 768
|
|
9
6
|
|
|
10
7
|
export const CDSOffer = ({ description, minValue, maxValue }) => {
|
|
11
|
-
|
|
8
|
+
|
|
12
9
|
const { theme } = useTheme();
|
|
13
|
-
|
|
10
|
+
const isMobile = theme.isMobile
|
|
11
|
+
|
|
14
12
|
return (
|
|
15
13
|
<View style={[styles.mainContainer, { gap: theme.space.md }]}>
|
|
16
14
|
<Text style={theme.typography.regular.md}>Por tu</Text>
|
package/components/CDSSelect.jsx
CHANGED
|
@@ -13,7 +13,6 @@ const SelectOption = ({ item, isSelected, onSelect, theme }) => {
|
|
|
13
13
|
return (
|
|
14
14
|
<Pressable
|
|
15
15
|
onPress={() => onSelect(item)}
|
|
16
|
-
// Eventos de Hover para Web
|
|
17
16
|
onMouseEnter={() => Platform.OS === 'web' && setIsHovered(true)}
|
|
18
17
|
onMouseLeave={() => Platform.OS === 'web' && setIsHovered(false)}
|
|
19
18
|
style={({ pressed }) => [
|
|
@@ -45,12 +44,8 @@ const SelectOption = ({ item, isSelected, onSelect, theme }) => {
|
|
|
45
44
|
export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null }) => {
|
|
46
45
|
const { theme } = useTheme();
|
|
47
46
|
const [isOpen, setIsOpen] = useState(false);
|
|
48
|
-
const [dropdownTop, setDropdownTop] = useState(0);
|
|
49
|
-
const [dropdownWidth, setDropdownWidth] = useState(0);
|
|
50
|
-
const [dropdownLeft, setDropdownLeft] = useState(0);
|
|
51
|
-
|
|
52
|
-
const containerRef = useRef(null);
|
|
53
47
|
const animatedValue = useRef(new Animated.Value(0)).current;
|
|
48
|
+
const containerRef = useRef(null);
|
|
54
49
|
|
|
55
50
|
const [selectedItem, setSelectedItem] = useState(
|
|
56
51
|
options.find(opt => opt.value === selectedValue) || null
|
|
@@ -63,36 +58,49 @@ export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null
|
|
|
63
58
|
|
|
64
59
|
const toggleDropdown = () => {
|
|
65
60
|
if (isOpen) {
|
|
66
|
-
Animated.timing(animatedValue, {
|
|
67
|
-
|
|
61
|
+
Animated.timing(animatedValue, {
|
|
62
|
+
toValue: 0,
|
|
63
|
+
duration: 250,
|
|
64
|
+
useNativeDriver: false
|
|
65
|
+
}).start(() => setIsOpen(false));
|
|
68
66
|
} else {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
setIsOpen(true);
|
|
68
|
+
requestAnimationFrame(() => {
|
|
69
|
+
Animated.timing(animatedValue, {
|
|
70
|
+
toValue: 1,
|
|
71
|
+
duration: 300,
|
|
72
|
+
useNativeDriver: false
|
|
73
|
+
}).start();
|
|
75
74
|
});
|
|
76
75
|
}
|
|
77
76
|
};
|
|
78
77
|
|
|
79
|
-
const handleSelect = (item) => {
|
|
80
|
-
// 1. Actualizamos localmente PRIMERO para que el cambio sea instantáneo
|
|
78
|
+
const handleSelect = (item) => {
|
|
81
79
|
setSelectedItem(item);
|
|
82
|
-
|
|
83
|
-
// 2. Cerramos el menú
|
|
84
80
|
toggleDropdown();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
if (onSelect) onSelect(item.value);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Estructura idéntica a CardFeedback para mantener consistencia
|
|
85
|
+
const animatedLayout = {
|
|
86
|
+
maxHeight: animatedValue.interpolate({
|
|
87
|
+
inputRange: [0, 1],
|
|
88
|
+
outputRange: [0, 240]
|
|
89
|
+
}),
|
|
90
|
+
opacity: animatedValue,
|
|
91
|
+
marginTop: animatedValue.interpolate({
|
|
92
|
+
inputRange: [0, 1],
|
|
93
|
+
outputRange: [0, theme.space.sm]
|
|
94
|
+
}),
|
|
91
95
|
};
|
|
92
96
|
|
|
93
97
|
return (
|
|
94
98
|
<View style={styles.mainContainer} ref={containerRef} collapsable={false}>
|
|
95
|
-
{label &&
|
|
99
|
+
{label && (
|
|
100
|
+
<Text style={[theme.typography.label, { color: theme.text.neutral.primary, marginBottom: theme.space.sm }]}>
|
|
101
|
+
{label}
|
|
102
|
+
</Text>
|
|
103
|
+
)}
|
|
96
104
|
|
|
97
105
|
<TouchableOpacity
|
|
98
106
|
activeOpacity={0.8}
|
|
@@ -114,15 +122,15 @@ const handleSelect = (item) => {
|
|
|
114
122
|
</TouchableOpacity>
|
|
115
123
|
|
|
116
124
|
{isOpen && (
|
|
117
|
-
<View
|
|
125
|
+
<Animated.View
|
|
118
126
|
style={[
|
|
119
127
|
styles.dropdownInline,
|
|
128
|
+
animatedLayout, // Aplicamos el objeto de animación aquí
|
|
120
129
|
{
|
|
121
|
-
marginTop: theme.space.sm,
|
|
122
130
|
backgroundColor: theme.surface.neutral.primary,
|
|
123
131
|
borderRadius: theme.radius.sm,
|
|
124
132
|
borderColor: theme.outline.neutral.primary,
|
|
125
|
-
|
|
133
|
+
overflow: 'hidden',
|
|
126
134
|
}
|
|
127
135
|
]}
|
|
128
136
|
>
|
|
@@ -137,10 +145,9 @@ const handleSelect = (item) => {
|
|
|
137
145
|
theme={theme}
|
|
138
146
|
/>
|
|
139
147
|
)}
|
|
140
|
-
// Importante para que el scroll funcione dentro de la lista expandida
|
|
141
148
|
nestedScrollEnabled={true}
|
|
142
149
|
/>
|
|
143
|
-
</View>
|
|
150
|
+
</Animated.View>
|
|
144
151
|
)}
|
|
145
152
|
</View>
|
|
146
153
|
);
|
|
@@ -149,7 +156,9 @@ const handleSelect = (item) => {
|
|
|
149
156
|
const styles = StyleSheet.create({
|
|
150
157
|
mainContainer: { width: '100%' },
|
|
151
158
|
inputContainer: { height: 48, borderWidth: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
|
152
|
-
dropdownInline: {
|
|
159
|
+
dropdownInline: {
|
|
160
|
+
borderWidth: 1,
|
|
161
|
+
},
|
|
153
162
|
optionItem: { height: 48, justifyContent: 'center' },
|
|
154
163
|
optionContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }
|
|
155
164
|
});
|
package/package.json
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { useFonts, Inter_400Regular, Inter_600SemiBold, Inter_700Bold } from "@expo-google-fonts/inter";
|
|
2
|
-
|
|
3
1
|
import { CDSprimitiveFontValues } from "./CDSprimitiveTokens";
|
|
4
2
|
|
|
5
3
|
|
|
@@ -67,7 +65,7 @@ export const getSemanticTextStyles = (theme, isMobile) => {
|
|
|
67
65
|
sm: {
|
|
68
66
|
fontFamily: CDSprimitiveFontValues.family.interRegular,
|
|
69
67
|
fontSize: getVal('size', 'sm'),
|
|
70
|
-
lineHeight: getVal('lineHeight', '
|
|
68
|
+
lineHeight: getVal('lineHeight', 'sm'), // Usando xs ya que sm no estaba en tu lista
|
|
71
69
|
color: theme.text.neutral.primary,
|
|
72
70
|
},
|
|
73
71
|
xs: {
|