11 | }
12 |
13 | export const Dropdown = memo((props: DropdownProps) => {
14 | const {
15 | dataSet,
16 | suggestionsListMaxHeight,
17 | renderItem,
18 | ListEmptyComponent,
19 | ItemSeparatorComponent,
20 | direction,
21 | theme,
22 | ...rest
23 | } = props
24 | const themeName = theme || useColorScheme()
25 | const styles = useMemo(() => getStyles(themeName || 'light'), [themeName])
26 |
27 | const defaultItemSeparator = useMemo(() => {
28 | return () =>
29 | }, [styles.itemSeparator])
30 |
31 | return (
32 |
42 | item.id}
50 | ListEmptyComponent={ListEmptyComponent}
51 | ItemSeparatorComponent={ItemSeparatorComponent ?? defaultItemSeparator}
52 | {...rest.flatListProps}
53 | />
54 |
55 | )
56 | })
57 |
58 | const getStyles = (themeName: 'light' | 'dark' = 'light') =>
59 | StyleSheet.create({
60 | container: {},
61 | listContainer: {
62 | backgroundColor: theme[themeName].suggestionsListBackgroundColor,
63 | width: '100%',
64 | zIndex: 9,
65 | borderRadius: 5,
66 | shadowColor: theme[themeName || 'light'].shadowColor,
67 | shadowOffset: {
68 | width: 0,
69 | height: 12,
70 | },
71 | shadowOpacity: 0.3,
72 | shadowRadius: 15.46,
73 |
74 | elevation: 20,
75 | },
76 | itemSeparator: {
77 | height: 1,
78 | width: '100%',
79 | backgroundColor: theme[themeName || 'light'].itemSeparatorColor,
80 | },
81 | })
82 |
--------------------------------------------------------------------------------
/src/HOC/withFadeAnimation.tsx:
--------------------------------------------------------------------------------
1 | import type { FC, ComponentType } from 'react'
2 | import React, { useEffect, useRef } from 'react'
3 | import type { ViewProps } from 'react-native'
4 | import { Animated, Easing } from 'react-native'
5 |
6 | interface WithFadeAnimationProps {
7 | containerStyle?: ViewProps['style']
8 | }
9 |
10 | export const withFadeAnimation = (
11 | WrappedComponent: ComponentType
,
12 | { containerStyle }: WithFadeAnimationProps = {},
13 | ): FC
=> {
14 | return (props: P) => {
15 | const opacityAnimationValue = useRef(new Animated.Value(0)).current
16 |
17 | useEffect(() => {
18 | Animated.timing(opacityAnimationValue, {
19 | duration: 800,
20 | toValue: 1,
21 | useNativeDriver: true,
22 | easing: Easing.bezier(0.3, 0.58, 0.25, 0.99),
23 | }).start()
24 | }, [opacityAnimationValue])
25 |
26 | return (
27 |
28 |
29 |
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/NothingFound.tsx:
--------------------------------------------------------------------------------
1 | import type { FC } from 'react'
2 | import React, { memo } from 'react'
3 | import { StyleSheet, Text, View } from 'react-native'
4 | import { withFadeAnimation } from './HOC/withFadeAnimation'
5 |
6 | interface NothingFoundProps {
7 | emptyResultText?: string
8 | }
9 |
10 | export const NothingFound: FC = memo(({ ...props }) => {
11 | const EL = withFadeAnimation(
12 | () => (
13 |
14 | {props.emptyResultText || 'Nothing found'}
15 |
16 | ),
17 | {},
18 | )
19 | return
20 | })
21 |
22 | const styles = StyleSheet.create({
23 | container: {
24 | padding: 10,
25 | },
26 | text: { textAlign: 'center' },
27 | })
28 |
--------------------------------------------------------------------------------
/src/RightButton.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useEffect, useRef } from 'react'
2 | import type { StyleProp, ViewStyle } from 'react-native'
3 | import { ActivityIndicator, Animated, Easing, StyleSheet, TouchableOpacity, View } from 'react-native'
4 | import { ChevronDown, XCircle } from 'react-native-feather'
5 |
6 | interface RightButtonProps {
7 | inputHeight?: number
8 | onClearPress?: () => void
9 | onChevronPress?: () => void
10 | isOpened?: boolean
11 | showChevron?: boolean
12 | showClear?: boolean
13 | loading?: boolean
14 | enableLoadingIndicator?: boolean
15 | buttonsContainerStyle?: StyleProp
16 | ChevronIconComponent?: React.ReactNode
17 | ClearIconComponent?: React.ReactNode
18 | RightIconComponent?: React.ReactNode
19 | onRightIconComponentPress?: () => void
20 | }
21 |
22 | export const RightButton: React.FC = memo(
23 | ({
24 | inputHeight,
25 | onClearPress,
26 | onChevronPress,
27 | isOpened,
28 | showChevron,
29 | showClear,
30 | loading,
31 | enableLoadingIndicator,
32 | buttonsContainerStyle,
33 | ChevronIconComponent,
34 | ClearIconComponent,
35 | RightIconComponent,
36 | onRightIconComponentPress,
37 | }) => {
38 | const isOpenedAnimationValue = useRef(new Animated.Value(0)).current
39 |
40 | useEffect(() => {
41 | Animated.timing(isOpenedAnimationValue, {
42 | duration: 350,
43 | toValue: isOpened ? 1 : 0,
44 | useNativeDriver: true,
45 | easing: Easing.bezier(0.3, 0.58, 0.25, 0.99),
46 | }).start()
47 | }, [isOpened, isOpenedAnimationValue])
48 |
49 | const chevronSpin = isOpenedAnimationValue.interpolate({
50 | inputRange: [0, 1],
51 | outputRange: ['0deg', '180deg'],
52 | })
53 |
54 | return (
55 |
61 | {(!enableLoadingIndicator || !loading) && showClear && (
62 |
63 | {ClearIconComponent ?? }
64 |
65 | )}
66 | {enableLoadingIndicator && loading && }
67 | {RightIconComponent && (
68 |
69 | {RightIconComponent}
70 |
71 | )}
72 | {showChevron && (
73 |
74 |
75 | {ChevronIconComponent ?? }
76 |
77 |
78 | )}
79 |
80 | )
81 | },
82 | )
83 |
84 | const styles = StyleSheet.create({
85 | container: {
86 | position: 'relative',
87 | flex: 0,
88 | flexDirection: 'row',
89 | right: 8,
90 | zIndex: 10,
91 | justifyContent: 'center',
92 | alignItems: 'center',
93 | backgroundColor: 'transparent',
94 | },
95 | clearButton: {
96 | width: 26,
97 | alignItems: 'center',
98 | },
99 | chevronButton: {
100 | width: 26,
101 | alignItems: 'center',
102 | height: '100%',
103 | justifyContent: 'center',
104 | },
105 | })
106 |
--------------------------------------------------------------------------------
/src/ScrollViewListItem.tsx:
--------------------------------------------------------------------------------
1 | import type { FC } from 'react'
2 | import React, { memo, useMemo } from 'react'
3 | import type { ViewProps } from 'react-native'
4 | import { StyleSheet, Text, TouchableOpacity, View, useColorScheme } from 'react-native'
5 | import diacriticless from './diacriticless'
6 | import { theme } from './theme'
7 |
8 | interface ScrollViewListItemProps {
9 | highlight: string
10 | title: string
11 | style?: ViewProps['style']
12 | onPress?: () => void
13 | ignoreAccents?: boolean
14 | numberOfLines?: number
15 | }
16 |
17 | export const ScrollViewListItem: FC = memo(
18 | ({ highlight, title, style, onPress, ignoreAccents, numberOfLines = 2 }) => {
19 | const themeName = useColorScheme()
20 | const styles = useMemo(() => getStyles(themeName || 'light'), [themeName])
21 |
22 | const titleParts = useMemo(() => {
23 | let titleHighlighted = ''
24 | let titleStart = title
25 | let titleEnd = ''
26 |
27 | if (typeof title === 'string' && title?.length > 0 && highlight?.length > 0) {
28 | const highlightIn = ignoreAccents ? diacriticless(title?.toLowerCase()) : title?.toLowerCase()
29 | const highlightWhat = ignoreAccents ? diacriticless(highlight?.toLowerCase()) : highlight?.toLowerCase()
30 |
31 | const substrIndex = highlightIn?.indexOf(highlightWhat)
32 | if (substrIndex !== -1) {
33 | titleStart = title?.slice(0, substrIndex)
34 | titleHighlighted = title?.slice(substrIndex, substrIndex + highlight?.length)
35 | titleEnd = title?.slice(substrIndex + highlight?.length)
36 | }
37 | }
38 |
39 | return { titleHighlighted, titleStart, titleEnd }
40 | }, [highlight, ignoreAccents, title])
41 |
42 | return (
43 |
44 |
45 |
46 |
47 | {titleParts.titleStart}
48 |
49 |
50 | {titleParts.titleHighlighted}
51 |
52 |
53 | {titleParts.titleEnd}
54 |
55 |
56 |
57 |
58 | )
59 | },
60 | )
61 |
62 | const getStyles = (themeName: 'light' | 'dark' = 'light') =>
63 | StyleSheet.create({
64 | container: {
65 | padding: 15,
66 | flex: 1,
67 | flexDirection: 'row',
68 | alignItems: 'flex-start',
69 | justifyContent: 'flex-start',
70 | flexWrap: 'nowrap',
71 |
72 | width: '100%',
73 | },
74 | text: {
75 | color: theme[themeName].listItemTextColor,
76 | fontSize: 16,
77 | flexGrow: 1,
78 | flexShrink: 0,
79 | },
80 | textBold: {
81 | fontWeight: 'bold',
82 | },
83 | })
84 |
--------------------------------------------------------------------------------
/src/diacriticless.ts:
--------------------------------------------------------------------------------
1 | interface DiacriticsMap {
2 | [key: string]: string[]
3 | }
4 |
5 | // all diacritics
6 | const diacritics: DiacriticsMap = {
7 | a: [
8 | 'a',
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 | 'a',
53 | ],
54 | A: [
55 | 'A',
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 | 'A',
92 | ],
93 |
94 | b: ['b', 'ƀ', 'ƃ', 'ɓ', 'ᖯ', 'ᵇ', 'ᵬ', 'ᶀ', 'ḃ', 'ḅ', 'ḇ', 'b'],
95 | B: ['B', 'Ɓ', 'Ƃ', 'Ƀ', 'ʙ', 'ᛒ', 'ᴃ', 'ᴮ', 'ᴯ', 'Ḃ', 'Ḅ', 'Ḇ', 'B'],
96 |
97 | c: ['c', 'ç', 'ć', 'ĉ', 'ċ', 'č', 'ƈ', 'ȼ', 'ɕ', 'ͨ', 'ᴄ', 'ᶜ', 'ḉ', 'ↄ', 'c'],
98 | C: ['C', 'Ç', 'Ć', 'Ĉ', 'Ċ', 'Č', 'Ƈ', 'Ȼ', 'ʗ', 'Ḉ', 'C'],
99 |
100 | d: ['d', 'ď', 'đ', 'Ƌ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ͩ', 'ᵈ', 'ᵭ', 'ᶁ', 'ᶑ', 'ḋ', 'ḍ', 'ḏ', 'ḑ', 'ḓ', 'd'],
101 | D: ['D', 'Ď', 'Đ', 'Ɖ', 'Ɗ', 'ᴰ', 'Ḋ', 'Ḍ', 'Ḏ', 'Ḑ', 'Ḓ', 'D'],
102 |
103 | e: [
104 | 'e',
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 | 'e',
138 | ],
139 | E: [
140 | 'E',
141 | 'È',
142 | 'É',
143 | 'Ê',
144 | 'Ë',
145 | 'Ē',
146 | 'Ĕ',
147 | 'Ė',
148 | 'Ę',
149 | 'Ě',
150 | 'Œ',
151 | 'Ǝ',
152 | 'Ɛ',
153 | 'Ȅ',
154 | 'Ȇ',
155 | 'Ȩ',
156 | 'Ɇ',
157 | 'ɛ',
158 | 'ɜ',
159 | 'ɶ',
160 | 'Є',
161 | 'Э',
162 | 'э',
163 | 'є',
164 | 'Ӭ',
165 | 'ӭ',
166 | 'ᴇ',
167 | 'ᴈ',
168 | 'ᴱ',
169 | 'ᴲ',
170 | 'ᵋ',
171 | 'ᵌ',
172 | 'ᶓ',
173 | 'ᶔ',
174 | 'ᶟ',
175 | 'Ḕ',
176 | 'Ḗ',
177 | 'Ḙ',
178 | 'Ḛ',
179 | 'Ḝ',
180 | 'Ẹ',
181 | 'Ẻ',
182 | 'Ẽ',
183 | 'Ế',
184 | 'Ề',
185 | 'Ể',
186 | 'Ễ',
187 | 'Ệ',
188 | 'E',
189 | '𐐁',
190 | '𐐩',
191 | ],
192 |
193 | f: ['f', 'ƒ', 'ᵮ', 'ᶂ', 'ᶠ', 'ḟ', 'f'],
194 | F: ['F', 'Ƒ', 'Ḟ', 'ⅎ', 'F'],
195 |
196 | g: ['g', 'ĝ', 'ğ', 'ġ', 'ģ', 'ǥ', 'ǧ', 'ǵ', 'ɠ', 'ɡ', 'ᵍ', 'ᵷ', 'ᵹ', 'ᶃ', 'ᶢ', 'ḡ', 'g'],
197 | G: ['G', 'Ĝ', 'Ğ', 'Ġ', 'Ģ', 'Ɠ', 'Ǥ', 'Ǧ', 'Ǵ', 'ɢ', 'ʛ', 'ᴳ', 'Ḡ', 'G'],
198 |
199 | h: [
200 | 'h',
201 | 'ĥ',
202 | 'ħ',
203 | 'ƕ',
204 | 'ȟ',
205 | 'ɥ',
206 | 'ɦ',
207 | 'ʮ',
208 | 'ʯ',
209 | 'ʰ',
210 | 'ʱ',
211 | 'ͪ',
212 | 'Һ',
213 | 'һ',
214 | 'ᑋ',
215 | 'ᶣ',
216 | 'ḣ',
217 | 'ḥ',
218 | 'ḧ',
219 | 'ḩ',
220 | 'ḫ',
221 | 'ⱨ',
222 | 'h',
223 | ],
224 | H: ['H', 'Ĥ', 'Ħ', 'Ȟ', 'ʜ', 'ᕼ', 'ᚺ', 'ᚻ', 'ᴴ', 'Ḣ', 'Ḥ', 'Ḧ', 'Ḩ', 'Ḫ', 'Ⱨ', 'H'],
225 |
226 | i: [
227 | 'i',
228 | 'ì',
229 | 'í',
230 | 'î',
231 | 'ï',
232 | 'ĩ',
233 | 'ī',
234 | 'ĭ',
235 | 'į',
236 | 'ǐ',
237 | 'ȉ',
238 | 'ȋ',
239 | 'ɨ',
240 | 'ͥ',
241 | 'ᴉ',
242 | 'ᵎ',
243 | 'ᵢ',
244 | 'ᶖ',
245 | 'ᶤ',
246 | 'ḭ',
247 | 'ḯ',
248 | 'ỉ',
249 | 'ị',
250 | 'i',
251 | ],
252 | I: [
253 | 'I',
254 | 'Ì',
255 | 'Í',
256 | 'Î',
257 | 'Ï',
258 | 'Ĩ',
259 | 'Ī',
260 | 'Ĭ',
261 | 'Į',
262 | 'İ',
263 | 'Ǐ',
264 | 'Ȉ',
265 | 'Ȋ',
266 | 'ɪ',
267 | 'І',
268 | 'ᴵ',
269 | 'ᵻ',
270 | 'ᶦ',
271 | 'ᶧ',
272 | 'Ḭ',
273 | 'Ḯ',
274 | 'Ỉ',
275 | 'Ị',
276 | 'I',
277 | ],
278 |
279 | j: ['j', 'ĵ', 'ǰ', 'ɉ', 'ʝ', 'ʲ', 'ᶡ', 'ᶨ', 'j'],
280 | J: ['J', 'Ĵ', 'ᴊ', 'ᴶ', 'J'],
281 |
282 | k: ['k', 'ķ', 'ƙ', 'ǩ', 'ʞ', 'ᵏ', 'ᶄ', 'ḱ', 'ḳ', 'ḵ', 'ⱪ', 'k'],
283 | K: ['K', 'Ķ', 'Ƙ', 'Ǩ', 'ᴷ', 'Ḱ', 'Ḳ', 'Ḵ', 'Ⱪ', 'K'],
284 |
285 | l: ['l', 'ĺ', 'ļ', 'ľ', 'ŀ', 'ł', 'ƚ', 'ȴ', 'ɫ', 'ɬ', 'ɭ', 'ˡ', 'ᶅ', 'ᶩ', 'ᶪ', 'ḷ', 'ḹ', 'ḻ', 'ḽ', 'ℓ', 'ⱡ'],
286 | L: ['L', 'Ĺ', 'Ļ', 'Ľ', 'Ŀ', 'Ł', 'Ƚ', 'ʟ', 'ᴌ', 'ᴸ', 'ᶫ', 'Ḷ', 'Ḹ', 'Ḻ', 'Ḽ', 'Ⱡ', 'Ɫ'],
287 |
288 | m: ['m', 'ɯ', 'ɰ', 'ɱ', 'ͫ', 'ᴟ', 'ᵐ', 'ᵚ', 'ᵯ', 'ᶆ', 'ᶬ', 'ᶭ', 'ḿ', 'ṁ', 'ṃ', '㎡', '㎥', 'm'],
289 | M: ['M', 'Ɯ', 'ᴍ', 'ᴹ', 'Ḿ', 'Ṁ', 'Ṃ', 'M'],
290 |
291 | n: ['n', 'ñ', 'ń', 'ņ', 'ň', 'ʼn', 'ƞ', 'ǹ', 'ȵ', 'ɲ', 'ɳ', 'ᵰ', 'ᶇ', 'ᶮ', 'ᶯ', 'ṅ', 'ṇ', 'ṉ', 'ṋ', 'ⁿ', 'n'],
292 | N: ['N', 'Ñ', 'Ń', 'Ņ', 'Ň', 'Ɲ', 'Ǹ', 'Ƞ', 'ɴ', 'ᴎ', 'ᴺ', 'ᴻ', 'ᶰ', 'Ṅ', 'Ṇ', 'Ṉ', 'Ṋ', 'N'],
293 |
294 | o: [
295 | 'o',
296 | 'ò',
297 | 'ó',
298 | 'ô',
299 | 'õ',
300 | 'ö',
301 | 'ø',
302 | 'ō',
303 | 'ŏ',
304 | 'ő',
305 | 'ơ',
306 | 'ǒ',
307 | 'ǫ',
308 | 'ǭ',
309 | 'ǿ',
310 | 'ȍ',
311 | 'ȏ',
312 | 'ȫ',
313 | 'ȭ',
314 | 'ȯ',
315 | 'ȱ',
316 | 'ɵ',
317 | 'ͦ',
318 | 'о',
319 | 'ӧ',
320 | 'ө',
321 | 'ᴏ',
322 | 'ᴑ',
323 | 'ᴓ',
324 | 'ᴼ',
325 | 'ᵒ',
326 | 'ᶱ',
327 | 'ṍ',
328 | 'ṏ',
329 | 'ṑ',
330 | 'ṓ',
331 | 'ọ',
332 | 'ỏ',
333 | 'ố',
334 | 'ồ',
335 | 'ổ',
336 | 'ỗ',
337 | 'ộ',
338 | 'ớ',
339 | 'ờ',
340 | 'ở',
341 | 'ỡ',
342 | 'ợ',
343 | 'ₒ',
344 | 'o',
345 | '𐐬',
346 | ],
347 | O: [
348 | 'O',
349 | 'Ò',
350 | 'Ó',
351 | 'Ô',
352 | 'Õ',
353 | 'Ö',
354 | 'Ø',
355 | 'Ō',
356 | 'Ŏ',
357 | 'Ő',
358 | 'Ɵ',
359 | 'Ơ',
360 | 'Ǒ',
361 | 'Ǫ',
362 | 'Ǭ',
363 | 'Ǿ',
364 | 'Ȍ',
365 | 'Ȏ',
366 | 'Ȫ',
367 | 'Ȭ',
368 | 'Ȯ',
369 | 'Ȱ',
370 | 'О',
371 | 'Ӧ',
372 | 'Ө',
373 | 'Ṍ',
374 | 'Ṏ',
375 | 'Ṑ',
376 | 'Ṓ',
377 | 'Ọ',
378 | 'Ỏ',
379 | 'Ố',
380 | 'Ồ',
381 | 'Ổ',
382 | 'Ỗ',
383 | 'Ộ',
384 | 'Ớ',
385 | 'Ờ',
386 | 'Ở',
387 | 'Ỡ',
388 | 'Ợ',
389 | 'O',
390 | '𐐄',
391 | ],
392 |
393 | p: ['p', 'ᵖ', 'ᵱ', 'ᵽ', 'ᶈ', 'ṕ', 'ṗ', 'p'],
394 | P: ['P', 'Ƥ', 'ᴘ', 'ᴾ', 'Ṕ', 'Ṗ', 'Ᵽ', 'P'],
395 |
396 | q: ['q', 'ɋ', 'ʠ', 'ᛩ', 'q'],
397 | Q: ['Q', 'Ɋ', 'Q'],
398 |
399 | r: ['r', 'ŕ', 'ŗ', 'ř', 'ȑ', 'ȓ', 'ɍ', 'ɹ', 'ɻ', 'ʳ', 'ʴ', 'ʵ', 'ͬ', 'ᵣ', 'ᵲ', 'ᶉ', 'ṙ', 'ṛ', 'ṝ', 'ṟ'],
400 | R: ['R', 'Ŕ', 'Ŗ', 'Ř', 'Ʀ', 'Ȑ', 'Ȓ', 'Ɍ', 'ʀ', 'ʁ', 'ʶ', 'ᚱ', 'ᴙ', 'ᴚ', 'ᴿ', 'Ṙ', 'Ṛ', 'Ṝ', 'Ṟ', 'Ɽ'],
401 |
402 | s: ['s', 'ś', 'ŝ', 'ş', 'š', 'ș', 'ʂ', 'ᔆ', 'ᶊ', 'ṡ', 'ṣ', 'ṥ', 'ṧ', 'ṩ', 's'],
403 | S: ['S', 'Ś', 'Ŝ', 'Ş', 'Š', 'Ș', 'ȿ', 'ˢ', 'ᵴ', 'Ṡ', 'Ṣ', 'Ṥ', 'Ṧ', 'Ṩ', 'S'],
404 |
405 | t: ['t', 'ţ', 'ť', 'ŧ', 'ƫ', 'ƭ', 'ț', 'ʇ', 'ͭ', 'ᵀ', 'ᵗ', 'ᵵ', 'ᶵ', 'ṫ', 'ṭ', 'ṯ', 'ṱ', 'ẗ', 't'],
406 | T: ['T', 'Ţ', 'Ť', 'Ƭ', 'Ʈ', 'Ț', 'Ⱦ', 'ᴛ', 'ᵀ', 'Ṫ', 'Ṭ', 'Ṯ', 'Ṱ', 'T'],
407 |
408 | u: [
409 | 'u',
410 | 'ù',
411 | 'ú',
412 | 'û',
413 | 'ü',
414 | 'ũ',
415 | 'ū',
416 | 'ŭ',
417 | 'ů',
418 | 'ű',
419 | 'ų',
420 | 'ư',
421 | 'ǔ',
422 | 'ǖ',
423 | 'ǘ',
424 | 'ǚ',
425 | 'ǜ',
426 | 'ȕ',
427 | 'ȗ',
428 | 'ͧ',
429 | 'ߎ',
430 | 'ᵘ',
431 | 'ᵤ',
432 | 'ṳ',
433 | 'ṵ',
434 | 'ṷ',
435 | 'ṹ',
436 | 'ṻ',
437 | 'ụ',
438 | 'ủ',
439 | 'ứ',
440 | 'ừ',
441 | 'ử',
442 | 'ữ',
443 | 'ự',
444 | 'u',
445 | ],
446 | U: [
447 | 'U',
448 | 'Ù',
449 | 'Ú',
450 | 'Û',
451 | 'Ü',
452 | 'Ũ',
453 | 'Ū',
454 | 'Ŭ',
455 | 'Ů',
456 | 'Ű',
457 | 'Ų',
458 | 'Ư',
459 | 'Ǔ',
460 | 'Ǖ',
461 | 'Ǘ',
462 | 'Ǚ',
463 | 'Ǜ',
464 | 'Ȕ',
465 | 'Ȗ',
466 | 'Ʉ',
467 | 'ᴜ',
468 | 'ᵁ',
469 | 'ᵾ',
470 | 'Ṳ',
471 | 'Ṵ',
472 | 'Ṷ',
473 | 'Ṹ',
474 | 'Ṻ',
475 | 'Ụ',
476 | 'Ủ',
477 | 'Ứ',
478 | 'Ừ',
479 | 'Ử',
480 | 'Ữ',
481 | 'Ự',
482 | 'U',
483 | ],
484 |
485 | v: ['v', 'ʋ', 'ͮ', 'ᵛ', 'ᵥ', 'ᶹ', 'ṽ', 'ṿ', 'ⱱ', 'v', 'ⱴ'],
486 | V: ['V', 'Ʋ', 'Ʌ', 'ʌ', 'ᴠ', 'ᶌ', 'Ṽ', 'Ṿ', 'V'],
487 |
488 | w: ['w', 'ŵ', 'ʷ', 'ᵂ', 'ẁ', 'ẃ', 'ẅ', 'ẇ', 'ẉ', 'ẘ', 'ⱳ', 'w'],
489 | W: ['W', 'Ŵ', 'ʍ', 'ᴡ', 'Ẁ', 'Ẃ', 'Ẅ', 'Ẇ', 'Ẉ', 'Ⱳ', 'W'],
490 |
491 | x: ['x', '̽', '͓', 'ᶍ', 'ͯ', 'ẋ', 'ẍ', 'ₓ', 'x'],
492 | X: ['X', 'ˣ', 'ͯ', 'Ẋ', 'Ẍ', '☒', '✕', '✖', '✗', '✘', 'X'],
493 |
494 | y: ['y', 'ý', 'ÿ', 'ŷ', 'ȳ', 'ɏ', 'ʸ', 'ẏ', 'ỳ', 'ỵ', 'ỷ', 'ỹ', 'y'],
495 | Y: ['Y', 'Ý', 'Ŷ', 'Ÿ', 'Ƴ', 'ƴ', 'Ȳ', 'Ɏ', 'ʎ', 'ʏ', 'Ẏ', 'Ỳ', 'Ỵ', 'Ỷ', 'Ỹ', 'Y'],
496 |
497 | z: ['z', 'ź', 'ż', 'ž', 'ƶ', 'ȥ', 'ɀ', 'ʐ', 'ʑ', 'ᙆ', 'ᙇ', 'ᶻ', 'ᶼ', 'ᶽ', 'ẑ', 'ẓ', 'ẕ', 'ⱬ', 'z'],
498 | Z: ['Z', 'Ź', 'Ż', 'Ž', 'Ƶ', 'Ȥ', 'ᴢ', 'ᵶ', 'Ẑ', 'Ẓ', 'Ẕ', 'Ⱬ', 'Z'],
499 | }
500 |
501 | /*
502 | * Main function of the module which removes all diacritics from the received text
503 | */
504 |
505 | export default function removeDiacritics(text: string): string {
506 | const result: string[] = []
507 |
508 | for (let i = 0; i < text.length; i++) {
509 | const searchChar = text.charAt(i)
510 | let foundChar = false
511 |
512 | for (const key in diacritics) {
513 | const index = diacritics[key]?.indexOf(searchChar)
514 | if (index !== -1) {
515 | result.push(key)
516 | foundChar = true
517 | break
518 | }
519 | }
520 |
521 | if (!foundChar) {
522 | result.push(searchChar)
523 | }
524 | }
525 |
526 | return result.join('')
527 | }
528 |
--------------------------------------------------------------------------------
/src/helpers.ts:
--------------------------------------------------------------------------------
1 | export const fadeInDownShort = {
2 | 0: {
3 | opacity: 0,
4 | transform: [{ translateY: -20 }],
5 | },
6 | 1: {
7 | opacity: 1,
8 | transform: [{ translateY: 0 }],
9 | },
10 | }
11 |
12 | export const fadeInUpShort = {
13 | 0: {
14 | opacity: 0,
15 | transform: [{ translateY: 20 }],
16 | },
17 | 1: {
18 | opacity: 1,
19 | transform: [{ translateY: 0 }],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useCallback, useEffect, useMemo, useRef, useState, useContext } from 'react'
2 | import debounce from 'lodash.debounce'
3 | import type {
4 | GestureResponderEvent,
5 | ListRenderItem,
6 | NativeSyntheticEvent,
7 | TextInputFocusEventData,
8 | TextInputSubmitEditingEventData,
9 | } from 'react-native'
10 | import {
11 | Dimensions,
12 | Keyboard,
13 | Platform,
14 | Pressable,
15 | TextInput,
16 | TouchableOpacity,
17 | View,
18 | useColorScheme,
19 | } from 'react-native'
20 | import { moderateScale, ScaledSheet } from 'react-native-size-matters'
21 | import { Dropdown } from './Dropdown'
22 | import { NothingFound } from './NothingFound'
23 | import { RightButton } from './RightButton'
24 | import { ScrollViewListItem } from './ScrollViewListItem'
25 | import { AutocompleteDropdownContext, AutocompleteDropdownContextProvider } from './AutocompleteDropdownContext'
26 | import diacriticless from './diacriticless'
27 | import { theme } from './theme'
28 | import type { IAutocompleteDropdownProps, AutocompleteDropdownItem } from './types'
29 |
30 | export * from './types'
31 | export { AutocompleteDropdownContextProvider }
32 |
33 | export const AutocompleteDropdown = memo((props: IAutocompleteDropdownProps) => {
34 | const {
35 | dataSet: dataSetProp,
36 | initialValue: initialValueProp,
37 | clearOnFocus = true,
38 | caseSensitive = false,
39 | ignoreAccents = true,
40 | trimSearchText = true,
41 | editable = true,
42 | enableLoadingIndicator = true,
43 | matchFrom,
44 | inputHeight = moderateScale(40, 0.2),
45 | suggestionsListMaxHeight = moderateScale(200, 0.2),
46 | // bottomOffset = 0,
47 | direction: directionProp,
48 | controller,
49 | onSelectItem: onSelectItemProp,
50 | onOpenSuggestionsList: onOpenSuggestionsListProp,
51 | useFilter,
52 | renderItem: customRenderItem,
53 | EmptyResultComponent,
54 | emptyResultText,
55 | onClear,
56 | onChangeText: onTextChange,
57 | debounce: debounceDelay = 0,
58 | onChevronPress: onChevronPressProp,
59 | onFocus: onFocusProp,
60 | onBlur: onBlurProp,
61 | onSubmit: onSubmitProp,
62 | closeOnSubmit,
63 | loading: loadingProp,
64 | LeftComponent,
65 | textInputProps,
66 | showChevron,
67 | showClear,
68 | rightButtonsContainerStyle,
69 | ChevronIconComponent,
70 | ClearIconComponent,
71 | RightIconComponent,
72 | onRightIconComponentPress,
73 | containerStyle,
74 | inputContainerStyle,
75 | suggestionsListTextStyle,
76 | ref,
77 | } = props
78 | const InputComponent = (props.InputComponent as typeof TextInput) || TextInput
79 | const inputRef = useRef(null)
80 | const containerRef = useRef(null)
81 | const [searchText, setSearchText] = useState('')
82 | const [inputValue, setInputValue] = useState('')
83 | const [loading, setLoading] = useState(loadingProp)
84 | const [selectedItem, setSelectedItem] = useState(null)
85 | const [isOpened, setIsOpened] = useState(false)
86 | const initialDataSetRef = useRef(dataSetProp)
87 | const initialValueRef = useRef(initialValueProp)
88 | const [dataSet, setDataSet] = useState(dataSetProp)
89 | const matchFromStart = matchFrom === 'start' ? true : false
90 | const {
91 | content,
92 | setContent,
93 | activeInputContainerRef,
94 | activeControllerRef,
95 | direction = directionProp,
96 | setDirection,
97 | controllerRefs,
98 | } = useContext(AutocompleteDropdownContext)
99 | const themeName = useColorScheme() || 'light'
100 | const styles = useMemo(() => getStyles(themeName), [themeName])
101 |
102 | useEffect(() => {
103 | setLoading(loadingProp)
104 | }, [loadingProp])
105 |
106 | const calculateDirection = useCallback(
107 | async ({ waitForKeyboard }: { waitForKeyboard: boolean }) => {
108 | const [, positionY] = await new Promise<[x: number, y: number, width: number, height: number]>(resolve =>
109 | containerRef.current?.measureInWindow((...rect) => resolve(rect)),
110 | )
111 |
112 | return new Promise(resolve => {
113 | setTimeout(
114 | () => {
115 | const kbHeight = Keyboard.metrics?.()?.height || 0
116 | const screenHeight = Dimensions.get('window').height
117 | setDirection((screenHeight - kbHeight) / 2 > positionY ? 'down' : 'up')
118 | resolve()
119 | },
120 | waitForKeyboard ? Platform.select({ ios: 600, android: 250, default: 1 }) : 1, // wait for keyboard to show
121 | )
122 | })
123 | },
124 | [setDirection],
125 | )
126 |
127 | const onClearPress = useCallback(() => {
128 | setSearchText('')
129 | setInputValue('')
130 | setSelectedItem(null)
131 |
132 | setIsOpened(false)
133 | inputRef.current?.blur()
134 | if (typeof onClear === 'function') {
135 | onClear()
136 | }
137 | }, [onClear])
138 |
139 | /** methods */
140 | const close = useCallback(() => {
141 | setIsOpened(false)
142 | setContent(undefined)
143 | }, [setContent])
144 |
145 | const blur = useCallback(() => {
146 | inputRef.current?.blur()
147 | }, [])
148 |
149 | const open = useCallback(async () => {
150 | if (directionProp) {
151 | setDirection(directionProp)
152 | } else {
153 | await calculateDirection({ waitForKeyboard: !!inputRef.current?.isFocused() })
154 | }
155 |
156 | setTimeout(() => {
157 | setIsOpened(true)
158 | }, 0)
159 | }, [calculateDirection, directionProp, setDirection])
160 |
161 | const toggle = useCallback(() => {
162 | isOpened ? close() : open()
163 | }, [close, isOpened, open])
164 |
165 | const clear = useCallback(() => {
166 | onClearPress()
167 | }, [onClearPress])
168 |
169 | useEffect(() => {
170 | if (ref) {
171 | if (typeof ref === 'function') {
172 | ref(inputRef.current)
173 | } else {
174 | ref.current = inputRef.current
175 | }
176 | }
177 | }, [ref])
178 |
179 | /** Set initial value */
180 | useEffect(() => {
181 | const initialDataSet = initialDataSetRef.current
182 | const initialValue = initialValueRef.current
183 |
184 | let initialValueItem: AutocompleteDropdownItem | undefined
185 | if (typeof initialValue === 'string') {
186 | initialValueItem = initialDataSet?.find(el => el.id === initialValue)
187 | } else if (typeof initialValue === 'object' && initialValue.id) {
188 | initialValueItem = initialDataSet?.find(el => el.id === initialValue?.id)
189 | if (!initialValueItem) {
190 | // set the item as it is if it's not in the list
191 | initialValueItem = initialValue
192 | }
193 | }
194 |
195 | if (initialValueItem) {
196 | setSelectedItem(initialValueItem)
197 | }
198 | }, [])
199 |
200 | useEffect(() => {
201 | return () => {
202 | setContent(undefined)
203 | setIsOpened(false)
204 | }
205 | }, [setContent])
206 |
207 | const setInputText = useCallback((text: string) => {
208 | setSearchText(text)
209 | }, [])
210 |
211 | const setItem = useCallback((item: AutocompleteDropdownItem | null) => {
212 | setSelectedItem(item)
213 | }, [])
214 |
215 | useEffect(() => {
216 | if (activeControllerRef?.current) {
217 | controllerRefs?.current.push(activeControllerRef?.current)
218 | }
219 | // eslint-disable-next-line react-hooks/exhaustive-deps
220 | }, [])
221 |
222 | const closeAll = useCallback(() => {
223 | controllerRefs?.current.forEach(c => {
224 | c?.blur?.()
225 | c?.close?.()
226 | })
227 | }, [controllerRefs])
228 |
229 | /** expose controller methods */
230 | useEffect(() => {
231 | const methods = activeControllerRef ? { close, blur, open, toggle, clear, setInputText, setItem } : null
232 | if (activeControllerRef) {
233 | activeControllerRef.current = methods
234 | }
235 | if (typeof controller === 'function') {
236 | controller(methods)
237 | } else if (controller) {
238 | controller.current = methods
239 | }
240 | }, [blur, clear, close, controller, activeControllerRef, open, setInputText, setItem, toggle])
241 |
242 | useEffect(() => {
243 | if (selectedItem) {
244 | setInputValue(selectedItem.title ?? '')
245 | } else {
246 | setInputValue('')
247 | }
248 | }, [selectedItem])
249 |
250 | useEffect(() => {
251 | setInputValue(searchText)
252 | }, [searchText])
253 |
254 | useEffect(() => {
255 | if (typeof onSelectItemProp === 'function') {
256 | onSelectItemProp(selectedItem)
257 | }
258 | // eslint-disable-next-line react-hooks/exhaustive-deps
259 | }, [selectedItem])
260 |
261 | useEffect(() => {
262 | if (typeof onOpenSuggestionsListProp === 'function') {
263 | onOpenSuggestionsListProp(isOpened)
264 | }
265 | // eslint-disable-next-line react-hooks/exhaustive-deps
266 | }, [isOpened])
267 |
268 | useEffect(() => {
269 | // renew state on close
270 | if (!isOpened && selectedItem && !loading && !inputRef.current?.isFocused()) {
271 | setInputValue(selectedItem.title || '')
272 | }
273 | }, [isOpened, loading, searchText, selectedItem])
274 |
275 | const _onSelectItem = useCallback((item: AutocompleteDropdownItem) => {
276 | setSelectedItem(item)
277 | inputRef.current?.blur()
278 | setIsOpened(false)
279 | }, [])
280 |
281 | useEffect(() => {
282 | initialDataSetRef.current = dataSetProp
283 | setDataSet(dataSetProp)
284 | }, [dataSetProp])
285 |
286 | useEffect(() => {
287 | const initialDataSet = initialDataSetRef.current
288 | if (!searchText?.length) {
289 | setDataSet(initialDataSet)
290 | return
291 | }
292 |
293 | if (!Array.isArray(initialDataSet) || useFilter === false) {
294 | return
295 | }
296 |
297 | let findWhat = caseSensitive ? searchText : searchText.toLowerCase()
298 |
299 | if (ignoreAccents) {
300 | findWhat = diacriticless(findWhat)
301 | }
302 |
303 | if (trimSearchText) {
304 | findWhat = findWhat.trim()
305 | }
306 |
307 | const newSet = initialDataSet.filter((item: AutocompleteDropdownItem) => {
308 | const titleStr = item.title || ''
309 | const title = caseSensitive ? titleStr : titleStr.toLowerCase()
310 | const findWhere = ignoreAccents ? diacriticless(title) : title
311 |
312 | if (matchFromStart) {
313 | return typeof item.title === 'string' && findWhere.startsWith(findWhat)
314 | } else {
315 | return typeof item.title === 'string' && findWhere.indexOf(findWhat) !== -1
316 | }
317 | })
318 |
319 | setDataSet(newSet)
320 | }, [ignoreAccents, matchFromStart, caseSensitive, searchText, trimSearchText, useFilter])
321 |
322 | const renderItem: ListRenderItem = useCallback(
323 | ({ item }) => {
324 | if (typeof customRenderItem === 'function') {
325 | const EL = customRenderItem(item, searchText)
326 | return _onSelectItem(item)}>{EL}
327 | }
328 |
329 | return (
330 | _onSelectItem(item)}
336 | ignoreAccents={ignoreAccents}
337 | />
338 | )
339 | },
340 | [_onSelectItem, customRenderItem, ignoreAccents, searchText, suggestionsListTextStyle],
341 | )
342 |
343 | const ListEmptyComponent = useMemo(() => {
344 | return EmptyResultComponent ??
345 | }, [EmptyResultComponent, emptyResultText])
346 |
347 | const debouncedEvent = useMemo(
348 | () =>
349 | debounce((text: string) => {
350 | if (typeof onTextChange === 'function') {
351 | onTextChange(text)
352 | }
353 | setLoading(false)
354 | }, debounceDelay),
355 | [debounceDelay, onTextChange],
356 | )
357 |
358 | const onChangeText = useCallback(
359 | (text: string) => {
360 | setSearchText(text)
361 | setInputValue(text)
362 | setLoading(true)
363 | debouncedEvent(text)
364 | },
365 | [debouncedEvent],
366 | )
367 |
368 | const onChevronPress = useCallback(() => {
369 | toggle()
370 | Keyboard.dismiss()
371 |
372 | if (typeof onChevronPressProp === 'function') {
373 | onChevronPressProp()
374 | }
375 | }, [onChevronPressProp, toggle])
376 |
377 | const onFocus = useCallback(
378 | (e: NativeSyntheticEvent) => {
379 | if (clearOnFocus) {
380 | setSearchText('')
381 | setInputValue('')
382 | }
383 | if (typeof onFocusProp === 'function') {
384 | onFocusProp(e)
385 | }
386 | open()
387 | },
388 | [clearOnFocus, onFocusProp, open],
389 | )
390 |
391 | const onBlur = useCallback(
392 | (e: NativeSyntheticEvent) => {
393 | if (typeof onBlurProp === 'function') {
394 | onBlurProp(e)
395 | }
396 | },
397 | [onBlurProp],
398 | )
399 |
400 | const onSubmit = useCallback(
401 | (e: NativeSyntheticEvent) => {
402 | inputRef.current?.blur()
403 | if (closeOnSubmit) {
404 | close()
405 | }
406 |
407 | if (typeof onSubmitProp === 'function') {
408 | onSubmitProp(e)
409 | }
410 | },
411 | [close, closeOnSubmit, onSubmitProp],
412 | )
413 |
414 | const onPressOut = useCallback(
415 | (e: GestureResponderEvent) => {
416 | closeAll()
417 | if (editable) {
418 | inputRef?.current?.focus()
419 | } else {
420 | toggle()
421 | }
422 | },
423 | [closeAll, editable, toggle],
424 | )
425 |
426 | useEffect(() => {
427 | if ((!content && !inputRef.current?.isFocused()) || loading) {
428 | const db = debounce(() => {
429 | setIsOpened(false)
430 | }, 100)
431 | db()
432 | return () => {
433 | db.cancel()
434 | }
435 | }
436 | }, [content, loading])
437 |
438 | useEffect(() => {
439 | // searchTextRef
440 | if (searchText && inputRef.current?.isFocused() && !loading) {
441 | setIsOpened(true)
442 | }
443 | }, [loading, searchText])
444 |
445 | useEffect(() => {
446 | if (isOpened && Array.isArray(dataSet)) {
447 | if (activeInputContainerRef) {
448 | activeInputContainerRef.current = containerRef.current
449 | }
450 |
451 | setContent(
452 | ,
463 | )
464 | } else {
465 | setContent(undefined)
466 | }
467 | }, [
468 | ListEmptyComponent,
469 | activeInputContainerRef,
470 | dataSet,
471 | direction,
472 | inputHeight,
473 | isOpened,
474 | props.suggestionsListContainerStyle,
475 | props.flatListProps,
476 | props.ItemSeparatorComponent,
477 | renderItem,
478 | setContent,
479 | suggestionsListMaxHeight,
480 | ])
481 |
482 | return (
483 | true}
485 | onTouchEnd={e => {
486 | e.stopPropagation()
487 | }}
488 | style={[styles.container, containerStyle]}>
489 | {}} // it's necessary use onLayout here for Androd (bug?)
492 | style={[styles.inputContainerStyle, inputContainerStyle]}>
493 | {LeftComponent}
494 |
498 |
511 |
512 |
527 |
528 |
529 | )
530 | })
531 |
532 | const getStyles = (themeName: 'light' | 'dark' = 'light') =>
533 | ScaledSheet.create({
534 | container: {
535 | marginVertical: 2,
536 | },
537 | inputContainerStyle: {
538 | display: 'flex',
539 | flexDirection: 'row',
540 | backgroundColor: theme[themeName].inputBackgroundColor,
541 | borderRadius: 5,
542 | overflow: 'hidden',
543 | },
544 | input: {
545 | flexGrow: 1,
546 | flexShrink: 1,
547 | overflow: 'hidden',
548 | paddingHorizontal: 13,
549 | fontSize: 16,
550 | color: theme[themeName].inputTextColor,
551 | },
552 | pressable: {
553 | flexGrow: 1,
554 | flexShrink: 1,
555 | },
556 | })
557 |
--------------------------------------------------------------------------------
/src/theme.tsx:
--------------------------------------------------------------------------------
1 | export const light = {
2 | inputBackgroundColor: '#e5ecf2',
3 | inputPlaceholderColor: '#00000066',
4 | inputTextColor: '#333',
5 | suggestionsListBackgroundColor: '#fff',
6 | itemSeparatorColor: '#ddd',
7 | shadowColor: '#00000099',
8 | listItemTextColor: '#333',
9 | }
10 |
11 | type Theme = typeof light
12 |
13 | export const dark: Theme = {
14 | inputBackgroundColor: '#1c1c1e',
15 | inputPlaceholderColor: '#767680',
16 | inputTextColor: '#fff',
17 | suggestionsListBackgroundColor: '#151516',
18 | itemSeparatorColor: '#3e3e41',
19 | shadowColor: '#919aaa5d',
20 | listItemTextColor: '#dbdddf',
21 | }
22 |
23 | export const theme = { light, dark }
24 |
--------------------------------------------------------------------------------
/src/types/global.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | namespace setInterval {
3 | function setInterval(callback: () => void, ms?: number | undefined): NodeJS.Timeout
4 | }
5 | namespace setTimeout {
6 | function setTimeout(callback: () => void, ms?: number | undefined): NodeJS.Timeout
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 | import type { StyleProp, TextInputProps, TextStyle, ViewStyle, FlatListProps, TextInput } from 'react-native'
3 |
4 | export type AutocompleteDropdownItem = {
5 | id: string
6 | title?: string | null
7 | }
8 |
9 | export interface IAutocompleteDropdownRef {
10 | clear: () => void
11 | close: () => void
12 | blur: () => void
13 | open: () => Promise
14 | setInputText: (text: string) => void
15 | toggle: () => void
16 | setItem: (item: AutocompleteDropdownItem) => void
17 | }
18 |
19 | export interface IAutocompleteDropdownProps {
20 | /**
21 | * @example [
22 | * { id: "1", title: "Alpha" },
23 | * { id: "2", title: "Beta" },
24 | * { id: "3", title: "Gamma" }
25 | * ]
26 | */
27 | dataSet: AutocompleteDropdownItem[] | null
28 | inputHeight?: number
29 | suggestionsListMaxHeight?: number
30 | initialValue?: string | { id: string } | AutocompleteDropdownItem
31 | enableLoadingIndicator?: boolean
32 | loading?: boolean
33 | useFilter?: boolean
34 | showClear?: boolean
35 | showChevron?: boolean
36 | closeOnBlur?: boolean
37 | closeOnSubmit?: boolean
38 | clearOnFocus?: boolean
39 | caseSensitive?: boolean
40 | ignoreAccents?: boolean
41 | trimSearchText?: boolean
42 | editable?: boolean
43 | matchFrom?: 'any' | 'start'
44 | debounce?: number
45 | direction?: 'down' | 'up'
46 | position?: 'absolute' | 'relative'
47 | bottomOffset?: number
48 | textInputProps?: TextInputProps
49 | theme?: 'light' | 'dark'
50 | onChangeText?: (text: string) => void
51 | onSelectItem?: (item: AutocompleteDropdownItem | null) => void
52 | renderItem?: (item: AutocompleteDropdownItem, searchText: string) => React.ReactElement | null
53 | onOpenSuggestionsList?: (isOpened: boolean) => void
54 | onClear?: () => void
55 | onChevronPress?: () => void
56 | onRightIconComponentPress?: () => void
57 | onSubmit?: TextInputProps['onSubmitEditing']
58 | onBlur?: TextInputProps['onBlur']
59 | onFocus?: TextInputProps['onFocus']
60 | controller?:
61 | | React.RefObject
62 | | ((controller: IAutocompleteDropdownRef | null) => void)
63 | containerStyle?: StyleProp
64 | inputContainerStyle?: StyleProp
65 | rightButtonsContainerStyle?: StyleProp
66 | suggestionsListContainerStyle?: StyleProp
67 | suggestionsListTextStyle?: StyleProp
68 | ChevronIconComponent?: React.ReactElement
69 | RightIconComponent?: React.ReactElement
70 | LeftComponent?: React.ReactElement
71 | ClearIconComponent?: React.ReactElement
72 | InputComponent?: React.ComponentType
73 | ItemSeparatorComponent?: React.ComponentType | null
74 | EmptyResultComponent?: React.ReactElement
75 | emptyResultText?: string
76 | flatListProps?: Partial>
77 | ref?: React.Ref
78 | }
79 |
--------------------------------------------------------------------------------
/src/useKeyboardHeight.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import { Keyboard } from 'react-native'
3 |
4 | export function useKeyboardHeight() {
5 | const [keyboardHeight, setKeyboardHeight] = useState(0)
6 |
7 | useEffect(() => {
8 | const showSubscription = Keyboard.addListener('keyboardDidShow', e => setKeyboardHeight(e.endCoordinates.height))
9 | const hideSubscription = Keyboard.addListener('keyboardDidHide', () => setKeyboardHeight(0))
10 | return () => {
11 | showSubscription.remove()
12 | hideSubscription.remove()
13 | }
14 | }, [])
15 |
16 | return keyboardHeight
17 | }
18 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "exclude": [
4 | "example"
5 | ]
6 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "rootDir": ".",
4 | "paths": {
5 | "react-native-autocomplete-dropdown": [
6 | "./src/index"
7 | ]
8 | },
9 | "allowUnreachableCode": false,
10 | "allowUnusedLabels": false,
11 | "esModuleInterop": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "jsx": "react",
14 | "lib": [
15 | "esnext"
16 | ],
17 | "module": "esnext",
18 | "moduleResolution": "node",
19 | "noFallthroughCasesInSwitch": true,
20 | "noImplicitReturns": false,
21 | "noImplicitUseStrict": false,
22 | "noStrictGenericChecks": false,
23 | "noUncheckedIndexedAccess": true,
24 | "noUnusedLocals": false,
25 | "noUnusedParameters": false,
26 | "resolveJsonModule": true,
27 | "skipLibCheck": true,
28 | "strict": true,
29 | "target": "esnext",
30 | "verbatimModuleSyntax": true
31 | }
32 | }
--------------------------------------------------------------------------------