@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.
@@ -0,0 +1,442 @@
1
+ import { Ionicons, MaterialIcons } from "@expo/vector-icons";
2
+ import React, { useRef, useState } from "react";
3
+ import { Animated, TextInput, TouchableOpacity, View } from "react-native";
4
+ import {
5
+ ScaledSheet,
6
+ moderateScale,
7
+ verticalScale,
8
+ } from "react-native-size-matters";
9
+ import { useColors } from "../hooks";
10
+ import { TextFieldProps } from "../types";
11
+ import SelectMenu from "./SelectMenu";
12
+ import Typography from "./Typography";
13
+
14
+ const TextField: React.FC<TextFieldProps> = ({
15
+ label,
16
+ keyboardType,
17
+ variant,
18
+ color = "dark",
19
+ value,
20
+ type,
21
+ helperText,
22
+ onChangeText,
23
+ onSubmitEditing = () => {},
24
+ onFocus = () => {},
25
+ onBlur = () => {},
26
+ error,
27
+ start,
28
+ rounded,
29
+ disabled = false,
30
+ style = {},
31
+ inputStyles = {},
32
+ gutterBottom = 0,
33
+ end,
34
+ options,
35
+ ...props
36
+ }) => {
37
+ const colors = useColors();
38
+ const [focused, setFocused] = useState(false);
39
+
40
+ const labelAnim = useRef(new Animated.Value(0)).current;
41
+
42
+ const height = moderateScale(variant === "text" ? 50 : 45);
43
+
44
+ React.useEffect(() => {
45
+ if (focused || value) {
46
+ Animated.timing(labelAnim, {
47
+ toValue: variant === "outlined" ? verticalScale(-12) : verticalScale(5),
48
+ duration: 300,
49
+ useNativeDriver: false,
50
+ }).start();
51
+ } else {
52
+ Animated.timing(labelAnim, {
53
+ toValue: height / moderateScale(variant === "text" ? 2 : 3.2),
54
+ duration: 300,
55
+ useNativeDriver: false,
56
+ }).start();
57
+ }
58
+ }, [focused, value]);
59
+ const styles: any = ScaledSheet.create({
60
+ root: {
61
+ marginBottom: gutterBottom + "@vs",
62
+ width: "100%",
63
+ ...style,
64
+ },
65
+ container: {
66
+ height: height,
67
+ overflow: "hidden",
68
+
69
+ backgroundColor:
70
+ variant === "outlined" || variant === "text"
71
+ ? "#fff0"
72
+ : focused
73
+ ? colors.white[3]
74
+ : colors.white[4],
75
+ flexDirection: "row",
76
+ borderColor: error
77
+ ? colors.error.main
78
+ : focused
79
+ ? 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,
83
+ width: "100%",
84
+ borderRadius: variant === "text" ? 0 : rounded ? 30 : 7,
85
+ alignItems: "center",
86
+ ...inputStyles,
87
+ },
88
+ input: {
89
+ fontSize: "14@s",
90
+ flex: 1,
91
+ alignSelf: "stretch",
92
+ paddingLeft: moderateScale(15),
93
+ paddingRight: moderateScale(10),
94
+ paddingTop: variant !== "outlined" ? "11@vs" : 0,
95
+ color: colors.black[1],
96
+ zIndex: 10,
97
+ // backgroundColor: "#284",
98
+ },
99
+ inputText: {
100
+ fontSize: "14@ms",
101
+ paddingLeft: moderateScale(15),
102
+ paddingTop: variant !== "outlined" ? "13@ms" : 0,
103
+ },
104
+ label: {
105
+ position: "absolute",
106
+ left: moderateScale(15),
107
+ fontSize: focused || value ? "10@s" : "13@s",
108
+ color: focused ? colors[color].main : colors.black[1],
109
+ },
110
+ helperText: {
111
+ paddingHorizontal: "15@s",
112
+ color: focused ? colors[color].dark : colors.black[1],
113
+ paddingTop: "4@ms",
114
+ },
115
+ error: {
116
+ paddingLeft: 10,
117
+ paddingRight: 10,
118
+ paddingTop: 5,
119
+ flexDirection: "row",
120
+ alignItems: "center",
121
+ },
122
+ errorText: {
123
+ fontSize: 12,
124
+ marginLeft: 10,
125
+ },
126
+ });
127
+ const formProps: any =
128
+ type === "email"
129
+ ? {
130
+ textContentType: "emailAddress",
131
+ keyboardType: "email-address",
132
+ autoCapitalize: "none",
133
+ autoCompleteType: "email",
134
+ }
135
+ : type === "number"
136
+ ? {
137
+ keyboardType: "numeric",
138
+ }
139
+ : type === "tel"
140
+ ? {
141
+ textContentType: "telephoneNumber",
142
+ keyboardType: "phone-pad",
143
+ }
144
+ : type === "search"
145
+ ? {
146
+ keyboardType: "web-search",
147
+ returnKeyType: "search",
148
+ autoCapitalize: "none",
149
+ }
150
+ : type === "password"
151
+ ? {
152
+ secureTextEntry: true,
153
+ autoCompleteType: "password",
154
+ autoCapitalize: "none",
155
+ textContentType: "password",
156
+ }
157
+ : {};
158
+ return (
159
+ <>
160
+ <View style={styles.root}>
161
+ <TouchableOpacity
162
+ onPress={() => setFocused(true)}
163
+ style={styles.container}
164
+ >
165
+ <Animated.Text style={{ ...styles.label, top: labelAnim }}>
166
+ {label}
167
+ </Animated.Text>
168
+ {start}
169
+ {options ? (
170
+ <Typography style={styles.inputText}>
171
+ {options.find((cur) => cur.value === value)?.label}
172
+ </Typography>
173
+ ) : (
174
+ <TextInput
175
+ onFocus={() => {
176
+ onFocus();
177
+ setFocused(true);
178
+ }}
179
+ onBlur={() => {
180
+ onBlur();
181
+ setFocused(false);
182
+ }}
183
+ value={value}
184
+ onChangeText={onChangeText}
185
+ keyboardType={keyboardType}
186
+ editable={!disabled}
187
+ selectTextOnFocus={!disabled}
188
+ onSubmitEditing={onSubmitEditing}
189
+ {...formProps}
190
+ {...props}
191
+ style={styles.input}
192
+ />
193
+ )}
194
+ {end && <View style={{ marginRight: 20 }}>{end}</View>}
195
+ </TouchableOpacity>
196
+ {helperText && (
197
+ <Typography
198
+ color="textSecondary"
199
+ style={styles.helperText}
200
+ variant="caption"
201
+ >
202
+ {helperText}
203
+ </Typography>
204
+ )}
205
+ {error && (
206
+ <View style={styles.error}>
207
+ <MaterialIcons name="error" color={colors.error.main} size={16} />
208
+ <Typography style={styles.errorText} color="error">
209
+ {error}
210
+ </Typography>
211
+ </View>
212
+ )}
213
+ </View>
214
+ {options && (
215
+ <SelectMenu
216
+ options={options}
217
+ value={value}
218
+ open={focused}
219
+ onClose={() => setFocused(false)}
220
+ label={label}
221
+ helperText={helperText}
222
+ onChange={onChangeText!}
223
+ />
224
+ )}
225
+ </>
226
+ );
227
+ };
228
+
229
+ export const TextField2: React.FC<TextFieldProps> = ({
230
+ label,
231
+ keyboardType,
232
+ variant,
233
+ color = "primary",
234
+ value,
235
+ type,
236
+ helperText,
237
+ onChangeText,
238
+ onSubmitEditing = () => {},
239
+ onFocus = () => {},
240
+ onBlur = () => {},
241
+ error,
242
+ start,
243
+ rounded,
244
+ disabled = false,
245
+ style = {},
246
+ inputStyles = {},
247
+ gutterBottom = 8,
248
+ end,
249
+ options,
250
+ ...props
251
+ }) => {
252
+ const colors = useColors();
253
+ const [focused, setFocused] = useState(false);
254
+
255
+ const labelAnim = useRef(new Animated.Value(0)).current;
256
+
257
+ const height = moderateScale(50);
258
+
259
+ React.useEffect(() => {
260
+ if (focused || value) {
261
+ Animated.timing(labelAnim, {
262
+ toValue: verticalScale(5),
263
+ duration: 300,
264
+ useNativeDriver: false,
265
+ }).start();
266
+ } else {
267
+ Animated.timing(labelAnim, {
268
+ toValue: height / moderateScale(3),
269
+ duration: 300,
270
+ useNativeDriver: false,
271
+ }).start();
272
+ }
273
+ }, [focused, value]);
274
+ const styles: any = ScaledSheet.create({
275
+ root: {
276
+ marginBottom: gutterBottom + "@vs",
277
+ ...style,
278
+ },
279
+ container: {
280
+ height: height,
281
+ overflow: "hidden",
282
+ flexDirection: "row",
283
+ borderColor: error
284
+ ? colors.error.main
285
+ : focused
286
+ ? colors[color].main
287
+ : colors.white[5],
288
+ borderWidth: error ? 1 : focused ? 2 : 1,
289
+ width: "100%",
290
+ borderRadius: rounded ? 30 : 10,
291
+ alignItems: "center",
292
+ ...inputStyles,
293
+ },
294
+ input: {
295
+ fontSize: "14@s",
296
+ flex: 1,
297
+ alignSelf: "stretch",
298
+ paddingLeft: moderateScale(10),
299
+ paddingRight: moderateScale(10),
300
+ color: colors.dark.light,
301
+ zIndex: 10,
302
+ // backgroundColor: "#284",
303
+ },
304
+ inputText: {
305
+ fontSize: "14@ms",
306
+ color: colors.dark.light,
307
+ paddingLeft: moderateScale(10),
308
+ },
309
+ label: {},
310
+ helperText: {
311
+ paddingHorizontal: "15@s",
312
+ color: focused ? colors[color].dark : "#fffa",
313
+ paddingTop: "4@ms",
314
+ },
315
+ error: {
316
+ paddingLeft: 10,
317
+ paddingRight: 10,
318
+ paddingTop: 5,
319
+ flexDirection: "row",
320
+ alignItems: "center",
321
+ },
322
+ errorText: {
323
+ fontSize: 12,
324
+ marginLeft: 10,
325
+ },
326
+ });
327
+ const formProps: any =
328
+ type === "email"
329
+ ? {
330
+ textContentType: "emailAddress",
331
+ keyboardType: "email-address",
332
+ autoCapitalize: "none",
333
+ autoCompleteType: "email",
334
+ }
335
+ : type === "number"
336
+ ? {
337
+ keyboardType: "numeric",
338
+ }
339
+ : type === "tel"
340
+ ? {
341
+ textContentType: "telephoneNumber",
342
+ keyboardType: "phone-pad",
343
+ }
344
+ : type === "search"
345
+ ? {
346
+ keyboardType: "web-search",
347
+ returnKeyType: "search",
348
+ autoCapitalize: "none",
349
+ }
350
+ : type === "password"
351
+ ? {
352
+ secureTextEntry: true,
353
+ autoCompleteType: "password",
354
+ autoCapitalize: "none",
355
+ textContentType: "password",
356
+ }
357
+ : {};
358
+ return (
359
+ <>
360
+ <View style={styles.root}>
361
+ {label && (
362
+ <Typography variant="body1" gutterBottom={10} fontWeight={600}>
363
+ {label}
364
+ </Typography>
365
+ )}
366
+ <TouchableOpacity
367
+ onPress={() => setFocused(true)}
368
+ style={styles.container}
369
+ >
370
+ {start}
371
+
372
+ {options ? (
373
+ <>
374
+ <Typography style={styles.inputText}>
375
+ {options.find((cur) => cur.value === value)?.label}
376
+ </Typography>
377
+
378
+ <Ionicons
379
+ name="chevron-down"
380
+ size={24}
381
+ style={{ marginLeft: "auto", marginRight: 15 }}
382
+ color={colors.dark.light}
383
+ />
384
+ </>
385
+ ) : (
386
+ <TextInput
387
+ onFocus={() => {
388
+ onFocus();
389
+ setFocused(true);
390
+ }}
391
+ onBlur={() => {
392
+ onBlur();
393
+ setFocused(false);
394
+ }}
395
+ value={value}
396
+ onChangeText={onChangeText}
397
+ keyboardType={keyboardType}
398
+ editable={!disabled}
399
+ selectTextOnFocus={!disabled}
400
+ onSubmitEditing={onSubmitEditing}
401
+ {...formProps}
402
+ {...props}
403
+ style={styles.input}
404
+ />
405
+ )}
406
+
407
+ {end && <View style={{ marginRight: 20 }}>{end}</View>}
408
+ </TouchableOpacity>
409
+ {helperText && (
410
+ <Typography
411
+ color="textSecondary"
412
+ style={styles.helperText}
413
+ variant="caption"
414
+ >
415
+ {helperText}
416
+ </Typography>
417
+ )}
418
+ {error && (
419
+ <View style={styles.error}>
420
+ <MaterialIcons name="error" color={colors.error.main} size={16} />
421
+ <Typography style={styles.errorText} color="error">
422
+ {error}
423
+ </Typography>
424
+ </View>
425
+ )}
426
+ </View>
427
+ {options && (
428
+ <SelectMenu
429
+ options={options}
430
+ value={value}
431
+ open={focused}
432
+ onClose={() => setFocused(false)}
433
+ label={label}
434
+ helperText={helperText}
435
+ onChange={onChangeText!}
436
+ />
437
+ )}
438
+ </>
439
+ );
440
+ };
441
+
442
+ export default TextField;
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import { Text, StyleSheet } from "react-native";
3
+ import { moderateScale, scale, verticalScale } from "react-native-size-matters";
4
+ import { useColors } from "../hooks";
5
+ import { TypographyProps } from "../types";
6
+
7
+ const Typography: React.FC<TypographyProps> = ({
8
+ children,
9
+ color = "dark",
10
+ style = {},
11
+ textCase = null,
12
+ variant = "body1",
13
+ align = "left",
14
+ gutterBottom = 0,
15
+ fontWeight = 400,
16
+ }) => {
17
+ const colors = useColors();
18
+ const fontSize = {
19
+ h1: moderateScale(42),
20
+ h2: moderateScale(37),
21
+ h3: moderateScale(32),
22
+ h4: moderateScale(27),
23
+ h5: moderateScale(22),
24
+ h6: moderateScale(17),
25
+ body1: moderateScale(15),
26
+ body2: moderateScale(12),
27
+ caption: moderateScale(10),
28
+ };
29
+ const styles: any = StyleSheet.create({
30
+ text: {
31
+ fontSize: fontSize[variant],
32
+ marginBottom: verticalScale(gutterBottom) || 0,
33
+ color: colors[color]?.main || color,
34
+ textTransform: textCase,
35
+ alignItems: "center",
36
+ textAlign: align,
37
+ fontWeight: fontWeight.toString(),
38
+ },
39
+ });
40
+ return (
41
+ <Text adjustsFontSizeToFit style={{ ...styles.text, ...style }}>
42
+ {children}
43
+ </Text>
44
+ );
45
+ };
46
+
47
+ export default Typography;
package/src/hooks.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { useContext } from "react";
2
+ import { Platform } from "react-native";
3
+ import { ms, vs } from "react-native-size-matters";
4
+ import colors from "./theme/colors";
5
+ import { UIThemeContext } from "./theme";
6
+
7
+ export const useColors = () => {
8
+ const { themeState } = useContext(UIThemeContext);
9
+ return colors(themeState.value);
10
+ };
11
+
12
+ export const useTheme = () => {
13
+ const { themeState } = useContext(UIThemeContext);
14
+ return themeState.value;
15
+ };
16
+ export const useNavScreenOptions = (type: "stack" | "tab" | "drawer") => {
17
+ const colors = useColors();
18
+ const options = {
19
+ stack: {
20
+ headerShown: false,
21
+
22
+ headerStyle: {
23
+ backgroundColor: colors.white[1],
24
+ },
25
+ headerShadowVisible: false,
26
+ contentStyle: {
27
+ backgroundColor: colors.white[1],
28
+ },
29
+ headerTitleStyle: {
30
+ color: colors.black[1],
31
+ },
32
+ headerTintColor:
33
+ Platform.OS === "android" ? colors.black[1] : colors.blue.light,
34
+ },
35
+ tab: {
36
+ headerShown: false,
37
+ headerTintColor: colors.dark.main,
38
+ tabBarStyle: {
39
+ borderTopColor: colors.white[3],
40
+ borderTopWidth: 1,
41
+ // shadowColor: "#000",
42
+ // shadowOffset: { height: -3, width: 0 },
43
+ // shadowRadius: 7,
44
+ // shadowOpacity: 0.1,
45
+ height: vs(70),
46
+ backgroundColor: colors.white[1],
47
+ },
48
+ tabBarActiveTintColor: colors.blue.main,
49
+ tabBarInactiveTintColor: colors.textSecondary.main,
50
+ tabBarLabelStyle: {
51
+ // fontSize: ms(12),
52
+ },
53
+ },
54
+ drawer: {
55
+ headerShown: false,
56
+ drawerActiveTintColor: colors.primary.main,
57
+ drawerInactiveTintColor: colors.textSecondary.main,
58
+ sceneContainerStyle: {
59
+ backgroundColor: colors.white[2],
60
+ },
61
+ drawerStyle: {
62
+ backgroundColor: colors.white[1],
63
+ },
64
+ headerStyle: {
65
+ backgroundColor: colors.white[1],
66
+ },
67
+ headerTitleStyle: {
68
+ color: colors.dark.main,
69
+ },
70
+ },
71
+ };
72
+ return options[type];
73
+ };
@@ -0,0 +1,141 @@
1
+ import { ThemeTypes } from "../types";
2
+
3
+ const lightColors = {
4
+ white: {
5
+ 1: "#fff",
6
+ 2: "#f7f7f7",
7
+ 3: "#eee",
8
+ 4: "#ddd",
9
+ 5: "#bbb",
10
+ },
11
+ black: {
12
+ 1: "#888",
13
+ 2: "#777",
14
+ 3: "#555",
15
+ 4: "#333",
16
+ 5: "#000",
17
+ },
18
+ };
19
+
20
+ const darkColors = {
21
+ black: {
22
+ 1: "#fff",
23
+ 2: "#f7f7f7",
24
+ 3: "#eee",
25
+ 4: "#ddd",
26
+ 5: "#aaa",
27
+ },
28
+ white: {
29
+ 1: "#111",
30
+ 2: "#222",
31
+ 3: "#444",
32
+ 4: "#333",
33
+ 5: "#555",
34
+ },
35
+ dark: {
36
+ main: "#f2f3f4",
37
+ light: "#fff",
38
+ dark: "#ddd",
39
+ text: "#000",
40
+ },
41
+ light: {
42
+ main: "#000",
43
+ light: "#555",
44
+ dark: "#333",
45
+ text: "#fff",
46
+ },
47
+
48
+ textSecondary: {
49
+ main: "#777",
50
+ light: "#aaa",
51
+ dark: "#555",
52
+ text: "#fff",
53
+ },
54
+ secondary: {
55
+ main: "#a00",
56
+ light: "#a33",
57
+ dark: "#900",
58
+ text: "#fff",
59
+ },
60
+ purple: {
61
+ main: "#815",
62
+ light: "#f4d",
63
+ dark: "#a06",
64
+ text: "#fff",
65
+ },
66
+ };
67
+
68
+ export default function colors(theme: ThemeTypes) {
69
+ const dynamicColors = theme === "dark" ? darkColors : lightColors;
70
+ return {
71
+ primary: {
72
+ main: "#FD7",
73
+ light: "#FEFFD3",
74
+ dark: "#fa0",
75
+ orange: "#F68B1E",
76
+ text: "#000",
77
+ },
78
+ secondary: {
79
+ main: "#f11",
80
+ light: "#f43",
81
+ dark: "#d00",
82
+ text: "#fff",
83
+ },
84
+ purple: {
85
+ main: "#a09",
86
+ light: "#f4d",
87
+ dark: "#a06",
88
+ text: "#fff",
89
+ },
90
+ light: {
91
+ main: "#fff",
92
+ light: "#fff",
93
+ dark: "#ddd",
94
+ text: "#000",
95
+ },
96
+ dark: {
97
+ main: "#000",
98
+ light: "#777",
99
+ dark: "#111",
100
+ text: "#fff",
101
+ },
102
+ textSecondary: {
103
+ main: "#aaa",
104
+ light: "#bbb",
105
+ dark: "#777",
106
+ text: "#000",
107
+ },
108
+ blue: {
109
+ main: "#09F",
110
+ light: "#39f",
111
+ dark: "#028",
112
+ text: "#fff",
113
+ },
114
+ info: {
115
+ main: "#09f",
116
+ light: "#3af",
117
+ dark: "#08a",
118
+ text: "#fff",
119
+ },
120
+ success: {
121
+ main: "#0a4",
122
+ text: "#fff",
123
+ light: "#5c3",
124
+ dark: "#062",
125
+ },
126
+
127
+ warning: {
128
+ main: "#fa2",
129
+ light: "#fc7",
130
+ dark: "#f90",
131
+ text: "#fff",
132
+ },
133
+ error: {
134
+ main: "#D22",
135
+ text: "#fff",
136
+ light: "#f43",
137
+ dark: "#a20",
138
+ },
139
+ ...dynamicColors,
140
+ };
141
+ }