@highbeek/create-rnstarterkit 1.0.1-beta.0 → 1.0.1-beta.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 (30) hide show
  1. package/dist/src/generators/appGenerator.js +113 -51
  2. package/dist/templates/cli-base/App.tsx +161 -23
  3. package/dist/templates/cli-base/assets/images/icon.png +0 -0
  4. package/dist/templates/cli-base/assets/images/partial-react-logo.png +0 -0
  5. package/dist/templates/cli-base/assets/images/react-logo.png +0 -0
  6. package/dist/templates/expo-base/app/index.tsx +164 -5
  7. package/dist/templates/optional/auth-context/components/layout/ScreenLayout.tsx +46 -0
  8. package/dist/templates/optional/auth-context/navigation/ProtectedStack.tsx +4 -3
  9. package/dist/templates/optional/auth-context/screens/HomeScreen.tsx +3 -3
  10. package/dist/templates/optional/auth-context/screens/LoginScreen.tsx +3 -3
  11. package/dist/templates/optional/auth-context/screens/ProfileScreen.tsx +3 -3
  12. package/dist/templates/optional/auth-context/screens/RegisterScreen.tsx +3 -3
  13. package/dist/templates/optional/auth-context/screens/SettingsScreen.tsx +3 -3
  14. package/dist/templates/optional/auth-redux/components/layout/ScreenLayout.tsx +46 -0
  15. package/dist/templates/optional/auth-redux/screens/HomeScreen.tsx +3 -3
  16. package/dist/templates/optional/auth-redux/screens/LoginScreen.tsx +3 -3
  17. package/dist/templates/optional/auth-redux/screens/ProfileScreen.tsx +7 -11
  18. package/dist/templates/optional/auth-redux/screens/RegisterScreen.tsx +3 -3
  19. package/dist/templates/optional/auth-redux/screens/SettingsScreen.tsx +6 -10
  20. package/dist/templates/optional/auth-zustand/components/layout/ScreenLayout.tsx +46 -0
  21. package/dist/templates/optional/auth-zustand/navigation/ProtectedStack.tsx +4 -3
  22. package/dist/templates/optional/auth-zustand/screens/HomeScreen.tsx +3 -3
  23. package/dist/templates/optional/auth-zustand/screens/LoginScreen.tsx +3 -3
  24. package/dist/templates/optional/auth-zustand/screens/ProfileScreen.tsx +3 -3
  25. package/dist/templates/optional/auth-zustand/screens/RegisterScreen.tsx +3 -3
  26. package/dist/templates/optional/auth-zustand/screens/SettingsScreen.tsx +3 -3
  27. package/package.json +1 -1
  28. package/dist/templates/expo-base/components/ui/collapsible.tsx +0 -45
  29. package/dist/templates/expo-base/components/ui/icon-symbol.ios.tsx +0 -32
  30. package/dist/templates/expo-base/components/ui/icon-symbol.tsx +0 -41
@@ -18,6 +18,9 @@ async function generateApp(options) {
18
18
  filter: (src) => shouldCopyPath(src, templatePath),
19
19
  });
20
20
  await replaceProjectName(targetPath, projectName);
21
+ if (platform === "React Native CLI") {
22
+ await configureCliNativeProjectNames(targetPath, projectName);
23
+ }
21
24
  await createStandardStructure(targetPath, platform);
22
25
  if (auth) {
23
26
  const authFolder = state === "Redux Toolkit"
@@ -102,6 +105,59 @@ async function replaceProjectName(dir, projectName) {
102
105
  }
103
106
  }
104
107
  }
108
+ function toNativeAppName(projectName) {
109
+ const cleaned = projectName
110
+ .replace(/[^a-zA-Z0-9]+/g, " ")
111
+ .trim()
112
+ .split(/\s+/)
113
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
114
+ .join("");
115
+ const fallback = cleaned || "App";
116
+ return /^\d/.test(fallback) ? `App${fallback}` : fallback;
117
+ }
118
+ function toAndroidPackageSegment(projectName) {
119
+ const cleaned = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "");
120
+ const fallback = cleaned || "app";
121
+ return /^\d/.test(fallback) ? `app${fallback}` : fallback;
122
+ }
123
+ async function replaceInTextFiles(dir, replacements) {
124
+ const files = await fs_extra_1.default.readdir(dir);
125
+ for (const file of files) {
126
+ const filePath = path_1.default.join(dir, file);
127
+ const stat = await fs_extra_1.default.stat(filePath);
128
+ if (stat.isDirectory()) {
129
+ await replaceInTextFiles(filePath, replacements);
130
+ continue;
131
+ }
132
+ if (!isTextFile(filePath))
133
+ continue;
134
+ let content = await fs_extra_1.default.readFile(filePath, "utf8");
135
+ for (const [pattern, replacement] of replacements) {
136
+ content = content.replace(pattern, replacement);
137
+ }
138
+ await fs_extra_1.default.writeFile(filePath, content, "utf8");
139
+ }
140
+ }
141
+ async function renameIfExists(fromPath, toPath) {
142
+ if (!(await fs_extra_1.default.pathExists(fromPath)))
143
+ return;
144
+ await fs_extra_1.default.move(fromPath, toPath, { overwrite: true });
145
+ }
146
+ async function configureCliNativeProjectNames(targetPath, projectName) {
147
+ const nativeAppName = toNativeAppName(projectName);
148
+ const androidPackageSegment = toAndroidPackageSegment(projectName);
149
+ await replaceInTextFiles(targetPath, [
150
+ [/BaseApp/g, nativeAppName],
151
+ [/com\.baseapp/g, `com.${androidPackageSegment}`],
152
+ ]);
153
+ const iosPath = path_1.default.join(targetPath, "ios");
154
+ const androidJavaRoot = path_1.default.join(targetPath, "android", "app", "src", "main", "java");
155
+ await renameIfExists(path_1.default.join(iosPath, "BaseApp"), path_1.default.join(iosPath, nativeAppName));
156
+ await renameIfExists(path_1.default.join(iosPath, "BaseApp.xcodeproj"), path_1.default.join(iosPath, `${nativeAppName}.xcodeproj`));
157
+ await renameIfExists(path_1.default.join(iosPath, "BaseApp.xcworkspace"), path_1.default.join(iosPath, `${nativeAppName}.xcworkspace`));
158
+ await renameIfExists(path_1.default.join(iosPath, `${nativeAppName}.xcodeproj`, "xcshareddata", "xcschemes", "BaseApp.xcscheme"), path_1.default.join(iosPath, `${nativeAppName}.xcodeproj`, "xcshareddata", "xcschemes", `${nativeAppName}.xcscheme`));
159
+ await renameIfExists(path_1.default.join(androidJavaRoot, "com", "baseapp"), path_1.default.join(androidJavaRoot, "com", androidPackageSegment));
160
+ }
105
161
  async function installDependencies(targetPath) {
106
162
  console.log("📦 Installing dependencies...");
107
163
  try {
@@ -496,8 +552,9 @@ export default function RootLayout() {
496
552
  `,
497
553
  indexRoute: `import React from "react";
498
554
  import { useEffect } from "react";
499
- import { ActivityIndicator, View } from "react-native";
555
+ import { ActivityIndicator } from "react-native";
500
556
  import { Redirect } from "expo-router";
557
+ import ScreenLayout from "../components/layout/ScreenLayout";
501
558
  import { useAuthStore } from "../store/authStore";
502
559
 
503
560
  export default function Index() {
@@ -511,9 +568,9 @@ export default function Index() {
511
568
 
512
569
  if (!isHydrated) {
513
570
  return (
514
- <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
571
+ <ScreenLayout centered padded={false}>
515
572
  <ActivityIndicator />
516
- </View>
573
+ </ScreenLayout>
517
574
  );
518
575
  }
519
576
 
@@ -521,8 +578,9 @@ export default function Index() {
521
578
  }
522
579
  `,
523
580
  authLayout: `import React, { useEffect } from "react";
524
- import { ActivityIndicator, View } from "react-native";
581
+ import { ActivityIndicator } from "react-native";
525
582
  import { Redirect, Stack } from "expo-router";
583
+ import ScreenLayout from "../../components/layout/ScreenLayout";
526
584
  import { useAuthStore } from "../../store/authStore";
527
585
 
528
586
  export default function AuthLayout() {
@@ -536,9 +594,9 @@ export default function AuthLayout() {
536
594
 
537
595
  if (!isHydrated) {
538
596
  return (
539
- <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
597
+ <ScreenLayout centered padded={false}>
540
598
  <ActivityIndicator />
541
- </View>
599
+ </ScreenLayout>
542
600
  );
543
601
  }
544
602
 
@@ -553,8 +611,9 @@ export default function AuthLayout() {
553
611
  }
554
612
  `,
555
613
  tabsLayout: `import React, { useEffect } from "react";
556
- import { ActivityIndicator, View } from "react-native";
614
+ import { ActivityIndicator } from "react-native";
557
615
  import { Redirect, Tabs } from "expo-router";
616
+ import ScreenLayout from "../../components/layout/ScreenLayout";
558
617
  import { useAuthStore } from "../../store/authStore";
559
618
 
560
619
  export default function TabsLayout() {
@@ -568,9 +627,9 @@ export default function TabsLayout() {
568
627
 
569
628
  if (!isHydrated) {
570
629
  return (
571
- <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
630
+ <ScreenLayout centered padded={false}>
572
631
  <ActivityIndicator />
573
- </View>
632
+ </ScreenLayout>
574
633
  );
575
634
  }
576
635
 
@@ -604,8 +663,9 @@ export default function RootLayout() {
604
663
  }
605
664
  `,
606
665
  indexRoute: `import React, { useContext } from "react";
607
- import { ActivityIndicator, View } from "react-native";
666
+ import { ActivityIndicator } from "react-native";
608
667
  import { Redirect } from "expo-router";
668
+ import ScreenLayout from "../components/layout/ScreenLayout";
609
669
  import { AuthContext } from "../context/AuthContext";
610
670
 
611
671
  export default function Index() {
@@ -613,9 +673,9 @@ export default function Index() {
613
673
 
614
674
  if (!isHydrated) {
615
675
  return (
616
- <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
676
+ <ScreenLayout centered padded={false}>
617
677
  <ActivityIndicator />
618
- </View>
678
+ </ScreenLayout>
619
679
  );
620
680
  }
621
681
 
@@ -623,8 +683,9 @@ export default function Index() {
623
683
  }
624
684
  `,
625
685
  authLayout: `import React, { useContext } from "react";
626
- import { ActivityIndicator, View } from "react-native";
686
+ import { ActivityIndicator } from "react-native";
627
687
  import { Redirect, Stack } from "expo-router";
688
+ import ScreenLayout from "../../components/layout/ScreenLayout";
628
689
  import { AuthContext } from "../../context/AuthContext";
629
690
 
630
691
  export default function AuthLayout() {
@@ -632,9 +693,9 @@ export default function AuthLayout() {
632
693
 
633
694
  if (!isHydrated) {
634
695
  return (
635
- <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
696
+ <ScreenLayout centered padded={false}>
636
697
  <ActivityIndicator />
637
- </View>
698
+ </ScreenLayout>
638
699
  );
639
700
  }
640
701
 
@@ -649,8 +710,9 @@ export default function AuthLayout() {
649
710
  }
650
711
  `,
651
712
  tabsLayout: `import React, { useContext } from "react";
652
- import { ActivityIndicator, View } from "react-native";
713
+ import { ActivityIndicator } from "react-native";
653
714
  import { Redirect, Tabs } from "expo-router";
715
+ import ScreenLayout from "../../components/layout/ScreenLayout";
654
716
  import { AuthContext } from "../../context/AuthContext";
655
717
 
656
718
  export default function TabsLayout() {
@@ -658,9 +720,9 @@ export default function TabsLayout() {
658
720
 
659
721
  if (!isHydrated) {
660
722
  return (
661
- <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
723
+ <ScreenLayout centered padded={false}>
662
724
  <ActivityIndicator />
663
- </View>
725
+ </ScreenLayout>
664
726
  );
665
727
  }
666
728
 
@@ -680,7 +742,7 @@ export default function TabsLayout() {
680
742
  function getExpoAuthRouteFiles(state) {
681
743
  const registerRedux = `import React, { useState } from "react";
682
744
  import { Button, Text, TextInput } from "react-native";
683
- import { SafeAreaView } from "react-native-safe-area-context";
745
+ import ScreenLayout from "../../components/layout/ScreenLayout";
684
746
  import { useDispatch } from "react-redux";
685
747
  import { registerApi } from "../../api";
686
748
  import { login } from "../../store/authSlice";
@@ -702,7 +764,7 @@ export default function RegisterScreen() {
702
764
  };
703
765
 
704
766
  return (
705
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
767
+ <ScreenLayout>
706
768
  <TextInput
707
769
  placeholder="Email"
708
770
  value={email}
@@ -739,13 +801,13 @@ export default function RegisterScreen() {
739
801
  />
740
802
  {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
741
803
  <Button title="Register" onPress={() => void handleRegister()} />
742
- </SafeAreaView>
804
+ </ScreenLayout>
743
805
  );
744
806
  }
745
807
  `;
746
808
  const registerZustand = `import React, { useState } from "react";
747
809
  import { Button, Text, TextInput } from "react-native";
748
- import { SafeAreaView } from "react-native-safe-area-context";
810
+ import ScreenLayout from "../../components/layout/ScreenLayout";
749
811
  import { registerApi } from "../../api";
750
812
  import { useAuthStore } from "../../store/authStore";
751
813
 
@@ -766,7 +828,7 @@ export default function RegisterScreen() {
766
828
  };
767
829
 
768
830
  return (
769
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
831
+ <ScreenLayout>
770
832
  <TextInput
771
833
  placeholder="Email"
772
834
  value={email}
@@ -803,13 +865,13 @@ export default function RegisterScreen() {
803
865
  />
804
866
  {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
805
867
  <Button title="Register" onPress={() => void handleRegister()} />
806
- </SafeAreaView>
868
+ </ScreenLayout>
807
869
  );
808
870
  }
809
871
  `;
810
872
  const registerContext = `import React, { useContext, useState } from "react";
811
873
  import { Button, Text, TextInput } from "react-native";
812
- import { SafeAreaView } from "react-native-safe-area-context";
874
+ import ScreenLayout from "../../components/layout/ScreenLayout";
813
875
  import { registerApi } from "../../api";
814
876
  import { AuthContext } from "../../context/AuthContext";
815
877
 
@@ -830,7 +892,7 @@ export default function RegisterScreen() {
830
892
  };
831
893
 
832
894
  return (
833
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
895
+ <ScreenLayout>
834
896
  <TextInput
835
897
  placeholder="Email"
836
898
  value={email}
@@ -867,31 +929,31 @@ export default function RegisterScreen() {
867
929
  />
868
930
  {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
869
931
  <Button title="Register" onPress={() => void handleRegister()} />
870
- </SafeAreaView>
932
+ </ScreenLayout>
871
933
  );
872
934
  }
873
935
  `;
874
936
  const profile = `import React from "react";
875
937
  import { Text } from "react-native";
876
- import { SafeAreaView } from "react-native-safe-area-context";
938
+ import ScreenLayout from "../../components/layout/ScreenLayout";
877
939
 
878
940
  export default function ProfileScreen() {
879
941
  return (
880
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
942
+ <ScreenLayout>
881
943
  <Text>ProfileScreen</Text>
882
- </SafeAreaView>
944
+ </ScreenLayout>
883
945
  );
884
946
  }
885
947
  `;
886
948
  const settings = `import React from "react";
887
949
  import { Text } from "react-native";
888
- import { SafeAreaView } from "react-native-safe-area-context";
950
+ import ScreenLayout from "../../components/layout/ScreenLayout";
889
951
 
890
952
  export default function SettingsScreen() {
891
953
  return (
892
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
954
+ <ScreenLayout>
893
955
  <Text>SettingsScreen</Text>
894
- </SafeAreaView>
956
+ </ScreenLayout>
895
957
  );
896
958
  }
897
959
  `;
@@ -899,7 +961,7 @@ export default function SettingsScreen() {
899
961
  return {
900
962
  login: `import React, { useState } from "react";
901
963
  import { Button, Text, TextInput } from "react-native";
902
- import { SafeAreaView } from "react-native-safe-area-context";
964
+ import ScreenLayout from "../../components/layout/ScreenLayout";
903
965
  import { useDispatch } from "react-redux";
904
966
  import { loginApi } from "../../api";
905
967
  import { login } from "../../store/authSlice";
@@ -921,7 +983,7 @@ export default function LoginScreen() {
921
983
  };
922
984
 
923
985
  return (
924
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
986
+ <ScreenLayout>
925
987
  <TextInput
926
988
  placeholder="Email"
927
989
  value={email}
@@ -958,14 +1020,14 @@ export default function LoginScreen() {
958
1020
  />
959
1021
  {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
960
1022
  <Button title="Login" onPress={() => void handleLogin()} />
961
- </SafeAreaView>
1023
+ </ScreenLayout>
962
1024
  );
963
1025
  }
964
1026
  `,
965
1027
  register: registerRedux,
966
1028
  home: `import React from "react";
967
1029
  import { Button, Text } from "react-native";
968
- import { SafeAreaView } from "react-native-safe-area-context";
1030
+ import ScreenLayout from "../../components/layout/ScreenLayout";
969
1031
  import { useDispatch } from "react-redux";
970
1032
  import { logout } from "../../store/authSlice";
971
1033
 
@@ -973,10 +1035,10 @@ export default function HomeScreen() {
973
1035
  const dispatch = useDispatch();
974
1036
 
975
1037
  return (
976
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
1038
+ <ScreenLayout>
977
1039
  <Text>Welcome Home!</Text>
978
1040
  <Button title="Logout" onPress={() => dispatch(logout())} />
979
- </SafeAreaView>
1041
+ </ScreenLayout>
980
1042
  );
981
1043
  }
982
1044
  `,
@@ -988,7 +1050,7 @@ export default function HomeScreen() {
988
1050
  return {
989
1051
  login: `import React, { useState } from "react";
990
1052
  import { Button, Text, TextInput } from "react-native";
991
- import { SafeAreaView } from "react-native-safe-area-context";
1053
+ import ScreenLayout from "../../components/layout/ScreenLayout";
992
1054
  import { loginApi } from "../../api";
993
1055
  import { useAuthStore } from "../../store/authStore";
994
1056
 
@@ -1009,7 +1071,7 @@ export default function LoginScreen() {
1009
1071
  };
1010
1072
 
1011
1073
  return (
1012
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
1074
+ <ScreenLayout>
1013
1075
  <TextInput
1014
1076
  placeholder="Email"
1015
1077
  value={email}
@@ -1046,24 +1108,24 @@ export default function LoginScreen() {
1046
1108
  />
1047
1109
  {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
1048
1110
  <Button title="Login" onPress={() => void handleLogin()} />
1049
- </SafeAreaView>
1111
+ </ScreenLayout>
1050
1112
  );
1051
1113
  }
1052
1114
  `,
1053
1115
  register: registerZustand,
1054
1116
  home: `import React from "react";
1055
1117
  import { Button, Text } from "react-native";
1056
- import { SafeAreaView } from "react-native-safe-area-context";
1118
+ import ScreenLayout from "../../components/layout/ScreenLayout";
1057
1119
  import { useAuthStore } from "../../store/authStore";
1058
1120
 
1059
1121
  export default function HomeScreen() {
1060
1122
  const logout = useAuthStore((state) => state.logout);
1061
1123
 
1062
1124
  return (
1063
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
1125
+ <ScreenLayout>
1064
1126
  <Text>Welcome Home!</Text>
1065
1127
  <Button title="Logout" onPress={() => void logout()} />
1066
- </SafeAreaView>
1128
+ </ScreenLayout>
1067
1129
  );
1068
1130
  }
1069
1131
  `,
@@ -1074,7 +1136,7 @@ export default function HomeScreen() {
1074
1136
  return {
1075
1137
  login: `import React, { useContext, useState } from "react";
1076
1138
  import { Button, Text, TextInput } from "react-native";
1077
- import { SafeAreaView } from "react-native-safe-area-context";
1139
+ import ScreenLayout from "../../components/layout/ScreenLayout";
1078
1140
  import { loginApi } from "../../api";
1079
1141
  import { AuthContext } from "../../context/AuthContext";
1080
1142
 
@@ -1095,7 +1157,7 @@ export default function LoginScreen() {
1095
1157
  };
1096
1158
 
1097
1159
  return (
1098
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
1160
+ <ScreenLayout>
1099
1161
  <TextInput
1100
1162
  placeholder="Email"
1101
1163
  value={email}
@@ -1132,24 +1194,24 @@ export default function LoginScreen() {
1132
1194
  />
1133
1195
  {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
1134
1196
  <Button title="Login" onPress={() => void handleLogin()} />
1135
- </SafeAreaView>
1197
+ </ScreenLayout>
1136
1198
  );
1137
1199
  }
1138
1200
  `,
1139
1201
  register: registerContext,
1140
1202
  home: `import React, { useContext } from "react";
1141
1203
  import { Button, Text } from "react-native";
1142
- import { SafeAreaView } from "react-native-safe-area-context";
1204
+ import ScreenLayout from "../../components/layout/ScreenLayout";
1143
1205
  import { AuthContext } from "../../context/AuthContext";
1144
1206
 
1145
1207
  export default function HomeScreen() {
1146
1208
  const { logout } = useContext(AuthContext);
1147
1209
 
1148
1210
  return (
1149
- <SafeAreaView style={{ flex: 1, padding: 20 }}>
1211
+ <ScreenLayout>
1150
1212
  <Text>Welcome Home!</Text>
1151
1213
  <Button title="Logout" onPress={() => void logout()} />
1152
- </SafeAreaView>
1214
+ </ScreenLayout>
1153
1215
  );
1154
1216
  }
1155
1217
  `,
@@ -1,44 +1,182 @@
1
- /**
2
- * Sample React Native App
3
- * https://github.com/facebook/react-native
4
- *
5
- * @format
6
- */
7
-
8
- import { NewAppScreen } from '@react-native/new-app-screen';
9
- import { StatusBar, StyleSheet, useColorScheme, View } from 'react-native';
1
+ import React, { useRef, useState } from "react";
2
+ import {
3
+ FlatList,
4
+ Image,
5
+ ImageSourcePropType,
6
+ Pressable,
7
+ StatusBar,
8
+ StyleSheet,
9
+ Text,
10
+ useWindowDimensions,
11
+ View,
12
+ ViewToken,
13
+ } from "react-native";
10
14
  import {
11
15
  SafeAreaProvider,
12
- useSafeAreaInsets,
13
- } from 'react-native-safe-area-context';
16
+ SafeAreaView,
17
+ } from "react-native-safe-area-context";
14
18
 
15
- function App() {
16
- const isDarkMode = useColorScheme() === 'dark';
19
+ type Slide = {
20
+ id: string;
21
+ title: string;
22
+ description: string;
23
+ image: ImageSourcePropType;
24
+ };
25
+
26
+ const slides: Slide[] = [
27
+ {
28
+ id: "1",
29
+ title: "Welcome to RN Starter Kit",
30
+ description: "Start with a clean React Native CLI project structure.",
31
+ image: require("./assets/images/react-logo.png"),
32
+ },
33
+ {
34
+ id: "2",
35
+ title: "Ready for Scale",
36
+ description: "Build faster with sensible architecture you can extend.",
37
+ image: require("./assets/images/partial-react-logo.png"),
38
+ },
39
+ {
40
+ id: "3",
41
+ title: "Make It Yours",
42
+ description: "Use this onboarding as a placeholder for your product design.",
43
+ image: require("./assets/images/icon.png"),
44
+ },
45
+ ];
17
46
 
47
+ function App() {
18
48
  return (
19
49
  <SafeAreaProvider>
20
- <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
21
- <AppContent />
50
+ <StatusBar barStyle="dark-content" />
51
+ <WelcomeScreen />
22
52
  </SafeAreaProvider>
23
53
  );
24
54
  }
25
55
 
26
- function AppContent() {
27
- const safeAreaInsets = useSafeAreaInsets();
56
+ function WelcomeScreen() {
57
+ const { width } = useWindowDimensions();
58
+ const [activeIndex, setActiveIndex] = useState(0);
59
+ const listRef = useRef<FlatList<Slide>>(null);
60
+
61
+ const onViewableItemsChanged = useRef(
62
+ ({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
63
+ if (viewableItems[0]?.index != null) {
64
+ setActiveIndex(viewableItems[0].index);
65
+ }
66
+ },
67
+ ).current;
68
+
69
+ const viewabilityConfig = useRef({ viewAreaCoveragePercentThreshold: 60 }).current;
70
+
71
+ const goNext = () => {
72
+ const nextIndex = activeIndex + 1;
73
+ if (nextIndex < slides.length) {
74
+ listRef.current?.scrollToIndex({ index: nextIndex, animated: true });
75
+ }
76
+ };
77
+
78
+ const isLastSlide = activeIndex === slides.length - 1;
28
79
 
29
80
  return (
30
- <View style={styles.container}>
31
- <NewAppScreen
32
- templateFileName="App.tsx"
33
- safeAreaInsets={safeAreaInsets}
81
+ <SafeAreaView style={styles.safeArea}>
82
+ <FlatList
83
+ ref={listRef}
84
+ data={slides}
85
+ horizontal
86
+ pagingEnabled
87
+ bounces={false}
88
+ keyExtractor={(item) => item.id}
89
+ showsHorizontalScrollIndicator={false}
90
+ renderItem={({ item }) => (
91
+ <View style={[styles.slide, { width }]}>
92
+ <Image source={item.image} style={styles.image} resizeMode="contain" />
93
+ <Text style={styles.title}>{item.title}</Text>
94
+ <Text style={styles.description}>{item.description}</Text>
95
+ </View>
96
+ )}
97
+ onViewableItemsChanged={onViewableItemsChanged}
98
+ viewabilityConfig={viewabilityConfig}
34
99
  />
35
- </View>
100
+
101
+ <View style={styles.footer}>
102
+ <View style={styles.dotsRow}>
103
+ {slides.map((slide, index) => (
104
+ <View
105
+ key={slide.id}
106
+ style={[styles.dot, index === activeIndex && styles.dotActive]}
107
+ />
108
+ ))}
109
+ </View>
110
+
111
+ <Pressable style={styles.button} onPress={goNext}>
112
+ <Text style={styles.buttonLabel}>{isLastSlide ? "Get Started" : "Next"}</Text>
113
+ </Pressable>
114
+ </View>
115
+ </SafeAreaView>
36
116
  );
37
117
  }
38
118
 
39
119
  const styles = StyleSheet.create({
40
- container: {
120
+ safeArea: {
121
+ flex: 1,
122
+ backgroundColor: "#F7F7F9",
123
+ },
124
+ slide: {
41
125
  flex: 1,
126
+ paddingHorizontal: 24,
127
+ justifyContent: "center",
128
+ alignItems: "center",
129
+ },
130
+ image: {
131
+ width: 220,
132
+ height: 220,
133
+ marginBottom: 28,
134
+ },
135
+ title: {
136
+ fontSize: 28,
137
+ fontWeight: "700",
138
+ color: "#111827",
139
+ textAlign: "center",
140
+ marginBottom: 12,
141
+ },
142
+ description: {
143
+ fontSize: 16,
144
+ lineHeight: 24,
145
+ color: "#4B5563",
146
+ textAlign: "center",
147
+ maxWidth: 320,
148
+ },
149
+ footer: {
150
+ paddingHorizontal: 24,
151
+ paddingBottom: 28,
152
+ gap: 20,
153
+ },
154
+ dotsRow: {
155
+ flexDirection: "row",
156
+ justifyContent: "center",
157
+ gap: 8,
158
+ },
159
+ dot: {
160
+ width: 8,
161
+ height: 8,
162
+ borderRadius: 999,
163
+ backgroundColor: "#D1D5DB",
164
+ },
165
+ dotActive: {
166
+ width: 24,
167
+ backgroundColor: "#111827",
168
+ },
169
+ button: {
170
+ height: 52,
171
+ borderRadius: 12,
172
+ backgroundColor: "#111827",
173
+ alignItems: "center",
174
+ justifyContent: "center",
175
+ },
176
+ buttonLabel: {
177
+ color: "#FFFFFF",
178
+ fontSize: 16,
179
+ fontWeight: "600",
42
180
  },
43
181
  });
44
182