cdslibrary 1.1.2 → 1.1.4

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.
@@ -0,0 +1,150 @@
1
+ import React, { useState } from "react";
2
+ import { View, Text, StyleSheet, Pressable } from "react-native";
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ withTiming,
7
+ Easing,
8
+ interpolate,
9
+ } from "react-native-reanimated";
10
+ import { useTheme } from "../context/CDSThemeContext";
11
+ import { MaterialCommunityIcons } from "@expo/vector-icons";
12
+
13
+ export const CDSAccordion = ({ title, description, children, defaultExpanded = false }) => {
14
+ const { theme } = useTheme();
15
+ const [expanded, setExpanded] = useState(defaultExpanded);
16
+ const [contentHeight, setContentHeight] = useState(0); // Altura real medida
17
+
18
+ const animation = useSharedValue(defaultExpanded ? 1 : 0);
19
+
20
+ const toggleAccordion = () => {
21
+ const newValue = !expanded;
22
+ setExpanded(newValue);
23
+
24
+ animation.value = withTiming(newValue ? 1 : 0, {
25
+ duration: 250,
26
+ easing: Easing.bezier(0.4, 0, 0.2, 1)
27
+ });
28
+ };
29
+
30
+ // Función que captura el tamaño real del contenido al renderizarse
31
+ const onLayout = (event) => {
32
+ const { height } = event.nativeEvent.layout;
33
+ // Solo actualizamos si la altura es distinta de cero y ha cambiado
34
+ if (height > 0 && height !== contentHeight) {
35
+ setContentHeight(height);
36
+ }
37
+ };
38
+
39
+ const bodyStyle = useAnimatedStyle(() => {
40
+ return {
41
+ opacity: animation.value,
42
+ // Animamos de 0 a la altura medida exactamente
43
+ height: interpolate(animation.value, [0, 1], [0, contentHeight]),
44
+ };
45
+ });
46
+
47
+ return (
48
+ <View style={[
49
+ styles.container,
50
+ {
51
+ backgroundColor: theme.surface.neutral.primary,
52
+ borderRadius: theme.radius.lg,
53
+ borderColor: theme.outline.neutral.primary,
54
+ borderWidth: 1
55
+ }
56
+ ]}>
57
+ <Pressable
58
+ onPress={toggleAccordion}
59
+ style={[
60
+ styles.header,
61
+ {
62
+ borderTopRightRadius: theme.radius.lg,
63
+ borderTopLeftRadius: theme.radius.lg,
64
+ // Si no está expandido, redondeamos también abajo
65
+ borderBottomRightRadius: expanded ? 0 : theme.radius.lg,
66
+ borderBottomLeftRadius: expanded ? 0 : theme.radius.lg,
67
+ },
68
+ expanded && {
69
+ backgroundColor: theme.surface.brand.primary,
70
+ borderBottomWidth: 1,
71
+ borderBottomColor: theme.outline.brand.primary
72
+ }
73
+ ]}
74
+ >
75
+ <Text style={[styles.title, theme.typography.semiBold.md]}>
76
+ {title}
77
+ </Text>
78
+ <MaterialCommunityIcons
79
+ name={expanded ? "minus" : "plus"}
80
+ size={24}
81
+ color={theme.text.neutral.primary}
82
+ />
83
+ </Pressable>
84
+
85
+ <Animated.View style={[styles.content, bodyStyle, { overflow: 'hidden' }]}>
86
+ {/* CONTENEDOR DE MEDICIÓN: Es invisible y absoluto, solo sirve para saber cuánto mide el children */}
87
+ <View
88
+ onLayout={onLayout}
89
+ style={styles.measureContainer}
90
+ pointerEvents="none"
91
+ >
92
+ <View style={styles.paddingWrapper}>
93
+ {description && (
94
+ <Text style={[styles.description, theme.typography.regular.sm]}>
95
+ {description}
96
+ </Text>
97
+ )}
98
+ {children}
99
+ </View>
100
+ </View>
101
+
102
+ {/* CONTENEDOR VISIBLE: El que el usuario realmente ve animarse */}
103
+ <View style={styles.paddingWrapper}>
104
+ {description && (
105
+ <Text style={[styles.description, theme.typography.regular.sm]}>
106
+ {description}
107
+ </Text>
108
+ )}
109
+ <View style={styles.children}>{children}</View>
110
+ </View>
111
+ </Animated.View>
112
+ </View>
113
+ );
114
+ };
115
+
116
+ const styles = StyleSheet.create({
117
+ container: {
118
+ width: "100%",
119
+ marginBottom: 16,
120
+ },
121
+ header: {
122
+ flexDirection: "row",
123
+ justifyContent: "space-between",
124
+ alignItems: "center",
125
+ padding: 16,
126
+ minHeight: 56,
127
+ },
128
+ title: {
129
+ flex: 1
130
+ },
131
+ content: {
132
+ paddingHorizontal: 16,
133
+ },
134
+ measureContainer: {
135
+ position: 'absolute',
136
+ left: 16,
137
+ right: 16,
138
+ opacity: 0, // No se ve, pero ocupa espacio para onLayout
139
+ },
140
+ paddingWrapper: {
141
+ paddingBottom: 16,
142
+ paddingTop: 8,
143
+ },
144
+ description: {
145
+ marginBottom: 8,
146
+ },
147
+ children: {
148
+ width: '100%',
149
+ }
150
+ });
@@ -0,0 +1,80 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { View, StyleSheet, Modal } from "react-native";
3
+ import LottieView from "lottie-react-native";
4
+ import Animated, {
5
+ useSharedValue,
6
+ useAnimatedStyle,
7
+ withTiming,
8
+ withDelay, // 👈 Importamos withDelay
9
+ runOnJS
10
+ } from "react-native-reanimated";
11
+ import { useTheme } from "../context/CDSThemeContext";
12
+
13
+ export const CDSLoader = ({ visible = false }) => {
14
+ const { theme } = useTheme();
15
+ const [shouldRender, setShouldRender] = useState(visible);
16
+ const opacity = useSharedValue(0);
17
+
18
+ useEffect(() => {
19
+ if (visible) {
20
+ setShouldRender(true);
21
+ // Entrada rápida para que no se sienta lag al empezar
22
+ opacity.value = withTiming(1, { duration: 300 });
23
+ } else {
24
+ // 1. Espera 800ms antes de hacer nada (Sostenido)
25
+ // 2. Desvanece lentamente en 1200ms
26
+ opacity.value = withDelay(
27
+ 1000,
28
+ withTiming(0, { duration: 1200 }, (isFinished) => {
29
+ if (isFinished) {
30
+ runOnJS(setShouldRender)(false);
31
+ }
32
+ })
33
+ );
34
+ }
35
+ }, [visible]);
36
+
37
+ const animatedStyle = useAnimatedStyle(() => ({
38
+ opacity: opacity.value,
39
+ }));
40
+
41
+ if (!shouldRender && !visible) return null;
42
+
43
+ return (
44
+ <Modal transparent visible={shouldRender} animationType="none">
45
+ <Animated.View style={[styles.overlay, animatedStyle,
46
+ { backgroundColor: theme.overlay },]}>
47
+ <View
48
+ style={[
49
+ styles.loaderContainer,
50
+ ]}
51
+ >
52
+ <LottieView
53
+ autoPlay
54
+ loop
55
+ source={require("../assets/animations/animationLoaderWhite.json")}
56
+ resizeMode="contain"
57
+ />
58
+ </View>
59
+ </Animated.View>
60
+ </Modal>
61
+ );
62
+ };
63
+
64
+ const styles = StyleSheet.create({
65
+ overlay: {
66
+ height: '100%',
67
+ width: '100%',
68
+ justifyContent: "center",
69
+ alignItems: "center",
70
+ zIndex: 1000,
71
+ },
72
+ loaderContainer: {
73
+ padding: 20,
74
+ // O si usas fijos, asegúrate que sean mayores al estilo del Lottie
75
+ minWidth: 180,
76
+ minHeight: 180,
77
+ justifyContent: "center",
78
+ alignItems: "center",
79
+ },
80
+ });
@@ -0,0 +1,64 @@
1
+
2
+ import { View, Text, StyleSheet, } from "react-native";
3
+ import { useTheme } from "../context/CDSThemeContext";
4
+ import { Dimensions } from "react-native";
5
+
6
+
7
+ const { width } = Dimensions.get("window");
8
+ const isMobile = width <= 768
9
+
10
+ export const CDSOffer = ({ description, minValue, maxValue }) => {
11
+
12
+ const { theme } = useTheme();
13
+ console.log(theme)
14
+ return (
15
+ <View style={[styles.mainContainer, { gap: theme.space.xs }]}>
16
+ <Text style={theme.typography.regular.md}>Por tu</Text>
17
+ <Text style={theme.typography.semiBold.lg}>{description}</Text>
18
+ <Text style={theme.typography.regular.md}>Hoy podrías obtener:</Text>
19
+ <View style={[styles.minMaxContainer, { flexDirection: isMobile ? 'column' : 'row' }]}>
20
+ <View style={[styles.containerMin, { backgroundColor: theme.surface.neutral.secondary, borderColor: theme.outline.neutral.tertiaryVariant, gap: theme.space.xs, padding: theme.space.sm }]}>
21
+ <Text style={theme.typography.regular.md}>Desde</Text>
22
+ <Text style={[theme.typography.h3, { textAlign: 'center' }]}>{`$${minValue}`}</Text>
23
+ </View>
24
+ <View style={[styles.containerMax, { marginTop: isMobile ? -theme.space.xs : 0, marginLeft: isMobile ? 0 : -theme.space.xl, backgroundColor: theme.surface.brand.primaryVariant, borderColor: theme.outline.brand.secondary, gap: theme.space.xs, padding: theme.space.sm }]}>
25
+ <Text style={[theme.typography.regular.md, { color: theme.text.brand.primary }]}>Hasta</Text>
26
+ <Text style={[theme.typography.h1, { textAlign: 'center', color: theme.text.brand.primary }]}>{`$${maxValue}`}</Text>
27
+ </View>
28
+ </View>
29
+ </View>
30
+ )
31
+ }
32
+
33
+
34
+ const styles = StyleSheet.create({
35
+
36
+ mainContainer: {
37
+ width: '100%',
38
+ maxWidth: 800,
39
+ alignItems: 'center',
40
+ },
41
+
42
+ minMaxContainer: {
43
+ width: '100%',
44
+ flexDirection: 'row',
45
+
46
+ },
47
+
48
+ containerMin: {
49
+ flexGrow: 1,
50
+ justifyContent: 'space-around',
51
+ borderRadius: 8,
52
+ flexDirection: 'column',
53
+ borderWidth: 1,
54
+ },
55
+
56
+ containerMax: {
57
+ flexGrow: 1,
58
+ justifyContent: 'space-around',
59
+ borderRadius: 8,
60
+ flexDirection: 'column',
61
+ borderWidth: 1,
62
+ },
63
+
64
+ });
@@ -48,7 +48,6 @@ export const CDSSelect = ({ label, options = [], onSelect, placeholder = "Selecc
48
48
  },
49
49
  ],
50
50
  };
51
- console.log(theme);
52
51
  return (
53
52
  <View style={[styles.mainContainer, { zIndex: isOpen ? 1000 : 1, gap: theme.space.xs }]}>
54
53
  {label && <Text style={[theme.typography.label, { color: theme.text.neutral.primary }]}>{label}</Text>}
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet, ScrollView, useWindowDimensions } from 'react-native';
3
+ import { useTheme } from '../context/CDSThemeContext';
4
+
5
+ export const CDSTable = ({ columns, data, breakpoint = 500 }) => {
6
+ const { theme } = useTheme();
7
+ const { width: screenWidth } = useWindowDimensions();
8
+
9
+ // Aquí definimos que "Mobile" es cualquier pantalla menor al breakpoint
10
+ const isSmallScreen = screenWidth < breakpoint;
11
+
12
+ const TableContent = (
13
+ <View style={[
14
+ styles.tableContainer,
15
+ {
16
+ borderColor: theme.outline.neutral.primary,
17
+ borderRadius: theme.radius.md,
18
+ // Si es pantalla pequeña, ancho libre para que crezca a la derecha.
19
+ // Si es pantalla grande, ocupa el 100% del padre.
20
+ width: isSmallScreen ? undefined : '100%',
21
+ }
22
+ ]}>
23
+ {/* Encabezado */}
24
+ <View style={[
25
+ styles.row,
26
+ styles.header,
27
+ { backgroundColor: theme.surface.neutral.primaryVariant, borderBottomColor: theme.outline.neutral.primary }
28
+ ]}>
29
+ {columns.map((col, index) => (
30
+ <View
31
+ key={`header-${index}`}
32
+ style={[
33
+ styles.cell,
34
+ { padding: theme.space.xs },
35
+ col.width ? { width: col.width } : { flex: 1 }
36
+ ]}
37
+ >
38
+ <Text style={[styles.headerText, theme.typography.semiBold.md]}>
39
+ {col.title}
40
+ </Text>
41
+ </View>
42
+ ))}
43
+ </View>
44
+
45
+ {/* Filas */}
46
+ {data.map((item, rowIndex) => (
47
+ <View
48
+ key={`row-${rowIndex}`}
49
+ style={[
50
+ styles.row,
51
+ rowIndex !== data.length - 1 && { borderBottomWidth: 1 },
52
+ { borderBottomColor: theme.outline.neutral.primary }
53
+ ]}
54
+ >
55
+ {columns.map((col, colIndex) => (
56
+ <View
57
+ key={`cell-${rowIndex}-${colIndex}`}
58
+ style={[
59
+ styles.cell,
60
+ { padding: theme.space.xs },
61
+ col.width ? { width: col.width } : { flex: 1 }
62
+ ]}
63
+ >
64
+ <Text style={[styles.cellText, theme.typography.regular.md]}>
65
+ {item[col.key]}
66
+ </Text>
67
+ </View>
68
+ ))}
69
+ </View>
70
+ ))}
71
+ </View>
72
+ );
73
+
74
+ if (isSmallScreen) {
75
+ return (
76
+ <View style={styles.scrollWrapper}>
77
+ <ScrollView
78
+ horizontal
79
+ showsHorizontalScrollIndicator={true}
80
+ // Importante: No ponemos alignItems: 'flex-start' aquí para que no muerda el final
81
+ contentContainerStyle={styles.scrollContent}
82
+ >
83
+ {TableContent}
84
+ </ScrollView>
85
+ </View>
86
+ );
87
+ }
88
+
89
+ return (
90
+ <View style={styles.fullWidthContainer}>
91
+ {TableContent}
92
+ </View>
93
+ );
94
+ };
95
+
96
+ const styles = StyleSheet.create({
97
+ scrollWrapper: {
98
+ width: '100%',
99
+ },
100
+ scrollContent: {
101
+ // Asegura que la tabla pueda expandirse horizontalmente sin restricciones
102
+ minWidth: '100%',
103
+ paddingRight: 20, // Espacio extra al final del scroll para que la última columna no pegue
104
+ },
105
+ fullWidthContainer: {
106
+ width: '100%',
107
+ },
108
+ tableContainer: {
109
+ borderWidth: 1,
110
+ overflow: 'hidden',
111
+ },
112
+ row: {
113
+ flexDirection: 'row',
114
+ },
115
+ header: {
116
+ borderBottomWidth: 1,
117
+ },
118
+ cell: {
119
+ justifyContent: 'center',
120
+ // Ancho mínimo por columna para que siempre sea legible en scroll
121
+ minWidth: 100,
122
+ },
123
+ headerText: {
124
+ textAlign: 'center',
125
+ },
126
+ cellText: {
127
+ textAlign: 'center',
128
+ },
129
+ });
package/index.js CHANGED
@@ -15,6 +15,9 @@ export {CDSNavBar} from './components/CDSNavBar';
15
15
  export {CDSCardFeedback} from './components/CDSCardFeedback';
16
16
  export {CDSSplashScreen} from './components/CDSSplashScreen';
17
17
  export {CDSButtonGroup} from './components/CDSButtonGroup';
18
- export {CDSSelect} from './components/CDSSelect';
18
+ export {CDSSelect} from './components/CDSSelect';
19
+ export {CDSLoader} from './components/CDSLoader';
20
+ export {CDSOffer} from './components/CDSOffer';
21
+ export {CDSTable} from './components/CDSTable';
19
22
 
20
23
  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.1.2",
4
+ "version": "1.1.4",
5
5
  "main": "index.js",
6
6
  "author": "Nat Viramontes",
7
7
  "description": "A library of components for the CDS project",
@@ -25,7 +25,8 @@
25
25
  "react": "19.1.0",
26
26
  "react-dom": "^19.1.0",
27
27
  "react-native": "0.81.5",
28
- "react-native-reanimated": "^4.2.1",
28
+ "react-native-gesture-handler": "~2.20.2",
29
+ "react-native-reanimated": "~3.16.1",
29
30
  "react-native-safe-area-context": "~5.6.0",
30
31
  "react-native-screens": "~4.16.0",
31
32
  "react-native-web": "^0.21.0"
@@ -2,8 +2,8 @@ import { CDSprimitiveColors } from "./CDSprimitiveTokens";
2
2
 
3
3
  export const CDSsemanticColors = {
4
4
  light: {
5
- logo:{
6
- source:"../assets/logoMonte.png"
5
+ logo: {
6
+ source: "../assets/logoMonte.png"
7
7
  },
8
8
  icon: {
9
9
  neutral: {
@@ -14,9 +14,6 @@ export const CDSsemanticColors = {
14
14
  tertiaryVariant: CDSprimitiveColors.neutral[600],
15
15
  secondaryVariant: CDSprimitiveColors.neutral[100],
16
16
  },
17
- brand: {
18
- primary: CDSprimitiveColors.brand[600],
19
- },
20
17
  feedback: {
21
18
  info: CDSprimitiveColors.blue[300],
22
19
  success: CDSprimitiveColors.green[300],
@@ -30,6 +27,13 @@ export const CDSsemanticColors = {
30
27
  primary: CDSprimitiveColors.neutral[800],
31
28
  pressed: CDSprimitiveColors.neutral[1000],
32
29
  },
30
+ brand:{
31
+ primary: CDSprimitiveColors.brand[50],
32
+ primaryVariant: CDSprimitiveColors.brand[100],
33
+ secondary: CDSprimitiveColors.brand[200],
34
+ tertiary: CDSprimitiveColors.brand[400],
35
+ tertiaryVariant: CDSprimitiveColors.brand[800],
36
+ },
33
37
  feedback: {
34
38
  info: CDSprimitiveColors.blue[100],
35
39
  success: CDSprimitiveColors.green[100],
@@ -104,10 +108,11 @@ export const CDSsemanticColors = {
104
108
  error: CDSprimitiveColors.scarlet[300],
105
109
  },
106
110
  },
111
+ overlay: CDSprimitiveColors.overlay.light,
107
112
  },
108
113
  dark: {
109
- logo:{
110
- source:'../assets/logoMonteW.png'
114
+ logo: {
115
+ source: '../assets/logoMonteW.png'
111
116
  },
112
117
  icon: {
113
118
  neutral: {
@@ -118,9 +123,6 @@ export const CDSsemanticColors = {
118
123
  tertiaryVariant: CDSprimitiveColors.neutral[600],
119
124
  secondaryVariant: CDSprimitiveColors.neutral[975],
120
125
  },
121
- brand: {
122
- primary: CDSprimitiveColors.brand[150],
123
- },
124
126
  feedback: {
125
127
  info: CDSprimitiveColors.blue[350],
126
128
  success: CDSprimitiveColors.green[350],
@@ -134,6 +136,15 @@ export const CDSsemanticColors = {
134
136
  primary: CDSprimitiveColors.neutral[200],
135
137
  pressed: CDSprimitiveColors.neutral[100],
136
138
  },
139
+
140
+ brand:{
141
+ primary: CDSprimitiveColors.neutral[900],
142
+ primaryVariant: CDSprimitiveColors.brand[800],
143
+ secondary: CDSprimitiveColors.brand[150],
144
+ tertiary: CDSprimitiveColors.brand[400],
145
+ tertiaryVariant: CDSprimitiveColors.brand[700],
146
+ },
147
+
137
148
  feedback: {
138
149
  info: CDSprimitiveColors.blue[250],
139
150
  success: CDSprimitiveColors.green[250],
@@ -197,7 +208,7 @@ export const CDSsemanticColors = {
197
208
  placeholder: CDSprimitiveColors.neutral[300],
198
209
  },
199
210
  brand: {
200
- primary: CDSprimitiveColors.brand[150],
211
+ primary: CDSprimitiveColors.brand[50],
201
212
  secondary: CDSprimitiveColors.brand[700],
202
213
  tertiary: CDSprimitiveColors.brand[900],
203
214
  },
@@ -208,5 +219,6 @@ export const CDSsemanticColors = {
208
219
  error: CDSprimitiveColors.scarlet[350],
209
220
  },
210
221
  },
222
+ overlay: CDSprimitiveColors.overlay.dark,
211
223
  },
212
224
  };