ripal-ui 1.1.394 → 2.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/README.md +3 -0
- package/components/Alert.jsx +74 -0
- package/components/Avatar.jsx +157 -0
- package/components/Button.jsx +58 -0
- package/components/COLORS.js +138 -0
- package/components/Checkbox.jsx +51 -0
- package/components/Dialog.jsx +221 -0
- package/components/Divider.jsx +62 -0
- package/components/Dropdown.jsx +229 -0
- package/components/Inline.jsx +37 -0
- package/components/Input.jsx +89 -0
- package/components/Rate.jsx +39 -0
- package/components/Slider.jsx +219 -0
- package/components/Switch.jsx +45 -0
- package/components/Table.jsx +67 -0
- package/components/Text.jsx +56 -0
- package/components/Toggle.jsx +88 -0
- package/index.js +16 -2
- package/package.json +20 -20
- package/babel.config.js +0 -3
- package/components/BottomSheet.tsx +0 -197
- package/components/Carousel.tsx +0 -61
- package/components/Circle.tsx +0 -44
- package/components/DatePicker.jsx +0 -181
- package/components/Tab.tsx +0 -90
- package/components/Table.tsx +0 -95
- package/components/index.ts +0 -5
- package/config.js +0 -4
- package/dist/BottomSheet.js +0 -186
- package/dist/Button.js +0 -109
- package/dist/Carousel.js +0 -52
- package/dist/Circle.js +0 -42
- package/dist/DatePicker.js +0 -199
- package/dist/Dialog.js +0 -81
- package/dist/Dropdown.js +0 -97
- package/dist/Inline.js +0 -38
- package/dist/Input.js +0 -88
- package/dist/ProgressBar.js +0 -64
- package/dist/Separator.js +0 -47
- package/dist/Skeleton.js +0 -62
- package/dist/Switch.js +0 -74
- package/dist/Tab.js +0 -85
- package/dist/Table.js +0 -96
- package/dist/Text.js +0 -78
- package/dist/Toast.js +0 -72
- package/dist/Toggle.js +0 -54
- package/dist/index.js +0 -96
- package/elements/Button.tsx +0 -121
- package/elements/ColorPicker.tsx +0 -70
- package/elements/Dialog.tsx +0 -87
- package/elements/Dropdown.tsx +0 -88
- package/elements/Inline.tsx +0 -52
- package/elements/Input.tsx +0 -83
- package/elements/ProgressBar.tsx +0 -52
- package/elements/SecureStorage.js +0 -27
- package/elements/Separator.tsx +0 -71
- package/elements/Skeleton.tsx +0 -64
- package/elements/Slider.tsx +0 -133
- package/elements/Switch.tsx +0 -63
- package/elements/Text.tsx +0 -95
- package/elements/Toast.tsx +0 -71
- package/elements/Toggle.tsx +0 -59
- package/elements/index.js +0 -14
- package/index.d.ts +0 -237
- package/scripts/generateConfig.js +0 -80
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, useState } from "react";
|
|
2
|
+
import { Keyboard, KeyboardAvoidingView, Modal, Platform, Pressable, StyleSheet, TouchableWithoutFeedback, View } from "react-native";
|
|
3
|
+
import Text from "./Text";
|
|
4
|
+
import Inline from "./Inline";
|
|
5
|
+
import COLORS from "./COLORS";
|
|
6
|
+
import Button from "./Button";
|
|
7
|
+
import MaterialIcons from "@react-native-vector-icons/material-icons";
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} DialogProps
|
|
10
|
+
* @property {boolean} [visible] - State to control dialog visibility (useState)
|
|
11
|
+
* @property {void} [setVisible] - Function to mutate the visible state (useState)
|
|
12
|
+
* @property {void} [onClose] - Close event
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} BodyProps
|
|
17
|
+
* @property {int|null} [gap] - Flex gap inside the dialog's body
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} TitleProps
|
|
22
|
+
* @property {bool|null} [withClose] - Set to false to hide close button
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} ActionProps
|
|
27
|
+
* @property {int|null} [gap] - Flex gap inside the dialog's body
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const DialogContext = createContext(null);
|
|
31
|
+
|
|
32
|
+
const useDialog = () => {
|
|
33
|
+
const ctx = useContext(DialogContext);
|
|
34
|
+
if (!ctx) throw new Error("Dialog subcomponents must be used inside Dialog");
|
|
35
|
+
return ctx;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {DialogProps} props
|
|
40
|
+
*/
|
|
41
|
+
const Dialog = ({
|
|
42
|
+
children,
|
|
43
|
+
visible = false,
|
|
44
|
+
onClose,
|
|
45
|
+
setVisible = null,
|
|
46
|
+
keyboardOffset = 0
|
|
47
|
+
}) => {
|
|
48
|
+
|
|
49
|
+
const [isVisible, setIsVisible] = useState(visible);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
setIsVisible(visible);
|
|
53
|
+
}, [visible]);
|
|
54
|
+
|
|
55
|
+
const closeHandler = () => {
|
|
56
|
+
const next = false;
|
|
57
|
+
|
|
58
|
+
if (onClose) onClose();
|
|
59
|
+
if (setVisible) setVisible(next);
|
|
60
|
+
|
|
61
|
+
setIsVisible(next);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<DialogContext.Provider
|
|
66
|
+
value={{
|
|
67
|
+
visible: isVisible,
|
|
68
|
+
close: closeHandler,
|
|
69
|
+
setVisible
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<Modal
|
|
73
|
+
transparent
|
|
74
|
+
visible={isVisible}
|
|
75
|
+
onRequestClose={closeHandler}
|
|
76
|
+
statusBarTranslucent
|
|
77
|
+
animationType="fade"
|
|
78
|
+
>
|
|
79
|
+
<KeyboardAvoidingView
|
|
80
|
+
style={styles.wrapper}
|
|
81
|
+
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
82
|
+
keyboardVerticalOffset={keyboardOffset}
|
|
83
|
+
>
|
|
84
|
+
|
|
85
|
+
{/* tap outside dialog */}
|
|
86
|
+
<Pressable
|
|
87
|
+
style={StyleSheet.absoluteFill}
|
|
88
|
+
onPress={closeHandler}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
{/* dismiss keyboard when tap inside empty area */}
|
|
92
|
+
<TouchableWithoutFeedback onPress={() => {
|
|
93
|
+
Keyboard.dismiss();
|
|
94
|
+
closeHandler();
|
|
95
|
+
}}>
|
|
96
|
+
<View style={styles.center}>
|
|
97
|
+
<Pressable
|
|
98
|
+
style={styles.container}
|
|
99
|
+
onPress={(e) => e.stopPropagation()}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</Pressable>
|
|
103
|
+
</View>
|
|
104
|
+
</TouchableWithoutFeedback>
|
|
105
|
+
|
|
106
|
+
</KeyboardAvoidingView>
|
|
107
|
+
</Modal>
|
|
108
|
+
</DialogContext.Provider>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {BodyProps} props
|
|
114
|
+
*/
|
|
115
|
+
Dialog.Body = ({ children, gap = 20 }) => {
|
|
116
|
+
const { close } = useDialog();
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<View style={{padding: 20, gap: gap }}>
|
|
120
|
+
{children}
|
|
121
|
+
</View>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {TitleProps} props
|
|
127
|
+
*/
|
|
128
|
+
Dialog.Title = ({ children, withClose = true }) => {
|
|
129
|
+
const { close } = useDialog();
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<Inline style={{padding: 20}}>
|
|
133
|
+
{
|
|
134
|
+
typeof children === "string"
|
|
135
|
+
? (
|
|
136
|
+
<Text
|
|
137
|
+
size={20}
|
|
138
|
+
color={COLORS.slate[800]}
|
|
139
|
+
style={styles.title_text}
|
|
140
|
+
>
|
|
141
|
+
{children}
|
|
142
|
+
</Text>
|
|
143
|
+
)
|
|
144
|
+
: (
|
|
145
|
+
<View style={{ gap: 5, flexGrow: 1 }}>
|
|
146
|
+
{children}
|
|
147
|
+
</View>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
{withClose && (
|
|
152
|
+
<Button
|
|
153
|
+
circle
|
|
154
|
+
color={`${COLORS.slate[200]}aa`}
|
|
155
|
+
onPress={close}
|
|
156
|
+
>
|
|
157
|
+
<MaterialIcons name="close" />
|
|
158
|
+
</Button>
|
|
159
|
+
)}
|
|
160
|
+
</Inline>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {ActionProps} props
|
|
166
|
+
*/
|
|
167
|
+
Dialog.Actions = ({ children, gap = 10 }) => {
|
|
168
|
+
return (
|
|
169
|
+
<Inline style={[styles.action_area]} gap={gap}>
|
|
170
|
+
{children}
|
|
171
|
+
</Inline>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const styles = StyleSheet.create({
|
|
176
|
+
wrapperss: {
|
|
177
|
+
alignItems: 'center',
|
|
178
|
+
justifyContent: 'center',
|
|
179
|
+
flexGrow: 1,
|
|
180
|
+
position: 'absolute',
|
|
181
|
+
top: 0,left: 0,right: 0,bottom: 0,
|
|
182
|
+
backgroundColor: '#00000080',
|
|
183
|
+
padding: 20,
|
|
184
|
+
},
|
|
185
|
+
wrapper: {
|
|
186
|
+
flex: 1,
|
|
187
|
+
backgroundColor: '#00000080',
|
|
188
|
+
},
|
|
189
|
+
center: {
|
|
190
|
+
flex: 1,
|
|
191
|
+
justifyContent: "center",
|
|
192
|
+
alignItems: "center",
|
|
193
|
+
padding: 20,
|
|
194
|
+
},
|
|
195
|
+
container: {
|
|
196
|
+
backgroundColor: "#fff",
|
|
197
|
+
borderRadius: 14,
|
|
198
|
+
width: "100%",
|
|
199
|
+
maxWidth: 500,
|
|
200
|
+
},
|
|
201
|
+
containerss: {
|
|
202
|
+
backgroundColor: '#fff',
|
|
203
|
+
padding: 0,
|
|
204
|
+
flexGrow: 1,
|
|
205
|
+
borderRadius: 14,
|
|
206
|
+
},
|
|
207
|
+
title_text: {
|
|
208
|
+
fontWeight: '700',
|
|
209
|
+
flexGrow: 1,
|
|
210
|
+
},
|
|
211
|
+
action_area: {
|
|
212
|
+
padding: 20,
|
|
213
|
+
justifyContent: 'flex-end',
|
|
214
|
+
backgroundColor: COLORS.slate[100],
|
|
215
|
+
borderBottomLeftRadius: 14,
|
|
216
|
+
borderBottomRightRadius: 14,
|
|
217
|
+
marginTop: 20,
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
export default Dialog;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StyleSheet, TouchableOpacity, View } from "react-native";
|
|
3
|
+
import Text from "./Text";
|
|
4
|
+
import Inline from "./Inline";
|
|
5
|
+
import COLORS from "./COLORS";
|
|
6
|
+
|
|
7
|
+
const Divider = ({label = null, labelBackgroundColor = '#fff', labelStyle, labelPosition = "center", labelColor = COLORS.slate[500], height = 0.5, color = COLORS.slate[300]}) => {
|
|
8
|
+
const positionToFlex = {
|
|
9
|
+
left: "flex-start",
|
|
10
|
+
center: "center",
|
|
11
|
+
right: "flex-end"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Inline style={styles.container}>
|
|
16
|
+
<View style={[
|
|
17
|
+
styles.line,
|
|
18
|
+
{
|
|
19
|
+
backgroundColor: color,
|
|
20
|
+
height,
|
|
21
|
+
}
|
|
22
|
+
]} />
|
|
23
|
+
{
|
|
24
|
+
label !== null &&
|
|
25
|
+
<Inline style={styles.label_container} justifyContent={positionToFlex[labelPosition.toLowerCase()]}>
|
|
26
|
+
<Inline style={[
|
|
27
|
+
styles.label_area,
|
|
28
|
+
{
|
|
29
|
+
backgroundColor: labelBackgroundColor,
|
|
30
|
+
},
|
|
31
|
+
labelStyle
|
|
32
|
+
]}>
|
|
33
|
+
{ typeof label === "string" ?
|
|
34
|
+
<Text color={labelColor} size={12}>{label}</Text>
|
|
35
|
+
:
|
|
36
|
+
label
|
|
37
|
+
}
|
|
38
|
+
</Inline>
|
|
39
|
+
</Inline>
|
|
40
|
+
}
|
|
41
|
+
</Inline>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const styles = StyleSheet.create({
|
|
46
|
+
container: {
|
|
47
|
+
position: 'relative'
|
|
48
|
+
},
|
|
49
|
+
line: {
|
|
50
|
+
flexGrow: 1,
|
|
51
|
+
},
|
|
52
|
+
label_area: {
|
|
53
|
+
padding: 5,
|
|
54
|
+
paddingHorizontal: 15,
|
|
55
|
+
},
|
|
56
|
+
label_container: {
|
|
57
|
+
position: 'absolute',
|
|
58
|
+
left: 0,right: 0,
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export default Divider;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import React, { useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Pressable,
|
|
5
|
+
ScrollView,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
Modal,
|
|
9
|
+
Dimensions
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import { MaterialIcons } from "@react-native-vector-icons/material-icons";
|
|
12
|
+
import Inline from "./Inline";
|
|
13
|
+
import Text from "./Text";
|
|
14
|
+
import COLORS from "./COLORS";
|
|
15
|
+
|
|
16
|
+
const Dropdown = ({
|
|
17
|
+
label = "Pick one",
|
|
18
|
+
options = [],
|
|
19
|
+
objectKey = null,
|
|
20
|
+
keyExtractor = null,
|
|
21
|
+
value = null,
|
|
22
|
+
onChange = () => {},
|
|
23
|
+
labelStyle,
|
|
24
|
+
style,
|
|
25
|
+
inputStyle,
|
|
26
|
+
}) => {
|
|
27
|
+
|
|
28
|
+
const triggerRef = useRef(null);
|
|
29
|
+
const [visible, setVisible] = useState(false);
|
|
30
|
+
const [layout, setLayout] = useState(null);
|
|
31
|
+
|
|
32
|
+
const screenHeight = Dimensions.get("window").height;
|
|
33
|
+
|
|
34
|
+
const getLabel = (item) => {
|
|
35
|
+
if (item === null || item === undefined) return "";
|
|
36
|
+
if (typeof item === "string") return item;
|
|
37
|
+
if (typeof item === "object") {
|
|
38
|
+
if (objectKey && item[objectKey] !== undefined) {
|
|
39
|
+
return item[objectKey];
|
|
40
|
+
}
|
|
41
|
+
return JSON.stringify(item);
|
|
42
|
+
}
|
|
43
|
+
return String(item);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getKey = (item, index) => {
|
|
47
|
+
if (keyExtractor) return keyExtractor(item, index);
|
|
48
|
+
if (typeof item === "object" && item?.id !== undefined) {
|
|
49
|
+
return item.id.toString();
|
|
50
|
+
}
|
|
51
|
+
return index.toString();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const isSelected = (item) => {
|
|
55
|
+
if (!value) return false;
|
|
56
|
+
|
|
57
|
+
if (typeof item === "string") {
|
|
58
|
+
return value === item;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof item === "object") {
|
|
62
|
+
if (objectKey) {
|
|
63
|
+
return value?.[objectKey] === item?.[objectKey];
|
|
64
|
+
}
|
|
65
|
+
return value === item;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return false;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const openDropdown = () => {
|
|
72
|
+
if (!triggerRef.current) return;
|
|
73
|
+
|
|
74
|
+
triggerRef.current.measure((fx, fy, width, height, px, py) => {
|
|
75
|
+
setLayout({
|
|
76
|
+
x: px,
|
|
77
|
+
y: py,
|
|
78
|
+
width,
|
|
79
|
+
height
|
|
80
|
+
});
|
|
81
|
+
setVisible(true);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const closeDropdown = () => {
|
|
86
|
+
setVisible(false);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleSelect = (item) => {
|
|
90
|
+
onChange(item);
|
|
91
|
+
closeDropdown();
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const dropdownPosition = () => {
|
|
95
|
+
if (!layout) return {};
|
|
96
|
+
|
|
97
|
+
const spaceBelow = screenHeight - (layout.y + layout.height);
|
|
98
|
+
const dropdownHeight = Math.min(options.length * 44, 250);
|
|
99
|
+
|
|
100
|
+
// If not enough space below, open upward
|
|
101
|
+
if (spaceBelow < dropdownHeight) {
|
|
102
|
+
return {
|
|
103
|
+
top: layout.y - dropdownHeight,
|
|
104
|
+
left: layout.x,
|
|
105
|
+
width: layout.width,
|
|
106
|
+
maxHeight: 250
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Default open downward
|
|
111
|
+
return {
|
|
112
|
+
top: layout.y + layout.height,
|
|
113
|
+
left: layout.x,
|
|
114
|
+
width: layout.width,
|
|
115
|
+
maxHeight: 250
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<View style={styles.wrapper}>
|
|
121
|
+
<Text
|
|
122
|
+
color={COLORS.slate[800]}
|
|
123
|
+
style={[{ fontWeight: "600" }, labelStyle]}
|
|
124
|
+
>
|
|
125
|
+
{label}
|
|
126
|
+
</Text>
|
|
127
|
+
|
|
128
|
+
<Pressable
|
|
129
|
+
ref={triggerRef}
|
|
130
|
+
style={[styles.box, style]}
|
|
131
|
+
onPress={openDropdown}
|
|
132
|
+
>
|
|
133
|
+
<Inline gap={10} alignItems="center">
|
|
134
|
+
<View
|
|
135
|
+
style={[
|
|
136
|
+
{
|
|
137
|
+
minHeight: 28,
|
|
138
|
+
flex: 1,
|
|
139
|
+
justifyContent: "center"
|
|
140
|
+
},
|
|
141
|
+
inputStyle
|
|
142
|
+
]}
|
|
143
|
+
>
|
|
144
|
+
<Text>
|
|
145
|
+
{value ? getLabel(value) : "Select..."}
|
|
146
|
+
</Text>
|
|
147
|
+
</View>
|
|
148
|
+
|
|
149
|
+
<MaterialIcons
|
|
150
|
+
name={visible ? "expand-less" : "expand-more"}
|
|
151
|
+
size={20}
|
|
152
|
+
/>
|
|
153
|
+
</Inline>
|
|
154
|
+
</Pressable>
|
|
155
|
+
|
|
156
|
+
<Modal
|
|
157
|
+
transparent
|
|
158
|
+
visible={visible}
|
|
159
|
+
animationType="fade"
|
|
160
|
+
onRequestClose={closeDropdown}
|
|
161
|
+
>
|
|
162
|
+
<Pressable
|
|
163
|
+
style={styles.overlay}
|
|
164
|
+
onPress={closeDropdown}
|
|
165
|
+
>
|
|
166
|
+
{layout && (
|
|
167
|
+
<View
|
|
168
|
+
style={[
|
|
169
|
+
styles.optionArea,
|
|
170
|
+
dropdownPosition()
|
|
171
|
+
]}
|
|
172
|
+
>
|
|
173
|
+
<ScrollView>
|
|
174
|
+
{options.map((item, index) => (
|
|
175
|
+
<TouchableOpacity
|
|
176
|
+
key={index}
|
|
177
|
+
style={[
|
|
178
|
+
styles.optionItem,
|
|
179
|
+
isSelected(item) && styles.selectedItem
|
|
180
|
+
]}
|
|
181
|
+
onPress={() => handleSelect(item)}
|
|
182
|
+
>
|
|
183
|
+
<Text color={isSelected(item) ? COLORS.primary : null} style={{fontWeight: isSelected(item) ? '700' : '400'}}>
|
|
184
|
+
{getLabel(item)}
|
|
185
|
+
</Text>
|
|
186
|
+
</TouchableOpacity>
|
|
187
|
+
))}
|
|
188
|
+
</ScrollView>
|
|
189
|
+
</View>
|
|
190
|
+
)}
|
|
191
|
+
</Pressable>
|
|
192
|
+
</Modal>
|
|
193
|
+
</View>
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const styles = StyleSheet.create({
|
|
198
|
+
wrapper: {
|
|
199
|
+
gap: 8,
|
|
200
|
+
},
|
|
201
|
+
box: {
|
|
202
|
+
borderWidth: 0.5,
|
|
203
|
+
borderRadius: 12,
|
|
204
|
+
borderColor: COLORS.slate[300],
|
|
205
|
+
padding: 10
|
|
206
|
+
},
|
|
207
|
+
overlay: {
|
|
208
|
+
flex: 1
|
|
209
|
+
},
|
|
210
|
+
optionArea: {
|
|
211
|
+
position: "absolute",
|
|
212
|
+
backgroundColor: "#fff",
|
|
213
|
+
borderRadius: 12,
|
|
214
|
+
borderWidth: 1,
|
|
215
|
+
borderColor: COLORS.slate[300],
|
|
216
|
+
elevation: 10,
|
|
217
|
+
padding: 10,
|
|
218
|
+
marginTop: 10
|
|
219
|
+
},
|
|
220
|
+
optionItem: {
|
|
221
|
+
padding: 15,
|
|
222
|
+
borderRadius: 8,
|
|
223
|
+
},
|
|
224
|
+
selectedItem: {
|
|
225
|
+
backgroundColor: `${COLORS.primary}15`
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
export default Dropdown;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StyleSheet, TouchableOpacity, View } from "react-native";
|
|
3
|
+
|
|
4
|
+
const Inline = ({children, gap = 20, alignItems = 'center', justifyContent, onPress = null, onLongPress = null, style}) => {
|
|
5
|
+
let computedStyles = [
|
|
6
|
+
styles.container,
|
|
7
|
+
{
|
|
8
|
+
gap,
|
|
9
|
+
alignItems,
|
|
10
|
+
justifyContent
|
|
11
|
+
},
|
|
12
|
+
style
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
if (onPress === null && onLongPress === null) {
|
|
16
|
+
return (
|
|
17
|
+
<View style={computedStyles}>
|
|
18
|
+
{children}
|
|
19
|
+
</View>
|
|
20
|
+
)
|
|
21
|
+
} else {
|
|
22
|
+
return (
|
|
23
|
+
<TouchableOpacity onPress={onPress} onLongPress={onLongPress} style={computedStyles}>
|
|
24
|
+
{children}
|
|
25
|
+
</TouchableOpacity>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const styles = StyleSheet.create({
|
|
31
|
+
container: {
|
|
32
|
+
flexDirection: 'row',
|
|
33
|
+
// flexWrap: 'wrap'
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export default Inline;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import Inline from "./Inline";
|
|
3
|
+
import { Pressable, StyleSheet, TextInput, View } from "react-native";
|
|
4
|
+
import Text from "./Text";
|
|
5
|
+
import COLORS from "./COLORS";
|
|
6
|
+
|
|
7
|
+
const Input = ({label = 'Nama', labelStyle, style, inputStyle, value, onChangeText, onChange, keyboardType = "default", onFocus, onBlur, left = null, right = null, ...props}) => {
|
|
8
|
+
const [isFocused, setFocused] = useState(false);
|
|
9
|
+
const inputRef = useRef(null);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Pressable style={[
|
|
13
|
+
styles.container
|
|
14
|
+
]}>
|
|
15
|
+
<Text color={COLORS.slate[800]} style={[
|
|
16
|
+
{
|
|
17
|
+
fontWeight: '600',
|
|
18
|
+
},
|
|
19
|
+
labelStyle
|
|
20
|
+
]}>{label}</Text>
|
|
21
|
+
<Inline style={[
|
|
22
|
+
styles.box,
|
|
23
|
+
isFocused ? {borderColor: '#ffffff00'} : null,
|
|
24
|
+
style,
|
|
25
|
+
]} gap={10}>
|
|
26
|
+
{
|
|
27
|
+
isFocused &&
|
|
28
|
+
<Pressable style={styles.input_active} />
|
|
29
|
+
}
|
|
30
|
+
{left}
|
|
31
|
+
<TextInput
|
|
32
|
+
ref={inputRef}
|
|
33
|
+
style={[
|
|
34
|
+
{
|
|
35
|
+
height: props?.multiline ? 'auto' : 28,
|
|
36
|
+
minHeight: 28,
|
|
37
|
+
flexGrow: 1,
|
|
38
|
+
flexShrink: 1,
|
|
39
|
+
},
|
|
40
|
+
inputStyle
|
|
41
|
+
]}
|
|
42
|
+
onFocus={() => {
|
|
43
|
+
setFocused(true);
|
|
44
|
+
if (onFocus) {
|
|
45
|
+
onFocus();
|
|
46
|
+
}
|
|
47
|
+
}}
|
|
48
|
+
onBlur={() => {
|
|
49
|
+
setFocused(false);
|
|
50
|
+
if (onBlur) {
|
|
51
|
+
onBlur();
|
|
52
|
+
}
|
|
53
|
+
}}
|
|
54
|
+
value={value}
|
|
55
|
+
onChangeText={e => {
|
|
56
|
+
onChangeText(e);
|
|
57
|
+
}}
|
|
58
|
+
onChange={onChange}
|
|
59
|
+
keyboardType={keyboardType}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
{right}
|
|
63
|
+
</Inline>
|
|
64
|
+
</Pressable>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const styles = StyleSheet.create({
|
|
69
|
+
container: {
|
|
70
|
+
position: 'relative',
|
|
71
|
+
gap: 8,
|
|
72
|
+
flexGrow: 1,
|
|
73
|
+
},
|
|
74
|
+
box: {
|
|
75
|
+
borderWidth: 0.5,
|
|
76
|
+
borderRadius: 12,
|
|
77
|
+
borderColor: COLORS.slate[300],
|
|
78
|
+
padding: 10,
|
|
79
|
+
},
|
|
80
|
+
input_active: {
|
|
81
|
+
borderWidth: 4,
|
|
82
|
+
borderColor: `${COLORS.primary}40`,
|
|
83
|
+
position: 'absolute',
|
|
84
|
+
top: -3,left: -3,right: -3,bottom: -3,
|
|
85
|
+
borderRadius: 12,
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
export default Input;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StyleSheet, TouchableOpacity } from "react-native";
|
|
3
|
+
import Inline from "./Inline";
|
|
4
|
+
import MaterialIcons from "@react-native-vector-icons/material-icons";
|
|
5
|
+
import COLORS from "./COLORS";
|
|
6
|
+
|
|
7
|
+
const Rate = ({rating = 4, setRating = null, icon = null, gap = 5}) => {
|
|
8
|
+
return (
|
|
9
|
+
<Inline gap={gap}>
|
|
10
|
+
{
|
|
11
|
+
[1,1,1,1,1].map((item, i) => {
|
|
12
|
+
let color = i + 1 <= rating ? COLORS.yellow[500] : COLORS.slate[300];
|
|
13
|
+
|
|
14
|
+
if (icon !== null) {
|
|
15
|
+
return icon({
|
|
16
|
+
color
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<TouchableOpacity key={i} onPress={() => {
|
|
22
|
+
if (setRating !== null) {
|
|
23
|
+
setRating(i + 1);
|
|
24
|
+
}
|
|
25
|
+
}}>
|
|
26
|
+
<MaterialIcons name="star" size={24} color={color} />
|
|
27
|
+
</TouchableOpacity>
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
</Inline>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const styles = StyleSheet.create({
|
|
36
|
+
//
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export default Rate;
|