rn-onboarding-analytics 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.
Files changed (105) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +752 -0
  3. package/lib/module/index.js +26 -0
  4. package/lib/module/index.js.map +1 -0
  5. package/lib/module/package.json +1 -0
  6. package/lib/module/spill-onboarding/adapters/expo-image.js +13 -0
  7. package/lib/module/spill-onboarding/adapters/expo-image.js.map +1 -0
  8. package/lib/module/spill-onboarding/adapters/react-native-svg.js +16 -0
  9. package/lib/module/spill-onboarding/adapters/react-native-svg.js.map +1 -0
  10. package/lib/module/spill-onboarding/analytics.js +56 -0
  11. package/lib/module/spill-onboarding/analytics.js.map +1 -0
  12. package/lib/module/spill-onboarding/buttons/PrimaryButton.js +50 -0
  13. package/lib/module/spill-onboarding/buttons/PrimaryButton.js.map +1 -0
  14. package/lib/module/spill-onboarding/buttons/SecondaryButton.js +51 -0
  15. package/lib/module/spill-onboarding/buttons/SecondaryButton.js.map +1 -0
  16. package/lib/module/spill-onboarding/buttons/SkipButton.js +35 -0
  17. package/lib/module/spill-onboarding/buttons/SkipButton.js.map +1 -0
  18. package/lib/module/spill-onboarding/components/OnboardingImageContainer.js +128 -0
  19. package/lib/module/spill-onboarding/components/OnboardingImageContainer.js.map +1 -0
  20. package/lib/module/spill-onboarding/components/OnboardingIntroPanel.js +97 -0
  21. package/lib/module/spill-onboarding/components/OnboardingIntroPanel.js.map +1 -0
  22. package/lib/module/spill-onboarding/components/OnboardingModal.js +69 -0
  23. package/lib/module/spill-onboarding/components/OnboardingModal.js.map +1 -0
  24. package/lib/module/spill-onboarding/components/OnboardingStepContainer.js +60 -0
  25. package/lib/module/spill-onboarding/components/OnboardingStepContainer.js.map +1 -0
  26. package/lib/module/spill-onboarding/components/OnboardingStepPanel.js +122 -0
  27. package/lib/module/spill-onboarding/components/OnboardingStepPanel.js.map +1 -0
  28. package/lib/module/spill-onboarding/hooks/useMeasureHeight.js +18 -0
  29. package/lib/module/spill-onboarding/hooks/useMeasureHeight.js.map +1 -0
  30. package/lib/module/spill-onboarding/icons/ArrowLeftIcon.js +57 -0
  31. package/lib/module/spill-onboarding/icons/ArrowLeftIcon.js.map +1 -0
  32. package/lib/module/spill-onboarding/icons/CloseIcon.js +49 -0
  33. package/lib/module/spill-onboarding/icons/CloseIcon.js.map +1 -0
  34. package/lib/module/spill-onboarding/index.js +206 -0
  35. package/lib/module/spill-onboarding/index.js.map +1 -0
  36. package/lib/module/spill-onboarding/types.js +4 -0
  37. package/lib/module/spill-onboarding/types.js.map +1 -0
  38. package/lib/module/utils/ThemeContext.js +78 -0
  39. package/lib/module/utils/ThemeContext.js.map +1 -0
  40. package/lib/module/utils/fontStyles.js +21 -0
  41. package/lib/module/utils/fontStyles.js.map +1 -0
  42. package/lib/module/utils/theme.js +27 -0
  43. package/lib/module/utils/theme.js.map +1 -0
  44. package/lib/typescript/package.json +1 -0
  45. package/lib/typescript/src/index.d.ts +5 -0
  46. package/lib/typescript/src/index.d.ts.map +1 -0
  47. package/lib/typescript/src/spill-onboarding/adapters/expo-image.d.ts +4 -0
  48. package/lib/typescript/src/spill-onboarding/adapters/expo-image.d.ts.map +1 -0
  49. package/lib/typescript/src/spill-onboarding/adapters/react-native-svg.d.ts +5 -0
  50. package/lib/typescript/src/spill-onboarding/adapters/react-native-svg.d.ts.map +1 -0
  51. package/lib/typescript/src/spill-onboarding/analytics.d.ts +2 -0
  52. package/lib/typescript/src/spill-onboarding/analytics.d.ts.map +1 -0
  53. package/lib/typescript/src/spill-onboarding/buttons/PrimaryButton.d.ts +13 -0
  54. package/lib/typescript/src/spill-onboarding/buttons/PrimaryButton.d.ts.map +1 -0
  55. package/lib/typescript/src/spill-onboarding/buttons/SecondaryButton.d.ts +13 -0
  56. package/lib/typescript/src/spill-onboarding/buttons/SecondaryButton.d.ts.map +1 -0
  57. package/lib/typescript/src/spill-onboarding/buttons/SkipButton.d.ts +6 -0
  58. package/lib/typescript/src/spill-onboarding/buttons/SkipButton.d.ts.map +1 -0
  59. package/lib/typescript/src/spill-onboarding/components/OnboardingImageContainer.d.ts +18 -0
  60. package/lib/typescript/src/spill-onboarding/components/OnboardingImageContainer.d.ts.map +1 -0
  61. package/lib/typescript/src/spill-onboarding/components/OnboardingIntroPanel.d.ts +4 -0
  62. package/lib/typescript/src/spill-onboarding/components/OnboardingIntroPanel.d.ts.map +1 -0
  63. package/lib/typescript/src/spill-onboarding/components/OnboardingModal.d.ts +8 -0
  64. package/lib/typescript/src/spill-onboarding/components/OnboardingModal.d.ts.map +1 -0
  65. package/lib/typescript/src/spill-onboarding/components/OnboardingStepContainer.d.ts +16 -0
  66. package/lib/typescript/src/spill-onboarding/components/OnboardingStepContainer.d.ts.map +1 -0
  67. package/lib/typescript/src/spill-onboarding/components/OnboardingStepPanel.d.ts +4 -0
  68. package/lib/typescript/src/spill-onboarding/components/OnboardingStepPanel.d.ts.map +1 -0
  69. package/lib/typescript/src/spill-onboarding/hooks/useMeasureHeight.d.ts +9 -0
  70. package/lib/typescript/src/spill-onboarding/hooks/useMeasureHeight.d.ts.map +1 -0
  71. package/lib/typescript/src/spill-onboarding/icons/ArrowLeftIcon.d.ts +7 -0
  72. package/lib/typescript/src/spill-onboarding/icons/ArrowLeftIcon.d.ts.map +1 -0
  73. package/lib/typescript/src/spill-onboarding/icons/CloseIcon.d.ts +7 -0
  74. package/lib/typescript/src/spill-onboarding/icons/CloseIcon.d.ts.map +1 -0
  75. package/lib/typescript/src/spill-onboarding/index.d.ts +4 -0
  76. package/lib/typescript/src/spill-onboarding/index.d.ts.map +1 -0
  77. package/lib/typescript/src/spill-onboarding/types.d.ts +192 -0
  78. package/lib/typescript/src/spill-onboarding/types.d.ts.map +1 -0
  79. package/lib/typescript/src/utils/ThemeContext.d.ts +14 -0
  80. package/lib/typescript/src/utils/ThemeContext.d.ts.map +1 -0
  81. package/lib/typescript/src/utils/fontStyles.d.ts +19 -0
  82. package/lib/typescript/src/utils/fontStyles.d.ts.map +1 -0
  83. package/lib/typescript/src/utils/theme.d.ts +30 -0
  84. package/lib/typescript/src/utils/theme.d.ts.map +1 -0
  85. package/package.json +177 -0
  86. package/src/index.tsx +35 -0
  87. package/src/spill-onboarding/adapters/expo-image.ts +12 -0
  88. package/src/spill-onboarding/adapters/react-native-svg.ts +17 -0
  89. package/src/spill-onboarding/analytics.ts +75 -0
  90. package/src/spill-onboarding/buttons/PrimaryButton.tsx +70 -0
  91. package/src/spill-onboarding/buttons/SecondaryButton.tsx +71 -0
  92. package/src/spill-onboarding/buttons/SkipButton.tsx +34 -0
  93. package/src/spill-onboarding/components/OnboardingImageContainer.tsx +181 -0
  94. package/src/spill-onboarding/components/OnboardingIntroPanel.tsx +105 -0
  95. package/src/spill-onboarding/components/OnboardingModal.tsx +75 -0
  96. package/src/spill-onboarding/components/OnboardingStepContainer.tsx +85 -0
  97. package/src/spill-onboarding/components/OnboardingStepPanel.tsx +118 -0
  98. package/src/spill-onboarding/hooks/useMeasureHeight.ts +21 -0
  99. package/src/spill-onboarding/icons/ArrowLeftIcon.tsx +69 -0
  100. package/src/spill-onboarding/icons/CloseIcon.tsx +55 -0
  101. package/src/spill-onboarding/index.tsx +251 -0
  102. package/src/spill-onboarding/types.ts +243 -0
  103. package/src/utils/ThemeContext.tsx +87 -0
  104. package/src/utils/fontStyles.ts +19 -0
  105. package/src/utils/theme.ts +29 -0
@@ -0,0 +1,105 @@
1
+ import { useMemo } from 'react';
2
+ import { Image, StyleSheet, Text, View } from 'react-native';
3
+ import { useTheme } from '../../utils/ThemeContext';
4
+ import type { Theme } from '../../utils/theme';
5
+ import PrimaryButton from '../buttons/PrimaryButton';
6
+ import { fontSizes, lineHeights } from '../../utils/fontStyles';
7
+ import type { OnboardingIntroPanelProps } from '../types';
8
+
9
+ function OnboardingIntroPanel({
10
+ onPressStart,
11
+ title,
12
+ subtitle,
13
+ button,
14
+ image,
15
+ }: OnboardingIntroPanelProps) {
16
+ const { theme } = useTheme();
17
+ const styles = useMemo(() => createStyles(theme), [theme]);
18
+
19
+ const renderTitle = () => {
20
+ if (!title) {
21
+ return undefined;
22
+ }
23
+
24
+ if (typeof title === 'string') {
25
+ return (
26
+ <Text style={[styles.text, styles.line1, styles.titleText]}>
27
+ {title}
28
+ </Text>
29
+ );
30
+ }
31
+
32
+ return title;
33
+ };
34
+
35
+ const renderSubtitle = () => {
36
+ if (!subtitle) {
37
+ return undefined;
38
+ }
39
+
40
+ if (typeof subtitle === 'string') {
41
+ return (
42
+ <Text style={[styles.text, styles.line2, styles.subtitleText]}>
43
+ {subtitle}
44
+ </Text>
45
+ );
46
+ }
47
+
48
+ return subtitle;
49
+ };
50
+
51
+ const renderButton = () => {
52
+ if (typeof button === 'string') {
53
+ return <PrimaryButton text={button} onPress={onPressStart} />;
54
+ }
55
+
56
+ return button({ onPressStart });
57
+ };
58
+
59
+ return (
60
+ <View style={styles.container}>
61
+ {typeof image === 'function'
62
+ ? image()
63
+ : image && <Image source={image} style={styles.image} />}
64
+ <View style={styles.textContainer}>
65
+ {renderTitle()}
66
+ {renderSubtitle()}
67
+ </View>
68
+ {renderButton()}
69
+ </View>
70
+ );
71
+ }
72
+
73
+ export default OnboardingIntroPanel;
74
+
75
+ const createStyles = (theme: Theme) =>
76
+ StyleSheet.create({
77
+ container: {
78
+ marginTop: 16,
79
+ },
80
+ image: {
81
+ alignSelf: 'center',
82
+ },
83
+ textContainer: {
84
+ alignItems: 'center',
85
+ marginBottom: 48,
86
+ },
87
+ text: {
88
+ fontSize: fontSizes.xxl,
89
+ lineHeight: lineHeights.xxl,
90
+ textAlign: 'center',
91
+ },
92
+ line1: {
93
+ marginTop: 20,
94
+ color: theme.text.primary,
95
+ },
96
+ line2: {
97
+ color: theme.bg.primary,
98
+ },
99
+ titleText: {
100
+ fontFamily: theme.fonts.introTitle,
101
+ },
102
+ subtitleText: {
103
+ fontFamily: theme.fonts.introSubtitle,
104
+ },
105
+ });
@@ -0,0 +1,75 @@
1
+ import React, { useMemo } from 'react';
2
+ import {
3
+ View,
4
+ StyleSheet,
5
+ Modal,
6
+ TouchableOpacity,
7
+ useWindowDimensions,
8
+ } from 'react-native';
9
+ import { useTheme } from '../../utils/ThemeContext';
10
+ import type { Theme } from '../../utils/theme';
11
+
12
+ interface OnboardingModalProps {
13
+ onSkip?: () => void;
14
+ children: React.ReactNode;
15
+ }
16
+
17
+ export default function OnboardingModal({
18
+ onSkip,
19
+ children,
20
+ }: OnboardingModalProps) {
21
+ const { theme } = useTheme();
22
+ const { height, width } = useWindowDimensions();
23
+ const styles = useMemo(
24
+ () => createStyles(theme, height, width),
25
+ [height, width, theme]
26
+ );
27
+
28
+ return (
29
+ <Modal visible transparent onRequestClose={onSkip}>
30
+ <View style={styles.webOverlay}>
31
+ <TouchableOpacity
32
+ style={styles.webBackdrop}
33
+ activeOpacity={1}
34
+ onPress={onSkip}
35
+ />
36
+ <View style={styles.webModal}>{children}</View>
37
+ </View>
38
+ </Modal>
39
+ );
40
+ }
41
+
42
+ const createStyles = (theme: Theme, height: number, width: number) =>
43
+ StyleSheet.create({
44
+ webOverlay: {
45
+ flex: 1,
46
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
47
+ justifyContent: 'center',
48
+ alignItems: 'center',
49
+ },
50
+ webBackdrop: {
51
+ position: 'absolute',
52
+ top: 0,
53
+ left: 0,
54
+ right: 0,
55
+ bottom: 0,
56
+ },
57
+ webModal: {
58
+ width: Math.min(width, 500),
59
+ height: Math.min(height, 800),
60
+ borderRadius: width > 500 ? 28 : 0,
61
+ shadowColor: '#000',
62
+ shadowOffset: {
63
+ width: 0,
64
+ height: 10,
65
+ },
66
+ shadowOpacity: 0.25,
67
+ shadowRadius: 20,
68
+ overflow: 'hidden',
69
+ backgroundColor: theme.bg.secondary,
70
+ },
71
+ webContent: {
72
+ flex: 1,
73
+ paddingTop: 16,
74
+ },
75
+ });
@@ -0,0 +1,85 @@
1
+ import React, { useMemo, type ReactNode } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import Reanimated, {
4
+ FadeIn,
5
+ FadeInDown,
6
+ FadeOut,
7
+ FadeOutDown,
8
+ } from 'react-native-reanimated';
9
+ import SkipButton from '../buttons/SkipButton';
10
+ import { useTheme } from '../../utils/ThemeContext';
11
+ import type { Theme } from '../../utils/theme';
12
+ import type { OnboardingStep } from '../types';
13
+
14
+ interface OnboardingStepContainerProps {
15
+ currentStep: OnboardingStep | undefined;
16
+ showCloseButton?: boolean;
17
+ animationDuration: number;
18
+ onSkip?: () => void;
19
+ ref: React.RefObject<any>;
20
+ renderStepContent: () => React.ReactNode;
21
+ skipButton?: ({ onPress }: { onPress: () => void }) => ReactNode;
22
+ }
23
+
24
+ function OnboardingStepContainer({
25
+ currentStep,
26
+ showCloseButton,
27
+ animationDuration,
28
+ onSkip,
29
+ ref,
30
+ renderStepContent,
31
+ skipButton,
32
+ }: OnboardingStepContainerProps) {
33
+ const { theme } = useTheme();
34
+ const styles = useMemo(() => createStyles(theme), [theme]);
35
+
36
+ if (!currentStep) {
37
+ return null;
38
+ }
39
+
40
+ return (
41
+ <>
42
+ {showCloseButton && onSkip && (
43
+ <Reanimated.View
44
+ entering={FadeIn.duration(animationDuration)}
45
+ exiting={FadeOut.duration(animationDuration)}
46
+ style={styles.close}
47
+ >
48
+ {skipButton ? (
49
+ skipButton({ onPress: onSkip })
50
+ ) : (
51
+ <SkipButton onPress={onSkip} />
52
+ )}
53
+ </Reanimated.View>
54
+ )}
55
+
56
+ <Reanimated.View
57
+ ref={ref}
58
+ entering={FadeInDown.duration(animationDuration)}
59
+ exiting={FadeOutDown.duration(animationDuration)}
60
+ style={styles.bottomPanel}
61
+ >
62
+ {renderStepContent()}
63
+ </Reanimated.View>
64
+ </>
65
+ );
66
+ }
67
+
68
+ export default OnboardingStepContainer;
69
+
70
+ const createStyles = (theme: Theme) =>
71
+ StyleSheet.create({
72
+ bottomPanel: {
73
+ paddingHorizontal: 16,
74
+ paddingBottom: 16 + theme.insets.bottom,
75
+ position: 'absolute',
76
+ bottom: 0,
77
+ left: 0,
78
+ right: 0,
79
+ },
80
+ close: {
81
+ position: 'absolute',
82
+ top: theme.insets.top + 16,
83
+ right: 16,
84
+ },
85
+ });
@@ -0,0 +1,118 @@
1
+ import { useMemo } from 'react';
2
+ import { StyleSheet, Text, View } from 'react-native';
3
+ import { useTheme } from '../../utils/ThemeContext';
4
+ import { type Theme } from '../../utils/theme';
5
+ import { fontSizes, lineHeights } from '../../utils/fontStyles';
6
+ import PrimaryButton from '../buttons/PrimaryButton';
7
+ import SecondaryButton from '../buttons/SecondaryButton';
8
+ import ArrowLeftIcon from '../icons/ArrowLeftIcon';
9
+ import type { OnboardingStepPanelProps } from '../types';
10
+
11
+ function OnboardingStepPanel({
12
+ label,
13
+ title,
14
+ description,
15
+ buttonLabel,
16
+ onBackPress,
17
+ onNextPress,
18
+ buttonPrimary,
19
+ showBackButton = true,
20
+ }: OnboardingStepPanelProps) {
21
+ const { theme } = useTheme();
22
+ const styles = useMemo(() => createStyles(theme), [theme]);
23
+
24
+ return (
25
+ <View style={styles.container}>
26
+ <View style={styles.textContainer}>
27
+ {label && (
28
+ <View style={styles.labelBadge}>
29
+ <Text style={styles.labelText}>{label}</Text>
30
+ </View>
31
+ )}
32
+ <Text style={styles.title}>{title}</Text>
33
+ <Text style={styles.description}>{description}</Text>
34
+ </View>
35
+ <View style={styles.buttonRow}>
36
+ {onBackPress && showBackButton && (
37
+ <View style={styles.backButton}>
38
+ <SecondaryButton
39
+ text=""
40
+ onPress={onBackPress}
41
+ icon={<ArrowLeftIcon color={theme.text.primary} />}
42
+ />
43
+ </View>
44
+ )}
45
+ <View style={styles.nextButton}>
46
+ {buttonPrimary ? (
47
+ <PrimaryButton text={buttonLabel} onPress={onNextPress} />
48
+ ) : (
49
+ <SecondaryButton
50
+ text={buttonLabel}
51
+ onPress={onNextPress}
52
+ textStyle={styles.nextButtonText}
53
+ />
54
+ )}
55
+ </View>
56
+ </View>
57
+ </View>
58
+ );
59
+ }
60
+
61
+ export default OnboardingStepPanel;
62
+
63
+ const createStyles = (theme: Theme) =>
64
+ StyleSheet.create({
65
+ container: {
66
+ backgroundColor: theme.bg.secondary,
67
+ padding: 16,
68
+ borderRadius: 18,
69
+ gap: 24,
70
+ },
71
+ textContainer: {
72
+ alignItems: 'center',
73
+ gap: 16,
74
+ },
75
+ labelBadge: {
76
+ backgroundColor: theme.bg.label,
77
+ paddingHorizontal: 8,
78
+ paddingVertical: 4,
79
+ borderRadius: 999,
80
+ },
81
+ labelText: {
82
+ fontFamily: theme.fonts.stepLabel,
83
+ fontSize: fontSizes.xs,
84
+ lineHeight: lineHeights.xs,
85
+ textAlign: 'center',
86
+ color: theme.text.primary,
87
+ },
88
+ title: {
89
+ fontFamily: theme.fonts.stepTitle,
90
+ fontSize: fontSizes.lg,
91
+ lineHeight: lineHeights.lg,
92
+ textAlign: 'center',
93
+ color: theme.text.primary,
94
+ },
95
+ description: {
96
+ fontFamily: theme.fonts.stepDescription,
97
+ fontSize: fontSizes.md,
98
+ lineHeight: lineHeights.md,
99
+ textAlign: 'center',
100
+ color: theme.text.secondary,
101
+ },
102
+
103
+ buttonRow: {
104
+ flexDirection: 'row',
105
+ gap: 8,
106
+ },
107
+ backButton: {
108
+ width: 48,
109
+ },
110
+ nextButton: {
111
+ flex: 1,
112
+ },
113
+ nextButtonText: {
114
+ fontFamily: theme.fonts.stepButton,
115
+ fontSize: fontSizes.md,
116
+ lineHeight: lineHeights.md,
117
+ },
118
+ });
@@ -0,0 +1,21 @@
1
+ import { useLayoutEffect, useRef, useState } from 'react';
2
+ import type { View } from 'react-native';
3
+ import type Reanimated from 'react-native-reanimated';
4
+
5
+ export type ViewRef = React.ComponentRef<typeof Reanimated.View> &
6
+ React.ComponentRef<typeof View>;
7
+
8
+ function useMeasureHeight() {
9
+ const ref = useRef<ViewRef>(null);
10
+ const [height, setHeight] = useState(0);
11
+
12
+ useLayoutEffect(() => {
13
+ ref.current?.measure?.((_, __, ___, viewHeight) => {
14
+ setHeight(viewHeight);
15
+ });
16
+ });
17
+
18
+ return { ref, height };
19
+ }
20
+
21
+ export default useMeasureHeight;
@@ -0,0 +1,69 @@
1
+ import { Text, StyleSheet } from 'react-native';
2
+ import { ExpoImage } from '../adapters/expo-image';
3
+ import {
4
+ ReactNativeSVG,
5
+ ReactNativeSVGPath,
6
+ } from '../adapters/react-native-svg';
7
+
8
+ interface ArrowLeftIconProps {
9
+ size?: number;
10
+ color?: string;
11
+ }
12
+
13
+ const SVG_ARROW_LEFT_STRING =
14
+ 'M10.7071 5.29289C11.0976 5.68342 11.0976 6.31658 10.7071 6.70711L6.41421 11L20 11C20.5523 11 21 11.4477 21 12C21 12.5523 20.5523 13 20 13L6.41421 13L10.7071 17.2929C11.0976 17.6834 11.0976 18.3166 10.7071 18.7071C10.3166 19.0976 9.68342 19.0976 9.29289 18.7071L3.29289 12.7071C3.10536 12.5196 3 12.2652 3 12C3 11.7348 3.10536 11.4804 3.29289 11.2929L9.29289 5.29289C9.68342 4.90237 10.3166 4.90237 10.7071 5.29289Z';
15
+
16
+ export default function ArrowLeftIcon({
17
+ size = 24,
18
+ color = '#000',
19
+ }: ArrowLeftIconProps) {
20
+ // Use expo-image with dynamic SVG string (includes color)
21
+ if (ExpoImage) {
22
+ const svgString = `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
23
+ <path fill-rule="evenodd" clip-rule="evenodd" d="${SVG_ARROW_LEFT_STRING}" fill="${color}"/>
24
+ </svg>`;
25
+
26
+ return (
27
+ <ExpoImage
28
+ source={{ uri: `data:image/svg+xml;base64,${btoa(svgString)}` }}
29
+ style={{ width: size, height: size }}
30
+ contentFit="contain"
31
+ />
32
+ );
33
+ }
34
+
35
+ // Use SVG if available
36
+ if (ReactNativeSVG && ReactNativeSVGPath) {
37
+ return (
38
+ <ReactNativeSVG
39
+ width={size}
40
+ height={size}
41
+ viewBox="0 0 24 24"
42
+ fill="none"
43
+ >
44
+ <ReactNativeSVGPath d={SVG_ARROW_LEFT_STRING} fill={color} />
45
+ </ReactNativeSVG>
46
+ );
47
+ }
48
+
49
+ // Fallback to text arrow
50
+ return (
51
+ <Text
52
+ style={[
53
+ styles.arrow,
54
+ {
55
+ fontSize: size,
56
+ color,
57
+ },
58
+ ]}
59
+ >
60
+ Go Back
61
+ </Text>
62
+ );
63
+ }
64
+
65
+ const styles = StyleSheet.create({
66
+ arrow: {
67
+ textAlign: 'center',
68
+ },
69
+ });
@@ -0,0 +1,55 @@
1
+ import { View, StyleSheet } from 'react-native';
2
+
3
+ interface CloseIconProps {
4
+ size?: number;
5
+ color?: string;
6
+ }
7
+
8
+ export default function CloseIcon({
9
+ size = 24,
10
+ color = '#000',
11
+ }: CloseIconProps) {
12
+ const lineWidth = Math.max(2, size * 0.1);
13
+ const lineLength = size * 0.7;
14
+
15
+ return (
16
+ <View style={[styles.container, { width: size, height: size }]}>
17
+ {/* First diagonal line */}
18
+ <View
19
+ style={[
20
+ styles.line,
21
+ {
22
+ width: lineLength,
23
+ height: lineWidth,
24
+ backgroundColor: color,
25
+ transform: [{ rotate: '45deg' }],
26
+ },
27
+ ]}
28
+ />
29
+ {/* Second diagonal line */}
30
+ <View
31
+ style={[
32
+ styles.line,
33
+ // eslint-disable-next-line react-native/no-inline-styles
34
+ {
35
+ width: lineLength,
36
+ height: lineWidth,
37
+ backgroundColor: color,
38
+ transform: [{ rotate: '-45deg' }],
39
+ position: 'absolute',
40
+ },
41
+ ]}
42
+ />
43
+ </View>
44
+ );
45
+ }
46
+
47
+ const styles = StyleSheet.create({
48
+ container: {
49
+ justifyContent: 'center',
50
+ alignItems: 'center',
51
+ },
52
+ line: {
53
+ position: 'absolute',
54
+ },
55
+ });