mirror of
https://github.com/Stef-00012/Zipline-Android-App.git
synced 2025-05-10 18:05:52 +02:00
237 lines
5.4 KiB
TypeScript
237 lines
5.4 KiB
TypeScript
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
|
import { useState, useEffect, type ReactNode } from "react";
|
|
import { styles } from "@/styles/components/select";
|
|
import CheckBox from "@/components/CheckBox";
|
|
import {
|
|
Text,
|
|
View,
|
|
TouchableOpacity,
|
|
FlatList,
|
|
Modal,
|
|
type DimensionValue,
|
|
} from "react-native";
|
|
|
|
export interface SelectProps {
|
|
data: Array<{
|
|
label: string;
|
|
value: string;
|
|
[key: string]: string | number | boolean | null;
|
|
}>;
|
|
placeholder: string;
|
|
onSelect: (selectedItem: SelectProps["data"], id?: SelectProps["id"]) => void;
|
|
showScrollIndicator?: boolean;
|
|
disabled?: boolean;
|
|
defaultValue?: SelectProps["data"][0];
|
|
defaultValues?: SelectProps["data"];
|
|
maxHeight?: number;
|
|
multiple?: boolean;
|
|
renderSelectedItem?: (
|
|
item: SelectProps["data"][0],
|
|
key: string,
|
|
) => ReactNode | string;
|
|
renderItem?: (
|
|
item: SelectProps["data"][0],
|
|
closeSelect: () => void,
|
|
) => ReactNode;
|
|
id?: string;
|
|
width?: DimensionValue;
|
|
margin?: {
|
|
top?: DimensionValue;
|
|
bottom?: DimensionValue;
|
|
left?: DimensionValue;
|
|
right?: DimensionValue;
|
|
};
|
|
title?: string;
|
|
description?: string;
|
|
}
|
|
|
|
export default function Select({
|
|
data,
|
|
placeholder,
|
|
showScrollIndicator,
|
|
defaultValue,
|
|
onSelect,
|
|
defaultValues,
|
|
disabled = false,
|
|
maxHeight = 200,
|
|
multiple = false,
|
|
width,
|
|
title,
|
|
description,
|
|
margin = {},
|
|
renderSelectedItem = (item, key) => (
|
|
<Text
|
|
key={key}
|
|
style={{
|
|
...styles.selectText,
|
|
...styles.selectedText,
|
|
}}
|
|
>
|
|
{item.label}
|
|
</Text>
|
|
),
|
|
renderItem = (item) => <Text style={styles.menuItemText}>{item.label}</Text>,
|
|
id,
|
|
}: SelectProps) {
|
|
const [selectedItems, setSelectedItems] = useState<SelectProps["data"]>(
|
|
getDefaultValues(),
|
|
);
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
/*
|
|
biome-ignore lint/correctness/useExhaustiveDependencies: this hook has to update the
|
|
default values when they are changed, and those values are used in the function it uses
|
|
*/
|
|
useEffect(() => {
|
|
setSelectedItems(getDefaultValues());
|
|
}, [defaultValue, defaultValues, multiple]);
|
|
|
|
function getDefaultValues(): SelectProps["data"] {
|
|
if (multiple) {
|
|
if (defaultValues) return defaultValues;
|
|
if (defaultValue) return [defaultValue];
|
|
return [];
|
|
}
|
|
|
|
if (defaultValue) return [defaultValue];
|
|
if (defaultValues?.length) return [defaultValues[0]];
|
|
return [];
|
|
}
|
|
|
|
function handleSelect(item: SelectProps["data"][0], index: number) {
|
|
if (multiple) {
|
|
setSelectedItems((prevItems) => {
|
|
const isSelected = prevItems.some(
|
|
(selectedItem) => selectedItem.value === item.value,
|
|
);
|
|
if (isSelected) {
|
|
return prevItems.filter(
|
|
(selectedItem) => selectedItem.value !== item.value,
|
|
);
|
|
}
|
|
|
|
return [...prevItems, item];
|
|
});
|
|
} else {
|
|
setSelectedItems([item]);
|
|
setIsOpen(false);
|
|
onSelect([item], id);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<View
|
|
style={{
|
|
width: width,
|
|
marginLeft: margin.left,
|
|
marginRight: margin.right,
|
|
marginTop: margin.top,
|
|
marginBottom: margin.bottom,
|
|
}}
|
|
>
|
|
{title && (
|
|
<>
|
|
<Text
|
|
style={{
|
|
...styles.inputHeader,
|
|
...(!description && {
|
|
marginBottom: 5,
|
|
}),
|
|
...(disabled && styles.inputHeaderDisabled),
|
|
}}
|
|
>
|
|
{title}
|
|
</Text>
|
|
|
|
{description && (
|
|
<Text style={styles.inputDescription}>{description}</Text>
|
|
)}
|
|
</>
|
|
)}
|
|
<TouchableOpacity
|
|
style={styles.selectButton}
|
|
onPress={() => {
|
|
const open = !isOpen;
|
|
|
|
setIsOpen(open);
|
|
if (!open) onSelect(selectedItems, id);
|
|
}}
|
|
disabled={disabled}
|
|
>
|
|
<View style={styles.selectedTextContainer}>
|
|
{selectedItems.length ? (
|
|
selectedItems.map((item) => (
|
|
<View key={item.value} style={styles.selectedItemContainer}>
|
|
{renderSelectedItem(item, item.value)}
|
|
</View>
|
|
))
|
|
) : (
|
|
<Text style={styles.selectText}>{placeholder}</Text>
|
|
)}
|
|
</View>
|
|
<MaterialIcons
|
|
name={isOpen ? "keyboard-arrow-up" : "keyboard-arrow-down"}
|
|
size={32}
|
|
color={styles.selectText.color}
|
|
/>
|
|
</TouchableOpacity>
|
|
|
|
{isOpen && (
|
|
<Modal
|
|
transparent
|
|
animationType="fade"
|
|
visible={isOpen}
|
|
onRequestClose={() => {
|
|
setIsOpen(false);
|
|
onSelect(selectedItems, id);
|
|
}}
|
|
>
|
|
<TouchableOpacity
|
|
style={styles.selectContainer}
|
|
onPress={() => {
|
|
setIsOpen(false);
|
|
onSelect(selectedItems, id);
|
|
}}
|
|
>
|
|
<View
|
|
style={{
|
|
...styles.select,
|
|
maxHeight: maxHeight,
|
|
}}
|
|
>
|
|
<FlatList
|
|
data={data}
|
|
style={styles.openSelectContainer}
|
|
keyExtractor={(item, index) => index.toString()}
|
|
showsVerticalScrollIndicator={showScrollIndicator}
|
|
renderItem={({ item, index }) => (
|
|
<TouchableOpacity
|
|
style={{
|
|
...styles.menuItem,
|
|
...(selectedItems.some(
|
|
(selectedItem) => selectedItem.value === item.value,
|
|
) && styles.menuItemSelected),
|
|
}}
|
|
onPress={() => handleSelect(item, index)}
|
|
>
|
|
{multiple && (
|
|
<CheckBox
|
|
value={selectedItems.some(
|
|
(selectedItem) => selectedItem.value === item.value,
|
|
)}
|
|
onValueChange={() => handleSelect(item, index)}
|
|
/>
|
|
)}
|
|
{renderItem(item, () => {
|
|
setIsOpen(false);
|
|
})}
|
|
</TouchableOpacity>
|
|
)}
|
|
/>
|
|
</View>
|
|
</TouchableOpacity>
|
|
</Modal>
|
|
)}
|
|
</View>
|
|
);
|
|
}
|