├── .eslintignore ├── .eslintrc.json ├── .expo-shared └── assets.json ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .vscode └── settings.json ├── App.tsx ├── LICENSE ├── README.md ├── app.json ├── assets ├── adaptive-icon.png ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── package-lock.json ├── package.json ├── src └── components │ └── Dropdown.tsx └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | *.css 2 | *.svg -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "plugin:react/recommended", 8 | "airbnb", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:prettier/recommended" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "jsx": true 16 | }, 17 | "ecmaVersion": 12, 18 | "sourceType": "module" 19 | }, 20 | "plugins": [ 21 | "react", 22 | "@typescript-eslint", 23 | "react-hooks" 24 | ], 25 | "rules": { 26 | "no-use-before-define": "off", 27 | "@typescript-eslint/no-use-before-define": [ 28 | "warn" 29 | ], 30 | "react/jsx-filename-extension": [ 31 | "warn", 32 | { 33 | "extensions": [ 34 | ".tsx" 35 | ] 36 | } 37 | ], 38 | "import/extensions": [ 39 | "error", 40 | "ignorePackages", 41 | { 42 | "ts": "never", 43 | "tsx": "never" 44 | } 45 | ], 46 | "no-shadow": "off", 47 | "@typescript-eslint/no-shadow": [ 48 | "error" 49 | ], 50 | "@typescript-eslint/explicit-function-return-type": [ 51 | "error", 52 | { 53 | "allowExpressions": true 54 | } 55 | ], 56 | "react/function-component-definition": [ 57 | 2, 58 | { 59 | "namedComponents": "arrow-function", 60 | "unnamedComponents": "arrow-function" 61 | } 62 | ], 63 | "max-len": [ 64 | "warn", 65 | { 66 | "code": 80 67 | } 68 | ], 69 | "no-unused-expressions": [2, { 70 | "allowTernary": true 71 | }], 72 | "react-hooks/rules-of-hooks": "error", 73 | "react-hooks/exhaustive-deps": "warn", 74 | "import/prefer-default-export": "off", 75 | "react/prop-types": "off" 76 | }, 77 | "settings": { 78 | "import/resolver": { 79 | "typescript": { 80 | 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | 12 | # macOS 13 | .DS_Store 14 | 15 | tools/*.mov 16 | tools/*.gif -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "printWidth": 80 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "eslint.alwaysShowStatus": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true 6 | } 7 | } -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | import Dropdown from './src/components/Dropdown'; 4 | 5 | const App: FC = () => { 6 | const [selected, setSelected] = useState(undefined); 7 | const data = [ 8 | { label: 'One', value: '1' }, 9 | { label: 'Two', value: '2' }, 10 | { label: 'Three', value: '3' }, 11 | { label: 'Four', value: '4' }, 12 | { label: 'Five', value: '5' }, 13 | ]; 14 | 15 | return ( 16 | 17 | {!!selected && ( 18 | 19 | Selected: label = {selected.label} and value = {selected.value} 20 | 21 | )} 22 | 23 | This is the rest of the form. 24 | 25 | ); 26 | }; 27 | 28 | const styles = StyleSheet.create({ 29 | container: { 30 | flex: 1, 31 | backgroundColor: '#fff', 32 | alignItems: 'center', 33 | justifyContent: 'center', 34 | flexDirection: 'column', 35 | }, 36 | }); 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Stephan Miller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # log-rocket-react-native-dropdown-tutoria 2 | Log Rocket React Native Dropdown Tutorial 3 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "tutorial-app", 4 | "slug": "tutorial-app", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": [ 17 | "**/*" 18 | ], 19 | "ios": { 20 | "supportsTablet": true 21 | }, 22 | "android": { 23 | "adaptiveIcon": { 24 | "foregroundImage": "./assets/adaptive-icon.png", 25 | "backgroundColor": "#FFFFFF" 26 | } 27 | }, 28 | "web": { 29 | "favicon": "./assets/favicon.png" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eristoddle/log-rocket-react-native-dropdown-tutoria/6bbad42e1db45d3ea44514e5c56951e8d7464278/assets/adaptive-icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eristoddle/log-rocket-react-native-dropdown-tutoria/6bbad42e1db45d3ea44514e5c56951e8d7464278/assets/favicon.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eristoddle/log-rocket-react-native-dropdown-tutoria/6bbad42e1db45d3ea44514e5c56951e8d7464278/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eristoddle/log-rocket-react-native-dropdown-tutoria/6bbad42e1db45d3ea44514e5c56951e8d7464278/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tutorial-app", 3 | "version": "1.0.0", 4 | "main": "node_modules/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 | "eject": "expo eject", 11 | "format": "prettier --write src/**/*.ts{,x}", 12 | "lint": "tsc --noEmit && eslint src/**/*.ts{,x}" 13 | }, 14 | "dependencies": { 15 | "expo": "~41.0.0-alpha.0", 16 | "expo-status-bar": "~1.0.4", 17 | "react": "16.13.1", 18 | "react-dom": "16.13.1", 19 | "react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz", 20 | "react-native-elements": "^3.4.2", 21 | "react-native-web": "~0.13.12", 22 | "typescript": "^4.5.2" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "~7.9.0", 26 | "@types/react": "~16.9.35", 27 | "@types/react-native": "~0.63.2", 28 | "@typescript-eslint/eslint-plugin": "^5.4.0", 29 | "@typescript-eslint/parser": "^5.4.0", 30 | "eslint": "^8.3.0", 31 | "eslint-config-airbnb": "^19.0.1", 32 | "eslint-config-prettier": "^8.3.0", 33 | "eslint-import-resolver-typescript": "^2.5.0", 34 | "eslint-plugin-import": "^2.25.3", 35 | "eslint-plugin-jsx-a11y": "^6.5.1", 36 | "eslint-plugin-prettier": "^4.0.0", 37 | "eslint-plugin-react": "^7.27.1", 38 | "eslint-plugin-react-hooks": "^4.3.0", 39 | "prettier": "2.4.1" 40 | }, 41 | "private": true 42 | } 43 | -------------------------------------------------------------------------------- /src/components/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactElement, useRef, useState } from 'react'; 2 | import { 3 | FlatList, 4 | StyleSheet, 5 | Text, 6 | TouchableOpacity, 7 | Modal, 8 | View, 9 | } from 'react-native'; 10 | import { Icon } from 'react-native-elements'; 11 | 12 | interface Props { 13 | label: string; 14 | data: Array<{ label: string; value: string }>; 15 | onSelect: (item: { label: string; value: string }) => void; 16 | } 17 | 18 | const Dropdown: FC = ({ label, data, onSelect }) => { 19 | const DropdownButton = useRef(); 20 | const [visible, setVisible] = useState(false); 21 | const [selected, setSelected] = useState(undefined); 22 | const [dropdownTop, setDropdownTop] = useState(0); 23 | 24 | const toggleDropdown = (): void => { 25 | visible ? setVisible(false) : openDropdown(); 26 | }; 27 | 28 | const openDropdown = (): void => { 29 | DropdownButton.current.measure((_fx, _fy, _w, h, _px, py) => { 30 | setDropdownTop(py + h); 31 | }); 32 | setVisible(true); 33 | }; 34 | 35 | const onItemPress = (item): void => { 36 | setSelected(item); 37 | onSelect(item); 38 | setVisible(false); 39 | }; 40 | 41 | const renderItem = ({ item }): ReactElement => ( 42 | onItemPress(item)}> 43 | {item.label} 44 | 45 | ); 46 | 47 | const renderDropdown = (): ReactElement => { 48 | return ( 49 | 50 | setVisible(false)} 53 | > 54 | 55 | index.toString()} 59 | /> 60 | 61 | 62 | 63 | ); 64 | }; 65 | 66 | return ( 67 | 72 | {renderDropdown()} 73 | 74 | {(selected && selected.label) || label} 75 | 76 | 77 | 78 | ); 79 | }; 80 | 81 | const styles = StyleSheet.create({ 82 | button: { 83 | flexDirection: 'row', 84 | alignItems: 'center', 85 | backgroundColor: '#efefef', 86 | height: 50, 87 | zIndex: 1, 88 | }, 89 | buttonText: { 90 | flex: 1, 91 | textAlign: 'center', 92 | }, 93 | icon: { 94 | marginRight: 10, 95 | }, 96 | dropdown: { 97 | position: 'absolute', 98 | backgroundColor: '#fff', 99 | width: '100%', 100 | shadowColor: '#000000', 101 | shadowRadius: 4, 102 | shadowOffset: { height: 4, width: 0 }, 103 | shadowOpacity: 0.5, 104 | }, 105 | overlay: { 106 | width: '100%', 107 | height: '100%', 108 | }, 109 | item: { 110 | paddingHorizontal: 10, 111 | paddingVertical: 10, 112 | borderBottomWidth: 1, 113 | }, 114 | }); 115 | 116 | export default Dropdown; 117 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": {}, 3 | "extends": "expo/tsconfig.base" 4 | } 5 | --------------------------------------------------------------------------------