cdslibrary 1.1.3 → 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,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
+ });
@@ -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.3",
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"
@@ -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],
@@ -119,9 +123,6 @@ export const CDSsemanticColors = {
119
123
  tertiaryVariant: CDSprimitiveColors.neutral[600],
120
124
  secondaryVariant: CDSprimitiveColors.neutral[975],
121
125
  },
122
- brand: {
123
- primary: CDSprimitiveColors.brand[150],
124
- },
125
126
  feedback: {
126
127
  info: CDSprimitiveColors.blue[350],
127
128
  success: CDSprimitiveColors.green[350],
@@ -135,6 +136,15 @@ export const CDSsemanticColors = {
135
136
  primary: CDSprimitiveColors.neutral[200],
136
137
  pressed: CDSprimitiveColors.neutral[100],
137
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
+
138
148
  feedback: {
139
149
  info: CDSprimitiveColors.blue[250],
140
150
  success: CDSprimitiveColors.green[250],
@@ -198,7 +208,7 @@ export const CDSsemanticColors = {
198
208
  placeholder: CDSprimitiveColors.neutral[300],
199
209
  },
200
210
  brand: {
201
- primary: CDSprimitiveColors.brand[150],
211
+ primary: CDSprimitiveColors.brand[50],
202
212
  secondary: CDSprimitiveColors.brand[700],
203
213
  tertiary: CDSprimitiveColors.brand[900],
204
214
  },