Zipline-Android-App/components/Dropdown.tsx

215 lines
4.5 KiB
TypeScript

import { MaterialIcons } from "@expo/vector-icons";
import { useState, useRef } from "react";
import Button from "@/components/Button";
import {
View,
Text,
TouchableOpacity,
Dimensions,
Modal,
type StyleProp,
type ViewStyle,
Pressable,
type DimensionValue,
ScrollView,
type ColorValue,
} from "react-native";
interface Item {
name: string;
id: string;
color?: ColorValue;
iconColor?: ColorValue;
disabled?: boolean;
icon?: keyof typeof MaterialIcons.glyphMap;
onPress: () => Promise<void> | void;
}
interface DropdownPosition {
top: number;
left: number;
}
interface Props {
data: Array<Item>;
containerStyle?: StyleProp<ViewStyle>;
width?: DimensionValue;
height?: DimensionValue;
iconColor?: ColorValue;
iconSize?: number;
icon: keyof typeof MaterialIcons.glyphMap;
margin?: {
top?: DimensionValue;
bottom?: DimensionValue;
left?: DimensionValue;
right?: DimensionValue;
};
dropdown?: {
width?: number;
maxHeight?: number;
margin?: {
top?: DimensionValue;
bottom?: DimensionValue;
left?: DimensionValue;
right?: DimensionValue;
};
};
}
const minimumMargin = 15;
export default function Dropdown({
data,
containerStyle,
height,
width,
icon,
iconSize = 25,
iconColor = "#575DB5",
dropdown,
margin = {},
}: Props) {
const [visible, setVisible] = useState(false);
const [dropdownPosition, setDropdownPosition] = useState<DropdownPosition>({
top: 0,
left: 0,
});
const dropdownButtonRef = useRef<View>(null);
const screen = Dimensions.get("window");
const screenWidth = screen.width;
const dropdownWidth = dropdown?.width || screenWidth * 0.4;
return (
<View
style={[
{
position: "relative",
width: width,
height: height,
marginTop: margin.top,
marginBottom: margin.bottom,
marginLeft: margin.left,
marginRight: margin.right,
},
containerStyle,
]}
>
<Pressable
android_ripple={{
color: "#464953",
}}
ref={dropdownButtonRef}
style={{
borderWidth: 2,
borderColor: "#222c47",
borderRadius: 7,
}}
onPress={() => {
if (visible) {
setVisible(false);
} else {
dropdownButtonRef.current?.measure(
(
_fx: number,
_fy: number,
_elementWidth: number,
elementHeight: number,
px: number,
py: number,
) => {
let left = px;
const spaceBelow = screen.height - (py + elementHeight);
const spaceAbove = py;
const dropdownHeight =
(data.length * 50 > (dropdown?.maxHeight || 325)
? dropdown?.maxHeight || 325
: data.length * 50) +
(typeof height === "number" ? height : 40) +
minimumMargin;
let top = py + elementHeight;
if (spaceAbove > spaceBelow && spaceAbove > dropdownHeight) {
top = py - dropdownHeight;
}
if (left < minimumMargin) {
left = minimumMargin;
} else if (left + dropdownWidth > screenWidth - minimumMargin) {
left = screenWidth - dropdownWidth - minimumMargin;
}
setDropdownPosition({ top: top, left: left });
setVisible(true);
},
);
}
}}
>
<MaterialIcons name={icon} size={iconSize} color={iconColor} />
</Pressable>
<Modal visible={visible} transparent animationType="none">
<TouchableOpacity
style={{
width: "100%",
height: "100%",
backgroundColor: "#0000004d",
}}
activeOpacity={1}
onPress={() => setVisible(false)}
>
<ScrollView
style={[
{
position: "absolute",
borderRadius: 8,
overflow: "hidden",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.25,
shadowRadius: 8,
elevation: 8,
maxHeight: dropdown?.maxHeight || 325,
},
{
backgroundColor: "#1a202c",
borderColor: "#2d3748",
borderWidth: 1,
},
{
top: dropdownPosition.top,
left: dropdownPosition.left,
width: dropdownWidth,
},
]}
>
{data.map((item) => (
<Button
disabled={item.disabled}
position="left"
bold={false}
text={item.name}
icon={item.icon}
color="#191b27"
textColor={item.color}
onPress={() => {
item.onPress();
setVisible(false);
}}
key={item.id}
iconColor={item.iconColor}
/>
))}
</ScrollView>
</TouchableOpacity>
</Modal>
</View>
);
}