cdslibrary 1.0.56 → 1.1.3

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.
@@ -4,7 +4,9 @@ import { CDSButton } from "./CDSButton";
4
4
 
5
5
  import { MaterialIcons } from "@expo/vector-icons";
6
6
  import { useTheme } from "../context/CDSThemeContext";
7
- import { getSemanticTextStyles } from "../tokens/CDSsemanticTextStyles";
7
+ import { ScrollView } from "react-native-web";
8
+ import { LinearGradient } from 'expo-linear-gradient';
9
+
8
10
 
9
11
  const { height, width } = Dimensions.get("window");
10
12
 
@@ -14,82 +16,119 @@ export const CDSBottomSheet = ({
14
16
  type,
15
17
  isVisible,
16
18
  hasClose,
17
- title,
18
- description ,
19
+ title = 'title',
20
+ description,
19
21
  customSlot,
20
- primaryButtonLabel,
22
+ primaryButtonLabel = "Aceptar",
21
23
  primaryButtonOnPress,
22
24
  secondaryButtonLabel = "Cancelar",
23
25
  onFinish,
24
26
  }) => {
25
27
 
26
- const [childHeight, setChildHeight] = useState(0);
27
28
  const handleLayout = (event) => {
28
29
  const { width } = event.nativeEvent.layout;
29
30
  setChildHeight(width); // Guardamos el ancho en el estado
30
- };
31
+ };
31
32
 
32
33
  const { theme } = useTheme();
33
- const textStyles = getSemanticTextStyles(theme);
34
34
 
35
35
  const [modalVisible, setModalVisible] = useState(isVisible);
36
36
  const [modalOpacity] = useState(new Animated.Value(0));
37
37
  const [slidePosition] = useState(new Animated.Value(height));
38
38
 
39
39
  useEffect(() => {
40
+ const targetValue = isMobile ? height : width; // Valor fuera de pantalla
41
+
40
42
  Animated.parallel([
41
43
  Animated.timing(modalOpacity, {
42
44
  toValue: modalVisible ? 1 : 0,
43
- duration: 500,
45
+ duration: 400,
44
46
  useNativeDriver: false,
45
47
  }),
46
48
  Animated.timing(slidePosition, {
47
- toValue: modalVisible ? 0 : height,
48
- duration: modalVisible ? 500 : 800,
49
+ toValue: modalVisible ? 0 : targetValue, // 0 es visible, targetValue es oculto
50
+ duration: 400,
49
51
  useNativeDriver: false,
50
52
  }),
51
53
  ]).start();
52
- }, [modalVisible]);
53
-
54
+ }, [modalVisible, isMobile]); // Añadimos isMobile a las dependencias
54
55
  return (
55
56
  <>
56
- <Animated.View onLayout={handleLayout}
57
- style={[isMobile ? styles.centeredView.typeBottomSheet : styles.centeredView.typeModal,
58
- {opacity: modalOpacity, transform: isMobile && [{ translateY: slidePosition }] },
59
- ]}
57
+ <Animated.View
58
+ onLayout={handleLayout}
59
+ style={[
60
+ isMobile ? styles.container.typeBottomSheet : styles.container.typeDrawer,
61
+ {
62
+ opacity: modalOpacity,
63
+ transform: [
64
+ isMobile
65
+ ? { translateY: slidePosition } // Sube en móvil
66
+ : { translateX: slidePosition } // Entra lateral en desktop
67
+ ],
68
+ backgroundColor: theme.surface.neutral.primary, paddingHorizontal: theme.space.sm,
69
+ paddingTop: theme.space.xl,
70
+ gap: theme.space.sm,
71
+ }, isMobile ? [styles.container.typeBottomSheet, {
72
+ borderTopLeftRadius: theme.radius.lg,
73
+ borderTopRightRadius: theme.radius.lg,
74
+
75
+ }] : [styles.container.typeDrawer, {
76
+ borderBottomLeftRadius: theme.radius.lg, borderTopLeftRadius: theme.radius.lg, paddingBottom: theme.space.md,
77
+ },
78
+ ]]}
60
79
  >
61
- <View style={[isMobile ? styles.container.typeBottomSheet : styles.container.typeModal, { backgroundColor: theme.surface.neutral.primary } ]}>
62
- {hasClose && (
63
- <MaterialIcons
64
- name={"close"}
65
- size={textStyles.icon.fontSize}
66
- color={"red"}
67
- style={{ position: "absolute", right: 16, top: 16 }}
68
- />
69
- )}
70
- {title && <Text style={textStyles.bold.lg}>{title}</Text>}
71
- {description && (
72
- <Text style={textStyles.regular.md}>{description}</Text>
73
- )}
74
- {customSlot && <View style={{ height: childHeight,}}>{customSlot}</View>}
75
- {type !== "informative" && (
76
- <View style={styles.actionsContainer}>
77
- <CDSButton
78
- label={primaryButtonLabel}
79
- onPress={
80
- onFinish
81
- ? () => settypeMVisible(!modalVisible)
82
- : primaryButtonOnPress
83
- }
84
- />
85
- <CDSButton
86
- label={secondaryButtonLabel}
87
- type="ghost"
88
- onPress={() => setModalVisible(!modalVisible)}
89
- />
90
- </View>
91
- )}
80
+
81
+ {hasClose && (
82
+ <MaterialIcons
83
+ name={"close"}
84
+ size={theme.typography.icon.lg}
85
+ color={theme.text.neutral.primary}
86
+ onPress={() => setModalVisible(false)}
87
+ style={{ position: "absolute", right: theme.space.sm, top: theme.space.sm }}
88
+ />
89
+ )}
90
+ {title && <Text style={theme.typography.bold.lg}>{title}</Text>}
91
+ <View style={styles.scrollWrapper}> {/* 👈 Nuevo View para envolver */}
92
+ <ScrollView
93
+ style={styles.scrollArea}
94
+ contentContainerStyle={[styles.scrollContent, {
95
+ flexGrow: 0,
96
+ paddingBottom: theme.space.md
97
+ }]}
98
+ showsVerticalScrollIndicator={true}
99
+ persistentScrollbar={true}
100
+ indicatorStyle={theme.name === 'dark' ? 'white' : 'black'}
101
+ >
102
+ {description && (
103
+ <Text style={[theme.typography.regular.md, { marginBottom: theme.space.md }]}>{description}</Text>
104
+ )}
105
+ {customSlot && <View style={styles.customSlot}>{customSlot}</View>}
106
+ </ScrollView>
107
+
108
+ {/* El Gradiente */}
109
+ <LinearGradient
110
+ colors={['transparent', theme.surface.neutral.primary]} // Ajusta los colores
111
+ style={styles.fadeGradient}
112
+ pointerEvents="none" // Importante para que no bloquee el click en los botones debajo
113
+ />
92
114
  </View>
115
+ {type !== "informative" && (
116
+ <View style={isMobile ? styles.actionsContainer.typeBottomSheet : styles.actionsContainer.typeDrawer}>
117
+ <CDSButton
118
+ label={primaryButtonLabel}
119
+ onPress={
120
+ onFinish
121
+ ? () => setModalVisible(!modalVisible)
122
+ : primaryButtonOnPress
123
+ }
124
+ />
125
+ <CDSButton
126
+ label={secondaryButtonLabel}
127
+ type="ghost"
128
+ onPress={() => setModalVisible(!modalVisible)}
129
+ />
130
+ </View>
131
+ )}
93
132
  </Animated.View>
94
133
  <Animated.View
95
134
  style={[
@@ -111,51 +150,91 @@ const styles = StyleSheet.create({
111
150
  position: "absolute",
112
151
  top: 0,
113
152
  left: 0,
114
- width: "110%",
115
- height: "104.4%",
116
- zIndex: 9,
153
+ width: "100%",
154
+ height: "100%"
117
155
  },
118
156
 
119
- centeredView: {
157
+ container: {
120
158
  typeBottomSheet: {
159
+ maxHeight: '90%',
121
160
  flex: 1,
122
161
  justifyContent: "flex-end",
123
162
  alignItems: "center",
124
163
  zIndex: 99,
125
- pointerEvents: "none",
126
164
  width: "100%",
165
+ position: "absolute",
166
+ bottom: 0,
127
167
  },
128
- typeModal: {
168
+ typeDrawer: {
169
+ position: "absolute", // Clave para el Drawer
170
+ right: 0, // Anclado a la derecha
171
+ top: 0,
172
+ bottom: 0,
129
173
  justifyContent: "center",
130
- alignItems: "center",
131
174
  zIndex: 99,
175
+ width: 600,
132
176
  },
133
177
  },
134
- container: {
178
+
179
+ scrollWrapper: {
180
+ flex: 1, // Ocupa todo el espacio vertical disponible
181
+ position: 'relative', // Para que el gradiente se posicione absolutamente dentro de él
182
+ width: '100%',
183
+ },
184
+ scrollArea: {
185
+ flexShrink: 1,
186
+ flexGrow: 1,
187
+ width: '100%',
188
+ paddingBottom: 20,
189
+ },
190
+
191
+ scrollContent: {
192
+ paddingBottom: 20, // Aire al final del contenido
193
+ gap: 16,
194
+ },
195
+
196
+ fadeGradient: {
197
+ position: 'absolute',
198
+ bottom: 0,
199
+ left: 0,
200
+ right: 0,
201
+ height: 60, // Ajusta la altura del gradiente según tu diseño
202
+ },
203
+
204
+ infoContainer: {
135
205
  typeBottomSheet: {
136
206
  width: "100%",
137
- borderTopLeftRadius: 16,
138
- borderTopRightRadius: 16,
139
- paddingHorizontal: 16,
140
- paddingVertical: 24,
141
- gap: 16,
142
207
  zIndex: 100,
143
208
  pointerEvents: "all",
144
209
  },
145
- typeModal: {
146
- width: 600,
147
- borderRadius: 16,
148
- paddingHorizontal: 16,
149
- paddingVertical: 24,
150
- gap: 16,
210
+ typeDrawer: {
211
+ flex: 1, // Ocupa todo el alto disponible en el Drawer
212
+ width: "100%",
151
213
  zIndex: 100,
152
214
  pointerEvents: "all",
215
+ // Opcional: una sombra elegante para Desktop
216
+ ...Platform.select({
217
+ web: { boxShadow: '-10px 0px 15px rgba(0,0,0,0.2)' }
218
+ })
153
219
  },
154
220
  },
155
221
  actionsContainer: {
156
- flex: "row",
157
- width: "100%",
158
- gap: 8,
222
+ typeBottomSheet: {
223
+ flex: "row",
224
+ width: "100%",
225
+ gap: 8,
226
+ },
227
+ typeDrawer: {
228
+ marginTop: 'auto',
229
+ flexDirection: "row-reverse",
230
+ width: "100%",
231
+ gap: 8,
232
+ },
233
+ },
234
+
235
+ customSlot: {
236
+ width: '100%',
237
+ height: 'auto',
159
238
  },
160
239
 
161
240
  });
@@ -3,7 +3,6 @@ import { Text, StyleSheet, TouchableOpacity } from "react-native";
3
3
  import { Dimensions, Platform } from "react-native";
4
4
  import { MaterialIcons } from "@expo/vector-icons";
5
5
  import { useTheme } from "../context/CDSThemeContext";
6
- import { getSemanticTextStyles } from "../tokens/CDSsemanticTextStyles";
7
6
 
8
7
  const { width } = Dimensions.get("window");
9
8
  const isMobile = width <= 768
@@ -11,13 +10,14 @@ const isMobile = width <= 768
11
10
  export const CDSButton = ({
12
11
  label,
13
12
  type = "primary",
13
+ variant,
14
14
  size,
15
15
  onPress,
16
16
  icon,
17
17
  flexend,
18
18
  }) => {
19
19
  const { theme } = useTheme();
20
- const textStyles = getSemanticTextStyles(theme);
20
+
21
21
 
22
22
  const backgroundColor =
23
23
  type === "disabled"
@@ -38,11 +38,11 @@ export const CDSButton = ({
38
38
  onPress={type !== "disabled" ? onPress : null}
39
39
  activeOpacity={0.7}
40
40
  style={[
41
- styles.container,
42
- { backgroundColor },
41
+ styles.container,
42
+ { flexGrow: (variant === "fill" ? 1 : 0), backgroundColor, borderRadius: theme.radius.full },
43
43
  flexend && { justifyContent: "flex-end", paddingHorizontal: 0 },
44
44
  {
45
- paddingHorizontal: type === "icon" ? 12 : 24,
45
+ paddingHorizontal: type === "icon" ? theme.space.xs : theme.space.sm,
46
46
  ...(isMobile && { width: "100%" })
47
47
  },
48
48
  type === "secondary" && {
@@ -52,7 +52,6 @@ export const CDSButton = ({
52
52
  type === 'link' && {
53
53
  paddingHorizontal: 0,
54
54
  paddingVertical: 0,
55
- width: 'auto',
56
55
  minWidth: 1,
57
56
  }
58
57
  ]}
@@ -62,9 +61,9 @@ export const CDSButton = ({
62
61
  style={[
63
62
  type === "link"
64
63
  ? size === "small"
65
- ? textStyles.link.small
66
- : textStyles.link.regular
67
- : textStyles.buttonText,
64
+ ? theme.typography.link.small
65
+ : theme.typography.link.regular
66
+ : theme.typography.buttonText,
68
67
  { color: textColor },
69
68
  ]}
70
69
  >
@@ -74,7 +73,7 @@ export const CDSButton = ({
74
73
  {icon && (
75
74
  <MaterialIcons
76
75
  name={icon}
77
- size={textStyles.icon.fontSize}
76
+ size={theme.typography.icon.sm}
78
77
  color={textColor}
79
78
  />
80
79
  )}
@@ -84,7 +83,7 @@ export const CDSButton = ({
84
83
 
85
84
  const styles = StyleSheet.create({
86
85
  container: {
87
- width: 'auto',
86
+ alignSelf: "center",
88
87
  flexDirection: "row",
89
88
  alignItems: "center",
90
89
  justifyContent: "center",
@@ -0,0 +1,52 @@
1
+
2
+ import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
3
+ import { Dimensions, Platform } from "react-native";
4
+ import { MaterialIcons } from "@expo/vector-icons";
5
+ import { useTheme } from "../context/CDSThemeContext";
6
+ import { CDSButton } from "./CDSButton";
7
+
8
+
9
+ import React, { useState } from "react";
10
+
11
+ export const CDSButtonGroup = ({ options, onSelect, label='Label' }) => {
12
+ const { theme } = useTheme();
13
+ const [selectedIndex, setSelectedIndex] = useState(null);
14
+
15
+ const handlePress = (index, label) => {
16
+ setSelectedIndex(index);
17
+ if (onSelect) onSelect(label);
18
+ };
19
+
20
+ return (
21
+ <View style={[styles.container, { gap: theme.space.xs }]}>
22
+ <Text style={[theme.typography.label]}>{label}</Text>
23
+ <View style={[styles.actionsContainer, { gap: theme.space.xs }]}>
24
+ {options.map((option, index) => (
25
+ <CDSButton key={index} variant={option.variant}
26
+ label={option.label}
27
+ // Si está seleccionado es 'primary', si no 'secondary'
28
+ type={selectedIndex === index ? "primary" : "secondary"}
29
+ isActive={selectedIndex === index}
30
+ onPress={() => handlePress(index, option.label)}
31
+ />
32
+
33
+ ))}
34
+ </View>
35
+ </View>
36
+ );
37
+ }
38
+
39
+ const styles = StyleSheet.create({
40
+ container:{
41
+ width: '100%',
42
+ },
43
+
44
+ actionsContainer: {
45
+ flex: 1,
46
+ flexDirection: 'row',
47
+ width: '100%',
48
+ backgroundColor: 'red',
49
+ alignItems: 'center',
50
+ justifyContent: 'center',
51
+ },
52
+ });
@@ -0,0 +1,121 @@
1
+ import { useState, useRef } from "react";
2
+ import { View, Text, StyleSheet, TouchableOpacity, Platform, Animated } from "react-native";
3
+
4
+
5
+ import { MaterialIcons } from "@expo/vector-icons";
6
+ import { useTheme } from "../context/CDSThemeContext";
7
+
8
+ export const CDSCardFeedback = ({
9
+
10
+ style,
11
+ title,
12
+ description,
13
+ secondAction,
14
+ onPressSecondAction,
15
+ onClose }) => {
16
+ const { theme } = useTheme();
17
+ const [isVisible, setIsVisible] = useState(true); // 3. Estado inicial: visible
18
+ const fadeAnim = useRef(new Animated.Value(1)).current;
19
+ // 4. Si isVisible es falso, no renderizamos nada (devolvemos null)
20
+
21
+ const handleClose = () => {
22
+ // Iniciar la animación de desvanecimiento
23
+ Animated.timing(fadeAnim, {
24
+ toValue: 0,
25
+ duration: 300,
26
+ useNativeDriver: Platform.OS !== 'web'
27
+ }).start(() => {
28
+ // Una vez que la animación termina, ocultar el componente
29
+ setIsVisible(false);
30
+ })
31
+ }
32
+ if (!isVisible) return null;
33
+
34
+ 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
+ }
66
+ const currentStyle = feedbackStyles[style] || feedbackStyles.info;
67
+
68
+ return (
69
+ <Animated.View style={[styles.container, {
70
+ backgroundColor: currentStyle.bg,
71
+ borderColor: currentStyle.border,
72
+ borderRadius: theme.radius.md,
73
+ padding: theme.space.sm,
74
+ gap: theme.space.xs,
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
+ }]}>
83
+
84
+ <View style={[styles.titleContainer, { gap: theme.space.xs }]}>
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 || 'Title'}</Text>
91
+ </View>
92
+ {description && <Text style={[styles.description, theme.typography.regular.md, { color: currentStyle.text }]}>{description}</Text>}
93
+ <View style={[styles.actionsContainer, { gap: theme.space.xs }]}>
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>
98
+ </Animated.View>
99
+ )
100
+ }
101
+
102
+ const styles = StyleSheet.create({
103
+ container: {
104
+ width: '100%',
105
+ justifyContent: 'space-between',
106
+ borderWidth: 1,
107
+ },
108
+
109
+ titleContainer: {
110
+ flexDirection: 'row',
111
+ alignItems: 'center',
112
+ },
113
+ description: {
114
+ },
115
+ actionsContainer: {
116
+ flexDirection: 'row',
117
+ width: '100%',
118
+ justifyContent: 'flex-end',
119
+
120
+ },
121
+ });
@@ -1,54 +1,61 @@
1
- import { StyleSheet, Text, TextInput, View } from "react-native";
1
+ import React, { useState } from "react";
2
+ import { View, Text, TextInput, StyleSheet } from "react-native";
2
3
  import { useTheme } from "../context/CDSThemeContext";
3
- import {getSemanticTextStyles} from "../tokens/CDSsemanticTextStyles";
4
- import { useState } from "react";
5
4
 
6
5
  export const CDSInput = ({ label, type, keyboard, placeholder }) => {
7
6
  const { theme } = useTheme();
8
- const textStyles = getSemanticTextStyles(theme);
9
7
  const [text, setText] = useState("");
8
+ const [isFocused, setIsFocused] = useState(false);
10
9
 
11
10
  return (
12
- <View style={[styles.container]}>
13
- <Text
14
- style={[
15
- textStyles.label,
16
- ]}
17
- >
18
- {label}
19
- </Text>
20
- {text === "" && (
21
- <Text style={[styles.fakePlaceholder, textStyles.inputText.placeholder]}>
22
- {placeholder}
11
+ <View style={[styles.container, { gap: theme.space.xs }]}>
12
+ {label && (
13
+ <Text style={[theme.typography.label, { color: theme.text.neutral.primary }]}>
14
+ {label}
23
15
  </Text>
24
16
  )}
17
+
25
18
  <TextInput
26
- style={[styles.textBox, textStyles.inputText.value, {backgroundColor:theme.surface.neutral.primary, borderColor: theme.outline.neutral.primary }]}
19
+ style={[
20
+ styles.textBox,
21
+ theme.typography.inputText.value, // La tipografía se aplica aquí y la hereda el placeholder
22
+ {
23
+ backgroundColor: theme.surface.neutral.primary,
24
+ // Cambia el color del borde si está en focus
25
+ borderColor: isFocused
26
+ ? theme.outline.neutral.focus : text ? theme.outline.neutral.tertiaryVariant
27
+ : theme.outline.neutral.primary,
28
+ borderRadius: theme.radius.sm,
29
+ paddingHorizontal: theme.space.xs,
30
+ color: theme.text.neutral.primary,
31
+ // Elimina el borde azul en Web/Android
32
+ outlineStyle: 'none',
33
+ },
34
+ ]}
35
+ placeholder={placeholder}
36
+ placeholderTextColor={theme.typography.inputText.placeholder}
27
37
  keyboardType={keyboard}
28
- secureTextEntry={type === "password" ? true : false}
38
+ secureTextEntry={type === "password"}
29
39
  onChangeText={setText}
30
40
  value={text}
41
+ // Manejo de estados de Focus
42
+ onFocus={() => setIsFocused(true)}
43
+ onBlur={() => setIsFocused(false)}
44
+ underlineColorAndroid="transparent"
31
45
  />
32
46
  </View>
33
47
  );
34
- }
48
+ };
35
49
 
36
50
  const styles = StyleSheet.create({
37
51
  container: {
38
52
  width: "100%",
39
- gap: 8,
40
- },
41
-
42
- fakePlaceholder: {
43
- position: "absolute",
44
- zIndex: 1,
45
- left: 14,
46
- top: 40,
47
53
  },
48
-
49
54
  textBox: {
50
- padding: 12,
51
- borderRadius: 8,
52
55
  borderWidth: 1,
56
+ height: 48,
57
+ // Asegura que la transición de color no sea brusca en web
58
+ transitionProperty: 'border-color',
59
+ transitionDuration: '0.2s',
53
60
  },
54
- });
61
+ });