-
Notifications
You must be signed in to change notification settings - Fork 52
/
Select.tsx
153 lines (141 loc) · 4.27 KB
/
Select.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import React, { useRef, useState } from 'react';
import {
FlatList,
LayoutRectangle,
Modal,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { cn } from '../lib/utils';
// Extra types to you if you need :)
export interface ISelectedOption {
label: string;
value: string;
}
export interface ISelectedOptionsArray {
options?: ISelectedOption[];
}
export type ISelectedValue = string | number | undefined;
const convertToOptions = <T extends Record<string, any>>(
data?: T[],
labelKey?: keyof T,
valueKey?: keyof T
): ISelectedOption[] => {
if (!data || !labelKey || !valueKey) return [];
return data.map(item => ({
label: String(item[labelKey]),
value: item[valueKey],
}));
};
export interface SelectProps {
/** Add label string */
label?: string;
/** Add style to label*/
labelClasses?: string;
/** Add style to touchableOpacity selector*/
selectClasses?: string;
/** Add your options array -> send any type (example model: [{item:'',key:''}]) to converter to ISelectedOption > {label, value}*/
options: any[];
/** Add your selected state changer*/
onSelect: (value: string | number) => void;
/** Add your selected state value*/
selectedValue?: string | number;
/** Add your selected placeholder -> default is 'Select an option' */
placeholder?: string;
/** Define labelKey to options */
labelKey: string;
/** Define valueKey to options */
valueKey: string;
}
/** Customizable Select Component :) options receive any data type and converter into label and value to render */
export const Select = ({
label,
labelClasses,
selectClasses,
options,
onSelect,
selectedValue,
placeholder = 'Select an option',
labelKey,
valueKey,
}: SelectProps) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [dropdownPosition, setDropdownPosition] =
useState<LayoutRectangle | null>(null);
const selectButtonRef = useRef<TouchableOpacity>(null);
const new_options = convertToOptions(options, labelKey, valueKey);
const handleSelect = (value: string | number) => {
onSelect(value);
setIsDropdownOpen(false);
};
const openDropdown = () => {
selectButtonRef.current?.measure((_fx, _fy, _w, _h, px, py) => {
setDropdownPosition({
x: px,
y: py + _h,
width: _w,
height: _h,
});
setIsDropdownOpen(true);
});
};
return (
<View className={cn('flex flex-col gap-1.5')}>
{label && (
<Text className={cn('text-base text-primary', labelClasses)}>
{label}
</Text>
)}
<TouchableOpacity
ref={selectButtonRef}
className={cn(
selectClasses,
'border border-input py-2.5 px-4 rounded-lg bg-white dark:bg-black'
)}
onPress={openDropdown}
>
<Text className="text-primary">
{selectedValue
? new_options.find(option => option.value === selectedValue)?.label
: placeholder}
</Text>
</TouchableOpacity>
{/* Dropdown modal */}
{isDropdownOpen && dropdownPosition && (
<Modal visible={isDropdownOpen} transparent animationType="none">
<TouchableOpacity
style={{ flex: 1 }}
onPress={() => setIsDropdownOpen(false)}
>
<View
style={{
top: dropdownPosition.y,
left: dropdownPosition.x,
width: dropdownPosition.width,
shadowOpacity: 0.2,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 5,
}}
className="absolute bg-white shadow-sm dark:bg-black p-2 rounded-md shadow-black dark:shadow-white"
>
<FlatList
data={new_options}
keyExtractor={item => item.value.toString()}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => handleSelect(item.value)}
className="p-2 border-b border-input"
>
<Text className="text-primary">{item.label}</Text>
</TouchableOpacity>
)}
/>
</View>
</TouchableOpacity>
</Modal>
)}
</View>
);
};