@umituz/react-native-design-system 4.23.117 → 4.23.119

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 (31) hide show
  1. package/package.json +1 -1
  2. package/src/device/presentation/hooks/useAnonymousUser.ts +25 -31
  3. package/src/device/presentation/hooks/useDeviceInfo.ts +47 -91
  4. package/src/init/useAppInitialization.ts +24 -57
  5. package/src/media/domain/entities/Media.ts +1 -0
  6. package/src/media/infrastructure/utils/media-collection-utils.ts +9 -6
  7. package/src/media/presentation/hooks/useMedia.ts +73 -128
  8. package/src/molecules/alerts/AlertBanner.tsx +24 -46
  9. package/src/molecules/alerts/AlertInline.tsx +8 -9
  10. package/src/molecules/alerts/AlertModal.tsx +23 -14
  11. package/src/molecules/alerts/AlertToast.tsx +25 -53
  12. package/src/molecules/alerts/components/AlertContent.tsx +79 -0
  13. package/src/molecules/alerts/components/AlertIcon.tsx +31 -0
  14. package/src/molecules/alerts/components/index.ts +6 -0
  15. package/src/molecules/alerts/hooks/index.ts +6 -0
  16. package/src/molecules/alerts/hooks/useAlertAutoDismiss.ts +26 -0
  17. package/src/molecules/alerts/hooks/useAlertDismissHandler.ts +21 -0
  18. package/src/molecules/alerts/utils/alertUtils.ts +0 -21
  19. package/src/storage/cache/presentation/useCachedValue.ts +24 -65
  20. package/src/storage/presentation/hooks/useStorageState.ts +20 -29
  21. package/src/utilities/sharing/presentation/hooks/useSharing.ts +75 -140
  22. package/src/utils/errors/DesignSystemError.ts +57 -1
  23. package/src/utils/errors/ErrorHandler.ts +105 -1
  24. package/src/utils/errors/adapters/CacheErrorAdapter.ts +68 -0
  25. package/src/utils/errors/adapters/ImageErrorAdapter.ts +91 -0
  26. package/src/utils/errors/adapters/StorageErrorAdapter.ts +107 -0
  27. package/src/utils/errors/index.ts +5 -1
  28. package/src/utils/errors/types/Result.ts +64 -0
  29. package/src/utils/hooks/index.ts +12 -0
  30. package/src/utils/hooks/types/AsyncOperationTypes.ts +75 -0
  31. package/src/utils/hooks/useAsyncOperation.ts +223 -0
@@ -6,38 +6,28 @@
6
6
  * Auto-dismisses after duration (default 3 seconds).
7
7
  */
8
8
 
9
- import React, { useEffect, useCallback } from 'react';
9
+ import React from 'react';
10
10
  import { StyleSheet, View, Pressable } from 'react-native';
11
11
  import { useSafeAreaInsets } from '../../safe-area';
12
- import { AtomicText, AtomicIcon, useIconName } from '../../atoms';
12
+ import { AtomicText, useIconName } from '../../atoms';
13
13
  import { useAppDesignTokens } from '../../theme';
14
14
  import { Alert, AlertPosition } from './AlertTypes';
15
- import { useAlertStore } from './AlertStore';
16
- import { getAlertBackgroundColor, getAlertTextColor, DEFAULT_ALERT_DURATION } from './utils/alertUtils';
15
+ import { getAlertBackgroundColor, getAlertTextColor, getActionButtonStyle, getActionTextColor } from './utils/alertUtils';
16
+ import { useAlertDismissHandler, useAlertAutoDismiss } from './hooks';
17
+ import { AlertIcon, AlertContent } from './components';
17
18
 
18
19
  interface AlertBannerProps {
19
20
  alert: Alert;
20
21
  }
21
22
 
22
23
  export function AlertBanner({ alert }: AlertBannerProps) {
23
- const dismissAlert = useAlertStore((state: { dismissAlert: (id: string) => void }) => state.dismissAlert);
24
24
  const insets = useSafeAreaInsets();
25
25
  const tokens = useAppDesignTokens();
26
26
  const closeIcon = useIconName('close');
27
27
 
28
- const handleDismiss = useCallback(() => {
29
- dismissAlert(alert.id);
30
- alert.onDismiss?.();
31
- }, [alert.id, dismissAlert, alert.onDismiss]);
32
-
33
- // Auto-dismiss after duration
34
- useEffect(() => {
35
- const duration = alert.duration ?? DEFAULT_ALERT_DURATION;
36
- if (duration <= 0) return;
37
-
38
- const timer = setTimeout(handleDismiss, duration);
39
- return () => clearTimeout(timer);
40
- }, [alert.duration, handleDismiss]);
28
+ // Use shared hooks (replaces 20 lines of duplicate code)
29
+ const handleDismiss = useAlertDismissHandler(alert);
30
+ useAlertAutoDismiss(alert, handleDismiss);
41
31
 
42
32
  const backgroundColor = getAlertBackgroundColor(alert.type, tokens);
43
33
  const textColor = getAlertTextColor(tokens);
@@ -59,36 +49,22 @@ export function AlertBanner({ alert }: AlertBannerProps) {
59
49
  <View style={styles.content}>
60
50
  <View style={styles.row}>
61
51
  {alert.icon && (
62
- <AtomicIcon
52
+ <AlertIcon
63
53
  name={alert.icon}
64
- customSize={20}
65
- customColor={textColor}
66
- style={{ marginRight: tokens.spacing.sm }}
54
+ color={textColor}
55
+ marginRight={tokens.spacing.sm}
67
56
  />
68
57
  )}
69
58
 
70
- <View style={styles.textContainer}>
71
- <AtomicText
72
- type="bodyMedium"
73
- style={[styles.title, { color: textColor }]}
74
- numberOfLines={1}
75
- >
76
- {alert.title}
77
- </AtomicText>
78
-
79
- {alert.message && (
80
- <AtomicText
81
- type="bodySmall"
82
- style={[
83
- styles.message,
84
- { color: textColor, marginTop: tokens.spacing.xs },
85
- ]}
86
- numberOfLines={2}
87
- >
88
- {alert.message}
89
- </AtomicText>
90
- )}
91
- </View>
59
+ <AlertContent
60
+ title={alert.title}
61
+ message={alert.message}
62
+ titleColor={textColor}
63
+ messageColor={textColor}
64
+ messageMarginTop={tokens.spacing.xs}
65
+ titleNumberOfLines={1}
66
+ messageNumberOfLines={2}
67
+ />
92
68
 
93
69
  {alert.dismissible && (
94
70
  <Pressable
@@ -96,7 +72,7 @@ export function AlertBanner({ alert }: AlertBannerProps) {
96
72
  style={[styles.closeButton, { marginLeft: tokens.spacing.sm }]}
97
73
  hitSlop={8}
98
74
  >
99
- <AtomicIcon name={closeIcon} customSize={20} customColor={textColor} />
75
+ <AlertIcon name={closeIcon} color={textColor} />
100
76
  </Pressable>
101
77
  )}
102
78
  </View>
@@ -118,14 +94,16 @@ export function AlertBanner({ alert }: AlertBannerProps) {
118
94
  paddingVertical: tokens.spacing.xs,
119
95
  paddingHorizontal: tokens.spacing.sm,
120
96
  marginRight: tokens.spacing.xs,
97
+ borderRadius: tokens.borders.radius.sm,
121
98
  },
99
+ getActionButtonStyle(action.style, tokens),
122
100
  ]}
123
101
  >
124
102
  <AtomicText
125
103
  type="bodySmall"
126
104
  style={[
127
105
  styles.actionText,
128
- { color: textColor },
106
+ { color: getActionTextColor(action.style, tokens) },
129
107
  ]}
130
108
  >
131
109
  {action.label}
@@ -4,10 +4,10 @@
4
4
 
5
5
  import React from 'react';
6
6
  import { StyleSheet, View } from 'react-native';
7
- import { AtomicText } from '../../atoms';
8
7
  import { useAppDesignTokens } from '../../theme';
9
8
  import { Alert } from './AlertTypes';
10
9
  import { getAlertBorderColor, getAlertBackgroundColorInline } from './utils/alertUtils';
10
+ import { AlertContent } from './components';
11
11
 
12
12
  interface AlertInlineProps {
13
13
  alert: Alert;
@@ -27,14 +27,13 @@ export const AlertInline: React.FC<AlertInlineProps> = ({ alert }) => {
27
27
  marginVertical: tokens.spacing.sm,
28
28
  }
29
29
  ]}>
30
- <AtomicText type="bodyMedium" style={{ color: tokens.colors.textPrimary, fontWeight: '700' }}>
31
- {alert.title}
32
- </AtomicText>
33
- {alert.message && (
34
- <AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary, marginTop: tokens.spacing.xs }}>
35
- {alert.message}
36
- </AtomicText>
37
- )}
30
+ <AlertContent
31
+ title={alert.title}
32
+ message={alert.message}
33
+ titleColor={tokens.colors.textPrimary}
34
+ messageColor={tokens.colors.textSecondary}
35
+ messageMarginTop={tokens.spacing.xs}
36
+ />
38
37
  </View>
39
38
  );
40
39
  };
@@ -4,24 +4,22 @@
4
4
 
5
5
  import React from 'react';
6
6
  import { StyleSheet, View, Modal, Pressable } from 'react-native';
7
- import { AtomicText, AtomicButton } from '../../atoms';
7
+ import { AtomicButton } from '../../atoms';
8
8
  import { useAppDesignTokens } from '../../theme';
9
9
  import { Alert } from './AlertTypes';
10
- import { useAlertStore } from './AlertStore';
11
10
  import { getAlertBackgroundColor } from './utils/alertUtils';
11
+ import { useAlertDismissHandler } from './hooks';
12
+ import { AlertContent } from './components';
12
13
 
13
14
  interface AlertModalProps {
14
15
  alert: Alert;
15
16
  }
16
17
 
17
18
  export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
18
- const dismissAlert = useAlertStore((state: { dismissAlert: (id: string) => void }) => state.dismissAlert);
19
19
  const tokens = useAppDesignTokens();
20
20
 
21
- const handleClose = () => {
22
- dismissAlert(alert.id);
23
- alert.onDismiss?.();
24
- };
21
+ // Use shared hook (replaces 8 lines of duplicate code)
22
+ const handleClose = useAlertDismissHandler(alert);
25
23
 
26
24
  const headerColor = getAlertBackgroundColor(alert.type, tokens);
27
25
 
@@ -47,16 +45,26 @@ export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
47
45
  }
48
46
  ]}>
49
47
  <View style={[styles.header, { backgroundColor: headerColor }]}>
50
- <AtomicText type="titleLarge" style={{ color: tokens.colors.textInverse }}>
51
- {alert.title}
52
- </AtomicText>
48
+ <AlertContent
49
+ title={alert.title}
50
+ message=""
51
+ titleColor={tokens.colors.textInverse}
52
+ messageColor={tokens.colors.textInverse}
53
+ titleType="titleLarge"
54
+ textAlign="center"
55
+ />
53
56
  </View>
54
57
 
55
58
  <View style={[styles.content, { padding: tokens.spacing.lg }]}>
56
59
  {alert.message && (
57
- <AtomicText type="bodyMedium" style={{ color: tokens.colors.textPrimary, textAlign: 'center' }}>
58
- {alert.message}
59
- </AtomicText>
60
+ <AlertContent
61
+ title=""
62
+ message={alert.message}
63
+ titleColor={tokens.colors.textPrimary}
64
+ messageColor={tokens.colors.textPrimary}
65
+ messageType="bodyMedium"
66
+ textAlign="center"
67
+ />
60
68
  )}
61
69
 
62
70
  <View style={[styles.actions, { marginTop: tokens.spacing.lg, gap: tokens.spacing.sm }]}>
@@ -66,10 +74,11 @@ export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
66
74
  title={action.label}
67
75
  variant={action.style === 'destructive' ? 'danger' : action.style === 'secondary' ? 'secondary' : 'primary'}
68
76
  onPress={async () => {
77
+ // BUG FIX: Execute action BEFORE closing (was backwards)
78
+ await action.onPress();
69
79
  if (action.closeOnPress ?? true) {
70
80
  handleClose();
71
81
  }
72
- await action.onPress();
73
82
  }}
74
83
  fullWidth
75
84
  />
@@ -5,48 +5,31 @@
5
5
  * Floats on top of content.
6
6
  */
7
7
 
8
- import React, { useEffect, useCallback } from 'react';
8
+ import React from 'react';
9
9
  import { StyleSheet, View, Pressable } from 'react-native';
10
- import { AtomicText, AtomicIcon, useIconName } from '../../atoms';
10
+ import { AtomicText, useIconName } from '../../atoms';
11
11
  import { useAppDesignTokens } from '../../theme';
12
12
  import { Alert } from './AlertTypes';
13
- import { useAlertStore } from './AlertStore';
14
13
  import {
15
14
  getAlertBackgroundColor,
16
15
  getAlertTextColor,
17
16
  getActionButtonStyle,
18
17
  getActionTextColor,
19
- DEFAULT_ALERT_DURATION,
20
18
  } from './utils/alertUtils';
19
+ import { useAlertDismissHandler, useAlertAutoDismiss } from './hooks';
20
+ import { AlertIcon, AlertContent } from './components';
21
21
 
22
22
  interface AlertToastProps {
23
23
  alert: Alert;
24
24
  }
25
25
 
26
26
  export function AlertToast({ alert }: AlertToastProps) {
27
- const dismissAlert = useAlertStore((state: { dismissAlert: (id: string) => void }) => state.dismissAlert);
28
27
  const tokens = useAppDesignTokens();
29
28
  const closeIcon = useIconName('close');
30
29
 
31
- const dismiss = useCallback(() => {
32
- dismissAlert(alert.id);
33
- alert.onDismiss?.();
34
- }, [alert.id, dismissAlert, alert.onDismiss]);
35
-
36
- const handleDismiss = useCallback(() => {
37
- if (alert.dismissible) {
38
- dismiss();
39
- }
40
- }, [alert.dismissible, dismiss]);
41
-
42
- // Auto-dismiss after duration
43
- useEffect(() => {
44
- const duration = alert.duration ?? DEFAULT_ALERT_DURATION;
45
- if (duration <= 0) return;
46
-
47
- const timer = setTimeout(dismiss, duration);
48
- return () => clearTimeout(timer);
49
- }, [alert.duration, dismiss]);
30
+ // Use shared hooks (replaces 25 lines of duplicate code)
31
+ const handleDismiss = useAlertDismissHandler(alert);
32
+ useAlertAutoDismiss(alert, handleDismiss);
50
33
 
51
34
  const backgroundColor = getAlertBackgroundColor(alert.type, tokens);
52
35
  const textColor = getAlertTextColor(tokens);
@@ -63,39 +46,28 @@ export function AlertToast({ alert }: AlertToastProps) {
63
46
  ]}
64
47
  testID={alert.testID}
65
48
  >
66
- <Pressable onPress={handleDismiss} style={styles.content}>
49
+ <Pressable
50
+ onPress={alert.dismissible ? handleDismiss : undefined}
51
+ style={styles.content}
52
+ >
67
53
  <View style={styles.row}>
68
54
  {alert.icon && (
69
- <AtomicIcon
55
+ <AlertIcon
70
56
  name={alert.icon}
71
- customSize={20}
72
- customColor={textColor}
73
- style={{ marginRight: tokens.spacing.sm }}
57
+ color={textColor}
58
+ marginRight={tokens.spacing.sm}
74
59
  />
75
60
  )}
76
61
 
77
- <View style={styles.textContainer}>
78
- <AtomicText
79
- type="bodyMedium"
80
- style={[styles.title, { color: textColor }]}
81
- numberOfLines={2}
82
- >
83
- {alert.title}
84
- </AtomicText>
85
-
86
- {alert.message && (
87
- <AtomicText
88
- type="bodySmall"
89
- style={[
90
- styles.message,
91
- { color: textColor, marginTop: tokens.spacing.xs },
92
- ]}
93
- numberOfLines={3}
94
- >
95
- {alert.message}
96
- </AtomicText>
97
- )}
98
- </View>
62
+ <AlertContent
63
+ title={alert.title}
64
+ message={alert.message}
65
+ titleColor={textColor}
66
+ messageColor={textColor}
67
+ messageMarginTop={tokens.spacing.xs}
68
+ titleNumberOfLines={2}
69
+ messageNumberOfLines={3}
70
+ />
99
71
 
100
72
  {alert.dismissible && (
101
73
  <Pressable
@@ -103,7 +75,7 @@ export function AlertToast({ alert }: AlertToastProps) {
103
75
  style={[styles.closeButton, { marginLeft: tokens.spacing.sm }]}
104
76
  hitSlop={8}
105
77
  >
106
- <AtomicIcon name={closeIcon} customSize={20} customColor={textColor} />
78
+ <AlertIcon name={closeIcon} color={textColor} />
107
79
  </Pressable>
108
80
  )}
109
81
  </View>
@@ -116,7 +88,7 @@ export function AlertToast({ alert }: AlertToastProps) {
116
88
  onPress={async () => {
117
89
  await action.onPress();
118
90
  if (action.closeOnPress ?? true) {
119
- dismiss();
91
+ handleDismiss();
120
92
  }
121
93
  }}
122
94
  style={[
@@ -0,0 +1,79 @@
1
+ /**
2
+ * AlertContent Component
3
+ *
4
+ * Shared title + message layout for alert variants.
5
+ */
6
+
7
+ import React from 'react';
8
+ import { StyleSheet, View } from 'react-native';
9
+ import { AtomicText } from '../../../atoms';
10
+
11
+ interface AlertContentProps {
12
+ title: string;
13
+ message?: string;
14
+ titleColor: string;
15
+ messageColor: string;
16
+ titleType?: 'bodyMedium' | 'titleLarge';
17
+ messageType?: 'bodySmall' | 'bodyMedium';
18
+ messageMarginTop?: number;
19
+ titleNumberOfLines?: number;
20
+ messageNumberOfLines?: number;
21
+ textAlign?: 'left' | 'center' | 'right';
22
+ }
23
+
24
+ export function AlertContent({
25
+ title,
26
+ message,
27
+ titleColor,
28
+ messageColor,
29
+ titleType = 'bodyMedium',
30
+ messageType = 'bodySmall',
31
+ messageMarginTop = 0,
32
+ titleNumberOfLines = 2,
33
+ messageNumberOfLines = 3,
34
+ textAlign = 'left',
35
+ }: AlertContentProps) {
36
+ return (
37
+ <View style={styles.container}>
38
+ <AtomicText
39
+ type={titleType}
40
+ style={[
41
+ styles.title,
42
+ { color: titleColor, textAlign },
43
+ ]}
44
+ numberOfLines={titleNumberOfLines}
45
+ >
46
+ {title}
47
+ </AtomicText>
48
+
49
+ {message && (
50
+ <AtomicText
51
+ type={messageType}
52
+ style={[
53
+ styles.message,
54
+ {
55
+ color: messageColor,
56
+ marginTop: messageMarginTop,
57
+ textAlign,
58
+ },
59
+ ]}
60
+ numberOfLines={messageNumberOfLines}
61
+ >
62
+ {message}
63
+ </AtomicText>
64
+ )}
65
+ </View>
66
+ );
67
+ }
68
+
69
+ const styles = StyleSheet.create({
70
+ container: {
71
+ flex: 1,
72
+ },
73
+ title: {
74
+ fontWeight: '700',
75
+ },
76
+ message: {
77
+ opacity: 0.9,
78
+ },
79
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * AlertIcon Component
3
+ *
4
+ * Shared icon rendering for alert variants.
5
+ */
6
+
7
+ import React from 'react';
8
+ import { AtomicIcon } from '../../../atoms';
9
+
10
+ interface AlertIconProps {
11
+ name: string;
12
+ color: string;
13
+ size?: number;
14
+ marginRight?: number;
15
+ }
16
+
17
+ export function AlertIcon({
18
+ name,
19
+ color,
20
+ size = 20,
21
+ marginRight = 0
22
+ }: AlertIconProps) {
23
+ return (
24
+ <AtomicIcon
25
+ name={name}
26
+ customSize={size}
27
+ customColor={color}
28
+ style={{ marginRight }}
29
+ />
30
+ );
31
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Alert Components Barrel Export
3
+ */
4
+
5
+ export { AlertIcon } from './AlertIcon';
6
+ export { AlertContent } from './AlertContent';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Alert Hooks Barrel Export
3
+ */
4
+
5
+ export { useAlertDismissHandler } from './useAlertDismissHandler';
6
+ export { useAlertAutoDismiss } from './useAlertAutoDismiss';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * useAlertAutoDismiss Hook
3
+ *
4
+ * Handles auto-dismiss timer for alerts with duration.
5
+ * Checks dismissible flag before setting timer (fixes AlertBanner bug).
6
+ */
7
+
8
+ import { useEffect } from 'react';
9
+ import type { Alert } from '../AlertTypes';
10
+ import { DEFAULT_ALERT_DURATION } from '../utils/alertUtils';
11
+
12
+ export function useAlertAutoDismiss(
13
+ alert: Alert,
14
+ onDismiss: () => void
15
+ ) {
16
+ useEffect(() => {
17
+ // BUG FIX: Check dismissible flag before auto-dismissing
18
+ if (!alert.dismissible) return;
19
+
20
+ const duration = alert.duration ?? DEFAULT_ALERT_DURATION;
21
+ if (duration <= 0) return;
22
+
23
+ const timer = setTimeout(onDismiss, duration);
24
+ return () => clearTimeout(timer);
25
+ }, [alert.duration, alert.dismissible, onDismiss]);
26
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * useAlertDismissHandler Hook
3
+ *
4
+ * Extracts common dismiss logic from all alert variants.
5
+ * Combines store dismissal with optional callback.
6
+ */
7
+
8
+ import { useCallback } from 'react';
9
+ import { useAlertStore } from '../AlertStore';
10
+ import type { Alert } from '../AlertTypes';
11
+
12
+ export function useAlertDismissHandler(alert: Alert) {
13
+ const dismissAlert = useAlertStore((state) => state.dismissAlert);
14
+
15
+ const handleDismiss = useCallback(() => {
16
+ dismissAlert(alert.id);
17
+ alert.onDismiss?.();
18
+ }, [alert.id, alert.onDismiss, dismissAlert]);
19
+
20
+ return handleDismiss;
21
+ }
@@ -66,27 +66,6 @@ export function getAlertTextColor(tokens: DesignTokens): string {
66
66
  return tokens.colors.textInverse;
67
67
  }
68
68
 
69
- /**
70
- * Gets default icon for alert type
71
- *
72
- * @param type - Alert type
73
- * @returns Icon name or undefined
74
- */
75
- export function getAlertIcon(type: AlertType): string | undefined {
76
- switch (type) {
77
- case AlertType.SUCCESS:
78
- return 'CheckCircle';
79
- case AlertType.ERROR:
80
- return 'AlertCircle';
81
- case AlertType.WARNING:
82
- return 'AlertTriangle';
83
- case AlertType.INFO:
84
- return 'Info';
85
- default:
86
- return undefined;
87
- }
88
- }
89
-
90
69
  /**
91
70
  * Gets action button style
92
71
  *