@umituz/react-native-splash 1.6.3 → 1.7.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 (42) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/lib/__tests__/mocks/expoLinearGradient.js +7 -0
  4. package/lib/__tests__/mocks/reactNative.js +16 -0
  5. package/lib/__tests__/setup.ts +57 -0
  6. package/lib/__tests__/utils/testUtils.tsx +86 -0
  7. package/lib/domain/entities/SplashOptions.ts +74 -0
  8. package/lib/index.ts +31 -0
  9. package/lib/presentation/components/SplashDecorations.tsx +56 -0
  10. package/lib/presentation/components/SplashErrorBoundary.tsx +63 -0
  11. package/lib/presentation/components/SplashLoading.tsx +74 -0
  12. package/lib/presentation/components/SplashLogo.tsx +80 -0
  13. package/lib/presentation/components/SplashScreen.tsx +175 -0
  14. package/lib/presentation/components/SplashTypography.tsx +72 -0
  15. package/lib/presentation/hooks/useSplash.ts +70 -0
  16. package/lib/presentation/utils/splashGradient.utils.ts +47 -0
  17. package/lib/types/expo-linear-gradient.d.ts +12 -0
  18. package/package.json +18 -5
  19. package/src/__tests__/SplashScreen.test.tsx +161 -0
  20. package/src/__tests__/accessibility/Accessibility.test.tsx +264 -0
  21. package/src/__tests__/basic/Basic.test.tsx +106 -0
  22. package/src/__tests__/basic/Simple.test.tsx +32 -0
  23. package/src/__tests__/edge-cases/EdgeCases.test.tsx +446 -0
  24. package/src/__tests__/integration/SplashScreen.integration.test.tsx +200 -0
  25. package/src/__tests__/mocks/expoLinearGradient.js +7 -0
  26. package/src/__tests__/mocks/reactNative.js +16 -0
  27. package/src/__tests__/performance/Performance.test.tsx +297 -0
  28. package/src/__tests__/setup.ts +57 -0
  29. package/src/__tests__/useSplash.test.tsx +123 -0
  30. package/src/__tests__/utils/testUtils.tsx +86 -0
  31. package/src/__tests__/visual/VisualRegression.test.tsx +338 -0
  32. package/src/domain/entities/SplashOptions.ts +7 -0
  33. package/src/index.ts +2 -0
  34. package/src/presentation/components/SplashDecorations.tsx +13 -5
  35. package/src/presentation/components/SplashErrorBoundary.tsx +63 -0
  36. package/src/presentation/components/SplashLoading.tsx +7 -5
  37. package/src/presentation/components/SplashLogo.tsx +4 -2
  38. package/src/presentation/components/SplashScreen.tsx +50 -30
  39. package/src/presentation/components/SplashTypography.tsx +6 -4
  40. package/src/presentation/hooks/useSplash.ts +70 -0
  41. package/src/presentation/utils/splashGradient.utils.ts +0 -0
  42. package/src/types/expo-linear-gradient.d.ts +12 -0
@@ -3,7 +3,7 @@
3
3
  * Single Responsibility: Orchestrate splash screen UI
4
4
  */
5
5
 
6
- import React, { useEffect } from "react";
6
+ import React, { useEffect, useRef } from "react";
7
7
  import { View, StyleSheet } from "react-native";
8
8
  import { LinearGradient } from "expo-linear-gradient";
9
9
  import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -13,10 +13,14 @@ import { SplashLogo } from "./SplashLogo";
13
13
  import { SplashTypography } from "./SplashTypography";
14
14
  import { SplashLoading } from "./SplashLoading";
15
15
  import { SplashDecorations } from "./SplashDecorations";
16
+ import { SplashErrorBoundary } from "./SplashErrorBoundary";
16
17
  import type { SplashOptions } from "../../domain/entities/SplashOptions";
17
18
 
18
19
  export interface SplashScreenProps extends SplashOptions {
19
20
  visible?: boolean;
21
+ textColor?: string;
22
+ iconColor?: string;
23
+ decorationColor?: string;
20
24
  }
21
25
 
22
26
  /**
@@ -36,6 +40,9 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
36
40
  renderContent,
37
41
  renderFooter,
38
42
  visible = true,
43
+ textColor,
44
+ iconColor,
45
+ decorationColor,
39
46
  }) => {
40
47
  const insets = useSafeAreaInsets();
41
48
  const { t } = useLocalization();
@@ -43,45 +50,54 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
43
50
 
44
51
  const styles = getStyles(insets, tokens.spacing);
45
52
 
53
+ const timerRef = useRef<NodeJS.Timeout>();
54
+
46
55
  useEffect(() => {
47
56
  if (!visible) return;
48
57
 
49
- const timer = setTimeout(() => {
58
+ timerRef.current = setTimeout(() => {
50
59
  if (onReady) {
51
60
  onReady();
52
61
  }
53
62
  }, minimumDisplayTime);
54
63
 
55
- return () => clearTimeout(timer);
64
+ return () => {
65
+ if (timerRef.current) {
66
+ clearTimeout(timerRef.current);
67
+ }
68
+ };
56
69
  }, [visible, minimumDisplayTime, onReady]);
57
70
 
58
71
  if (!visible) return null;
59
72
 
60
- const displayAppName = appName || t("branding.appName", "App Name");
61
- const displayTagline = tagline || t("branding.tagline", "Your tagline here");
62
- const displayLoadingText = loadingText || t("general.loading", "Loading...");
73
+ const displayAppName = appName || t("branding.appName", "");
74
+ const displayTagline = tagline || t("branding.tagline", "");
75
+ const displayLoadingText = loadingText || t("general.loading", "");
63
76
 
64
77
  // Use gradientColors if provided, otherwise use backgroundColor as solid color
65
78
  const finalBackgroundColor = gradientColors
66
79
  ? undefined
67
80
  : backgroundColor || tokens.colors.primary;
68
81
 
69
- const finalGradientColors: string[] = gradientColors
70
- ? [...gradientColors]
71
- : [finalBackgroundColor!];
72
-
73
82
  // Use LinearGradient only if gradientColors provided (length > 1)
74
83
  const hasGradient = gradientColors && gradientColors.length > 1;
75
84
 
85
+ // For LinearGradient, ensure at least 2 colors (tuple type requirement)
86
+ const finalGradientColors: [string, string, ...string[]] = hasGradient
87
+ ? (gradientColors.length >= 2
88
+ ? (gradientColors as [string, string, ...string[]])
89
+ : [gradientColors[0], gradientColors[0] || finalBackgroundColor!])
90
+ : [finalBackgroundColor!, finalBackgroundColor!];
91
+
76
92
  const content = (
77
93
  <>
78
- <SplashDecorations />
94
+ <SplashDecorations decorationColor={decorationColor} />
79
95
 
80
96
  <View style={styles.content}>
81
97
  {renderLogo ? (
82
98
  renderLogo()
83
99
  ) : (
84
- <SplashLogo logo={logo} />
100
+ <SplashLogo logo={logo} iconColor={iconColor} />
85
101
  )}
86
102
 
87
103
  {renderContent ? (
@@ -91,6 +107,7 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
91
107
  appName={displayAppName}
92
108
  tagline={displayTagline}
93
109
  tokens={tokens}
110
+ textColor={textColor}
94
111
  />
95
112
  )}
96
113
  </View>
@@ -100,6 +117,7 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
100
117
  loadingText={displayLoadingText}
101
118
  tokens={tokens}
102
119
  bottomInset={insets.bottom}
120
+ textColor={textColor}
103
121
  />
104
122
  )}
105
123
 
@@ -108,24 +126,26 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({
108
126
  );
109
127
 
110
128
  return (
111
- <View style={styles.container}>
112
- {hasGradient ? (
113
- <LinearGradient
114
- colors={finalGradientColors}
115
- start={{ x: 0, y: 0 }}
116
- end={{ x: 1, y: 1 }}
117
- style={styles.gradient}
118
- >
119
- {content}
120
- </LinearGradient>
121
- ) : (
122
- <View
123
- style={[styles.gradient, { backgroundColor: finalBackgroundColor }]}
124
- >
125
- {content}
126
- </View>
127
- )}
128
- </View>
129
+ <SplashErrorBoundary>
130
+ <View style={styles.container}>
131
+ {hasGradient ? (
132
+ <LinearGradient
133
+ colors={finalGradientColors}
134
+ start={{ x: 0, y: 0 }}
135
+ end={{ x: 1, y: 1 }}
136
+ style={styles.gradient}
137
+ >
138
+ {content}
139
+ </LinearGradient>
140
+ ) : (
141
+ <View
142
+ style={[styles.gradient, { backgroundColor: finalBackgroundColor }]}
143
+ >
144
+ {content}
145
+ </View>
146
+ )}
147
+ </View>
148
+ </SplashErrorBoundary>
129
149
  );
130
150
  };
131
151
 
@@ -11,14 +11,16 @@ export interface SplashTypographyProps {
11
11
  appName: string;
12
12
  tagline?: string;
13
13
  tokens: DesignTokens;
14
+ textColor?: string;
14
15
  }
15
16
 
16
17
  export const SplashTypography: React.FC<SplashTypographyProps> = ({
17
18
  appName,
18
19
  tagline,
19
20
  tokens,
21
+ textColor = "#FFFFFF",
20
22
  }) => {
21
- const styles = getStyles(tokens);
23
+ const styles = getStyles(tokens, textColor);
22
24
 
23
25
  return (
24
26
  <View style={styles.container}>
@@ -34,7 +36,7 @@ export const SplashTypography: React.FC<SplashTypographyProps> = ({
34
36
  );
35
37
  };
36
38
 
37
- const getStyles = (tokens: DesignTokens) => {
39
+ const getStyles = (tokens: DesignTokens, textColor: string) => {
38
40
  return StyleSheet.create({
39
41
  container: {
40
42
  alignItems: "center",
@@ -43,7 +45,7 @@ const getStyles = (tokens: DesignTokens) => {
43
45
  appName: {
44
46
  fontSize: 48,
45
47
  fontWeight: "800" as const,
46
- color: "#FFFFFF",
48
+ color: textColor,
47
49
  textAlign: "center" as const,
48
50
  marginBottom: tokens.spacing.md,
49
51
  letterSpacing: -1.2,
@@ -54,7 +56,7 @@ const getStyles = (tokens: DesignTokens) => {
54
56
  },
55
57
  tagline: {
56
58
  fontSize: 17,
57
- color: "#FFFFFF",
59
+ color: textColor,
58
60
  textAlign: "center" as const,
59
61
  opacity: 0.95,
60
62
  maxWidth: 320,
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Use Splash Hook
3
+ * Single Responsibility: Manage splash screen state and timing
4
+ */
5
+
6
+ import { useEffect, useState, useRef } from "react";
7
+
8
+ interface UseSplashOptions {
9
+ minimumDisplayTime?: number;
10
+ onReady?: () => void | Promise<void>;
11
+ autoHide?: boolean;
12
+ }
13
+
14
+ export const useSplash = ({
15
+ minimumDisplayTime = 1500,
16
+ onReady,
17
+ autoHide = true,
18
+ }: UseSplashOptions = {}) => {
19
+ const [isVisible, setIsVisible] = useState(true);
20
+ const [isReady, setIsReady] = useState(false);
21
+ const timerRef = useRef<NodeJS.Timeout>();
22
+ const isReadyRef = useRef(false);
23
+
24
+ const hide = () => {
25
+ setIsVisible(false);
26
+ };
27
+
28
+ const show = () => {
29
+ setIsVisible(true);
30
+ };
31
+
32
+ const markReady = async () => {
33
+ if (isReadyRef.current) return;
34
+
35
+ isReadyRef.current = true;
36
+ setIsReady(true);
37
+
38
+ if (onReady) {
39
+ try {
40
+ await onReady();
41
+ } catch (error) {
42
+ if (__DEV__) {
43
+ console.error("Splash onReady error:", error);
44
+ }
45
+ }
46
+ }
47
+
48
+ if (autoHide) {
49
+ timerRef.current = setTimeout(() => {
50
+ hide();
51
+ }, minimumDisplayTime);
52
+ }
53
+ };
54
+
55
+ useEffect(() => {
56
+ return () => {
57
+ if (timerRef.current) {
58
+ clearTimeout(timerRef.current);
59
+ }
60
+ };
61
+ }, []);
62
+
63
+ return {
64
+ isVisible,
65
+ isReady,
66
+ hide,
67
+ show,
68
+ markReady,
69
+ };
70
+ };
File without changes
@@ -0,0 +1,12 @@
1
+ declare module 'expo-linear-gradient' {
2
+ import { View, ViewProps } from 'react-native';
3
+
4
+ export interface LinearGradientProps extends ViewProps {
5
+ colors: readonly string[];
6
+ start?: { x: number; y: number };
7
+ end?: { x: number; y: number };
8
+ locations?: readonly number[];
9
+ }
10
+
11
+ export const LinearGradient: React.FC<LinearGradientProps>;
12
+ }