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
- <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
- };
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
- const styles = StyleSheet.create ({
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
- mainContainer:{
108
+ const styles = StyleSheet.create({
109
+ mainContainer: {
56
110
  alignItems: 'center',
57
111
  flexGrow: 1,
58
112
  maxWidth: 160,
59
113
  minWidth: 96,
60
- height: 'auto'
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cdslibrary",
3
3
  "license": "0BSD",
4
- "version": "1.2.86",
4
+ "version": "1.2.88",
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],