| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867 |
- 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<Exclude<SelectBoxType, "default">, NCoreUIKitIcon> = {
- "question": BadgeQuestionMarkIcon,
- "success": BadgeSuccessIcon,
- "warning": BadgeAlertIcon,
- "danger": BadgeDangerIcon,
- "info": BadgeInfoIcon
- };
- function SelectBox<T>({
- 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,
- onCancel,
- style,
- title,
- onOk
- }: ISelectBoxProps<T>, ref: Ref<ISelectBoxRef<T>>) {
- const {
- inlineSpaces,
- typography,
- radiuses,
- borders,
- spaces,
- colors
- } = NCoreUIKitTheme.useContext(customTheme);
- const {
- localize
- } = NCoreUIKitLocalize.useContext(customLocalize);
- const bottomSheetRef = useRef<IBottomSheetRef>(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<Array<T & SelectedItem | SelectedItem>>([]);
- const [
- searchedData,
- setSearchedData
- ] = useState<Array<T & SelectedItem | SelectedItem>>([]);
- const [
- isActive,
- setIsActive
- ] = useState(false);
- const [
- searchText,
- setSearchText
- ] = useState("");
- const [
- isLoading,
- setIsLoading
- ] = useState(false);
- const [
- isMoreLoading,
- setIsMoreLoading
- ] = useState(true);
- const [
- selectedItems,
- setSelectedItems
- ] = useState<Array<SelectedItem>>(initialSelectedItems ?? []);
- const [
- tempSelectedItems,
- setTempSelectedItems
- ] = useState<Array<SelectedItem>>(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<T & SelectedItem>, mainSelectedItems);
- setSearchedData(_searchedData);
- } else {
- const _searchedData = data.filter(dI => {
- return dI.__title.toLocaleLowerCase().includes(searchText.toLocaleLowerCase());
- }) as Array<T & SelectedItem>;
- setSearchedData(_searchedData);
- }
- } else if(searchedData && searchedData.length) {
- setSearchedData([]);
- }
- }, [
- data,
- searchText
- ]);
- useEffect(() => {
- const initData = prepareDatas(initialData as Array<T & SelectedItem>, initialSelectedItems);
- setData(initData);
- }, []);
- useEffect(() => {
- if(onChange) {
- onChange(mainSelectedItems, data, setIsMoreLoading, setIsLoading);
- }
- }, [
- tempSelectedItems,
- selectedItems,
- data
- ]);
- 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();
- });
- if(onCancel) {
- onCancel(mainSelectedItems, data, setIsMoreLoading, setIsLoading);
- }
- }
- };
- const ok = () => {
- if(!isWorkWithRealtime) {
- setSelectedItems(tempSelectedItems);
- bottomSheetRef.current?.close(() => {
- blur();
- });
- if(onOk) {
- onOk(mainSelectedItems, data, setIsMoreLoading, setIsLoading);
- }
- }
- };
- const cleanSelections = () => {
- setSelectedItems([]);
- if(!isWorkWithRealtime) {
- setTempSelectedItems([]);
- }
- };
- const updateData = (newData: Array<T>, newSelectedItems: Array<SelectedItem>) => {
- 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<SelectedItem>) => {
- 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<T & SelectedItem>, sItems?: Array<SelectedItem>) => {
- const initData: Array<SelectedItem | T & SelectedItem> = [];
- 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 <TouchableOpacity
- style={[
- cleanIconStyle,
- stylesheet.cleanButton,
- cleanButtonDynamicStyle
- ]}
- onPress={() => {
- if(isWorkWithRealtime) {
- setSelectedItems([]);
- } else {
- setTempSelectedItems([]);
- }
- }}
- >
- <CleanIcon
- color="mid"
- size={20}
- />
- </TouchableOpacity>;
- };
- const renderIcon = () => {
- if (!IconComponentProp) {
- return null;
- }
- return <TouchableOpacity
- onPress={iconOnPress}
- style={[
- iconStyle,
- stylesheet.icon,
- iconDynamicStyle
- ]}
- >
- <IconComponentProp
- color={iconProps.color}
- size={iconProps.size}
- />
- </TouchableOpacity>;
- };
- const renderRightIcon = () => {
- if (!RightIconComponentProp) {
- return null;
- }
- if(isCleanEnabled && selectedItems.length > 0 && variant === "text") {
- return null;
- }
- return <TouchableOpacity
- onPress={rightIconOnPress}
- style={[
- rightIconStyle,
- stylesheet.rightIcon,
- rightIconDynamicStyle
- ]}
- >
- <RightIconComponentProp
- color={iconProps.color}
- size={iconProps.size}
- />
- </TouchableOpacity>;
- };
- const renderHintIcon = () => {
- if(!isShowHintTextIcon) {
- return null;
- }
- if(HintTextIconProp) {
- return <HintTextIconProp
- color={isDisabled ? "disabled" : currentType.hintTextIconColor}
- size={20}
- style={[
- hintTextIconStyle,
- stylesheet.hintTextIcon,
- hintTextIconDynamicStyle
- ]}
- />;
- }
- const CurrentHintIcon = SelectBoxTypeIcon[type === "default" ? "question" : type];
- return <CurrentHintIcon
- customColor={isDisabled ? colors.system.state.content.disabled[styleType] : undefined}
- color={currentType.hintTextIconColor}
- size={20}
- style={[
- hintTextIconStyle,
- stylesheet.hintTextIcon,
- hintTextIconDynamicStyle
- ]}
- />;
- };
- const renderHintText = () => {
- if (!hintText) {
- return null;
- }
- return <View
- style={[
- hintTextContainerStyle,
- stylesheet.hintText,
- hintTextDynamicStyle
- ]}
- >
- {renderHintIcon()}
- <Text
- customColor={isDisabled ? colors.system.state.content.disabled[styleType] : undefined}
- color={currentType.hintTextColor}
- variant="labelSmallSize"
- >
- {hintText}
- </Text>
- </View>;
- };
- const renderRequired = () => {
- if(!isRequired) {
- return null;
- }
- return <Text
- color="danger"
- style={[
- stylesheet.required,
- requiredDynamicStyle
- ]}
- >*</Text>;
- };
- const renderSubtitle = () => {
- if(!isShowSubTitle && !isOptional) {
- return null;
- }
- return <Text
- variant="labelLargeSize"
- color={titleProps.color}
- style={[
- stylesheet.subTitle,
- subTitleDynamicStyle
- ]}
- >
- ( {isOptional ? localize("is-optional") : subTitle} )
- </Text>;
- };
- const renderTitle = () => {
- if (!title) {
- return null;
- }
- return <TouchableOpacity
- style={[
- stylesheet.titleContainer,
- titleContainerDynamicStyle
- ]}
- onPress={() => {
- if(!isDisabled) {
- focus();
- }
- }}
- >
- {renderRequired()}
- <Text
- {...titleProps}
- variant={titleProps.variant}
- color={titleProps.color}
- style={[
- stylesheet.title,
- titleDynamicStyle
- ]}
- >
- {title}
- </Text>
- {renderSubtitle()}
- </TouchableOpacity>;
- };
- const renderValue = () => {
- if(!selectedItems.length) {
- return <Text
- variant="labelLargeSize"
- style={[
- stylesheet.contentText,
- contentTextDynamicStyle
- ]}
- >
- {placeholder ? placeholder : localize("select-an-option")}
- </Text>;
- }
- return <Text
- variant="labelLargeSize"
- style={[
- stylesheet.contentText,
- contentTextDynamicStyle
- ]}
- >
- {selectedItems.length > 1 ? localize("selected-options-with-count", [
- selectedItems.length
- ]) : selectedItems[0]?.__title}
- </Text>;
- };
- const renderContent = () => {
- return <View
- style={[
- contentStyle,
- stylesheet.content,
- contentDynamicStyle
- ]}
- >
- {renderValue()}
- </View>;
- };
- const renderOverlay = () => {
- return <View
- style={[
- stylesheet.overlay,
- overlayDynamicStyle
- ]}
- />;
- };
- return <View
- style={[
- style,
- stylesheet.container,
- containerDynamicStyle
- ]}
- >
- {renderTitle()}
- <TouchableOpacity
- disabled={isDisabled}
- style={[
- contentContainerStyle,
- stylesheet.contentContainer,
- contentContainerDynamicStyle
- ]}
- onPress={() => {
- if(!isDisabled) {
- focus();
- }
- }}
- >
- {renderOverlay()}
- {renderIcon()}
- {renderContent()}
- {renderCleanButton()}
- {renderRightIcon()}
- </TouchableOpacity>
- {renderHintText()}
- <SelectSheet
- LoadingIconComponentProp={LoadingIconComponentProp}
- customLocalize={customSelectSheetLocalize}
- isWorkWithRealtime={isWorkWithRealtime}
- mainSelectedItems={mainSelectedItems}
- moreLoadThreshold={moreLoadThreshold}
- isShowTools={isShowSelectSheetTools}
- customTheme={customSelectSheetTheme}
- isMultipleSelect={isMultipleSelect}
- renderOptionIcon={renderOptionIcon}
- setIsMoreLoading={setIsMoreLoading}
- bottomSheetProps={selectSheetProps}
- selectBoxKey={selectBoxKey.current}
- bottomSheetRef={bottomSheetRef}
- setSearchText={setSearchText}
- isMoreLoading={isMoreLoading}
- searchedData={searchedData}
- isSearchable={isSearchable}
- keyExtractor={keyExtractor}
- selectObject={selectObject}
- setIsLoading={setIsLoading}
- setIsActive={setIsActive}
- searchText={searchText}
- renderItem={renderItem}
- onMoreLoad={onMoreLoad}
- maxChoice={maxChoice}
- minChoice={minChoice}
- isLoading={isLoading}
- selectAll={selectAll}
- cleanAll={cleanAll}
- isActive={isActive}
- cancel={cancel}
- title={title}
- data={data}
- ok={ok}
- />
- </View>;
- };
- export default forwardRef(SelectBox) as unknown as SelectBoxComponent;
|