cdslibrary 1.2.89 → 1.2.90
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/CDSInput.jsx +1 -1
- package/components/CDSSelect.jsx +108 -109
- package/package.json +1 -1
package/components/CDSInput.jsx
CHANGED
|
@@ -80,7 +80,7 @@ export const CDSInput = ({ label, type, keyboard, placeholder, value, onChangeTe
|
|
|
80
80
|
return {
|
|
81
81
|
borderColor,
|
|
82
82
|
borderWidth: 1 + (flashValue.value * 2),
|
|
83
|
-
transform: [{ scale: 1 + (flashValue.value * 0.
|
|
83
|
+
transform: [{ scale: 1 + (flashValue.value * 0.02) }]
|
|
84
84
|
};
|
|
85
85
|
});
|
|
86
86
|
|
package/components/CDSSelect.jsx
CHANGED
|
@@ -1,151 +1,122 @@
|
|
|
1
|
-
import React, { useState,
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
2
|
import {
|
|
3
3
|
View, Text, TouchableOpacity, StyleSheet, ScrollView,
|
|
4
|
-
|
|
4
|
+
Pressable, Modal, SafeAreaView
|
|
5
5
|
} from "react-native";
|
|
6
6
|
import { MaterialIcons } from "@expo/vector-icons";
|
|
7
7
|
import { useTheme } from "../context/CDSThemeContext";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const SelectOption = ({ item, isSelected, onSelect, theme }) => {
|
|
11
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<Pressable
|
|
15
|
-
onPress={() => onSelect(item)}
|
|
16
|
-
onMouseEnter={() => Platform.OS === 'web' && setIsHovered(true)}
|
|
17
|
-
onMouseLeave={() => Platform.OS === 'web' && setIsHovered(false)}
|
|
18
|
-
style={({ pressed }) => [
|
|
19
|
-
styles.optionItem,
|
|
20
|
-
{
|
|
21
|
-
paddingHorizontal: theme.space.sm,
|
|
22
|
-
backgroundColor: (pressed || isHovered)
|
|
23
|
-
? theme.surface.neutral.secondary
|
|
24
|
-
: isSelected ? theme.surface.neutral.tertiary : 'transparent'
|
|
25
|
-
}
|
|
26
|
-
]}
|
|
27
|
-
>
|
|
28
|
-
<View style={styles.optionContent}>
|
|
29
|
-
<Text style={[
|
|
30
|
-
theme.typography.regular.sm,
|
|
31
|
-
{
|
|
32
|
-
color: theme.text.neutral.primary,
|
|
33
|
-
fontWeight: isSelected ? '700' : '400'
|
|
34
|
-
}
|
|
35
|
-
]}>
|
|
36
|
-
{item.label}
|
|
37
|
-
</Text>
|
|
38
|
-
{isSelected && <MaterialIcons name="check" size={18} color={theme.text.neutral.primary} />}
|
|
39
|
-
</View>
|
|
40
|
-
</Pressable>
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null }) => {
|
|
9
|
+
export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null, placeholder = 'Selecciona una opción' }) => {
|
|
45
10
|
const { theme } = useTheme();
|
|
46
|
-
const [
|
|
47
|
-
const animatedValue = useRef(new Animated.Value(0)).current;
|
|
48
|
-
const containerRef = useRef(null);
|
|
49
|
-
|
|
11
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
50
12
|
const [selectedItem, setSelectedItem] = useState(
|
|
51
13
|
options.find(opt => opt.value === selectedValue) || null
|
|
52
14
|
);
|
|
53
15
|
|
|
16
|
+
// Altura fija por cada fila (puedes ajustarla según tu diseño)
|
|
17
|
+
const ITEM_HEIGHT = 52;
|
|
18
|
+
// Mostramos 7.5 opciones para que la última se vea cortada y sugiera scroll
|
|
19
|
+
const MAX_VISIBLE_OPTIONS = 7.5;
|
|
20
|
+
const MAX_HEIGHT = ITEM_HEIGHT * MAX_VISIBLE_OPTIONS;
|
|
21
|
+
|
|
54
22
|
useEffect(() => {
|
|
55
23
|
const found = options.find(opt => opt.value === selectedValue);
|
|
56
24
|
setSelectedItem(found || null);
|
|
57
25
|
}, [selectedValue, options]);
|
|
58
26
|
|
|
59
|
-
const toggleDropdown = () => {
|
|
60
|
-
if (isOpen) {
|
|
61
|
-
Animated.timing(animatedValue, {
|
|
62
|
-
toValue: 0,
|
|
63
|
-
duration: 250,
|
|
64
|
-
useNativeDriver: false
|
|
65
|
-
}).start(() => setIsOpen(false));
|
|
66
|
-
} else {
|
|
67
|
-
setIsOpen(true);
|
|
68
|
-
requestAnimationFrame(() => {
|
|
69
|
-
Animated.timing(animatedValue, {
|
|
70
|
-
toValue: 1,
|
|
71
|
-
duration: 300,
|
|
72
|
-
useNativeDriver: false
|
|
73
|
-
}).start();
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
27
|
const handleSelect = (item) => {
|
|
79
28
|
setSelectedItem(item);
|
|
80
|
-
|
|
29
|
+
setModalVisible(false);
|
|
81
30
|
if (onSelect) onSelect(item.value);
|
|
82
31
|
};
|
|
83
32
|
|
|
84
|
-
const animatedLayout = {
|
|
85
|
-
maxHeight: animatedValue.interpolate({
|
|
86
|
-
inputRange: [0, 1],
|
|
87
|
-
outputRange: [0, 240]
|
|
88
|
-
}),
|
|
89
|
-
opacity: animatedValue,
|
|
90
|
-
marginTop: animatedValue.interpolate({
|
|
91
|
-
inputRange: [0, 1],
|
|
92
|
-
outputRange: [0, theme.space.sm]
|
|
93
|
-
}),
|
|
94
|
-
};
|
|
95
|
-
|
|
96
33
|
return (
|
|
97
|
-
<View style={styles.mainContainer}
|
|
34
|
+
<View style={styles.mainContainer}>
|
|
98
35
|
{label && (
|
|
99
36
|
<Text style={[theme.typography.label, { color: theme.text.neutral.primary, marginBottom: theme.space.sm }]}>
|
|
100
37
|
{label}
|
|
101
38
|
</Text>
|
|
102
39
|
)}
|
|
103
40
|
|
|
41
|
+
{/* Trigger del Select */}
|
|
104
42
|
<TouchableOpacity
|
|
105
|
-
activeOpacity={0.
|
|
43
|
+
activeOpacity={0.7}
|
|
106
44
|
style={[
|
|
107
45
|
styles.inputContainer,
|
|
108
46
|
{
|
|
109
47
|
backgroundColor: theme.surface.neutral.primary,
|
|
110
|
-
borderColor:
|
|
48
|
+
borderColor: theme.outline.neutral.primary,
|
|
111
49
|
borderRadius: theme.radius.sm,
|
|
112
50
|
paddingHorizontal: theme.space.sm
|
|
113
51
|
}
|
|
114
52
|
]}
|
|
115
|
-
onPress={
|
|
53
|
+
onPress={() => setModalVisible(true)}
|
|
116
54
|
>
|
|
117
55
|
<Text style={selectedItem ? theme.typography.inputText.value : theme.typography.inputText.placeholder}>
|
|
118
|
-
{selectedItem ? selectedItem.label :
|
|
56
|
+
{selectedItem ? selectedItem.label : placeholder}
|
|
119
57
|
</Text>
|
|
120
|
-
<MaterialIcons name=
|
|
58
|
+
<MaterialIcons name="expand-more" size={24} color={theme.text.neutral.primary} />
|
|
121
59
|
</TouchableOpacity>
|
|
122
60
|
|
|
123
|
-
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
]}
|
|
61
|
+
{/* Selector Nativo / Modal */}
|
|
62
|
+
<Modal
|
|
63
|
+
visible={modalVisible}
|
|
64
|
+
transparent={true}
|
|
65
|
+
animationType="fade"
|
|
66
|
+
onRequestClose={() => setModalVisible(false)}
|
|
67
|
+
>
|
|
68
|
+
<Pressable
|
|
69
|
+
style={styles.modalOverlay}
|
|
70
|
+
onPress={() => setModalVisible(false)}
|
|
135
71
|
>
|
|
136
|
-
<
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
72
|
+
<SafeAreaView style={styles.modalContent}>
|
|
73
|
+
<View style={[
|
|
74
|
+
styles.optionsContainer,
|
|
75
|
+
{
|
|
76
|
+
backgroundColor: theme.surface.neutral.primary,
|
|
77
|
+
borderRadius: theme.radius.md,
|
|
78
|
+
maxHeight: MAX_HEIGHT + 60 // +60 para compensar el header del modal
|
|
79
|
+
}
|
|
80
|
+
]}>
|
|
81
|
+
{/* Header del Modal */}
|
|
82
|
+
<View style={[styles.modalHeader, { borderBottomColor: theme.outline.neutral.primary }]}>
|
|
83
|
+
<Text style={theme.typography.semiBold.md}>{'Selecciona una opción'}</Text>
|
|
84
|
+
<TouchableOpacity onPress={() => setModalVisible(false)} hitSlop={{top: 10, bottom: 10, left: 10, right: 10}}>
|
|
85
|
+
<MaterialIcons name="close" size={24} color={theme.text.neutral.primary} />
|
|
86
|
+
</TouchableOpacity>
|
|
87
|
+
</View>
|
|
88
|
+
|
|
89
|
+
{/* Lista Scrolleable */}
|
|
90
|
+
<ScrollView
|
|
91
|
+
bounces={false}
|
|
92
|
+
showsVerticalScrollIndicator={true}
|
|
93
|
+
nestedScrollEnabled={true}
|
|
94
|
+
>
|
|
95
|
+
{options.map((item, index) => (
|
|
96
|
+
<TouchableOpacity
|
|
97
|
+
key={item.value?.toString() || index.toString()}
|
|
98
|
+
onPress={() => handleSelect(item)}
|
|
99
|
+
style={[
|
|
100
|
+
styles.optionItem,
|
|
101
|
+
{
|
|
102
|
+
height: ITEM_HEIGHT,
|
|
103
|
+
backgroundColor: selectedItem?.value === item.value ? theme.surface.neutral.secondary : 'transparent'
|
|
104
|
+
}
|
|
105
|
+
]}
|
|
106
|
+
>
|
|
107
|
+
<Text style={[theme.typography.regular.md, { color: theme.text.neutral.primary }]}>
|
|
108
|
+
{item.label}
|
|
109
|
+
</Text>
|
|
110
|
+
{selectedItem?.value === item.value && (
|
|
111
|
+
<MaterialIcons name="check" size={20} color={theme.text.neutral.primary} />
|
|
112
|
+
)}
|
|
113
|
+
</TouchableOpacity>
|
|
114
|
+
))}
|
|
115
|
+
</ScrollView>
|
|
116
|
+
</View>
|
|
117
|
+
</SafeAreaView>
|
|
118
|
+
</Pressable>
|
|
119
|
+
</Modal>
|
|
149
120
|
</View>
|
|
150
121
|
);
|
|
151
122
|
};
|
|
@@ -153,9 +124,37 @@ export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null
|
|
|
153
124
|
const styles = StyleSheet.create({
|
|
154
125
|
mainContainer: { width: '100%' },
|
|
155
126
|
inputContainer: { width: '100%', height: 48, borderWidth: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
|
156
|
-
|
|
157
|
-
|
|
127
|
+
modalOverlay: {
|
|
128
|
+
flex: 1,
|
|
129
|
+
backgroundColor: 'rgba(0,0,0,0.6)', // Un poco más oscuro para mejor enfoque
|
|
130
|
+
justifyContent: 'center',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
padding: 24
|
|
133
|
+
},
|
|
134
|
+
modalContent: {
|
|
135
|
+
width: '100%',
|
|
136
|
+
maxWidth: 400,
|
|
137
|
+
},
|
|
138
|
+
optionsContainer: {
|
|
139
|
+
width: '100%',
|
|
140
|
+
overflow: 'hidden',
|
|
141
|
+
elevation: 5,
|
|
142
|
+
shadowColor: '#000',
|
|
143
|
+
shadowOffset: { width: 0, height: 4 },
|
|
144
|
+
shadowOpacity: 0.3,
|
|
145
|
+
shadowRadius: 4.65,
|
|
146
|
+
},
|
|
147
|
+
modalHeader: {
|
|
148
|
+
flexDirection: 'row',
|
|
149
|
+
justifyContent: 'space-between',
|
|
150
|
+
alignItems: 'center',
|
|
151
|
+
padding: 16,
|
|
152
|
+
borderBottomWidth: 1,
|
|
158
153
|
},
|
|
159
|
-
optionItem: {
|
|
160
|
-
|
|
154
|
+
optionItem: {
|
|
155
|
+
paddingHorizontal: 16,
|
|
156
|
+
flexDirection: 'row',
|
|
157
|
+
justifyContent: 'space-between',
|
|
158
|
+
alignItems: 'center',
|
|
159
|
+
}
|
|
161
160
|
});
|