Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addition of multi-select feature. #127

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
no-console: 1,
no-unused-vars: 1,
jsx-quotes: ["error", "prefer-double"],
no-unused-expressions: 2,
no-unused-expressions: "off",
require-await: 2,
no-irregular-whitespace: 2,
arrow-spacing: "error",
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ If you have an urgent problem, hire a mentor for a 1:1 live session on Git-Start
Help others in paid 1:1 live sessions to get started.
[![Give paid 1:1 live support.](https://git-start.com/assets/git-start-mentor-banner-medium.svg?sanitize=true)](https://git-start.com/help-request/overview/peacechen%2Freact-native-modal-selector)

## Breaking changes

Version 2.0.0 onwards support multi-select feature. As such, following properties from the versions before have changed.

1) `selectedKey` is now `initSelectedKeys`. It accepts an array of keys.
2) `getSelectedItem` is now `getSelectedItems`. It returns an array of selected items.

## Usage

Expand Down Expand Up @@ -136,7 +142,7 @@ return (
Prop | Type | Optional | Default | Description
------------------- | -------- | -------- | ------------ | -----------
`data` | array | No | [] | array of objects with a unique `key` and `label` to select in the modal. Optional `component` overrides label text.
`onChange` | function | Yes | () => {} | callback function, when the users has selected an option
`onChange` | function | Yes | () => {} | callback function, when the users has selected an option. Returns an array of selected item/items.
`onModalOpen` | function | Yes | () => {} | callback function, when modal is opening
`onModalClose` | function | Yes | (item) => {} | callback function, when modal is closing. Returns the selected item.
`keyExtractor`      | function | Yes     | (data) => data.key   | extract the key from the data item
Expand Down Expand Up @@ -179,8 +185,10 @@ Prop | Type | Optional | Default | Description
`cancelButtonAccessibilityLabel` | string | Yes | undefined | Accessibility label for the cancel button
`modalOpenerHitSlop` | object | Yes | {} | How far touch can stray away from touchable that opens modal ([RN docs](https://facebook.github.io/react-native/docs/touchablewithoutfeedback.html#hitslop))
`customSelector` | node | Yes | undefined | Render a custom node instead of the built-in select box.
`selectedKey` | any | Yes | '' | Key of the item to be initially selected
`initSelectedKeys` | array<any> | Yes | [] | Key of the items to be initially selected. Should contain only one key if `multiple` is selected as false.
`multiple` | bool | No | false | Adds the ability to select multiple options.
`renderCheckbox` | function | Yes | (checked, onPress) => {} | Function that returns a checkbox element. Arguments of the function include `checked` state and `onPress` function that gets executed when the element is pressed. Required only if multiple option is selected.

### Methods

`getSelectedItem()`: get current selected item, updated by onChange event.
`getSelectedItems()`: get current selected items, updated by onChange event.
4 changes: 4 additions & 0 deletions SampleApp/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: '@react-native-community',
};
74 changes: 40 additions & 34 deletions SampleApp/.flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/

; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*

; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
.*/Libraries/react-native/React.js
; These should not be required directly
; require from fbjs/lib instead: require('fbjs/lib/warning')
node_modules/warning/.*

; Ignore polyfills
.*/Libraries/polyfills/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js

; Ignore metro
.*/node_modules/metro/.*
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*

[include]

Expand All @@ -31,39 +30,46 @@ emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable

module.system=haste
module.system.haste.use_name_reducers=true
# get basename
module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
# strip .js or .js.flow suffix
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
# strip .ios suffix
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
module.system.haste.paths.blacklist=.*/__tests__/.*
module.system.haste.paths.blacklist=.*/__mocks__/.*
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
module.file_ext=.js
module.file_ext=.json
module.file_ext=.ios.js

munge_underscores=true

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\)$' -> 'RelativeImageStub'

module.file_ext=.js
module.file_ext=.jsx
module.file_ext=.json
module.file_ext=.native.js
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/react-native/react-native-implementation'
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
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\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'

suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState

suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError

[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
sketchy-number=warn
untyped-type-import=warn
nonstrict-import=warn
deprecated-type=warn
unsafe-getters-setters=warn
inexact-spread=warn
unnecessary-invariant=warn
signature-verification-failure=warn
deprecated-utility=error

[strict]
deprecated-type
nonstrict-import
sketchy-null
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import

[version]
^0.92.0
^0.105.0
5 changes: 4 additions & 1 deletion SampleApp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace

# Android/IntelliJ
#
Expand All @@ -40,6 +39,7 @@ yarn-error.log
buck-out/
\.buckd/
*.keystore
!debug.keystore

# fastlane
#
Expand All @@ -54,3 +54,6 @@ buck-out/

# Bundle artifact
*.jsbundle

# CocoaPods
/ios/Pods/
6 changes: 6 additions & 0 deletions SampleApp/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
bracketSpacing: false,
jsxBracketSameLine: true,
singleQuote: true,
trailingComma: 'all',
};
167 changes: 96 additions & 71 deletions SampleApp/App.js
Original file line number Diff line number Diff line change
@@ -1,89 +1,114 @@
'use strict';

import React, { Component } from 'react';
import React, {Component} from 'react';

import {
View,
Text,
TextInput,
Switch
} from 'react-native';
import {View, Text, TextInput, Switch} from 'react-native';
import {CheckBox} from 'react-native-elements';

import ModalSelector from 'react-native-modal-selector'
import Icon from 'react-native-vector-icons/FontAwesome';

class SampleApp extends Component {

constructor() {
super();
import ModalSelector from 'react-native-modal-selector';

this.state = {
textInputValue: ''
}
}
Icon.loadFont();

render() {
let index = 0;
const data = [
{ key: index++, section: true, label: 'Fruits' },
{ key: index++, label: 'Red Apples', component: <View style={{backgroundColor: 'red', borderRadius: 5, alignItems: 'center'}}><Text style={{color: 'white'}}>Red Apples custom component ☺</Text></View> },
{ key: index++, label: 'Cherries' },
{ key: index++, label: 'Cranberries' },
{ key: index++, label: 'Pink Grapefruit' },
{ key: index++, label: 'Raspberries' },
{ key: index++, section: true, label: 'Vegetables' },
{ key: index++, label: 'Beets' },
{ key: index++, label: 'Red Peppers' },
{ key: index++, label: 'Radishes' },
{ key: index++, label: 'Radicchio' },
{ key: index++, label: 'Red Onions' },
{ key: index++, label: 'Red Potatoes' },
{ key: index++, label: 'Rhubarb' },
{ key: index++, label: 'Tomatoes' }
];
class SampleApp extends Component {
constructor() {
super();
this.state = {
textInputValue: '',
};
}

return (
<View style={{ flex: 1, justifyContent: 'space-around', padding: 50 }}>
render() {
let index = 0;
const data = [
{key: index++, section: true, label: 'Fruits'},
{
key: index++,
label: 'Red Apples',
component: (
<View
style={{
backgroundColor: 'red',
borderRadius: 5,
alignItems: 'center',
}}>
<Text style={{color: 'white'}}>Red Apples custom component ☺</Text>
</View>
),
},
{key: index++, label: 'Cherries'},
{key: index++, label: 'Cranberries'},
{key: index++, label: 'Pink Grapefruit'},
{key: index++, label: 'Raspberries'},
{key: index++, section: true, label: 'Vegetables'},
{key: index++, label: 'Beets'},
{key: index++, label: 'Red Peppers'},
{key: index++, label: 'Radishes'},
{key: index++, label: 'Radicchio'},
{key: index++, label: 'Red Onions'},
{key: index++, label: 'Red Potatoes'},
{key: index++, label: 'Rhubarb'},
{key: index++, label: 'Tomatoes'},
];

{ /* Default mode: a clickable button will re rendered */ }
<ModalSelector
data={data}
initValue="Select something yummy!"
onChange={option => { alert(`${option.label} (${option.key}) nom nom nom`) }} />
return (
<View style={{flex: 1, justifyContent: 'space-around', padding: 50}}>
{/* Default mode: a clickable button will re rendered */}
<ModalSelector
data={data}
multiple={true}
renderCheckbox={(checked, onPress) => (
<CheckBox onPress={onPress} checked={checked} />
)}
initValue="Select something yummy!"
onChange={option => {
alert(`${JSON.stringify(option)} nom nom nom`);
}}
/>

{ /*
{/*
Wrapper mode: just wrap your existing component with ModalSelector.
When the user clicks on your element, the modal selector is shown.
*/ }
<ModalSelector
data={data}
initValue="Select something yummy!"
onChange={option => { this.setState({textInputValue:option.label}) }}>

<TextInput
style={{ borderWidth: 1, borderColor: '#ccc', padding: 10, height: 40 }}
editable={false}
placeholder="Select something yummy!"
value={this.state.textInputValue} />

</ModalSelector>
*/}
<ModalSelector
data={data}
initValue="Select something yummy!"
onChange={option => {
this.setState({textInputValue: option.label});
}}>
<TextInput
style={{
borderWidth: 1,
borderColor: '#ccc',
padding: 10,
height: 40,
}}
editable={false}
placeholder="Select something yummy!"
value={this.state.textInputValue}
/>
</ModalSelector>

{ /*
{/*
Custom mode: you have to provide a react-native component that have to
control how the selector should open (and for this you need a ref to the modal)
*/ }
<ModalSelector
data={data}
ref={selector => { this.selector = selector; }}
customSelector={
<Switch
style={{ alignSelf: 'center' }}
onValueChange={() => this.selector.open()}
/>
}
/>
</View>
);
}
*/}
<ModalSelector
data={data}
ref={selector => {
this.selector = selector;
}}
customSelector={
<Switch
style={{alignSelf: 'center'}}
onValueChange={() => this.selector.open()}
/>
}
/>
</View>
);
}
}

export default SampleApp;
6 changes: 3 additions & 3 deletions SampleApp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This demo app demonstrates how to use this component.

```
$ npm install
$ cd ios && pod install
$ react-native run-[ios|android]
```

Expand All @@ -13,13 +14,12 @@ Use [nodemon](https://github.com/remy/nodemon) to keep the module in sync with `

```
$ npm install
$ react-native run-[ios|android]
$ npx react-native run-[ios|android]
$ npm run nodemon
```

You can now edit the files in `../`, and they will be synced to `node_modules/react-native-modal-selector/`


# Modifying react-native-modal-selector (RNMS) code during development

The RNMS source code is copied post-install due to a circular symlink bug. If the RNMS code changes, you must run `npm run postinstall` to update it in this sample app.
The RNMS source code is copied post-install due to a circular symlink bug. If the RNMS code changes, you must run `npm run postinstall` to update it in this sample app.
File renamed without changes.
Loading