@umituz/react-native-design-system 1.4.0 → 1.4.1

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 (77) hide show
  1. package/README.md +2 -1
  2. package/lib/index.d.ts +3 -2
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/index.js +8 -2
  5. package/lib/index.js.map +1 -1
  6. package/lib/presentation/atoms/AtomicButton.d.ts +1 -0
  7. package/lib/presentation/atoms/AtomicButton.d.ts.map +1 -1
  8. package/lib/presentation/atoms/AtomicButton.js +9 -6
  9. package/lib/presentation/atoms/AtomicButton.js.map +1 -1
  10. package/lib/presentation/atoms/AtomicDatePicker.js +1 -1
  11. package/lib/presentation/atoms/AtomicDatePicker.js.map +1 -1
  12. package/lib/presentation/atoms/AtomicFab.d.ts +1 -0
  13. package/lib/presentation/atoms/AtomicFab.d.ts.map +1 -1
  14. package/lib/presentation/atoms/AtomicFab.js +5 -4
  15. package/lib/presentation/atoms/AtomicFab.js.map +1 -1
  16. package/lib/presentation/atoms/AtomicPicker.js +1 -1
  17. package/lib/presentation/atoms/AtomicPicker.js.map +1 -1
  18. package/lib/presentation/atoms/AtomicProgress.d.ts +3 -0
  19. package/lib/presentation/atoms/AtomicProgress.d.ts.map +1 -1
  20. package/lib/presentation/atoms/AtomicProgress.js +2 -1
  21. package/lib/presentation/atoms/AtomicProgress.js.map +1 -1
  22. package/lib/presentation/atoms/AtomicSkeleton.d.ts +4 -4
  23. package/lib/presentation/atoms/AtomicSkeleton.js +4 -4
  24. package/lib/presentation/atoms/AtomicTouchable.d.ts +11 -1
  25. package/lib/presentation/atoms/AtomicTouchable.d.ts.map +1 -1
  26. package/lib/presentation/atoms/AtomicTouchable.js +19 -6
  27. package/lib/presentation/atoms/AtomicTouchable.js.map +1 -1
  28. package/lib/presentation/atoms/fab/types/index.d.ts +5 -0
  29. package/lib/presentation/atoms/fab/types/index.d.ts.map +1 -1
  30. package/lib/presentation/atoms/touchable/styles/touchableStyles.d.ts +5 -0
  31. package/lib/presentation/atoms/touchable/styles/touchableStyles.d.ts.map +1 -1
  32. package/lib/presentation/atoms/touchable/styles/touchableStyles.js +8 -0
  33. package/lib/presentation/atoms/touchable/styles/touchableStyles.js.map +1 -1
  34. package/lib/presentation/atoms/touchable/types/index.d.ts +7 -1
  35. package/lib/presentation/atoms/touchable/types/index.d.ts.map +1 -1
  36. package/lib/presentation/loading/presentation/components/LoadingState.d.ts +39 -0
  37. package/lib/presentation/loading/presentation/components/LoadingState.d.ts.map +1 -0
  38. package/lib/presentation/loading/presentation/components/LoadingState.js +123 -0
  39. package/lib/presentation/loading/presentation/components/LoadingState.js.map +1 -0
  40. package/lib/presentation/molecules/AtomicConfirmationModal.js +1 -1
  41. package/lib/presentation/organisms/ScreenLayout.d.ts +15 -0
  42. package/lib/presentation/organisms/ScreenLayout.d.ts.map +1 -1
  43. package/lib/presentation/organisms/ScreenLayout.js +10 -1
  44. package/lib/presentation/organisms/ScreenLayout.js.map +1 -1
  45. package/lib/presentation/tokens/AppDesignTokens.d.ts +1 -1
  46. package/lib/presentation/tokens/AppDesignTokens.d.ts.map +1 -1
  47. package/lib/presentation/tokens/AppDesignTokens.js +1 -1
  48. package/lib/presentation/tokens/AppDesignTokens.js.map +1 -1
  49. package/lib/presentation/tokens/commonStyles.d.ts +1 -1
  50. package/lib/presentation/tokens/commonStyles.js +1 -1
  51. package/lib/presentation/tokens/core/BaseTokens.d.ts +25 -0
  52. package/lib/presentation/tokens/core/BaseTokens.d.ts.map +1 -1
  53. package/lib/presentation/tokens/core/BaseTokens.js +18 -0
  54. package/lib/presentation/tokens/core/BaseTokens.js.map +1 -1
  55. package/lib/presentation/tokens/core/TokenFactory.d.ts +15 -2
  56. package/lib/presentation/tokens/core/TokenFactory.d.ts.map +1 -1
  57. package/lib/presentation/tokens/core/TokenFactory.js +2 -1
  58. package/lib/presentation/tokens/core/TokenFactory.js.map +1 -1
  59. package/package.json +3 -4
  60. package/src/index.ts +10 -1
  61. package/src/presentation/atoms/AtomicButton.tsx +16 -5
  62. package/src/presentation/atoms/AtomicDatePicker.tsx +1 -1
  63. package/src/presentation/atoms/AtomicFab.tsx +9 -3
  64. package/src/presentation/atoms/AtomicPicker.tsx +1 -1
  65. package/src/presentation/atoms/AtomicProgress.tsx +4 -0
  66. package/src/presentation/atoms/AtomicSkeleton.tsx +4 -4
  67. package/src/presentation/atoms/AtomicTouchable.tsx +27 -3
  68. package/src/presentation/atoms/fab/types/index.ts +6 -0
  69. package/src/presentation/atoms/touchable/styles/touchableStyles.ts +9 -0
  70. package/src/presentation/atoms/touchable/types/index.ts +8 -1
  71. package/src/presentation/loading/presentation/components/LoadingState.tsx +200 -0
  72. package/src/presentation/molecules/AtomicConfirmationModal.tsx +1 -1
  73. package/src/presentation/organisms/ScreenLayout.tsx +40 -0
  74. package/src/presentation/tokens/AppDesignTokens.ts +2 -0
  75. package/src/presentation/tokens/commonStyles.ts +1 -1
  76. package/src/presentation/tokens/core/BaseTokens.ts +22 -0
  77. package/src/presentation/tokens/core/TokenFactory.ts +4 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Universal design system for React Native apps - Domain-Driven Design architecture with Material Design 3 components",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -33,6 +33,7 @@
33
33
  "peerDependencies": {
34
34
  "react": ">=18.2.0",
35
35
  "react-native": ">=0.74.0",
36
+ "react-native-reanimated": "~3.10.1",
36
37
  "react-native-svg": ">=13.0.0",
37
38
  "@react-native-community/datetimepicker": "8.0.1",
38
39
  "@expo/vector-icons": "^14.0.0",
@@ -43,9 +44,6 @@
43
44
  "@umituz/react-native-localization": "^1.0.0"
44
45
  },
45
46
  "peerDependenciesMeta": {
46
- "@react-native-community/datetimepicker": {
47
- "optional": true
48
- },
49
47
  "expo-linear-gradient": {
50
48
  "optional": true
51
49
  },
@@ -62,6 +60,7 @@
62
60
  "@types/react-native": "^0.73.0",
63
61
  "react": ">=18.2.0",
64
62
  "react-native": ">=0.74.0",
63
+ "react-native-reanimated": "~3.10.1",
65
64
  "react-native-svg": "^15.0.0",
66
65
  "@expo/vector-icons": "^14.0.0",
67
66
  "lucide-react-native": "^0.468.0",
package/src/index.ts CHANGED
@@ -11,11 +11,12 @@
11
11
  * - presentation/atoms: Primitive UI components (AtomicButton, AtomicText, etc.)
12
12
  * - presentation/molecules: Composite components (SearchBar, ListItem, etc.)
13
13
  * - presentation/organisms: Complex patterns (ScreenLayout, AppHeader, FormContainer)
14
+ * - presentation/loading: Loading states (LoadingState, LoadingSpinner)
14
15
  * - presentation/tokens: Design tokens (colors, typography, spacing, etc.)
15
16
  * - presentation/utils: Utility functions and helpers
16
17
  *
17
18
  * Usage:
18
- * import { AtomicButton, AtomicFilter, AtomicTouchable, SearchBar, STATIC_TOKENS } from '@umituz/react-native-design-system';
19
+ * import { AtomicButton, AtomicFilter, AtomicTouchable, SearchBar, LoadingState, STATIC_TOKENS } from '@umituz/react-native-design-system';
19
20
  */
20
21
 
21
22
  // =============================================================================
@@ -227,6 +228,12 @@ export {
227
228
  // Note: FeedbackModal moved to @domains/feedback
228
229
  // Import from feedback domain: import { FeedbackModal } from '@domains/feedback';
229
230
 
231
+ // =============================================================================
232
+ // LOADING - Loading States
233
+ // =============================================================================
234
+ // Loading components moved to separate package
235
+ // Import from @umituz/react-native-loading when available
236
+
230
237
  // =============================================================================
231
238
  // TOKENS - Design Tokens (Refactored with ZERO duplication)
232
239
  // =============================================================================
@@ -248,6 +255,7 @@ export {
248
255
  // Individual base tokens
249
256
  spacing,
250
257
  typography,
258
+ animations,
251
259
  borders,
252
260
 
253
261
  // Type exports
@@ -256,6 +264,7 @@ export {
256
264
  type ColorPalette,
257
265
  type Spacing,
258
266
  type Typography,
267
+ type Animations,
259
268
  type Borders,
260
269
  type BaseTokens,
261
270
  } from './presentation/tokens/AppDesignTokens';
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { StyleSheet, StyleProp, ViewStyle, TextStyle, TouchableOpacity, View } from 'react-native';
2
+ import { StyleSheet, StyleProp, ViewStyle, TextStyle, TouchableOpacity, ActivityIndicator, View } from 'react-native';
3
3
  import { AtomicText } from './AtomicText';
4
4
  import { Icon } from '../../domains/icons/presentation/components/Icon';
5
5
  import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
@@ -15,6 +15,7 @@ export interface AtomicButtonProps {
15
15
  variant?: ButtonVariant;
16
16
  size?: ButtonSize;
17
17
  disabled?: boolean;
18
+ loading?: boolean;
18
19
  icon?: IconName;
19
20
  fullWidth?: boolean;
20
21
  style?: StyleProp<ViewStyle>;
@@ -29,6 +30,7 @@ export const AtomicButton: React.FC<AtomicButtonProps> = ({
29
30
  variant = 'primary',
30
31
  size = 'md',
31
32
  disabled = false,
33
+ loading = false,
32
34
  icon,
33
35
  fullWidth = false,
34
36
  style,
@@ -38,7 +40,7 @@ export const AtomicButton: React.FC<AtomicButtonProps> = ({
38
40
  const tokens = useAppDesignTokens();
39
41
 
40
42
  const handlePress = () => {
41
- if (!disabled) {
43
+ if (!disabled && !loading) {
42
44
  onPress();
43
45
  }
44
46
  };
@@ -179,7 +181,7 @@ export const AtomicButton: React.FC<AtomicButtonProps> = ({
179
181
  ];
180
182
 
181
183
  const buttonText = title || children;
182
- const showIcon = !!icon;
184
+ const showIcon = icon && !loading;
183
185
  const iconColor = variantStyles.text.color;
184
186
 
185
187
  return (
@@ -187,11 +189,17 @@ export const AtomicButton: React.FC<AtomicButtonProps> = ({
187
189
  style={containerStyle}
188
190
  onPress={handlePress}
189
191
  activeOpacity={0.8}
190
- disabled={disabled}
192
+ disabled={disabled || loading}
191
193
  testID={testID}
192
194
  >
193
195
  <View style={styles.content}>
194
- {showIcon ? (
196
+ {loading ? (
197
+ <ActivityIndicator
198
+ size="small"
199
+ color={variantStyles.text.color}
200
+ style={styles.loader}
201
+ />
202
+ ) : showIcon ? (
195
203
  <Icon
196
204
  name={icon}
197
205
  customSize={config.iconSize}
@@ -231,6 +239,9 @@ const styles = StyleSheet.create({
231
239
  icon: {
232
240
  marginRight: 8,
233
241
  },
242
+ loader: {
243
+ marginRight: 8,
244
+ },
234
245
  });
235
246
 
236
247
  export type { AtomicButtonProps as ButtonProps };
@@ -215,7 +215,7 @@ export const AtomicDatePicker: React.FC<AtomicDatePickerProps> = ({
215
215
  {show && (
216
216
  <Modal
217
217
  transparent
218
- animationType="none"
218
+ animationType={isTabletDevice ? 'fade' : 'slide'}
219
219
  visible={show}
220
220
  onRequestClose={() => setShow(false)}
221
221
  >
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { TouchableOpacity, StyleSheet } from 'react-native';
2
+ import { TouchableOpacity, ActivityIndicator, StyleSheet } from 'react-native';
3
3
  import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
4
4
  import { useResponsive } from '../hooks/useResponsive';
5
5
  import { AtomicIcon } from './AtomicIcon';
@@ -40,6 +40,7 @@ export { FAB_SIZES, getFabVariants, getFabIconSize, getFabBorder };
40
40
  * - Material Design 3 sizes (sm: 40px, md: 56px, lg: 72px)
41
41
  * - Three variants: primary, secondary, surface
42
42
  * - Responsive positioning (above tab bar, safe area aware)
43
+ * - Loading state with spinner
43
44
  * - Disabled state with opacity
44
45
  * - Theme-aware colors from design tokens
45
46
  * - Border for depth (no shadows per CLAUDE.md)
@@ -50,13 +51,14 @@ export const AtomicFab: React.FC<AtomicFabProps> = ({
50
51
  variant = 'primary',
51
52
  size = 'md',
52
53
  disabled = false,
54
+ loading = false,
53
55
  style,
54
56
  testID,
55
57
  accessibilityLabel,
56
58
  }) => {
57
59
  const tokens = useAppDesignTokens();
58
60
  const responsive = useResponsive();
59
- const isDisabled = disabled;
61
+ const isDisabled = disabled || loading;
60
62
 
61
63
  // Get configurations
62
64
  const sizeConfig = FAB_SIZES[size as 'sm' | 'md' | 'lg'];
@@ -92,7 +94,11 @@ export const AtomicFab: React.FC<AtomicFabProps> = ({
92
94
  accessibilityLabel={accessibilityLabel || `${icon} button`}
93
95
  accessibilityRole="button"
94
96
  >
95
- <AtomicIcon name={icon} size={iconSize} customColor={variantConfig.iconColor} />
97
+ {loading ? (
98
+ <ActivityIndicator size="small" color={variantConfig.iconColor} />
99
+ ) : (
100
+ <AtomicIcon name={icon} size={iconSize} customColor={variantConfig.iconColor} />
101
+ )}
96
102
  </TouchableOpacity>
97
103
  );
98
104
  };
@@ -382,7 +382,7 @@ export const AtomicPicker: React.FC<AtomicPickerProps> = ({
382
382
  {/* Selection Modal */}
383
383
  <Modal
384
384
  visible={modalVisible}
385
- animationType="none"
385
+ animationType="slide"
386
386
  transparent
387
387
  onRequestClose={closeModal}
388
388
  testID={`${testID}-modal`}
@@ -10,6 +10,7 @@
10
10
  * Usage:
11
11
  * - File upload progress
12
12
  * - Task completion progress
13
+ * - Loading progress
13
14
  * - Achievement progress
14
15
  * - Form completion
15
16
  */
@@ -41,6 +42,8 @@ export interface AtomicProgressProps {
41
42
  showValue?: boolean;
42
43
  /** Custom text color */
43
44
  textColor?: string;
45
+ /** Animation duration in milliseconds */
46
+ animationDuration?: number;
44
47
  /** Style overrides */
45
48
  style?: ViewStyle | ViewStyle[];
46
49
  /** Test ID for testing */
@@ -61,6 +64,7 @@ export const AtomicProgress: React.FC<AtomicProgressProps> = ({
61
64
  showPercentage = false,
62
65
  showValue = false,
63
66
  textColor,
67
+ animationDuration = 300,
64
68
  style,
65
69
  testID,
66
70
  }) => {
@@ -1,14 +1,14 @@
1
1
  /**
2
- * AtomicSkeleton - Universal Skeleton Placeholder Component
2
+ * AtomicSkeleton - Universal Skeleton Loading Component
3
3
  *
4
- * Displays skeleton placeholders for content
4
+ * Displays skeleton placeholders for loading states
5
5
  * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
6
6
  *
7
7
  * Atomic Design Level: ATOM
8
- * Purpose: Content placeholder
8
+ * Purpose: Loading state placeholder
9
9
  *
10
10
  * Usage:
11
- * - Content placeholders
11
+ * - Content loading placeholders
12
12
  * - List item skeletons
13
13
  * - Card skeletons
14
14
  * - Text line skeletons
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
- import { Pressable, StyleSheet } from 'react-native';
2
+ import { Pressable, View, ActivityIndicator, StyleSheet } from 'react-native';
3
3
  import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
4
4
  import { AtomicTouchableProps, TouchableFeedback, FeedbackStrength } from './touchable/types';
5
5
  import {
6
6
  getOpacityValue,
7
7
  getTouchableContainerStyle,
8
8
  getDisabledStyle,
9
+ getLoadingContainerStyle,
9
10
  normalizeHitSlop,
10
11
  } from './touchable/styles/touchableStyles';
11
12
 
@@ -20,6 +21,7 @@ export {
20
21
  getOpacityValue,
21
22
  getTouchableContainerStyle,
22
23
  getDisabledStyle,
24
+ getLoadingContainerStyle,
23
25
  normalizeHitSlop,
24
26
  } from './touchable/styles/touchableStyles';
25
27
 
@@ -32,6 +34,7 @@ export {
32
34
  * Features:
33
35
  * - Multiple feedback variants (opacity, highlight, ripple, none)
34
36
  * - Configurable feedback strength (subtle, normal, strong)
37
+ * - Loading state with indicator
35
38
  * - Disabled state with visual feedback
36
39
  * - Hit slop customization for small touch targets
37
40
  * - Minimum 48x48 touch target (iOS HIG compliance)
@@ -45,6 +48,15 @@ export {
45
48
  * <AtomicText>Press Me</AtomicText>
46
49
  * </AtomicTouchable>
47
50
  *
51
+ * // With loading state
52
+ * <AtomicTouchable
53
+ * onPress={handleSubmit}
54
+ * loading={isSubmitting}
55
+ * feedback="highlight"
56
+ * >
57
+ * <AtomicText>Submit</AtomicText>
58
+ * </AtomicTouchable>
59
+ *
48
60
  * // With custom hit slop (extends touch area)
49
61
  * <AtomicTouchable
50
62
  * onPress={handlePress}
@@ -64,6 +76,7 @@ export const AtomicTouchable: React.FC<AtomicTouchableProps> = ({
64
76
  feedback = 'opacity',
65
77
  strength = 'normal',
66
78
  disabled = false,
79
+ loading = false,
67
80
  hitSlop,
68
81
  style,
69
82
  pressedStyle,
@@ -79,7 +92,7 @@ export const AtomicTouchable: React.FC<AtomicTouchableProps> = ({
79
92
  const tokens = useAppDesignTokens();
80
93
 
81
94
  // Determine if touchable should be disabled
82
- const isDisabled = disabled;
95
+ const isDisabled = disabled || loading;
83
96
 
84
97
  // Get opacity value based on strength
85
98
  const opacityValue = getOpacityValue(strength as 'subtle' | 'normal' | 'strong');
@@ -156,10 +169,21 @@ export const AtomicTouchable: React.FC<AtomicTouchableProps> = ({
156
169
  accessibilityRole={accessibilityRole}
157
170
  accessibilityState={{
158
171
  disabled: isDisabled,
172
+ busy: loading,
159
173
  }}
160
174
  testID={testID}
161
175
  >
162
- {children}
176
+ {loading ? (
177
+ <View style={getLoadingContainerStyle()}>
178
+ <ActivityIndicator
179
+ size="small"
180
+ color={tokens.colors.primary}
181
+ testID={`${testID}-loading`}
182
+ />
183
+ </View>
184
+ ) : (
185
+ children
186
+ )}
163
187
  </Pressable>
164
188
  );
165
189
  };
@@ -65,6 +65,12 @@ export interface AtomicFabProps {
65
65
  */
66
66
  disabled?: boolean;
67
67
 
68
+ /**
69
+ * Whether to show loading state
70
+ * @default false
71
+ */
72
+ loading?: boolean;
73
+
68
74
  /**
69
75
  * Custom style for the FAB container
70
76
  */
@@ -35,6 +35,15 @@ export const getDisabledStyle = (): ViewStyle => ({
35
35
  opacity: 0.5,
36
36
  });
37
37
 
38
+ /**
39
+ * Get loading container style
40
+ * Centers the loading indicator
41
+ */
42
+ export const getLoadingContainerStyle = (): ViewStyle => ({
43
+ justifyContent: 'center',
44
+ alignItems: 'center',
45
+ });
46
+
38
47
  /**
39
48
  * Convert number to HitSlop object
40
49
  * If hitSlop is a number, apply it to all sides
@@ -40,7 +40,8 @@ export interface HitSlop {
40
40
  * onPress={handlePress}
41
41
  * feedback="opacity"
42
42
  * strength="normal"
43
- * disabled={isDisabled}
43
+ * disabled={isLoading}
44
+ * loading={isLoading}
44
45
  * hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
45
46
  * style={styles.touchable}
46
47
  * >
@@ -92,6 +93,12 @@ export interface AtomicTouchableProps {
92
93
  */
93
94
  disabled?: boolean;
94
95
 
96
+ /**
97
+ * Show loading indicator (disables touch)
98
+ * @default false
99
+ */
100
+ loading?: boolean;
101
+
95
102
  /**
96
103
  * Hit slop - extends touchable area
97
104
  * Useful for small touch targets
@@ -0,0 +1,200 @@
1
+ /**
2
+ * LoadingState - Dynamic Icon-Based Loading Component
3
+ *
4
+ * Universal loading component with configurable emoji/icon support
5
+ * Inspired by meditation_timer's breathing animation pattern
6
+ * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
7
+ *
8
+ * Features:
9
+ * - ✅ Dynamic emoji/icon per screen (🏠 Home, ⚙️ Settings, 💪 Workout, etc.)
10
+ * - ✅ Breathing animation effect (scale 1 → 1.15 → 1)
11
+ * - ✅ Size variants (small, medium, large)
12
+ * - ✅ Full screen or inline modes
13
+ * - ✅ Optional loading message
14
+ * - ✅ Theme-aware styling
15
+ */
16
+
17
+ import React, { useRef, useEffect, useMemo } from 'react';
18
+ import {
19
+ View,
20
+ StyleSheet,
21
+ Animated,
22
+ Easing,
23
+ } from 'react-native';
24
+ import { useAppDesignTokens } from '../../../hooks/useAppDesignTokens';
25
+ import { STATIC_TOKENS } from '../../../tokens/AppDesignTokens';
26
+ import { AtomicText } from '../../../atoms/AtomicText';
27
+
28
+ // =============================================================================
29
+ // TYPE DEFINITIONS
30
+ // =============================================================================
31
+
32
+ export type LoadingStateSize = 'small' | 'medium' | 'large';
33
+
34
+ export interface LoadingStateProps {
35
+ /**
36
+ * Emoji/icon to display (changes per screen context)
37
+ * Examples: 🏠 Home, ⚙️ Settings, 💪 Workout, 🧘 Meditation, 📊 Analytics
38
+ */
39
+ icon?: string;
40
+
41
+ /**
42
+ * Optional loading message
43
+ */
44
+ message?: string;
45
+
46
+ /**
47
+ * Size variant
48
+ */
49
+ size?: LoadingStateSize;
50
+
51
+ /**
52
+ * Full screen overlay mode
53
+ */
54
+ fullScreen?: boolean;
55
+ }
56
+
57
+ // =============================================================================
58
+ // SIZE CONFIGURATION
59
+ // =============================================================================
60
+
61
+ interface SizeConfig {
62
+ iconSize: number;
63
+ showMessage: boolean;
64
+ containerPadding: number;
65
+ }
66
+
67
+ const SIZE_CONFIG: Record<LoadingStateSize, SizeConfig> = {
68
+ small: {
69
+ iconSize: 32,
70
+ showMessage: false,
71
+ containerPadding: 16,
72
+ },
73
+ medium: {
74
+ iconSize: 48,
75
+ showMessage: true,
76
+ containerPadding: 24,
77
+ },
78
+ large: {
79
+ iconSize: 64,
80
+ showMessage: true,
81
+ containerPadding: 32,
82
+ },
83
+ };
84
+
85
+ // =============================================================================
86
+ // COMPONENT IMPLEMENTATION
87
+ // =============================================================================
88
+
89
+ export const LoadingState: React.FC<LoadingStateProps> = ({
90
+ icon = '⏳', // Default hourglass icon
91
+ message,
92
+ size = 'large',
93
+ fullScreen = false,
94
+ }) => {
95
+ // ✅ Dynamic theme tokens
96
+ const tokens = useAppDesignTokens();
97
+
98
+ // Animation ref for breathing effect
99
+ const scaleAnim = useRef(new Animated.Value(1)).current;
100
+
101
+ // Size configuration
102
+ const config = SIZE_CONFIG[size];
103
+
104
+ /**
105
+ * Breathing Animation Effect
106
+ * Smoothly scales icon from 1 → 1.15 → 1 in continuous loop
107
+ * Creates calming, natural breathing sensation
108
+ */
109
+ useEffect(() => {
110
+ const breathingAnimation = Animated.loop(
111
+ Animated.sequence([
112
+ // Expand (inhale)
113
+ Animated.timing(scaleAnim, {
114
+ toValue: 1.15,
115
+ duration: tokens.animations.slowest,
116
+ easing: Easing.inOut(Easing.ease),
117
+ useNativeDriver: true,
118
+ }),
119
+ // Contract (exhale)
120
+ Animated.timing(scaleAnim, {
121
+ toValue: 1,
122
+ duration: tokens.animations.slowest,
123
+ easing: Easing.inOut(Easing.ease),
124
+ useNativeDriver: true,
125
+ }),
126
+ ])
127
+ );
128
+
129
+ breathingAnimation.start();
130
+
131
+ return () => {
132
+ breathingAnimation.stop();
133
+ };
134
+ }, [scaleAnim]);
135
+
136
+ // Dynamic styles based on theme
137
+ const styles = useMemo(() => getStyles(tokens, config, fullScreen), [tokens, config, fullScreen]);
138
+
139
+ return (
140
+ <View style={styles.container}>
141
+ {/* Animated Icon/Emoji */}
142
+ <Animated.Text
143
+ style={[
144
+ styles.icon,
145
+ {
146
+ fontSize: config.iconSize,
147
+ transform: [{ scale: scaleAnim }],
148
+ },
149
+ ]}
150
+ >
151
+ {icon}
152
+ </Animated.Text>
153
+
154
+ {/* Optional Loading Message */}
155
+ {config.showMessage && message && (
156
+ <AtomicText
157
+ type="bodyMedium"
158
+ style={styles.message}
159
+ >
160
+ {message}
161
+ </AtomicText>
162
+ )}
163
+ </View>
164
+ );
165
+ };
166
+
167
+ // =============================================================================
168
+ // STYLES
169
+ // =============================================================================
170
+
171
+ const getStyles = (
172
+ tokens: ReturnType<typeof useAppDesignTokens>,
173
+ config: SizeConfig,
174
+ fullScreen: boolean
175
+ ) => StyleSheet.create({
176
+ container: {
177
+ ...(fullScreen ? {
178
+ flex: 1,
179
+ justifyContent: 'center',
180
+ alignItems: 'center',
181
+ backgroundColor: tokens.colors.backgroundPrimary,
182
+ } : {
183
+ justifyContent: 'center',
184
+ alignItems: 'center',
185
+ padding: config.containerPadding,
186
+ }),
187
+ },
188
+ icon: {
189
+ textAlign: 'center',
190
+ marginBottom: tokens.spacing.md,
191
+ },
192
+ message: {
193
+ color: tokens.colors.textSecondary,
194
+ textAlign: 'center',
195
+ marginTop: tokens.spacing.sm,
196
+ maxWidth: 300,
197
+ },
198
+ });
199
+
200
+ export default LoadingState;
@@ -117,7 +117,7 @@ export const AtomicConfirmationModal: React.FC<AtomicConfirmationModalProps> = (
117
117
  <Modal
118
118
  visible={visible}
119
119
  transparent
120
- animationType="none"
120
+ animationType="fade"
121
121
  onRequestClose={onCancel}
122
122
  statusBarTranslucent
123
123
  testID={testID}
@@ -4,6 +4,7 @@
4
4
  * Provides consistent layout structure for all screens:
5
5
  * - SafeAreaView with configurable edges
6
6
  * - Optional ScrollView for content
7
+ * - Loading state support
7
8
  * - Theme-aware background colors
8
9
  * - Optional header/footer slots
9
10
  * - Consistent spacing and padding
@@ -16,6 +17,7 @@
16
17
  * Advanced:
17
18
  * <ScreenLayout
18
19
  * scrollable={false}
20
+ * loading={isLoading}
19
21
  * edges={['top', 'bottom']}
20
22
  * header={<CustomHeader />}
21
23
  * >
@@ -27,6 +29,7 @@ import React, { useMemo } from 'react';
27
29
  import { View, ScrollView, StyleSheet, ViewStyle } from 'react-native';
28
30
  import { SafeAreaView, Edge } from 'react-native-safe-area-context';
29
31
  import { useAppDesignTokens } from '../hooks/useAppDesignTokens';
32
+ import { LoadingState } from '../loading/presentation/components/LoadingState';
30
33
 
31
34
  export interface ScreenLayoutProps {
32
35
  /**
@@ -49,6 +52,22 @@ export interface ScreenLayoutProps {
49
52
  */
50
53
  edges?: Edge[];
51
54
 
55
+ /**
56
+ * Show loading state
57
+ * When true, displays LoadingState component
58
+ */
59
+ loading?: boolean;
60
+
61
+ /**
62
+ * Loading icon name (default: 'settings')
63
+ */
64
+ loadingIcon?: string;
65
+
66
+ /**
67
+ * Loading message (default: 'Loading...')
68
+ */
69
+ loadingMessage?: string;
70
+
52
71
  /**
53
72
  * Optional header component
54
73
  * Rendered above scrollable content
@@ -99,6 +118,9 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
99
118
  children,
100
119
  scrollable = true,
101
120
  edges = ['top'],
121
+ loading = false,
122
+ loadingIcon = 'settings',
123
+ loadingMessage = 'Loading...',
102
124
  header,
103
125
  footer,
104
126
  backgroundColor,
@@ -114,6 +136,24 @@ export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
114
136
 
115
137
  const bgColor = backgroundColor || tokens.colors.backgroundPrimary;
116
138
 
139
+ // Show loading state
140
+ if (loading) {
141
+ return (
142
+ <SafeAreaView
143
+ style={[styles.container, { backgroundColor: bgColor }, containerStyle]}
144
+ edges={edges}
145
+ testID={testID}
146
+ >
147
+ <LoadingState
148
+ icon={loadingIcon}
149
+ message={loadingMessage}
150
+ size="large"
151
+ fullScreen
152
+ />
153
+ </SafeAreaView>
154
+ );
155
+ }
156
+
117
157
  // Non-scrollable layout
118
158
  if (!scrollable) {
119
159
  return (