mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-19 18:54:16 +01:00
FilterButton previously rendered its own per-button gorhom BottomSheetModal (FilterSheet), which silently failed to present — tapping the library filter badges did nothing. It now pushes a new FilterSheetContent into the app-level GlobalModal instead, the same sheet infrastructure PlatformDropdown already uses. FilterSheetContent renders a virtualized BottomSheetFlatList (filter lists can hold thousands of genres/tags/years), auto-shows search for lists over 15 items matching against the visible label, and owns its selection state locally since GlobalModal content is a static snapshot. The searchFilter/disableSearch props are gone (search is derived from renderItemLabel, which is now typed as returning a string), so all call sites are updated accordingly. Claude-Session: https://claude.ai/code/session_01GVcoCm9p5pLBXS9v8jDkHp
97 lines
2.4 KiB
TypeScript
97 lines
2.4 KiB
TypeScript
import { FontAwesome, Ionicons } from "@expo/vector-icons";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { TouchableOpacity, View, type ViewProps } from "react-native";
|
|
import { Text } from "@/components/common/Text";
|
|
import { useGlobalModal } from "@/providers/GlobalModalProvider";
|
|
import { FilterSheetContent } from "./FilterSheetContent";
|
|
|
|
interface FilterButtonProps<T> extends ViewProps {
|
|
id: string;
|
|
queryKey: string;
|
|
values: T[];
|
|
title: string;
|
|
set: (value: T[]) => void;
|
|
queryFn: (params: any) => Promise<any>;
|
|
renderItemLabel: (item: T) => string;
|
|
multiple?: boolean;
|
|
icon?: "filter" | "sort";
|
|
}
|
|
|
|
export const FilterButton = <T,>({
|
|
id,
|
|
queryFn,
|
|
queryKey,
|
|
set,
|
|
values, // selected values
|
|
title,
|
|
renderItemLabel,
|
|
multiple = false,
|
|
icon = "filter",
|
|
...props
|
|
}: FilterButtonProps<T>) => {
|
|
const { showModal, hideModal } = useGlobalModal();
|
|
|
|
const { data: filters } = useQuery<T[]>({
|
|
queryKey: ["filters", title, queryKey, id],
|
|
queryFn,
|
|
staleTime: 0,
|
|
enabled: !!id && !!queryFn && !!queryKey,
|
|
});
|
|
|
|
const openSheet = () => {
|
|
if (!filters?.length) return;
|
|
showModal(
|
|
<FilterSheetContent<T>
|
|
title={title}
|
|
data={filters}
|
|
initialValues={values}
|
|
set={set}
|
|
renderItemLabel={renderItemLabel}
|
|
multiple={multiple}
|
|
onClose={hideModal}
|
|
/>,
|
|
{ snapPoints: ["85%"] },
|
|
);
|
|
};
|
|
|
|
return (
|
|
<TouchableOpacity onPress={openSheet}>
|
|
<View
|
|
className={`
|
|
px-3 py-1.5 rounded-full flex flex-row items-center space-x-1
|
|
${
|
|
values.length > 0
|
|
? "bg-purple-600 border border-purple-700"
|
|
: "bg-neutral-900 border border-neutral-900"
|
|
}
|
|
${filters?.length === 0 ? "opacity-50" : ""}
|
|
`}
|
|
{...props}
|
|
>
|
|
<Text
|
|
className={`
|
|
${values.length > 0 ? "text-purple-100" : "text-neutral-100"}
|
|
text-xs font-semibold`}
|
|
>
|
|
{title}
|
|
</Text>
|
|
{icon === "filter" ? (
|
|
<Ionicons
|
|
name='filter'
|
|
size={14}
|
|
color='white'
|
|
style={{ opacity: 0.5 }}
|
|
/>
|
|
) : (
|
|
<FontAwesome
|
|
name='sort'
|
|
size={14}
|
|
color='white'
|
|
style={{ opacity: 0.5 }}
|
|
/>
|
|
)}
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|