UNPKG

react-native-modern-elements

Version:

A modern, customizable UI component library for React Native

202 lines (201 loc) 10.7 kB
import React, { memo, useEffect, useRef, useState } from "react"; import { Keyboard, Modal, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, TouchableWithoutFeedback, View, } from "react-native"; import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring, withTiming, } from "react-native-reanimated"; import { colors } from "../constants/theme"; import { verticalScale } from "../utils/styling"; import DownArrow from "../assets/svg/DownArrow"; import SearchIconSvg from "../assets/svg/SearchIconSvg"; import CancelIcon from "../assets/svg/CancelIcon"; /** ------------------ Default Styles ------------------ */ const DEFAULT_BOX_STYLE = { backgroundColor: "#fff", borderRadius: 10, paddingHorizontal: 5, }; const DEFAULT_DROPDOWN_SHADOW = { shadowColor: colors === null || colors === void 0 ? void 0 : colors.black_50, shadowOffset: { width: 0, height: 10 }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 10, }; const DEFAULT_DROPDOWN_STYLE = { backgroundColor: "#fff", borderRadius: 10, overflow: "hidden", }; /** ------------------ Component ------------------ */ const SelectList = ({ setSelected, placeholder, InputboxStyles, dropdownItemStyles, dropdownTextStyles, maxHeight = 200, data, defaultOption, searchicon = false, search = true, searchPlaceholder = "Search", notFoundText = "No data found", onSelect = () => { }, onSelectItem = () => { }, save = "key", dropdownShown = false, DefaultTitle, lable, lableStyle, Reset = false, allIconHeight = 16, allIconWidth = 16, inputTexStyles = 16, selectedIcons = false, dropdownShadow = true, dropdownBoxStyle, }) => { const ref = useRef(null); /** ------------------ States ------------------ */ const [dropdown, setDropdown] = useState(dropdownShown); const [selectedVal, setSelectedVal] = useState(""); const [selectedIcon, setSelectedIcon] = useState(null); const [filteredData, setFilteredData] = useState(data); const [boxHeight, setBoxHeight] = useState(0); const [dropdownPos, setDropdownPos] = useState({ x: 0, y: 0, width: 0 }); const [oldOption, setOldOption] = useState(null); const [_firstRender, setFirstRender] = useState(true); /** ------------------ Animation ------------------ */ const heights = useSharedValue(0); const rotate = useSharedValue(0); const rotateStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${rotate.value}deg` }], })); const dropdownAnimatedStyle = useAnimatedStyle(() => ({ height: heights.value, })); /** ------------------ Dropdown Controls ------------------ */ const openDropdown = () => { var _a; (_a = ref.current) === null || _a === void 0 ? void 0 : _a.measureInWindow((x, y, width, height) => { setDropdownPos({ x, y, width }); setBoxHeight(height); setDropdown(true); heights.value = withSpring(maxHeight, { damping: 12, stiffness: 120, mass: 1, }); rotate.value = withTiming(180, { duration: 300 }); }); }; const closeDropdown = () => { heights.value = withTiming(0, { duration: 300 }, (finished) => { if (finished) runOnJS(setDropdown)(false); }); rotate.value = withTiming(0, { duration: 300 }); }; /** ------------------ Effects ------------------ */ // Update filtered data when data changes useEffect(() => setFilteredData(data), [data]); // Trigger onSelect callback useEffect(() => { if (_firstRender) { setFirstRender(false); return; } onSelect(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedVal]); // Handle default option useEffect(() => { if (!_firstRender && defaultOption && oldOption !== defaultOption.key) { setOldOption(defaultOption.key); setSelected(defaultOption.key); setSelectedVal(defaultOption.value); } if (_firstRender && (defaultOption === null || defaultOption === void 0 ? void 0 : defaultOption.key) !== undefined) { setOldOption(defaultOption.key); setSelected(defaultOption.key); setSelectedVal(defaultOption.value); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultOption, _firstRender]); /** ------------------ Handlers ------------------ */ const handleSelect = (item) => { const valueToSave = save === "value" ? item.value : item.key; setSelected(valueToSave); setSelectedVal(item.modallable); setSelectedIcon(item.icons); onSelectItem(item.value); closeDropdown(); }; const handleReset = () => { setSelected(undefined); setSelectedVal(DefaultTitle || ""); onSelectItem("Reset"); closeDropdown(); setTimeout(() => setFilteredData(data), 500); }; const handleSearch = (text) => { const result = data.filter((item) => { var _a; return (_a = item.value) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(text.toLowerCase()); }); setFilteredData(result); }; /** ------------------ Render Option ------------------ */ const renderOption = (item, index) => (React.createElement(TouchableOpacity, { key: index, style: [styles.option, dropdownItemStyles], disabled: item.disabled, onPress: () => handleSelect(item) }, React.createElement(View, { style: { flexDirection: "row", alignItems: "center", gap: 4 } }, item.icons && selectedIcons && React.createElement(View, null, item.icons), React.createElement(Text, { style: [dropdownTextStyles, item.disabled && { color: "#c4c5c6" }] }, item.modallable)))); return (React.createElement(View, { style: { gap: 10 } }, lable && React.createElement(Text, { style: [styles.label, lableStyle] }, lable), React.createElement(TouchableOpacity, { ref: ref, activeOpacity: 0.9, style: [ InputboxStyles, { flexDirection: "row", alignItems: "center", justifyContent: "space-between", overflow: "hidden", }, ], onPress: () => { Keyboard.dismiss(); // eslint-disable-next-line no-unused-expressions dropdown ? closeDropdown() : openDropdown(); } }, React.createElement(View, { style: { flexDirection: "row", alignItems: "center" } }, selectedIcon && selectedIcons && React.createElement(View, null, selectedIcon), React.createElement(Text, { style: { fontSize: inputTexStyles } }, selectedVal || placeholder || DefaultTitle)), React.createElement(Animated.View, { style: [ { width: verticalScale(allIconWidth), height: verticalScale(allIconHeight), }, rotateStyle, ] }, React.createElement(DownArrow, { color: colors.black_50, width: "100%", height: "100%" }))), dropdown && (React.createElement(Modal, { transparent: true, animationType: "none", visible: dropdown }, React.createElement(View, { style: { flex: 1 } }, React.createElement(TouchableWithoutFeedback, { onPress: closeDropdown }, React.createElement(View, { style: styles.modalOverlay })), data.length >= 5 && search && (React.createElement(View, { style: [ DEFAULT_BOX_STYLE, InputboxStyles, { position: "absolute", top: dropdownPos.y, left: dropdownPos.x, width: dropdownPos.width, zIndex: 10, }, ] }, React.createElement(View, { style: { flexDirection: "row", alignItems: "center", flex: 1, justifyContent: "space-between", } }, searchicon && (React.createElement(SearchIconSvg, { color: colors.black_50, width: verticalScale(allIconWidth), height: verticalScale(allIconHeight) })), React.createElement(TextInput, { placeholder: searchPlaceholder, onChangeText: handleSearch, style: { paddingVertical: boxHeight - 35, width: searchicon ? "80%" : "85%", fontSize: inputTexStyles, } }), React.createElement(TouchableOpacity, { onPress: closeDropdown, style: { marginRight: searchicon ? 0 : 2 } }, React.createElement(CancelIcon, { width: verticalScale(allIconWidth - 1), height: verticalScale(allIconHeight - 1) }))))), React.createElement(Animated.View, { style: [ dropdownAnimatedStyle, dropdownShadow && DEFAULT_DROPDOWN_SHADOW, DEFAULT_DROPDOWN_STYLE, dropdownBoxStyle, { position: "absolute", top: dropdownPos.y + boxHeight + 5, left: dropdownPos.x, width: dropdownPos.width, maxHeight, zIndex: 3, }, ] }, React.createElement(ScrollView, { contentContainerStyle: { paddingVertical: 10 }, nestedScrollEnabled: true }, Reset && (React.createElement(TouchableOpacity, { style: [styles.option, dropdownItemStyles], onPress: handleReset }, React.createElement(Text, { style: [dropdownTextStyles, { color: "red" }] }, "Reset"))), filteredData.length > 0 ? (filteredData.map(renderOption)) : (React.createElement(Text, { style: dropdownTextStyles }, notFoundText))))))))); }; /** ------------------ Styles ------------------ */ const styles = StyleSheet.create({ option: { paddingHorizontal: 20, paddingVertical: 10 }, modalOverlay: { flex: 1, backgroundColor: "transparent" }, label: { fontSize: verticalScale(14), color: colors.black_80 }, }); export default memo(SelectList);