@towsila/expo-ui-library 1.0.17 → 1.0.19
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 +1 -1
- package/src/components/ui/sharpDropdown.tsx +165 -0
- package/src/index.ts +2 -1
package/package.json
CHANGED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useState, useRef } from "react";
|
|
2
|
+
import { Text, View, TouchableOpacity, Modal } from "react-native";
|
|
3
|
+
import { useAppTheme } from "../../context/ThemeContext";
|
|
4
|
+
import { Fonts, textSize } from "../../tokens/typography";
|
|
5
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
6
|
+
import SharpContainer from "./sharpContainer";
|
|
7
|
+
import ErrorMessage from "./error";
|
|
8
|
+
|
|
9
|
+
interface Option {
|
|
10
|
+
value: string;
|
|
11
|
+
label: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface SharpDropdownProps {
|
|
15
|
+
options: Option[];
|
|
16
|
+
value: string | null;
|
|
17
|
+
onChange: (value: string) => void;
|
|
18
|
+
label?: string;
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
onClearError?: () => void;
|
|
22
|
+
height?: number;
|
|
23
|
+
bgColor?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function SharpDropdown({
|
|
27
|
+
options,
|
|
28
|
+
value,
|
|
29
|
+
onChange,
|
|
30
|
+
label,
|
|
31
|
+
placeholder = "Select an option",
|
|
32
|
+
error,
|
|
33
|
+
onClearError,
|
|
34
|
+
height = 50,
|
|
35
|
+
bgColor,
|
|
36
|
+
}: SharpDropdownProps) {
|
|
37
|
+
const [open, setOpen] = useState(false);
|
|
38
|
+
const [dropdownPos, setDropdownPos] = useState({ x: 0, y: 0, width: 0 });
|
|
39
|
+
const triggerRef = useRef<View>(null);
|
|
40
|
+
const { colors } = useAppTheme();
|
|
41
|
+
|
|
42
|
+
const selectedLabel = options.find((o) => o.value === value)?.label;
|
|
43
|
+
|
|
44
|
+
const getBorderColor = () => {
|
|
45
|
+
if (error) return colors.semantic.error;
|
|
46
|
+
if (open) return colors.primary;
|
|
47
|
+
return colors.semantic.border;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleOpen = () => {
|
|
51
|
+
triggerRef.current?.measureInWindow((x, y, width, h) => {
|
|
52
|
+
setDropdownPos({ x, y: y + h + 4, width });
|
|
53
|
+
setOpen(true);
|
|
54
|
+
onClearError?.();
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<View style={{ marginBottom: 4 }}>
|
|
60
|
+
{label && (
|
|
61
|
+
<Text style={{
|
|
62
|
+
color: colors.text.primary,
|
|
63
|
+
fontSize: textSize.xs,
|
|
64
|
+
fontFamily: Fonts.montserrat.semiBold,
|
|
65
|
+
marginBottom: 6,
|
|
66
|
+
}}>
|
|
67
|
+
{label}
|
|
68
|
+
</Text>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
{/* Sharp trigger — same as SharpInput */}
|
|
72
|
+
<TouchableOpacity
|
|
73
|
+
onPress={() => open ? setOpen(false) : handleOpen()}
|
|
74
|
+
activeOpacity={0.8}
|
|
75
|
+
>
|
|
76
|
+
<View ref={triggerRef}>
|
|
77
|
+
<SharpContainer
|
|
78
|
+
height={height}
|
|
79
|
+
bgColor={bgColor ?? colors.background.main}
|
|
80
|
+
borderColor={getBorderColor()}
|
|
81
|
+
>
|
|
82
|
+
<View style={{
|
|
83
|
+
flex: 1,
|
|
84
|
+
flexDirection: "row",
|
|
85
|
+
alignItems: "center",
|
|
86
|
+
justifyContent: "space-between",
|
|
87
|
+
paddingHorizontal: 16,
|
|
88
|
+
}}>
|
|
89
|
+
<Text style={{
|
|
90
|
+
fontSize: textSize.sm,
|
|
91
|
+
fontFamily: Fonts.montserrat.regular,
|
|
92
|
+
color: selectedLabel ? colors.text.primary : colors.text.secondary,
|
|
93
|
+
}}>
|
|
94
|
+
{selectedLabel ?? placeholder}
|
|
95
|
+
</Text>
|
|
96
|
+
<Ionicons
|
|
97
|
+
name={open ? "chevron-up" : "chevron-down"}
|
|
98
|
+
size={16}
|
|
99
|
+
color={colors.text.secondary}
|
|
100
|
+
/>
|
|
101
|
+
</View>
|
|
102
|
+
</SharpContainer>
|
|
103
|
+
</View>
|
|
104
|
+
</TouchableOpacity>
|
|
105
|
+
|
|
106
|
+
{error && <ErrorMessage message={error} />}
|
|
107
|
+
|
|
108
|
+
{/* Normal rounded dropdown options */}
|
|
109
|
+
<Modal visible={open} transparent animationType="none">
|
|
110
|
+
<TouchableOpacity
|
|
111
|
+
style={{ flex: 1 }}
|
|
112
|
+
activeOpacity={1}
|
|
113
|
+
onPress={() => setOpen(false)}
|
|
114
|
+
>
|
|
115
|
+
<View style={{
|
|
116
|
+
position: "absolute",
|
|
117
|
+
top: dropdownPos.y,
|
|
118
|
+
left: dropdownPos.x,
|
|
119
|
+
width: dropdownPos.width,
|
|
120
|
+
backgroundColor: colors.background.card,
|
|
121
|
+
borderRadius: 12,
|
|
122
|
+
borderWidth: 1,
|
|
123
|
+
borderColor: colors.semantic.border,
|
|
124
|
+
elevation: 10,
|
|
125
|
+
shadowColor: "#000",
|
|
126
|
+
shadowOffset: { width: 0, height: 4 },
|
|
127
|
+
shadowOpacity: 0.15,
|
|
128
|
+
shadowRadius: 6,
|
|
129
|
+
overflow: "hidden",
|
|
130
|
+
}}>
|
|
131
|
+
{options.map((opt, index) => (
|
|
132
|
+
<TouchableOpacity
|
|
133
|
+
key={opt.value}
|
|
134
|
+
onPress={() => {
|
|
135
|
+
onChange(opt.value);
|
|
136
|
+
setOpen(false);
|
|
137
|
+
}}
|
|
138
|
+
style={{
|
|
139
|
+
padding: 14,
|
|
140
|
+
flexDirection: "row",
|
|
141
|
+
alignItems: "center",
|
|
142
|
+
justifyContent: "space-between",
|
|
143
|
+
borderBottomWidth: index < options.length - 1 ? 1 : 0,
|
|
144
|
+
borderBottomColor: colors.background.main,
|
|
145
|
+
backgroundColor: value === opt.value ? colors.primary + "15" : "transparent",
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<Text style={{
|
|
149
|
+
fontSize: textSize.sm,
|
|
150
|
+
fontFamily: value === opt.value ? Fonts.montserrat.semiBold : Fonts.montserrat.regular,
|
|
151
|
+
color: value === opt.value ? colors.primary : colors.text.primary,
|
|
152
|
+
}}>
|
|
153
|
+
{opt.label}
|
|
154
|
+
</Text>
|
|
155
|
+
{value === opt.value && (
|
|
156
|
+
<Ionicons name="checkmark" size={16} color={colors.primary} />
|
|
157
|
+
)}
|
|
158
|
+
</TouchableOpacity>
|
|
159
|
+
))}
|
|
160
|
+
</View>
|
|
161
|
+
</TouchableOpacity>
|
|
162
|
+
</Modal>
|
|
163
|
+
</View>
|
|
164
|
+
);
|
|
165
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -59,4 +59,5 @@ export {default as Badge} from "./components/ui/badge";
|
|
|
59
59
|
export {default as WarningModel} from "./components/ui/warningModel";
|
|
60
60
|
export {default as Toast} from "./components/ui/toast";
|
|
61
61
|
export {default as AccountStatusScreen} from "./components/screens/AccountStatusScreen";
|
|
62
|
-
export {sendEmail} from "./service/sendEmail";
|
|
62
|
+
export {sendEmail} from "./service/sendEmail";
|
|
63
|
+
export {default as SharpDropdown} from "./components/ui/sharpDropdown";
|