From cbc3daca8bf7e6cb30efb6cb8b455495004e8bd7 Mon Sep 17 00:00:00 2001 From: Azeezat Date: Wed, 21 Aug 2024 16:55:02 -0700 Subject: [PATCH] fix: allow addition of custom component to dropdown list, added unit tests, added autoCloseOnSelect prop. --- README.md | 69 ++++++++++++++++++------------------ example/ios/Podfile.lock | 2 +- example/src/App.tsx | 56 +++++++++++++++++++++++++++-- package.json | 1 + src/__tests__/index.test.tsx | 39 +++++++++++++++++--- src/index.tsx | 6 +++- src/types/index.types.ts | 4 ++- yarn.lock | 56 +++++++++++++++++++++++++++++ 8 files changed, 188 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index b7dad92..1304739 100644 --- a/README.md +++ b/README.md @@ -241,40 +241,41 @@ For more examples visit our [wiki page](https://github.com/azeezat/react-native- ## Props -| Proptypes | Datatype | Example | -| ------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| label | `string` | Countries | -| placeholder | `string` | Select a country | -| options | `Array` | `[{ name: 'Nigeria', code: 'NG' }, { name: 'Albania', code: 'AL' }]` | -| optionLabel | `string` | `name` | -| optionValue | `string` | `code` | -| error | `string` | `This is a requiredfield` | -| helperText | `string` | `Only few countries are listed` | -| selectedValue | `string` or `Array` | `AL` or `[AL, AX]` | -| onValueChange | `function` | `()=>{}` | -| isMultiple | `Boolean` | `true` | -| isSearchable | `Boolean` | `true` | -| disabled | `Boolean` | `true` | -| dropdownIcon | `React Component` | `Image` or ` Show ` | -| labelStyle | `Object` | `{color: 'red', fontSize: 15, fontWeight: '500'}` | -| placeholderStyle | `Object` | `{color: 'blue', fontSize: 15, fontWeight: '500'}` | -| dropdownStyle | `Object` | `{borderColor: 'blue', margin: 5, borderWidth:0 ...}` | -| dropdownContainerStyle | `Object` | `{backgroundColor: 'red', width: '30%', ...}` | -| dropdownIconStyle | `Object` | `{top: 10 , right: 10, ...}` | -| selectedItemStyle | `Object` | `{fontWeight: '600', color: 'yellow', ...}` | -| multipleSelectedItemStyle | `Object` | `{backgroundColor: 'red', color: 'yellow', ...}` | -| dropdownErrorStyle | `Object` | `{borderWidth: 2, borderStyle: 'solid'}` | -| dropdownErrorTextStyle | `Object` | `{color: 'red', fontWeight:'500'}` | -| dropdownHelperTextStyle | `Object` | `{color: 'green', fontWeight:'500'}` | -| primaryColor | `string` | `blue` | -| listHeaderComponent | `React Component` | ` You can add any component here ` | -| listFooterComponent | `React Component` | ` You can add any component here ` | -| hideModal | `Boolean` | Use this to hide the modal as needed | -| listComponentStyles | `Object` | `{listEmptyComponentStyle: ViewStyle, itemSeparatorStyle: ViewStyle, sectionHeaderStyle: TextStyle}` | -| checkboxControls | `Object` | `{checkboxSize: number, checkboxStyle: ViewStyle, checkboxLabelStyle: TextStyle, checkboxComponent?: React.ReactNode}` | -| listControls | `Object` | `{ selectAllText: 'Choose all', unselectAllText: 'Remove all', selectAllCallback: () => {}, unselectAllCallback: () => {}, hideSelectAll: boolean, emptyListMessage: 'No record found'}` | -| searchControls | `Object` | `{ textInputStyle: ViewStyle \| TextStyle, textInputContainerStyle: ViewStyle, textInputProps: TextInputProps}` | -| modalControls | `Object` | `{ modalBackgroundStyle: ViewStyle, modalOptionsContainerStyle: ViewStyle, modalProps: ModalProps}` | +| Proptypes | Datatype | Example | +| ------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| label | `string` or `ReactComponent` | Countries or ` You can add any component here ` | +| placeholder | `string` | Select a country | +| options | `Array` | `[{ name: 'Nigeria', code: 'NG' }, { name: 'Albania', code: 'AL' }]` | +| optionLabel | `string` | `name` | +| optionValue | `string` | `code` | +| error | `string` | `This is a requiredfield` | +| helperText | `string` | `Only few countries are listed` | +| selectedValue | `string` or `Array` | `AL` or `[AL, AX]` | +| onValueChange | `function` | `()=>{}` | +| isMultiple | `Boolean` | `true` | +| isSearchable | `Boolean` | `true` | +| disabled | `Boolean` | `true` | +| dropdownIcon | `React Component` | `Image` or ` Show ` | +| labelStyle | `Object` | `{color: 'red', fontSize: 15, fontWeight: '500'}` | +| placeholderStyle | `Object` | `{color: 'blue', fontSize: 15, fontWeight: '500'}` | +| dropdownStyle | `Object` | `{borderColor: 'blue', margin: 5, borderWidth:0 ...}` | +| dropdownContainerStyle | `Object` | `{backgroundColor: 'red', width: '30%', ...}` | +| dropdownIconStyle | `Object` | `{top: 10 , right: 10, ...}` | +| selectedItemStyle | `Object` | `{fontWeight: '600', color: 'yellow', ...}` | +| multipleSelectedItemStyle | `Object` | `{backgroundColor: 'red', color: 'yellow', ...}` | +| dropdownErrorStyle | `Object` | `{borderWidth: 2, borderStyle: 'solid'}` | +| dropdownErrorTextStyle | `Object` | `{color: 'red', fontWeight:'500'}` | +| dropdownHelperTextStyle | `Object` | `{color: 'green', fontWeight:'500'}` | +| primaryColor | `string` | `blue` | +| autoCloseOnSelect | `boolean` | `false` | +| listHeaderComponent | `React Component` | ` You can add any component here ` | +| listFooterComponent | `React Component` | ` You can add any component here ` | +| hideModal | `Boolean` | Use this to hide the modal as needed | +| listComponentStyles | `Object` | `{listEmptyComponentStyle: ViewStyle, itemSeparatorStyle: ViewStyle, sectionHeaderStyle: TextStyle}` | +| checkboxControls | `Object` | `{checkboxSize: number, checkboxStyle: ViewStyle, checkboxLabelStyle: TextStyle, checkboxComponent?: React.ReactNode}` | +| listControls | `Object` | `{ selectAllText: 'Choose all', unselectAllText: 'Remove all', selectAllCallback: () => {}, unselectAllCallback: () => {}, hideSelectAll: boolean, emptyListMessage: 'No record found'}` | +| searchControls | `Object` | `{ textInputStyle: ViewStyle \| TextStyle, textInputContainerStyle: ViewStyle, textInputProps: TextInputProps}` | +| modalControls | `Object` | `{ modalBackgroundStyle: ViewStyle, modalOptionsContainerStyle: ViewStyle, modalProps: ModalProps}` | ## Deprecation Notice diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index e452cdd..5863af7 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1345,7 +1345,7 @@ SPEC CHECKSUMS: DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 FBLazyVector: 4bc164e5b5e6cfc288d2b5ff28643ea15fa1a589 fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 - glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 + glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f hermes-engine: 01d3e052018c2a13937aca1860fbedbccd4a41b7 RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47 RCTDeprecation: b03c35057846b685b3ccadc9bfe43e349989cdb2 diff --git a/example/src/App.tsx b/example/src/App.tsx index 7a4c9e6..5a7bf52 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -46,7 +46,10 @@ export default function App() { label="Currency" placeholder="Select multiple currencies..." options={[ - {name: 'Naira (NGN) \u20A6', code: 'NGN'}, + { + name: 'Naira (NGN) \u20A6', + code: 'NGN', + }, {name: 'Dollar (USD) \u0024', code: 'USD'}, {name: 'Euro (EUR) \u20AC', code: 'EUR'}, ]} @@ -84,15 +87,56 @@ export default function App() { onDismiss: () => console.log('modal was dismissed'), //only works for ios }, }} + autoCloseOnSelect={false} /> + + + John Doe + + ), + }, + { + value: '02', + labelComponent: ( + + + + Azeezat Raheem + + ), + }, ]} + optionLabel={'labelComponent'} + optionValue={'value'} selectedValue={users} onValueChange={(itemValue: any) => setUsers(itemValue)} isSearchable @@ -412,4 +456,10 @@ const styles = StyleSheet.create({ borderWidth: 3, borderColor: 'white', }, + avatarStyle: { + height: 20, + width: 20, + borderRadius: 20, + marginRight: 5, + }, }); diff --git a/package.json b/package.json index 26556f3..3c927ee 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@commitlint/config-conventional": "^19.2.2", "@react-native-community/eslint-config": "^3.2.0", "@release-it/conventional-changelog": "^8.0.1", + "@testing-library/jest-dom": "^6.4.8", "@testing-library/react-native": "^12.5.1", "@types/jest": "^29.5.12", "@types/react": "18.3.2", diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index 09a3e35..a0c3d62 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -1,10 +1,39 @@ import React from 'react'; import DropdownSelect from '../index'; +import { render, screen, userEvent } from '@testing-library/react-native'; +import '@testing-library/jest-dom'; +describe('Initial state of component', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); -import { render,screen } from '@testing-library/react-native'; + afterAll(() => { + jest.useRealTimers(); + }); + const defaultDropdown = ( + {}} /> + ); -test('basic test', () => { - render( {}} />); - expect(screen.getByText(" Select an option")) -}); \ No newline at end of file + test('show default texts', () => { + render(defaultDropdown); + expect(screen.getByText('Select an option')); + }); + + test('show default styles', () => { + render(defaultDropdown); + const placeholderStyle = screen.getByText('Select an option'); + console.log(placeholderStyle.props) + expect(placeholderStyle.props.style).toMatchObject([ + { color: '#000000' }, + undefined, + ]); + }); + + test('open modal when dropdown is clicked', async () => { + const user = userEvent.setup(); + render(defaultDropdown); + await user.press(screen.getByText('Select an option')); + expect(screen.getByText('No options available')); + }); +}); diff --git a/src/index.tsx b/src/index.tsx index 6b50dea..d0bb28c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -59,6 +59,7 @@ export const DropdownSelect: React.FC = ({ searchControls, modalControls, checkboxControls, + autoCloseOnSelect=true, ...rest }) => { const [newOptions, setNewOptions] = useState([]); @@ -122,7 +123,10 @@ export const DropdownSelect: React.FC = ({ } else { setSelectedItem(value); onValueChange(value); // send value to parent + + if(autoCloseOnSelect){ setOpen(false); // close modal upon selection + } } }; @@ -390,7 +394,7 @@ export const DropdownSelect: React.FC = ({ ListHeaderComponent={ <> {isSearchable && ( - onSearch(text)} style={searchControls?.textInputStyle || searchInputStyle} diff --git a/src/types/index.types.ts b/src/types/index.types.ts index 25d31bd..3e15734 100644 --- a/src/types/index.types.ts +++ b/src/types/index.types.ts @@ -1,3 +1,4 @@ +import React from 'react'; import type { ViewStyle, ColorValue, @@ -27,6 +28,7 @@ export type CommonDropdownProps = { | boolean[] | number[] | null; + autoCloseOnSelect?: boolean; }; export type TDropdownInputProps = { @@ -119,7 +121,7 @@ export type TListControls = { export type TFlatList = TFlatListItem[]; export type TFlatListItem = { - [key: string]: string | number | boolean; + [key: string]: string | number | boolean | React.JSX.Element; }; export type TSectionList = TSectionListItem[]; diff --git a/yarn.lock b/yarn.lock index 6dcdf0f..1f4eeb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63" + integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ== + "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -1166,6 +1171,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.9.2": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.0.0", "@babel/template@^7.24.7", "@babel/template@^7.3.3": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" @@ -2274,6 +2286,20 @@ dependencies: defer-to-connect "^2.0.1" +"@testing-library/jest-dom@^6.4.8": + version "6.4.8" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz#9c435742b20c6183d4e7034f2b329d562c079daa" + integrity sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw== + dependencies: + "@adobe/css-tools" "^4.4.0" + "@babel/runtime" "^7.9.2" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + "@testing-library/react-native@^12.5.1": version "12.5.1" resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-12.5.1.tgz#8ff67e87589d7d3307fce8ec41131c898c9dbd98" @@ -2701,6 +2727,13 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +aria-query@^5.0.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + array-buffer-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" @@ -3143,6 +3176,14 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -3603,6 +3644,11 @@ crypto-random-string@^4.0.0: dependencies: type-fest "^1.0.1" +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + csstype@^3.0.2: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" @@ -3792,6 +3838,11 @@ deprecation@^2.0.0: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" @@ -3821,6 +3872,11 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"