import * as React from 'react';
import { Modal, View, Text, Pressable, Animated } from 'react-native';
import { cn } from '@/lib/utils';
interface DialogProps {
open?: boolean;
onOpenChange?: (open: boolean) => void;
children: React.ReactNode;
}
interface DialogContentProps {
children: React.ReactNode;
className?: string;
}
interface DialogHeaderProps {
children: React.ReactNode;
className?: string;
}
interface DialogTitleProps {
children: React.ReactNode;
className?: string;
}
interface DialogDescriptionProps {
children: React.ReactNode;
className?: string;
}
interface DialogFooterProps {
children: React.ReactNode;
className?: string;
}
const DialogContext = React.createContext<{
open: boolean;
onOpenChange: (open: boolean) => void;
} | null>(null);
function useDialog() {
const context = React.useContext(DialogContext);
if (!context) {
throw new Error('Dialog components must be used within Dialog');
}
return context;
}
export function Dialog({ open: controlledOpen, onOpenChange: controlledOnOpenChange, children }: DialogProps) {
const [internalOpen, setInternalOpen] = React.useState(false);
const hasDialogTrigger = React.Children.toArray(children).some(
(child) => React.isValidElement(child) && child.type === DialogTrigger
);
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
const onOpenChange = controlledOnOpenChange || setInternalOpen;
return (
{children}
);
}
export function DialogTrigger({ children }: { children: React.ReactNode }) {
const { onOpenChange } = useDialog();
if (React.isValidElement(children)) {
return React.cloneElement(children as React.ReactElement, {
onPress: () => onOpenChange(true),
});
}
return (
onOpenChange(true)}>
{children}
);
}
export function DialogContent({ children, className }: DialogContentProps) {
const { open, onOpenChange } = useDialog();
// DONE: Track visible state separately for animation
const [visible, setVisible] = React.useState(false);
const scaleAnim = React.useRef(new Animated.Value(0.9)).current;
const opacityAnim = React.useRef(new Animated.Value(0)).current;
React.useEffect(() => {
if (open) {
// Show modal first
setVisible(true);
// Then animate in
Animated.parallel([
Animated.spring(scaleAnim, {
toValue: 1,
useNativeDriver: true,
tension: 80,
friction: 10,
}),
Animated.timing(opacityAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
]).start();
} else {
// Animate out first
Animated.parallel([
Animated.timing(scaleAnim, {
toValue: 0.9,
duration: 150,
useNativeDriver: true,
}),
Animated.timing(opacityAnim, {
toValue: 0,
duration: 150,
useNativeDriver: true,
}),
]).start(() => {
// Then hide modal after animation completes
setVisible(false);
});
}
}, [open]);
if (!visible) return null;
return (
onOpenChange(false)}
>
onOpenChange(false)}
className="flex-1 bg-black/50 items-center justify-center p-4"
>
e.stopPropagation()}
className={cn(
'bg-white rounded-lg p-6 shadow-lg',
className
)}
>
{children}
);
}
export function DialogHeader({ children, className }: DialogHeaderProps) {
return (
{children}
);
}
export function DialogTitle({ children, className }: DialogTitleProps) {
return (
{children}
);
}
export function DialogDescription({ children, className }: DialogDescriptionProps) {
return (
{children}
);
}
export function DialogFooter({ children, className }: DialogFooterProps) {
return (
{children}
);
}
export function DialogClose({ children }: { children: React.ReactNode }) {
const { onOpenChange } = useDialog();
if (React.isValidElement(children)) {
return React.cloneElement(children as React.ReactElement, {
onPress: () => onOpenChange(false),
});
}
return (
onOpenChange(false)}>
{children}
);
}