expo-popcore-app/components/ui/alert-dialog.tsx

429 lines
8.1 KiB
TypeScript

import * as React from "react";
import {
Modal,
View,
Text,
Pressable,
type ViewStyle,
type TextStyle,
StyleSheet,
StatusBar,
Platform,
} from "react-native";
import { BlurView } from "expo-blur";
import { cn } from "../../lib/utils";
import { Button, buttonVariants } from "./button";
interface AlertDialogContextValue {
open: boolean;
setOpen: (open: boolean) => void;
}
const AlertDialogContext = React.createContext<AlertDialogContextValue | null>(
null
);
const useAlertDialogContext = () => {
const context = React.useContext(AlertDialogContext);
if (!context) {
throw new Error(
"AlertDialog components must be used within AlertDialog provider"
);
}
return context;
};
interface AlertDialogProps {
children: React.ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}
function AlertDialog({ children, open, onOpenChange }: AlertDialogProps) {
const [internalOpen, setInternalOpen] = React.useState(false);
const isControlled = open !== undefined;
const isOpen = isControlled ? open : internalOpen;
const handleSetOpen = React.useCallback(
(value: boolean) => {
if (!isControlled) {
setInternalOpen(value);
}
onOpenChange?.(value);
},
[isControlled, onOpenChange]
);
const value: AlertDialogContextValue = {
open: isOpen,
setOpen: handleSetOpen,
};
return (
<AlertDialogContext.Provider value={value}>
{children}
</AlertDialogContext.Provider>
);
}
interface AlertDialogTriggerProps {
children: React.ReactNode;
asChild?: boolean;
style?: ViewStyle;
}
function AlertDialogTrigger({
children,
asChild,
style,
}: AlertDialogTriggerProps) {
const { setOpen } = useAlertDialogContext();
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children as React.ReactElement<any>, {
onPress: () => setOpen(true),
});
}
return (
<Pressable onPress={() => setOpen(true)} style={style}>
{children}
</Pressable>
);
}
interface AlertDialogPortalProps {
children: React.ReactNode;
}
function AlertDialogPortal({ children }: AlertDialogPortalProps) {
return <>{children}</>;
}
interface AlertDialogOverlayProps {
className?: string;
style?: ViewStyle;
}
function AlertDialogOverlay({ className, style }: AlertDialogOverlayProps) {
return (
<View
className={cn("absolute inset-0", className)}
style={[styles.overlay, style]}
>
{Platform.OS === 'ios' ? (
<BlurView
intensity={40}
tint="dark"
style={StyleSheet.absoluteFill}
/>
) : null}
<View
style={[
StyleSheet.absoluteFill,
{ backgroundColor: "#00000080" },
]}
/>
</View>
);
}
interface AlertDialogContentProps {
children: React.ReactNode;
className?: string;
style?: ViewStyle;
}
function AlertDialogContent({
children,
className,
style,
}: AlertDialogContentProps) {
const { open, setOpen } = useAlertDialogContext();
React.useEffect(() => {
if (Platform.OS === "android") {
if (open) {
StatusBar.setBackgroundColor("rgba(0,0,0,0.5)", true);
} else {
StatusBar.setBackgroundColor("transparent", true);
}
}
}, [open]);
if (!open) return null;
return (
<Modal
transparent
visible={open}
animationType="none"
statusBarTranslucent={Platform.OS === 'android'}
onRequestClose={() => setOpen(false)}
hardwareAccelerated
>
<View style={styles.modalContainer}>
<Pressable
style={styles.backdrop}
onPress={() => setOpen(false)}
android_ripple={null}
>
<AlertDialogOverlay />
</Pressable>
<View
className={cn(
"bg-background w-[90%] max-w-[500px] rounded-lg border border-border p-6 shadow-2xl gap-4",
className
)}
style={[styles.content, style]}
>
{children}
</View>
</View>
</Modal>
);
}
interface AlertDialogHeaderProps {
children: React.ReactNode;
className?: string;
style?: ViewStyle;
}
function AlertDialogHeader({
children,
className,
style,
}: AlertDialogHeaderProps) {
return (
<View
className={cn("flex-col gap-2", className)}
style={style}
>
{children}
</View>
);
}
interface AlertDialogFooterProps {
children: React.ReactNode;
className?: string;
style?: ViewStyle;
}
function AlertDialogFooter({
children,
className,
style,
}: AlertDialogFooterProps) {
return (
<View
className={cn("flex flex-row gap-2 justify-end", className)}
style={style}
>
{children}
</View>
);
}
interface AlertDialogTitleProps {
children: React.ReactNode;
className?: string;
style?: TextStyle;
}
function AlertDialogTitle({
children,
className,
style,
}: AlertDialogTitleProps) {
return (
<Text
className={cn("text-lg font-semibold text-foreground", className)}
style={style}
>
{children}
</Text>
);
}
interface AlertDialogDescriptionProps {
children: React.ReactNode;
className?: string;
style?: TextStyle;
}
function AlertDialogDescription({
children,
className,
style,
}: AlertDialogDescriptionProps) {
return (
<Text
className={cn("text-sm text-muted-foreground", className)}
style={style}
>
{children}
</Text>
);
}
interface AlertDialogActionProps {
children: React.ReactNode;
className?: string;
onPress?: () => void;
style?: ViewStyle;
}
function AlertDialogAction({
children,
className,
onPress,
style,
}: AlertDialogActionProps) {
const { setOpen } = useAlertDialogContext();
const handlePress = () => {
onPress?.();
setOpen(false);
};
return (
<Button
onPress={handlePress}
className={cn("flex-1", className)}
style={style}
>
{children}
</Button>
);
}
interface AlertDialogCancelProps {
children: React.ReactNode;
className?: string;
onPress?: () => void;
style?: ViewStyle;
}
function AlertDialogCancel({
children,
className,
onPress,
style,
}: AlertDialogCancelProps) {
const { setOpen } = useAlertDialogContext();
const handlePress = () => {
onPress?.();
setOpen(false);
};
return (
<Button
variant="outline"
onPress={handlePress}
className={cn("flex-1", className)}
style={style}
>
{children}
</Button>
);
}
interface DeleteAlertDialogActionProps {
onPress?: () => void;
style?: ViewStyle;
className?: string;
}
function DeleteAlertDialogAction({
onPress,
style,
className,
}: DeleteAlertDialogActionProps) {
return (
<AlertDialogAction
onPress={onPress}
className={className}
style={{ ...styles.deleteActionButton, ...style }}
>
<Text style={styles.deleteActionText}></Text>
</AlertDialogAction>
);
}
function CancelAlertDialogAction({
onPress,
style,
className,
}: DeleteAlertDialogActionProps) {
return (
<AlertDialogAction
onPress={onPress}
className={className}
style={{ ...styles.cancelActionButton, ...style }}
>
<Text style={styles.cancelActionText}></Text>
</AlertDialogAction>
);
}
const styles = StyleSheet.create({
modalContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
...Platform.select({
android: {
backgroundColor: 'transparent',
},
}),
},
backdrop: {
...StyleSheet.absoluteFillObject,
},
overlay: {
zIndex: 1,
},
content: {
zIndex: 2,
},
deleteActionButton: {
backgroundColor: "#FA3F2C",
alignItems: 'center'
},
deleteActionText: {
color: "#FFFFFF",
fontSize: 14,
fontWeight: "500",
},
cancelActionButton: {
backgroundColor: '#262A31',
alignItems: 'center'
},
cancelActionText: {
color: "#CCCCCC",
fontSize: 14,
}
});
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
DeleteAlertDialogAction,
CancelAlertDialogAction
};