@funtools/create-react-native-app 1.1.3 → 1.1.5

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 (46) hide show
  1. package/index.js +7 -10
  2. package/package.json +2 -8
  3. package/templates/base/dependencies.json +39 -0
  4. package/templates/base/files/babel.config.js +11 -0
  5. package/templates/base/files/global.css +3 -0
  6. package/templates/base/files/index.js +6 -0
  7. package/templates/base/files/metro.config.js +18 -0
  8. package/templates/base/files/nativewind-env.d.ts +1 -0
  9. package/templates/base/files/react-native.config.js +4 -0
  10. package/templates/base/files/src/App.tsx +16 -0
  11. package/templates/base/files/src/Navigation/NavigationContainer/App.tsx +16 -0
  12. package/templates/base/files/src/Navigation/StackNavigators/Root/index.tsx +23 -0
  13. package/templates/base/files/src/Navigation/StackNavigators/Root/types.ts +5 -0
  14. package/templates/base/files/src/Navigation/index.ts +2 -0
  15. package/templates/base/files/src/Screens/Home.tsx +10 -0
  16. package/templates/base/files/src/Shared/Assets/Fonts/Roboto-Bold.ttf +0 -0
  17. package/templates/base/files/src/Shared/Assets/Fonts/Roboto-ExtraBold.ttf +0 -0
  18. package/templates/base/files/src/Shared/Assets/Fonts/Roboto-ExtraLight.ttf +0 -0
  19. package/templates/base/files/src/Shared/Assets/Fonts/Roboto-Light.ttf +0 -0
  20. package/templates/base/files/src/Shared/Assets/Fonts/Roboto-Medium.ttf +0 -0
  21. package/templates/base/files/src/Shared/Assets/Fonts/Roboto-Regular.ttf +0 -0
  22. package/templates/base/files/src/Shared/Assets/Fonts/Roboto-SemiBold.ttf +0 -0
  23. package/templates/base/files/src/Shared/Assets/Fonts/Roboto-Thin.ttf +0 -0
  24. package/templates/base/files/src/Shared/Components/Core/Icon.tsx +24 -0
  25. package/templates/base/files/src/Shared/Components/Core/Modals/CenterModal.tsx +134 -0
  26. package/templates/base/files/src/Shared/Components/Core/Modals/index.tsx +1 -0
  27. package/templates/base/files/src/Shared/Components/Core/RippleContainer.tsx +91 -0
  28. package/templates/base/files/src/Shared/Components/Core/ShowWhen.tsx +11 -0
  29. package/templates/base/files/src/Shared/Components/UI/Buttons/Button.tsx +53 -0
  30. package/templates/base/files/src/Shared/Components/UI/Buttons/IconButton.tsx +32 -0
  31. package/templates/base/files/src/Shared/Components/UI/Buttons/Utils/constance.ts +39 -0
  32. package/templates/base/files/src/Shared/Components/UI/Buttons/Utils/functions.ts +55 -0
  33. package/templates/base/files/src/Shared/Components/UI/Buttons/Utils/types.ts +12 -0
  34. package/templates/base/files/src/Shared/Components/UI/Buttons/index.ts +2 -0
  35. package/templates/base/files/src/Shared/Hooks/useUpdateEffect.ts +14 -0
  36. package/templates/base/files/src/Shared/Stores/Theme/Components/ThemeText.tsx +22 -0
  37. package/templates/base/files/src/Shared/Stores/Theme/Components/ThemeView.tsx +25 -0
  38. package/templates/base/files/src/Shared/Stores/Theme/Components/index.ts +2 -0
  39. package/templates/base/files/src/Shared/Stores/Theme/constance.ts +25 -0
  40. package/templates/base/files/src/Shared/Stores/Theme/index.ts +19 -0
  41. package/templates/base/files/src/Shared/Stores/Theme/types.ts +3 -0
  42. package/templates/base/files/src/Shared/Types/native.type.ts +4 -0
  43. package/templates/base/files/src/Shared/Types/number.type.ts +8 -0
  44. package/templates/base/files/src/Shared/Types/svg.d.ts +7 -0
  45. package/templates/base/files/tailwind.config.js +22 -0
  46. package/templates/base/files/tsconfig.json +17 -0
package/index.js CHANGED
@@ -1,10 +1,9 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { fileURLToPath } from "url";
4
4
  import path from "path";
5
5
  import { execSync } from "child_process";
6
6
  import fs from "fs";
7
- import { rm, cp } from "fs/promises";
8
7
  import prompts from "prompts";
9
8
 
10
9
  const __filename = fileURLToPath(import.meta.url);
@@ -14,19 +13,17 @@ let appName = "MyApp";
14
13
 
15
14
  try {
16
15
  console.log("Welcome to @funtools");
17
- console.log('Thanks for using @funtools/create-react-native-app@1.1.3');
16
+ console.log('Thanks for using @funtools/create-react-native-app@1.1.4');
18
17
 
19
18
  await getAppName();
20
19
 
21
20
  initApp();
22
-
23
- await initTemplates();
24
-
21
+ initTemplates();
25
22
  installDependencies();
26
23
 
27
24
  console.log("✅ App setup complete");
28
25
  process.exit(0);
29
- } catch (e) {
26
+ } catch (error) {
30
27
  console.error("❌ Failed to create app");
31
28
  console.error(error);
32
29
  process.exit(1);
@@ -63,12 +60,12 @@ function initApp() {
63
60
  }
64
61
 
65
62
 
66
- async function initTemplates() {
63
+ function initTemplates() {
67
64
  console.log("Initializing templates...");
68
65
 
69
- await rm(path.join(process.cwd(), 'App.tsx'), { recursive: true, force: true });
66
+ fs.unlinkSync(path.join(process.cwd(), 'App.tsx'));
70
67
 
71
- await cp(path.join(__dirname, `templates/base/files`), process.cwd(), {
68
+ fs.cpSync(path.join(__dirname, `templates/base/files`), process.cwd(), {
72
69
  recursive: true,
73
70
  force: true,
74
71
  });
package/package.json CHANGED
@@ -1,22 +1,16 @@
1
1
  {
2
2
  "name": "@funtools/create-react-native-app",
3
3
  "description": "Create a React Native app with preconfigured setup",
4
-
5
- "version": "1.1.3",
6
-
4
+ "version": "1.1.5",
7
5
  "type": "module",
8
-
9
6
  "license": "MIT",
10
-
11
7
  "bin": {
12
8
  "create-react-native-app": "index.js"
13
9
  },
14
-
15
10
  "files": [
16
11
  "index.js",
17
- "initApp.js"
12
+ "templates"
18
13
  ],
19
-
20
14
  "dependencies": {
21
15
  "prompts": "^2.4.2"
22
16
  }
@@ -0,0 +1,39 @@
1
+ {
2
+ "dependencies": [
3
+ "@funtools/store",
4
+ "@react-navigation/native",
5
+ "@react-navigation/native-stack",
6
+ "lucide-react-native",
7
+ "nativewind",
8
+ "react",
9
+ "react-native",
10
+ "react-native-gesture-handler",
11
+ "react-native-safe-area-context",
12
+ "react-native-screens",
13
+ "react-native-svg"
14
+ ],
15
+
16
+ "devDependencies": [
17
+ "@babel/core",
18
+ "@babel/preset-env",
19
+ "@babel/runtime",
20
+ "@react-native-community/cli",
21
+ "@react-native-community/cli-platform-android",
22
+ "@react-native-community/cli-platform-ios",
23
+ "@react-native/babel-preset",
24
+ "@react-native/eslint-config",
25
+ "@react-native/metro-config",
26
+ "@react-native/typescript-config",
27
+ "@types/jest",
28
+ "@types/react",
29
+ "@types/react-test-renderer",
30
+ "babel-plugin-module-resolver",
31
+ "eslint",
32
+ "jest",
33
+ "prettier",
34
+ "react-native-svg-transformer",
35
+ "react-test-renderer",
36
+ "tailwindcss",
37
+ "typescript"
38
+ ]
39
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ presets: ['module:@react-native/babel-preset', 'nativewind/babel'],
3
+ plugins: [
4
+ [
5
+ 'module-resolver', {
6
+ alias: { '@': './src' },
7
+ extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
8
+ }
9
+ ],
10
+ ],
11
+ };
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,6 @@
1
+ import { AppRegistry } from 'react-native';
2
+ import { name as appName } from './app.json';
3
+ import App from './src/App';
4
+ import './global.css';
5
+
6
+ AppRegistry.registerComponent(appName, () => App);
@@ -0,0 +1,18 @@
1
+ const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
2
+ const { withNativeWind } = require("nativewind/metro");
3
+
4
+ const defaultConfig = getDefaultConfig(__dirname);
5
+ const { assetExts, sourceExts } = defaultConfig.resolver;
6
+
7
+ const config = mergeConfig(defaultConfig, {
8
+ /* your config */
9
+ transformer: {
10
+ babelTransformerPath: require.resolve("react-native-svg-transformer")
11
+ },
12
+ resolver: {
13
+ assetExts: assetExts.filter((ext) => ext !== "svg"),
14
+ sourceExts: [...sourceExts, "svg"]
15
+ }
16
+ });
17
+
18
+ module.exports = withNativeWind(config, { input: "./global.css" });
@@ -0,0 +1 @@
1
+ /// <reference types="nativewind/types" />
@@ -0,0 +1,4 @@
1
+ // react-native.config.js
2
+ module.exports = {
3
+ assets: ['./src/Shared/Assets/Fonts'],
4
+ };
@@ -0,0 +1,16 @@
1
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
2
+ import { GestureHandlerRootView } from 'react-native-gesture-handler';
3
+ import { AppNavigationContainer } from './Navigation';
4
+ import ThemeView from './Shared/Stores/Theme/Components/ThemeView';
5
+
6
+ export default function App() {
7
+ return (
8
+ <GestureHandlerRootView className='flex-1' >
9
+ <SafeAreaProvider>
10
+ <ThemeView className='flex-1 w-full h-full' >
11
+ <AppNavigationContainer/>
12
+ </ThemeView>
13
+ </SafeAreaProvider>
14
+ </GestureHandlerRootView>
15
+ );
16
+ }
@@ -0,0 +1,16 @@
1
+ import {
2
+ createNavigationContainerRef,
3
+ NavigationContainer,
4
+ } from '@react-navigation/native';
5
+ import RootStackNavigator from '../StackNavigators/Root';
6
+ import { RootStackParamList } from '../StackNavigators/Root/types';
7
+
8
+ export const navigationRef = createNavigationContainerRef<RootStackParamList>();
9
+
10
+ export default function AppNavigationContainer() {
11
+ return (
12
+ <NavigationContainer ref={navigationRef}>
13
+ <RootStackNavigator />
14
+ </NavigationContainer>
15
+ );
16
+ }
@@ -0,0 +1,23 @@
1
+ import { createNativeStackNavigator } from '@react-navigation/native-stack';
2
+ import { RootStackParamList } from './types';
3
+ import HomeScreen from '@/Screens/Home';
4
+
5
+ const Stack = createNativeStackNavigator<RootStackParamList>();
6
+
7
+ const screens: Array<Parameters<typeof Stack.Screen>[0]> = [
8
+ { name: 'Home', component: HomeScreen },
9
+ ];
10
+
11
+ export default function RootStackNavigator() {
12
+ return (
13
+ <Stack.Navigator
14
+ screenOptions={{
15
+ headerShown: false,
16
+ }}
17
+ >
18
+ {screens.map((screen, index) => (
19
+ <Stack.Screen key={index} {...screen} />
20
+ ))}
21
+ </Stack.Navigator>
22
+ );
23
+ }
@@ -0,0 +1,5 @@
1
+ import { NavigatorScreenParams } from "@react-navigation/native"
2
+
3
+ export type RootStackParamList = {
4
+ Home: undefined;
5
+ }
@@ -0,0 +1,2 @@
1
+ export { default as AppNavigationContainer } from './NavigationContainer/App';
2
+ export { navigationRef } from './NavigationContainer/App'
@@ -0,0 +1,10 @@
1
+ import { ThemeText, ThemeView } from "@/Shared/Stores/Theme/Components";
2
+
3
+
4
+ export default function HomeScreen() {
5
+ return (
6
+ <ThemeView>
7
+ <ThemeText>Home</ThemeText>
8
+ </ThemeView>
9
+ )
10
+ }
@@ -0,0 +1,24 @@
1
+ import * as icons from 'lucide-react-native/icons';
2
+ import { LucideProps } from "lucide-react-native";
3
+ import { useThemeStore } from "../../Stores/Theme";
4
+ import { ColorStates } from '../../Stores/Theme/types';
5
+
6
+
7
+ export type IconName = keyof typeof icons;
8
+
9
+ export type IconProps = LucideProps & {
10
+ name: IconName,
11
+
12
+ size?: number,
13
+ color?: ColorStates,
14
+ customColor?: string,
15
+ }
16
+
17
+ export default function Icon({name, size=16, color='text', customColor, ...props}: IconProps){
18
+
19
+ const colors = useThemeStore(states => customColor ?? states.colors[color])
20
+
21
+ const LucideIcon = icons[name];
22
+
23
+ return <LucideIcon {...props} color={colors} size={size} />;
24
+ }
@@ -0,0 +1,134 @@
1
+ import { Dispatch, ReactNode, SetStateAction, useEffect, useRef, useState } from "react";
2
+ import { Animated, Modal, ModalProps, PanResponder, useAnimatedValue, useWindowDimensions } from "react-native";
3
+ import RippleContainer from "../RippleContainer";
4
+ import ThemeView, { ThemeViewProps } from "../../../Stores/Theme/Components/ThemeView";
5
+ import { useThemeStore } from "../../../Stores/Theme";
6
+ import { ColorStates } from "../../../Stores/Theme/types";
7
+
8
+
9
+
10
+ export type CenterModalProps = Omit<ModalProps, 'animationType'> & {
11
+ children: ReactNode,
12
+ visible: boolean,
13
+ setVisible: Dispatch<SetStateAction<boolean>>,
14
+ preventCloseRequest?: boolean,
15
+ containerProps?: ThemeViewProps,
16
+ backdropColor?: ColorStates
17
+ }
18
+
19
+
20
+ export default function CenterModal({children, visible, setVisible, preventCloseRequest=false, onRequestClose, style, containerProps, backdropColor: backdropVariant='text', ...props}: CenterModalProps) {
21
+
22
+ const backdropColor = useThemeStore(states => states.colors[backdropVariant].replace(')', ', 0.8)'));
23
+
24
+ const {width: windowWidth, height: windowHeight} = useWindowDimensions();
25
+
26
+ const [show, setShow] = useState(visible);
27
+
28
+ const animatedValue = useAnimatedValue(0);
29
+
30
+ const translate = useRef(new Animated.ValueXY({x: 0, y: 0})).current;
31
+
32
+ const {panHandlers} = useRef(PanResponder.create({
33
+ onStartShouldSetPanResponder: () => true,
34
+ onMoveShouldSetPanResponder: (_, gestureState) => {
35
+ return Math.abs(gestureState.dy) > 5;
36
+ },
37
+
38
+ onPanResponderTerminationRequest: () => false,
39
+
40
+ onPanResponderMove: (_, {dx, dy}) => {
41
+ translate.setValue({x: dx, y: dy});
42
+ },
43
+
44
+ onPanResponderRelease: (_, {vx, vy, dx, dy}) => {
45
+ const isNearEdge = [
46
+ Math.abs(dx) > windowWidth * 0.4,
47
+ Math.abs(dy) > windowHeight * 0.4
48
+ ].some(Boolean);
49
+
50
+ const isMovingFast = [Math.abs(vx) > 2, Math.abs(vy) > 2].some(Boolean);
51
+
52
+ if((isNearEdge || isMovingFast) && !preventCloseRequest) {
53
+ return setVisible(false);
54
+ }
55
+
56
+ Animated.spring(translate, {
57
+ toValue: {x: 0, y: 0},
58
+ bounciness: 12,
59
+ useNativeDriver: true
60
+ }).start()
61
+ }
62
+
63
+ })).current;
64
+
65
+ function handleClose() {
66
+ setTimeout(() => setShow(false), 150)
67
+
68
+ Animated.spring(animatedValue, {
69
+ toValue: 0,
70
+ bounciness: 12,
71
+ useNativeDriver: true
72
+ }).start()
73
+ }
74
+
75
+ function handleOnRequestClose() {
76
+ if(preventCloseRequest) return;
77
+
78
+ setVisible(false);
79
+ }
80
+
81
+ useEffect(() => {
82
+ if(visible){
83
+ setShow(true);
84
+ translate.setValue({x: 0, y: 0});
85
+
86
+ Animated.spring(animatedValue, {
87
+ toValue: 1,
88
+ bounciness: 12,
89
+ useNativeDriver: true
90
+ }).start()
91
+ } else {
92
+ handleClose();
93
+ }
94
+ }, [visible])
95
+
96
+
97
+ return (
98
+ <Modal {...props}
99
+ visible={show}
100
+ transparent
101
+
102
+ onRequestClose={handleOnRequestClose}
103
+ >
104
+ <Animated.View className="flex-1 w-full h-full items-center justify-center"
105
+ style={{
106
+ opacity: animatedValue,
107
+ backgroundColor: backdropColor
108
+ }}
109
+ >
110
+ <RippleContainer className="flex-1 w-full" onPress={handleOnRequestClose} rippleOpacity={0.2} />
111
+
112
+ <Animated.View {...panHandlers}
113
+ className={'w-full p-2 relative'}
114
+ style={{
115
+ opacity: animatedValue,
116
+ transform: [
117
+ {translateX: translate.x}, {translateY: translate.y},
118
+ {scale: animatedValue.interpolate({inputRange: [0, 1], outputRange: [0.4, 1]})}
119
+ ]
120
+ }}
121
+ >
122
+ <ThemeView
123
+ {...containerProps}
124
+ style={[{ borderRadius: 12, padding: 4 }, style, {overflow: 'hidden', width: '100%'}]}
125
+ >
126
+ {children}
127
+ </ThemeView>
128
+ </Animated.View>
129
+
130
+ <RippleContainer className="flex-1 w-full" onPress={handleOnRequestClose} rippleOpacity={0.2} />
131
+ </Animated.View>
132
+ </Modal>
133
+ )
134
+ }
@@ -0,0 +1 @@
1
+ export {default as CenterModal} from './CenterModal';
@@ -0,0 +1,91 @@
1
+ import { ReactNode, useRef, useState } from "react";
2
+ import { useThemeStore } from "../../Stores/Theme";
3
+ import { ColorStates } from "../../Stores/Theme/types";
4
+ import { Animated, GestureResponderEvent, Pressable, PressableProps, useAnimatedValue, View, ViewStyle } from "react-native";
5
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
6
+
7
+
8
+ export type RippleContainerProps = PressableProps & {
9
+ children?: ReactNode,
10
+ color?: ColorStates
11
+ style?: ViewStyle
12
+ rippleOpacity?: number,
13
+ rippleColor?: string,
14
+ rippleScale?: number,
15
+ rippleCount?: number,
16
+ duration?: number,
17
+ }
18
+
19
+
20
+ export default function RippleContainer({children, style, onPress, color='text', rippleColor, rippleOpacity=0.4, rippleScale=1, duration=300, rippleCount=3, ...props}: RippleContainerProps) {
21
+
22
+ const {top, left} = useSafeAreaInsets();
23
+
24
+ const rgb = useThemeStore(s => s.colors[color]);
25
+ rippleColor ??= `rgb(${rgb})`;
26
+
27
+ const [position, setPosition] = useState<{top: number, left: number}>({top: 0, left: 0});
28
+
29
+ const animatedValue = useAnimatedValue(0);
30
+
31
+ const button = useRef<View>(null);
32
+
33
+ function handleOnPress(event: GestureResponderEvent) {
34
+ const {pageX, pageY} = event.nativeEvent;
35
+
36
+ button.current?.measureInWindow((x, y, w) => {
37
+ x += left;
38
+ y += top;
39
+
40
+ setPosition({top: pageY - y - w / 2, left: pageX - x - w / 2})
41
+ })
42
+
43
+ startAnimation();
44
+
45
+ onPress?.(event);
46
+ }
47
+
48
+
49
+ function startAnimation() {
50
+ Animated.timing(animatedValue, {
51
+ toValue: 1,
52
+ duration,
53
+ useNativeDriver: true
54
+ }).start(() => {
55
+ animatedValue.setValue(0);
56
+ })
57
+ }
58
+
59
+
60
+ return (
61
+ <Pressable ref={button} {...props} onPress={handleOnPress} style={[style, { overflow: 'hidden', position: 'relative' }]}>
62
+ <View className="absolute aspect-square" style={{...position, width: '100%'}} >
63
+ {
64
+ [...new Array(Math.min(rippleCount, 5))].map((_, index) => (
65
+ <Animated.View key={index}
66
+ className={'absolute w-full aspect-square rounded-full'}
67
+ style={{
68
+ backgroundColor: rippleColor,
69
+
70
+
71
+ opacity: animatedValue.interpolate({
72
+ inputRange: [0, 1],
73
+ outputRange: [rippleOpacity * ((rippleCount - index) / rippleCount), 0],
74
+ }),
75
+
76
+ transform: [{
77
+ scale: animatedValue.interpolate({
78
+ inputRange: [0, 0.1, 1],
79
+ outputRange: [0, rippleScale * 0.1, rippleScale + (index * 0.1)]
80
+ })
81
+ }]
82
+ }}
83
+ />
84
+ ))
85
+ }
86
+ </View>
87
+
88
+ {children}
89
+ </Pressable>
90
+ );
91
+ }
@@ -0,0 +1,11 @@
1
+ import { ReactNode } from "react"
2
+
3
+ type ShowWhenProps = {
4
+ when: boolean
5
+ children: ReactNode
6
+ otherwise?: ReactNode
7
+ }
8
+
9
+ export default function ShowWhen({ when, children, otherwise=null }: ShowWhenProps) {
10
+ return when ? children: otherwise
11
+ }
@@ -0,0 +1,53 @@
1
+ import RippleContainer, { RippleContainerProps } from '@/Shared/Components/Core/RippleContainer';
2
+ import Icon, { IconName } from '@/Shared/Components/Core/Icon';
3
+ import { ButtonSize, ButtonVariants } from './Utils/types';
4
+ import { RANGE } from '@/Shared/Types/number.type';
5
+ import { getButtonStyle } from './Utils/functions';
6
+ import { BUTTON_LAYOUT } from './Utils/constance';
7
+ import ThemeText from '@/Shared/Stores/Theme/Components/ThemeText';
8
+ import ShowWhen from '@/Shared/Components/Core/ShowWhen';
9
+
10
+ type ButtonProp = Omit<RippleContainerProps, 'rippleColor' | 'rippleScale'> & {
11
+ title: string;
12
+
13
+ startIcon?: IconName;
14
+ endIcon?: IconName;
15
+ variant?: ButtonVariants;
16
+ size?: ButtonSize;
17
+ rounded?: number | `${RANGE<0, 100>}%`;
18
+ };
19
+
20
+
21
+ export default function Button({ title, startIcon, endIcon, variant = 'solid', color = 'primary', size = 'md', rounded, style, ...props}: ButtonProp) {
22
+
23
+ const { color: textColor, borderColor, backgroundColor } = getButtonStyle(variant, color);
24
+
25
+ const {fontSize, ...containerStyle} = {
26
+ ...BUTTON_LAYOUT[size],
27
+ ...rounded !== undefined ? {borderRadius: rounded} : {}
28
+ };
29
+
30
+ return (
31
+ <RippleContainer
32
+ {...props}
33
+ rippleColor={textColor}
34
+ rippleScale={2}
35
+
36
+ style={{
37
+ backgroundColor, borderColor, flexDirection: 'row', gap: Math.floor(fontSize / 2), alignItems: 'center', justifyContent: 'center',
38
+ ...containerStyle,
39
+ ...style,
40
+ }}
41
+ >
42
+ <ShowWhen when={!!startIcon}>
43
+ <Icon name={startIcon as IconName} customColor={textColor} />
44
+ </ShowWhen>
45
+
46
+ <ThemeText textColor={textColor} style={{fontSize}} >{title}</ThemeText>
47
+
48
+ <ShowWhen when={!!endIcon} >
49
+ <Icon name={endIcon as IconName} customColor={textColor} />
50
+ </ShowWhen>
51
+ </RippleContainer>
52
+ );
53
+ }
@@ -0,0 +1,32 @@
1
+ import { RANGE } from "../../../Types/number.type"
2
+ import Icon, { IconName } from "../../Core/Icon"
3
+ import RippleContainer, { RippleContainerProps } from "../../Core/RippleContainer"
4
+ import { BUTTON_LAYOUT } from "./Utils/constance"
5
+ import { getButtonStyle } from "./Utils/functions"
6
+ import { ButtonSize, ButtonVariants } from "./Utils/types"
7
+
8
+
9
+ export type IconButtonProps = RippleContainerProps & {
10
+ icon: IconName,
11
+
12
+ variant?: ButtonVariants,
13
+ size?: number | ButtonSize,
14
+ rounded?: number | `${RANGE<0, 100>}%`
15
+ }
16
+
17
+ export default function IconButton({variant='soft', color='primary', icon, size='md', rounded='50%', ...props}: IconButtonProps) {
18
+
19
+ const {color: textColor, ...style} = getButtonStyle(variant, color);
20
+
21
+ const height = typeof size === 'number' ? size : BUTTON_LAYOUT[size].height;
22
+ const borderWidth = typeof size === 'number' ? 1 : BUTTON_LAYOUT[size].borderWidth;
23
+ return (
24
+ <RippleContainer {...props}
25
+ rippleScale={2}
26
+ rippleColor={textColor}
27
+ style={{...style, height, borderRadius: rounded, borderWidth, alignItems: 'center', justifyContent: 'center', aspectRatio: 1}}
28
+ >
29
+ <Icon customColor={textColor} name={icon} size={Math.floor(height * 0.6)} />
30
+ </RippleContainer>
31
+ )
32
+ }
@@ -0,0 +1,39 @@
1
+ import { ButtonLayout, ButtonSize } from "./types";
2
+
3
+ export const BUTTON_LAYOUT: Record<ButtonSize, ButtonLayout> = {
4
+ 'xs': {
5
+ height: 24,
6
+ borderRadius: 8,
7
+ paddingInline: 8,
8
+ fontSize: 10,
9
+ borderWidth: 1
10
+ },
11
+ 'sm': {
12
+ height: 32,
13
+ borderRadius: 12,
14
+ paddingInline: 12,
15
+ fontSize: 14,
16
+ borderWidth: 1
17
+ },
18
+ 'md': {
19
+ height: 40,
20
+ borderRadius: 14,
21
+ paddingInline: 14,
22
+ fontSize: 16,
23
+ borderWidth: 1
24
+ },
25
+ 'lg': {
26
+ height: 48,
27
+ borderRadius: 16,
28
+ paddingInline: 16,
29
+ fontSize: 20,
30
+ borderWidth: 2
31
+ },
32
+ 'xl': {
33
+ height: 56,
34
+ borderRadius: 18,
35
+ paddingInline: 18,
36
+ fontSize: 24,
37
+ borderWidth: 2
38
+ }
39
+ }
@@ -0,0 +1,55 @@
1
+ import { useMemo } from "react";
2
+ import { useThemeStore } from "../../../../Stores/Theme";
3
+ import { ColorStates } from "../../../../Stores/Theme/types";
4
+ import { ButtonVariants } from "./types";
5
+
6
+ export function getButtonStyle(variant: ButtonVariants, color: ColorStates) {
7
+ const {textColor, bgColor} = useThemeStore((states) => {
8
+ if(['text', 'bg'].includes(color ?? '')) {
9
+ return {
10
+ bgColor: states.colors[color],
11
+ textColor: states.colors[color === 'text' ? 'bg' : 'text']
12
+ }
13
+ }
14
+
15
+ return {
16
+ bgColor: states.colors[color],
17
+ textColor: 'rgb(255,255,255)'
18
+ }
19
+ })
20
+
21
+
22
+ const style = useMemo(() => ({
23
+ 'solid': {
24
+ color: textColor,
25
+ backgroundColor: bgColor,
26
+ borderColor: bgColor,
27
+ },
28
+
29
+ 'outlined': {
30
+ color: bgColor,
31
+ backgroundColor: 'transparent',
32
+ borderColor: bgColor,
33
+ },
34
+
35
+ 'soft': {
36
+ color: bgColor,
37
+ backgroundColor: bgColor.replace(')', ', 0.2)'),
38
+ borderColor: bgColor.replace(')', ', 0.2)'),
39
+ },
40
+
41
+ 'soft-outlined': {
42
+ color: bgColor,
43
+ backgroundColor: bgColor.replace(')', ', 0.2)'),
44
+ borderColor: bgColor
45
+ },
46
+
47
+ 'text': {
48
+ color: bgColor,
49
+ backgroundColor: 'transparent',
50
+ borderColor: 'transparent',
51
+ }
52
+ }[variant]), [bgColor, textColor]);
53
+
54
+ return style;
55
+ }
@@ -0,0 +1,12 @@
1
+ export type ButtonVariants = 'solid' | 'outlined' | 'soft' | 'soft-outlined' | 'text';
2
+
3
+ export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
4
+
5
+ // add more according to need;
6
+ export type ButtonLayout = {
7
+ height: number,
8
+ borderRadius: number,
9
+ paddingInline: number,
10
+ fontSize: number,
11
+ borderWidth: number
12
+ }
@@ -0,0 +1,2 @@
1
+ export {default as Button} from './Button';
2
+ export {default as IconButton} from './IconButton';
@@ -0,0 +1,14 @@
1
+ import { DependencyList, EffectCallback, useEffect, useRef } from "react";
2
+
3
+
4
+ export default function useUpdateEffect(effect: EffectCallback, deps: DependencyList) {
5
+ const mountedRef = useRef(false);
6
+
7
+ useEffect(() => {
8
+ if (mountedRef.current) {
9
+ return effect();
10
+ }
11
+
12
+ mountedRef.current = true;
13
+ }, deps);
14
+ }
@@ -0,0 +1,22 @@
1
+ import { Animated, TextProps } from "react-native";
2
+ import { useThemeStore } from "..";
3
+ import { ColorStates } from "../types";
4
+ import { AnimatedInterpolValue } from "../../../Types/native.type";
5
+
6
+
7
+ export type ThemeTextProps = TextProps & {
8
+ color?: ColorStates,
9
+ textColor?: string | AnimatedInterpolValue
10
+ }
11
+
12
+ export default function ThemeText({style, color: _color = 'text', textColor, className, ...props}: ThemeTextProps): React.JSX.Element {
13
+
14
+ const color = useThemeStore(states => textColor ?? states.colors[_color]);
15
+
16
+ return (
17
+ <Animated.Text {...props}
18
+ style={[style, {color}]}
19
+ className={`font-regular ${className}`}
20
+ />
21
+ )
22
+ }
@@ -0,0 +1,25 @@
1
+ import { Animated, ViewProps } from "react-native";
2
+ import { useThemeStore } from "..";
3
+ import { ColorStates } from "../types";
4
+
5
+
6
+ export type ThemeViewProps = ViewProps & {
7
+ color?: ColorStates,
8
+ backgroundColor?: string,
9
+ useWindBackground?: boolean
10
+ }
11
+
12
+ export default function ThemeView({style, color = 'bg', backgroundColor, useWindBackground=false, ...props}: ThemeViewProps): React.JSX.Element {
13
+
14
+ const {_backgroundColor} = useThemeStore(states => ({
15
+ _backgroundColor: states.colors[color]
16
+ }));
17
+
18
+ if(!backgroundColor) backgroundColor = _backgroundColor;
19
+
20
+ return (
21
+ <Animated.View {...props}
22
+ style={[style, useWindBackground === false ? {backgroundColor} : null]}
23
+ />
24
+ )
25
+ }
@@ -0,0 +1,2 @@
1
+ export { default as ThemeText } from "./ThemeText";
2
+ export { default as ThemeView } from "./ThemeView";
@@ -0,0 +1,25 @@
1
+ import { Theme, ColorStates } from './types';
2
+
3
+ export const _theme: Theme = 'light';
4
+
5
+ export const _colors: Record<Theme, Record<ColorStates, string>> = {
6
+ light: {
7
+ text: 'rgb(0, 0, 0)',
8
+ bg: 'rgb(240, 242, 245)',
9
+
10
+ primary: 'rgb(40, 120, 255)',
11
+ error: 'rgb(245, 34, 45)',
12
+ warning: 'rgb(255, 184, 0)',
13
+ info: 'rgb(74, 108, 135)',
14
+ },
15
+
16
+ dark: {
17
+ text: 'rgb(240, 242, 245)',
18
+ bg: 'rgb(0, 0, 0)',
19
+
20
+ primary: 'rgb(40, 120, 255)',
21
+ error: 'rgb(245, 34, 45)',
22
+ warning: 'rgb(255, 184, 0)',
23
+ info: 'rgb(74, 108, 135)',
24
+ },
25
+ };
@@ -0,0 +1,19 @@
1
+ import { Theme } from './types';
2
+ import { _colors, _theme } from './constance';
3
+ import { createStore } from '@fun-tools/store';
4
+
5
+ const { useStore, useHandlers } = createStore({
6
+ states: {
7
+ theme: _theme,
8
+ colors: _colors[_theme],
9
+ },
10
+
11
+ syncHandlers: {
12
+ toggleTheme(state) {
13
+ state.theme = state.theme === 'dark' ? 'light' : 'dark';
14
+ state.colors = _colors[state.theme];
15
+ },
16
+ },
17
+ });
18
+
19
+ export { useStore as useThemeStore, useHandlers as useThemeHandlers };
@@ -0,0 +1,3 @@
1
+ export type Theme = 'light' | 'dark';
2
+
3
+ export type ColorStates = 'text' | 'bg' | 'primary' | 'error' | 'info' | 'warning';
@@ -0,0 +1,4 @@
1
+ import { Animated } from "react-native";
2
+
3
+ const interpol = new Animated.Value(0).interpolate;
4
+ export type AnimatedInterpolValue = ReturnType<typeof interpol>;
@@ -0,0 +1,8 @@
1
+ export type FINITE_NUMBER<E extends number, A extends unknown[] = []> = (
2
+ A['length'] extends E ? A[number] | E : FINITE_NUMBER<E, [...A, A['length']]>
3
+ )
4
+
5
+
6
+ export type RANGE<S extends number, E extends number> = (
7
+ Exclude<FINITE_NUMBER<E>, FINITE_NUMBER<S>> | S
8
+ )
@@ -0,0 +1,7 @@
1
+ declare module '*.svg' {
2
+ import React from 'react';
3
+ import { SvgProps } from 'react-native-svg';
4
+
5
+ const content: React.FC<SvgProps>;
6
+ export default content;
7
+ }
@@ -0,0 +1,22 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ "./src/**/*.{js,jsx,ts,tsx}",
5
+ ],
6
+ presets: [require("nativewind/preset")],
7
+ theme: {
8
+ extend: {
9
+ fontFamily: {
10
+ thin: ['Roboto-Thin'],
11
+ extralight: ['Roboto-ExtraLight'],
12
+ light: ['Roboto-Light'],
13
+ regular: ['Roboto-Regular'],
14
+ medium: ['Roboto-Medium'],
15
+ semibold: ['Roboto-SemiBold'],
16
+ bold: ['Roboto-Bold'],
17
+ extrabold: ['Roboto-ExtraBold'],
18
+ }
19
+ },
20
+ },
21
+ plugins: [],
22
+ };
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "@react-native/typescript-config",
3
+ "compilerOptions": {
4
+ "jsx": "react-native",
5
+ "types": ["jest"],
6
+ "paths": {
7
+ "@/*": ["./src/*"]
8
+ }
9
+ },
10
+ "include": [
11
+ "**/*.ts",
12
+ "**/*.tsx",
13
+ "nativewind-env.d.ts",
14
+ "react-native.config.js"
15
+ ],
16
+ "exclude": ["**/node_modules", "**/Pods"]
17
+ }