271 lines
7 KiB
TypeScript
Executable file
271 lines
7 KiB
TypeScript
Executable file
import React, { ReactNode, useState } from 'react';
|
|
import { Modal, View, TouchableOpacity, StyleSheet, Animated, Easing, Dimensions, TextInput, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
|
import MyText from './text';
|
|
import MyButton from './button';
|
|
import tw from "../lib/tailwind";
|
|
|
|
interface DialogProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
children: ReactNode;
|
|
enableDismiss?: boolean;
|
|
}
|
|
|
|
const SCREEN_HEIGHT = Dimensions.get('window').height;
|
|
|
|
export const BottomDialog: React.FC<DialogProps> = ({ open, onClose, children, enableDismiss = true }) => {
|
|
const [slideAnim] = useState(new Animated.Value(SCREEN_HEIGHT));
|
|
|
|
React.useEffect(() => {
|
|
if (open) {
|
|
Animated.timing(slideAnim, {
|
|
toValue: 0,
|
|
duration: 300,
|
|
easing: Easing.out(Easing.cubic),
|
|
useNativeDriver: true,
|
|
}).start();
|
|
} else {
|
|
Animated.timing(slideAnim, {
|
|
toValue: SCREEN_HEIGHT,
|
|
duration: 200,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
}
|
|
}, [open]);
|
|
|
|
return (
|
|
<Modal
|
|
visible={open}
|
|
transparent
|
|
animationType="none"
|
|
onRequestClose={enableDismiss ? onClose : undefined}
|
|
>
|
|
{enableDismiss ? (
|
|
<TouchableOpacity style={styles.backdrop} activeOpacity={1} onPress={onClose} />
|
|
) : (
|
|
<View style={[styles.backdrop, styles.disabledBackdrop]} />
|
|
)}
|
|
<Animated.View
|
|
style={[
|
|
styles.dialog,
|
|
{
|
|
transform: [{ translateY: slideAnim }],
|
|
},
|
|
]}
|
|
>
|
|
<View style={styles.handle} />
|
|
<KeyboardAvoidingView
|
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
style={styles.keyboardAvoidingContainer}
|
|
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
|
|
>
|
|
<ScrollView
|
|
style={styles.scrollContainer}
|
|
contentContainerStyle={styles.scrollContent}
|
|
keyboardShouldPersistTaps="handled"
|
|
showsVerticalScrollIndicator={true}
|
|
>
|
|
{children}
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
</Animated.View>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export const RawBottomDialog: React.FC<DialogProps> = ({ open, onClose, children, enableDismiss = true }) => {
|
|
const [slideAnim] = useState(new Animated.Value(SCREEN_HEIGHT));
|
|
|
|
React.useEffect(() => {
|
|
if (open) {
|
|
Animated.timing(slideAnim, {
|
|
toValue: 0,
|
|
duration: 300,
|
|
easing: Easing.out(Easing.cubic),
|
|
useNativeDriver: true,
|
|
}).start();
|
|
} else {
|
|
Animated.timing(slideAnim, {
|
|
toValue: SCREEN_HEIGHT,
|
|
duration: 200,
|
|
useNativeDriver: true,
|
|
}).start();
|
|
}
|
|
}, [open]);
|
|
|
|
return (
|
|
<Modal
|
|
visible={open}
|
|
transparent
|
|
animationType="none"
|
|
onRequestClose={enableDismiss ? onClose : undefined}
|
|
>
|
|
{enableDismiss ? (
|
|
<TouchableOpacity style={tw`flex-1 bg-black/30`} activeOpacity={1} onPress={onClose} />
|
|
) : (
|
|
<View style={[tw`flex-1 bg-black/30`, { pointerEvents: 'none' }]} />
|
|
)}
|
|
|
|
{children}
|
|
{/* <Animated.View
|
|
style={[
|
|
tw`absolute left-0 right-0 bottom-0 bg-white rounded-t-[18px] px-5 pb-0 shadow-lg`,
|
|
{
|
|
maxHeight: SCREEN_HEIGHT * 0.85,
|
|
transform: [{ translateY: slideAnim }],
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: -2 },
|
|
shadowOpacity: 0.2,
|
|
shadowRadius: 8,
|
|
elevation: 8,
|
|
},
|
|
]}
|
|
>
|
|
{children}
|
|
</Animated.View> */}
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
backdrop: {
|
|
flex: 1,
|
|
backgroundColor: 'rgba(0,0,0,0.3)',
|
|
},
|
|
disabledBackdrop: {
|
|
pointerEvents: 'none',
|
|
},
|
|
dialog: {
|
|
position: 'absolute',
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
backgroundColor: '#fff',
|
|
borderTopLeftRadius: 18,
|
|
borderTopRightRadius: 18,
|
|
paddingHorizontal: 20,
|
|
paddingBottom: 0,
|
|
elevation: 8,
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: -2 },
|
|
shadowOpacity: 0.2,
|
|
shadowRadius: 8,
|
|
maxHeight: SCREEN_HEIGHT * 0.85, // Limit max height so it doesn't cover entire screen
|
|
},
|
|
handle: {
|
|
width: 40,
|
|
height: 5,
|
|
borderRadius: 3,
|
|
backgroundColor: '#ccc',
|
|
alignSelf: 'center',
|
|
marginTop: 12,
|
|
marginBottom: 8,
|
|
},
|
|
keyboardAvoidingContainer: {
|
|
flex: 1,
|
|
width: '100%',
|
|
},
|
|
scrollContainer: {
|
|
flex: 1,
|
|
width: '100%',
|
|
},
|
|
scrollContent: {
|
|
flexGrow: 1,
|
|
paddingBottom: Platform.OS === 'ios' ? 40 : 20, // Extra padding for iOS keyboard
|
|
},
|
|
});
|
|
|
|
const commentStyles = StyleSheet.create({
|
|
container: {
|
|
marginBottom: 16,
|
|
},
|
|
input: {
|
|
borderWidth: 1,
|
|
borderColor: '#ccc',
|
|
borderRadius: 8,
|
|
padding: 10,
|
|
minHeight: 80,
|
|
fontSize: 16,
|
|
},
|
|
});
|
|
|
|
export default BottomDialog;
|
|
|
|
interface ConfirmationDialogProps {
|
|
open: boolean;
|
|
positiveAction: (comment?: string) => void;
|
|
commentNeeded?: boolean;
|
|
negativeAction?: () => void;
|
|
title?: string;
|
|
message?: string;
|
|
confirmText?: string;
|
|
cancelText?: string;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = (props) => {
|
|
const {
|
|
open,
|
|
positiveAction,
|
|
commentNeeded = false,
|
|
negativeAction,
|
|
title = "Are you sure?",
|
|
message = "Do you really want to proceed with this action?",
|
|
confirmText = "Confirm",
|
|
cancelText = "Cancel",
|
|
isLoading = false,
|
|
} = props;
|
|
|
|
const [comment, setComment] = useState('');
|
|
|
|
const handleConfirm = () => {
|
|
positiveAction(commentNeeded ? comment : undefined);
|
|
setComment(''); // Reset comment after action
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
if (negativeAction) {
|
|
negativeAction();
|
|
}
|
|
setComment(''); // Reset comment on cancel
|
|
};
|
|
|
|
return (
|
|
<BottomDialog open={open} onClose={handleCancel}>
|
|
<View style={{ padding: 20 }}>
|
|
<MyText style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 16 }}>
|
|
{title}
|
|
</MyText>
|
|
<MyText style={{ marginBottom: commentNeeded ? 16 : 24 }}>
|
|
{message}
|
|
</MyText>
|
|
|
|
{commentNeeded && (
|
|
<View style={commentStyles.container}>
|
|
<TextInput
|
|
style={commentStyles.input}
|
|
value={comment}
|
|
onChangeText={setComment}
|
|
placeholder="Add a comment..."
|
|
multiline={true}
|
|
numberOfLines={3}
|
|
textAlignVertical="top"
|
|
/>
|
|
</View>
|
|
)}
|
|
|
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: commentNeeded ? 16 : 0 }}>
|
|
<MyButton textContent={cancelText} onPress={handleCancel} fillColor="gray1" textColor="white1" disabled={isLoading} />
|
|
<MyButton
|
|
textContent={isLoading ? "Processing..." : confirmText}
|
|
style={{ flexShrink: 0 }}
|
|
onPress={handleConfirm}
|
|
fillColor="red1"
|
|
textColor="white1"
|
|
disabled={isLoading}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</BottomDialog>
|
|
);
|
|
}
|