cdslibrary 1.2.47 → 1.2.49

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.
@@ -6,7 +6,6 @@ import { useTheme } from "../context/CDSThemeContext";
6
6
  import { LinearGradient } from 'expo-linear-gradient';
7
7
 
8
8
  const { height, width } = Dimensions.get("window");
9
- const isMobile = width <= 878;
10
9
 
11
10
  const bottomSheetRender = ({
12
11
  type,
@@ -21,6 +20,7 @@ const bottomSheetRender = ({
21
20
  onFinish,
22
21
  }, ref) => {
23
22
  const { theme } = useTheme();
23
+ const isMobile = theme.isMobile
24
24
  const [modalVisible, setModalVisible] = useState(isVisible);
25
25
  const [modalOpacity] = useState(new Animated.Value(0));
26
26
  const [slidePosition] = useState(new Animated.Value(isMobile ? height : width));
@@ -1,11 +1,8 @@
1
1
  import React from "react";
2
2
  import { Text, StyleSheet, TouchableOpacity } from "react-native";
3
- import { Dimensions, Platform } from "react-native";
4
3
  import { MaterialIcons } from "@expo/vector-icons";
5
4
  import { useTheme } from "../context/CDSThemeContext";
6
5
 
7
- const { width } = Dimensions.get("window");
8
- const isMobile = width <= 768
9
6
 
10
7
  export const CDSButton = ({
11
8
  label,
@@ -17,7 +14,8 @@ export const CDSButton = ({
17
14
  flexend,
18
15
  }) => {
19
16
  const { theme } = useTheme();
20
-
17
+ const isMobile = theme.isMobile
18
+
21
19
  const backgroundColor =
22
20
  type === "disabled"
23
21
  ? theme.surface.action.disabled
@@ -1,121 +1,127 @@
1
- import { useState, useRef } from "react";
1
+ import React, { useState, useRef } from "react";
2
2
  import { View, Text, StyleSheet, TouchableOpacity, Platform, Animated } from "react-native";
3
-
4
-
5
3
  import { MaterialIcons } from "@expo/vector-icons";
6
4
  import { useTheme } from "../context/CDSThemeContext";
7
5
 
8
6
  export const CDSCardFeedback = ({
9
-
10
7
  style,
11
8
  title,
12
9
  description,
13
10
  secondAction,
14
11
  onPressSecondAction,
15
- onClose }) => {
12
+ onClose
13
+ }) => {
16
14
  const { theme } = useTheme();
17
- const [isVisible, setIsVisible] = useState(true); // 3. Estado inicial: visible
15
+ const [isVisible, setIsVisible] = useState(true);
18
16
  const fadeAnim = useRef(new Animated.Value(1)).current;
19
- // 4. Si isVisible es falso, no renderizamos nada (devolvemos null)
20
17
 
21
18
  const handleClose = () => {
22
- // Iniciar la animación de desvanecimiento
19
+ // IMPORTANTE: useNativeDriver debe ser false para animar maxHeight y padding
23
20
  Animated.timing(fadeAnim, {
24
21
  toValue: 0,
25
22
  duration: 300,
26
- useNativeDriver: Platform.OS !== 'web'
23
+ useNativeDriver: false
27
24
  }).start(() => {
28
- // Una vez que la animación termina, ocultar el componente
29
25
  setIsVisible(false);
30
- })
31
- }
26
+ if (onClose) onClose();
27
+ });
28
+ };
29
+
32
30
  if (!isVisible) return null;
33
31
 
34
32
  const feedbackStyles = {
35
- success: {
36
- bg: theme.surface.feedback.success,
37
- border: theme.outline.feedback.success,
38
- text: theme.text.feedback.success,
39
- icon: 'check-circle',
40
- },
41
- warning: {
42
- bg: theme.surface.feedback.warning,
43
- border: theme.outline.feedback.warning,
44
- text: theme.text.feedback.warning,
45
- icon: 'warning',
46
- },
47
- error: {
48
- bg: theme.surface.feedback.error,
49
- border: theme.outline.feedback.error,
50
- text: theme.text.feedback.error,
51
- icon: 'error',
52
- },
53
- neutral: {
54
- bg: theme.surface.neutral.primaryVariant,
55
- border: theme.outline.tertiary,
56
- text: theme.text.neutral.primary,
57
- icon: 'lightbulb',
58
- },
59
- info: {
60
- bg: theme.surface.feedback.info,
61
- border: theme.outline.feedback.info,
62
- text: theme.text.feedback.info,
63
- icon: 'info',
64
- }
65
- }
33
+ success: { bg: theme.surface.feedback.success, border: theme.outline.feedback.success, text: theme.text.feedback.success, icon: 'check-circle' },
34
+ warning: { bg: theme.surface.feedback.warning, border: theme.outline.feedback.warning, text: theme.text.feedback.warning, icon: 'warning' },
35
+ error: { bg: theme.surface.feedback.error, border: theme.outline.feedback.error, text: theme.text.feedback.error, icon: 'error' },
36
+ neutral: { bg: theme.surface.neutral.primaryVariant, border: theme.outline.tertiary, text: theme.text.neutral.primary, icon: 'lightbulb' },
37
+ info: { bg: theme.surface.feedback.info, border: theme.outline.feedback.info, text: theme.text.feedback.info, icon: 'info' }
38
+ };
39
+
66
40
  const currentStyle = feedbackStyles[style] || feedbackStyles.info;
67
41
 
42
+ // Interpolamos todos los valores físicos para que colapsen a 0
43
+ const animatedLayout = {
44
+ maxHeight: fadeAnim.interpolate({
45
+ inputRange: [0, 1],
46
+ outputRange: [0, 600] // Suficiente para el contenido
47
+ }),
48
+ opacity: fadeAnim,
49
+ // Animamos el padding y el margen para que no queden espacios vacíos al cerrar
50
+ paddingVertical: fadeAnim.interpolate({
51
+ inputRange: [0, 1],
52
+ outputRange: [0, theme.space.md]
53
+ }),
54
+ marginBottom: fadeAnim.interpolate({
55
+ inputRange: [0, 1],
56
+ outputRange: [0, theme.space.sm]
57
+ }),
58
+ borderWidth: 1,
59
+
60
+ };
61
+
68
62
  return (
69
- <Animated.View style={[styles.container, {
70
- backgroundColor: currentStyle.bg,
71
- borderColor: currentStyle.border,
72
- borderRadius: theme.radius.md,
73
- padding: theme.space.md,
74
- gap: theme.space.sm,
75
- opacity: fadeAnim,
76
- transform: [{
77
- scale: fadeAnim.interpolate({
78
- inputRange: [0, 1],
79
- outputRange: [0.95, 1] // Se encoge ligeramente al 95%
80
- })
81
- }]
82
- }]}>
63
+ <Animated.View style={[
64
+ styles.container,
65
+ animatedLayout,
66
+ {
67
+ backgroundColor: currentStyle.bg,
68
+ borderColor: currentStyle.border,
69
+ borderRadius: theme.radius.md,
70
+ paddingHorizontal: theme.space.md,
71
+ gap: theme.space.sm,
72
+ overflow: 'hidden', // Evita que el texto se salga mientras se achica
73
+ }
74
+ ]}>
75
+ {title && (
76
+ <View style={[styles.titleContainer, { gap: theme.space.sm }]}>
77
+ <MaterialIcons
78
+ name={currentStyle.icon}
79
+ size={theme.typography.icon.md}
80
+ color={currentStyle.text}
81
+ />
82
+ <Text style={[styles.title, theme.typography.bold.lg, { color: currentStyle.text }]}>
83
+ {title}
84
+ </Text>
85
+ </View>
86
+ )}
87
+
88
+ {description && (
89
+ <Text style={[styles.description, theme.typography.regular.md, { color: currentStyle.text }]}>
90
+ {description}
91
+ </Text>
92
+ )}
83
93
 
84
- {title && <View style={[styles.titleContainer, { gap: theme.space.sm }]}>
85
- <MaterialIcons
86
- name={currentStyle.icon}
87
- size={theme.typography.icon.md}
88
- color={currentStyle.text}
89
- />
90
- <Text style={[styles.title, theme.typography.bold.lg, { color: currentStyle.text }]}>{title}</Text>
91
- </View>}
92
- {description && <Text style={[styles.description, theme.typography.regular.md, { color: currentStyle.text }]}>{description}</Text>}
93
94
  <View style={[styles.actionsContainer, { gap: theme.space.sm }]}>
94
- {secondAction && <TouchableOpacity onPress={onPressSecondAction}>{<Text style={[theme.typography.bold.sm, { color: currentStyle.text }]}>{secondAction}</Text>}</TouchableOpacity>}
95
- <TouchableOpacity onPress={() => {
96
- handleClose()
97
- }}>{<Text style={[theme.typography.bold.sm, { color: currentStyle.text }]}>{'Cerrar'}</Text>}</TouchableOpacity></View>
95
+ {secondAction && (
96
+ <TouchableOpacity onPress={onPressSecondAction}>
97
+ <Text style={[theme.typography.bold.sm, { color: currentStyle.text }]}>
98
+ {secondAction}
99
+ </Text>
100
+ </TouchableOpacity>
101
+ )}
102
+ <TouchableOpacity onPress={handleClose}>
103
+ <Text style={[theme.typography.bold.sm, { color: currentStyle.text }]}>
104
+ {'Cerrar'}
105
+ </Text>
106
+ </TouchableOpacity>
107
+ </View>
98
108
  </Animated.View>
99
- )
100
- }
109
+ );
110
+ };
101
111
 
102
112
  const styles = StyleSheet.create({
103
113
  container: {
104
114
  width: '100%',
105
115
  justifyContent: 'space-between',
106
- borderWidth: 1,
107
116
  },
108
-
109
117
  titleContainer: {
110
118
  flexDirection: 'row',
111
119
  alignItems: 'center',
112
120
  },
113
- description: {
114
- },
121
+ description: {},
115
122
  actionsContainer: {
116
123
  flexDirection: 'row',
117
124
  width: '100%',
118
125
  justifyContent: 'flex-end',
119
-
120
126
  },
121
- });
127
+ });
@@ -8,6 +8,7 @@ import { useTheme } from "../context/CDSThemeContext";
8
8
  export const CDSImageButton = ({ object, isActive, onPress }) => { // 👈 Añadimos isActive
9
9
  const { theme } = useTheme();
10
10
 
11
+ console.log(theme.typography)
11
12
  return (
12
13
  <TouchableOpacity
13
14
  onPress={onPress}
@@ -18,7 +19,7 @@ export const CDSImageButton = ({ object, isActive, onPress }) => { // 👈 Añad
18
19
  ? theme.surface.neutral.primaryVariant // Color cuando está presionado
19
20
  : theme.surface.neutral.primary,
20
21
  gap: theme.space.sm,
21
- padding: theme.space.sm,
22
+ padding: theme.space.md,
22
23
  // borderColor: isActive
23
24
  // ? theme.outline.neutral.primary // Borde de color de marca si está activo
24
25
  // : theme.outline.neutral.primary,
@@ -34,11 +35,12 @@ export const CDSImageButton = ({ object, isActive, onPress }) => { // 👈 Añad
34
35
  source={object.image}
35
36
  resizeMode="cover"
36
37
  style={{
38
+ flexGrow: 1,
37
39
  width: "100%",
38
- height: 60,
40
+ height: 80,
39
41
  }}
40
42
  />
41
- <Text style={{ fontWeight: isActive ? 'bold' : 'normal' }}>
43
+ <Text style={isActive ? theme.typography.semiBold.sm : theme.typography.regular.sm }>
42
44
  {object.label}
43
45
  </Text>
44
46
  </TouchableOpacity>
@@ -52,8 +54,9 @@ const styles = StyleSheet.create ({
52
54
  mainContainer:{
53
55
  alignItems: 'center',
54
56
  flexGrow: 1,
55
- maxWidth: 200,
56
- height: 120
57
+ maxWidth: 160,
58
+ minWidth: 120,
59
+ height: 'auto'
57
60
  }
58
61
 
59
62
  })
@@ -1,28 +1,61 @@
1
- import { StyleSheet, Text, View, Image } from 'react-native';
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { View, Animated, StyleSheet, ScrollView } from 'react-native';
2
3
  import { CDSImageButton } from './CDSImageButton';
3
- import { useState } from 'react';
4
-
4
+ import { useTheme } from "../context/CDSThemeContext";
5
5
 
6
6
  export const CDSImageButtonGroup = ({ array, onSelect }) => {
7
+ const { theme } = useTheme();
7
8
  const [selectedId, setSelectedId] = useState(null);
9
+ const animatedValue = useRef(new Animated.Value(0)).current;
10
+
11
+ useEffect(() => {
12
+ Animated.timing(animatedValue, {
13
+ toValue: 1,
14
+ duration: 400,
15
+ useNativeDriver: false
16
+ }).start();
17
+ }, []);
8
18
 
9
19
  const handlePress = (id) => {
10
20
  const nextId = id === selectedId ? null : id;
11
21
  setSelectedId(nextId);
12
-
13
- // Avisamos al padre qué objeto se seleccionó (o null si se deseleccionó)
14
22
  if (onSelect) {
15
23
  const selectedItem = array.find(item => item.id === nextId);
16
24
  onSelect(selectedItem || null);
17
25
  }
18
26
  };
19
27
 
28
+ const animatedLayout = {
29
+ maxHeight: animatedValue.interpolate({
30
+ inputRange: [0, 1],
31
+ outputRange: [0, 250]
32
+ }),
33
+ opacity: animatedValue,
34
+ marginTop: animatedValue.interpolate({
35
+ inputRange: [0, 1],
36
+ outputRange: [0, theme.space.md]
37
+ })
38
+ };
39
+
20
40
  return (
21
- <View style={{ width: '100%' }}>
22
- <View style={{
23
- flexDirection: 'row', gap: 10,
24
- width: '100%', justifyContent: 'center'
25
- }}>
41
+ <Animated.View style={[
42
+ animatedLayout,
43
+ { width: '100%' } // Asegura que el contenedor ocupe todo el ancho disponible
44
+ ]}>
45
+ <ScrollView
46
+ horizontal={true}
47
+ showsHorizontalScrollIndicator={false}
48
+ // IMPORTANTE: overflow visible para que no corte sombras,
49
+ // pero necesitamos que el ScrollView sepa su límite
50
+ style={styles.scrollView}
51
+ contentContainerStyle={[
52
+ styles.scrollContent,
53
+ {
54
+ gap: theme.space.sm,
55
+ paddingHorizontal: theme.space.md,
56
+ }
57
+ ]}
58
+ >
26
59
  {array.map((item) => (
27
60
  <CDSImageButton
28
61
  key={item.id}
@@ -31,7 +64,21 @@ export const CDSImageButtonGroup = ({ array, onSelect }) => {
31
64
  onPress={() => handlePress(item.id)}
32
65
  />
33
66
  ))}
34
- </View>
35
- </View>
67
+ </ScrollView>
68
+ </Animated.View>
36
69
  );
37
- }
70
+ }
71
+
72
+ const styles = StyleSheet.create({
73
+ scrollView: {
74
+ width: '100%',
75
+ overflow: 'visible'
76
+ },
77
+ scrollContent: {
78
+ // Quitamos el justifyContent: 'center' porque impide el scroll si hay pocos elementos
79
+ flexDirection: 'row',
80
+ alignItems: 'center',
81
+ paddingVertical: 10,
82
+ overflow: 'visible'
83
+ }
84
+ });
@@ -1,24 +1,21 @@
1
1
 
2
- import { View, Text, Dimensions, StyleSheet, Image, TouchableOpacity } from "react-native";
2
+ import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
3
3
 
4
4
 
5
5
  import { MaterialIcons } from "@expo/vector-icons";
6
6
  import { useTheme } from "../context/CDSThemeContext";
7
- import { getSemanticTextStyles } from "../tokens/CDSsemanticTextStyles";
8
7
  import { useNavigation } from "@react-navigation/native";
9
8
 
10
- const { height, width } = Dimensions.get("window");
11
-
12
- const isMobile = width <= 878;
13
9
  export const CDSNavBar = ({
14
-
10
+
15
11
  type,
16
12
  isExpanded,
17
13
  hasClose,
18
14
  title,
19
15
  hasBack, }) => {
20
-
21
- const { theme, isDarkMode } = useTheme();
16
+
17
+ const { theme, isDarkMode } = useTheme();
18
+ const isMobile = theme.isMobile
22
19
 
23
20
  const navigation = useNavigation();
24
21
 
@@ -1,16 +1,14 @@
1
1
 
2
2
  import { View, Text, StyleSheet, } from "react-native";
3
3
  import { useTheme } from "../context/CDSThemeContext";
4
- import { Dimensions } from "react-native";
5
4
 
6
5
 
7
- const { width } = Dimensions.get("window");
8
- const isMobile = width <= 768
9
6
 
10
7
  export const CDSOffer = ({ description, minValue, maxValue }) => {
11
-
8
+
12
9
  const { theme } = useTheme();
13
- console.log(theme)
10
+ const isMobile = theme.isMobile
11
+
14
12
  return (
15
13
  <View style={[styles.mainContainer, { gap: theme.space.md }]}>
16
14
  <Text style={theme.typography.regular.md}>Por tu</Text>
@@ -13,7 +13,6 @@ const SelectOption = ({ item, isSelected, onSelect, theme }) => {
13
13
  return (
14
14
  <Pressable
15
15
  onPress={() => onSelect(item)}
16
- // Eventos de Hover para Web
17
16
  onMouseEnter={() => Platform.OS === 'web' && setIsHovered(true)}
18
17
  onMouseLeave={() => Platform.OS === 'web' && setIsHovered(false)}
19
18
  style={({ pressed }) => [
@@ -45,12 +44,8 @@ const SelectOption = ({ item, isSelected, onSelect, theme }) => {
45
44
  export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null }) => {
46
45
  const { theme } = useTheme();
47
46
  const [isOpen, setIsOpen] = useState(false);
48
- const [dropdownTop, setDropdownTop] = useState(0);
49
- const [dropdownWidth, setDropdownWidth] = useState(0);
50
- const [dropdownLeft, setDropdownLeft] = useState(0);
51
-
52
- const containerRef = useRef(null);
53
47
  const animatedValue = useRef(new Animated.Value(0)).current;
48
+ const containerRef = useRef(null);
54
49
 
55
50
  const [selectedItem, setSelectedItem] = useState(
56
51
  options.find(opt => opt.value === selectedValue) || null
@@ -63,36 +58,49 @@ export const CDSSelect = ({ label, options = [], onSelect, selectedValue = null
63
58
 
64
59
  const toggleDropdown = () => {
65
60
  if (isOpen) {
66
- Animated.timing(animatedValue, { toValue: 0, duration: 200, useNativeDriver: true })
67
- .start(() => setIsOpen(false));
61
+ Animated.timing(animatedValue, {
62
+ toValue: 0,
63
+ duration: 250,
64
+ useNativeDriver: false
65
+ }).start(() => setIsOpen(false));
68
66
  } else {
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();
67
+ setIsOpen(true);
68
+ requestAnimationFrame(() => {
69
+ Animated.timing(animatedValue, {
70
+ toValue: 1,
71
+ duration: 300,
72
+ useNativeDriver: false
73
+ }).start();
75
74
  });
76
75
  }
77
76
  };
78
77
 
79
- const handleSelect = (item) => {
80
- // 1. Actualizamos localmente PRIMERO para que el cambio sea instantáneo
78
+ const handleSelect = (item) => {
81
79
  setSelectedItem(item);
82
-
83
- // 2. Cerramos el menú
84
80
  toggleDropdown();
85
-
86
- // 3. Notificamos al padre
87
- if (onSelect) {
88
- // Enviamos el valor (o el objeto, según prefieras)
89
- onSelect(item.value);
90
- }
81
+ if (onSelect) onSelect(item.value);
82
+ };
83
+
84
+ // Estructura idéntica a CardFeedback para mantener consistencia
85
+ const animatedLayout = {
86
+ maxHeight: animatedValue.interpolate({
87
+ inputRange: [0, 1],
88
+ outputRange: [0, 240]
89
+ }),
90
+ opacity: animatedValue,
91
+ marginTop: animatedValue.interpolate({
92
+ inputRange: [0, 1],
93
+ outputRange: [0, theme.space.sm]
94
+ }),
91
95
  };
92
96
 
93
97
  return (
94
98
  <View style={styles.mainContainer} ref={containerRef} collapsable={false}>
95
- {label && <Text style={[theme.typography.label, { color: theme.text.neutral.primary, marginBottom: theme.space.sm }]}>{label}</Text>}
99
+ {label && (
100
+ <Text style={[theme.typography.label, { color: theme.text.neutral.primary, marginBottom: theme.space.sm }]}>
101
+ {label}
102
+ </Text>
103
+ )}
96
104
 
97
105
  <TouchableOpacity
98
106
  activeOpacity={0.8}
@@ -114,15 +122,15 @@ const handleSelect = (item) => {
114
122
  </TouchableOpacity>
115
123
 
116
124
  {isOpen && (
117
- <View
125
+ <Animated.View
118
126
  style={[
119
127
  styles.dropdownInline,
128
+ animatedLayout, // Aplicamos el objeto de animación aquí
120
129
  {
121
- marginTop: theme.space.sm,
122
130
  backgroundColor: theme.surface.neutral.primary,
123
131
  borderRadius: theme.radius.sm,
124
132
  borderColor: theme.outline.neutral.primary,
125
- maxHeight: 240,
133
+ overflow: 'hidden',
126
134
  }
127
135
  ]}
128
136
  >
@@ -137,10 +145,9 @@ const handleSelect = (item) => {
137
145
  theme={theme}
138
146
  />
139
147
  )}
140
- // Importante para que el scroll funcione dentro de la lista expandida
141
148
  nestedScrollEnabled={true}
142
149
  />
143
- </View>
150
+ </Animated.View>
144
151
  )}
145
152
  </View>
146
153
  );
@@ -149,7 +156,9 @@ const handleSelect = (item) => {
149
156
  const styles = StyleSheet.create({
150
157
  mainContainer: { width: '100%' },
151
158
  inputContainer: { height: 48, borderWidth: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
152
- dropdownInline: { borderWidth: 1},
159
+ dropdownInline: {
160
+ borderWidth: 1,
161
+ },
153
162
  optionItem: { height: 48, justifyContent: 'center' },
154
163
  optionContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }
155
164
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cdslibrary",
3
3
  "license": "0BSD",
4
- "version": "1.2.47",
4
+ "version": "1.2.49",
5
5
  "main": "index.js",
6
6
  "author": "Nat Viramontes",
7
7
  "description": "A library of components for the CDS project",
@@ -1,5 +1,3 @@
1
- import { useFonts, Inter_400Regular, Inter_600SemiBold, Inter_700Bold } from "@expo-google-fonts/inter";
2
-
3
1
  import { CDSprimitiveFontValues } from "./CDSprimitiveTokens";
4
2
 
5
3
 
@@ -67,7 +65,7 @@ export const getSemanticTextStyles = (theme, isMobile) => {
67
65
  sm: {
68
66
  fontFamily: CDSprimitiveFontValues.family.interRegular,
69
67
  fontSize: getVal('size', 'sm'),
70
- lineHeight: getVal('lineHeight', 'xs'), // Usando xs ya que sm no estaba en tu lista
68
+ lineHeight: getVal('lineHeight', 'sm'), // Usando xs ya que sm no estaba en tu lista
71
69
  color: theme.text.neutral.primary,
72
70
  },
73
71
  xs: {