blessing-skin-server/resources/assets/src/components/Modal.tsx

166 lines
4.2 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react'
import $ from 'jquery'
import 'bootstrap'
import { t } from '../scripts/i18n'
import ModalHeader from './ModalHeader'
import ModalBody from './ModalBody'
import ModalFooter from './ModalFooter'
import type { Props as HeaderProps } from './ModalHeader'
import type { Props as BodyProps } from './ModalBody'
import type { Props as FooterProps } from './ModalFooter'
type BasicOptions = {
mode?: 'alert' | 'confirm' | 'prompt'
show?: boolean
input?: string
validator?(value: any): string | boolean | undefined
type?: string
showHeader?: boolean
center?: boolean
children?: React.ReactNode
}
export type ModalOptions = BasicOptions & HeaderProps & BodyProps & FooterProps
type Props = {
id?: string
children?: React.ReactNode
footer?: React.ReactNode
onConfirm?(payload: { value: string }): void
onDismiss?(): void
onClose?(): void
}
export type ModalResult = {
value: string
}
const Modal: React.FC<ModalOptions & Props> = (props) => {
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,
} = props
const [value, setValue] = useState(input)
const [valid, setValid] = useState(true)
const [validatorMessage, setValidatorMessage] = useState('')
const ref = useRef<HTMLDivElement>(null)
const { show } = props
useEffect(() => {
if (!show) {
return
}
const onHidden = () => props.onClose?.()
const el = $(ref.current!)
el.on('hidden.bs.modal', onHidden)
return () => {
el.off('hidden.bs.modal', onHidden)
}
}, [show, props.onClose])
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value)
}
const confirm = () => {
const { validator } = props
if (typeof validator === 'function') {
const result = validator(value)
if (typeof result === 'string') {
setValidatorMessage(result)
setValid(false)
return
}
}
props.onConfirm?.({ value })
$(ref.current!).modal('hide')
// The "hidden.bs.modal" event can't be trigged automatically when testing.
/* istanbul ignore next */
if (process.env.NODE_ENV === 'test') {
$(ref.current!).trigger('hidden.bs.modal')
}
}
const dismiss = () => {
props.onDismiss?.()
$(ref.current!).modal('hide')
/* istanbul ignore next */
if (process.env.NODE_ENV === 'test') {
$(ref.current!).trigger('hidden.bs.modal')
}
}
useEffect(() => {
if (show) {
setTimeout(() => $(ref.current!).modal('show'), 50)
}
}, [show])
if (!show) {
return null
}
return (
<div id={props.id} className="modal fade" role="dialog" ref={ref}>
<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={props.dangerousHTML}
showInput={mode === 'prompt'}
value={value}
choices={props.choices}
onChange={handleInputChange}
inputType={inputType}
inputMode={inputMode}
placeholder={placeholder}
invalid={!valid}
validatorMessage={validatorMessage}
>
{props.children}
</ModalBody>
<ModalFooter
showCancelButton={mode !== 'alert'}
flexFooter={flexFooter}
okButtonType={okButtonType}
okButtonText={okButtonText}
cancelButtonType={cancelButtonType}
cancelButtonText={cancelButtonText}
onConfirm={confirm}
onDismiss={dismiss}
>
{props.footer}
</ModalFooter>
</div>
</div>
</div>
)
}
export default Modal