From 6ae655abf237e1800e49b8ea192dec68c2456fcd Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Tue, 30 Sep 2025 10:20:11 +0200 Subject: [PATCH] chore --- components/PlatformOptionsMenu.tsx | 477 ----------------------------- 1 file changed, 477 deletions(-) delete mode 100644 components/PlatformOptionsMenu.tsx diff --git a/components/PlatformOptionsMenu.tsx b/components/PlatformOptionsMenu.tsx deleted file mode 100644 index eaae01a7..00000000 --- a/components/PlatformOptionsMenu.tsx +++ /dev/null @@ -1,477 +0,0 @@ -import { Ionicons } from "@expo/vector-icons"; -import { - BottomSheetBackdrop, - type BottomSheetBackdropProps, - BottomSheetModal, - BottomSheetView, -} from "@gorhom/bottom-sheet"; -import type React from "react"; -import { useCallback, useEffect, useRef } from "react"; -import { - Platform, - StyleSheet, - TouchableOpacity, - View, - type ViewProps, -} from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { Text } from "@/components/common/Text"; - -// Conditional import for Expo UI (iOS only) -let ContextMenu: any = null; -let Host: any = null; -let Button: any = null; -let Picker: any = null; - -if (!Platform.isTV && Platform.OS === "ios") { - try { - const ExpoUI = require("@expo/ui/swift-ui"); - ContextMenu = ExpoUI.ContextMenu; - Host = ExpoUI.Host; - Button = ExpoUI.Button; - Picker = ExpoUI.Picker; - } catch { - console.warn( - "Expo UI not available, falling back to Android implementation", - ); - } -} - -// Core option types -export type OptionType = - | "radio" - | "checkbox" - | "toggle" - | "action" - | "separator"; - -// Base option interface -export interface BaseOption { - id: string; - type: OptionType; - label: string; - disabled?: boolean; - hidden?: boolean; -} - -// Specific option types -export interface RadioOption extends BaseOption { - type: "radio"; - groupId: string; // For grouping radio buttons - selected?: boolean; -} - -export interface CheckboxOption extends BaseOption { - type: "checkbox"; - checked: boolean; -} - -export interface ToggleOption extends BaseOption { - type: "toggle"; - value: boolean; -} - -export interface ActionOption extends BaseOption { - type: "action"; - icon?: string; // Ionicons name -} - -export interface SeparatorOption extends BaseOption { - type: "separator"; - label: ""; // Separator doesn't need a label -} - -// Union type for all options -export type Option = - | RadioOption - | CheckboxOption - | ToggleOption - | ActionOption - | SeparatorOption; - -// Option groups -export interface OptionGroup { - id: string; - title: string; - options: Option[]; -} - -// Component props interface -export interface PlatformOptionsMenuProps extends ViewProps { - // Data - groups: OptionGroup[]; - - // Presentation - trigger: React.ReactNode; // Custom trigger button - title: string; - - // Behavior - open: boolean; - onOpenChange: (open: boolean) => void; - onOptionSelect: (optionId: string, value?: any) => void; - - // Platform specific configurations - expoUIConfig?: { - hostStyle?: ViewProps["style"]; - }; - - bottomSheetConfig?: { - snapPoints?: string[]; - enableDynamicSizing?: boolean; - enablePanDownToClose?: boolean; - }; -} - -// Helper component for Android bottom sheet option rendering -const AndroidOptionItem: React.FC<{ - option: Option; - onSelect: (optionId: string, value?: any) => void; - isLast?: boolean; -}> = ({ option, onSelect, isLast }) => { - if (option.hidden) return null; - - if (option.type === "separator") { - return ( - - ); - } - - const handlePress = () => { - if (option.disabled) return; - - switch (option.type) { - case "radio": - onSelect(option.id, !option.selected); - break; - case "checkbox": - onSelect(option.id, !option.checked); - break; - case "toggle": - onSelect(option.id, !option.value); - break; - case "action": - onSelect(option.id); - break; - } - }; - - const renderIcon = () => { - switch (option.type) { - case "radio": - return ( - - ); - case "checkbox": - return ( - - ); - case "toggle": - return ( - - - - ); - case "action": - return option.icon ? ( - - ) : null; - default: - return null; - } - }; - - return ( - <> - - {option.label} - {renderIcon()} - - {!isLast && ( - - )} - - ); -}; - -// Helper component for Android bottom sheet group rendering -const AndroidOptionGroup: React.FC<{ - group: OptionGroup; - onSelect: (optionId: string, value?: any) => void; - isLast?: boolean; -}> = ({ group, onSelect, isLast }) => { - const visibleOptions = group.options.filter((option) => !option.hidden); - - if (visibleOptions.length === 0) return null; - - return ( - - - {group.title} - - - {visibleOptions.map((option, index) => ( - - ))} - - - ); -}; - -/** - * PlatformOptionsMenu Component - * - * A unified component that renders platform-appropriate option menus: - * - iOS: Expo UI ContextMenu with native SwiftUI integration - * - Android: Bottom sheet modal - * - * Supports radio buttons, checkboxes, toggles, actions, and separators. - */ -export const PlatformOptionsMenu: React.FC = ({ - groups, - trigger, - title, - open, - onOpenChange, - onOptionSelect, - expoUIConfig, - bottomSheetConfig, - ...viewProps -}) => { - const isIOS = Platform.OS === "ios"; - const isTv = Platform.isTV; - const bottomSheetModalRef = useRef(null); - const insets = useSafeAreaInsets(); - - // Bottom sheet effects - useEffect(() => { - if (!isIOS) { - if (open) bottomSheetModalRef.current?.present(); - else bottomSheetModalRef.current?.dismiss(); - } - }, [open, isIOS]); - - const handleSheetChanges = useCallback( - (index: number) => { - if (index === -1) { - onOpenChange(false); - } - }, - [onOpenChange], - ); - - const renderBackdrop = useCallback( - (props: BottomSheetBackdropProps) => ( - - ), - [], - ); - - if (isTv) return null; - - // iOS Implementation with Expo UI ContextMenu - if (isIOS && ContextMenu && Host && Button) { - const renderContextMenuItems = () => { - const items: React.ReactNode[] = []; - - groups.forEach((group) => { - // Add group items - group.options.forEach((option) => { - if (option.hidden) return; - - if (option.type === "separator") { - return; - } - - if (option.type === "radio") { - // For radio options, create a picker if multiple options in the same group - const groupOptions = groups - .flatMap((g) => g.options) - .filter( - (opt) => - opt.type === "radio" && - (opt as RadioOption).groupId === - (option as RadioOption).groupId, - ); - - if (groupOptions.length > 1) { - // Create a picker for radio group - const selectedIndex = groupOptions.findIndex( - (opt) => (opt as RadioOption).selected, - ); - const pickerOptions = groupOptions.map((opt) => opt.label); - - items.push( - { - const selectedOption = groupOptions[index]; - if (selectedOption) { - onOptionSelect(selectedOption.id, true); - } - }} - />, - ); - return; // Skip individual radio buttons when we have a picker - } - } - - // For other option types, create buttons - const systemImage = - option.type === "action" && (option as ActionOption).icon - ? (option as ActionOption).icon - : undefined; - - const variant = (() => { - if (option.type === "checkbox") { - return (option as CheckboxOption).checked ? "filled" : "bordered"; - } - if (option.type === "toggle") { - return (option as ToggleOption).value ? "filled" : "bordered"; - } - return "bordered"; - })(); - - items.push( - , - ); - }); - }); - - return items; - }; - - return ( - - - - {renderContextMenuItems()} - {trigger} - - - - ); - } - - // Android Implementation with Bottom Sheet - return ( - - - - - {title} - - {groups.map((group, index) => ( - - ))} - - - - - ); -};