├── README.md ├── assets ├── icon.png ├── splash.png ├── favicon.png └── adaptive-icon.png ├── tsconfig.json ├── babel.config.js ├── .gitignore ├── package.json ├── app.json ├── utils └── countries.ts ├── App.tsx └── components └── DropDown.tsx /README.md: -------------------------------------------------------------------------------- 1 | # React-Native-Dropdown 2 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liptonzuma/React-Native-Dropdown/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liptonzuma/React-Native-Dropdown/HEAD/assets/splash.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liptonzuma/React-Native-Dropdown/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liptonzuma/React-Native-Dropdown/HEAD/assets/adaptive-icon.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dropdown", 3 | "version": "1.0.0", 4 | "main": "expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "expo": "~51.0.2", 13 | "expo-status-bar": "~1.12.1", 14 | "react": "18.2.0", 15 | "react-native": "0.74.1", 16 | "react-native-size-matters": "^0.4.2" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.20.0", 20 | "@types/react": "~18.2.45", 21 | "typescript": "^5.1.3" 22 | }, 23 | "private": true 24 | } 25 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "dropdown", 4 | "slug": "dropdown", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "ios": { 15 | "supportsTablet": true 16 | }, 17 | "android": { 18 | "adaptiveIcon": { 19 | "foregroundImage": "./assets/adaptive-icon.png", 20 | "backgroundColor": "#ffffff" 21 | } 22 | }, 23 | "web": { 24 | "favicon": "./assets/favicon.png" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /utils/countries.ts: -------------------------------------------------------------------------------- 1 | export const countries = [ 2 | { 3 | label: "Ghana", 4 | value: "gh", 5 | flag: "🇬🇭", 6 | }, 7 | { 8 | label: "Nigeria", 9 | value: "ng", 10 | flag: "🇳🇬", 11 | }, 12 | { 13 | label: "Kenya", 14 | value: "ky", 15 | flag: "🇰🇪", 16 | }, 17 | { 18 | label: "United States", 19 | value: "us", 20 | flag: "🇺🇸", 21 | }, 22 | { 23 | label: "Pakistan", 24 | value: "pk", 25 | flag: "🇵🇰", 26 | }, 27 | { 28 | label: "India", 29 | value: "in", 30 | flag: "🇮🇳", 31 | }, 32 | { 33 | label: "Finland", 34 | value: "fn", 35 | flag: "🇫🇮", 36 | }, 37 | { 38 | label: "France", 39 | value: "fr", 40 | flag: "🇫🇷", 41 | }, 42 | ]; -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import { StatusBar } from "expo-status-bar"; 2 | import { StyleSheet, Text, View } from "react-native"; 3 | import Dropdown from "./components/DropDown"; 4 | import { countries } from "./utils/countries"; 5 | 6 | const formattedCountries = countries.map((c) => ({ 7 | value: c.label, 8 | label: `${c.flag} ${c.label}`, 9 | })); 10 | 11 | export default function App() { 12 | return ( 13 | 14 | 15 | 20 | 21 | 30 | 31 | ); 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | container: { 36 | flex: 1, 37 | backgroundColor: "#ddd", 38 | alignItems: "center", 39 | justifyContent: "center", 40 | paddingHorizontal: 20, 41 | gap: 10, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /components/DropDown.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | View, 3 | Text, 4 | TouchableOpacity, 5 | StyleSheet, 6 | FlatList, 7 | Modal, 8 | TouchableWithoutFeedback, 9 | Platform, 10 | } from "react-native"; 11 | import React, { useCallback, useRef, useState } from "react"; 12 | import { AntDesign } from "@expo/vector-icons"; 13 | 14 | type OptionItem = { 15 | value: string; 16 | label: string; 17 | }; 18 | 19 | interface DropDownProps { 20 | data: OptionItem[]; 21 | onChange: (item: OptionItem) => void; 22 | placeholder: string; 23 | } 24 | 25 | export default function Dropdown({ 26 | data, 27 | onChange, 28 | placeholder, 29 | }: DropDownProps) { 30 | const [expanded, setExpanded] = useState(false); 31 | 32 | const toggleExpanded = useCallback(() => setExpanded(!expanded), [expanded]); 33 | 34 | const [value, setValue] = useState(""); 35 | 36 | const buttonRef = useRef(null); 37 | 38 | const [top, setTop] = useState(0); 39 | 40 | const onSelect = useCallback((item: OptionItem) => { 41 | onChange(item); 42 | setValue(item.label); 43 | setExpanded(false); 44 | }, []); 45 | return ( 46 | { 49 | const layout = event.nativeEvent.layout; 50 | const topOffset = layout.y; 51 | const heightOfComponent = layout.height; 52 | 53 | const finalValue = 54 | topOffset + heightOfComponent + (Platform.OS === "android" ? -32 : 3); 55 | 56 | setTop(finalValue); 57 | }} 58 | > 59 | 64 | {value || placeholder} 65 | 66 | 67 | {expanded ? ( 68 | 69 | setExpanded(false)}> 70 | 71 | 79 | item.value} 81 | data={data} 82 | renderItem={({ item }) => ( 83 | onSelect(item)} 87 | > 88 | {item.label} 89 | 90 | )} 91 | ItemSeparatorComponent={() => ( 92 | 93 | )} 94 | /> 95 | 96 | 97 | 98 | 99 | ) : null} 100 | 101 | ); 102 | } 103 | 104 | const styles = StyleSheet.create({ 105 | backdrop: { 106 | padding: 20, 107 | justifyContent: "center", 108 | alignItems: "center", 109 | flex: 1, 110 | }, 111 | optionItem: { 112 | height: 40, 113 | justifyContent: "center", 114 | }, 115 | separator: { 116 | height: 4, 117 | }, 118 | options: { 119 | position: "absolute", 120 | // top: 53, 121 | backgroundColor: "white", 122 | width: "100%", 123 | padding: 10, 124 | borderRadius: 6, 125 | maxHeight: 250, 126 | }, 127 | text: { 128 | fontSize: 15, 129 | opacity: 0.8, 130 | }, 131 | button: { 132 | height: 50, 133 | justifyContent: "space-between", 134 | backgroundColor: "#fff", 135 | flexDirection: "row", 136 | width: "100%", 137 | alignItems: "center", 138 | paddingHorizontal: 15, 139 | borderRadius: 8, 140 | }, 141 | }); 142 | --------------------------------------------------------------------------------