@hoddy-ui/core 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.
package/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ // Components
2
+ export { default as AdaptiveStatusBar } from "./src/Components/AdaptiveStatusBar";
3
+ export { default as AlertX } from "./src/Components/AlertX";
4
+ export { default as Avatar } from "./src/Components/Avatar";
5
+ export * from "./src/Components/Button";
6
+ export { default as Button } from "./src/Components/Button";
7
+ export { default as Spinner } from "./src/Components/Spinner";
8
+ export * from "./src/Components/TextField";
9
+ export { default as TextField } from "./src/Components/TextField";
10
+ export { default as Typography } from "./src/Components/Typography";
11
+ export { default as SafeAreaView } from "./src/Components/SafeAreaView";
12
+ export { default as SelectMenu } from "./src/Components/SelectMenu";
13
+ // Others
14
+ export * from "./src/hooks";
15
+ export * from "./src/theme/index";
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@hoddy-ui/core",
3
+ "version": "1.0.0",
4
+ "description": "Core rich react native components written in typescript",
5
+ "main": "index.ts",
6
+ "repository": {
7
+ "directory": "packages/core",
8
+ "type": "git",
9
+ "url": "https://github.com/kinghoddy/hoddy-ui"
10
+ },
11
+ "author": "Hoddy inc",
12
+ "license": "MIT",
13
+ "private": false,
14
+ "peerDependencies": {
15
+ "@expo/vector-icons": "^13.0.0",
16
+ "@react-native-async-storage/async-storage": "^1.18.1",
17
+ "@react-navigation/native": "^6.1.6",
18
+ "expo-navigation-bar": "^2.1.1",
19
+ "expo-system-ui": "^2.2.1",
20
+ "react": "^18.2.0",
21
+ "react-native": "^0.71.8",
22
+ "react-native-safe-area-context": "^4.5.3",
23
+ "react-native-size-matters": "^0.4.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/react": "^18.2.6",
27
+ "@types/react-native": "^0.72.0",
28
+ "typescript": "^5.0.4"
29
+ },
30
+ "dependencies": {},
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }
@@ -0,0 +1,38 @@
1
+ import { useFocusEffect } from "@react-navigation/native";
2
+ import React, { useState } from "react";
3
+ import { Platform, StatusBar } from "react-native";
4
+ import { useColors, useTheme } from "../hooks";
5
+
6
+ const AdaptiveStatusBar = ({ translucent = false }) => {
7
+ const [focused, setFocused] = useState(false);
8
+ const colors = useColors();
9
+ const theme = useTheme();
10
+ useFocusEffect(
11
+ React.useCallback(() => {
12
+ // setFocused(true);
13
+ StatusBar.setBarStyle(
14
+ theme === "dark" ? "light-content" : "dark-content"
15
+ );
16
+ if (Platform.OS === "android") {
17
+ StatusBar.setBackgroundColor(
18
+ colors.white[1] + (translucent ? "0" : "")
19
+ );
20
+ StatusBar.setTranslucent(true);
21
+ }
22
+ // return () => setFocused(false);
23
+ }, [theme])
24
+ );
25
+
26
+ React.useEffect(() => {
27
+ // setFocused(true);
28
+ StatusBar.setBarStyle(theme === "dark" ? "light-content" : "dark-content");
29
+ if (Platform.OS === "android") {
30
+ StatusBar.setBackgroundColor(colors.white[1] + (translucent ? "0" : ""));
31
+ StatusBar.setTranslucent(true);
32
+ }
33
+ // return () => setFocused(false);
34
+ }, [theme]);
35
+ return <></>;
36
+ };
37
+
38
+ export default AdaptiveStatusBar;
@@ -0,0 +1,60 @@
1
+ import { MaterialIcons } from "@expo/vector-icons";
2
+ import React from "react";
3
+ import { View } from "react-native";
4
+ import { ScaledSheet } from "react-native-size-matters";
5
+ import { useColors } from "../hooks";
6
+ import { AlertXProps } from "../types";
7
+ import Typography from "./Typography";
8
+
9
+ const AlertX: React.FC<AlertXProps> = ({
10
+ type = "info",
11
+ variant = "contained",
12
+ title,
13
+ gutterBottom = 0,
14
+ body,
15
+ style = {},
16
+ }) => {
17
+ const colors = useColors();
18
+
19
+ const styles: any = ScaledSheet.create({
20
+ container: {
21
+ padding: 20,
22
+ paddingTop: 10,
23
+ paddingBottom: 10,
24
+ borderRadius: 8,
25
+ alignItems: "center",
26
+ flexDirection: "row",
27
+ marginBottom: gutterBottom + "@ms",
28
+ backgroundColor: colors[type].main + (variant === "contained" ? "" : "3"),
29
+ },
30
+ title: {
31
+ color: variant === "contained" ? "#fff" : colors[type].main,
32
+ },
33
+ body: {
34
+ color: variant === "contained" ? "#fff" : colors[type].main,
35
+ },
36
+ });
37
+ return (
38
+ <View style={{ ...styles.container, ...style }}>
39
+ <View style={{ width: "80%" }}>
40
+ <Typography style={styles.title} gutterBottom={3} fontWeight={700}>
41
+ {title}
42
+ </Typography>
43
+ {body && (
44
+ <Typography fontWeight={700} variant="body2" style={styles.body}>
45
+ {body}
46
+ </Typography>
47
+ )}
48
+ </View>
49
+ <View style={{ marginLeft: "auto" }}>
50
+ <MaterialIcons
51
+ color={variant === "contained" ? "#fff" : colors[type].main}
52
+ size={36}
53
+ name={type === "success" ? "check" : type}
54
+ />
55
+ </View>
56
+ </View>
57
+ );
58
+ };
59
+
60
+ export default AlertX;
@@ -0,0 +1,57 @@
1
+ import { AntDesign } from "@expo/vector-icons";
2
+ import React from "react";
3
+ import { Image, View } from "react-native";
4
+ import { ScaledSheet } from "react-native-size-matters";
5
+ import { useColors } from "../hooks";
6
+ import { AvatarProps } from "../types";
7
+ import Typography from "./Typography";
8
+
9
+ const Avatar: React.FC<AvatarProps> = ({
10
+ color = "dark",
11
+ label,
12
+ variant = "contained",
13
+ source,
14
+ size = 48,
15
+ style = {},
16
+ }) => {
17
+ const colors = useColors();
18
+ const styles: any = ScaledSheet.create({
19
+ root: {
20
+ borderRadius: 150,
21
+ height: size + "@ms",
22
+ width: size + "@ms",
23
+ alignItems: "center",
24
+ justifyContent: "center",
25
+ overflow: "hidden",
26
+ borderWidth: variant === "outlined" ? 5 : 0,
27
+ borderColor: variant === "outlined" ? "#fff" : "#0000",
28
+ backgroundColor:
29
+ variant === "outlined"
30
+ ? null
31
+ : label
32
+ ? colors[color].main
33
+ : colors.white[4],
34
+ ...style,
35
+ },
36
+ image: {
37
+ height: "110%",
38
+ width: "110%",
39
+ },
40
+ });
41
+
42
+ return (
43
+ <View style={styles.root}>
44
+ {source ? (
45
+ <Image resizeMode="cover" style={styles.image} source={source} />
46
+ ) : label ? (
47
+ <Typography style={{ color: colors[color].text }}>
48
+ {label[0]}
49
+ </Typography>
50
+ ) : (
51
+ <AntDesign name="user" color="#fff" size={Math.round(size / 1.5)} />
52
+ )}
53
+ </View>
54
+ );
55
+ };
56
+
57
+ export default Avatar;
@@ -0,0 +1,174 @@
1
+ import { Ionicons, MaterialIcons } from "@expo/vector-icons";
2
+ import React from "react";
3
+ import { ActivityIndicator, Text, TouchableOpacity } from "react-native";
4
+ import { ScaledSheet, moderateScale } from "react-native-size-matters";
5
+ import { useColors } from "../hooks";
6
+ import { ButtonProps, IconButtonProps, LinkButtonProps } from "../types";
7
+
8
+ export const LinkButton: React.FC<LinkButtonProps> = ({
9
+ title,
10
+ style = {},
11
+ color = "blue",
12
+ fontSize = 12,
13
+ fontWeight = "400",
14
+ disabled,
15
+ onPress = () => {},
16
+ }) => {
17
+ const colors = useColors();
18
+
19
+ const styles: any = ScaledSheet.create({
20
+ text: {
21
+ fontSize: moderateScale(fontSize),
22
+ fontWeight: fontWeight,
23
+ color: disabled ? "#777" : colors[color].main,
24
+ },
25
+ });
26
+ return (
27
+ <TouchableOpacity onPress={onPress} disabled={disabled}>
28
+ <Text style={{ ...styles.text, ...style }}>{title}</Text>
29
+ </TouchableOpacity>
30
+ );
31
+ };
32
+
33
+ export const IconButton: React.FC<IconButtonProps> = ({
34
+ style = {},
35
+ color = "dark",
36
+ disabled,
37
+ icon,
38
+ elevation,
39
+ bg = false,
40
+ size = 24,
41
+ containerStyles = {},
42
+ onPress = () => {},
43
+ iconType = "material",
44
+ }) => {
45
+ const colors = useColors();
46
+
47
+ const styles: any = ScaledSheet.create({
48
+ container: {
49
+ alignSelf: "flex-start",
50
+ flexGrow: 0,
51
+ backgroundColor: bg
52
+ ? colors.white[1]
53
+ : elevation! > 0
54
+ ? colors.white[1]
55
+ : null,
56
+ padding: "5@ms",
57
+ shadowColor: "#000",
58
+ shadowOpacity: 0.1,
59
+ shadowOffset: {
60
+ height: 1,
61
+ width: 0,
62
+ },
63
+ height: bg ? size + 20 + "@ms" : undefined,
64
+ width: bg ? size + 20 + "@ms" : undefined,
65
+ alignItems: "center",
66
+ justifyContent: "center",
67
+ shadowRadius: elevation,
68
+ elevation: elevation,
69
+ borderRadius: size,
70
+ },
71
+ text: {
72
+ color: disabled ? "#777" : colors[color].main,
73
+ },
74
+ });
75
+
76
+ const IconComp = {
77
+ material: MaterialIcons,
78
+ ion: Ionicons,
79
+ }[iconType];
80
+ return (
81
+ <TouchableOpacity
82
+ onPress={onPress}
83
+ activeOpacity={0.3}
84
+ style={{ ...styles.container, ...containerStyles }}
85
+ >
86
+ <IconComp style={{ ...styles.text, ...style }} name={icon} size={size} />
87
+ </TouchableOpacity>
88
+ );
89
+ };
90
+
91
+ const Button: React.FC<ButtonProps> = ({
92
+ elevation = 0,
93
+ onPress = () => {},
94
+ disabled = false,
95
+ title,
96
+ loading,
97
+ size,
98
+ rounded = false,
99
+ gutterBottom,
100
+ style = {},
101
+ fullWidth = false,
102
+ translucent = false,
103
+ color = "primary",
104
+ variant = "contained",
105
+ start,
106
+ end,
107
+ }) => {
108
+ const colors = useColors();
109
+
110
+ const styles: any = ScaledSheet.create({
111
+ con: {
112
+ flexDirection: "row",
113
+ alignItems: "center",
114
+ alignSelf: "flex-start",
115
+ justifyContent: "center",
116
+ backgroundColor:
117
+ variant === "text" || variant === "outlined"
118
+ ? null
119
+ : translucent
120
+ ? translucent === "dark"
121
+ ? colors.white[3] + "2"
122
+ : colors.black[3] + "2"
123
+ : loading
124
+ ? colors[color].light
125
+ : disabled
126
+ ? colors.white[4]
127
+ : colors[color].main,
128
+ borderRadius: rounded ? 30 : 10,
129
+ elevation: variant === "text" ? 0 : elevation,
130
+ paddingVertical: size === "small" ? 8 : "13@ms",
131
+ paddingHorizontal: size === "small" ? "10@ms" : "18@ms",
132
+ borderColor: colors[color].main,
133
+ borderWidth: variant === "outlined" ? 1 : 0,
134
+ shadowColor: "#000",
135
+ shadowRadius: elevation,
136
+ marginBottom: gutterBottom,
137
+ shadowOffset: {
138
+ height: elevation / 2,
139
+ width: 0,
140
+ },
141
+ shadowOpacity: variant === "text" ? 0 : 0.3,
142
+ width: fullWidth ? "100%" : null,
143
+ ...style,
144
+ },
145
+ text: {
146
+ color: disabled
147
+ ? variant === "text" || variant === "outlined"
148
+ ? colors.black[1]
149
+ : colors[color].text
150
+ : colors[color][
151
+ variant === "text" || variant === "outlined" ? "main" : "text"
152
+ ],
153
+ fontWeight: variant === "outlined" ? "700" : "500",
154
+ fontSize: size === "small" ? "12@ms" : "16@ms",
155
+ },
156
+ });
157
+
158
+ return (
159
+ <TouchableOpacity onPress={onPress} disabled={disabled} style={styles.con}>
160
+ {start}
161
+ {loading && (
162
+ <ActivityIndicator
163
+ size="small"
164
+ color={colors[color].text}
165
+ style={{ marginRight: 10 }}
166
+ />
167
+ )}
168
+ <Text style={styles.text}>{title}</Text>
169
+ {end}
170
+ </TouchableOpacity>
171
+ );
172
+ };
173
+
174
+ export default Button;
@@ -0,0 +1,19 @@
1
+ import { SafeAreaView as Safe } from "react-native";
2
+ import React from "react";
3
+
4
+ import { Platform, StyleSheet } from "react-native";
5
+ import { moderateScale } from "react-native-size-matters";
6
+ import { SafeAreaViewProps } from "../types";
7
+
8
+ const styles = StyleSheet.create({
9
+ droidSafeArea: {
10
+ flex: 1,
11
+ paddingTop: Platform.OS === "android" ? moderateScale(35) : 0,
12
+ },
13
+ });
14
+
15
+ const SafeAreaView: React.FC<SafeAreaViewProps> = ({ children, style }) => {
16
+ return <Safe style={{ ...styles.droidSafeArea, ...style }}>{children}</Safe>;
17
+ };
18
+
19
+ export default SafeAreaView;
@@ -0,0 +1,139 @@
1
+ import React from "react";
2
+ import { View, Modal, TouchableOpacity, ScrollView } from "react-native";
3
+ import { ScaledSheet } from "react-native-size-matters";
4
+ import Typography from "./Typography";
5
+ import { MaterialIcons } from "@expo/vector-icons";
6
+ import Button from "./Button";
7
+ import { useColors } from "../hooks";
8
+ import { SelectMenuProps } from "../types";
9
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
10
+
11
+ const SelectMenu: React.FC<SelectMenuProps> = ({
12
+ open = false,
13
+ onClose,
14
+ value,
15
+ options = [],
16
+ onChange,
17
+ disableAutoClose = false,
18
+ label,
19
+ secondary,
20
+ helperText,
21
+ }) => {
22
+ const colors = useColors();
23
+ const { bottom } = useSafeAreaInsets();
24
+ const styles: any = ScaledSheet.create({
25
+ root: {
26
+ backgroundColor: colors.white[1],
27
+ flex: 1,
28
+ },
29
+ content: {
30
+ flex: 1,
31
+ paddingHorizontal: "10@ms",
32
+ },
33
+ header: {
34
+ paddingTop: "80@ms",
35
+ marginBottom: "20@vs",
36
+ },
37
+
38
+ options: {},
39
+ option: {
40
+ paddingHorizontal: "10@s",
41
+ paddingVertical: "10@vs",
42
+ borderRadius: 8,
43
+ flexDirection: "row",
44
+ alignItems: "center",
45
+ marginBottom: "10@vs",
46
+ },
47
+ footer: {
48
+ paddingBottom: bottom,
49
+ paddingHorizontal: "15@ms",
50
+ paddingTop: "15@ms",
51
+ },
52
+ });
53
+ return (
54
+ <Modal visible={open} animationType="slide" onRequestClose={onClose}>
55
+ <View style={styles.root}>
56
+ <View style={{ flex: 1 }}>
57
+ <ScrollView>
58
+ <View style={styles.content}>
59
+ <View style={styles.header}>
60
+ <Typography variant="h5" gutterBottom={5} fontWeight={700}>
61
+ {label}
62
+ </Typography>
63
+ {helperText ? (
64
+ <Typography variant="body2" color="textSecondary">
65
+ {helperText}
66
+ </Typography>
67
+ ) : null}
68
+ </View>
69
+ <View style={styles.options}>
70
+ {[...options].map((cur) => (
71
+ <TouchableOpacity
72
+ style={{
73
+ ...styles.option,
74
+ backgroundColor:
75
+ cur.value === value
76
+ ? colors.blue.light + "2"
77
+ : colors.white[2],
78
+ }}
79
+ onPress={() => {
80
+ onChange(cur.value);
81
+ if (!disableAutoClose) onClose();
82
+ }}
83
+ key={cur.label}
84
+ >
85
+ <View>
86
+ <Typography
87
+ style={{
88
+ color:
89
+ cur.value === value
90
+ ? colors.blue.light
91
+ : colors.black[2],
92
+ }}
93
+ >
94
+ {cur.label}
95
+ </Typography>
96
+ {cur.secondary ? (
97
+ <Typography
98
+ variant="body2"
99
+ style={{
100
+ marginTop: 2,
101
+ color:
102
+ cur.value === value
103
+ ? colors.blue.light
104
+ : colors.white[5],
105
+ }}
106
+ >
107
+ {cur.secondary}
108
+ </Typography>
109
+ ) : null}
110
+ </View>
111
+ {value === cur.value && (
112
+ <MaterialIcons
113
+ name="check"
114
+ color={colors.blue.light}
115
+ size={24}
116
+ style={{ marginLeft: "auto" }}
117
+ />
118
+ )}
119
+ </TouchableOpacity>
120
+ ))}
121
+ </View>
122
+ </View>
123
+ </ScrollView>
124
+ </View>
125
+ <View style={styles.footer}>
126
+ <Button
127
+ color="error"
128
+ variant="outlined"
129
+ fullWidth
130
+ title="Close"
131
+ onPress={onClose}
132
+ />
133
+ </View>
134
+ </View>
135
+ </Modal>
136
+ );
137
+ };
138
+
139
+ export default SelectMenu;
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import { ActivityIndicator, Dimensions, View } from "react-native";
3
+
4
+ import { ScaledSheet } from "react-native-size-matters";
5
+ import { useColors } from "../hooks";
6
+ import { SpinnerProps } from "../types";
7
+ import Typography from "./Typography";
8
+
9
+ const Spinner: React.FC<SpinnerProps> = ({
10
+ label,
11
+ size = "large",
12
+ color = "primary",
13
+ fullscreen = false,
14
+ style = {},
15
+ }) => {
16
+ const colors = useColors();
17
+ const styles: any = ScaledSheet.create({
18
+ root: {
19
+ width: fullscreen ? Dimensions.get("screen").width : "100%",
20
+ height: fullscreen ? Dimensions.get("screen").height : "100%",
21
+ left: 0,
22
+ bottom: 0,
23
+ zIndex: 100,
24
+ alignItems: "center",
25
+ justifyContent: "center",
26
+ position: fullscreen ? "absolute" : "relative",
27
+ backgroundColor: fullscreen ? colors.white[1] + "d" : undefined,
28
+ ...style,
29
+ },
30
+ content: {
31
+ flexDirection: "row",
32
+ alignItems: "center",
33
+ paddingVertical: "15@vs",
34
+ paddingHorizontal: "20@s",
35
+ borderRadius: 10,
36
+ // backgroundColor: fullscreen ? colors.light.main : null,
37
+ },
38
+ label: {
39
+ marginLeft: "10@s",
40
+ color: color === "light" ? colors.white[2] : colors.black[4],
41
+ },
42
+ });
43
+
44
+ return (
45
+ <View style={styles.root}>
46
+ <View style={styles.content}>
47
+ <ActivityIndicator color={colors[color].dark} size={size} />
48
+ {label && <Typography style={styles.label}>{label}</Typography>}
49
+ </View>
50
+ </View>
51
+ );
52
+ };
53
+
54
+ export default Spinner;