1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-30 10:36:35 +02:00
joplin/packages/app-mobile/components/DialogManager.tsx
2024-08-02 14:51:49 +01:00

151 lines
3.7 KiB
TypeScript

import * as React from 'react';
import { createContext, useEffect, useMemo, useRef, useState } from 'react';
import { Alert, Platform, StyleSheet } from 'react-native';
import { Button, Dialog, Portal, Text } from 'react-native-paper';
import Modal from './Modal';
import { _ } from '@joplin/lib/locale';
import shim from '@joplin/lib/shim';
import makeShowMessageBox from '../utils/makeShowMessageBox';
export interface PromptButton {
text: string;
onPress?: ()=> void;
style?: 'cancel'|'default'|'destructive';
}
interface PromptOptions {
cancelable?: boolean;
}
export interface DialogControl {
prompt(title: string, message: string, buttons?: PromptButton[], options?: PromptOptions): void;
}
export const DialogContext = createContext<DialogControl>(null);
interface Props {
children: React.ReactNode;
}
interface PromptDialogData {
key: string;
title: string;
message: string;
buttons: PromptButton[];
onDismiss: (()=> void)|null;
}
const styles = StyleSheet.create({
dialogContainer: {
maxWidth: 400,
minWidth: '50%',
alignSelf: 'center',
},
modalContainer: {
marginTop: 'auto',
marginBottom: 'auto',
},
});
const DialogManager: React.FC<Props> = props => {
const [dialogModels, setPromptDialogs] = useState<PromptDialogData[]>([]);
const nextDialogIdRef = useRef(0);
const dialogControl: DialogControl = useMemo(() => {
const defaultButtons = [{ text: _('OK') }];
return {
prompt: (title: string, message: string, buttons: PromptButton[] = defaultButtons, options?: PromptOptions) => {
if (Platform.OS !== 'web') {
// Alert.alert provides a more native style on iOS.
Alert.alert(title, message, buttons, options);
// Alert.alert doesn't work on web.
} else {
const onDismiss = () => {
setPromptDialogs(dialogs => dialogs.filter(d => d !== dialog));
};
const cancelable = options?.cancelable ?? true;
const dialog: PromptDialogData = {
key: `dialog-${nextDialogIdRef.current++}`,
title,
message,
buttons: buttons.map(button => ({
...button,
onPress: () => {
onDismiss();
button.onPress?.();
},
})),
onDismiss: cancelable ? onDismiss : null,
};
setPromptDialogs(dialogs => {
return [
...dialogs,
dialog,
];
});
}
},
};
}, []);
const dialogControlRef = useRef(dialogControl);
dialogControlRef.current = dialogControl;
useEffect(() => {
shim.showMessageBox = makeShowMessageBox(dialogControlRef);
return () => {
dialogControlRef.current = null;
};
}, []);
const dialogComponents: React.ReactNode[] = [];
for (const dialog of dialogModels) {
const buttons = dialog.buttons.map((button, index) => {
return (
<Button key={`${index}-${button.text}`} onPress={button.onPress}>{button.text}</Button>
);
});
dialogComponents.push(
<Dialog
testID={'prompt-dialog'}
style={styles.dialogContainer}
key={dialog.key}
visible={true}
onDismiss={dialog.onDismiss}
>
<Dialog.Title>{dialog.title}</Dialog.Title>
<Dialog.Content>
<Text variant='bodyMedium'>{dialog.message}</Text>
</Dialog.Content>
<Dialog.Actions>
{buttons}
</Dialog.Actions>
</Dialog>,
);
}
// Web: Use a <Modal> wrapper for better keyboard focus handling.
return <>
<DialogContext.Provider value={dialogControl}>
{props.children}
</DialogContext.Provider>
<Portal>
<Modal
visible={!!dialogComponents.length}
containerStyle={styles.modalContainer}
animationType='none'
backgroundColor='rgba(0, 0, 0, 0.1)'
transparent={true}
onRequestClose={dialogModels[dialogComponents.length - 1]?.onDismiss}
>
{dialogComponents}
</Modal>
</Portal>
</>;
};
export default DialogManager;