@hoddy-ui/core 1.0.5 → 1.0.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoddy-ui/core",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Core rich react native components written in typescript",
5
5
  "main": "index.ts",
6
6
  "repository": {
@@ -0,0 +1,36 @@
1
+ import { Pressable, StyleSheet, Text, View } from "react-native";
2
+ import React from "react";
3
+ import { MaterialCommunityIcons } from "@expo/vector-icons";
4
+
5
+ const CheckBox = (props: any) => {
6
+ const iconName = props.isChecked
7
+ ? "checkbox-marked"
8
+ : "checkbox-blank-outline";
9
+
10
+ return (
11
+ <View style={styles.container}>
12
+ <Pressable onPress={props.onPress}>
13
+ <MaterialCommunityIcons name={iconName} size={24} color="#0195FF" />
14
+ </Pressable>
15
+ </View>
16
+ );
17
+ };
18
+
19
+ export default CheckBox;
20
+
21
+ const styles = StyleSheet.create({
22
+ container: {
23
+ justifyContent: "flex-start",
24
+ alignItems: "center",
25
+ flexDirection: "row",
26
+ width: 20,
27
+ marginTop: 5,
28
+ marginRight: 10,
29
+ },
30
+ title: {
31
+ fontSize: 16,
32
+ color: "#000",
33
+ marginLeft: 5,
34
+ fontWeight: "600",
35
+ },
36
+ });
@@ -0,0 +1,57 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { View } from "react-native";
3
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
4
+ import { ScaledSheet } from "react-native-size-matters";
5
+ import { useColors } from "../hooks";
6
+ import { FlashMessageProps } from "../types";
7
+ import Typography from "./Typography";
8
+
9
+ const FlashMessage: React.FC<FlashMessageProps> = ({
10
+ title,
11
+ message,
12
+ type,
13
+ }) => {
14
+ const { top } = useSafeAreaInsets();
15
+ const colors = useColors();
16
+ const [show, setShow] = useState(false);
17
+ const styles = ScaledSheet.create({
18
+ root: {
19
+ position: "absolute",
20
+ top: 0,
21
+ zIndex: 1000,
22
+ left: 0,
23
+ paddingTop: top + 10,
24
+ paddingHorizontal: "15@ms",
25
+ backgroundColor: colors[type].main,
26
+ width: "100%",
27
+ paddingBottom: "10@ms",
28
+ },
29
+ });
30
+ useEffect(() => {
31
+ if (message) setShow(true);
32
+ setTimeout(() => {
33
+ setShow(false);
34
+ }, 2000);
35
+ }, [message]);
36
+ return show ? (
37
+ <View style={styles.root}>
38
+ {title && (
39
+ <Typography
40
+ variant="body2"
41
+ fontWeight={600}
42
+ gutterBottom={3}
43
+ style={{ color: "#fff" }}
44
+ >
45
+ {title}
46
+ </Typography>
47
+ )}
48
+ <Typography fontWeight={700} style={{ color: "#fff" }}>
49
+ {message}
50
+ </Typography>
51
+ </View>
52
+ ) : (
53
+ <></>
54
+ );
55
+ };
56
+
57
+ export default FlashMessage;
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+ import {
3
+ Keyboard,
4
+ KeyboardAvoidingView,
5
+ ScrollView,
6
+ TouchableWithoutFeedback,
7
+ View,
8
+ } from "react-native";
9
+ import { ScaledSheet } from "react-native-size-matters";
10
+ import { FormWrapperProps } from "../types";
11
+ const FormWrapper: React.FC<FormWrapperProps> = ({
12
+ children,
13
+ behavior = "position",
14
+ contentContainerStyle,
15
+ mode = "scroll",
16
+ keyboardVerticalOffset = 50,
17
+ style = {},
18
+ onScroll,
19
+ }) => {
20
+ const styles = ScaledSheet.create({
21
+ root: {
22
+ width: "100%",
23
+ flex: 1,
24
+ ...style,
25
+ },
26
+ });
27
+ return mode === "static" ? (
28
+ <TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
29
+ <KeyboardAvoidingView
30
+ style={styles.root}
31
+ behavior={behavior}
32
+ contentContainerStyle={styles.root}
33
+ keyboardVerticalOffset={keyboardVerticalOffset}
34
+ >
35
+ {children}
36
+ </KeyboardAvoidingView>
37
+ </TouchableWithoutFeedback>
38
+ ) : (
39
+ <ScrollView
40
+ onScroll={onScroll}
41
+ showsVerticalScrollIndicator={false}
42
+ scrollEventThrottle={40}
43
+ keyboardDismissMode="interactive"
44
+ contentContainerStyle={contentContainerStyle}
45
+ keyboardShouldPersistTaps="handled"
46
+ >
47
+ <KeyboardAvoidingView
48
+ behavior={behavior}
49
+ style={styles.root}
50
+ keyboardVerticalOffset={keyboardVerticalOffset}
51
+ >
52
+ {children}
53
+ </KeyboardAvoidingView>
54
+ </ScrollView>
55
+ );
56
+ };
57
+
58
+ export default FormWrapper;
@@ -0,0 +1,33 @@
1
+ import { View, Text } from "react-native";
2
+ import React from "react";
3
+ import { ScaledSheet } from "react-native-size-matters";
4
+ import { GridItemProps, GridProps } from "../types";
5
+
6
+ export const GridItem: React.FC<GridItemProps> = ({
7
+ children,
8
+ col = 2,
9
+ alignItems,
10
+ spacing = 1,
11
+ style = {},
12
+ }) => {
13
+ const styles = ScaledSheet.create({
14
+ gridItem: {
15
+ width: 100 / col + "%",
16
+ padding: spacing * 10 + "@ms",
17
+ alignItems: alignItems,
18
+ },
19
+ });
20
+ return <View children={children} style={[styles.gridItem, style]} />;
21
+ };
22
+ const Grid: React.FC<GridProps> = ({ children, spacing = 1, style = {} }) => {
23
+ const styles = ScaledSheet.create({
24
+ grid: {
25
+ flexWrap: "wrap",
26
+ marginHorizontal: -spacing * 10 + "@ms",
27
+ flexDirection: "row",
28
+ },
29
+ });
30
+ return <View children={children} style={[styles.grid, style]} />;
31
+ };
32
+
33
+ export default Grid;
@@ -0,0 +1,98 @@
1
+ import { MaterialIcons } from "@expo/vector-icons";
2
+ import React from "react";
3
+ import { TouchableOpacity, View } from "react-native";
4
+ import { ScaledSheet } from "react-native-size-matters";
5
+ import { useColors } from "../hooks";
6
+ import { ListItemProps, ListItemTextProps, ListProps } from "../types";
7
+ import Typography from "./Typography";
8
+
9
+ export const ListItemText: React.FC<ListItemTextProps> = ({
10
+ primary,
11
+ divider,
12
+ primaryProps = {},
13
+ secondaryProps = {},
14
+ secondary,
15
+ style = {},
16
+ }) => {
17
+ const colors = useColors();
18
+ const styles = ScaledSheet.create({
19
+ container: {
20
+ borderBottomColor: colors.white[4],
21
+ borderBottomWidth: divider ? 1 : 0,
22
+ paddingVertical: 0,
23
+ flexGrow: 1,
24
+ ...style,
25
+ },
26
+ });
27
+ return (
28
+ <View style={styles.container}>
29
+ {primary && (
30
+ <Typography
31
+ style={{ alignItems: "center" }}
32
+ variant="body1"
33
+ gutterBottom={2}
34
+ {...primaryProps}
35
+ >
36
+ {primary}
37
+ </Typography>
38
+ )}
39
+ {secondary && (
40
+ <Typography variant="body2" color="textSecondary" {...secondaryProps}>
41
+ {secondary}
42
+ </Typography>
43
+ )}
44
+ </View>
45
+ );
46
+ };
47
+ export const ListItem: React.FC<ListItemProps> = ({
48
+ link = false,
49
+ divider = false,
50
+ onPress,
51
+ index = 1,
52
+ style = {},
53
+ children,
54
+ }) => {
55
+ const colors = useColors();
56
+
57
+ const styles: any = ScaledSheet.create({
58
+ root: {
59
+ flexDirection: "row",
60
+ alignItems: "center",
61
+ paddingHorizontal: "10@s",
62
+ borderBottomColor: colors.white[4],
63
+ borderBottomWidth: divider ? 1 : 0,
64
+ paddingVertical: "10@vs",
65
+ },
66
+ });
67
+ return (
68
+ <View
69
+
70
+ // layout={Layout.springify()}
71
+ // exiting={SlideOutDown.delay(index * 100)}
72
+ // entering={SlideInUp.delay(index * 100)}
73
+ >
74
+ <TouchableOpacity disabled={Boolean(!onPress)} onPress={onPress}>
75
+ <View style={{ ...styles.root, ...style }}>
76
+ {children}
77
+ {link && (
78
+ <MaterialIcons
79
+ color={colors.white[5]}
80
+ name="arrow-forward-ios"
81
+ size={15}
82
+ />
83
+ )}
84
+ </View>
85
+ </TouchableOpacity>
86
+ </View>
87
+ );
88
+ };
89
+ export const List: React.FC<ListProps> = ({ style = {}, children }) => {
90
+ const styles = ScaledSheet.create({
91
+ root: {
92
+ flex: 1,
93
+ paddingHorizontal: "20@ms",
94
+ ...style,
95
+ },
96
+ });
97
+ return <View style={styles.root}>{children}</View>;
98
+ };
@@ -0,0 +1,225 @@
1
+ import { Ionicons } from "@expo/vector-icons";
2
+ import React, { useState } from "react";
3
+ import { Alert, TouchableOpacity, View } from "react-native";
4
+ import { ListItem } from "./List";
5
+ import TextField from "./TextField";
6
+
7
+ import * as Location from "expo-location";
8
+ import { ScaledSheet } from "react-native-size-matters";
9
+ import { useColors } from "../hooks";
10
+ import { LocatorProps } from "../types";
11
+
12
+ // import { GOOGLE_MAP_API } from "../../api/config";
13
+ import Typography from "./Typography";
14
+ import { GOOGLE_MAP_API_KEY } from "../../api/config";
15
+
16
+ Location.setGoogleApiKey(GOOGLE_MAP_API_KEY);
17
+
18
+ type predictionType = {
19
+ id: string;
20
+ description: string;
21
+ };
22
+ export const getPredictionsFromCoords = async (coords: any) => {
23
+ if (!coords) return [];
24
+ const res = await (
25
+ await fetch(
26
+ `https://maps.googleapis.com/maps/api/geocode/json?radius=200&latlng=${coords.latitude},${coords.longitude}&key=${GOOGLE_MAP_API_KEY}`
27
+ )
28
+ ).json();
29
+
30
+ const p = [];
31
+
32
+ for (let key in res.results) {
33
+ const { formatted_address: description, place_id } = res.results[key];
34
+ p.push({
35
+ description,
36
+ id: place_id,
37
+ latLng: { lst: coords.latitude, lng: coords.longitude },
38
+ });
39
+ }
40
+
41
+ return p;
42
+ };
43
+
44
+ const Locator: React.FC<LocatorProps> = ({
45
+ variant = "contained",
46
+ onLocationSelected,
47
+ label,
48
+ error,
49
+ location = {
50
+ description: null,
51
+ },
52
+ renderInput,
53
+ gutterBottom = 0,
54
+ helperText,
55
+ float = true,
56
+ country = "ng",
57
+ }) => {
58
+ const [changed, setChanged] = useState(false);
59
+ const [value, setValue] = useState("");
60
+ const [prediction, setPrediction] = useState<predictionType[]>([]);
61
+ const colors = useColors();
62
+ const styles: any = ScaledSheet.create({
63
+ list: {
64
+ backgroundColor: colors.white[2],
65
+ elevation: 10,
66
+ shadowColor: "#000",
67
+ shadowOpacity: 0.1,
68
+ shadowRadius: float ? 15 : 0,
69
+ shadowOffset: {
70
+ height: 10,
71
+ },
72
+ borderRadius: 10,
73
+ marginBottom: 10,
74
+ width: "100%",
75
+ zIndex: 1000,
76
+ marginTop: float ? 2 : "15@ms",
77
+ top: float ? "100%" : undefined,
78
+ position: float ? "absolute" : "relative",
79
+ },
80
+ });
81
+ const search = async (query: string) => {
82
+ const endpoint = `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${query}&components=country:${country}&radius=20000&location=6.465422,3.406448&key=${GOOGLE_MAP_API_KEY}`;
83
+ const res = await (await fetch(endpoint)).json();
84
+ const p = [];
85
+ for (let key in res.predictions) {
86
+ const { description, place_id } = res.predictions[key];
87
+ p.push({
88
+ description,
89
+ id: place_id,
90
+ });
91
+ }
92
+ setPrediction(p);
93
+ };
94
+
95
+ const locateMe = () => {
96
+ // Alert.alert(
97
+ // "Use my location",
98
+ // "Auto fill this input with my current location",
99
+ // [{ text: "Cancel" }, { text: "Use Location", onPress: () => getLoc() }]
100
+ // );
101
+ const getLoc = async () => {
102
+ const { status } = await Location.requestForegroundPermissionsAsync();
103
+ if (status !== "granted")
104
+ return Alert.alert(
105
+ "Error",
106
+ "Permission to access location was denied! "
107
+ );
108
+ try {
109
+ let { coords } = await Location.getCurrentPositionAsync({
110
+ accuracy: Location.LocationAccuracy.High,
111
+ });
112
+ const p = await getPredictionsFromCoords(coords);
113
+ setPrediction(p);
114
+ } catch (err) {
115
+ console.log(err);
116
+ Alert.alert(
117
+ "Can't access your location",
118
+ "Make sure your location settings are turned on and you are connected to the internet. "
119
+ );
120
+ }
121
+ };
122
+ getLoc();
123
+ };
124
+
125
+ const clear = () => {
126
+ setPrediction([]);
127
+ setValue("");
128
+ onLocationSelected(null);
129
+ setChanged(false);
130
+ };
131
+ const locationPressed = async (loc: predictionType) => {
132
+ setValue(loc.description);
133
+ const res = await (
134
+ await fetch(
135
+ `https://maps.googleapis.com/maps/api/place/details/json?place_id=${loc.id}&fields=formatted_address%2Cgeometry&key=${GOOGLE_MAP_API_KEY}`
136
+ )
137
+ ).json();
138
+ onLocationSelected({
139
+ latitude: res.result?.geometry.location.lat,
140
+ longitude: res.result?.geometry.location.lng,
141
+ description: loc.description,
142
+ });
143
+ setChanged(false);
144
+ setPrediction([]);
145
+ };
146
+
147
+ return (
148
+ <View style={{ zIndex: 100 }}>
149
+ {renderInput ? (
150
+ renderInput({
151
+ onFocus: () => search(value),
152
+ onBlur: () => setPrediction([]),
153
+ value: changed ? value : location?.description || value,
154
+ onChangeText: (val) => {
155
+ setChanged(true);
156
+ setValue(val);
157
+ search(val);
158
+ },
159
+ clear,
160
+ locateMe,
161
+ })
162
+ ) : (
163
+ <TextField
164
+ label={label}
165
+ onChangeText={(val) => {
166
+ setChanged(true);
167
+ setValue(val);
168
+ search(val);
169
+ }}
170
+ onBlur={() => {
171
+ setPrediction([]);
172
+ }}
173
+ onFocus={() => {
174
+ search(value);
175
+ }}
176
+ value={changed ? value : location?.description || value}
177
+ gutterBottom={gutterBottom}
178
+ error={error}
179
+ helperText={helperText}
180
+ variant={variant}
181
+ end={
182
+ <View style={{ flexDirection: "row" }}>
183
+ <TouchableOpacity onPress={locateMe} style={{ marginRight: 10 }}>
184
+ <Ionicons
185
+ color={colors.primary.main}
186
+ size={18}
187
+ name="location"
188
+ />
189
+ </TouchableOpacity>
190
+ <TouchableOpacity onPress={clear}>
191
+ <Ionicons color={colors.dark.main} size={18} name="close" />
192
+ </TouchableOpacity>
193
+ </View>
194
+ }
195
+ />
196
+ )}
197
+
198
+ {prediction.length > 0 && (
199
+ <View style={styles.list}>
200
+ {prediction.map(
201
+ (cur, i) =>
202
+ i < 5 && (
203
+ <ListItem
204
+ divider={i < prediction.length - 1}
205
+ key={cur.id}
206
+ link
207
+ onPress={() => locationPressed(cur)}
208
+ >
209
+ <Ionicons
210
+ name="location-outline"
211
+ style={{ marginRight: 10 }}
212
+ size={16}
213
+ color={colors.textSecondary.main}
214
+ />
215
+ <Typography style={{ flex: 1 }}>{cur.description}</Typography>
216
+ </ListItem>
217
+ )
218
+ )}
219
+ </View>
220
+ )}
221
+ </View>
222
+ );
223
+ };
224
+
225
+ export default Locator;
@@ -0,0 +1,99 @@
1
+ import { Modal, Pressable, StyleSheet, View } from "react-native";
2
+
3
+ import React, { useState } from "react";
4
+ import { ScaledSheet } from "react-native-size-matters";
5
+ import { useColors } from "../hooks";
6
+ import { PopupProps } from "../types";
7
+ import { IconButton } from "./Button";
8
+ import Typography from "./Typography";
9
+
10
+ const Popup: React.FC<PopupProps> = ({
11
+ title,
12
+ sheet,
13
+ bare = false,
14
+ children,
15
+ open,
16
+ onClose = () => {},
17
+ }) => {
18
+ const colors = useColors();
19
+ const [show, setShow] = useState(open);
20
+
21
+ const styles: any = ScaledSheet.create({
22
+ container: {
23
+ marginTop: sheet ? "auto" : "50%",
24
+ paddingBottom: sheet ? "30@ms" : 0,
25
+ minHeight: sheet,
26
+ maxHeight: "80%",
27
+ backgroundColor: colors.white[2],
28
+ borderTopLeftRadius: 20,
29
+ borderTopRightRadius: 20,
30
+ borderBottomRightRadius: sheet ? 0 : 20,
31
+ borderBottomLeftRadius: sheet ? 0 : 20,
32
+ borderColor: colors.white[5],
33
+ borderWidth: 0,
34
+ alignSelf: "center",
35
+ maxWidth: sheet ? undefined : "90%",
36
+ width: sheet ? "100%" : undefined,
37
+ },
38
+ content: {
39
+ paddingHorizontal: bare ? undefined : "10@ms",
40
+ // flex: 1,
41
+ },
42
+ title: {
43
+ flexDirection: "row",
44
+ alignItems: "center",
45
+ paddingVertical: "5@ms",
46
+ paddingHorizontal: "10@ms",
47
+ marginBottom: "10@ms",
48
+ },
49
+ backdrop: {
50
+ position: "absolute",
51
+ height: "100%",
52
+ width: "100%",
53
+ zIndex: 10000,
54
+ backgroundColor: "#000b",
55
+ },
56
+ });
57
+
58
+ React.useEffect(() => {
59
+ setShow(open);
60
+ }, [open]);
61
+
62
+ const closeAction = () => {
63
+ setShow(false);
64
+ onClose();
65
+ };
66
+
67
+ return (
68
+ <>
69
+ {open && <Pressable style={styles.backdrop} />}
70
+ <Modal
71
+ transparent
72
+ animationType="slide"
73
+ visible={show}
74
+ onRequestClose={() => setShow(false)}
75
+ >
76
+ {open && (
77
+ <Pressable style={StyleSheet.absoluteFill} onPress={closeAction} />
78
+ )}
79
+
80
+ <View style={styles.container}>
81
+ {!bare && (
82
+ <View style={styles.title}>
83
+ <IconButton size={20} icon="close" onPress={closeAction} />
84
+ <View style={{ flex: 1 }}>
85
+ <Typography color="textSecondary" align="center">
86
+ {title}
87
+ </Typography>
88
+ </View>
89
+ </View>
90
+ )}
91
+
92
+ <View style={styles.content}>{children}</View>
93
+ </View>
94
+ </Modal>
95
+ </>
96
+ );
97
+ };
98
+
99
+ export default Popup;
@@ -1,12 +1,13 @@
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
1
  import { MaterialIcons } from "@expo/vector-icons";
6
- import Button from "./Button";
2
+ import React, { useCallback, useState } from "react";
3
+ import { FlatList, Modal, TouchableOpacity, View } from "react-native";
4
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
5
+ import { ScaledSheet } from "react-native-size-matters";
7
6
  import { useColors } from "../hooks";
8
7
  import { SelectMenuProps } from "../types";
9
- import { useSafeAreaInsets } from "react-native-safe-area-context";
8
+ import Button from "./Button";
9
+ import TextField from "./TextField";
10
+ import Typography from "./Typography";
10
11
 
11
12
  const SelectMenu: React.FC<SelectMenuProps> = ({
12
13
  open = false,
@@ -21,6 +22,8 @@ const SelectMenu: React.FC<SelectMenuProps> = ({
21
22
  }) => {
22
23
  const colors = useColors();
23
24
  const { bottom } = useSafeAreaInsets();
25
+
26
+ const [search, setSearch] = useState("");
24
27
  const styles: any = ScaledSheet.create({
25
28
  root: {
26
29
  backgroundColor: colors.white[1],
@@ -35,7 +38,6 @@ const SelectMenu: React.FC<SelectMenuProps> = ({
35
38
  marginBottom: "20@vs",
36
39
  },
37
40
 
38
- options: {},
39
41
  option: {
40
42
  paddingHorizontal: "10@s",
41
43
  paddingVertical: "10@vs",
@@ -50,77 +52,89 @@ const SelectMenu: React.FC<SelectMenuProps> = ({
50
52
  paddingTop: "15@ms",
51
53
  },
52
54
  });
55
+
56
+ const renderItem = useCallback(
57
+ ({ item }: any) => (
58
+ <TouchableOpacity
59
+ style={{
60
+ ...styles.option,
61
+ backgroundColor:
62
+ item.value === value ? colors.blue.light + "2" : colors.white[2],
63
+ }}
64
+ onPress={() => {
65
+ onChange(item.value);
66
+ if (!disableAutoClose) onClose();
67
+ }}
68
+ key={item.label}
69
+ >
70
+ {item.start && <View style={{ marginRight: 10 }}>{item.start}</View>}
71
+ <View style={{ flex: 1 }}>
72
+ <Typography
73
+ style={{
74
+ color: item.value === value ? colors.blue.light : colors.black[2],
75
+ }}
76
+ >
77
+ {item.label}
78
+ </Typography>
79
+ {item.secondary ? (
80
+ <Typography
81
+ variant="body2"
82
+ style={{
83
+ marginTop: 2,
84
+ color:
85
+ item.value === value ? colors.blue.light : colors.white[5],
86
+ }}
87
+ >
88
+ {item.secondary}
89
+ </Typography>
90
+ ) : null}
91
+ </View>
92
+ {value === item.value && (
93
+ <MaterialIcons
94
+ name="check"
95
+ color={colors.blue.light}
96
+ size={24}
97
+ style={{ marginLeft: "auto" }}
98
+ />
99
+ )}
100
+ </TouchableOpacity>
101
+ ),
102
+ [value, colors]
103
+ );
53
104
  return (
54
105
  <Modal visible={open} animationType="slide" onRequestClose={onClose}>
55
106
  <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>
107
+ <View style={styles.content}>
108
+ <View style={styles.header}>
109
+ <Typography variant="h5" gutterBottom={5} fontWeight={700}>
110
+ {label}
111
+ </Typography>
112
+ {helperText ? (
113
+ <Typography variant="body2" color="textSecondary">
114
+ {helperText}
115
+ </Typography>
116
+ ) : null}
117
+
118
+ <TextField
119
+ label="Search"
120
+ value={search}
121
+ type="search"
122
+ onChangeText={setSearch}
123
+ variant="outlined"
124
+ />
125
+ </View>
126
+ <FlatList
127
+ removeClippedSubviews
128
+ keyExtractor={(item) => item.value}
129
+ renderItem={renderItem}
130
+ data={options
131
+ .filter((item) =>
132
+ search.length > 1
133
+ ? item.label.toLowerCase().indexOf(search.toLowerCase()) > -1
134
+ : item
135
+ )
136
+ .sort((a, b) => a.label.localeCompare(b.label))}
137
+ />
124
138
  </View>
125
139
  <View style={styles.footer}>
126
140
  <Button
@@ -1,9 +1,10 @@
1
1
  import { Ionicons, MaterialIcons } from "@expo/vector-icons";
2
- import React, { useRef, useState } from "react";
2
+ import React, { startTransition, useRef, useState } from "react";
3
3
  import { Animated, TextInput, TouchableOpacity, View } from "react-native";
4
4
  import {
5
5
  ScaledSheet,
6
6
  moderateScale,
7
+ ms,
7
8
  verticalScale,
8
9
  } from "react-native-size-matters";
9
10
  import { useColors } from "../hooks";
@@ -15,7 +16,7 @@ const TextField: React.FC<TextFieldProps> = ({
15
16
  label,
16
17
  keyboardType,
17
18
  variant,
18
- color = "dark",
19
+ color = "primary",
19
20
  value,
20
21
  type,
21
22
  helperText,
@@ -44,13 +45,13 @@ const TextField: React.FC<TextFieldProps> = ({
44
45
  React.useEffect(() => {
45
46
  if (focused || value) {
46
47
  Animated.timing(labelAnim, {
47
- toValue: variant === "outlined" ? verticalScale(-12) : verticalScale(5),
48
+ toValue: verticalScale(variant === "text" ? 2 : 4),
48
49
  duration: 300,
49
50
  useNativeDriver: false,
50
51
  }).start();
51
52
  } else {
52
53
  Animated.timing(labelAnim, {
53
- toValue: height / moderateScale(variant === "text" ? 2 : 3.2),
54
+ toValue: height / moderateScale(variant === "text" ? 2.5 : 3.2),
54
55
  duration: 300,
55
56
  useNativeDriver: false,
56
57
  }).start();
@@ -77,9 +78,9 @@ const TextField: React.FC<TextFieldProps> = ({
77
78
  ? colors.error.main
78
79
  : focused
79
80
  ? colors[color].main
80
- : colors.black[1],
81
- borderWidth: error ? 1 : variant === "outlined" ? (focused ? 2 : 1) : 0,
82
- borderBottomWidth: variant === "text" ? 0.5 : error ? 1 : 0,
81
+ : colors.textSecondary.main,
82
+ borderWidth: error ? 1 : variant === "outlined" ? (focused ? 2 : 0.5) : 0,
83
+ borderBottomWidth: variant === "text" ? 0.5 : undefined,
83
84
  width: "100%",
84
85
  borderRadius: variant === "text" ? 0 : rounded ? 30 : 7,
85
86
  alignItems: "center",
@@ -89,27 +90,29 @@ const TextField: React.FC<TextFieldProps> = ({
89
90
  fontSize: "14@s",
90
91
  flex: 1,
91
92
  alignSelf: "stretch",
92
- paddingLeft: moderateScale(15),
93
+ paddingLeft: variant === "text" ? 0 : moderateScale(15),
93
94
  paddingRight: moderateScale(10),
94
- paddingTop: variant !== "outlined" ? "11@vs" : 0,
95
+ paddingTop: "11@vs",
95
96
  color: colors.black[1],
96
97
  zIndex: 10,
97
98
  // backgroundColor: "#284",
98
99
  },
99
100
  inputText: {
100
101
  fontSize: "14@ms",
101
- paddingLeft: moderateScale(15),
102
- paddingTop: variant !== "outlined" ? "13@ms" : 0,
102
+ flex: 1,
103
+ paddingLeft: variant === "text" ? 0 : moderateScale(15),
104
+ paddingTop: "13@ms",
103
105
  },
104
106
  label: {
105
107
  position: "absolute",
106
- left: moderateScale(15),
108
+ left: variant === "text" ? 0 : moderateScale(15),
107
109
  fontSize: focused || value ? "10@s" : "13@s",
108
- color: focused ? colors[color].main : colors.black[1],
110
+ color: focused ? colors[color].main : colors.textSecondary.main,
109
111
  },
110
112
  helperText: {
111
113
  paddingHorizontal: "15@s",
112
- color: focused ? colors[color].dark : colors.black[1],
114
+ flex: 1,
115
+ color: focused ? colors[color].dark : colors.textSecondary.main,
113
116
  paddingTop: "4@ms",
114
117
  },
115
118
  error: {
@@ -167,9 +170,24 @@ const TextField: React.FC<TextFieldProps> = ({
167
170
  </Animated.Text>
168
171
  {start}
169
172
  {options ? (
170
- <Typography style={styles.inputText}>
171
- {options.find((cur) => cur.value === value)?.label}
172
- </Typography>
173
+ <View
174
+ style={{ flex: 1, alignItems: "center", flexDirection: "row" }}
175
+ >
176
+ {options.find((cur) => cur.value === value)?.start && (
177
+ <View
178
+ style={{
179
+ paddingTop: variant !== "outlined" ? ms(13) : 0,
180
+ paddingRight: 10,
181
+ }}
182
+ >
183
+ {options.find((cur) => cur.value === value)?.start}
184
+ </View>
185
+ )}
186
+
187
+ <Typography style={styles.inputText}>
188
+ {options.find((cur) => cur.value === value)?.label}
189
+ </Typography>
190
+ </View>
173
191
  ) : (
174
192
  <TextInput
175
193
  onFocus={() => {
@@ -192,6 +210,15 @@ const TextField: React.FC<TextFieldProps> = ({
192
210
  />
193
211
  )}
194
212
  {end && <View style={{ marginRight: 20 }}>{end}</View>}
213
+ {options && (
214
+ <View style={{ marginRight: 20 }}>
215
+ <Ionicons
216
+ name="chevron-down"
217
+ color={colors.textSecondary.main}
218
+ size={24}
219
+ />
220
+ </View>
221
+ )}
195
222
  </TouchableOpacity>
196
223
  {helperText && (
197
224
  <Typography
@@ -250,12 +277,18 @@ export const TextField2: React.FC<TextFieldProps> = ({
250
277
  ...props
251
278
  }) => {
252
279
  const colors = useColors();
253
- const [focused, setFocused] = useState(false);
280
+ const [focused, _setFocused] = useState(false);
254
281
 
255
282
  const labelAnim = useRef(new Animated.Value(0)).current;
256
283
 
257
284
  const height = moderateScale(50);
258
285
 
286
+ const setFocused = (value: boolean) => {
287
+ startTransition(() => {
288
+ _setFocused(value);
289
+ });
290
+ };
291
+
259
292
  React.useEffect(() => {
260
293
  if (focused || value) {
261
294
  Animated.timing(labelAnim, {
@@ -374,7 +407,6 @@ export const TextField2: React.FC<TextFieldProps> = ({
374
407
  <Typography style={styles.inputText}>
375
408
  {options.find((cur) => cur.value === value)?.label}
376
409
  </Typography>
377
-
378
410
  <Ionicons
379
411
  name="chevron-down"
380
412
  size={24}
@@ -0,0 +1,13 @@
1
+ type apikeys = {
2
+ GOOGLE_MAP_API_KEY?: string;
3
+ };
4
+
5
+ let apiKey: apikeys;
6
+
7
+ export function setApiKey(key: apikeys): void {
8
+ apiKey = key;
9
+ }
10
+
11
+ export function getApiKey(): apikeys {
12
+ return apiKey;
13
+ }
@@ -0,0 +1,39 @@
1
+ // import * as fs from "fs";
2
+ import { setApiKey } from "./KeyManager";
3
+
4
+ type configProps = {
5
+ googleMapApiKey?: string;
6
+ colors?: {
7
+ primary?: {
8
+ mains: string;
9
+ light: string;
10
+ dark: string;
11
+ text: string;
12
+ };
13
+ };
14
+ };
15
+
16
+ export function initialize(config: configProps): void {
17
+ try {
18
+ setApiKey({
19
+ GOOGLE_MAP_API_KEY: config.googleMapApiKey,
20
+ });
21
+
22
+ console.log("Got key from frontend", config.googleMapApiKey);
23
+ } catch (error) {
24
+ console.error("Error reading the config file:", error);
25
+ }
26
+ }
27
+ // export function loadConfig(): void {
28
+ // try {
29
+ // const configData = fs.readFileSync("./hui-config.json", "utf-8");
30
+ // const config: configProps = JSON.parse(configData);
31
+ // setApiKey({
32
+ // GOOGLE_MAP_API_KEY: config.googleMapApiKey,
33
+ // });
34
+
35
+ // console.log("Got key from frontend", config.googleMapApiKey);
36
+ // } catch (error) {
37
+ // console.error("Error reading the config file:", error);
38
+ // }
39
+ // }
package/src/hooks.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { useContext } from "react";
2
2
  import { Platform } from "react-native";
3
- import { ms, vs } from "react-native-size-matters";
4
- import colors from "./theme/colors";
3
+ import { vs } from "react-native-size-matters";
5
4
  import { UIThemeContext } from "./theme";
5
+ import colors from "./theme/colors";
6
6
 
7
7
  export const useColors = () => {
8
8
  const { themeState } = useContext(UIThemeContext);
@@ -26,7 +26,7 @@ const darkColors = {
26
26
  5: "#aaa",
27
27
  },
28
28
  white: {
29
- 1: "#111",
29
+ 1: "#000",
30
30
  2: "#222",
31
31
  3: "#444",
32
32
  4: "#333",
@@ -39,17 +39,17 @@ const darkColors = {
39
39
  text: "#000",
40
40
  },
41
41
  light: {
42
- main: "#000",
42
+ main: "#111",
43
43
  light: "#555",
44
44
  dark: "#333",
45
45
  text: "#fff",
46
46
  },
47
-
47
+ grey: {
48
+ dark: "#d0d8d8",
49
+ main: "#e4e0e4",
50
+ },
48
51
  textSecondary: {
49
- main: "#777",
50
- light: "#aaa",
51
- dark: "#555",
52
- text: "#fff",
52
+ main: "#666",
53
53
  },
54
54
  secondary: {
55
55
  main: "#a00",
@@ -69,11 +69,11 @@ export default function colors(theme: ThemeTypes) {
69
69
  const dynamicColors = theme === "dark" ? darkColors : lightColors;
70
70
  return {
71
71
  primary: {
72
- main: "#FD7",
72
+ main: "#f80",
73
73
  light: "#FEFFD3",
74
74
  dark: "#fa0",
75
75
  orange: "#F68B1E",
76
- text: "#000",
76
+ text: "#fff",
77
77
  },
78
78
  secondary: {
79
79
  main: "#f11",
@@ -98,18 +98,21 @@ export default function colors(theme: ThemeTypes) {
98
98
  light: "#777",
99
99
  dark: "#111",
100
100
  text: "#fff",
101
+ mid: "#f2f3f4",
101
102
  },
102
103
  textSecondary: {
103
104
  main: "#aaa",
104
- light: "#bbb",
105
- dark: "#777",
106
- text: "#000",
105
+ mid: "#9ab",
106
+ dark: "#678",
107
+ darkBlue: "#123",
107
108
  },
108
109
  blue: {
109
110
  main: "#09F",
110
111
  light: "#39f",
111
112
  dark: "#028",
112
113
  text: "#fff",
114
+ navy: "#071440",
115
+ soft: "#EBF2FF",
113
116
  },
114
117
  info: {
115
118
  main: "#09f",
@@ -122,6 +125,9 @@ export default function colors(theme: ThemeTypes) {
122
125
  text: "#fff",
123
126
  light: "#5c3",
124
127
  dark: "#062",
128
+
129
+ green: "#49D3BA",
130
+ lighter: "#00A86B",
125
131
  },
126
132
 
127
133
  warning: {
@@ -135,6 +141,13 @@ export default function colors(theme: ThemeTypes) {
135
141
  text: "#fff",
136
142
  light: "#f43",
137
143
  dark: "#a20",
144
+ red: "#D92D20",
145
+ soft: "#fee",
146
+ bold: "#d22",
147
+ },
148
+ grey: {
149
+ dark: "#101828",
150
+ main: "#344054",
138
151
  },
139
152
  ...dynamicColors,
140
153
  };
package/src/types.ts CHANGED
@@ -200,7 +200,12 @@ export interface TextFieldProps extends TextInputProps {
200
200
  inputStyles?: any;
201
201
  gutterBottom?: number;
202
202
  end?: ReactNode;
203
- options?: { secondary?: string; value: string | number; label: string }[];
203
+ options?: {
204
+ start?: ReactNode;
205
+ secondary?: string;
206
+ value: string | number;
207
+ label: string;
208
+ }[];
204
209
  onFocus?: () => void;
205
210
  onBlur?: () => void;
206
211
  }
@@ -233,9 +238,9 @@ export interface SafeAreaViewProps {
233
238
  export interface SelectMenuProps {
234
239
  open: boolean;
235
240
  onClose: () => void;
236
- value: string | number;
237
- options: { secondary?: string; value: string | number; label: string }[];
238
- onChange: (value: string | number) => void;
241
+ value: any;
242
+ options: { secondary?: string; value: any; label: string }[];
243
+ onChange: (value: string) => void;
239
244
  disableAutoClose?: boolean;
240
245
  label?: string;
241
246
  secondary?: string;