@umituz/react-native-splash 1.0.6 → 1.4.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.
package/README.md CHANGED
@@ -1,17 +1,13 @@
1
1
  # @umituz/react-native-splash
2
2
 
3
- Generic splash screen for React Native apps with animations, gradients, and customizable branding. Follows SOLID, DRY, KISS principles - fully reusable across hundreds of apps.
3
+ Simple splash screen for React Native apps with logo, app name, and loading indicator. Minimal dependencies, no over-engineering.
4
4
 
5
5
  ## Features
6
6
 
7
- - ✅ **Generic & Reusable**: Works with any app, fully customizable via props
8
- - ✅ **Animations**: Smooth fade, scale, and slide animations
9
- - ✅ **Gradient Backgrounds**: Support for gradient or solid colors
10
- - ✅ **Customizable Branding**: Logo, app name, tagline, footer
11
- - ✅ **Loading Indicators**: Progress bar and loading text
12
- - ✅ **SOLID Principles**: Clean architecture, easy to extend
13
- - ✅ **DRY**: No code duplication, generic components
14
- - ✅ **KISS**: Simple API, easy to understand and use
7
+ - ✅ **Simple & Lightweight**: No animations, no gradients, just the essentials
8
+ - ✅ **Minimal Dependencies**: Only requires AtomicIcon and localization
9
+ - ✅ **Customizable**: Logo, app name, tagline, background color
10
+ - ✅ **Loading Indicator**: Built-in ActivityIndicator
15
11
  - ✅ **Type-Safe**: Full TypeScript support
16
12
 
17
13
  ## Installation
@@ -37,7 +33,7 @@ import { SplashScreen } from '@umituz/react-native-splash';
37
33
  />
38
34
  ```
39
35
 
40
- ### Advanced Example
36
+ ### With Custom Background
41
37
 
42
38
  ```typescript
43
39
  <SplashScreen
@@ -45,13 +41,8 @@ import { SplashScreen } from '@umituz/react-native-splash';
45
41
  tagline="Your tagline here"
46
42
  logo="Sparkles"
47
43
  backgroundColor="#3B82F6"
48
- gradientColors={['#3B82F6', '#8B5CF6']}
49
44
  loadingText="Loading..."
50
45
  showLoading={true}
51
- showProgressBar={true}
52
- footerText="Powered by"
53
- versionText="v1.0.0"
54
- animationDuration={2000}
55
46
  minimumDisplayTime={1500}
56
47
  onReady={handleReady}
57
48
  />
@@ -79,13 +70,8 @@ interface SplashOptions {
79
70
  tagline?: string;
80
71
  logo?: ReactNode | string;
81
72
  backgroundColor?: string;
82
- gradientColors?: string[];
83
73
  loadingText?: string;
84
74
  showLoading?: boolean;
85
- showProgressBar?: boolean;
86
- footerText?: string;
87
- versionText?: string;
88
- animationDuration?: number;
89
75
  minimumDisplayTime?: number;
90
76
  onReady?: () => void | Promise<void>;
91
77
  renderLogo?: () => ReactNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-splash",
3
- "version": "1.0.6",
3
+ "version": "1.4.0",
4
4
  "description": "Generic splash screen for React Native apps with animations, gradients, and customizable branding. SOLID, DRY, KISS principles applied.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -35,6 +35,7 @@
35
35
  "@umituz/react-native-design-system-responsive": "*",
36
36
  "@umituz/react-native-design-system-theme": "*",
37
37
  "@umituz/react-native-localization": "latest",
38
+ "expo-linear-gradient": "~14.0.0",
38
39
  "react": ">=18.2.0",
39
40
  "react-native": ">=0.74.0",
40
41
  "react-native-safe-area-context": "^5.0.0"
@@ -27,10 +27,9 @@ export interface SplashOptions {
27
27
  logo?: ReactNode | string;
28
28
 
29
29
  /**
30
- * Background color or gradient colors
30
+ * Background color
31
31
  */
32
32
  backgroundColor?: string;
33
- gradientColors?: string[];
34
33
 
35
34
  /**
36
35
  * Loading text
@@ -42,26 +41,6 @@ export interface SplashOptions {
42
41
  */
43
42
  showLoading?: boolean;
44
43
 
45
- /**
46
- * Show progress bar (default: true)
47
- */
48
- showProgressBar?: boolean;
49
-
50
- /**
51
- * Footer text (e.g., "Powered by...")
52
- */
53
- footerText?: string;
54
-
55
- /**
56
- * Version text
57
- */
58
- versionText?: string;
59
-
60
- /**
61
- * Animation duration in milliseconds (default: 2000)
62
- */
63
- animationDuration?: number;
64
-
65
44
  /**
66
45
  * Minimum display time in milliseconds (default: 1500)
67
46
  */
@@ -1,17 +1,19 @@
1
1
  /**
2
2
  * Splash Screen Component
3
3
  *
4
- * Generic splash screen with animations, gradients, and customizable branding
4
+ * Simple splash screen with logo, app name, and loading indicator
5
5
  */
6
6
 
7
- import React, { useEffect, useRef, useMemo } from "react";
8
- import { View, StyleSheet, Animated } from "react-native";
7
+ import React, { useEffect } from "react";
8
+ import { View, StyleSheet, Text, ActivityIndicator } from "react-native";
9
+ import { LinearGradient } from "expo-linear-gradient";
9
10
  import { useSafeAreaInsets } from "react-native-safe-area-context";
10
- import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
11
11
  import { useLocalization } from "@umituz/react-native-localization";
12
- import { useResponsive } from "@umituz/react-native-design-system-responsive";
13
- import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system-atoms";
14
- import { STATIC_TOKENS, withAlpha } from "@umituz/react-native-design-system-theme";
12
+ import {
13
+ useDesignSystemTheme,
14
+ useAppDesignTokens,
15
+ } from "@umituz/react-native-design-system-theme";
16
+ import { AtomicIcon } from "@umituz/react-native-design-system-atoms";
15
17
  import type { SplashOptions } from "../../domain/entities/SplashOptions";
16
18
 
17
19
  export interface SplashScreenProps extends SplashOptions {
@@ -29,13 +31,8 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
29
31
  tagline,
30
32
  logo,
31
33
  backgroundColor,
32
- gradientColors,
33
34
  loadingText,
34
35
  showLoading = true,
35
- showProgressBar = true,
36
- footerText,
37
- versionText,
38
- animationDuration = 2000,
39
36
  minimumDisplayTime = 1500,
40
37
  onReady,
41
38
  renderLogo,
@@ -43,309 +40,211 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
43
40
  renderFooter,
44
41
  visible = true,
45
42
  }) => {
46
- const tokens = useAppDesignTokens();
47
- const responsive = useResponsive();
48
43
  const insets = useSafeAreaInsets();
49
44
  const { t } = useLocalization();
45
+ const { themeMode } = useDesignSystemTheme();
46
+ const tokens = useAppDesignTokens();
50
47
 
51
- // Safety check: Don't render if tokens or responsive are not ready
52
- if (!tokens || !responsive || !tokens.spacing || !responsive.horizontalPadding) {
53
- return null;
54
- }
48
+ const isDark = themeMode === "dark";
49
+ const colors = tokens.colors;
50
+ const spacing = tokens.spacing;
55
51
 
56
- const styles = useMemo(
57
- () => getStyles(tokens, responsive, insets, backgroundColor, gradientColors),
58
- [tokens, responsive, insets, backgroundColor, gradientColors],
59
- );
60
-
61
- // Animation values
62
- const fadeAnim = useRef(new Animated.Value(0)).current;
63
- const scaleAnim = useRef(new Animated.Value(0.8)).current;
64
- const slideAnim = useRef(new Animated.Value(50)).current;
52
+ // Use provided backgroundColor or theme-based color
53
+ const finalBackgroundColor = backgroundColor || colors.primary;
54
+
55
+ // Generate gradient colors based on theme
56
+ const gradientColors = backgroundColor
57
+ ? [backgroundColor, adjustColorBrightness(backgroundColor, isDark ? -15 : 10)]
58
+ : isDark
59
+ ? [colors.primary, adjustColorBrightness(colors.primary, -20)]
60
+ : [colors.primary, adjustColorBrightness(colors.primary, 15)];
61
+
62
+ const styles = getStyles(insets, finalBackgroundColor, colors, spacing, isDark);
65
63
 
66
64
  /**
67
- * Initialize animations
65
+ * Call onReady after minimum display time
68
66
  */
69
67
  useEffect(() => {
70
68
  if (!visible) return;
71
69
 
72
- // Start entrance animations
73
- Animated.parallel([
74
- Animated.timing(fadeAnim, {
75
- toValue: 1,
76
- duration: 1000,
77
- useNativeDriver: true,
78
- }),
79
- Animated.spring(scaleAnim, {
80
- toValue: 1,
81
- tension: 100,
82
- friction: 8,
83
- useNativeDriver: true,
84
- }),
85
- Animated.timing(slideAnim, {
86
- toValue: 0,
87
- duration: 800,
88
- useNativeDriver: true,
89
- }),
90
- ]).start();
91
-
92
- // Call onReady after minimum display time
93
70
  const timer = setTimeout(() => {
94
71
  if (onReady) {
95
72
  onReady();
96
73
  }
97
- }, Math.max(minimumDisplayTime, animationDuration));
74
+ }, minimumDisplayTime);
98
75
 
99
76
  return () => clearTimeout(timer);
100
- }, [visible, fadeAnim, scaleAnim, slideAnim, minimumDisplayTime, animationDuration, onReady]);
77
+ }, [visible, minimumDisplayTime, onReady]);
101
78
 
102
79
  if (!visible) return null;
103
80
 
104
81
  const displayAppName = appName || t("branding.appName", "App Name");
105
82
  const displayTagline = tagline || t("branding.tagline", "Your tagline here");
106
83
  const displayLoadingText = loadingText || t("general.loading", "Loading...");
107
- const displayFooterText = footerText || t("branding.poweredBy", "Powered by");
108
- const displayVersionText = versionText;
109
84
 
110
85
  return (
111
86
  <View style={styles.container}>
112
- {/* Background Gradient Effect */}
113
- {gradientColors && gradientColors.length > 0 ? (
114
- <View style={[styles.backgroundGradient, { backgroundColor: gradientColors[0] }]} />
115
- ) : (
116
- <View style={[styles.backgroundGradient, { backgroundColor: backgroundColor || tokens.colors.primary }]} />
117
- )}
118
-
119
- {/* Main Content */}
120
- <Animated.View
121
- style={[
122
- styles.content,
123
- {
124
- opacity: fadeAnim,
125
- transform: [{ scale: scaleAnim }],
126
- },
127
- ]}
87
+ <LinearGradient
88
+ colors={gradientColors}
89
+ start={{ x: 0, y: 0 }}
90
+ end={{ x: 1, y: 1 }}
91
+ style={styles.gradient}
128
92
  >
129
- {renderLogo ? (
130
- renderLogo()
131
- ) : (
132
- <View style={styles.logoContainer}>
133
- <Animated.View
134
- style={[
135
- styles.logoBackground,
136
- {
137
- transform: [{ scale: scaleAnim }],
138
- },
139
- ]}
140
- >
141
- {typeof logo === "string" ? (
142
- <AtomicIcon name={logo || "Sparkles"} size="xl" color="primary" />
143
- ) : logo ? (
144
- logo
145
- ) : (
146
- <AtomicIcon name="Sparkles" size="xl" color="primary" />
147
- )}
148
- </Animated.View>
149
- </View>
150
- )}
151
-
152
- {renderContent ? (
153
- renderContent()
154
- ) : (
155
- <Animated.View
156
- style={[
157
- styles.textContainer,
158
- {
159
- opacity: fadeAnim,
160
- transform: [{ translateY: slideAnim }],
161
- },
162
- ]}
163
- >
164
- <AtomicText style={styles.appName}>{displayAppName}</AtomicText>
165
- <AtomicText style={styles.tagline}>{displayTagline}</AtomicText>
166
- </Animated.View>
167
- )}
168
- </Animated.View>
169
-
170
- {/* Loading Indicator */}
171
- {showLoading && (
172
- <Animated.View
173
- style={[
174
- styles.loadingContainer,
175
- {
176
- opacity: fadeAnim,
177
- },
178
- ]}
179
- >
180
- {showProgressBar && (
181
- <View style={styles.loadingBar}>
182
- <Animated.View
183
- style={[
184
- styles.loadingProgress,
185
- {
186
- transform: [{ scaleX: scaleAnim }],
187
- },
188
- ]}
189
- />
93
+ {/* Main Content */}
94
+ <View style={styles.content}>
95
+ {renderLogo ? (
96
+ renderLogo()
97
+ ) : (
98
+ <View style={styles.logoContainer}>
99
+ <View style={styles.logoBackground}>
100
+ {typeof logo === "string" ? (
101
+ <AtomicIcon
102
+ name={logo || "Sparkles"}
103
+ size="xl"
104
+ color={isDark ? "primary" : "white"}
105
+ />
106
+ ) : logo ? (
107
+ logo
108
+ ) : (
109
+ <AtomicIcon
110
+ name="Sparkles"
111
+ size="xl"
112
+ color={isDark ? "primary" : "white"}
113
+ />
114
+ )}
115
+ </View>
190
116
  </View>
191
117
  )}
192
- <AtomicText style={styles.loadingText}>{displayLoadingText}</AtomicText>
193
- </Animated.View>
194
- )}
195
118
 
196
- {/* Footer */}
197
- {renderFooter ? (
198
- renderFooter()
199
- ) : (
200
- <Animated.View
201
- style={[
202
- styles.footer,
203
- {
204
- opacity: fadeAnim,
205
- },
206
- ]}
207
- >
208
- {displayFooterText && (
209
- <AtomicText style={styles.footerText}>{displayFooterText}</AtomicText>
210
- )}
211
- {displayVersionText && (
212
- <AtomicText style={styles.versionText}>{displayVersionText}</AtomicText>
119
+ {renderContent ? (
120
+ renderContent()
121
+ ) : (
122
+ <View style={styles.textContainer}>
123
+ <Text style={styles.appName}>{displayAppName}</Text>
124
+ {displayTagline && (
125
+ <Text style={styles.tagline}>{displayTagline}</Text>
126
+ )}
127
+ </View>
213
128
  )}
214
- </Animated.View>
215
- )}
129
+ </View>
130
+
131
+ {/* Loading Indicator */}
132
+ {showLoading && (
133
+ <View style={styles.loadingContainer}>
134
+ <ActivityIndicator
135
+ size="large"
136
+ color={isDark ? colors.primary : "#FFFFFF"}
137
+ />
138
+ <Text style={styles.loadingText}>{displayLoadingText}</Text>
139
+ </View>
140
+ )}
141
+
142
+ {/* Footer */}
143
+ {renderFooter && renderFooter()}
144
+ </LinearGradient>
216
145
  </View>
217
146
  );
218
147
  };
219
148
 
149
+ /**
150
+ * Adjust color brightness
151
+ */
152
+ const adjustColorBrightness = (hex: string, percent: number): string => {
153
+ const num = parseInt(hex.replace("#", ""), 16);
154
+ const amt = Math.round(2.55 * percent);
155
+ const R = (num >> 16) + amt;
156
+ const G = ((num >> 8) & 0x00ff) + amt;
157
+ const B = (num & 0x0000ff) + amt;
158
+ return (
159
+ "#" +
160
+ (
161
+ 0x1000000 +
162
+ (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 +
163
+ (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 +
164
+ (B < 255 ? (B < 1 ? 0 : B) : 255)
165
+ )
166
+ .toString(16)
167
+ .slice(1)
168
+ );
169
+ };
170
+
220
171
  const getStyles = (
221
- tokens: ReturnType<typeof useAppDesignTokens>,
222
- responsive: ReturnType<typeof useResponsive>,
223
172
  insets: { top: number; bottom: number },
224
- backgroundColor?: string,
225
- gradientColors?: string[],
173
+ backgroundColor: string,
174
+ colors: any,
175
+ spacing: any,
176
+ isDark: boolean,
226
177
  ) => {
227
- // Safety check: Return empty styles if tokens or responsive are not ready
228
- if (!tokens || !responsive || !tokens.spacing || !tokens.colors || !responsive.horizontalPadding) {
229
- return StyleSheet.create({
230
- container: { flex: 1 },
231
- backgroundGradient: {},
232
- content: {},
233
- logoContainer: {},
234
- logoBackground: {},
235
- textContainer: {},
236
- appName: {},
237
- tagline: {},
238
- loadingContainer: {},
239
- loadingBar: {},
240
- loadingProgress: {},
241
- loadingText: {},
242
- footer: {},
243
- footerText: {},
244
- versionText: {},
245
- });
246
- }
247
-
178
+ const textColor = isDark ? colors.textPrimary : "#FFFFFF";
179
+ const logoBgOpacity = isDark ? 0.15 : 0.2;
180
+ const textOpacity = isDark ? 1 : 0.95;
181
+
248
182
  return StyleSheet.create({
249
183
  container: {
250
184
  flex: 1,
251
- backgroundColor: backgroundColor || tokens.colors.primary,
252
- paddingHorizontal: responsive.horizontalPadding,
185
+ backgroundColor: backgroundColor,
186
+ },
187
+ gradient: {
188
+ flex: 1,
253
189
  paddingTop: insets.top,
254
190
  paddingBottom: insets.bottom,
255
191
  },
256
- backgroundGradient: {
257
- position: "absolute",
258
- top: 0,
259
- left: 0,
260
- right: 0,
261
- bottom: 0,
262
- opacity: 0.9,
263
- },
264
192
  content: {
265
- flex: 3,
193
+ flex: 1,
266
194
  alignItems: "center",
267
195
  justifyContent: "center",
268
- paddingHorizontal: tokens.spacing.lg,
196
+ paddingHorizontal: spacing.lg,
269
197
  },
270
198
  logoContainer: {
271
- marginBottom: tokens.spacing.xxl * 2,
199
+ marginBottom: spacing.xl,
272
200
  alignItems: "center",
273
201
  justifyContent: "center",
274
202
  },
275
203
  logoBackground: {
276
- width: responsive.logoSize,
277
- height: responsive.logoSize,
278
- borderRadius: STATIC_TOKENS.borders.radius.full,
279
- backgroundColor: tokens.colors.surface,
204
+ width: 120,
205
+ height: 120,
206
+ borderRadius: 60,
207
+ backgroundColor: isDark
208
+ ? `${colors.primary}${Math.round(logoBgOpacity * 255).toString(16).padStart(2, "0")}`
209
+ : `rgba(255, 255, 255, ${logoBgOpacity})`,
280
210
  alignItems: "center",
281
211
  justifyContent: "center",
282
- padding: STATIC_TOKENS.spacing.md,
212
+ padding: spacing.md,
213
+ borderWidth: isDark ? 2 : 0,
214
+ borderColor: isDark ? `${colors.primary}40` : "transparent",
283
215
  },
284
216
  textContainer: {
285
217
  alignItems: "center",
286
- marginBottom: tokens.spacing.xxl * 2,
218
+ marginBottom: spacing.xxxl,
287
219
  },
288
220
  appName: {
289
- ...STATIC_TOKENS.typography.headingLarge,
290
- color: tokens.colors.textInverse,
221
+ fontSize: 32,
222
+ fontWeight: "700",
223
+ color: textColor,
291
224
  textAlign: "center",
292
- marginBottom: tokens.spacing.md,
293
- fontSize: responsive.getFontSize(STATIC_TOKENS.typography.headingLarge.fontSize ?? 32),
294
- fontWeight: STATIC_TOKENS.typography.bold,
225
+ marginBottom: spacing.sm,
226
+ letterSpacing: -0.5,
295
227
  },
296
228
  tagline: {
297
- ...STATIC_TOKENS.typography.bodyLarge,
298
- color: tokens.colors.textInverse,
229
+ fontSize: 16,
230
+ color: textColor,
299
231
  textAlign: "center",
300
- opacity: 0.9,
301
- maxWidth: responsive.maxContentWidth,
302
- fontSize: responsive.getFontSize(STATIC_TOKENS.typography.bodyLarge.fontSize ?? 18),
232
+ opacity: textOpacity * 0.85,
233
+ maxWidth: 300,
234
+ lineHeight: 22,
235
+ fontWeight: "400",
303
236
  },
304
237
  loadingContainer: {
305
- flex: 1,
306
238
  alignItems: "center",
307
239
  justifyContent: "center",
308
- width: responsive.maxContentWidth,
309
- alignSelf: "center",
310
- },
311
- loadingBar: {
312
- width: "100%",
313
- height: 4,
314
- backgroundColor: withAlpha(tokens.colors.textInverse, 0.3),
315
- borderRadius: STATIC_TOKENS.borders.radius.xs,
316
- marginBottom: tokens.spacing.md,
317
- overflow: "hidden",
318
- },
319
- loadingProgress: {
320
- width: "100%",
321
- height: "100%",
322
- backgroundColor: tokens.colors.textInverse,
323
- borderRadius: STATIC_TOKENS.borders.radius.xs,
240
+ paddingBottom: spacing.xxxl,
324
241
  },
325
242
  loadingText: {
326
- ...STATIC_TOKENS.typography.bodyMedium,
327
- color: tokens.colors.textInverse,
328
- opacity: 0.8,
329
- fontSize: responsive.getFontSize(STATIC_TOKENS.typography.bodyMedium.fontSize ?? 16),
330
- },
331
- footer: {
332
- flex: 0.5,
333
- alignItems: "center",
334
- justifyContent: "flex-start",
335
- paddingBottom: tokens.spacing.md,
336
- },
337
- footerText: {
338
- ...STATIC_TOKENS.typography.caption,
339
- color: tokens.colors.textInverse,
340
- opacity: 0.7,
341
- marginBottom: tokens.spacing.xs,
342
- fontSize: responsive.getFontSize(STATIC_TOKENS.typography.caption.fontSize ?? 12),
343
- },
344
- versionText: {
345
- ...STATIC_TOKENS.typography.caption,
346
- color: tokens.colors.textInverse,
347
- opacity: 0.5,
348
- fontSize: responsive.getFontSize(STATIC_TOKENS.typography.caption.fontSize ?? 12),
243
+ fontSize: 14,
244
+ color: textColor,
245
+ opacity: textOpacity * 0.7,
246
+ marginTop: spacing.md,
247
+ fontWeight: "500",
349
248
  },
350
249
  });
351
250
  };