cdslibrary 1.2.86 → 1.2.88
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.
|
@@ -118,10 +118,9 @@ const animatedStyle = useAnimatedStyle(() => {
|
|
|
118
118
|
return (
|
|
119
119
|
<>
|
|
120
120
|
<Animated.View
|
|
121
|
-
pointerEvents={isVisible ? "auto" : "none"} // Evita clics fantasma
|
|
122
121
|
style={[
|
|
123
122
|
styles.overlay,
|
|
124
|
-
{ backgroundColor: theme.surface.special.overlay },
|
|
123
|
+
{pointerEvents: isVisible ? "auto" : "none", backgroundColor: theme.surface.special.overlay },
|
|
125
124
|
backdropStyle
|
|
126
125
|
]}
|
|
127
126
|
>
|
|
@@ -131,6 +130,7 @@ const animatedStyle = useAnimatedStyle(() => {
|
|
|
131
130
|
style={[
|
|
132
131
|
isMobile ? styles.container.typeBottomSheet : styles.container.typeDrawer,
|
|
133
132
|
animatedStyle,
|
|
133
|
+
theme.shadows.lg,
|
|
134
134
|
{
|
|
135
135
|
backgroundColor: theme.surface.neutral.primary,
|
|
136
136
|
paddingTop: (hasClose ? theme.space['2xl'] : theme.space.xl),
|
|
@@ -239,7 +239,6 @@ const styles = StyleSheet.create({
|
|
|
239
239
|
zIndex: 99,
|
|
240
240
|
...Platform.select({
|
|
241
241
|
web: {
|
|
242
|
-
boxShadow: '-10px 0px 15px rgba(0,0,0,0.1)',
|
|
243
242
|
height: 'auto', // En web forzamos que sea auto
|
|
244
243
|
}
|
|
245
244
|
})
|
|
@@ -2,62 +2,121 @@ import React, { useState } from "react";
|
|
|
2
2
|
import { Text, StyleSheet, TouchableOpacity, View, Image } from "react-native";
|
|
3
3
|
import { MaterialIcons } from "@expo/vector-icons";
|
|
4
4
|
import { useTheme } from "../context/CDSThemeContext";
|
|
5
|
+
import { CDSTooltip } from "./CDSTooltip"; // Asegúrate de importar tu Tooltip
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export const CDSImageButton = ({ object, isActive, onPress }) => { // 👈 Añadimos isActive
|
|
7
|
+
export const CDSImageButton = ({ object, isActive, onPress, hasHelper, helperMessage, arrowAlignment }) => {
|
|
9
8
|
const { theme } = useTheme();
|
|
10
9
|
|
|
10
|
+
// Estados para controlar el Tooltip interno
|
|
11
|
+
const [tooltipVisible, setTooltipVisible] = useState(false);
|
|
12
|
+
const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });
|
|
13
|
+
|
|
14
|
+
const handleHelperPress = (event) => {
|
|
15
|
+
// Si estamos en Web, usamos getBoundingClientRect para precisión total
|
|
16
|
+
if (event.target && event.target.getBoundingClientRect) {
|
|
17
|
+
const rect = event.target.getBoundingClientRect();
|
|
18
|
+
setTooltipPos({
|
|
19
|
+
x: rect.left,
|
|
20
|
+
y: rect.top,
|
|
21
|
+
width: rect.width,
|
|
22
|
+
height: rect.height
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
// Fallback para Mobile (Nativo)
|
|
26
|
+
const { pageX, pageY } = event.nativeEvent;
|
|
27
|
+
setTooltipPos({
|
|
28
|
+
x: pageX - 10, // Ajuste manual si no hay medida
|
|
29
|
+
y: pageY,
|
|
30
|
+
width: 20,
|
|
31
|
+
height: 20
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
setTooltipVisible(true);
|
|
35
|
+
};
|
|
11
36
|
|
|
12
37
|
return (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
/>
|
|
44
|
-
<Text style={isActive ? theme.typography.semiBold.sm : theme.typography.regular.sm }>
|
|
45
|
-
{object.label}
|
|
46
|
-
</Text>
|
|
47
|
-
</TouchableOpacity>
|
|
48
|
-
);
|
|
49
|
-
};
|
|
38
|
+
<>
|
|
39
|
+
<TouchableOpacity
|
|
40
|
+
onPress={onPress}
|
|
41
|
+
activeOpacity={0.7}
|
|
42
|
+
style={[
|
|
43
|
+
styles.mainContainer,
|
|
44
|
+
{
|
|
45
|
+
backgroundColor: isActive
|
|
46
|
+
? theme.surface.neutral.primaryVariant
|
|
47
|
+
: theme.surface.neutral.primary,
|
|
48
|
+
gap: theme.space.xs, // Reducimos un poco el gap para el label
|
|
49
|
+
padding: theme.space.sm,
|
|
50
|
+
borderRadius: theme.radius.md,
|
|
51
|
+
borderWidth: 1,
|
|
52
|
+
borderColor: isActive ? theme.outline.neutral.focus : 'transparent',
|
|
53
|
+
...(!isActive && theme.shadows.lg),
|
|
54
|
+
transform: [{ scale: isActive ? 0.98 : 1 }]
|
|
55
|
+
},
|
|
56
|
+
]}
|
|
57
|
+
>
|
|
58
|
+
<Image
|
|
59
|
+
source={object.image}
|
|
60
|
+
resizeMode="cover"
|
|
61
|
+
style={{
|
|
62
|
+
width: "100%",
|
|
63
|
+
height: 80,
|
|
64
|
+
borderRadius: theme.radius.sm,
|
|
65
|
+
marginBottom: 4
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
50
68
|
|
|
69
|
+
{/* Contenedor de Texto + Ícono de ayuda */}
|
|
70
|
+
<View style={styles.labelContainer}>
|
|
71
|
+
<Text
|
|
72
|
+
numberOfLines={1}
|
|
73
|
+
style={[
|
|
74
|
+
isActive ? theme.typography.semiBold.sm : theme.typography.regular.sm,
|
|
75
|
+
{ color: theme.text.neutral.primary }
|
|
76
|
+
]}
|
|
77
|
+
>
|
|
78
|
+
{object.label}
|
|
79
|
+
</Text>
|
|
51
80
|
|
|
81
|
+
{hasHelper && (
|
|
82
|
+
<TouchableOpacity
|
|
83
|
+
onPress={handleHelperPress}
|
|
84
|
+
hitSlop={{ top: 15, bottom: 15, left: 15, right: 15 }}
|
|
85
|
+
>
|
|
86
|
+
<MaterialIcons
|
|
87
|
+
name="help-outline"
|
|
88
|
+
size={14}
|
|
89
|
+
color={theme.text.neutral.secondary}
|
|
90
|
+
/>
|
|
91
|
+
</TouchableOpacity>
|
|
92
|
+
)}
|
|
93
|
+
</View>
|
|
94
|
+
</TouchableOpacity>
|
|
52
95
|
|
|
53
|
-
|
|
96
|
+
{/* Tooltip renderizado fuera del botón para evitar cortes de overflow */}
|
|
97
|
+
<CDSTooltip
|
|
98
|
+
visible={tooltipVisible}
|
|
99
|
+
message={helperMessage}
|
|
100
|
+
targetPosition={tooltipPos}
|
|
101
|
+
onDismiss={() => setTooltipVisible(false)}
|
|
102
|
+
arrowAlign={arrowAlignment}
|
|
103
|
+
/>
|
|
104
|
+
</>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
54
107
|
|
|
55
|
-
|
|
108
|
+
const styles = StyleSheet.create({
|
|
109
|
+
mainContainer: {
|
|
56
110
|
alignItems: 'center',
|
|
57
111
|
flexGrow: 1,
|
|
58
112
|
maxWidth: 160,
|
|
59
113
|
minWidth: 96,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
114
|
+
},
|
|
115
|
+
labelContainer: {
|
|
116
|
+
flexDirection: 'row',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
justifyContent: 'center',
|
|
119
|
+
gap: 4, // Espacio entre el texto y el ícono
|
|
120
|
+
width: '100%',
|
|
121
|
+
}
|
|
122
|
+
});
|
|
@@ -59,6 +59,9 @@ export const CDSImageButtonGroup = ({ array, onSelect, isCentered }) => {
|
|
|
59
59
|
object={item}
|
|
60
60
|
isActive={selectedId === item.id}
|
|
61
61
|
onPress={() => handlePress(item.id)}
|
|
62
|
+
hasHelper={item.helper ? true : false}
|
|
63
|
+
helperMessage={item.helper}
|
|
64
|
+
arrowAlignment={item.arrowAlignment}
|
|
62
65
|
/>
|
|
63
66
|
))}
|
|
64
67
|
</ScrollView>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { StyleSheet, Text, View, Modal, Pressable, Dimensions } from "react-native";
|
|
3
|
+
import Animated, { useSharedValue, useAnimatedStyle, withTiming, runOnJS } from "react-native-reanimated";
|
|
4
|
+
import { useTheme } from "../context/CDSThemeContext";
|
|
5
|
+
|
|
6
|
+
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
7
|
+
|
|
8
|
+
export const CDSTooltip = ({ message, visible, onDismiss, targetPosition = { x: 0, y: 0, width: 0, height: 0 }, arrowAlign = 'center' }) => {
|
|
9
|
+
const { theme } = useTheme();
|
|
10
|
+
const opacity = useSharedValue(0);
|
|
11
|
+
const [shouldRender, setShouldRender] = useState(visible);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (visible) {
|
|
15
|
+
setShouldRender(true);
|
|
16
|
+
opacity.value = withTiming(1, { duration: 150 });
|
|
17
|
+
} else {
|
|
18
|
+
opacity.value = withTiming(0, { duration: 150 }, (finished) => {
|
|
19
|
+
if (finished) runOnJS(setShouldRender)(false);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}, [visible]);
|
|
23
|
+
|
|
24
|
+
const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
|
|
25
|
+
|
|
26
|
+
// --- LÓGICA DE POSICIONAMIENTO ---
|
|
27
|
+
const tooltipWidth = 320; // Ancho fijo para facilitar el cálculo de la flecha
|
|
28
|
+
const paddingScreen = 16;
|
|
29
|
+
|
|
30
|
+
// Centro horizontal del icono/botón
|
|
31
|
+
const targetCenterX = targetPosition.x + (targetPosition.width / 2);
|
|
32
|
+
|
|
33
|
+
// Calculamos el Left del contenedor
|
|
34
|
+
let leftPos = targetCenterX - (tooltipWidth / 2);
|
|
35
|
+
if (arrowAlign === 'left') leftPos = targetCenterX - 30;
|
|
36
|
+
if (arrowAlign === 'right') leftPos = targetCenterX - tooltipWidth + 30;
|
|
37
|
+
|
|
38
|
+
// Ajuste para que no choque con los bordes del celular/navegador
|
|
39
|
+
const finalLeft = Math.max(paddingScreen, Math.min(leftPos, SCREEN_WIDTH - tooltipWidth - paddingScreen));
|
|
40
|
+
|
|
41
|
+
// Calculamos dónde debe ir la flecha respecto al globo
|
|
42
|
+
const arrowLeft = targetCenterX - finalLeft - 8; // 8 es la mitad del ancho de la flecha
|
|
43
|
+
|
|
44
|
+
if (!shouldRender) return null;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Modal transparent visible={visible} animationType="none" onRequestClose={onDismiss}>
|
|
48
|
+
<Pressable style={styles.overlay} onPress={onDismiss}>
|
|
49
|
+
<Animated.View
|
|
50
|
+
style={[
|
|
51
|
+
styles.tooltip,
|
|
52
|
+
animatedStyle,
|
|
53
|
+
theme.shadows.lg,
|
|
54
|
+
{
|
|
55
|
+
top: targetPosition.y + targetPosition.height + 8,
|
|
56
|
+
left: finalLeft,
|
|
57
|
+
width: tooltipWidth,
|
|
58
|
+
backgroundColor: theme.surface.special.tooltip,
|
|
59
|
+
borderRadius: theme.radius.sm,
|
|
60
|
+
padding: theme.space.sm,
|
|
61
|
+
}
|
|
62
|
+
]}
|
|
63
|
+
>
|
|
64
|
+
{/* Flecha dinámica */}
|
|
65
|
+
<View style={[
|
|
66
|
+
styles.arrowUp,
|
|
67
|
+
{ left: arrowLeft, borderBottomColor: theme.surface.special.tooltip }
|
|
68
|
+
]} />
|
|
69
|
+
|
|
70
|
+
<Text style={[
|
|
71
|
+
theme.typography.regular.xs,
|
|
72
|
+
{ color: theme.text.neutral.contrast, textAlign: 'center' }
|
|
73
|
+
]}>
|
|
74
|
+
{message}
|
|
75
|
+
</Text>
|
|
76
|
+
</Animated.View>
|
|
77
|
+
</Pressable>
|
|
78
|
+
</Modal>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const styles = StyleSheet.create({
|
|
83
|
+
overlay: { ...StyleSheet.absoluteFillObject, backgroundColor: 'transparent' },
|
|
84
|
+
tooltip: { position: 'absolute', zIndex: 10000 },
|
|
85
|
+
arrowUp: {
|
|
86
|
+
position: 'absolute',
|
|
87
|
+
top: -7,
|
|
88
|
+
width: 0,
|
|
89
|
+
height: 0,
|
|
90
|
+
borderLeftWidth: 8,
|
|
91
|
+
borderRightWidth: 8,
|
|
92
|
+
borderBottomWidth: 8,
|
|
93
|
+
borderLeftColor: 'transparent',
|
|
94
|
+
borderRightColor: 'transparent',
|
|
95
|
+
borderTopColor: 'transparent',
|
|
96
|
+
backgroundColor: 'transparent',
|
|
97
|
+
}
|
|
98
|
+
});
|
package/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export {CDSSnackBar} from './components/CDSSnackBar';
|
|
|
25
25
|
export {CDSImageButton} from './components/CDSImageButton'
|
|
26
26
|
export {CDSImageButtonGroup} from './components/CDSImageButtonGroup'
|
|
27
27
|
export {CDSCheckbox} from './components/CDSCheckbox';
|
|
28
|
+
export {CDSTooltip} from './components/CDSTooltip';
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
export {CDSThemeProvider, useTheme} from './context/CDSThemeContext';
|
package/package.json
CHANGED
|
@@ -54,7 +54,7 @@ export const CDSsemanticColors = {
|
|
|
54
54
|
overlay: CDSprimitiveColors.overlay.light,
|
|
55
55
|
scroll: CDSprimitiveColors.neutral[400],
|
|
56
56
|
snackbar: CDSprimitiveColors.neutral[700],
|
|
57
|
-
tooltip: CDSprimitiveColors.neutral[
|
|
57
|
+
tooltip: CDSprimitiveColors.neutral[600],
|
|
58
58
|
progress: CDSprimitiveColors.brand[600],
|
|
59
59
|
progressbarBg: CDSprimitiveColors.neutral[400],
|
|
60
60
|
display: CDSprimitiveColors.neutral[800],
|