import { BlurView } from "expo-blur"; import { useEffect, useMemo, useRef, useState } from "react"; import { Animated, Easing, ScrollView, StyleSheet, TVFocusGuideView, View, } from "react-native"; import { Text } from "@/components/common/Text"; import { useScaledTVTypography } from "@/constants/TVTypography"; import { TVCancelButton } from "./TVCancelButton"; import { TVOptionCard } from "./TVOptionCard"; export type TVOptionItem = { label: string; sublabel?: string; value: T; selected: boolean; }; export interface TVOptionSelectorProps { visible: boolean; title: string; options: TVOptionItem[]; onSelect: (value: T) => void; onClose: () => void; cancelLabel?: string; cardWidth?: number; cardHeight?: number; } export const TVOptionSelector = ({ visible, title, options, onSelect, onClose, cancelLabel = "Cancel", cardWidth = 160, cardHeight = 75, }: TVOptionSelectorProps) => { const typography = useScaledTVTypography(); const [isReady, setIsReady] = useState(false); const firstCardRef = useRef(null); const overlayOpacity = useRef(new Animated.Value(0)).current; const sheetTranslateY = useRef(new Animated.Value(200)).current; const initialSelectedIndex = useMemo(() => { const idx = options.findIndex((o) => o.selected); return idx >= 0 ? idx : 0; }, [options]); useEffect(() => { if (visible) { overlayOpacity.setValue(0); sheetTranslateY.setValue(200); Animated.parallel([ Animated.timing(overlayOpacity, { toValue: 1, duration: 250, easing: Easing.out(Easing.quad), useNativeDriver: true, }), Animated.timing(sheetTranslateY, { toValue: 0, duration: 300, easing: Easing.out(Easing.cubic), useNativeDriver: true, }), ]).start(); } }, [visible, overlayOpacity, sheetTranslateY]); useEffect(() => { if (visible) { const timer = setTimeout(() => setIsReady(true), 100); return () => clearTimeout(timer); } setIsReady(false); }, [visible]); useEffect(() => { if (isReady && firstCardRef.current) { const timer = setTimeout(() => { (firstCardRef.current as any)?.requestTVFocus?.(); }, 50); return () => clearTimeout(timer); } }, [isReady]); const styles = useMemo(() => createStyles(typography), [typography]); if (!visible) return null; return ( {title} {isReady && ( {options.map((option, index) => ( { onSelect(option.value); onClose(); }} width={cardWidth} height={cardHeight} /> ))} )} {isReady && ( )} ); }; const createStyles = (typography: ReturnType) => StyleSheet.create({ overlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "rgba(0, 0, 0, 0.5)", justifyContent: "flex-end", zIndex: 1000, }, sheetContainer: { width: "100%", }, blurContainer: { borderTopLeftRadius: 24, borderTopRightRadius: 24, overflow: "hidden", }, content: { paddingTop: 24, paddingBottom: 50, overflow: "visible", }, title: { fontSize: typography.callout, fontWeight: "500", color: "rgba(255,255,255,0.6)", marginBottom: 16, paddingHorizontal: 48, textTransform: "uppercase", letterSpacing: 1, }, scrollView: { overflow: "visible", }, scrollContent: { paddingHorizontal: 48, paddingVertical: 20, gap: 12, }, cancelButtonContainer: { marginTop: 16, paddingHorizontal: 48, alignItems: "flex-start", }, });