import { createContext, forwardRef, useContext, useId } from 'react'; import { Pressable, View, type GestureResponderEvent } from 'react-native'; import { useControllableState } from '../../helpers/internal/hooks'; import * as Slot from '../slot'; import type { ContentProps, ContentRef, HeaderProps, HeaderRef, IndicatorProps, IndicatorRef, ItemProps, ItemRef, RootContext, RootProps, RootRef, TriggerProps, TriggerRef, } from './accordion.types'; import { isItemSelected, toStringArray } from './accordion.utils'; const AccordionContext = createContext(null); const Root = forwardRef( ( { asChild, selectionMode = 'single', isDisabled, isCollapsible = true, value: valueProp, onValueChange: onValueChangeProps, defaultValue, ...viewProps }, ref ) => { const [ value = selectionMode === 'multiple' ? [] : undefined, onValueChange, ] = useControllableState<(string | undefined) | string[]>({ prop: valueProp, defaultProp: defaultValue, onChange: onValueChangeProps as ( state: string | string[] | undefined ) => void, }); const Component = asChild ? Slot.View : View; return ( ); } ); Root.displayName = 'HeroUINative.Primitive.Accordion.Root'; function useRootContext() { const context = useContext(AccordionContext); if (!context) { throw new Error( 'Accordion compound components cannot be rendered outside the Accordion component' ); } return context; } // -------------------------------------------------- type AccordionItemContext = ItemProps & { nativeID: string; isExpanded: boolean; }; const AccordionItemContext = createContext(null); const Item = forwardRef( ({ asChild, value, isDisabled, ...viewProps }, ref) => { const { value: rootValue } = useRootContext(); const nativeID = useId(); const Component = asChild ? Slot.View : View; return ( ); } ); Item.displayName = 'HeroUINative.Primitive.Accordion.Item'; function useItemContext() { const context = useContext(AccordionItemContext); if (!context) { throw new Error( 'AccordionItem compound components cannot be rendered outside the AccordionItem component' ); } return context; } // -------------------------------------------------- const Header = forwardRef( ({ asChild, ...props }, ref) => { const { isDisabled: rootDisabled } = useRootContext(); const { isDisabled: itemDisabled, isExpanded } = useItemContext(); const Component = asChild ? Slot.View : View; return ( ); } ); Header.displayName = 'HeroUINative.Primitive.Accordion.Header'; // -------------------------------------------------- const Trigger = forwardRef( ( { asChild, onPress: onPressProp, isDisabled: disabledProp, ...props }, ref ) => { const { isDisabled: rootDisabled, selectionMode, onValueChange, value: rootValue, isCollapsible, } = useRootContext(); const { nativeID, isDisabled: itemDisabled, value, isExpanded, } = useItemContext(); function onPress(ev: GestureResponderEvent) { if (rootDisabled || itemDisabled) return; if (selectionMode === 'single') { const newValue = isCollapsible ? value === rootValue ? undefined : value : value; onValueChange(newValue); } if (selectionMode === 'multiple') { const rootToArray = toStringArray(rootValue); const newValue = isCollapsible ? rootToArray.includes(value) ? rootToArray.filter((val) => val !== value) : rootToArray.concat(value) : [...new Set(rootToArray.concat(value))]; // @ts-ignore - `newValue` is of type `string[]` which is OK onValueChange(newValue); } onPressProp?.(ev); } const isTriggerDisabled = disabledProp || rootDisabled || itemDisabled; const Component = asChild ? Slot.Pressable : Pressable; return ( ); } ); Trigger.displayName = 'HeroUINative.Primitive.Accordion.Trigger'; // -------------------------------------------------- const Indicator = forwardRef( ({ asChild, ...props }, ref) => { const Component = asChild ? Slot.View : View; return ( ); } ); Indicator.displayName = 'HeroUINative.Primitive.Accordion.Indicator'; // -------------------------------------------------- const Content = forwardRef( ({ asChild, forceMount, ...props }, ref) => { const { selectionMode } = useRootContext(); const { nativeID, isExpanded } = useItemContext(); if (!forceMount) { if (!isExpanded) { return null; } } const Component = asChild ? Slot.View : View; return ( ); } ); Content.displayName = 'HeroUINative.Primitive.Accordion.Content'; export { Content, Header, Indicator, Item, Root, Trigger, useItemContext, useRootContext, };