react-native-modern-elements
Version:
A modern, customizable UI component library for React Native
202 lines (201 loc) • 10.7 kB
JavaScript
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);