From 2b01adb4d4d45ddc6be664342b8e2f9d1a469faf Mon Sep 17 00:00:00 2001 From: Azeezat Date: Wed, 6 Sep 2023 23:36:01 -0700 Subject: [PATCH 1/2] fix: Search and select all issues --- README.md | 24 ++++++++++++++++---- example/src/App.tsx | 11 ++++++++++ src/index.tsx | 47 ++++++++++++++++++++-------------------- src/types/index.types.ts | 5 +++++ 4 files changed, 59 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 3ee2ad1..340e376 100644 --- a/README.md +++ b/README.md @@ -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 @@ -238,6 +237,7 @@ For more examples visit our [wiki page](https://github.com/azeezat/react-native- | Screenshot 2023-05-16 at 6 17 09 AM | Screenshot 2023-03-23 at 5 26 58 PM | Screenshot 2023-03-23 at 5 28 49 PM | ## Props + | Proptypes | Datatype | Example | | -------------------------- | ------------------------ | -------------------------------------------------------------------- | | label | `string` | Countries | @@ -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)'}` | @@ -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` | `` | | listControls | `Object` | `{ selectAllText: 'Choose all', unselectAllText: 'Remove all', selectAllCallback?: () => {}, unselectAllCallback?: () => {}}` | +| searchControls | `Object` | `{ searchInputStyle?: ViewStyle | TextStyle, textInputProps: TextInputProps}` | ## Deprecation Notice @@ -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', @@ -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. diff --git a/example/src/App.tsx b/example/src/App.tsx index 2e0d5a2..c4e6ffe 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -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', + }, + }} /> = ({ multipleSelectedItemStyle, modalBackgroundStyle, modalOptionsContainerStyle, - searchInputStyle, + searchInputStyle, // kept for backwards compatibility primaryColor, disabled, checkboxSize, // kept for backwards compatibility @@ -59,6 +59,7 @@ export const DropdownSelect: React.FC = ({ modalProps, hideModal = false, listControls, + searchControls, ...rest }) => { const [newOptions, setNewOptions] = useState([]); @@ -91,6 +92,7 @@ export const DropdownSelect: React.FC = ({ * 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) ); @@ -106,7 +108,7 @@ export const DropdownSelect: React.FC = ({ 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 @@ -114,11 +116,11 @@ export const DropdownSelect: React.FC = ({ 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 } }; @@ -131,19 +133,21 @@ export const DropdownSelect: React.FC = ({ } 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(optionsCopy); if (!prevVal) { for (let i = 0; i < filteredOptions.length; i++) { @@ -152,21 +156,15 @@ export const DropdownSelect: React.FC = ({ } 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(); } }; @@ -178,8 +176,7 @@ export const DropdownSelect: React.FC = ({ (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 { @@ -229,7 +226,7 @@ export const DropdownSelect: React.FC = ({ 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); @@ -243,7 +240,7 @@ export const DropdownSelect: React.FC = ({ item[optLabel].toString().toLowerCase().search(regexFilter) !== -1 || item[optValue].toString().toLowerCase().search(regexFilter) !== -1 ) { - return item; + return true; } return; }); @@ -355,14 +352,16 @@ export const DropdownSelect: React.FC = ({ modalProps={modalProps} > {isSearchable && ( onSearch(text)} - style={searchInputStyle} + style={searchControls?.searchInputStyle || searchInputStyle} primaryColor={primary} + {...searchControls?.textInputProps} /> )} {listHeaderComponent} diff --git a/src/types/index.types.ts b/src/types/index.types.ts index 3ad2cf3..d349cb7 100644 --- a/src/types/index.types.ts +++ b/src/types/index.types.ts @@ -3,6 +3,7 @@ import type { ColorValue, TextStyle, ModalProps, + TextInputProps, } from 'react-native'; export type DropdownProps = { @@ -64,6 +65,10 @@ export type DropdownProps = { selectAllCallback?: () => void; unselectAllCallback?: () => void; }; + searchControls?: { + searchInputStyle?: ViewStyle | TextStyle; + textInputProps?: TextInputProps; + }; }; export type TFlatList = TFlatListItem[]; From 9ac7d59149a13226e5f045f4281d3b4be1d574b7 Mon Sep 17 00:00:00 2001 From: Azeezat Date: Thu, 7 Sep 2023 00:33:41 -0700 Subject: [PATCH 2/2] fix: remove redundant functions --- src/index.tsx | 27 ++++++++++++--------------- src/utils/index.ts | 18 +----------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 34354ee..615ab0f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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 = ({ placeholder, @@ -104,6 +101,11 @@ export const DropdownSelect: React.FC = ({ 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; @@ -147,7 +149,11 @@ export const DropdownSelect: React.FC = ({ const selectedValues = []; // don't select disabled items - const filteredOptions = removeDisabledItems(optionsCopy); + const filteredOptions = removeDisabledItems( + isSectionList + ? extractPropertyFromArray(optionsCopy, 'data').flat() + : optionsCopy + ); if (!prevVal) { for (let i = 0; i < filteredOptions.length; i++) { @@ -287,15 +293,6 @@ export const DropdownSelect: React.FC = ({ let primary = primaryColor || colors.gray; - const sectionListMaxLength = getMaxLengthOfSectionListProperty( - newOptions as TSectionList, - 'data' - ); - - const listIsEmpty = isSectionList - ? sectionListMaxLength > 1 - : newOptions.length > 1; - /*=========================================== * setIndexOfSelectedItem - For ScrollToIndex *==========================================*/ @@ -365,7 +362,7 @@ export const DropdownSelect: React.FC = ({ /> )} {listHeaderComponent} - {isMultiple && listIsEmpty && ( + {isMultiple && modifiedOptions.length > 1 && ( {}}> { 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; -};