index.tsx 9.1 KB

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