import { useImperativeHandle, forwardRef, useEffect, useState, type Ref, useRef } from "react"; import { TouchableOpacity, View } from "react-native"; import { type SelectBoxComponent, type ISelectBoxRef, type SelectBoxType, type SelectedItem } from "./type"; import type ISelectBoxProps from "./type"; import stylesheet, { getSelectBoxType, useStyles } from "./stylesheet"; import { NCoreUIKitLocalize, NCoreUIKitTheme, NCoreUIKitToast } from "../../core/hooks"; import { type NCoreUIKitIcon } from "../../types"; import type { IBottomSheetRef } from "../bottomSheet/type"; import type ITextProps from "../text/type"; import { BadgeQuestionMarkIcon, BadgeSuccessIcon, BadgeDangerIcon, BadgeAlertIcon, BadgeInfoIcon, CleanIcon } from "../../assets/svg"; import { uuid } from "../../utils"; import SelectSheet from "../selectSheet"; import Text from "../text"; const SelectBoxTypeIcon: Record, NCoreUIKitIcon> = { "question": BadgeQuestionMarkIcon, "success": BadgeSuccessIcon, "warning": BadgeAlertIcon, "danger": BadgeDangerIcon, "info": BadgeInfoIcon }; function SelectBox({ renderLoadingIcon : LoadingIconComponentProp, rightIcon: RightIconComponentProp, hintTextIcon: HintTextIconProp, isShowSelectSheetTools = true, spreadBehaviour = "baseline", isShowHintTextIcon = false, customSelectSheetLocalize, isWorkWithRealtime = true, isMultipleSelect = false, icon: IconComponentProp, hintTextContainerStyle, removeSelectValidation, customSelectSheetTheme, isCleanEnabled = false, contentContainerStyle, subTitle = "Optional", initialSelectedItems, isSearchable = false, onFocus: onFocusProp, isRequired = false, isDisabled = false, onBlur: onBlurProp, moreLoadThreshold, hintTextIconStyle, data: initialData, selectSheetProps, variant = "text", type = "default", rightIconOnPress, renderOptionIcon, selectValidation, customLocalize, rightIconStyle, isShowSubTitle, cleanIconStyle, titleExtractor, maxChoice = -1, keyExtractor, contentStyle, customTheme, placeholder, iconOnPress, onMoreLoad, isOptional, renderItem, customKey, minChoice, iconStyle, onChange, hintText, onSearch, style, title }: ISelectBoxProps, ref: Ref>) { const { inlineSpaces, typography, radiuses, borders, spaces, colors } = NCoreUIKitTheme.useContext(customTheme); const { localize } = NCoreUIKitLocalize.useContext(customLocalize); const bottomSheetRef = useRef(null); const selectBoxKey = useRef(customKey ? customKey : uuid()); const currentType = getSelectBoxType({ type }); const styleType = type === "default" ? "neutral" : type === "question" ? "neutral" : type === "danger" ? "error" : type; const [ data, setData ] = useState>([]); const [ searchedData, setSearchedData ] = useState>([]); const [ isActive, setIsActive ] = useState(false); const [ searchText, setSearchText ] = useState(""); const [ isLoading, setIsLoading ] = useState(false); const [ isMoreLoading, setIsMoreLoading ] = useState(true); const [ selectedItems, setSelectedItems ] = useState>(initialSelectedItems ?? []); const [ tempSelectedItems, setTempSelectedItems ] = useState>(initialSelectedItems ?? []); const mainSelectedItems = isWorkWithRealtime ? selectedItems : tempSelectedItems; const { contentContainer: contentContainerDynamicStyle, titleContainer: titleContainerDynamicStyle, hintTextIcon: hintTextIconDynamicStyle, contentText: contentTextDynamicStyle, cleanButton: cleanButtonDynamicStyle, rightIcon: rightIconDynamicStyle, container: containerDynamicStyle, hintText: hintTextDynamicStyle, required: requiredDynamicStyle, subTitle: subTitleDynamicStyle, overlay: overlayDynamicStyle, content: contentDynamicStyle, title: titleDynamicStyle, icon: iconDynamicStyle } = useStyles({ icon: IconComponentProp ? true : false, spreadBehaviour, inlineSpaces, isSearchable, currentType, isDisabled, isActive, radiuses, borders, spaces, colors, title, type }); useImperativeHandle( ref, () => ({ setIsMoreLoading: (e) => setIsMoreLoading(e), setIsLoading: (e) => setIsLoading(e), updateSelections, cleanSelections, updateData, selectAll, cleanAll, cancel, focus, blur, ok }), [] ); useEffect(() => { if(isActive) { if(onFocus) onFocus(); } else { if(!isWorkWithRealtime) { setTempSelectedItems([]); } setSearchText(""); setSearchedData([]); setIsMoreLoading(false); if(onBlur) onBlur(); } }, [isActive]); useEffect(() => { if(isSearchable && searchText && searchText.length) { if(onSearch) { const _searchedData = prepareDatas(onSearch({ selectedItems: mainSelectedItems, setIsMoreLoading, setIsLoading, searchText, data }) as Array, mainSelectedItems); setSearchedData(_searchedData); } else { const _searchedData = data.filter(dI => { return dI.__title.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()); }) as Array; setSearchedData(_searchedData); } } else if(searchedData && searchedData.length) { setSearchedData([]); } }, [ data, searchText ]); useEffect(() => { const initData = prepareDatas(initialData as Array, initialSelectedItems); setData(initData); }, []); const titleProps: ITextProps = { color: currentType.titleColor, variant: "bodyLargeSize" }; const iconProps: NCoreUIKit.IconCallbackProps = { size: Number(typography.labelLargeSize.fontSize) + 6, color: currentType.iconColor }; if(isDisabled) { iconProps.color = "disabled"; } const blur = () => { setIsActive(false); }; const focus = () => { if(!isWorkWithRealtime) { setTempSelectedItems(selectedItems); } setIsActive(true); }; const cancel = () => { if(!isWorkWithRealtime) { bottomSheetRef.current?.close(() => { blur(); }); } }; const ok = () => { if(!isWorkWithRealtime) { setSelectedItems(tempSelectedItems); bottomSheetRef.current?.close(() => { blur(); }); } }; const cleanSelections = () => { setSelectedItems([]); if(!isWorkWithRealtime) { setTempSelectedItems([]); } if(onChange) { onChange([], data, setIsMoreLoading); } }; const updateData = (newData: Array, newSelectedItems: Array) => { const _newData = newData.map((bIItem, bIIndex) => { return { ...bIItem, __title: titleExtractor(bIItem as T & SelectedItem, bIIndex), __key: keyExtractor(bIItem as T & SelectedItem, bIIndex) }; }); setData(_newData); const _newSelectedItems = JSON.parse(JSON.stringify(newSelectedItems ? newSelectedItems : mainSelectedItems)).map((sItem: SelectedItem) => { const originalDataIndex = _newData.findIndex(ssItem => ssItem.__key === sItem.__key); return { __title: _newData[originalDataIndex]?.__title, __key: _newData[originalDataIndex]?.__key }; }); if(isWorkWithRealtime) { setSelectedItems(_newSelectedItems); } else { setTempSelectedItems(_newSelectedItems); } }; const updateSelections = (newSelectedItems: Array) => { const _newSelectedItems = JSON.parse(JSON.stringify(newSelectedItems)).map((sItem: SelectedItem) => { const originalDataIndex = data.findIndex(ssItem => ssItem.__key === sItem.__key); return { ...sItem, __title: data[originalDataIndex]?.__title, __key: data[originalDataIndex]?.__key }; }); if(isWorkWithRealtime) { setSelectedItems(_newSelectedItems); } else { setTempSelectedItems(_newSelectedItems); } }; const prepareDatas = (items: Array, sItems?: Array) => { const initData: Array = []; if(sItems && sItems.length) { sItems.forEach((iS) => { const dataIndex = items.findIndex((iD, iDI) => keyExtractor(iD, iDI) === iS.__key); if(dataIndex === -1) { initData.unshift(iS); } }); } const finalData = items.map((bIItem, bIIndex) => { return { ...bIItem, __title: titleExtractor(bIItem, bIIndex), __key: keyExtractor(bIItem, bIIndex) }; }); return [ ...initData, ...finalData ]; }; const selectObject = (selectedItem: T & SelectedItem | SelectedItem) => { const isAlreadySelected = mainSelectedItems.findIndex(sItem => sItem.__key === selectedItem.__key); let _selectedItems = JSON.parse(JSON.stringify(mainSelectedItems)); if(isAlreadySelected !== -1) { let isProcessPerm = false; if(isMultipleSelect) { if(minChoice) { if(_selectedItems.length > minChoice) { isProcessPerm = true; } } else { isProcessPerm = true; } } else { const _minChoice = minChoice === undefined ? 0 : minChoice; if(_minChoice > 0) { NCoreUIKitToast.open({ title: localize("minimum-a-item-selection-required") }); } else { isProcessPerm = true; } } if(isProcessPerm) { if(removeSelectValidation) { if(removeSelectValidation(selectedItem)) { _selectedItems = _selectedItems.filter((sItem: SelectedItem) => sItem.__key !== selectedItem.__key); } } else { _selectedItems = _selectedItems.filter((sItem: SelectedItem) => sItem.__key !== selectedItem.__key); } } } else { let isProcessPerm = false; if(isMultipleSelect) { if(_selectedItems.length < maxChoice || maxChoice === -1) { isProcessPerm = true; } else { NCoreUIKitToast.open({ title: localize("maximum-selection-limit-has-been-reached") }); } } else { _selectedItems = []; isProcessPerm = true; } if(isProcessPerm) { if(selectValidation) { if(selectValidation(selectedItem)) { _selectedItems.push({ __title: selectedItem.__title, __key: selectedItem.__key }); } } else { _selectedItems.push({ __title: selectedItem.__title, __key: selectedItem.__key }); } } } if(isWorkWithRealtime) { setSelectedItems(_selectedItems); } else { setTempSelectedItems(_selectedItems); } }; const cleanAll = () => { if(minChoice) { console.error("minChoice must be 0 or undefined for cleanAll function."); return; } if(isWorkWithRealtime) { setSelectedItems([]); } else { setTempSelectedItems([]); } }; const selectAll = () => { if(maxChoice !== -1) { console.error("maxChoice must be -1 for selectAll function."); return; } const _selectedItems = JSON.parse(JSON.stringify(mainSelectedItems)); if(searchText && searchText.length) { searchedData.forEach(searchedItem => { if(mainSelectedItems.findIndex(sI => sI.__key === searchedItem.__key) === -1) { _selectedItems.push({ __title: searchedItem.__title, __key: searchedItem.__key }); } }); } else { data.forEach(dataItem => { if(mainSelectedItems.findIndex(sI => sI.__key === dataItem.__key) === -1) { _selectedItems.push({ __title: dataItem.__title, __key: dataItem.__key }); } }); } if(isWorkWithRealtime) { setSelectedItems(_selectedItems); } else { setTempSelectedItems(_selectedItems); } }; const onFocus = () => { if(onFocusProp) onFocusProp(); }; const onBlur = () => { if(onBlurProp) onBlurProp(); }; const renderCleanButton = () => { if(isDisabled) { return null; } if(variant !== "text") { return null; } if(!isCleanEnabled || !selectedItems.length) { return null; } return { if(isWorkWithRealtime) { setSelectedItems([]); } else { setTempSelectedItems([]); } if(onChange) { onChange([], data, setIsMoreLoading); } }} > ; }; const renderIcon = () => { if (!IconComponentProp) { return null; } return ; }; const renderRightIcon = () => { if (!RightIconComponentProp) { return null; } if(isCleanEnabled && selectedItems.length > 0 && variant === "text") { return null; } return ; }; const renderHintIcon = () => { if(!isShowHintTextIcon) { return null; } if(HintTextIconProp) { return ; } const CurrentHintIcon = SelectBoxTypeIcon[type === "default" ? "question" : type]; return ; }; const renderHintText = () => { if (!hintText) { return null; } return {renderHintIcon()} {hintText} ; }; const renderRequired = () => { if(!isRequired) { return null; } return *; }; const renderSubtitle = () => { if(!isShowSubTitle && !isOptional) { return null; } return ( {isOptional ? localize("is-optional") : subTitle} ) ; }; const renderTitle = () => { if (!title) { return null; } return { if(!isDisabled) { focus(); } }} > {renderRequired()} {title} {renderSubtitle()} ; }; const renderValue = () => { if(!selectedItems.length) { return {placeholder ? placeholder : localize("select-an-option")} ; } return {selectedItems.length > 1 ? localize("selected-options-with-count", [ selectedItems.length ]) : selectedItems[0]?.__title} ; }; const renderContent = () => { return {renderValue()} ; }; const renderOverlay = () => { return ; }; return {renderTitle()} { if(!isDisabled) { focus(); } }} > {renderOverlay()} {renderIcon()} {renderContent()} {renderCleanButton()} {renderRightIcon()} {renderHintText()} ; }; export default forwardRef(SelectBox) as unknown as SelectBoxComponent;