cdslibrary 1.2.22 → 1.2.24
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/CDSCardFeedback.jsx +3 -3
- package/components/CDSSelect.jsx +117 -97
- package/package.json +1 -1
|
@@ -81,14 +81,14 @@ export const CDSCardFeedback = ({
|
|
|
81
81
|
}]
|
|
82
82
|
}]}>
|
|
83
83
|
|
|
84
|
-
<View style={[styles.titleContainer, { gap: theme.space.xs }]}>
|
|
84
|
+
{title && <View style={[styles.titleContainer, { gap: theme.space.xs }]}>
|
|
85
85
|
<MaterialIcons
|
|
86
86
|
name={currentStyle.icon}
|
|
87
87
|
size={theme.typography.icon.md}
|
|
88
88
|
color={currentStyle.text}
|
|
89
89
|
/>
|
|
90
|
-
<Text style={[styles.title, theme.typography.bold.lg, { color: currentStyle.text }]}>{title
|
|
91
|
-
</View>
|
|
90
|
+
<Text style={[styles.title, theme.typography.bold.lg, { color: currentStyle.text }]}>{title}</Text>
|
|
91
|
+
</View>}
|
|
92
92
|
{description && <Text style={[styles.description, theme.typography.regular.md, { color: currentStyle.text }]}>{description}</Text>}
|
|
93
93
|
<View style={[styles.actionsContainer, { gap: theme.space.xs }]}>
|
|
94
94
|
{secondAction && <TouchableOpacity onPress={onPressSecondAction}>{<Text style={[theme.typography.bold.sm, { color: currentStyle.text }]}>{secondAction}</Text>}</TouchableOpacity>}
|
package/components/CDSSelect.jsx
CHANGED
|
@@ -1,133 +1,153 @@
|
|
|
1
|
-
import React, { useState, useRef } from "react";
|
|
2
|
-
import {
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
View, Text, TouchableOpacity, StyleSheet, FlatList,
|
|
4
|
+
Animated, Modal, Pressable, Dimensions, Platform
|
|
5
|
+
} from "react-native";
|
|
3
6
|
import { MaterialIcons } from "@expo/vector-icons";
|
|
4
7
|
import { useTheme } from "../context/CDSThemeContext";
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
// Componente Interno para la Opción con soporte de Hover
|
|
10
|
+
const SelectOption = ({ item, isSelected, onSelect, theme }) => {
|
|
11
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Pressable
|
|
15
|
+
onPress={() => onSelect(item)}
|
|
16
|
+
// Eventos de Hover para Web
|
|
17
|
+
onMouseEnter={() => Platform.OS === 'web' && setIsHovered(true)}
|
|
18
|
+
onMouseLeave={() => Platform.OS === 'web' && setIsHovered(false)}
|
|
19
|
+
style={({ pressed }) => [
|
|
20
|
+
styles.optionItem,
|
|
21
|
+
{
|
|
22
|
+
paddingHorizontal: theme.space.xs,
|
|
23
|
+
backgroundColor: (pressed || isHovered)
|
|
24
|
+
? theme.surface.neutral.secondary
|
|
25
|
+
: isSelected ? theme.surface.neutral.tertiary : 'transparent'
|
|
26
|
+
}
|
|
27
|
+
]}
|
|
28
|
+
>
|
|
29
|
+
<View style={styles.optionContent}>
|
|
30
|
+
<Text style={[
|
|
31
|
+
theme.typography.regular.sm,
|
|
32
|
+
{
|
|
33
|
+
color: theme.text.neutral.primary,
|
|
34
|
+
fontWeight: isSelected ? '700' : '400'
|
|
35
|
+
}
|
|
36
|
+
]}>
|
|
37
|
+
{item.label}
|
|
38
|
+
</Text>
|
|
39
|
+
{isSelected && <MaterialIcons name="check" size={18} color={theme.text.neutral.primary} />}
|
|
40
|
+
</View>
|
|
41
|
+
</Pressable>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const CDSSelect = ({ label, options = [], onSelect, placeholder, selectedValue = null }) => {
|
|
7
46
|
const { theme } = useTheme();
|
|
8
47
|
const [isOpen, setIsOpen] = useState(false);
|
|
9
|
-
const [
|
|
10
|
-
|
|
11
|
-
|
|
48
|
+
const [dropdownTop, setDropdownTop] = useState(0);
|
|
49
|
+
const [dropdownWidth, setDropdownWidth] = useState(0);
|
|
50
|
+
const [dropdownLeft, setDropdownLeft] = useState(0);
|
|
51
|
+
|
|
52
|
+
const containerRef = useRef(null);
|
|
12
53
|
const animatedValue = useRef(new Animated.Value(0)).current;
|
|
13
54
|
|
|
55
|
+
const [selectedItem, setSelectedItem] = useState(
|
|
56
|
+
options.find(opt => opt.value === selectedValue) || null
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const found = options.find(opt => opt.value === selectedValue);
|
|
61
|
+
setSelectedItem(found || null);
|
|
62
|
+
}, [selectedValue, options]);
|
|
63
|
+
|
|
14
64
|
const toggleDropdown = () => {
|
|
15
65
|
if (isOpen) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
toValue: 0,
|
|
19
|
-
duration: 200,
|
|
20
|
-
useNativeDriver: true,
|
|
21
|
-
}).start(() => setIsOpen(false));
|
|
66
|
+
Animated.timing(animatedValue, { toValue: 0, duration: 200, useNativeDriver: true })
|
|
67
|
+
.start(() => setIsOpen(false));
|
|
22
68
|
} else {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
useNativeDriver: true
|
|
29
|
-
})
|
|
69
|
+
containerRef.current.measure((fx, fy, width, height, px, py) => {
|
|
70
|
+
setDropdownTop(py + height);
|
|
71
|
+
setDropdownWidth(width);
|
|
72
|
+
setDropdownLeft(px); // Usamos px para alineación exacta en X
|
|
73
|
+
setIsOpen(true);
|
|
74
|
+
Animated.timing(animatedValue, { toValue: 1, duration: 300, useNativeDriver: true }).start();
|
|
75
|
+
});
|
|
30
76
|
}
|
|
31
77
|
};
|
|
32
78
|
|
|
33
79
|
const handleSelect = (item) => {
|
|
34
|
-
|
|
80
|
+
setSelectedItem(item);
|
|
35
81
|
toggleDropdown();
|
|
36
82
|
if (onSelect) onSelect(item.value);
|
|
37
83
|
};
|
|
38
84
|
|
|
39
|
-
// Interpolación para el desplazamiento y la opacidad
|
|
40
|
-
const dropdownStyle = {
|
|
41
|
-
opacity: animatedValue,
|
|
42
|
-
transform: [
|
|
43
|
-
{
|
|
44
|
-
translateY: animatedValue.interpolate({
|
|
45
|
-
inputRange: [0, 1],
|
|
46
|
-
outputRange: [-10, 0], // Sube 10px mientras desaparece
|
|
47
|
-
}),
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
};
|
|
51
85
|
return (
|
|
52
|
-
<View style={
|
|
53
|
-
{label && <Text style={[theme.typography.label, { color: theme.text.neutral.primary }]}>{label}</Text>}
|
|
86
|
+
<View style={styles.mainContainer} ref={containerRef} collapsable={false}>
|
|
87
|
+
{label && <Text style={[theme.typography.label, { color: theme.text.neutral.primary, marginBottom: theme.space.xs }]}>{label}</Text>}
|
|
54
88
|
|
|
55
89
|
<TouchableOpacity
|
|
56
90
|
activeOpacity={0.8}
|
|
57
91
|
style={[
|
|
58
92
|
styles.inputContainer,
|
|
59
|
-
|
|
93
|
+
{
|
|
94
|
+
backgroundColor: theme.surface.neutral.primary,
|
|
95
|
+
borderColor: selectedItem ? theme.outline.neutral.tertiaryVariant : theme.outline.neutral.primary,
|
|
96
|
+
borderRadius: theme.radius.sm,
|
|
97
|
+
paddingHorizontal: theme.space.xs
|
|
98
|
+
}
|
|
60
99
|
]}
|
|
61
100
|
onPress={toggleDropdown}
|
|
62
101
|
>
|
|
63
|
-
<Text style={
|
|
64
|
-
{
|
|
102
|
+
<Text style={selectedItem ? theme.typography.inputText.value : theme.typography.inputText.placeholder}>
|
|
103
|
+
{selectedItem ? selectedItem.label : placeholder}
|
|
65
104
|
</Text>
|
|
66
|
-
<MaterialIcons
|
|
67
|
-
name={isOpen ? "expand-less" : "expand-more"}
|
|
68
|
-
size={theme.typography.icon.md}
|
|
69
|
-
color={theme.text.neutral.primary}
|
|
70
|
-
/>
|
|
105
|
+
<MaterialIcons name={isOpen ? "expand-less" : "expand-more"} size={24} color={theme.text.neutral.primary} />
|
|
71
106
|
</TouchableOpacity>
|
|
72
107
|
|
|
73
|
-
{isOpen
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
{item
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
<Modal transparent visible={isOpen} animationType="none" onRequestClose={toggleDropdown}>
|
|
109
|
+
<Pressable style={styles.modalOverlay} onPress={toggleDropdown}>
|
|
110
|
+
<Animated.View
|
|
111
|
+
style={[
|
|
112
|
+
styles.dropdown,
|
|
113
|
+
{
|
|
114
|
+
opacity: animatedValue,
|
|
115
|
+
transform: [{ translateY: animatedValue.interpolate({ inputRange: [0, 1], outputRange: [-10, 0] }) }],
|
|
116
|
+
backgroundColor: theme.surface.neutral.primary,
|
|
117
|
+
borderRadius: theme.radius.sm,
|
|
118
|
+
borderColor: theme.outline.neutral.primary,
|
|
119
|
+
top: dropdownTop,
|
|
120
|
+
width: dropdownWidth,
|
|
121
|
+
left: dropdownLeft,
|
|
122
|
+
elevation: 8,
|
|
123
|
+
maxHeight: 240,
|
|
124
|
+
}
|
|
125
|
+
]}
|
|
126
|
+
>
|
|
127
|
+
<FlatList
|
|
128
|
+
data={options}
|
|
129
|
+
keyExtractor={(item) => item.value.toString()}
|
|
130
|
+
renderItem={({ item }) => (
|
|
131
|
+
<SelectOption
|
|
132
|
+
item={item}
|
|
133
|
+
isSelected={item.value === selectedValue}
|
|
134
|
+
onSelect={handleSelect}
|
|
135
|
+
theme={theme}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
/>
|
|
139
|
+
</Animated.View>
|
|
140
|
+
</Pressable>
|
|
141
|
+
</Modal>
|
|
104
142
|
</View>
|
|
105
143
|
);
|
|
106
144
|
};
|
|
107
145
|
|
|
108
146
|
const styles = StyleSheet.create({
|
|
109
|
-
mainContainer: {
|
|
110
|
-
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
flexDirection: 'row',
|
|
116
|
-
alignItems: 'center',
|
|
117
|
-
justifyContent: 'space-between',
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
dropdown: {
|
|
122
|
-
position: 'absolute',
|
|
123
|
-
top: 90,
|
|
124
|
-
width: '100%',
|
|
125
|
-
borderWidth: 1,
|
|
126
|
-
overflow: 'hidden', // Necesario para que el contenido no se salga de los bordes redondeados
|
|
127
|
-
},
|
|
128
|
-
optionItem: {
|
|
129
|
-
height: 48, // 👈 Altura fija por ítem para controlar el límite de 5
|
|
130
|
-
paddingHorizontal: 18,
|
|
131
|
-
justifyContent: 'center'
|
|
132
|
-
}
|
|
147
|
+
mainContainer: { width: '100%' },
|
|
148
|
+
inputContainer: { height: 48, borderWidth: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
|
|
149
|
+
modalOverlay: { flex: 1, backgroundColor: 'transparent' },
|
|
150
|
+
dropdown: { position: 'absolute', borderWidth: 1, overflow: 'hidden' },
|
|
151
|
+
optionItem: { height: 48, justifyContent: 'center' },
|
|
152
|
+
optionContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }
|
|
133
153
|
});
|