164 lines
4.0 KiB
TypeScript
164 lines
4.0 KiB
TypeScript
import {useState, useEffect, useRef} from 'react';
|
|
import $ from 'jquery';
|
|
import 'bootstrap';
|
|
import {t} from '../scripts/i18n';
|
|
import ModalHeader, {type Props as HeaderProperties} from './ModalHeader';
|
|
import ModalBody, {type Props as BodyProperties} from './ModalBody';
|
|
import ModalFooter, {type Props as FooterProperties} from './ModalFooter';
|
|
|
|
type BasicOptions = {
|
|
readonly mode?: 'alert' | 'confirm' | 'prompt';
|
|
readonly show?: boolean;
|
|
readonly input?: string;
|
|
validator?(value: any): string | boolean | undefined;
|
|
readonly type?: string;
|
|
readonly showHeader?: boolean;
|
|
readonly center?: boolean;
|
|
children?: React.ReactNode;
|
|
};
|
|
|
|
export type ModalOptions = BasicOptions & HeaderProperties & BodyProperties & FooterProperties;
|
|
|
|
type Properties = {
|
|
readonly id?: string;
|
|
readonly children?: React.ReactNode;
|
|
readonly footer?: React.ReactNode;
|
|
onConfirm?(payload: {value: string}): void;
|
|
onDismiss?(): void;
|
|
onClose?(): void;
|
|
};
|
|
|
|
export type ModalResult = {
|
|
value: string;
|
|
};
|
|
|
|
const Modal: React.FC<ModalOptions & Properties> = properties => {
|
|
const {
|
|
mode = 'confirm',
|
|
title = t('general.tip'),
|
|
text = '',
|
|
input = '',
|
|
placeholder = '',
|
|
inputType = 'text',
|
|
inputMode,
|
|
type = 'default',
|
|
showHeader = true,
|
|
center = false,
|
|
okButtonText = t('general.confirm'),
|
|
okButtonType = 'primary',
|
|
cancelButtonText = t('general.cancel'),
|
|
cancelButtonType = 'secondary',
|
|
flexFooter = false,
|
|
} = properties;
|
|
|
|
const [value, setValue] = useState(input);
|
|
const [valid, setValid] = useState(true);
|
|
const [validatorMessage, setValidatorMessage] = useState('');
|
|
const reference = useRef<HTMLDivElement>(null);
|
|
|
|
const {show, onClose} = properties;
|
|
|
|
useEffect(() => {
|
|
if (!show) {
|
|
return;
|
|
}
|
|
|
|
const onHidden = () => {
|
|
onClose ? onClose() : void 0;
|
|
};
|
|
|
|
const element = $(reference.current!);
|
|
element.on('hidden.bs.modal', onHidden);
|
|
|
|
return () => {
|
|
element.off('hidden.bs.modal', onHidden);
|
|
};
|
|
}, [show, onClose]);
|
|
|
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
setValue(event.target.value);
|
|
};
|
|
|
|
const confirm = () => {
|
|
const {validator} = properties;
|
|
if (typeof validator === 'function') {
|
|
const result = validator(value);
|
|
if (typeof result === 'string') {
|
|
setValidatorMessage(result);
|
|
setValid(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
properties.onConfirm?.({value});
|
|
$(reference.current!).modal('hide');
|
|
|
|
// The "hidden.bs.modal" event can't be trigged automatically when testing.
|
|
|
|
if (process.env.NODE_ENV === 'test') {
|
|
$(reference.current!).trigger('hidden.bs.modal');
|
|
}
|
|
};
|
|
|
|
const dismiss = () => {
|
|
properties.onDismiss?.();
|
|
$(reference.current!).modal('hide');
|
|
|
|
if (process.env.NODE_ENV === 'test') {
|
|
$(reference.current!).trigger('hidden.bs.modal');
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (show) {
|
|
setTimeout(() => $(reference.current!).modal('show'), 50);
|
|
}
|
|
}, [show]);
|
|
|
|
if (!show) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div ref={reference} id={properties.id} className='modal fade' role='dialog'>
|
|
<div
|
|
className={`modal-dialog ${center ? 'modal-dialog-centered' : ''}`}
|
|
role='document'
|
|
>
|
|
<div className={`modal-content bg-${type}`}>
|
|
<ModalHeader show={showHeader} title={title} onDismiss={dismiss}/>
|
|
<ModalBody
|
|
text={text}
|
|
dangerousHTML={properties.dangerousHTML}
|
|
showInput={mode === 'prompt'}
|
|
value={value}
|
|
choices={properties.choices}
|
|
inputType={inputType}
|
|
inputMode={inputMode}
|
|
placeholder={placeholder}
|
|
invalid={!valid}
|
|
validatorMessage={validatorMessage}
|
|
onChange={handleInputChange}
|
|
>
|
|
{properties.children}
|
|
</ModalBody>
|
|
<ModalFooter
|
|
showCancelButton={mode !== 'alert'}
|
|
flexFooter={flexFooter}
|
|
okButtonType={okButtonType}
|
|
okButtonText={okButtonText}
|
|
cancelButtonType={cancelButtonType}
|
|
cancelButtonText={cancelButtonText}
|
|
onConfirm={confirm}
|
|
onDismiss={dismiss}
|
|
>
|
|
{properties.footer}
|
|
</ModalFooter>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Modal;
|