import React, { useCallback, useEffect, useRef, useState } from "react"; import { Animated, Pressable, StyleSheet, Text, View, } from "react-native"; import { Check, ChevronUp } from "../icons"; import { gameUIColors } from "../ui/gameUI/constants/gameUIColors"; import type { Environment } from "../types/types"; // ============================================================================ // Constants // ============================================================================ const OPTION_HEIGHT = 36; // ============================================================================ // Types // ============================================================================ export interface EnvironmentSelectorInlineProps { /** The currently active environment */ currentEnvironment: Environment; /** List of environments available for selection */ availableEnvironments: Environment[]; /** Called when an environment is selected */ onSelect: (env: Environment) => void; } // ============================================================================ // Environment Config Helper // ============================================================================ interface EnvironmentConfig { label: string; backgroundColor: string; } function getEnvironmentConfig(environment: Environment): EnvironmentConfig { switch (environment) { case "local": return { label: "LOCAL", backgroundColor: gameUIColors.info, }; case "dev": return { label: "DEV", backgroundColor: gameUIColors.warning, }; case "qa": return { label: "QA", backgroundColor: gameUIColors.optional, }; case "staging": return { label: "STAGING", backgroundColor: gameUIColors.success, }; case "prod": return { label: "PROD", backgroundColor: gameUIColors.error, }; default: return { label: "LOCAL", backgroundColor: gameUIColors.info, }; } } // ============================================================================ // Environment Option Component // ============================================================================ interface EnvironmentOptionProps { environment: Environment; isSelected: boolean; onPress: () => void; } function EnvironmentOption({ environment, isSelected, onPress, }: EnvironmentOptionProps) { const config = getEnvironmentConfig(environment); return ( [ styles.optionContainer, isSelected && styles.optionContainerSelected, pressed && styles.optionContainerPressed, ]} > {config.label} {isSelected && ( )} ); } // ============================================================================ // Main Component // ============================================================================ export function EnvironmentSelectorInline({ currentEnvironment, availableEnvironments, onSelect, }: EnvironmentSelectorInlineProps) { const [isExpanded, setIsExpanded] = useState(false); const heightAnim = useRef(new Animated.Value(0)).current; const rotateAnim = useRef(new Animated.Value(0)).current; const config = getEnvironmentConfig(currentEnvironment); // Calculate expanded height based on number of environments const expandedHeight = availableEnvironments.length * OPTION_HEIGHT; // Animate height and chevron rotation useEffect(() => { Animated.parallel([ Animated.timing(heightAnim, { toValue: isExpanded ? expandedHeight : 0, duration: 200, useNativeDriver: false, }), Animated.timing(rotateAnim, { toValue: isExpanded ? 1 : 0, duration: 200, useNativeDriver: true, }), ]).start(); }, [isExpanded, expandedHeight, heightAnim, rotateAnim]); const handleToggle = useCallback(() => { setIsExpanded((prev) => !prev); }, []); const handleSelect = useCallback( (env: Environment) => { setIsExpanded(false); onSelect(env); }, [onSelect] ); const chevronRotation = rotateAnim.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "180deg"], }); return ( {/* Expanded options - absolutely positioned above the badge */} {availableEnvironments.map((env) => ( handleSelect(env)} /> ))} {/* Badge / trigger - this is the only thing that affects layout */} [ styles.badge, isExpanded && styles.badgeExpanded, pressed && styles.badgePressed, ]} accessibilityRole="button" accessibilityLabel={`Current environment: ${config.label}. Tap to switch.`} > {config.label} ); } // ============================================================================ // Styles // ============================================================================ const styles = StyleSheet.create({ wrapper: { position: "relative", zIndex: 1000, }, badge: { flexDirection: "row", alignItems: "center", paddingVertical: 6, paddingLeft: 8, paddingRight: 6, gap: 6, backgroundColor: "transparent", borderRadius: 6, }, badgeExpanded: { backgroundColor: gameUIColors.blackTint1, borderTopLeftRadius: 0, borderTopRightRadius: 0, }, badgePressed: { opacity: 0.7, }, dot: { width: 8, height: 8, borderRadius: 4, }, label: { fontSize: 11, fontWeight: "600", color: gameUIColors.primaryLight, letterSpacing: 0.5, }, optionsDropup: { position: "absolute", bottom: "100%", left: 0, right: 0, backgroundColor: gameUIColors.blackTint1, borderTopLeftRadius: 6, borderTopRightRadius: 6, borderBottomLeftRadius: 0, borderBottomRightRadius: 0, borderTopWidth: 1, borderLeftWidth: 1, borderRightWidth: 1, borderBottomWidth: 0, borderColor: gameUIColors.border, overflow: "hidden", shadowColor: "#000", shadowOffset: { width: 0, height: -4 }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 10, }, optionContainer: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingHorizontal: 10, height: OPTION_HEIGHT, }, optionContainerSelected: { backgroundColor: "rgba(255, 255, 255, 0.05)", }, optionContainerPressed: { backgroundColor: "rgba(255, 255, 255, 0.1)", }, optionLeft: { flexDirection: "row", alignItems: "center", flex: 1, }, optionDot: { width: 6, height: 6, borderRadius: 3, marginRight: 8, }, optionLabel: { fontSize: 10, fontWeight: "600", color: gameUIColors.primaryLight, letterSpacing: 0.5, }, });