├── exampleapp ├── .watchmanconfig ├── .editorconfig ├── app.json ├── babel.config.js ├── z.jpg ├── .prettierrc.js ├── .buckconfig ├── .gitattributes ├── index.js ├── metro.config.js ├── __tests__ │ ├── App.js │ └── __snapshots__ │ │ └── App.js.snap ├── package.json ├── .gitignore ├── .flowconfig ├── AppFunctional.tsx ├── App.js └── AppWithTypes.tsx ├── lib ├── .vscode │ ├── settings.json │ └── launch.json ├── helpers.js ├── components │ ├── index.js │ ├── ItemIcon.js │ ├── RowSubItem.js │ └── RowItem.js └── sectioned-multi-select.js ├── index.js ├── previews ├── example_recording-1.gif ├── example_recording-2.gif ├── example_recording-3.gif └── example_recording-4.gif ├── .prettierrc.js ├── .gitignore ├── .eslintrc.json ├── .github └── workflows │ └── main.yml ├── LICENSE ├── package.json ├── index.d.ts ├── Recipes.md ├── CHANGELOG.md └── README.md /exampleapp/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /lib/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /exampleapp/.editorconfig: -------------------------------------------------------------------------------- 1 | # Windows files 2 | [*.bat] 3 | end_of_line = crlf 4 | -------------------------------------------------------------------------------- /exampleapp/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exampleapp", 3 | "displayName": "exampleapp" 4 | } -------------------------------------------------------------------------------- /exampleapp/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /exampleapp/z.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renrizzolo/react-native-sectioned-multi-select/HEAD/exampleapp/z.jpg -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import SectionedMultiSelect from './lib/sectioned-multi-select' 2 | 3 | export default SectionedMultiSelect 4 | -------------------------------------------------------------------------------- /previews/example_recording-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renrizzolo/react-native-sectioned-multi-select/HEAD/previews/example_recording-1.gif -------------------------------------------------------------------------------- /previews/example_recording-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renrizzolo/react-native-sectioned-multi-select/HEAD/previews/example_recording-2.gif -------------------------------------------------------------------------------- /previews/example_recording-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renrizzolo/react-native-sectioned-multi-select/HEAD/previews/example_recording-3.gif -------------------------------------------------------------------------------- /previews/example_recording-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renrizzolo/react-native-sectioned-multi-select/HEAD/previews/example_recording-4.gif -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | singleQuote: true, 4 | semi: false, 5 | trailingComma: 'none' 6 | } 7 | -------------------------------------------------------------------------------- /exampleapp/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | singleQuote: true, 4 | semi: false, 5 | trailingComma: 'none' 6 | } 7 | -------------------------------------------------------------------------------- /exampleapp/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /exampleapp/.gitattributes: -------------------------------------------------------------------------------- 1 | # Windows files should use crlf line endings 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | export const callIfFunction = (maybeFn) => 2 | maybeFn 3 | ? maybeFn && typeof maybeFn === 'function' 4 | ? maybeFn() 5 | : maybeFn 6 | : null 7 | -------------------------------------------------------------------------------- /lib/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as RowItem } from './RowItem' 2 | export { default as RowSubItem } from './RowSubItem' 3 | export { default as ItemIcon } from './ItemIcon' 4 | -------------------------------------------------------------------------------- /exampleapp/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import { AppRegistry } from 'react-native' 6 | import App from './App' 7 | import { name as appName } from './app.json' 8 | 9 | AppRegistry.registerComponent(appName, () => App) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Dependency directories 10 | node_modules/ 11 | 12 | # exampleapp 13 | exampleapp/android/ 14 | exampleapp/ios/ 15 | exampleapp/third-party/ -------------------------------------------------------------------------------- /exampleapp/metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: true, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /lib/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${file}" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true 5 | }, 6 | "plugins": ["react"], 7 | "extends": [ 8 | "prettier", 9 | "@react-native-community", 10 | "plugin:react-native-a11y/all" 11 | ], 12 | "rules": { 13 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 14 | "react/require-default-props": [0], 15 | "semi": ["error", "never"], 16 | "comma-dangle": ["error", "never"], 17 | "no-underscore-dangle": ["error", { "allowAfterThis": true }], 18 | "react-native/no-inline-styles": 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v4 11 | with: 12 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' 13 | stale-pr-message: 'This PR is stale because it has been open 45 days with no activity.' 14 | close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.' 15 | days-before-stale: 30 16 | days-before-close: 5 17 | days-before-pr-close: -1 18 | -------------------------------------------------------------------------------- /exampleapp/__tests__/App.js: -------------------------------------------------------------------------------- 1 | import 'react-native' 2 | import React from 'react' 3 | import SectionedMultiSelect from 'react-native-sectioned-multi-select' 4 | import App from '../App' 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer' 7 | 8 | // 'it probably didn't break' 9 | 10 | const tree = renderer.create() 11 | it('renders example app correctly', () => { 12 | tree 13 | }) 14 | it('matches the snapshot', () => { 15 | // make assertions on tree 16 | expect(tree.toJSON()).toMatchSnapshot() 17 | }) 18 | 19 | const testInstance = tree.root 20 | it('has an expected prop', () => { 21 | expect(testInstance.findAllByType(SectionedMultiSelect)[0].props.single).toBe( 22 | false 23 | ) 24 | }) 25 | -------------------------------------------------------------------------------- /lib/components/ItemIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Image } from 'react-native' 4 | 5 | const ItemIcon = ({ iconRenderer: Icon, icon, iconKey, itemIconStyle }) => { 6 | return typeof icon === 'object' || typeof icon === 'number' ? ( 7 | 19 | ) : ( 20 | 21 | ) 22 | } 23 | ItemIcon.propTypes = { 24 | iconKey: PropTypes.string, 25 | itemIconStyle: PropTypes.object, 26 | icon: PropTypes.oneOfType([ 27 | PropTypes.string, 28 | PropTypes.shape({ 29 | uri: PropTypes.string 30 | }), 31 | PropTypes.number 32 | ]).isRequired 33 | } 34 | export default ItemIcon 35 | -------------------------------------------------------------------------------- /exampleapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exampleapp", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "test": "jest", 10 | "lint": "eslint ." 11 | }, 12 | "dependencies": { 13 | "react": "17.0.2", 14 | "react-native": "0.66.4", 15 | "react-native-sectioned-multi-select": "github:renrizzolo/react-native-sectioned-multi-select#master", 16 | "react-native-vector-icons": "^9.0.0" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.12.9", 20 | "@babel/runtime": "^7.12.5", 21 | "@react-native-community/eslint-config": "^3.0.1", 22 | "@types/react": "^17.0.37", 23 | "@types/react-native": "^0.66.9", 24 | "babel-jest": "^27.4.5", 25 | "eslint": "^7.32.0", 26 | "jest": "^27.4.5", 27 | "metro-react-native-babel-preset": "^0.66.2", 28 | "react-test-renderer": "17.0.2" 29 | }, 30 | "jest": { 31 | "preset": "react-native" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /exampleapp/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | *.hprof 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | !debug.keystore 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://docs.fastlane.tools/best-practices/source-control/ 51 | 52 | */fastlane/report.xml 53 | */fastlane/Preview.html 54 | */fastlane/screenshots 55 | 56 | # Bundle artifact 57 | *.jsbundle 58 | 59 | # CocoaPods 60 | /ios/Pods/ 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ren 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-sectioned-multi-select", 3 | "version": "0.10.0", 4 | "description": "a multi (or single) select component with support for sub categories, search, chips.", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "lint": "eslint ./lib/**/*.{js,jsx} --fix", 11 | "format": "prettier ./lib/**/*.{js,jsx} --write && npm run lint --fix", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "dependencies": { 15 | "lodash.isequal": "^4.5.0", 16 | "memoize-one": "^6.0.0" 17 | }, 18 | "devDependencies": { 19 | "@react-native-community/eslint-config": "^3.0.1", 20 | "eslint": "^7.32.0", 21 | "eslint-plugin-flowtype": "^8.0.3", 22 | "eslint-plugin-react-native-a11y": "^3.0.0", 23 | "prettier": "^2.5.1" 24 | }, 25 | "peerDependencies": { 26 | "prop-types": "^15.6.0" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/renrizzolo/react-native-sectioned-multi-select.git" 31 | }, 32 | "keywords": [ 33 | "select", 34 | "multiselect", 35 | "picker", 36 | "category", 37 | "react", 38 | "native" 39 | ], 40 | "author": "Ren Rizzolo", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/renrizzolo/react-native-sectioned-multi-select/issues" 44 | }, 45 | "homepage": "https://github.com/renrizzolo/react-native-sectioned-multi-select#readme" 46 | } 47 | -------------------------------------------------------------------------------- /exampleapp/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore polyfills 9 | node_modules/react-native/Libraries/polyfills/.* 10 | 11 | ; Flow doesn't support platforms 12 | .*/Libraries/Utilities/LoadingView.js 13 | 14 | [untyped] 15 | .*/node_modules/@react-native-community/cli/.*/.* 16 | 17 | [include] 18 | 19 | [libs] 20 | node_modules/react-native/interface.js 21 | node_modules/react-native/flow/ 22 | 23 | [options] 24 | emoji=true 25 | 26 | exact_by_default=true 27 | 28 | format.bracket_spacing=false 29 | 30 | module.file_ext=.js 31 | module.file_ext=.json 32 | module.file_ext=.ios.js 33 | 34 | munge_underscores=true 35 | 36 | module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' 37 | module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' 38 | 39 | suppress_type=$FlowIssue 40 | suppress_type=$FlowFixMe 41 | suppress_type=$FlowFixMeProps 42 | suppress_type=$FlowFixMeState 43 | 44 | [lints] 45 | sketchy-null-number=warn 46 | sketchy-null-mixed=warn 47 | sketchy-number=warn 48 | untyped-type-import=warn 49 | nonstrict-import=warn 50 | deprecated-type=warn 51 | unsafe-getters-setters=warn 52 | unnecessary-invariant=warn 53 | signature-verification-failure=warn 54 | 55 | [strict] 56 | deprecated-type 57 | nonstrict-import 58 | sketchy-null 59 | unclear-type 60 | unsafe-getters-setters 61 | untyped-import 62 | untyped-type-import 63 | 64 | [version] 65 | ^0.158.0 66 | -------------------------------------------------------------------------------- /exampleapp/AppFunctional.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { View, Button, Text, ScrollView, TouchableOpacity } from 'react-native' 3 | import SectionedMultiSelect from 'react-native-sectioned-multi-select' 4 | import Icon from 'react-native-vector-icons/MaterialIcons' 5 | import { Colors } from 'react-native/Libraries/NewAppScreen' 6 | 7 | type ItemType = { 8 | title: string 9 | id: number 10 | } 11 | const items = [ 12 | { 13 | id: 1, 14 | title: 'Item 1 234234' 15 | }, 16 | { 17 | id: 2, 18 | title: 'Item 2 asdfsa' 19 | }, 20 | { 21 | id: 3, 22 | title: 'Item 3 asdf d' 23 | }, 24 | { 25 | id: 4, 26 | title: 'Item 4d a' 27 | } 28 | ] 29 | 30 | const App: React.FC = () => { 31 | const ref = useRef>() 32 | const [selectedItems, setSelectedItems] = React.useState([]) 33 | 34 | const removeAll = () => { 35 | ref.current?._removeAllItems() 36 | } 37 | const toggle = () => { 38 | ref.current?._toggleSelector() 39 | } 40 | console.log(selectedItems) 41 | return ( 42 | 43 | 44 | uniqueKey="id" 45 | displayKey="title" 46 | onSelectedItemsChange={setSelectedItems} 47 | selectedItems={selectedItems} 48 | items={items} 49 | ref={ref} 50 | customChipsRenderer={(props) => ( 51 | 64 | {selectedItems.map((itemId) => { 65 | const item = items.find(({ id }) => id === itemId) 66 | return ( 67 | 81 | 89 | {item.title} 90 | 91 | ref.current?._removeItem(item)} 94 | > 95 | 96 | 97 | 98 | ) 99 | })} 100 | 101 | )} 102 | IconRenderer={Icon} 103 | icons={{ 104 | check: { 105 | name: 'check-circle', 106 | style: { 107 | color: Colors.secondary 108 | }, 109 | size: 22 110 | }, 111 | search: { 112 | name: 'search', 113 | color: '#333', 114 | size: 22 115 | } 116 | }} 117 | /> 118 |