@nexara/nativeflow
Version:
Beautiful, responsive, and customizable UI components for React Native – built for performance and seamless experiences.
156 lines (155 loc) • 4.46 kB
JavaScript
"use strict";
import React, { useState, useRef, useCallback, useEffect, useMemo } from "react";
import { StyleSheet, Pressable, ScrollView, Animated } from "react-native";
import { StyledView } from "../StyledComponents/index.js";
import Portal from "../Portal/Portal.js";
import { positionCalculations } from "./calculations.js";
import { horizontalScale, verticalScale } from "../../helpers/ResponsiveCalculations.js";
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
const Menu = ({
placement = 'top',
anchor,
disableBuiltInState = false,
isOpen = false,
onRequestOpen,
onRequestClose,
onSelect,
style,
children
}) => {
const [isVisible, setIsVisible] = useState(false);
const anchorLayoutRef = useRef({
pageY: 0,
pageX: 0,
height: 0,
width: 0
});
const menuLayoutRef = useRef({
height: 0,
width: 0
});
const buttonRef = useRef(null);
const animatedScaleRef = useRef(new Animated.Value(1)).current;
const positions = useCallback(() => positionCalculations(anchorLayoutRef.current, menuLayoutRef.current, placement), [menuLayoutRef, anchorLayoutRef, placement])();
const STYLES = useMemo(Styles, []);
useEffect(() => {
Animated.timing(animatedScaleRef, {
toValue: disableBuiltInState && isOpen || isVisible ? 1 : 0,
duration: 150,
useNativeDriver: true
}).start();
}, [isOpen, isVisible]);
const measureLayout = useCallback(() => {
return new Promise(resolve => {
buttonRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
anchorLayoutRef.current = {
pageY,
pageX,
height,
width
};
resolve(true);
});
});
}, []);
const onOpen = async () => {
await measureLayout();
if (disableBuiltInState) {
onRequestOpen?.();
} else {
setIsVisible(true);
}
};
const onClose = () => {
if (disableBuiltInState) {
onRequestClose?.();
} else {
setIsVisible(false);
}
};
const renderChildren = useCallback(() => {
return /*#__PURE__*/_jsx(ScrollView, {
children: React.Children?.toArray(children).map(child => {
if (/*#__PURE__*/React.isValidElement(child)) {
return /*#__PURE__*/React.cloneElement(child, {
onPress: () => {
child?.props?.onPress?.();
onSelect?.(child?.props?.name);
onClose();
}
});
}
return null;
})
});
}, [children])();
const scale = disableBuiltInState ? Number(isOpen) : Number(isVisible);
const pointerEvent = isVisible && !disableBuiltInState || isOpen && disableBuiltInState ? 'auto' : 'none';
const dynamicStyles = {
left: positions.left,
top: positions.top
};
const menuAnimatedStyle = {
transform: [{
scale: animatedScaleRef
}],
opacity: animatedScaleRef
};
return /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(Portal, {
name: "Menu",
children: /*#__PURE__*/_jsx(Pressable, {
style: [StyleSheet.absoluteFill, {
transform: [{
scale
}]
}],
pointerEvents: pointerEvent,
onPress: onClose,
children: /*#__PURE__*/_jsx(Animated.View, {
onStartShouldSetResponder: () => true,
style: [STYLES.MENU_CONT, dynamicStyles, menuAnimatedStyle, style],
onLayout: ({
nativeEvent: {
layout: {
height,
width
}
}
}) => menuLayoutRef.current = {
width,
height
},
children: renderChildren
})
})
}), /*#__PURE__*/_jsx(StyledView, {
alignItems: "flex-start",
ref: buttonRef,
onLayout: measureLayout,
children: anchor && /*#__PURE__*/React.isValidElement(anchor) && /*#__PURE__*/React.cloneElement(anchor, {
onPress: onOpen
})
})]
});
};
export default Menu;
const Styles = () => StyleSheet.create({
MENU_CONT: {
shadowColor: "#000",
backgroundColor: '#fff',
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.23,
shadowRadius: 2.62,
elevation: 4,
position: 'absolute',
borderRadius: 5,
maxHeight: 400,
minWidth: horizontalScale(150),
paddingVertical: verticalScale(5)
}
});
//# sourceMappingURL=Menu.js.map