cdslibrary 1.2.49 → 1.2.51

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.
@@ -1,10 +1,22 @@
1
- import React, { useEffect, useState, forwardRef } from "react";
2
- import { View, Text, Animated, Dimensions, StyleSheet, Pressable, Platform, ScrollView } from "react-native";
1
+ import React, { useEffect, forwardRef, useCallback } from "react";
2
+ import { View, Text, Dimensions, StyleSheet, Pressable, Platform, ScrollView } from "react-native";
3
3
  import { CDSButton } from "./CDSButton";
4
4
  import { MaterialIcons } from "@expo/vector-icons";
5
5
  import { useTheme } from "../context/CDSThemeContext";
6
6
  import { LinearGradient } from 'expo-linear-gradient';
7
7
 
8
+ // --- NUEVO: Imports de Reanimated y Gesture Handler ---
9
+ import Animated, {
10
+ useSharedValue,
11
+ useAnimatedStyle,
12
+ withSpring,
13
+ withTiming,
14
+ runOnJS,
15
+ interpolate,
16
+ Extrapolation
17
+ } from "react-native-reanimated";
18
+ import { GestureDetector, Gesture } from "react-native-gesture-handler";
19
+
8
20
  const { height, width } = Dimensions.get("window");
9
21
 
10
22
  const bottomSheetRender = ({
@@ -20,145 +32,180 @@ const bottomSheetRender = ({
20
32
  onFinish,
21
33
  }, ref) => {
22
34
  const { theme } = useTheme();
23
- const isMobile = theme.isMobile
24
- const [modalVisible, setModalVisible] = useState(isVisible);
25
- const [modalOpacity] = useState(new Animated.Value(0));
26
- const [slidePosition] = useState(new Animated.Value(isMobile ? height : width));
27
-
28
- // Función unificada para cerrar con animación
29
- const handleClose = () => {
30
- const targetValue = isMobile ? height : width;
31
-
32
- Animated.parallel([
33
- Animated.timing(modalOpacity, {
34
- toValue: 0,
35
- duration: 350,
36
- useNativeDriver: false,
37
- }),
38
- Animated.timing(slidePosition, {
39
- toValue: targetValue,
40
- duration: 350,
41
- useNativeDriver: false,
42
- }),
43
- ]).start(() => {
44
- // Importante: onFinish se ejecuta SOLO cuando la animación termina
45
- if (onFinish) onFinish();
46
- if (primaryButtonOnPress) primaryButtonOnPress();
47
- setModalVisible(false);
35
+ const isMobile = theme.isMobile;
36
+
37
+ // --- 1. Estado de Animación (Reanimated SharedValues) ---
38
+ const startPos = isMobile ? height : width; // Abajo en mobile, Derecha en desktop
39
+ const translation = useSharedValue(startPos);
40
+ const opacity = useSharedValue(0);
41
+
42
+ // --- 2. Funciones de Cierre (Wrapper para JS Thread) ---
43
+ const triggerOnFinish = useCallback(() => {
44
+ if (onFinish) onFinish();
45
+ if (primaryButtonOnPress) primaryButtonOnPress();
46
+ }, [onFinish, primaryButtonOnPress]);
47
+
48
+ const handleClose = useCallback(() => {
49
+ "worklet"; // Marca para ejecutar en UI thread
50
+ translation.value = withTiming(startPos, { duration: 300 }, (finished) => {
51
+ if (finished) {
52
+ runOnJS(triggerOnFinish)();
53
+ }
48
54
  });
49
- };
55
+ opacity.value = withTiming(0, { duration: 300 });
56
+ }, [startPos, triggerOnFinish]);
50
57
 
58
+ // --- 3. Lógica del Gesto (Solo Mobile) ---
59
+ const pan = Gesture.Pan()
60
+ .enabled(isMobile) // Desactivamos gesto en Desktop
61
+ .onUpdate((event) => {
62
+ // Solo permitir arrastrar hacia abajo (valores positivos)
63
+ if (event.translationY > 0) {
64
+ translation.value = event.translationY;
65
+ }
66
+ })
67
+ .onEnd((event) => {
68
+ // Si arrastra más de 100px o hace un "flick" rápido -> CERRAR
69
+ if (event.translationY > 100 || event.velocityY > 600) {
70
+ handleClose();
71
+ } else {
72
+ // Si no, rebota a posición original
73
+ translation.value = withSpring(0, { damping: 15, stiffness: 100 });
74
+ }
75
+ });
76
+
77
+ // --- 4. Efectos de Entrada/Salida ---
51
78
  useEffect(() => {
52
- if (modalVisible) {
53
- Animated.parallel([
54
- Animated.timing(modalOpacity, {
55
- toValue: 1,
56
- duration: 400,
57
- useNativeDriver: false,
58
- }),
59
- Animated.timing(slidePosition, {
60
- toValue: 0,
61
- duration: 400,
62
- useNativeDriver: false,
63
- }),
64
- ]).start();
79
+ if (isVisible) {
80
+ translation.value = withSpring(0, { damping: 15, stiffness: 90 });
81
+ opacity.value = withTiming(1, { duration: 300 });
82
+ } else {
83
+ // Si se controla false desde fuera
84
+ translation.value = withTiming(startPos, { duration: 300 });
85
+ opacity.value = withTiming(0, { duration: 300 });
65
86
  }
66
- }, [modalVisible]);
87
+ }, [isVisible, isMobile]);
88
+
89
+ // --- 5. Estilos Animados ---
90
+ const animatedStyle = useAnimatedStyle(() => {
91
+ return {
92
+ opacity: opacity.value,
93
+ transform: [
94
+ isMobile
95
+ ? { translateY: translation.value }
96
+ : { translateX: translation.value }
97
+ ],
98
+ };
99
+ });
100
+
101
+ const backdropStyle = useAnimatedStyle(() => {
102
+ // Interpolamos para que el fondo se aclare mientras arrastras
103
+ const currentY = translation.value;
104
+ const opacityVal = interpolate(currentY, [0, height], [1, 0], Extrapolation.CLAMP);
105
+ return {
106
+ opacity: opacityVal,
107
+ };
108
+ });
67
109
 
68
110
  return (
69
111
  <>
70
112
  <Animated.View
71
113
  style={[
72
- isMobile ? styles.container.typeBottomSheet : styles.container.typeDrawer,
73
- {
74
- opacity: modalOpacity,
75
- transform: [
76
- isMobile ? { translateY: slidePosition } : { translateX: slidePosition }
77
- ],
78
- backgroundColor: theme.surface.neutral.primary,
79
- paddingHorizontal: theme.space.md,
80
- paddingTop: theme.space.xl,
81
- gap: theme.space.md,
82
- ...(isMobile ? {
83
- borderTopLeftRadius: theme.radius.lg,
84
- borderTopRightRadius: theme.radius.lg,
85
- } : {
86
- borderBottomLeftRadius: theme.radius.lg,
87
- borderTopLeftRadius: theme.radius.lg,
88
- paddingBottom: theme.space.md,
89
- })
90
- }
114
+ styles.overlay,
115
+ { backgroundColor: theme.surface.special.overlay },
116
+ backdropStyle // Aplicamos estilo animado aquí
91
117
  ]}
92
118
  >
93
- {hasClose && (
94
- <MaterialIcons
95
- name="close"
96
- size={theme.typography.icon.lg}
97
- color={theme.text.neutral.primary}
98
- onPress={handleClose}
99
- style={{ position: "absolute", right: theme.space.md, top: theme.space.md, zIndex: 10 }}
100
- />
101
- )}
102
-
103
- {!!title && <Text style={theme.typography.bold.lg}>{title}</Text>}
104
-
105
- <View style={styles.scrollWrapper}>
106
- <ScrollView
107
- ref={ref}
108
- style={styles.scrollArea}
109
- contentContainerStyle={[styles.scrollContent, { paddingBottom: theme.space.xl }]}
110
- showsVerticalScrollIndicator={true}
111
- bounces={true}
112
- alwaysBounceVertical={false}
113
- scrollEventThrottle={16}
114
- >
115
- {!!description && (
116
- <Text style={[theme.typography.regular.md, { marginBottom: theme.space.md }]}>
117
- {description}
118
- </Text>
119
- )}
120
- {customSlot}
121
- </ScrollView>
122
- <LinearGradient
123
- colors={['transparent', theme.surface.neutral.primary]}
124
- style={styles.fadeGradient}
125
- pointerEvents="none"
126
- />
127
- </View>
128
-
129
- {type !== "informative" && (
130
- <View style={isMobile ? styles.actionsContainer.typeBottomSheet : styles.actionsContainer.typeDrawer}>
131
- <CDSButton
132
- label={primaryButtonLabel}
133
- onPress={() => {
134
- handleClose();
135
- }}
119
+ <Pressable onPress={() => runOnJS(handleClose)()} style={{ flex: 1 }} />
120
+ </Animated.View>
121
+
122
+ <GestureDetector gesture={pan}>
123
+ <Animated.View
124
+ style={[
125
+ isMobile ? styles.container.typeBottomSheet : styles.container.typeDrawer,
126
+ animatedStyle, // Inyectamos la animación de Reanimated
127
+ {
128
+ backgroundColor: theme.surface.neutral.primary,
129
+ // Mantenemos tu lógica exacta de padding y gaps
130
+ paddingTop: (hasClose ? theme.space['2xl'] : theme.space.md),
131
+ gap: theme.space.md,
132
+ ...(isMobile ? {
133
+ borderTopLeftRadius: theme.radius.lg,
134
+ borderTopRightRadius: theme.radius.lg,
135
+ } : {
136
+ borderBottomLeftRadius: theme.radius.lg,
137
+ borderTopLeftRadius: theme.radius.lg,
138
+ paddingBottom: theme.space.md,
139
+ })
140
+ }
141
+ ]}
142
+ >
143
+ {/* --- NUEVO: La línea visual (Handle) ---
144
+ Se agrega position absolute para NO afectar tus paddings/espacios originales */}
145
+ {isMobile && (
146
+ <View style={styles.handleBarContainer}>
147
+ <View style={[styles.handleBarLine, { backgroundColor: theme.outline.neutral.tertiary }]} />
148
+ </View>
149
+ )}
150
+
151
+ {hasClose && (
152
+ <MaterialIcons
153
+ name="close"
154
+ size={theme.typography.icon.lg}
155
+ color={theme.text.neutral.primary}
156
+ // Usamos runOnJS porque onPress espera una función normal
157
+ onPress={() => runOnJS(handleClose)()}
158
+ style={{ position: "absolute", right: theme.space.md, top: theme.space.md, zIndex: 10 }}
136
159
  />
137
- <CDSButton
138
- label={secondaryButtonLabel}
139
- type="ghost"
140
- onPress={handleClose}
160
+ )}
161
+
162
+ {!!title && <Text style={[theme.typography.h3, {marginHorizontal: theme.space.md}]}>{title}</Text>}
163
+
164
+ <View style={styles.scrollWrapper}>
165
+ <ScrollView
166
+ ref={ref}
167
+ style={styles.scrollArea}
168
+ contentContainerStyle={[styles.scrollContent, { paddingBottom: theme.space.sm }]}
169
+ showsVerticalScrollIndicator={true}
170
+ bounces={true}
171
+ alwaysBounceVertical={false}
172
+ scrollEventThrottle={16}
173
+ >
174
+ {!!description && (
175
+ <Text style={[theme.typography.regular.md, { marginBottom: theme.space.md }]}>
176
+ {description}
177
+ </Text>
178
+ )}
179
+ {customSlot}
180
+ </ScrollView>
181
+ <LinearGradient
182
+ colors={['transparent', theme.surface.neutral.primary]}
183
+ style={styles.fadeGradient}
184
+ pointerEvents="none"
141
185
  />
142
186
  </View>
143
- )}
144
- </Animated.View>
145
187
 
146
- <Animated.View
147
- style={[
148
- styles.overlay,
149
- {
150
- opacity: modalOpacity,
151
- backgroundColor: theme.surface.special.overlay,
152
- },
153
- ]}
154
- >
155
- <Pressable onPress={handleClose} style={{ flex: 1 }} />
156
- </Animated.View>
188
+ {type !== "informative" && (
189
+ <View style={[isMobile ? styles.actionsContainer.typeBottomSheet : styles.actionsContainer.typeDrawer, {paddingHorizontal: theme.space.md, paddingVertical: theme.space.lg}]}>
190
+ <CDSButton
191
+ label={primaryButtonLabel}
192
+ onPress={() => runOnJS(handleClose)()}
193
+ />
194
+ {secondaryButtonLabel && (<CDSButton
195
+ label={secondaryButtonLabel}
196
+ type="ghost"
197
+ onPress={() => runOnJS(handleClose)()}
198
+ />)}
199
+ </View>
200
+ )}
201
+ </Animated.View>
202
+ </GestureDetector>
157
203
  </>
158
204
  );
159
205
  };
160
206
 
161
207
  const styles = StyleSheet.create({
208
+ // --- Estilos originales intactos ---
162
209
  overlay: {
163
210
  position: "absolute",
164
211
  top: 0,
@@ -214,7 +261,6 @@ const styles = StyleSheet.create({
214
261
  flexDirection: "column",
215
262
  width: "100%",
216
263
  gap: 8,
217
- paddingBottom: 20,
218
264
  },
219
265
  typeDrawer: {
220
266
  flexDirection: "row-reverse",
@@ -223,7 +269,20 @@ const styles = StyleSheet.create({
223
269
  marginTop: 'auto',
224
270
  },
225
271
  },
272
+ // --- NUEVOS ESTILOS PARA EL HANDLE ---
273
+ handleBarContainer: {
274
+ position: 'absolute',
275
+ top: 8, // Un poco de margen superior
276
+ width: '100%',
277
+ alignItems: 'center',
278
+ zIndex: 1, // Debajo del botón de cerrar
279
+ },
280
+ handleBarLine: {
281
+ width: 40,
282
+ height: 4,
283
+ borderRadius: 2,
284
+ opacity: 0.5,
285
+ }
226
286
  });
227
287
 
228
-
229
288
  export const CDSBottomSheet = forwardRef(bottomSheetRender);
@@ -1,9 +1,8 @@
1
- import React from "react";
2
- import { Text, StyleSheet, TouchableOpacity } from "react-native";
1
+ import React, { useRef, useEffect } from "react";
2
+ import { Text, StyleSheet, TouchableOpacity, Animated } from "react-native";
3
3
  import { MaterialIcons } from "@expo/vector-icons";
4
4
  import { useTheme } from "../context/CDSThemeContext";
5
5
 
6
-
7
6
  export const CDSButton = ({
8
7
  label,
9
8
  type = "primary",
@@ -14,8 +13,36 @@ export const CDSButton = ({
14
13
  flexend,
15
14
  }) => {
16
15
  const { theme } = useTheme();
17
- const isMobile = theme.isMobile
18
-
16
+ const isMobile = theme.isMobile;
17
+
18
+ // 1. Referencia de animación
19
+ const animatedValue = useRef(new Animated.Value(0)).current;
20
+
21
+ // 2. Disparar entrada al montar
22
+ useEffect(() => {
23
+ Animated.timing(animatedValue, {
24
+ toValue: 1,
25
+ duration: 400,
26
+ useNativeDriver: false // Para manejar maxHeight y otros layouts
27
+ }).start();
28
+ }, []);
29
+
30
+ // 3. Estructura de animatedLayout unificada
31
+ const animatedLayout = {
32
+ opacity: animatedValue,
33
+ transform: [{
34
+ scale: animatedValue.interpolate({
35
+ inputRange: [0, 1],
36
+ outputRange: [0.95, 1]
37
+ })
38
+ }],
39
+ // Permite que el botón aparezca empujando suavemente
40
+ maxHeight: animatedValue.interpolate({
41
+ inputRange: [0, 1],
42
+ outputRange: [0, (variant === "fill" || isMobile ? 48 : 56)]
43
+ }),
44
+ };
45
+
19
46
  const backgroundColor =
20
47
  type === "disabled"
21
48
  ? theme.surface.action.disabled
@@ -31,46 +58,62 @@ export const CDSButton = ({
31
58
  : theme.text.neutral.contrast;
32
59
 
33
60
  return (
34
- <TouchableOpacity
35
- onPress={type !== "disabled" ? onPress : null}
36
- activeOpacity={0.7}
37
- style={[
38
- styles.container,
39
- { flexGrow: (variant === "fill" || isMobile ? 1 : 0), maxHeight: (variant === "fill" || isMobile ? 48 : 56), alignSelf: (variant === "fill" || isMobile) ? "stretch" : "center", paddingHorizontal: type === "icon" ? theme.space.sm : theme.space.md, paddingVertical: theme.space.sm, backgroundColor, borderRadius: theme.radius.full, gap: theme.space.sm, },
40
- flexend && { justifyContent: "flex-end", paddingHorizontal: 0 },
41
- type === "secondary" && {
42
- borderColor: theme.outline.action.primary,
43
- borderWidth: 1,
44
- },
45
- type === 'link' && {
46
- paddingHorizontal: 0,
47
- paddingVertical: 0,
48
- minWidth: 1,
49
- }
50
- ]}
51
- >
52
- {type !== "icon" && (
53
- <Text
54
- style={[
55
- type === "link"
56
- ? size === "small"
57
- ? theme.typography.link.small
58
- : theme.typography.link.regular
59
- : theme.typography.buttonText,
60
- { color: textColor },
61
- ]}
62
- >
63
- {label}
64
- </Text>
65
- )}
66
- {icon && (
67
- <MaterialIcons
68
- name={icon}
69
- size={theme.typography.icon.sm}
70
- color={textColor}
71
- />
72
- )}
73
- </TouchableOpacity>
61
+ <Animated.View style={[
62
+ animatedLayout,
63
+ {
64
+ overflow: 'hidden',
65
+ alignSelf: (variant === "fill" || isMobile) ? "stretch" : "center",
66
+ flexGrow: (variant === "fill" || isMobile ? 1 : 0),
67
+ }
68
+ ]}>
69
+ <TouchableOpacity
70
+ onPress={type !== "disabled" ? onPress : null}
71
+ activeOpacity={0.7}
72
+ style={[
73
+ styles.container,
74
+ {
75
+ paddingHorizontal: type === "icon" ? theme.space.sm : theme.space.md,
76
+ paddingVertical: theme.space.sm,
77
+ backgroundColor,
78
+ borderRadius: theme.radius.full,
79
+ gap: theme.space.sm,
80
+ height: (variant === "fill" || isMobile ? 48 : 56), // Altura fija interna
81
+ },
82
+ flexend && { justifyContent: "flex-end", paddingHorizontal: 0 },
83
+ type === "secondary" && {
84
+ borderColor: theme.outline.action.primary,
85
+ borderWidth: 1,
86
+ },
87
+ type === 'link' && {
88
+ paddingHorizontal: 0,
89
+ paddingVertical: 0,
90
+ minWidth: 1,
91
+ }
92
+ ]}
93
+ >
94
+ {type !== "icon" && (
95
+ <Text
96
+ style={[
97
+ type === "link"
98
+ ? size === "small"
99
+ ? theme.typography.link.small
100
+ : theme.typography.link.regular
101
+ : theme.typography.buttonText,
102
+ { color: textColor },
103
+ ]}
104
+ >
105
+ {label}
106
+ </Text>
107
+ )}
108
+ {icon && (
109
+ <MaterialIcons
110
+ name={icon}
111
+ size={theme.typography.icon.sm}
112
+ color={textColor}
113
+ />
114
+ )}
115
+ </TouchableOpacity>
116
+ </Animated.View>
74
117
  );
75
118
  };
76
119
 
@@ -80,5 +123,4 @@ const styles = StyleSheet.create({
80
123
  alignItems: "center",
81
124
  justifyContent: "center",
82
125
  },
83
- });
84
-
126
+ });
@@ -8,7 +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
12
  return (
13
13
  <TouchableOpacity
14
14
  onPress={onPress}
@@ -19,11 +19,11 @@ export const CDSImageButton = ({ object, isActive, onPress }) => { // 👈 Añad
19
19
  ? theme.surface.neutral.primaryVariant // Color cuando está presionado
20
20
  : theme.surface.neutral.primary,
21
21
  gap: theme.space.sm,
22
- padding: theme.space.md,
22
+ padding: theme.space.sm,
23
23
  // borderColor: isActive
24
24
  // ? theme.outline.neutral.primary // Borde de color de marca si está activo
25
25
  // : theme.outline.neutral.primary,
26
- borderRadius: theme.radius.lg,
26
+ borderRadius: theme.radius.md,
27
27
  // Si está activo, quitamos la sombra o usamos una más pequeña (md)
28
28
  ...(!isActive && theme.shadows.lg),
29
29
  // Si quieres que se vea "hundido", podrías bajar la escala
@@ -38,6 +38,7 @@ export const CDSImageButton = ({ object, isActive, onPress }) => { // 👈 Añad
38
38
  flexGrow: 1,
39
39
  width: "100%",
40
40
  height: 80,
41
+ borderRadius: theme.radius.sm,
41
42
  }}
42
43
  />
43
44
  <Text style={isActive ? theme.typography.semiBold.sm : theme.typography.regular.sm }>
@@ -55,7 +56,7 @@ mainContainer:{
55
56
  alignItems: 'center',
56
57
  flexGrow: 1,
57
58
  maxWidth: 160,
58
- minWidth: 120,
59
+ minWidth: 96,
59
60
  height: 'auto'
60
61
  }
61
62
 
@@ -3,8 +3,9 @@ import { View, Animated, StyleSheet, ScrollView } from 'react-native';
3
3
  import { CDSImageButton } from './CDSImageButton';
4
4
  import { useTheme } from "../context/CDSThemeContext";
5
5
 
6
- export const CDSImageButtonGroup = ({ array, onSelect }) => {
6
+ export const CDSImageButtonGroup = ({ array, onSelect, isCentered }) => {
7
7
  const { theme } = useTheme();
8
+ const isMobile = theme.isMobile
8
9
  const [selectedId, setSelectedId] = useState(null);
9
10
  const animatedValue = useRef(new Animated.Value(0)).current;
10
11
 
@@ -31,10 +32,6 @@ export const CDSImageButtonGroup = ({ array, onSelect }) => {
31
32
  outputRange: [0, 250]
32
33
  }),
33
34
  opacity: animatedValue,
34
- marginTop: animatedValue.interpolate({
35
- inputRange: [0, 1],
36
- outputRange: [0, theme.space.md]
37
- })
38
35
  };
39
36
 
40
37
  return (
@@ -45,14 +42,15 @@ export const CDSImageButtonGroup = ({ array, onSelect }) => {
45
42
  <ScrollView
46
43
  horizontal={true}
47
44
  showsHorizontalScrollIndicator={false}
48
- // IMPORTANTE: overflow visible para que no corte sombras,
49
- // pero necesitamos que el ScrollView sepa su límite
50
45
  style={styles.scrollView}
51
46
  contentContainerStyle={[
52
47
  styles.scrollContent,
53
48
  {
49
+ justifyContent: (isCentered ? 'center' : 'flex-start'),
50
+
54
51
  gap: theme.space.sm,
55
- paddingHorizontal: theme.space.md,
52
+ padding: theme.space.md,
53
+ width: (isMobile ? 'auto' : '100%')
56
54
  }
57
55
  ]}
58
56
  >
@@ -72,13 +70,11 @@ export const CDSImageButtonGroup = ({ array, onSelect }) => {
72
70
  const styles = StyleSheet.create({
73
71
  scrollView: {
74
72
  width: '100%',
75
- overflow: 'visible'
73
+ overflow: 'visible',
76
74
  },
77
75
  scrollContent: {
78
- // Quitamos el justifyContent: 'center' porque impide el scroll si hay pocos elementos
76
+ width: '100%',
79
77
  flexDirection: 'row',
80
78
  alignItems: 'center',
81
- paddingVertical: 10,
82
- overflow: 'visible'
83
79
  }
84
80
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cdslibrary",
3
3
  "license": "0BSD",
4
- "version": "1.2.49",
4
+ "version": "1.2.51",
5
5
  "main": "index.js",
6
6
  "author": "Nat Viramontes",
7
7
  "description": "A library of components for the CDS project",
@@ -1,14 +0,0 @@
1
- import React, { useEffect } from 'react';
2
- import { Platform } from 'react-native';
3
-
4
- const CustomSlotDebug = ({ customSlot, isVisible }) => {
5
- useEffect(() => {
6
- console.log('Platform:', Platform.OS);
7
- console.log('Custom Slot:', customSlot);
8
- console.log('Is Visible:', isVisible);
9
- }, [customSlot, isVisible]);
10
-
11
- return <>{customSlot}</>;
12
- };
13
-
14
- export default CustomSlotDebug;