index.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import React, { useEffect, useRef, useState } from 'react'
  2. import { createPortal } from 'react-dom'
  3. import { useTranslation } from 'react-i18next'
  4. import Button from '../button'
  5. export type IConfirm = {
  6. className?: string
  7. isShow: boolean
  8. type?: 'info' | 'warning'
  9. title: string
  10. content?: React.ReactNode
  11. confirmText?: string | null
  12. onConfirm: () => void
  13. cancelText?: string
  14. onCancel: () => void
  15. isLoading?: boolean
  16. isDisabled?: boolean
  17. showConfirm?: boolean
  18. showCancel?: boolean
  19. maskClosable?: boolean
  20. }
  21. function Confirm({
  22. isShow,
  23. type = 'warning',
  24. title,
  25. content,
  26. confirmText,
  27. cancelText,
  28. onConfirm,
  29. onCancel,
  30. showConfirm = true,
  31. showCancel = true,
  32. isLoading = false,
  33. isDisabled = false,
  34. maskClosable = true,
  35. }: IConfirm) {
  36. const { t } = useTranslation()
  37. const dialogRef = useRef<HTMLDivElement>(null)
  38. const [isVisible, setIsVisible] = useState(isShow)
  39. const confirmTxt = confirmText || `${t('common.operation.confirm')}`
  40. const cancelTxt = cancelText || `${t('common.operation.cancel')}`
  41. useEffect(() => {
  42. const handleKeyDown = (event: KeyboardEvent) => {
  43. if (event.key === 'Escape')
  44. onCancel()
  45. }
  46. document.addEventListener('keydown', handleKeyDown)
  47. return () => {
  48. document.removeEventListener('keydown', handleKeyDown)
  49. }
  50. }, [onCancel])
  51. const handleClickOutside = (event: MouseEvent) => {
  52. if (maskClosable && dialogRef.current && !dialogRef.current.contains(event.target as Node))
  53. onCancel()
  54. }
  55. useEffect(() => {
  56. document.addEventListener('mousedown', handleClickOutside)
  57. return () => {
  58. document.removeEventListener('mousedown', handleClickOutside)
  59. }
  60. }, [maskClosable])
  61. useEffect(() => {
  62. if (isShow) {
  63. setIsVisible(true)
  64. }
  65. else {
  66. const timer = setTimeout(() => setIsVisible(false), 200)
  67. return () => clearTimeout(timer)
  68. }
  69. }, [isShow])
  70. if (!isVisible)
  71. return null
  72. return createPortal(
  73. <div className={'fixed inset-0 flex items-center justify-center z-[10000000] bg-background-overlay'}
  74. onClick={(e) => {
  75. e.preventDefault()
  76. e.stopPropagation()
  77. }}>
  78. <div ref={dialogRef} className={'relative w-full max-w-[480px] overflow-hidden'}>
  79. <div className='flex flex-col items-start max-w-full rounded-2xl border-[0.5px] border-solid border-components-panel-border shadows-shadow-lg bg-components-panel-bg'>
  80. <div className='flex pt-6 pl-6 pr-6 pb-4 flex-col items-start gap-2 self-stretch'>
  81. <div className='title-2xl-semi-bold text-text-primary'>{title}</div>
  82. <div className='system-md-regular text-text-tertiary w-full'>{content}</div>
  83. </div>
  84. <div className='flex p-6 gap-2 justify-end items-start self-stretch'>
  85. {showCancel && <Button onClick={onCancel}>{cancelTxt}</Button>}
  86. {showConfirm && <Button variant={'primary'} destructive={type !== 'info'} loading={isLoading} disabled={isDisabled} onClick={onConfirm}>{confirmTxt}</Button>}
  87. </div>
  88. </div>
  89. </div>
  90. </div>, document.body,
  91. )
  92. }
  93. export default React.memo(Confirm)