Skip to content

Commit

Permalink
Merge pull request #37 from azeezat/search-fixes
Browse files Browse the repository at this point in the history
fix: Search and select all issues
  • Loading branch information
azeezat authored Sep 7, 2023
2 parents ee7f98a + 9ac7d59 commit 895bb38
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 58 deletions.
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
[![NPM](https://nodei.co/npm/react-native-input-select.png?downloads=true)](https://nodei.co/npm/react-native-input-select/)

[![npm version](https://badge.fury.io/js/react-native-input-select.svg)](https://badge.fury.io/js/react-native-input-select) [![GitHub stars](https://img.shields.io/github/stars/azeezat/react-native-select?style=social)](https://github.com/azeezat/react-native-select/stargazers) [![CodeQL](https://github.com/azeezat/react-native-select/actions/workflows/codeql.yml/badge.svg)](https://github.com/azeezat/react-native-select/actions/workflows/codeql.yml) [![Release & Publish to NPM](https://github.com/azeezat/react-native-select/actions/workflows/release-and-publish-to-npm.yml/badge.svg)](https://github.com/azeezat/react-native-select/actions/workflows/release-and-publish-to-npm.yml)

[![npm version](https://badge.fury.io/js/react-native-input-select.svg)](https://badge.fury.io/js/react-native-input-select) [![GitHub stars](https://img.shields.io/github/stars/azeezat/react-native-select?style=social)](https://github.com/azeezat/react-native-select/stargazers) [![CodeQL](https://github.com/azeezat/react-native-select/actions/workflows/codeql.yml/badge.svg)](https://github.com/azeezat/react-native-select/actions/workflows/codeql.yml) [![Release & Publish to NPM](https://github.com/azeezat/react-native-select/actions/workflows/release-and-publish-to-npm.yml/badge.svg)](https://github.com/azeezat/react-native-select/actions/workflows/release-and-publish-to-npm.yml)

# react-native-input-select

Expand Down Expand Up @@ -238,6 +237,7 @@ For more examples visit our [wiki page](https://github.com/azeezat/react-native-
| <img width="456" alt="Screenshot 2023-05-16 at 6 17 09 AM" src="https://github.com/azeezat/react-native-select/assets/9849221/d695657f-d840-4368-b841-42af479d6543"> | <img width="456" alt="Screenshot 2023-03-23 at 5 26 58 PM" src="https://user-images.githubusercontent.com/9849221/227393548-28796d7b-9760-43a9-8ed3-fb1618cd1b7d.png"> | <img width="456" alt="Screenshot 2023-03-23 at 5 28 49 PM" src="https://user-images.githubusercontent.com/9849221/227393554-91ed1a92-d229-4814-84d8-5f9095e8d048.png"> |

## Props

| Proptypes | Datatype | Example |
| -------------------------- | ------------------------ | -------------------------------------------------------------------- |
| label | `string` | Countries |
Expand All @@ -258,7 +258,6 @@ For more examples visit our [wiki page](https://github.com/azeezat/react-native-
| dropdownStyle | `Object` | `{borderColor: 'blue', margin: 5, borderWidth:0 ...}` |
| dropdownContainerStyle | `Object` | `{backgroundColor: 'red', width: '30%', ...}` |
| dropdownIconStyle | `Object` | `{top: 10 , right: 10, ...}` |
| searchInputStyle | `Object` | `{backgroundColor: 'red', borderRadius: 0, ...}` |
| selectedItemStyle | `Object` | `{fontWeight: '600', color: 'yellow', ...}` |
| multipleSelectedItemStyle | `Object` | `{backgroundColor: 'red', color: 'yellow', ...}` |
| modalBackgroundStyle | `Object` | `{backgroundColor: 'rgba(196, 198, 246, 0.5)'}` |
Expand All @@ -275,6 +274,7 @@ For more examples visit our [wiki page](https://github.com/azeezat/react-native-
| checkboxComponentStyles | `Object` | `{checkboxSize?: number, checkboxStyle?: ViewStyle, checkboxLabelStyle: TextStyle}` |
| checkboxComponent | `React Component` | `<View style={styles.radioButton} />` |
| listControls | `Object` | `{ selectAllText: 'Choose all', unselectAllText: 'Remove all', selectAllCallback?: () => {}, unselectAllCallback?: () => {}}` |
| searchControls | `Object` | `{ searchInputStyle?: ViewStyle | TextStyle, textInputProps: TextInputProps}` |

## Deprecation Notice

Expand All @@ -283,7 +283,7 @@ The following props would be removed in coming releases.
- Individual props `checkboxSize`, `checkboxStyle`, `checkboxLabelStyle` would be replaced with a single object `checkboxComponentStyles` e.g

```js
const checkboxComponentStyles = {
checkboxComponentStyles = {
checkboxSize: 20,
checkboxStyle: {
backgroundColor: 'purple',
Expand All @@ -295,6 +295,22 @@ const checkboxComponentStyles = {
};
```

- `searchInputStyle` would now be inside `searchControls`

```js
searchControls={{
searchInputStyle: {
backgroundColor: 'yellow',
color: 'blue',
fontWeight: '900',
},
textInputProps: {
placeholder: 'Search anything here',
placeholderTextColor: 'gray',
},
}}
```

## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
Expand Down
11 changes: 11 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ export default function App() {
)
}
dropdownIconStyle={users && { top: 20, right: 15 }}
searchControls={{
searchInputStyle: {
backgroundColor: 'yellow',
color: 'blue',
fontWeight: '900',
},
textInputProps: {
placeholder: 'Search anything here',
placeholderTextColor: 'gray',
},
}}
/>

<DropdownSelect
Expand Down
70 changes: 33 additions & 37 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ import type {
TSectionList,
TSectionListItem,
} from './types/index.types';
import {
extractPropertyFromArray,
getMaxLengthOfSectionListProperty,
} from './utils';
import { extractPropertyFromArray } from './utils';

export const DropdownSelect: React.FC<DropdownProps> = ({
placeholder,
Expand All @@ -45,7 +42,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
multipleSelectedItemStyle,
modalBackgroundStyle,
modalOptionsContainerStyle,
searchInputStyle,
searchInputStyle, // kept for backwards compatibility
primaryColor,
disabled,
checkboxSize, // kept for backwards compatibility
Expand All @@ -59,6 +56,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
modalProps,
hideModal = false,
listControls,
searchControls,
...rest
}) => {
const [newOptions, setNewOptions] = useState<TFlatList | TSectionList>([]);
Expand Down Expand Up @@ -91,6 +89,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
* List type
*==========================================*/

// check the structure of the new options array to determine if it is a section list or a
const isSectionList = newOptions.some(
(item) => item.title && item.data && Array.isArray(item.data)
);
Expand All @@ -102,23 +101,28 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
newOptions,
'data'
).flat();

/**
*`modifiedOptions` should only be used for computations newOptions remains the default array.
* At this point modifiedOptions now has the same structure for both `FlatList` and `SectionList`
*/
const modifiedOptions = isSectionList ? modifiedSectionData : newOptions;

const optLabel = optionLabel || DEFAULT_OPTION_LABEL;
const optValue = optionValue || DEFAULT_OPTION_VALUE;
const optionsCopy = JSON.parse(JSON.stringify(options)); //copy of the original options array
const optionsCopy = JSON.parse(JSON.stringify(options)); // copy of the original options array

/*===========================================
* Selection handlers
*==========================================*/
const handleSingleSelection = (value: string | number) => {
if (selectedItem === value) {
setSelectedItem(null);
onValueChange(null); //send value to parent
onValueChange(null); // send value to parent
} else {
setSelectedItem(value);
onValueChange(value); //send value to parent
setOpen(false); //close modal upon selection
onValueChange(value); // send value to parent
setOpen(false); // close modal upon selection
}
};

Expand All @@ -131,18 +135,24 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
} else {
selectedValues.push(value);
}
onValueChange(selectedValues); //send value to parent
onValueChange(selectedValues); // send value to parent
return selectedValues;
});
};

const removeDisabledItems = (items: TFlatList) => {
return items.filter((item: TFlatListItem) => !item.disabled);
};

const handleSelectAll = () => {
setSelectAll((prevVal) => {
const selectedValues = [];

//don't select disabled items
const filteredOptions = modifiedOptions.filter(
(item: TFlatListItem) => !item.disabled
// don't select disabled items
const filteredOptions = removeDisabledItems(
isSectionList
? extractPropertyFromArray(optionsCopy, 'data').flat()
: optionsCopy
);

if (!prevVal) {
Expand All @@ -152,21 +162,15 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
}

setSelectedItems(selectedValues);
onValueChange(selectedValues); //send value to parent
onValueChange(selectedValues); // send value to parent
return !prevVal;
});

if (
typeof listControls?.selectAllCallback === 'function' &&
!selectAll
) {
if (typeof listControls?.selectAllCallback === 'function' && !selectAll) {
listControls.selectAllCallback();
}

if (
typeof listControls?.unselectAllCallback === 'function' &&
selectAll
) {
if (typeof listControls?.unselectAllCallback === 'function' && selectAll) {
listControls.unselectAllCallback();
}
};
Expand All @@ -178,8 +182,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
(selectedValues: any[]) => {
//if the list contains disabled values, those values will not be selected
if (
modifiedOptions.filter((item: TFlatListItem) => !item.disabled)
.length === selectedValues.length
removeDisabledItems(modifiedOptions).length === selectedValues.length
) {
setSelectAll(true);
} else {
Expand Down Expand Up @@ -229,7 +232,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({

const regexFilter = new RegExp(searchText, 'i');

//Because Search mutates the initial state, we have to search with a copy of the original array
// Because the options array will be mutated after Search, we have to search with a copy of the original array
const searchResults = isSectionList
? searchSectionList(optionsCopy as TSectionList, regexFilter)
: searchFlatList(optionsCopy as TFlatList, regexFilter);
Expand All @@ -243,7 +246,7 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
item[optLabel].toString().toLowerCase().search(regexFilter) !== -1 ||
item[optValue].toString().toLowerCase().search(regexFilter) !== -1
) {
return item;
return true;
}
return;
});
Expand Down Expand Up @@ -290,15 +293,6 @@ export const DropdownSelect: React.FC<DropdownProps> = ({

let primary = primaryColor || colors.gray;

const sectionListMaxLength = getMaxLengthOfSectionListProperty(
newOptions as TSectionList,
'data'
);

const listIsEmpty = isSectionList
? sectionListMaxLength > 1
: newOptions.length > 1;

/*===========================================
* setIndexOfSelectedItem - For ScrollToIndex
*==========================================*/
Expand Down Expand Up @@ -355,18 +349,20 @@ export const DropdownSelect: React.FC<DropdownProps> = ({
modalProps={modalProps}
>
<ListTypeComponent
keyboardShouldPersistTaps="always"
ListHeaderComponent={
<>
{isSearchable && (
<Input
value={searchValue}
onChangeText={(text: string) => onSearch(text)}
style={searchInputStyle}
style={searchControls?.searchInputStyle || searchInputStyle}
primaryColor={primary}
{...searchControls?.textInputProps}
/>
)}
{listHeaderComponent}
{isMultiple && listIsEmpty && (
{isMultiple && modifiedOptions.length > 1 && (
<View style={styles.optionsContainerStyle}>
<TouchableOpacity onPress={() => {}}>
<CheckBox
Expand Down
5 changes: 5 additions & 0 deletions src/types/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
ColorValue,
TextStyle,
ModalProps,
TextInputProps,
} from 'react-native';

export type DropdownProps = {
Expand Down Expand Up @@ -64,6 +65,10 @@ export type DropdownProps = {
selectAllCallback?: () => void;
unselectAllCallback?: () => void;
};
searchControls?: {
searchInputStyle?: ViewStyle | TextStyle;
textInputProps?: TextInputProps;
};
};

export type TFlatList = TFlatListItem[];
Expand Down
18 changes: 1 addition & 17 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import { TSectionList } from 'src/types/index.types';

/**
* Extract property from array
*/

export const extractPropertyFromArray = (arr: any, property: string) => {
let extractedValue = arr.map((item: any) => item[property]);

return extractedValue;
};

export const getMaxLengthOfSectionListProperty = (
sectionList: TSectionList,
property: 'title' | 'data'
) => {
let maxLength = 0;

sectionList.forEach((obj) => {
if (obj[property]?.length > maxLength) {
maxLength = obj.data.length;
}
});

return maxLength;
};

0 comments on commit 895bb38

Please sign in to comment.