index.tsx 8.7 KB

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