TODO: // components/ui/accordion.tsx import * as React from 'react'; import { View, Text, Pressable, Animated, LayoutAnimation, Platform, UIManager } from 'react-native'; import { cn } from '@/lib/utils'; if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { UIManager.setLayoutAnimationEnabledExperimental(true); } type AccordionType = 'single' | 'multiple'; interface AccordionProps { type?: AccordionType; value?: string | string[]; onValueChange?: (value: string | string[]) => void; children: React.ReactNode; className?: string; collapsible?: boolean; // Allow closing open item in single mode } interface AccordionItemProps { value: string; children: React.ReactNode; className?: string; disabled?: boolean; } interface AccordionTriggerProps { children: React.ReactNode; className?: string; } interface AccordionContentProps { children: React.ReactNode; className?: string; } const AccordionContext = React.createContext<{ type: AccordionType; value?: string | string[]; onValueChange?: (value: string | string[]) => void; collapsible: boolean; } | null>(null); const AccordionItemContext = React.createContext<{ value: string; isOpen: boolean; toggle: () => void; disabled: boolean; } | null>(null); function useAccordion() { const context = React.useContext(AccordionContext); if (!context) { throw new Error('Accordion components must be used within Accordion'); } return context; } function useAccordionItem() { const context = React.useContext(AccordionItemContext); if (!context) { throw new Error('AccordionTrigger and AccordionContent must be used within AccordionItem'); } return context; } export function Accordion({ type = 'single', value, onValueChange, children, className, collapsible = false, }: AccordionProps) { return ( {children} ); } export function AccordionItem({ value, children, className, disabled = false }: AccordionItemProps) { const { type, value: accordionValue, onValueChange, collapsible } = useAccordion(); const isOpen = React.useMemo(() => { if (type === 'single') { return accordionValue === value; } else { return Array.isArray(accordionValue) && accordionValue.includes(value); } }, [type, accordionValue, value]); const toggle = () => { if (disabled) return; LayoutAnimation.configureNext( LayoutAnimation.create(250, LayoutAnimation.Types.easeInEaseOut, LayoutAnimation.Properties.opacity) ); if (type === 'single') { // Single mode: only one item can be open if (isOpen && collapsible) { onValueChange?.(''); } else { onValueChange?.(value); } } else { // Multiple mode: multiple items can be open const currentValues = (accordionValue as string[]) || []; if (isOpen) { onValueChange?.(currentValues.filter((v) => v !== value)); } else { onValueChange?.([...currentValues, value]); } } }; return ( {children} ); } export function AccordionTrigger({ children, className }: AccordionTriggerProps) { const { isOpen, toggle, disabled } = useAccordionItem(); return ( {children} {/* Chevron Icon */} ▼ ); } export function AccordionContent({ children, className }: AccordionContentProps) { const { isOpen } = useAccordionItem(); if (!isOpen) return null; return ( {children} ); } // Helper components for common patterns export function AccordionTriggerText({ children, className }: { children: React.ReactNode; className?: string; }) { return ( {children} ); } export function AccordionContentText({ children, className }: { children: React.ReactNode; className?: string; }) { return ( {children} ); }