import { createContext, forwardRef, useContext, useId } from 'react'; import { type GestureResponderEvent, Pressable, Text, View, } from 'react-native'; import { useControllableState } from '../../helpers/hooks'; import { Portal as PortalPrimitive } from '../portal'; import * as Slot from '../slot'; import type { CloseProps, CloseRef, ContentProps, ContentRef, DescriptionProps, DescriptionRef, OverlayProps, OverlayRef, PortalProps, RootContext, RootProps, RootRef, TitleProps, TitleRef, TriggerProps, TriggerRef, } from './bottom-sheet.types'; const BottomSheetContext = createContext< (RootContext & { nativeID: string }) | null >(null); const Root = forwardRef( ( { asChild, isOpen: isOpenProp, isDefaultOpen, onOpenChange: onOpenChangeProp, ...viewProps }, ref ) => { const [isOpen = false, onOpenChange] = useControllableState({ prop: isOpenProp, defaultProp: isDefaultOpen, onChange: onOpenChangeProp, }); const nativeID = useId(); const Component = asChild ? Slot.View : View; return ( ); } ); function useRootContext() { const context = useContext(BottomSheetContext); if (!context) { throw new Error( 'BottomSheet compound components cannot be rendered outside the BottomSheet component' ); } return context; } Root.displayName = 'HeroUINative.Primitive.BottomSheet.Root'; // -------------------------------------------------- const Trigger = forwardRef( ({ asChild, onPress: onPressProp, disabled = false, ...props }, ref) => { const { isOpen, onOpenChange } = useRootContext(); function onPress(ev: GestureResponderEvent) { if (disabled) return; const newValue = !isOpen; onOpenChange(newValue); onPressProp?.(ev); } const Component = asChild ? Slot.Pressable : Pressable; return ( ); } ); Trigger.displayName = 'HeroUINative.Primitive.BottomSheet.Trigger'; // -------------------------------------------------- /** * @warning when using a custom ``, you might have to adjust the Content's offset to account for nav elements like headers. */ function Portal({ hostName, children }: PortalProps) { const value = useRootContext(); if (!value.isOpen) { return null; } return ( {children} ); } // -------------------------------------------------- const Overlay = forwardRef( ({ asChild, isCloseOnPress = true, onPress: OnPressProp, ...props }, ref) => { const { isOpen, onOpenChange } = useRootContext(); function onPress(ev: GestureResponderEvent) { if (isCloseOnPress) { onOpenChange(!isOpen); } OnPressProp?.(ev); } if (!isOpen) { return null; } const Component = asChild ? Slot.Pressable : Pressable; return ; } ); Overlay.displayName = 'HeroUINative.Primitive.BottomSheet.Overlay'; // -------------------------------------------------- const Content = forwardRef( ({ asChild, ...props }, ref) => { const { isOpen, nativeID } = useRootContext(); if (!isOpen) { return null; } const Component = asChild ? Slot.View : View; return ( ); } ); Content.displayName = 'HeroUINative.Primitive.BottomSheet.Content'; // -------------------------------------------------- const Close = forwardRef( ({ asChild, onPress: onPressProp, disabled = false, ...props }, ref) => { const { onOpenChange } = useRootContext(); function onPress(ev: GestureResponderEvent) { if (disabled) return; onOpenChange(false); onPressProp?.(ev); } const Component = asChild ? Slot.Pressable : Pressable; return ( ); } ); Close.displayName = 'HeroUINative.Primitive.BottomSheet.Close'; // -------------------------------------------------- const Title = forwardRef((props, ref) => { const { nativeID } = useRootContext(); return ( ); }); Title.displayName = 'HeroUINative.Primitive.BottomSheet.Title'; // -------------------------------------------------- const Description = forwardRef( (props, ref) => { const { nativeID } = useRootContext(); return ; } ); Description.displayName = 'HeroUINative.Primitive.BottomSheet.Description'; // -------------------------------------------------- export { Close, Content, Description, Overlay, Portal, Root, Title, Trigger, useRootContext, };