├── .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 |
--------------------------------------------------------------------------------