{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "modal",
"type": "registry:ui",
"title": "Modal",
"description": "Centered/bottom modal with compound content parts.",
"registryDependencies": [
"https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/utils.json",
"https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/text.json",
"https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/theme.json",
"https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/animated-overlay.json"
],
"dependencies": [
"react-native-safe-area-context"
],
"files": [
{
"path": "registry/shared/ui/modal.tsx",
"type": "registry:ui",
"target": "components/ui/modal.tsx",
"content": "import {\n createContext,\n useContext,\n useEffect,\n useState,\n type ReactNode,\n} from 'react';\nimport {\n KeyboardAvoidingView,\n Modal as RNModal,\n Platform,\n Pressable,\n StyleSheet,\n View,\n type ViewProps,\n} from 'react-native';\nimport { useSafeAreaInsets } from 'react-native-safe-area-context';\nimport {\n AnimatedOverlay,\n type OverlayAnimationType,\n} from '@/components/ui/animated-overlay';\nimport { Text, type TextProps } from '@/components/ui/text';\nimport { cn } from '@/lib/utils';\n\nexport type ModalPosition = 'center' | 'bottom';\n\nconst ModalContext = createContext<{ position: ModalPosition }>({\n position: 'center',\n});\n\nexport interface ModalProps {\n open: boolean;\n onOpenChange?: (open: boolean) => void;\n /** `center` (default) scales in; `bottom` slides up and docks to the edge. */\n position?: ModalPosition;\n /** Render the dimmed backdrop. Tapping outside still closes when hidden. */\n showOverlay?: boolean;\n /** Keep the modal mounted after close (skips exit animation). */\n keepMounted?: boolean;\n /** Tapping the backdrop requests close. Default true. */\n dismissable?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Modal — generic overlay dialog, distinct from Sheet (bottom-anchored\n * drag surface). Backdrop press + Android back request close; exit\n * animation plays before unmount.\n */\nexport function Modal({\n open,\n onOpenChange,\n position = 'center',\n showOverlay = true,\n keepMounted = false,\n dismissable = true,\n children,\n}: ModalProps) {\n const [mounted, setMounted] = useState(open);\n\n useEffect(() => {\n if (open) setMounted(true);\n }, [open]);\n\n const animationType: OverlayAnimationType =\n position === 'bottom' ? 'slideUp' : 'scale';\n\n return (\n onOpenChange?.(false)}\n >\n \n onOpenChange?.(false) : undefined\n }\n onAnimationEnd={(entering) => {\n if (!entering && !keepMounted) setMounted(false);\n }}\n >\n \n {children}\n \n \n \n \n );\n}\n\nexport interface ModalContentProps extends ViewProps {\n className?: string;\n}\n\nexport function ModalContent({\n className,\n children,\n ...props\n}: ModalContentProps) {\n const { position } = useContext(ModalContext);\n const insets = useSafeAreaInsets();\n return (\n \n \n {position === 'bottom' && (\n \n )}\n {children}\n \n \n );\n}\n\nexport function ModalHeader({\n className,\n ...props\n}: ViewProps & { className?: string }) {\n return ;\n}\n\nexport function ModalTitle({ className, ...props }: TextProps) {\n return (\n \n );\n}\n\nexport function ModalDescription({ className, ...props }: TextProps) {\n return (\n \n );\n}\n\nexport function ModalFooter({\n className,\n ...props\n}: ViewProps & { className?: string }) {\n return (\n \n );\n}\n\nconst styles = StyleSheet.create({\n host: {\n flex: 1,\n width: '100%',\n alignItems: 'center',\n justifyContent: 'center',\n },\n hostBottom: {\n justifyContent: 'flex-end',\n },\n});\n"
}
]
}