index.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. import {
  2. useImperativeHandle,
  3. useLayoutEffect,
  4. forwardRef,
  5. useEffect,
  6. useState,
  7. Fragment,
  8. useRef
  9. } from "react";
  10. import {
  11. Platform,
  12. Animated,
  13. Easing,
  14. View
  15. } from "react-native";
  16. import {
  17. type MenuButton as MenuButtonType
  18. } from "./components/menuButton/type";
  19. import {
  20. type IMenuRef
  21. } from "./type";
  22. import type IMenuProps from "./type";
  23. import stylesheet, {
  24. useStyles
  25. } from "./stylesheet";
  26. import MenuButton from "./components/menuButton";
  27. import {
  28. NCoreUIKitTheme,
  29. NCoreUIKitMenu
  30. } from "../../core/hooks";
  31. import type {
  32. RefForwardingComponent
  33. } from "../../types";
  34. import {
  35. SafeAreaView
  36. } from "react-native-safe-area-context";
  37. import {
  38. Portal
  39. } from "../../helpers/portalize";
  40. import Seperator from "../seperator";
  41. import SiteLogo from "../siteLogo";
  42. import Modal from "../modal";
  43. const Menu: RefForwardingComponent<IMenuRef, IMenuProps> = ({
  44. isWorkWithSafeAreaView = true,
  45. isWorkWithAnimation = true,
  46. renderHeader: RenderHeader,
  47. renderFooter: RenderFooter,
  48. portalName = "menu-system",
  49. isWorkWithPortal = true,
  50. isWorkWithModal = true,
  51. customBackgroundColor,
  52. renderHeaderSeperator,
  53. close: closeAction,
  54. glassEffect = 0,
  55. customLocalize,
  56. seperatorProps,
  57. renderSiteLogo,
  58. siteLogoProps,
  59. customTheme,
  60. navigation,
  61. modalProps,
  62. isCollapse,
  63. onClosed,
  64. onOpened,
  65. buttons,
  66. onClose,
  67. onOpen,
  68. style,
  69. id,
  70. ...props
  71. }, ref) => {
  72. const {
  73. colors,
  74. spaces
  75. } = NCoreUIKitTheme.useContext(customTheme);
  76. const {
  77. headerContainer: headerContainerDynamicStyle,
  78. seperator: seperatorDynamicStyle,
  79. container: containerDynamicStyle
  80. } = useStyles({
  81. customBackgroundColor,
  82. glassEffect,
  83. colors,
  84. spaces
  85. });
  86. const [
  87. isOpacityVisible,
  88. setIsOpacityVisible
  89. ] = useState(false);
  90. const [
  91. measures,
  92. setMeasures
  93. ] = useState<null | number>(null);
  94. const [
  95. isActive,
  96. setIsActive
  97. ] = useState(false);
  98. const animatedX = useRef(new Animated.Value(0)).current;
  99. useImperativeHandle(
  100. ref,
  101. () => ({
  102. close,
  103. open
  104. }),
  105. []
  106. );
  107. useEffect(() => {
  108. if(measures !== null) {
  109. animatedX.setValue(measures * -1);
  110. if(!isOpacityVisible) setIsOpacityVisible(true);
  111. if(isActive) setIsActive(false);
  112. }
  113. }, [measures]);
  114. useEffect(() => {
  115. if(isCollapse !== undefined) {
  116. if(isCollapse) {
  117. setIsActive(isCollapse);
  118. } else {
  119. if(onClose) onClose({
  120. id
  121. });
  122. if(isWorkWithAnimation) {
  123. closeAnimation(() => setIsActive(isCollapse));
  124. } else {
  125. animatedX.setValue(measures! * -1);
  126. setIsActive(isCollapse);
  127. if(onClosed) onClosed({
  128. id
  129. });
  130. }
  131. }
  132. }
  133. }, [isCollapse]);
  134. useLayoutEffect(() => {
  135. if(isActive && isOpacityVisible) {
  136. if(onOpen) onOpen({
  137. id
  138. });
  139. if(isWorkWithAnimation) {
  140. openAnimation();
  141. } else {
  142. animatedX.setValue(0);
  143. if(onOpened) onOpened({
  144. id
  145. });
  146. }
  147. }
  148. }, [
  149. isOpacityVisible,
  150. isActive
  151. ]);
  152. const open = () => {
  153. setIsActive(true);
  154. };
  155. const close = () => {
  156. if(onClose) onClose({
  157. id
  158. });
  159. if(isWorkWithAnimation) {
  160. closeAnimation(() => setIsActive(false));
  161. } else {
  162. animatedX.setValue(measures! * -1);
  163. setIsActive(false);
  164. if(onClosed) onClosed({
  165. id
  166. });
  167. }
  168. };
  169. const openAnimation = () => {
  170. Animated.timing(animatedX, {
  171. useNativeDriver: Platform.OS !== "web",
  172. easing: Easing.linear,
  173. duration: 300,
  174. toValue: 0
  175. }).start(({
  176. finished
  177. }) => {
  178. if(finished) {
  179. if(onOpened) onOpened({
  180. id
  181. });
  182. }
  183. });
  184. };
  185. const closeAnimation = (callback?: () => void) => {
  186. Animated.timing(animatedX, {
  187. useNativeDriver: Platform.OS !== "web",
  188. toValue: measures! * -1,
  189. easing: Easing.linear,
  190. duration: 300
  191. }).start(({
  192. finished
  193. }) => {
  194. if(finished) {
  195. if(callback) callback();
  196. if(onClosed) onClosed({
  197. id
  198. });
  199. }
  200. });
  201. };
  202. const updateMenuButtonCollabs = ({
  203. depthMap,
  204. status
  205. }: {
  206. depthMap: Array<number>;
  207. status: boolean;
  208. }) => {
  209. const state = NCoreUIKitMenu.get({
  210. id
  211. });
  212. if(!state) {
  213. return;
  214. }
  215. const recursivelyCatch = (item: Array<MenuButtonType>, index = 0): Array<MenuButtonType> => {
  216. const tIndex = depthMap[index];
  217. return item.map((c_item, c_index) => {
  218. if(c_index !== tIndex) {
  219. return c_item;
  220. }
  221. if(index === depthMap.length - 1) {
  222. return {
  223. ...c_item,
  224. isCollapse: status
  225. };
  226. }
  227. return {
  228. ...c_item,
  229. subButtons: recursivelyCatch(c_item.subButtons as Array<MenuButtonType>, index + 1)
  230. };
  231. });
  232. };
  233. const newButtons = recursivelyCatch(state.buttons);
  234. state.buttons = newButtons;
  235. NCoreUIKitMenu.update({
  236. menuData: state,
  237. id: id
  238. });
  239. };
  240. const renderHeader = () => {
  241. if(RenderHeader) {
  242. return <RenderHeader/>;
  243. }
  244. return <View
  245. style={[
  246. stylesheet.headerContainer,
  247. headerContainerDynamicStyle
  248. ]}
  249. >
  250. {renderSiteLogo ? renderSiteLogo() : <SiteLogo
  251. customLocalize={customLocalize}
  252. customTheme={customTheme}
  253. {...siteLogoProps}
  254. />}
  255. {renderHeaderSeperator ? renderHeaderSeperator() : <Seperator
  256. {...seperatorProps}
  257. length="100%"
  258. style={[
  259. seperatorProps?.style,
  260. stylesheet.seperator,
  261. seperatorDynamicStyle
  262. ]}
  263. />}
  264. </View>;
  265. };
  266. const renderButtons = () => {
  267. if(!buttons || !buttons.length) {
  268. return null;
  269. }
  270. return buttons.map((buttonItem, buttonIndex) => {
  271. return <MenuButton
  272. updateMenuButtonCollabs={updateMenuButtonCollabs}
  273. key={`menu-button-${id}-${buttonIndex}`}
  274. closeAction={closeAction}
  275. buttonIndex={buttonIndex}
  276. depthMap={[buttonIndex]}
  277. buttonItem={buttonItem}
  278. navigation={navigation}
  279. id={id}
  280. />;
  281. });
  282. };
  283. const renderFooter = () => {
  284. if(!RenderFooter) {
  285. return null;
  286. }
  287. return <RenderFooter/>;
  288. };
  289. const renderSafeAreaContext = () => {
  290. if(!isWorkWithSafeAreaView) {
  291. return <Fragment>
  292. {renderHeader()}
  293. </Fragment>;
  294. }
  295. return <SafeAreaView
  296. style={[
  297. stylesheet.safeAreaView
  298. ]}
  299. >
  300. {renderHeader()}
  301. <View
  302. style={[
  303. stylesheet.content
  304. ]}
  305. >
  306. {renderButtons()}
  307. </View>
  308. {renderFooter()}
  309. </SafeAreaView>;
  310. };
  311. const renderContent = () => {
  312. return <Animated.View
  313. {...props}
  314. style={[
  315. style,
  316. stylesheet.container,
  317. containerDynamicStyle,
  318. {
  319. opacity: isOpacityVisible ? 1 : 0,
  320. transform: [{
  321. translateX: animatedX
  322. }]
  323. }
  324. ]}
  325. onLayout={(event) => {
  326. const width = event.nativeEvent.layout.width;
  327. setMeasures(width);
  328. }}
  329. >
  330. {renderSafeAreaContext()}
  331. </Animated.View>;
  332. };
  333. if(isWorkWithModal && measures !== null) {
  334. return <Modal
  335. {...modalProps}
  336. isWorkWithPortal={isWorkWithPortal}
  337. isOverlayVisible={isOpacityVisible}
  338. onOverlayPress={() => {
  339. close();
  340. }}
  341. portalName={portalName}
  342. isActive={isActive}
  343. id="menu-modal"
  344. >
  345. {renderContent()}
  346. </Modal>;
  347. }
  348. if(isWorkWithPortal && measures !== null) {
  349. return <Portal
  350. name={portalName}
  351. >
  352. {renderContent()}
  353. </Portal>;
  354. }
  355. return renderContent();
  356. };
  357. export default forwardRef(Menu);