index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import {
  2. TouchableOpacity,
  3. View
  4. } from "react-native";
  5. import {
  6. type SelectedItem
  7. } from "./type";
  8. import type ISelectSheetProps from "./type";
  9. import stylesheet, {
  10. useStyles
  11. } from "./stylesheet";
  12. import {
  13. NCoreUIKitLocalize,
  14. NCoreUIKitTheme
  15. } from "../../core/hooks";
  16. import {
  17. CheckIcon
  18. } from "lucide-react-native";
  19. import BottomSheet from "../bottomSheet";
  20. import TextInput from "../textInput";
  21. import Loading from "../loading";
  22. import Button from "../button";
  23. import Text from "../text";
  24. import CheckBox from "../checkBox";
  25. function SelectSheet<T>({
  26. LoadingIconComponentProp,
  27. isWorkWithRealtime,
  28. isShowTools = true,
  29. mainSelectedItems,
  30. moreLoadThreshold,
  31. isMultipleSelect,
  32. setIsMoreLoading,
  33. renderOptionIcon,
  34. bottomSheetProps,
  35. bottomSheetRef,
  36. customLocalize,
  37. setSearchText,
  38. isMoreLoading,
  39. selectBoxKey,
  40. setIsLoading,
  41. selectObject,
  42. keyExtractor,
  43. searchedData,
  44. isSearchable,
  45. customTheme,
  46. setIsActive,
  47. onMoreLoad,
  48. renderItem,
  49. searchText,
  50. maxChoice,
  51. minChoice,
  52. isLoading,
  53. selectAll,
  54. cleanAll,
  55. isActive,
  56. cancel,
  57. title,
  58. data,
  59. ok
  60. }: ISelectSheetProps<T>) {
  61. const {
  62. radiuses,
  63. colors,
  64. spaces
  65. } = NCoreUIKitTheme.useContext(customTheme);
  66. const {
  67. localize
  68. } = NCoreUIKitLocalize.useContext(customLocalize);
  69. const {
  70. moreLoadingContainer: moreLoadingContainerDynamicStyle,
  71. checkIconContainer: checkIconContainerDynamicStyle,
  72. loadingContainer: loadingContainerDynamicStyle,
  73. contentContainer: contentContainerDynamicStyle,
  74. headerContainer: headerContainerDynamicStyle,
  75. bottomContainer: bottomContainerDynamicStyle,
  76. toolsContainer: toolsContainerDynamicStyle,
  77. itemContainer: itemContainerDynamicStyle,
  78. cancelButton: cancelButtonDynamicStyle,
  79. headerTitle: headerTitleDynamicStyle,
  80. okButton: okButtonDynamicStyle
  81. } = useStyles({
  82. isSearchable,
  83. radiuses,
  84. spaces,
  85. colors
  86. });
  87. const renderBottomSheetContent = () => {
  88. if(isLoading) {
  89. return <View
  90. style={[
  91. stylesheet.loadingContainer,
  92. loadingContainerDynamicStyle
  93. ]}
  94. >
  95. {LoadingIconComponentProp ? <LoadingIconComponentProp
  96. color="emphasized"
  97. size={20}
  98. /> : <Loading/>}
  99. </View>;
  100. }
  101. const currentData = searchText && searchText.length ? searchedData : data;
  102. return <View
  103. style={[
  104. contentContainerDynamicStyle
  105. ]}
  106. >
  107. {
  108. currentData.map((item: T & SelectedItem | SelectedItem, index: number) => {
  109. const isSelected = mainSelectedItems.findIndex((sI: SelectedItem) => {
  110. return sI.__key === item.__key;
  111. }) !== -1;
  112. if(renderItem) {
  113. return renderItem({
  114. key: keyExtractor(item as T & SelectedItem, index),
  115. onSelect: (sItem) => {
  116. selectObject(sItem);
  117. },
  118. selectedItems: mainSelectedItems,
  119. setIsMoreLoading,
  120. setIsLoading,
  121. isSelected,
  122. searchText,
  123. index,
  124. item,
  125. data
  126. });
  127. }
  128. let isDisabled = false;
  129. if(isMultipleSelect && !isSelected && maxChoice !== -1) {
  130. if(!maxChoice) {
  131. throw new Error("If you want to use the \"isMultipleSelect\" prop, you must be provide \"maxChoice\" prop.");
  132. }
  133. if(mainSelectedItems.length >= maxChoice) {
  134. isDisabled = true;
  135. }
  136. }
  137. return <TouchableOpacity
  138. key={keyExtractor(item as T & SelectedItem, index)}
  139. disabled={isDisabled}
  140. style={[
  141. stylesheet.itemContainer,
  142. itemContainerDynamicStyle,
  143. {
  144. marginTop: index === 0 ? 0 : spaces.spacingSm,
  145. position: "relative"
  146. }
  147. ]}
  148. onPress={() => {
  149. if(isDisabled) {
  150. return;
  151. }
  152. selectObject(item);
  153. }}
  154. >
  155. <View
  156. style={[
  157. stylesheet.itemContentContainer,
  158. {
  159. zIndex: 99999
  160. }
  161. ]}
  162. >
  163. {renderOptionIcon ? renderOptionIcon({
  164. item,
  165. index
  166. }) : null}
  167. <Text
  168. color={isSelected ? "emphasized" : undefined}
  169. >{item.__title}</Text>
  170. </View>
  171. <View
  172. style={[
  173. stylesheet.checkIconContainer,
  174. checkIconContainerDynamicStyle
  175. ]}
  176. >
  177. {isSelected ?
  178. <CheckIcon
  179. color={colors.content.icon.emphasized}
  180. size={24}
  181. /> : null}
  182. </View>
  183. {isDisabled ? <View
  184. style={[
  185. {
  186. backgroundColor: colors.system.disabled.content,
  187. borderRadius: radiuses.actions,
  188. position: "absolute",
  189. zIndex: 99998,
  190. bottom: 0,
  191. right: 0,
  192. left: 0,
  193. top: 0
  194. }
  195. ]}
  196. /> : null}
  197. </TouchableOpacity>;
  198. })
  199. }
  200. {renderMoreLoading()}
  201. </View>;
  202. };
  203. const renderBottomSheetSearchInput = () => {
  204. if(!isSearchable) {
  205. return null;
  206. }
  207. return <TextInput
  208. key={`${selectBoxKey}-searchInput`}
  209. placeholder={localize("search")}
  210. spreadBehaviour="stretch"
  211. onChangeText={(text: string) => {
  212. setSearchText(text);
  213. }}
  214. />;
  215. };
  216. const renderTools = () => {
  217. if(!isShowTools) {
  218. return null;
  219. }
  220. if(!isMultipleSelect) {
  221. return null;
  222. }
  223. if(minChoice && maxChoice !== -1) {
  224. return null;
  225. }
  226. return <View
  227. style={[
  228. stylesheet.toolsContainer,
  229. toolsContainerDynamicStyle
  230. ]}
  231. >
  232. {!minChoice ? <CheckBox
  233. isChecked={mainSelectedItems.length === 0 ? "checked" : null}
  234. title={localize("clean-all")}
  235. spreadBehaviour="free"
  236. onPress={() => {
  237. cleanAll();
  238. }}
  239. /> : null}
  240. {maxChoice === -1 ? <CheckBox
  241. isChecked={data.length === mainSelectedItems.length ? "checked" : mainSelectedItems.length ? "partially" : null}
  242. title={localize("select-all")}
  243. spreadBehaviour="free"
  244. isFlip={!minChoice}
  245. onPress={() => {
  246. selectAll();
  247. }}
  248. /> : null}
  249. </View>;
  250. };
  251. const renderBottomSheetHeader = () => {
  252. return <View
  253. style={[
  254. stylesheet.headerContainer,
  255. headerContainerDynamicStyle
  256. ]}
  257. >
  258. <Text
  259. variant="titleMediumSize"
  260. style={[
  261. headerTitleDynamicStyle
  262. ]}
  263. >
  264. {title}
  265. </Text>
  266. {renderBottomSheetSearchInput()}
  267. {renderTools()}
  268. </View>;
  269. };
  270. const renderBottomSheetBottom = () => {
  271. if(isWorkWithRealtime) {
  272. return null;
  273. }
  274. return <View
  275. style={[
  276. stylesheet.bottomContainer,
  277. bottomContainerDynamicStyle
  278. ]}
  279. >
  280. <Button
  281. title={localize("cancel")}
  282. spreadBehaviour="stretch"
  283. variant="outline"
  284. type="neutral"
  285. onPress={() => {
  286. cancel();
  287. }}
  288. style={{
  289. ...cancelButtonDynamicStyle
  290. }}
  291. />
  292. <Button
  293. spreadBehaviour="stretch"
  294. title={localize("ok")}
  295. onPress={() => {
  296. ok();
  297. }}
  298. style={{
  299. ...okButtonDynamicStyle
  300. }}
  301. />
  302. </View>;
  303. };
  304. const renderMoreLoading = () => {
  305. if(!isMoreLoading) {
  306. return null;
  307. }
  308. return <View
  309. style={[
  310. stylesheet.moreLoadingContainer,
  311. moreLoadingContainerDynamicStyle
  312. ]}
  313. >
  314. <Loading/>
  315. </View>;
  316. };
  317. return <BottomSheet
  318. {...bottomSheetProps}
  319. scrollEndThreshold={moreLoadThreshold}
  320. key={`${selectBoxKey}-bottomsheet`}
  321. customKey={selectBoxKey}
  322. ref={bottomSheetRef}
  323. isAutoHeight={true}
  324. isActive={isActive}
  325. onScrollEnd={() => {
  326. if(onMoreLoad) onMoreLoad({
  327. selectedItems: mainSelectedItems,
  328. setIsMoreLoading,
  329. setIsLoading,
  330. searchText,
  331. data
  332. });
  333. }}
  334. renderBottom={() => {
  335. return renderBottomSheetBottom();
  336. }}
  337. renderHeader={() => {
  338. return renderBottomSheetHeader();
  339. }}
  340. onClose={() => {
  341. setIsActive(false);
  342. }}
  343. >
  344. {renderBottomSheetContent()}
  345. </BottomSheet>;
  346. };
  347. export default SelectSheet;