diff --git a/README.md b/README.md index aff68802..a60397e2 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Screenshot

+The example in the screenshots: https://snack.expo.dev/8mHmLfcZf + # Documentation **The documentation has been moved to https://hossein-zare.github.io/react-native-dropdown-picker-website/** diff --git a/index.d.ts b/index.d.ts index 7b648984..9beba118 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,5 @@ declare module 'react-native-dropdown-picker' { - import type { ComponentType, SetStateAction, Dispatch } from 'react'; + import type { Dispatch, PropsWithoutRef } from 'react'; import type { FlatListProps, LayoutChangeEvent, @@ -14,13 +14,16 @@ declare module 'react-native-dropdown-picker' { ViewStyle, } from 'react-native'; + type SetStateCallback = ((prevState: S) => S); + type SetStateValue = ((prevState: S) => S); + export type ValueType = string | number | boolean; - export type ItemType = { + export type ItemType = { label?: string; - value?: ValueType; + value?: T; icon?: () => void; - parent?: ValueType; + parent?: T; selectable?: boolean; disabled?: boolean; testID?: string; @@ -65,18 +68,23 @@ declare module 'react-native-dropdown-picker' { | 'FA' | 'TR' | 'RU' - | 'ES'; + | 'ES' + | 'ID' + | 'IT'; export interface TranslationInterface { PLACEHOLDER: string; SEARCH_PLACEHOLDER: string; - SELECTED_ITEMS_COUNT_TEXT: string; + SELECTED_ITEMS_COUNT_TEXT: string | { + [key in (number | "n")]: string; + }; NOTHING_TO_SHOW: string; } - export interface RenderBadgeItemPropsInterface { + export interface RenderBadgeItemPropsInterface { label: string; - value: ValueType; + value: T; + props: TouchableOpacityProps; IconComponent: () => JSX.Element; textStyle: StyleProp; badgeStyle: StyleProp; @@ -85,17 +93,17 @@ declare module 'react-native-dropdown-picker' { getBadgeColor: (value: string) => string; getBadgeDotColor: (value: string) => string; showBadgeDot: boolean; - onPress: (value: ValueType) => void; + onPress: (value: T) => void; rtl: boolean; THEME: ThemeType; } - export interface RenderListItemPropsInterface { + export interface RenderListItemPropsInterface { rtl: boolean; - item: ItemType; + item: ItemType; label: string; - value: ValueType; - parent: ValueType; + value: T; + parent: T; selectable: boolean; disabled: boolean; props: ViewProps; @@ -118,8 +126,8 @@ declare module 'react-native-dropdown-picker' { containerStyle: StyleProp; labelStyle: StyleProp; categorySelectable: boolean; - onPress: () => void; - setPosition: (value: ValueType, y: number) => void; + onPress: (value: T) => void; + setPosition: (value: T, y: number) => void; theme: ThemeNameType; THEME: ThemeType; } @@ -143,8 +151,8 @@ declare module 'react-native-dropdown-picker' { export type ThemeNameType = 'DEFAULT' | 'LIGHT' | 'DARK'; export type ThemeType = object; - interface DropDownPickerBaseProps { - items: ItemType[]; + interface DropDownPickerBaseProps { + items: ItemType[]; open: boolean; placeholder?: string; closeAfterSelecting?: boolean; @@ -198,8 +206,8 @@ declare module 'react-native-dropdown-picker' { mode?: ModeType; itemKey?: string; maxHeight?: number; - renderBadgeItem?: (props: RenderBadgeItemPropsInterface) => JSX.Element; - renderListItem?: (props: RenderListItemPropsInterface) => JSX.Element; + renderBadgeItem?: (props: RenderBadgeItemPropsInterface) => JSX.Element; + renderListItem?: (props: RenderListItemPropsInterface) => JSX.Element; itemSeparator?: boolean; bottomOffset?: number; badgeColors?: string[] | string; @@ -229,8 +237,9 @@ declare module 'react-native-dropdown-picker' { activityIndicatorColor?: string; props?: TouchableOpacityProps; itemProps?: TouchableOpacityProps; + badgeProps?: TouchableOpacityProps; modalProps?: ModalProps; - flatListProps?: Partial>; + flatListProps?: Partial>>; scrollViewProps?: ScrollViewProps; searchTextInputProps?: TextInputProps; modalTitle?: string; @@ -239,8 +248,8 @@ declare module 'react-native-dropdown-picker' { min?: number; max?: number; addCustomItem?: boolean; - setOpen: Dispatch>; - setItems?: Dispatch>; + setOpen: Dispatch>; + setItems?: Dispatch>; disableBorderRadius?: boolean; containerProps?: ViewProps; onLayout?: (e: LayoutChangeEvent) => void; @@ -248,6 +257,7 @@ declare module 'react-native-dropdown-picker' { onOpen?: () => void; onClose?: () => void; onChangeSearchText?: (text: string) => void; + onDirectionChanged?: (direction: DropDownDirectionType) => void; zIndex?: number; zIndexInverse?: number; disableLocalSearch?: boolean; @@ -256,22 +266,24 @@ declare module 'react-native-dropdown-picker' { rtl?: boolean; testID?: string; closeOnBackPressed?: boolean; + hideSelectedItemIcon?: boolean; + extendableBadgeContainer?: boolean; } - interface DropDownPickerSingleProps { + interface DropDownPickerSingleProps { multiple?: false; - onChangeValue?: (value: ValueType | null) => void; - onSelectItem?: (item: ItemType) => void; - setValue: Dispatch>; - value: ValueType | null; + onChangeValue?: (value: T | null) => void; + onSelectItem?: (item: ItemType) => void; + setValue: Dispatch>; + value: T | null; } - interface DropDownPickerMultipleProps { + interface DropDownPickerMultipleProps { multiple: true; - onChangeValue?: (value: ValueType[] | null) => void; - onSelectItem?: (items: ItemType[]) => void; - setValue: Dispatch>; - value: ValueType[] | null; + onChangeValue?: (value: T[] | null) => void; + onSelectItem?: (items: ItemType[]) => void; + setValue: Dispatch>; + value: T[] | null; } interface DropDownPickerInterface { @@ -297,13 +309,16 @@ declare module 'react-native-dropdown-picker' { ) => void; } - export type DropDownPickerProps = ( - | DropDownPickerSingleProps - | DropDownPickerMultipleProps + export type DropDownPickerProps = ( + | DropDownPickerSingleProps + | DropDownPickerMultipleProps ) & - DropDownPickerBaseProps; + DropDownPickerBaseProps; - const DropDownPicker: ComponentType & + const DropDownPicker: (( + props: PropsWithoutRef>, + ) => React.ReactElement) & DropDownPickerInterface; + export default DropDownPicker; } diff --git a/package.json b/package.json index 1846f418..a03b8238 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-dropdown-picker", - "version": "5.3.0", + "version": "5.4.0", "description": "A single / multiple, categorizable, customizable, localizable and searchable item picker (drop-down) component for react native which supports both Android & iOS.", "keywords": [ "picker", diff --git a/src/components/Picker.js b/src/components/Picker.js index a154d882..6ddf29b6 100644 --- a/src/components/Picker.js +++ b/src/components/Picker.js @@ -65,6 +65,7 @@ function Picker({ arrowIconStyle = {}, tickIconStyle = {}, closeIconStyle = {}, + hideSelectedItemIcon = false, badgeStyle = {}, badgeTextStyle = {}, badgeDotStyle = {}, @@ -128,6 +129,7 @@ function Picker({ activityIndicatorColor = Colors.GREY, props = {}, itemProps = {}, + badgeProps= {}, modalProps = {}, flatListProps = {}, scrollViewProps = {}, @@ -147,6 +149,7 @@ function Picker({ setValue = (callback) => {}, onChangeValue = (value) => {}, onChangeSearchText = (text) => {}, + onDirectionChanged = (direction) => {}, zIndex = 5000, zIndexInverse = 6000, rtl = false, @@ -155,6 +158,7 @@ function Picker({ theme = THEMES.DEFAULT, testID, closeOnBackPressed = false, + extendableBadgeContainer = false, onSelectItem = (item) => {} }) { const [necessaryItems, setNecessaryItems] = useState([]); @@ -586,9 +590,15 @@ function Picker({ const item = getSelectedItem(); if (multiple) - if (item.length > 0) - return _multipleText.replace('{count}', item.length); - else + if (item.length > 0) { + let mtext = _multipleText; + + if (typeof mtext !== 'string') { + mtext = mtext[item.length] ?? mtext.n; + } + + return mtext.replace('{count}', item.length); + } else return fallback; try { @@ -634,7 +644,10 @@ function Picker({ ); const size = y + maxHeight + pickerHeight + bottomOffset; - setDirection(size < WINDOW_HEIGHT ? 'top' : 'bottom'); + const direction = size < WINDOW_HEIGHT ? 'top' : 'bottom'; + + onDirectionChanged(direction); + setDirection(direction); } onPressToggle(); @@ -642,6 +655,7 @@ function Picker({ open, onPressToggle, onPress, + onDirectionChanged, maxHeight, pickerHeight, bottomOffset, @@ -864,12 +878,15 @@ function Picker({ const SelectedItemIconComponent = useMemo(() => { const Component = _selectedItemIcon(); + if (hideSelectedItemIcon) + return null; + return Component !== null && ( ); - }, [_selectedItemIcon, _iconContainerStyle]); + }, [_selectedItemIcon, hideSelectedItemIcon, _iconContainerStyle]); /** * The simple body component. @@ -957,6 +974,7 @@ function Picker({ */ const __renderBadge = useCallback(({item}) => ( ([ + const labelContainerStyle = useMemo(() => ([ THEME.labelContainer, rtl && { transform: [ {scaleX: -1} @@ -1038,12 +1056,12 @@ function Picker({ * @returns {JSX.Element} */ const BadgeListEmptyComponent = useCallback(() => ( - + {_placeholder} - ), [_labelStyle, labelContainer, labelProps, _placeholder]); + ), [_labelStyle, labelContainerStyle, labelProps, _placeholder]); /** * Set ref. @@ -1052,26 +1070,73 @@ function Picker({ badgeFlatListRef.current = ref; }, []); + /** + * The extendable badge container style. + * @returns {object} + */ + const extendableBadgeContainerStyle = useMemo(() => ([ + RTL_DIRECTION(rtl, THEME.extendableBadgeContainer) + ]), [rtl, THEME]); + + /** + * The extendable badge item container style. + * @returns {object} + */ + const extendableBadgeItemContainerStyle = useMemo(() => ([ + THEME.extendableBadgeItemContainer, rtl && { + marginEnd: 0, + marginStart: THEME.extendableBadgeItemContainer.marginEnd + } + ]), [rtl, THEME]); + + /** + * Extendable badge container. + * @returns {JSX.Element} + */ + const ExtendableBadgeContainer = useCallback(({selectedItems}) => { + if (selectedItems.length > 0) { + return ( + + {selectedItems.map((item, index) => ( + + <__renderBadge item={item} /> + + ))} + + ); + } + + return ; + }, [__renderBadge, extendableBadgeContainerStyle, extendableBadgeItemContainerStyle]); + /** * The badge body component. * @returns {JSX.Element} */ - const BadgeBodyComponent = useMemo(() => ( - - ), [ + const BadgeBodyComponent = useMemo(() => { + if (extendableBadgeContainer) { + return + } + + return ( + + ); + }, [ rtl, + extendableBadgeContainer, + ExtendableBadgeContainer, selectedItems, __renderBadge, keyExtractor, @@ -1186,7 +1251,6 @@ function Picker({ ...[textStyle].flat(), ...[listMessageTextStyle].flat() ]), [listMessageTextStyle, THEME]); - /** * onPress item. @@ -1749,4 +1813,4 @@ const styles = StyleSheet.create({ } }); -export default memo(Picker); +export default memo(Picker); \ No newline at end of file diff --git a/src/components/RenderBadgeItem.js b/src/components/RenderBadgeItem.js index 04fc8e74..e6353705 100644 --- a/src/components/RenderBadgeItem.js +++ b/src/components/RenderBadgeItem.js @@ -13,6 +13,7 @@ import { RTL_DIRECTION, RTL_STYLE } from '../constants'; function RenderBadge({ rtl, label, + props, value, textStyle, badgeStyle, @@ -61,7 +62,7 @@ function RenderBadge({ ]), [textStyle, badgeTextStyle]); return ( - + {showBadgeDot && } {label} diff --git a/src/constants/index.js b/src/constants/index.js index 5a9c661f..f4e9c768 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -46,7 +46,9 @@ export const LANGUAGE = { FARSI: 'FA', TURKISH: 'TR', RUSSIAN: 'RU', - SPANISH: 'ES' + SPANISH: 'ES', + INDONESIAN: 'ID', + ITALIAN: 'IT' } export const GET_DROPDOWN_DIRECTION = (direction) => { diff --git a/src/themes/dark/index.js b/src/themes/dark/index.js index 6f3fb4f4..1d778d9a 100644 --- a/src/themes/dark/index.js +++ b/src/themes/dark/index.js @@ -20,11 +20,12 @@ export default StyleSheet.create({ alignItems: 'center', justifyContent: 'space-between', width: '100%', - height: 50, + minHeight: 50, borderRadius: 8, borderWidth: 1, borderColor: Colors.BLACK, paddingHorizontal: 10, + paddingVertical: 3, backgroundColor: Colors.EBONY_CLAY, }, label: { @@ -159,7 +160,7 @@ export default StyleSheet.create({ padding: 10, }, listMessageText: { - + color: Colors.HEATHER }, selectedItemContainer: { @@ -170,5 +171,14 @@ export default StyleSheet.create({ modalTitle: { fontSize: 18, color: Colors.HEATHER + }, + extendableBadgeContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + flex: 1 + }, + extendableBadgeItemContainer: { + marginVertical: 3, + marginEnd: 7 } -}); \ No newline at end of file +}); diff --git a/src/themes/light/index.js b/src/themes/light/index.js index 4186419a..ba38795c 100644 --- a/src/themes/light/index.js +++ b/src/themes/light/index.js @@ -20,11 +20,12 @@ export default StyleSheet.create({ alignItems: 'center', justifyContent: 'space-between', width: '100%', - height: 50, + minHeight: 50, borderRadius: 8, borderWidth: 1, borderColor: Colors.BLACK, paddingHorizontal: 10, + paddingVertical: 3, backgroundColor: Colors.WHITE }, label: { @@ -169,5 +170,14 @@ export default StyleSheet.create({ modalTitle: { fontSize: 18, color: Colors.BLACK + }, + extendableBadgeContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + flex: 1 + }, + extendableBadgeItemContainer: { + marginVertical: 3, + marginEnd: 7 } }); \ No newline at end of file diff --git a/src/translations/index.js b/src/translations/index.js index 903578e8..1b5e05a1 100644 --- a/src/translations/index.js +++ b/src/translations/index.js @@ -2,7 +2,10 @@ export default { EN: { PLACEHOLDER: 'Select an item', SEARCH_PLACEHOLDER: 'Type something...', - SELECTED_ITEMS_COUNT_TEXT: '{count} item(s) have been selected', + SELECTED_ITEMS_COUNT_TEXT: { + 1: 'An item has been selected', + n: '{count} items have been selected' + }, NOTHING_TO_SHOW: 'There\'s nothing to show!' }, AR: { @@ -14,13 +17,19 @@ export default { FA: { PLACEHOLDER: 'آیتمی انتخاب کنید', SEARCH_PLACEHOLDER: 'چیزی تایپ کنید...', - SELECTED_ITEMS_COUNT_TEXT: '{count} آیتم انتخاب شده است', + SELECTED_ITEMS_COUNT_TEXT: { + 1: 'یک آیتم انتخاب شده است', + n: '{count} آیتم انتخاب شده است' + }, NOTHING_TO_SHOW: 'چیزی برای نمایش وجود ندارد!' }, TR: { PLACEHOLDER: 'Bir seçenek seçin', SEARCH_PLACEHOLDER: 'Arama...', - SELECTED_ITEMS_COUNT_TEXT: '{count} öğe seçildi', + SELECTED_ITEMS_COUNT_TEXT: { + 1: 'Bir öğe seçildi', + n: '{count} öğe seçildi' + }, NOTHING_TO_SHOW: 'Gösterecek hiçbir şey yok!' }, RU: { @@ -34,5 +43,20 @@ export default { SEARCH_PLACEHOLDER: 'Escribe algo...', SELECTED_ITEMS_COUNT_TEXT: 'Se han seleccionado {count} elemento(s)', NOTHING_TO_SHOW: '¡No hay nada que mostrar!' - } + }, + ID: { + PLACEHOLDER: 'Pilih item', + SEARCH_PLACEHOLDER: 'Ketik sesuatu...', + SELECTED_ITEMS_COUNT_TEXT: '{count} item telah dipilih', + NOTHING_TO_SHOW: 'Tidak ada yang bisa ditampilkan!' + }, + IT: { + PLACEHOLDER: 'Seleziona un elemento', + SEARCH_PLACEHOLDER: 'Digita qualcosa...', + SELECTED_ITEMS_COUNT_TEXT: { + 1: 'Un elemento è stato selezionato', + n: '{count} elementi sono stati selezionati' + }, + NOTHING_TO_SHOW: 'Non c\'è nulla da mostrare!' + }, }