cdslibrary 1.2.86 → 1.2.87

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.
@@ -2,62 +2,105 @@ 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 }) => {
9
8
  const { theme } = useTheme();
9
+
10
+ // Estados para controlar el Tooltip interno
11
+ const [tooltipVisible, setTooltipVisible] = useState(false);
12
+ const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });
10
13
 
14
+ const handleHelperPress = (event) => {
15
+ // Capturamos la posición global para el tooltip
16
+ const { pageX, pageY } = event.nativeEvent;
17
+ setTooltipPos({ x: pageX, y: pageY });
18
+ setTooltipVisible(true);
19
+ };
11
20
 
12
21
  return (
13
- <TouchableOpacity
14
- onPress={onPress}
15
- style={[
16
- styles.mainContainer,
17
- {
18
- backgroundColor: isActive
19
- ? theme.surface.neutral.primaryVariant // Color cuando está presionado
20
- : theme.surface.neutral.primary,
21
- gap: theme.space.sm,
22
- padding: theme.space.sm,
23
- // borderColor: isActive
24
- // ? theme.outline.neutral.primary // Borde de color de marca si está activo
25
- // : theme.outline.neutral.primary,
26
- borderRadius: theme.radius.md,
27
- // Si está activo, quitamos la sombra o usamos una más pequeña (md)
28
- ...(!isActive && theme.shadows.lg),
29
- // Si quieres que se vea "hundido", podrías bajar la escala
30
- transform: [{ scale: isActive ? 0.99 : 1 }]
31
- },
32
- ]}
33
- >
34
- <Image
35
- source={object.image}
36
- resizeMode="cover"
37
- style={{
38
- flexGrow: 1,
39
- width: "100%",
40
- height: 80,
41
- borderRadius: theme.radius.sm,
42
- }}
43
- />
44
- <Text style={isActive ? theme.typography.semiBold.sm : theme.typography.regular.sm }>
45
- {object.label}
46
- </Text>
47
- </TouchableOpacity>
48
- );
49
- };
22
+ <>
23
+ <TouchableOpacity
24
+ onPress={onPress}
25
+ activeOpacity={0.7}
26
+ style={[
27
+ styles.mainContainer,
28
+ {
29
+ backgroundColor: isActive
30
+ ? theme.surface.neutral.primaryVariant
31
+ : theme.surface.neutral.primary,
32
+ gap: theme.space.xs, // Reducimos un poco el gap para el label
33
+ padding: theme.space.sm,
34
+ borderRadius: theme.radius.md,
35
+ borderWidth: 1,
36
+ borderColor: isActive ? theme.outline.neutral.focus : 'transparent',
37
+ ...(!isActive && theme.shadows.lg),
38
+ transform: [{ scale: isActive ? 0.98 : 1 }]
39
+ },
40
+ ]}
41
+ >
42
+ <Image
43
+ source={object.image}
44
+ resizeMode="cover"
45
+ style={{
46
+ width: "100%",
47
+ height: 80,
48
+ borderRadius: theme.radius.sm,
49
+ marginBottom: 4
50
+ }}
51
+ />
50
52
 
53
+ {/* Contenedor de Texto + Ícono de ayuda */}
54
+ <View style={styles.labelContainer}>
55
+ <Text
56
+ numberOfLines={1}
57
+ style={[
58
+ isActive ? theme.typography.semiBold.sm : theme.typography.regular.sm,
59
+ { color: theme.text.neutral.primary }
60
+ ]}
61
+ >
62
+ {object.label}
63
+ </Text>
51
64
 
65
+ {hasHelper && (
66
+ <TouchableOpacity
67
+ onPress={handleHelperPress}
68
+ hitSlop={{ top: 15, bottom: 15, left: 15, right: 15 }}
69
+ >
70
+ <MaterialIcons
71
+ name="help-outline"
72
+ size={14}
73
+ color={theme.text.neutral.secondary}
74
+ />
75
+ </TouchableOpacity>
76
+ )}
77
+ </View>
78
+ </TouchableOpacity>
52
79
 
53
- const styles = StyleSheet.create ({
80
+ {/* Tooltip renderizado fuera del botón para evitar cortes de overflow */}
81
+ <CDSTooltip
82
+ visible={tooltipVisible}
83
+ message={helperMessage}
84
+ targetPosition={tooltipPos}
85
+ onDismiss={() => setTooltipVisible(false)}
86
+ arrowAlign="center"
87
+ />
88
+ </>
89
+ );
90
+ };
54
91
 
55
- mainContainer:{
92
+ const styles = StyleSheet.create({
93
+ mainContainer: {
56
94
  alignItems: 'center',
57
95
  flexGrow: 1,
58
96
  maxWidth: 160,
59
97
  minWidth: 96,
60
- height: 'auto'
61
- }
62
-
63
- })
98
+ },
99
+ labelContainer: {
100
+ flexDirection: 'row',
101
+ alignItems: 'center',
102
+ justifyContent: 'center',
103
+ gap: 4, // Espacio entre el texto y el ícono
104
+ width: '100%',
105
+ }
106
+ });
@@ -59,6 +59,8 @@ 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}
62
64
  />
63
65
  ))}
64
66
  </ScrollView>
@@ -0,0 +1,126 @@
1
+ import React, { useEffect } from "react";
2
+ import { StyleSheet, Text, View, Modal, TouchableWithoutFeedback } from "react-native";
3
+ import Animated, {
4
+ useSharedValue,
5
+ useAnimatedStyle,
6
+ withSpring,
7
+ withTiming,
8
+ } from "react-native-reanimated";
9
+ import { useTheme } from "../context/CDSThemeContext";
10
+
11
+ export const CDSTooltip = ({ message, visible, onDismiss, targetPosition = { x: 0, y: 0 }, arrowAlign = 'center' }) => {
12
+ const { theme } = useTheme();
13
+ const opacity = useSharedValue(0);
14
+ const scale = useSharedValue(1);
15
+
16
+ useEffect(() => {
17
+ if (visible) {
18
+ opacity.value = withTiming(1, { duration: 200 });
19
+ // scale.value = withSpring(1, { damping: 12 });
20
+ } else {
21
+ opacity.value = withTiming(0, { duration: 200 });
22
+ // scale.value = withTiming(0.8);
23
+ }
24
+ }, [visible]);
25
+
26
+ const animatedStyle = useAnimatedStyle(() => ({
27
+ opacity: opacity.value,
28
+ // transform: [{ scale: scale.value }],
29
+ }));
30
+
31
+ // 1. Alineación de la flecha dentro del globo
32
+ const getArrowStyle = () => {
33
+ switch (arrowAlign) {
34
+ case 'left': return { left: 15 };
35
+ case 'right': return { right: 15 };
36
+ default: return { alignSelf: 'center' };
37
+ }
38
+ };
39
+
40
+ // 2. Alineación del globo respecto al clic (x)
41
+ const getTooltipContainerStyle = () => {
42
+ const tooltipWidth = 200; // Ancho estimado o fijo
43
+ let leftPosition = targetPosition.x;
44
+
45
+ if (arrowAlign === 'center') {
46
+ leftPosition = targetPosition.x - (tooltipWidth / 2);
47
+ } else if (arrowAlign === 'right') {
48
+ leftPosition = targetPosition.x - tooltipWidth + 25; // 25 es el margen de la flecha
49
+ } else {
50
+ leftPosition = targetPosition.x - 25;
51
+ }
52
+
53
+ return {
54
+ top: targetPosition.y + 24, // Debajo del clic
55
+ left: Math.max(10, leftPosition), // Evita que se salga de la pantalla por la izquierda
56
+ width: tooltipWidth,
57
+ };
58
+ };
59
+
60
+ if (!visible && opacity.value === 0) return null;
61
+
62
+ return (
63
+ <Modal transparent visible={visible} animationType="none" onRequestClose={onDismiss}>
64
+ <TouchableWithoutFeedback onPress={onDismiss}>
65
+ <View style={styles.overlay}>
66
+ <Animated.View
67
+ style={[
68
+ styles.tooltip,
69
+ animatedStyle,
70
+ getTooltipContainerStyle(),
71
+ {
72
+ backgroundColor: theme.surface.special.tooltip,
73
+ borderRadius: theme.radius.sm,
74
+ padding: theme.space.sm,
75
+ }
76
+ ]}
77
+ >
78
+ {/* La Flecha */}
79
+ <View style={[
80
+ styles.arrowUp,
81
+ getArrowStyle(),
82
+ { borderBottomColor: theme.surface.special.tooltip }
83
+ ]} />
84
+
85
+ {/* El Texto con alineación dinámica */}
86
+ <Text style={[
87
+ theme.typography.regular.xs,
88
+ {
89
+ color: theme.text.neutral.contrast,
90
+ textAlign: arrowAlign // 'left', 'right' o 'center'
91
+ }
92
+ ]}>
93
+ {message}
94
+ </Text>
95
+ </Animated.View>
96
+ </View>
97
+ </TouchableWithoutFeedback>
98
+ </Modal>
99
+ );
100
+ };
101
+
102
+ const styles = StyleSheet.create({
103
+ overlay: { flex: 1, backgroundColor: 'transparent' },
104
+ tooltip: {
105
+ position: 'absolute',
106
+ zIndex: 10000,
107
+ elevation: 5,
108
+ shadowColor: "#000",
109
+ shadowOffset: { width: 0, height: 2 },
110
+ shadowOpacity: 0.2,
111
+ shadowRadius: 4,
112
+ },
113
+ arrowUp: {
114
+ position: 'absolute',
115
+ top: -8,
116
+ width: 0,
117
+ height: 0,
118
+ borderLeftWidth: 8,
119
+ borderRightWidth: 8,
120
+ borderBottomWidth: 8,
121
+ borderStyle: 'solid',
122
+ backgroundColor: 'transparent',
123
+ borderLeftColor: 'transparent',
124
+ borderRightColor: 'transparent',
125
+ }
126
+ });
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cdslibrary",
3
3
  "license": "0BSD",
4
- "version": "1.2.86",
4
+ "version": "1.2.87",
5
5
  "main": "index.js",
6
6
  "author": "Nat Viramontes",
7
7
  "description": "A library of components for the CDS project",
@@ -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[500],
57
+ tooltip: CDSprimitiveColors.neutral[600],
58
58
  progress: CDSprimitiveColors.brand[600],
59
59
  progressbarBg: CDSprimitiveColors.neutral[400],
60
60
  display: CDSprimitiveColors.neutral[800],