@umituz/react-native-loading 1.0.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.
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Loading Domain - SkeletonLoader Component
3
+ *
4
+ * Skeleton placeholder loader with shimmer animation.
5
+ * Provides visual feedback during content loading.
6
+ *
7
+ * @domain loading
8
+ * @layer presentation/components
9
+ */
10
+
11
+ import React, { useEffect, useRef } from 'react';
12
+ import { View, StyleSheet, Animated, type StyleProp, type ViewStyle } from 'react-native';
13
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
14
+ import type { SkeletonPattern, SkeletonConfig } from '../../domain/entities/Loading';
15
+ import {
16
+ SKELETON_PATTERNS,
17
+ LOADING_CONSTANTS,
18
+ } from '../../domain/entities/Loading';
19
+
20
+ /**
21
+ * SkeletonLoader component props
22
+ */
23
+ export interface SkeletonLoaderProps {
24
+ /** Skeleton pattern preset */
25
+ pattern?: SkeletonPattern;
26
+ /** Custom skeleton configurations */
27
+ custom?: SkeletonConfig[];
28
+ /** Number of skeleton items to render */
29
+ count?: number;
30
+ /** Custom container style */
31
+ style?: StyleProp<ViewStyle>;
32
+ /** Disable shimmer animation */
33
+ disableAnimation?: boolean;
34
+ }
35
+
36
+ /**
37
+ * SkeletonLoader Component
38
+ *
39
+ * Renders skeleton placeholders with shimmer animation.
40
+ * Provides visual feedback while content is loading.
41
+ *
42
+ * USAGE:
43
+ * ```typescript
44
+ * // List skeleton (default)
45
+ * <SkeletonLoader pattern="list" count={5} />
46
+ *
47
+ * // Card skeleton
48
+ * <SkeletonLoader pattern="card" count={3} />
49
+ *
50
+ * // Profile skeleton
51
+ * <SkeletonLoader pattern="profile" />
52
+ *
53
+ * // Text skeleton
54
+ * <SkeletonLoader pattern="text" count={3} />
55
+ *
56
+ * // Custom skeleton
57
+ * <SkeletonLoader
58
+ * pattern="custom"
59
+ * custom={[
60
+ * { width: 100, height: 100, borderRadius: 50 },
61
+ * { width: '80%', height: 20, borderRadius: 4 },
62
+ * ]}
63
+ * />
64
+ * ```
65
+ */
66
+ export const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
67
+ pattern = 'list',
68
+ custom,
69
+ count = 1,
70
+ style,
71
+ disableAnimation = false,
72
+ }) => {
73
+ const tokens = useAppDesignTokens();
74
+
75
+ // Get skeleton config
76
+ const skeletonConfigs = pattern === 'custom' && custom
77
+ ? custom
78
+ : SKELETON_PATTERNS[pattern];
79
+
80
+ // Animated value for shimmer effect
81
+ const shimmerAnim = useRef(new Animated.Value(0)).current;
82
+
83
+ useEffect(() => {
84
+ if (disableAnimation) return;
85
+
86
+ // Shimmer animation: 1.2 second cycle
87
+ const shimmerAnimation = Animated.loop(
88
+ Animated.sequence([
89
+ Animated.timing(shimmerAnim, {
90
+ toValue: 1,
91
+ duration: LOADING_CONSTANTS.SKELETON_SHIMMER_DURATION,
92
+ useNativeDriver: false, // backgroundColor animation requires false
93
+ }),
94
+ Animated.timing(shimmerAnim, {
95
+ toValue: 0,
96
+ duration: 0,
97
+ useNativeDriver: false,
98
+ }),
99
+ ])
100
+ );
101
+
102
+ shimmerAnimation.start();
103
+
104
+ return () => {
105
+ shimmerAnimation.stop();
106
+ };
107
+ }, [shimmerAnim, disableAnimation]);
108
+
109
+ // Interpolate shimmer animation to background color
110
+ const backgroundColor = shimmerAnim.interpolate({
111
+ inputRange: [0, 0.5, 1],
112
+ outputRange: [
113
+ tokens.colors.surfaceSecondary,
114
+ tokens.colors.border,
115
+ tokens.colors.surfaceSecondary,
116
+ ],
117
+ });
118
+
119
+ // Render skeleton items
120
+ const renderSkeletonItem = (index: number) => (
121
+ <View key={`skeleton-group-${index}`} style={styles.skeletonGroup}>
122
+ {skeletonConfigs.map((config, configIndex) => (
123
+ <Animated.View
124
+ key={`skeleton-${index}-${configIndex}`}
125
+ style={[
126
+ styles.skeleton,
127
+ {
128
+ width: config.width as number | `${number}%` | undefined,
129
+ height: config.height,
130
+ borderRadius: config.borderRadius,
131
+ marginBottom: config.marginBottom,
132
+ backgroundColor: disableAnimation
133
+ ? tokens.colors.surfaceSecondary
134
+ : backgroundColor,
135
+ } as any,
136
+ ]}
137
+ />
138
+ ))}
139
+ </View>
140
+ );
141
+
142
+ return (
143
+ <View style={[styles.container, style]}>
144
+ {Array.from({ length: count }).map((_, index) => renderSkeletonItem(index))}
145
+ </View>
146
+ );
147
+ };
148
+
149
+ const styles = StyleSheet.create({
150
+ container: {
151
+ width: '100%',
152
+ },
153
+ skeletonGroup: {
154
+ width: '100%',
155
+ },
156
+ skeleton: {
157
+ overflow: 'hidden',
158
+ },
159
+ });
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Loading Domain - useLoading Hook
3
+ *
4
+ * React hook for managing loading states.
5
+ * Provides consistent loading state management across components.
6
+ *
7
+ * @domain loading
8
+ * @layer presentation/hooks
9
+ */
10
+
11
+ import { useState, useCallback } from 'react';
12
+ import type { LoadingType } from '../../domain/entities/Loading';
13
+
14
+ /**
15
+ * useLoading hook return type
16
+ */
17
+ export interface UseLoadingReturn {
18
+ // Loading state
19
+ isLoading: boolean;
20
+ loadingMessage: string | null;
21
+ loadingType: LoadingType;
22
+
23
+ // Actions
24
+ startLoading: (message?: string, type?: LoadingType) => void;
25
+ stopLoading: () => void;
26
+ setLoadingMessage: (message: string | null) => void;
27
+ setLoadingType: (type: LoadingType) => void;
28
+
29
+ // Async wrapper
30
+ withLoading: <T>(
31
+ asyncFn: Promise<T>,
32
+ message?: string,
33
+ type?: LoadingType
34
+ ) => Promise<T>;
35
+ }
36
+
37
+ /**
38
+ * useLoading hook for loading state management
39
+ *
40
+ * USAGE:
41
+ * ```typescript
42
+ * const {
43
+ * isLoading,
44
+ * loadingMessage,
45
+ * startLoading,
46
+ * stopLoading,
47
+ * withLoading,
48
+ * } = useLoading();
49
+ *
50
+ * // Manual control
51
+ * const handleSave = async () => {
52
+ * startLoading('Saving data...');
53
+ * try {
54
+ * await saveData();
55
+ * } finally {
56
+ * stopLoading();
57
+ * }
58
+ * };
59
+ *
60
+ * // Automatic wrapper
61
+ * const handleLoad = () => withLoading(
62
+ * loadData(),
63
+ * 'Loading data...'
64
+ * );
65
+ *
66
+ * // In render
67
+ * {isLoading && <LoadingState message={loadingMessage} />}
68
+ * ```
69
+ */
70
+ export const useLoading = (): UseLoadingReturn => {
71
+ const [isLoading, setIsLoading] = useState(false);
72
+ const [loadingMessage, setLoadingMessage] = useState<string | null>(null);
73
+ const [loadingType, setLoadingType] = useState<LoadingType>('pulse');
74
+
75
+ /**
76
+ * Start loading state
77
+ */
78
+ const startLoading = useCallback((message?: string, type: LoadingType = 'pulse') => {
79
+ setIsLoading(true);
80
+ setLoadingMessage(message || null);
81
+ setLoadingType(type);
82
+ }, []);
83
+
84
+ /**
85
+ * Stop loading state
86
+ */
87
+ const stopLoading = useCallback(() => {
88
+ setIsLoading(false);
89
+ setLoadingMessage(null);
90
+ }, []);
91
+
92
+ /**
93
+ * Update loading message
94
+ */
95
+ const updateLoadingMessage = useCallback((message: string | null) => {
96
+ setLoadingMessage(message);
97
+ }, []);
98
+
99
+ /**
100
+ * Update loading type
101
+ */
102
+ const updateLoadingType = useCallback((type: LoadingType) => {
103
+ setLoadingType(type);
104
+ }, []);
105
+
106
+ /**
107
+ * Async wrapper that automatically manages loading state
108
+ */
109
+ const withLoading = useCallback(
110
+ async <T,>(
111
+ asyncFn: Promise<T>,
112
+ message?: string,
113
+ type: LoadingType = 'pulse'
114
+ ): Promise<T> => {
115
+ startLoading(message, type);
116
+
117
+ try {
118
+ const result = await asyncFn;
119
+ return result;
120
+ } finally {
121
+ stopLoading();
122
+ }
123
+ },
124
+ [startLoading, stopLoading]
125
+ );
126
+
127
+ return {
128
+ // Loading state
129
+ isLoading,
130
+ loadingMessage,
131
+ loadingType,
132
+
133
+ // Actions
134
+ startLoading,
135
+ stopLoading,
136
+ setLoadingMessage: updateLoadingMessage,
137
+ setLoadingType: updateLoadingType,
138
+
139
+ // Async wrapper
140
+ withLoading,
141
+ };
142
+ };
143
+
144
+ /**
145
+ * Convenience hook for simple loading state (no message)
146
+ */
147
+ export const useSimpleLoading = () => {
148
+ const [isLoading, setIsLoading] = useState(false);
149
+
150
+ const withLoading = useCallback(
151
+ async <T,>(asyncFn: Promise<T>): Promise<T> => {
152
+ setIsLoading(true);
153
+
154
+ try {
155
+ const result = await asyncFn;
156
+ return result;
157
+ } finally {
158
+ setIsLoading(false);
159
+ }
160
+ },
161
+ []
162
+ );
163
+
164
+ return {
165
+ isLoading,
166
+ startLoading: useCallback(() => setIsLoading(true), []),
167
+ stopLoading: useCallback(() => setIsLoading(false), []),
168
+ withLoading,
169
+ };
170
+ };