@space-uy/pulsar-ui 0.2.0

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.
Files changed (155) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +148 -0
  3. package/lib/module/components/Accordion.js +242 -0
  4. package/lib/module/components/Accordion.js.map +1 -0
  5. package/lib/module/components/BottomSheet.js +183 -0
  6. package/lib/module/components/BottomSheet.js.map +1 -0
  7. package/lib/module/components/Button.js +64 -0
  8. package/lib/module/components/Button.js.map +1 -0
  9. package/lib/module/components/ButtonContainer.js +118 -0
  10. package/lib/module/components/ButtonContainer.js.map +1 -0
  11. package/lib/module/components/CalendarPicker.js +374 -0
  12. package/lib/module/components/CalendarPicker.js.map +1 -0
  13. package/lib/module/components/Card.js +43 -0
  14. package/lib/module/components/Card.js.map +1 -0
  15. package/lib/module/components/Checkbox.js +122 -0
  16. package/lib/module/components/Checkbox.js.map +1 -0
  17. package/lib/module/components/Chip.js +50 -0
  18. package/lib/module/components/Chip.js.map +1 -0
  19. package/lib/module/components/CopyToClipboard.js +98 -0
  20. package/lib/module/components/CopyToClipboard.js.map +1 -0
  21. package/lib/module/components/Dialog.js +232 -0
  22. package/lib/module/components/Dialog.js.map +1 -0
  23. package/lib/module/components/Header.js +94 -0
  24. package/lib/module/components/Header.js.map +1 -0
  25. package/lib/module/components/Icon.js +22 -0
  26. package/lib/module/components/Icon.js.map +1 -0
  27. package/lib/module/components/IconButton.js +57 -0
  28. package/lib/module/components/IconButton.js.map +1 -0
  29. package/lib/module/components/Input.js +111 -0
  30. package/lib/module/components/Input.js.map +1 -0
  31. package/lib/module/components/InputContainer.js +104 -0
  32. package/lib/module/components/InputContainer.js.map +1 -0
  33. package/lib/module/components/LoadingIndicator.js +62 -0
  34. package/lib/module/components/LoadingIndicator.js.map +1 -0
  35. package/lib/module/components/OtpInput.js +85 -0
  36. package/lib/module/components/OtpInput.js.map +1 -0
  37. package/lib/module/components/OtpInputContainer.js +148 -0
  38. package/lib/module/components/OtpInputContainer.js.map +1 -0
  39. package/lib/module/components/Select.js +189 -0
  40. package/lib/module/components/Select.js.map +1 -0
  41. package/lib/module/components/Switch.js +74 -0
  42. package/lib/module/components/Switch.js.map +1 -0
  43. package/lib/module/components/Tabs.js +99 -0
  44. package/lib/module/components/Tabs.js.map +1 -0
  45. package/lib/module/components/Text.js +66 -0
  46. package/lib/module/components/Text.js.map +1 -0
  47. package/lib/module/components/TextArea.js +106 -0
  48. package/lib/module/components/TextArea.js.map +1 -0
  49. package/lib/module/hooks/useTheme.js +20 -0
  50. package/lib/module/hooks/useTheme.js.map +1 -0
  51. package/lib/module/index.js +27 -0
  52. package/lib/module/index.js.map +1 -0
  53. package/lib/module/package.json +1 -0
  54. package/lib/module/store/themeStore.js +50 -0
  55. package/lib/module/store/themeStore.js.map +1 -0
  56. package/lib/module/theme/colors.js +25 -0
  57. package/lib/module/theme/colors.js.map +1 -0
  58. package/lib/module/theme/meassures.js +10 -0
  59. package/lib/module/theme/meassures.js.map +1 -0
  60. package/lib/module/utils/stringUtils.js +12 -0
  61. package/lib/module/utils/stringUtils.js.map +1 -0
  62. package/lib/module/utils/uiUtils.js +63 -0
  63. package/lib/module/utils/uiUtils.js.map +1 -0
  64. package/lib/typescript/package.json +1 -0
  65. package/lib/typescript/src/components/Accordion.d.ts +22 -0
  66. package/lib/typescript/src/components/Accordion.d.ts.map +1 -0
  67. package/lib/typescript/src/components/BottomSheet.d.ts +13 -0
  68. package/lib/typescript/src/components/BottomSheet.d.ts.map +1 -0
  69. package/lib/typescript/src/components/Button.d.ts +16 -0
  70. package/lib/typescript/src/components/Button.d.ts.map +1 -0
  71. package/lib/typescript/src/components/ButtonContainer.d.ts +30 -0
  72. package/lib/typescript/src/components/ButtonContainer.d.ts.map +1 -0
  73. package/lib/typescript/src/components/CalendarPicker.d.ts +19 -0
  74. package/lib/typescript/src/components/CalendarPicker.d.ts.map +1 -0
  75. package/lib/typescript/src/components/Card.d.ts +7 -0
  76. package/lib/typescript/src/components/Card.d.ts.map +1 -0
  77. package/lib/typescript/src/components/Checkbox.d.ts +11 -0
  78. package/lib/typescript/src/components/Checkbox.d.ts.map +1 -0
  79. package/lib/typescript/src/components/Chip.d.ts +9 -0
  80. package/lib/typescript/src/components/Chip.d.ts.map +1 -0
  81. package/lib/typescript/src/components/CopyToClipboard.d.ts +12 -0
  82. package/lib/typescript/src/components/CopyToClipboard.d.ts.map +1 -0
  83. package/lib/typescript/src/components/Dialog.d.ts +40 -0
  84. package/lib/typescript/src/components/Dialog.d.ts.map +1 -0
  85. package/lib/typescript/src/components/Header.d.ts +18 -0
  86. package/lib/typescript/src/components/Header.d.ts.map +1 -0
  87. package/lib/typescript/src/components/Icon.d.ts +12 -0
  88. package/lib/typescript/src/components/Icon.d.ts.map +1 -0
  89. package/lib/typescript/src/components/IconButton.d.ts +13 -0
  90. package/lib/typescript/src/components/IconButton.d.ts.map +1 -0
  91. package/lib/typescript/src/components/Input.d.ts +17 -0
  92. package/lib/typescript/src/components/Input.d.ts.map +1 -0
  93. package/lib/typescript/src/components/InputContainer.d.ts +22 -0
  94. package/lib/typescript/src/components/InputContainer.d.ts.map +1 -0
  95. package/lib/typescript/src/components/LoadingIndicator.d.ts +9 -0
  96. package/lib/typescript/src/components/LoadingIndicator.d.ts.map +1 -0
  97. package/lib/typescript/src/components/OtpInput.d.ts +3 -0
  98. package/lib/typescript/src/components/OtpInput.d.ts.map +1 -0
  99. package/lib/typescript/src/components/OtpInputContainer.d.ts +17 -0
  100. package/lib/typescript/src/components/OtpInputContainer.d.ts.map +1 -0
  101. package/lib/typescript/src/components/Select.d.ts +20 -0
  102. package/lib/typescript/src/components/Select.d.ts.map +1 -0
  103. package/lib/typescript/src/components/Switch.d.ts +10 -0
  104. package/lib/typescript/src/components/Switch.d.ts.map +1 -0
  105. package/lib/typescript/src/components/Tabs.d.ts +14 -0
  106. package/lib/typescript/src/components/Tabs.d.ts.map +1 -0
  107. package/lib/typescript/src/components/Text.d.ts +7 -0
  108. package/lib/typescript/src/components/Text.d.ts.map +1 -0
  109. package/lib/typescript/src/components/TextArea.d.ts +16 -0
  110. package/lib/typescript/src/components/TextArea.d.ts.map +1 -0
  111. package/lib/typescript/src/hooks/useTheme.d.ts +9 -0
  112. package/lib/typescript/src/hooks/useTheme.d.ts.map +1 -0
  113. package/lib/typescript/src/index.d.ts +27 -0
  114. package/lib/typescript/src/index.d.ts.map +1 -0
  115. package/lib/typescript/src/store/themeStore.d.ts +32 -0
  116. package/lib/typescript/src/store/themeStore.d.ts.map +1 -0
  117. package/lib/typescript/src/theme/colors.d.ts +14 -0
  118. package/lib/typescript/src/theme/colors.d.ts.map +1 -0
  119. package/lib/typescript/src/theme/meassures.d.ts +9 -0
  120. package/lib/typescript/src/theme/meassures.d.ts.map +1 -0
  121. package/lib/typescript/src/utils/stringUtils.d.ts +7 -0
  122. package/lib/typescript/src/utils/stringUtils.d.ts.map +1 -0
  123. package/lib/typescript/src/utils/uiUtils.d.ts +21 -0
  124. package/lib/typescript/src/utils/uiUtils.d.ts.map +1 -0
  125. package/package.json +173 -0
  126. package/src/components/Accordion.tsx +284 -0
  127. package/src/components/BottomSheet.tsx +259 -0
  128. package/src/components/Button.tsx +85 -0
  129. package/src/components/ButtonContainer.tsx +161 -0
  130. package/src/components/CalendarPicker.tsx +428 -0
  131. package/src/components/Card.tsx +55 -0
  132. package/src/components/Checkbox.tsx +160 -0
  133. package/src/components/Chip.tsx +58 -0
  134. package/src/components/CopyToClipboard.tsx +108 -0
  135. package/src/components/Dialog.tsx +263 -0
  136. package/src/components/Header.tsx +100 -0
  137. package/src/components/Icon.tsx +27 -0
  138. package/src/components/IconButton.tsx +71 -0
  139. package/src/components/Input.tsx +144 -0
  140. package/src/components/InputContainer.tsx +134 -0
  141. package/src/components/LoadingIndicator.tsx +78 -0
  142. package/src/components/OtpInput.tsx +109 -0
  143. package/src/components/OtpInputContainer.tsx +196 -0
  144. package/src/components/Select.tsx +219 -0
  145. package/src/components/Switch.tsx +104 -0
  146. package/src/components/Tabs.tsx +117 -0
  147. package/src/components/Text.tsx +64 -0
  148. package/src/components/TextArea.tsx +141 -0
  149. package/src/hooks/useTheme.tsx +23 -0
  150. package/src/index.tsx +38 -0
  151. package/src/store/themeStore.ts +57 -0
  152. package/src/theme/colors.ts +35 -0
  153. package/src/theme/meassures.ts +7 -0
  154. package/src/utils/stringUtils.ts +16 -0
  155. package/src/utils/uiUtils.ts +70 -0
@@ -0,0 +1,160 @@
1
+ import { useEffect } from 'react';
2
+ import {
3
+ Platform,
4
+ Pressable,
5
+ StyleSheet,
6
+ View,
7
+ type ViewStyle,
8
+ type StyleProp,
9
+ } from 'react-native';
10
+ import { Check } from 'lucide-react-native';
11
+ import Animated, {
12
+ interpolateColor,
13
+ useAnimatedStyle,
14
+ useSharedValue,
15
+ withTiming,
16
+ } from 'react-native-reanimated';
17
+
18
+ import Text from './Text';
19
+
20
+ import useTheme from '../hooks/useTheme';
21
+
22
+ import { convertHexToRgba } from '../utils/uiUtils';
23
+
24
+ export type CheckboxProps = {
25
+ style?: StyleProp<ViewStyle>;
26
+ checked: boolean;
27
+ onCheckedChange: (checked: boolean) => void;
28
+ label: string;
29
+ disabled?: boolean;
30
+ description?: string;
31
+ };
32
+
33
+ export default function Checkbox({
34
+ style,
35
+ checked,
36
+ onCheckedChange,
37
+ label,
38
+ disabled = false,
39
+ description,
40
+ }: CheckboxProps) {
41
+ const { colors, theme } = useTheme();
42
+ const checkedValue = useSharedValue(checked ? 1 : 0);
43
+ const pressedValue = useSharedValue(0);
44
+
45
+ useEffect(() => {
46
+ checkedValue.value = withTiming(checked ? 1 : 0, {
47
+ duration: 200,
48
+ });
49
+ }, [checked, checkedValue]);
50
+
51
+ const checkboxAnimStyle = useAnimatedStyle(() => {
52
+ return {
53
+ borderColor: interpolateColor(
54
+ checkedValue.value,
55
+ [0, 1],
56
+ [colors.border, colors.foreground]
57
+ ),
58
+ backgroundColor: interpolateColor(
59
+ checkedValue.value,
60
+ [0, 1],
61
+ [colors.background, colors.foreground]
62
+ ),
63
+ };
64
+ });
65
+
66
+ const checkIconAnimStyle = useAnimatedStyle(() => {
67
+ return {
68
+ opacity: checkedValue.value,
69
+ };
70
+ });
71
+
72
+ const pressedColor = convertHexToRgba(colors.foreground, 0.05);
73
+
74
+ const pressableAnimStyle = useAnimatedStyle(() => {
75
+ return {
76
+ backgroundColor: interpolateColor(
77
+ pressedValue.value,
78
+ [0, 1],
79
+ ['transparent', pressedColor]
80
+ ),
81
+ };
82
+ });
83
+
84
+ const updatePressAnimation = (value: number) => {
85
+ pressedValue.value = withTiming(value, { duration: 200 });
86
+ };
87
+
88
+ const handlePressIn = () => Platform.OS !== 'web' && updatePressAnimation(1);
89
+
90
+ const handlePressOut = () => Platform.OS !== 'web' && updatePressAnimation(0);
91
+
92
+ const handlePress = () => onCheckedChange(!checked);
93
+
94
+ return (
95
+ <Pressable
96
+ style={[style, disabled && styles.disabled]}
97
+ onPress={handlePress}
98
+ onPressIn={handlePressIn}
99
+ onPressOut={handlePressOut}
100
+ onHoverIn={() => updatePressAnimation(1)}
101
+ onHoverOut={() => updatePressAnimation(0)}
102
+ disabled={disabled}
103
+ >
104
+ <Animated.View
105
+ style={[
106
+ styles.container,
107
+ !description && styles.centered,
108
+ { borderRadius: theme.roundness },
109
+ pressableAnimStyle,
110
+ ]}
111
+ >
112
+ <Animated.View
113
+ style={[
114
+ styles.box,
115
+ { borderRadius: theme.roundness },
116
+ checkboxAnimStyle,
117
+ ]}
118
+ >
119
+ <Animated.View style={checkIconAnimStyle}>
120
+ <Check
121
+ size={14}
122
+ color={checked ? colors.background : colors.foreground}
123
+ strokeWidth={3}
124
+ />
125
+ </Animated.View>
126
+ </Animated.View>
127
+ {(label || description) && (
128
+ <View style={styles.textContainer}>
129
+ <Text variant="h5">{label}</Text>
130
+ {description && (
131
+ <Text variant="ps" style={styles.description}>
132
+ {description}
133
+ </Text>
134
+ )}
135
+ </View>
136
+ )}
137
+ </Animated.View>
138
+ </Pressable>
139
+ );
140
+ }
141
+
142
+ const styles = StyleSheet.create({
143
+ disabled: { opacity: 0.5 },
144
+ centered: { alignItems: 'center' },
145
+ container: {
146
+ flexDirection: 'row',
147
+ alignItems: 'flex-start',
148
+ padding: 8,
149
+ margin: -8,
150
+ },
151
+ box: {
152
+ width: 24,
153
+ height: 24,
154
+ borderWidth: 2,
155
+ alignItems: 'center',
156
+ justifyContent: 'center',
157
+ },
158
+ textContainer: { marginLeft: 8, flex: 1 },
159
+ description: { marginTop: 2, opacity: 0.5 },
160
+ });
@@ -0,0 +1,58 @@
1
+ import { Pressable, StyleSheet, View, type PressableProps } from 'react-native';
2
+
3
+ import Text from './Text';
4
+
5
+ import useTheme from '../hooks/useTheme';
6
+
7
+ import { convertHexToRgba } from '../utils/uiUtils';
8
+
9
+ type Props = PressableProps & {
10
+ text: string;
11
+ size?: 'normal' | 'small';
12
+ disabled?: boolean;
13
+ };
14
+
15
+ export default function Chip({
16
+ text,
17
+ size = 'normal',
18
+ disabled = false,
19
+ ...rest
20
+ }: Props) {
21
+ const { theme, colors } = useTheme();
22
+
23
+ const height = size === 'small' ? 24 : 32;
24
+ const textVariant = size === 'small' ? 'ps' : 'pm';
25
+
26
+ return (
27
+ <Pressable disabled={disabled} {...rest}>
28
+ <View
29
+ style={[
30
+ styles.container,
31
+ {
32
+ backgroundColor: convertHexToRgba(colors.foreground, 0.08),
33
+ borderRadius: theme.roundness,
34
+ height,
35
+ },
36
+ disabled && styles.disabled,
37
+ ]}
38
+ >
39
+ <Text
40
+ variant={textVariant}
41
+ style={{ color: colors.foreground }}
42
+ numberOfLines={1}
43
+ >
44
+ {text}
45
+ </Text>
46
+ </View>
47
+ </Pressable>
48
+ );
49
+ }
50
+
51
+ const styles = StyleSheet.create({
52
+ container: {
53
+ justifyContent: 'center',
54
+ alignItems: 'center',
55
+ paddingHorizontal: 12,
56
+ },
57
+ disabled: { opacity: 0.3 },
58
+ });
@@ -0,0 +1,108 @@
1
+ import React, { useState } from 'react';
2
+ import { Pressable, StyleSheet, Alert, Clipboard, View } from 'react-native';
3
+ import Icon from './Icon';
4
+ import Text from './Text';
5
+ import useTheme from '../hooks/useTheme';
6
+
7
+ interface CopyToClipboardProps {
8
+ text: string;
9
+ children?: React.ReactNode;
10
+ onCopy?: () => void;
11
+ showFeedback?: boolean;
12
+ style?: any;
13
+ iconColor?: string; // Color personalizado para el ícono
14
+ }
15
+
16
+ export const CopyToClipboard: React.FC<CopyToClipboardProps> = ({
17
+ text,
18
+ children,
19
+ onCopy,
20
+ showFeedback = true,
21
+ style,
22
+ iconColor,
23
+ }) => {
24
+ const [copied, setCopied] = useState(false);
25
+ const { colors } = useTheme();
26
+
27
+ const handleCopy = () => {
28
+ try {
29
+ Clipboard.setString(text);
30
+ setCopied(true);
31
+
32
+ if (showFeedback) {
33
+ Alert.alert('Copied!', `"${text}" has been copied to clipboard`);
34
+ }
35
+
36
+ onCopy?.();
37
+
38
+ // Use setTimeout with reference for better control
39
+ setTimeout(() => {
40
+ setCopied(false);
41
+ }, 2500);
42
+ } catch (error) {
43
+ Alert.alert('Error', 'Failed to copy to clipboard');
44
+ }
45
+ };
46
+
47
+ return (
48
+ <Pressable
49
+ style={[
50
+ styles.container,
51
+ {
52
+ backgroundColor: colors.altBackground,
53
+ borderColor: colors.border,
54
+ },
55
+ style,
56
+ ]}
57
+ onPress={handleCopy}
58
+ >
59
+ <View style={styles.content}>
60
+ {children || (
61
+ <Text
62
+ variant="pm"
63
+ style={[styles.text, { color: colors.foreground }]}
64
+ >
65
+ {text}
66
+ </Text>
67
+ )}
68
+ </View>
69
+ <View style={styles.iconContainer}>
70
+ <Icon
71
+ name={copied ? 'Check' : 'Copy'}
72
+ size={16}
73
+ color={iconColor || (copied ? colors.primary : colors.foreground)}
74
+ />
75
+ </View>
76
+ </Pressable>
77
+ );
78
+ };
79
+
80
+ const styles = StyleSheet.create({
81
+ container: {
82
+ flexDirection: 'row',
83
+ alignItems: 'flex-start',
84
+ padding: 16,
85
+ borderRadius: 6,
86
+ borderWidth: 1,
87
+ minHeight: 48,
88
+ maxWidth: '100%', // Asegura que no se extienda más allá del contenedor padre
89
+ },
90
+ content: {
91
+ flex: 1,
92
+ marginRight: 12, // Aumenté para dar más espacio al ícono
93
+ minWidth: 0, // Permite que el contenido se comprima si es necesario
94
+ overflow: 'hidden', // Evita que el contenido se desborde
95
+ },
96
+ text: {
97
+ // Estilo para el texto por defecto cuando no hay children
98
+ },
99
+ iconContainer: {
100
+ flexShrink: 0, // Evita que el ícono se comprima
101
+ flexBasis: 16, // Base fija para el ícono
102
+ width: 16, // Ancho fijo del ícono
103
+ height: 16, // Alto fijo del ícono
104
+ justifyContent: 'center',
105
+ alignItems: 'center',
106
+ marginTop: 2, // Alineación con la primera línea del texto
107
+ },
108
+ });
@@ -0,0 +1,263 @@
1
+ import {
2
+ useEffect,
3
+ useState,
4
+ useCallback,
5
+ type PropsWithChildren,
6
+ type ReactNode,
7
+ } from 'react';
8
+ import {
9
+ Modal,
10
+ StyleSheet,
11
+ TouchableWithoutFeedback,
12
+ View,
13
+ type StyleProp,
14
+ type ViewStyle,
15
+ } from 'react-native';
16
+ import Animated, {
17
+ useSharedValue,
18
+ withTiming,
19
+ useAnimatedStyle,
20
+ runOnJS,
21
+ } from 'react-native-reanimated';
22
+
23
+ import useTheme from '../hooks/useTheme';
24
+ import { convertHexToRgba } from '../utils/uiUtils';
25
+ import Text from './Text';
26
+ import Button from './Button';
27
+
28
+ // Componente principal Dialog
29
+ interface DialogProps extends PropsWithChildren {
30
+ visible: boolean;
31
+ onDismiss: () => void;
32
+ }
33
+
34
+ const Dialog = ({ children, visible, onDismiss }: DialogProps) => {
35
+ const { colors, theme } = useTheme();
36
+ const [internalVisible, setInternalVisible] = useState(false);
37
+
38
+ const scale = useSharedValue(0);
39
+ const opacity = useSharedValue(0);
40
+
41
+ const runCloseAnimation = useCallback(() => {
42
+ scale.value = withTiming(0.7, { duration: 300 });
43
+ opacity.value = withTiming(0, { duration: 300 }, () => {
44
+ runOnJS(setInternalVisible)(false);
45
+ });
46
+ }, [scale, opacity]);
47
+
48
+ // Sync external visible prop with internal state
49
+ useEffect(() => {
50
+ if (visible) {
51
+ setInternalVisible(true);
52
+ } else if (internalVisible) {
53
+ // Only start close animation if dialog is currently visible
54
+ runCloseAnimation();
55
+ }
56
+ }, [visible, internalVisible, runCloseAnimation]);
57
+
58
+ // Start open animation when internal state becomes true
59
+ useEffect(() => {
60
+ if (internalVisible && visible) {
61
+ scale.value = withTiming(1, { duration: 300 });
62
+ opacity.value = withTiming(1, { duration: 300 });
63
+ }
64
+ }, [internalVisible, visible, scale, opacity]);
65
+
66
+ const animatedOverlayStyle = useAnimatedStyle(() => ({
67
+ opacity: opacity.value,
68
+ }));
69
+
70
+ const animatedContentStyle = useAnimatedStyle(() => ({
71
+ transform: [{ scale: scale.value }],
72
+ opacity: opacity.value,
73
+ }));
74
+
75
+ const handleBackdropPress = () => {
76
+ onDismiss();
77
+ };
78
+
79
+ return (
80
+ <Modal transparent visible={internalVisible} statusBarTranslucent>
81
+ <TouchableWithoutFeedback onPress={handleBackdropPress}>
82
+ <Animated.View style={[styles.overlay, animatedOverlayStyle]}>
83
+ <TouchableWithoutFeedback>
84
+ <Animated.View
85
+ style={[
86
+ styles.content,
87
+ {
88
+ backgroundColor: colors.background,
89
+ borderRadius: theme.roundness,
90
+ borderColor: colors.border,
91
+ },
92
+ animatedContentStyle,
93
+ ]}
94
+ >
95
+ {children}
96
+ </Animated.View>
97
+ </TouchableWithoutFeedback>
98
+ </Animated.View>
99
+ </TouchableWithoutFeedback>
100
+ </Modal>
101
+ );
102
+ };
103
+
104
+ // Dialog Header
105
+ interface DialogHeaderProps extends PropsWithChildren {
106
+ style?: StyleProp<ViewStyle>;
107
+ }
108
+
109
+ const DialogHeader = ({ children, style }: DialogHeaderProps) => {
110
+ return <View style={[styles.header, style]}>{children}</View>;
111
+ };
112
+
113
+ // Dialog Title
114
+ interface DialogTitleProps {
115
+ children: ReactNode;
116
+ style?: StyleProp<ViewStyle>;
117
+ }
118
+
119
+ const DialogTitle = ({ children, style }: DialogTitleProps) => {
120
+ return (
121
+ <Text variant="h3" style={[styles.title, style]}>
122
+ {children}
123
+ </Text>
124
+ );
125
+ };
126
+
127
+ // Dialog Description
128
+ interface DialogDescriptionProps {
129
+ children: ReactNode;
130
+ style?: StyleProp<ViewStyle>;
131
+ }
132
+
133
+ const DialogDescription = ({ children, style }: DialogDescriptionProps) => {
134
+ const { colors } = useTheme();
135
+
136
+ return (
137
+ <Text
138
+ variant="pm"
139
+ style={[
140
+ styles.description,
141
+ { color: convertHexToRgba(colors.foreground, 0.7) },
142
+ style,
143
+ ]}
144
+ >
145
+ {children}
146
+ </Text>
147
+ );
148
+ };
149
+
150
+ // Dialog Footer
151
+ interface DialogFooterProps extends PropsWithChildren {
152
+ style?: StyleProp<ViewStyle>;
153
+ }
154
+
155
+ const DialogFooter = ({ children, style }: DialogFooterProps) => {
156
+ return <View style={[styles.footer, style]}>{children}</View>;
157
+ };
158
+
159
+ // Dialog Action (botón de acción)
160
+ interface DialogActionProps {
161
+ onPress: () => void;
162
+ children: ReactNode;
163
+ variant?: 'flat' | 'outline';
164
+ destructive?: boolean;
165
+ }
166
+
167
+ const DialogAction = ({
168
+ onPress,
169
+ children,
170
+ variant = 'flat',
171
+ destructive = false,
172
+ }: DialogActionProps) => {
173
+ const { colors } = useTheme();
174
+
175
+ return (
176
+ <Button
177
+ text={children as string}
178
+ variant={destructive ? 'destructive' : variant}
179
+ size="large"
180
+ onPress={onPress}
181
+ style={[
182
+ styles.actionButton,
183
+ destructive && { borderColor: colors.destructive },
184
+ ]}
185
+ />
186
+ );
187
+ };
188
+
189
+ // Dialog Cancel (botón de cancelar)
190
+ interface DialogCancelProps {
191
+ onPress: () => void;
192
+ children: ReactNode;
193
+ }
194
+
195
+ const DialogCancel = ({ onPress, children }: DialogCancelProps) => {
196
+ return (
197
+ <Button
198
+ text={children as string}
199
+ variant="outline"
200
+ size="large"
201
+ onPress={onPress}
202
+ style={styles.cancelButton}
203
+ />
204
+ );
205
+ };
206
+
207
+ const styles = StyleSheet.create({
208
+ overlay: {
209
+ flex: 1,
210
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
211
+ justifyContent: 'center',
212
+ alignItems: 'center',
213
+ padding: 20,
214
+ },
215
+ content: {
216
+ width: '100%',
217
+ maxWidth: 400,
218
+ borderWidth: 1,
219
+ shadowColor: '#000',
220
+ shadowOffset: {
221
+ width: 0,
222
+ height: 4,
223
+ },
224
+ shadowOpacity: 0.25,
225
+ shadowRadius: 12,
226
+ elevation: 8,
227
+ },
228
+ header: {
229
+ padding: 20,
230
+ paddingBottom: 12,
231
+ },
232
+ title: {
233
+ marginBottom: 8,
234
+ },
235
+ description: {
236
+ lineHeight: 20,
237
+ },
238
+ footer: {
239
+ flexDirection: 'row',
240
+ justifyContent: 'flex-end',
241
+ gap: 12,
242
+ padding: 20,
243
+ paddingTop: 12,
244
+ },
245
+ actionButton: {
246
+ flex: 1,
247
+ minWidth: 80,
248
+ },
249
+ cancelButton: {
250
+ flex: 1,
251
+ minWidth: 80,
252
+ },
253
+ });
254
+
255
+ export default Dialog;
256
+ export {
257
+ DialogHeader,
258
+ DialogTitle,
259
+ DialogDescription,
260
+ DialogFooter,
261
+ DialogAction,
262
+ DialogCancel,
263
+ };
@@ -0,0 +1,100 @@
1
+ import { type PropsWithChildren } from 'react';
2
+ import { StyleSheet, View, type ViewStyle, type StyleProp } from 'react-native';
3
+
4
+ import Text from './Text';
5
+ import IconButton from './IconButton';
6
+ import { type IconName } from './Icon';
7
+
8
+ import useTheme from '../hooks/useTheme';
9
+
10
+ import meassures from '../theme/meassures';
11
+
12
+ type HeaderButtonProps = {
13
+ iconName: IconName;
14
+ onPress?: () => void;
15
+ disabled?: boolean;
16
+ };
17
+
18
+ type Props = PropsWithChildren & {
19
+ title: string;
20
+ leftButton?: HeaderButtonProps;
21
+ rightButton?: HeaderButtonProps;
22
+ style?: StyleProp<ViewStyle>;
23
+ useInsets?: boolean;
24
+ };
25
+
26
+ const HEADER_BASE_HEIGHT = 52;
27
+
28
+ export default function Header({
29
+ title,
30
+ leftButton,
31
+ rightButton,
32
+ style,
33
+ useInsets = true,
34
+ children,
35
+ }: Props) {
36
+ const { colors, theme } = useTheme();
37
+ const topInset = useInsets ? theme.insets?.top || 0 : 0;
38
+
39
+ const renderButton = (button: HeaderButtonProps) => {
40
+ return (
41
+ <IconButton
42
+ iconName={button.iconName}
43
+ variant="transparent"
44
+ size="medium"
45
+ onPress={button.onPress}
46
+ disabled={button.disabled}
47
+ />
48
+ );
49
+ };
50
+
51
+ return (
52
+ <View
53
+ style={[
54
+ styles.header,
55
+ {
56
+ backgroundColor: colors.background,
57
+ borderBottomColor: colors.border,
58
+ paddingTop: topInset,
59
+ },
60
+ !!children && styles.paddingBottom,
61
+ style,
62
+ ]}
63
+ >
64
+ <View style={styles.headerContent}>
65
+ <View style={[styles.slot, styles.leftSlot]}>
66
+ {leftButton && renderButton(leftButton)}
67
+ </View>
68
+ <View style={styles.titleContainer}>
69
+ <Text
70
+ variant="h3"
71
+ style={[styles.title, { color: colors.foreground }]}
72
+ numberOfLines={1}
73
+ ellipsizeMode="tail"
74
+ >
75
+ {title}
76
+ </Text>
77
+ </View>
78
+ <View style={[styles.slot, styles.rightSlot]}>
79
+ {rightButton && renderButton(rightButton)}
80
+ </View>
81
+ </View>
82
+ {children}
83
+ </View>
84
+ );
85
+ }
86
+
87
+ const styles = StyleSheet.create({
88
+ header: { borderBottomWidth: 1, paddingHorizontal: 16 },
89
+ headerContent: {
90
+ height: HEADER_BASE_HEIGHT,
91
+ flexDirection: 'row',
92
+ alignItems: 'center',
93
+ },
94
+ slot: { width: meassures.button.medium, justifyContent: 'center' },
95
+ leftSlot: { alignItems: 'flex-start' },
96
+ titleContainer: { flex: 1, alignItems: 'center', justifyContent: 'center' },
97
+ title: { textAlign: 'center', marginHorizontal: 8 },
98
+ rightSlot: { alignItems: 'flex-end' },
99
+ paddingBottom: { paddingBottom: 16 },
100
+ });
@@ -0,0 +1,27 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+ import { icons } from 'lucide-react-native';
3
+
4
+ import useTheme from '../hooks/useTheme';
5
+
6
+ export type IconName = keyof typeof icons;
7
+
8
+ type Props = {
9
+ style?: StyleProp<ViewStyle>;
10
+ name: IconName;
11
+ size?: number;
12
+ color?: string;
13
+ };
14
+
15
+ export default function Icon({ name, size = 24, color, style }: Props) {
16
+ const { colors } = useTheme();
17
+
18
+ const IconComponent = icons[name];
19
+
20
+ return (
21
+ <IconComponent
22
+ size={size}
23
+ color={color ?? colors.foreground}
24
+ style={style}
25
+ />
26
+ );
27
+ }