index.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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. if (event.key === 'Enter' && isShow) {
  46. event.preventDefault()
  47. onConfirm()
  48. }
  49. }
  50. document.addEventListener('keydown', handleKeyDown)
  51. return () => {
  52. document.removeEventListener('keydown', handleKeyDown)
  53. }
  54. }, [onCancel, onConfirm, isShow])
  55. const handleClickOutside = (event: MouseEvent) => {
  56. if (maskClosable && dialogRef.current && !dialogRef.current.contains(event.target as Node))
  57. onCancel()
  58. }
  59. useEffect(() => {
  60. document.addEventListener('mousedown', handleClickOutside)
  61. return () => {
  62. document.removeEventListener('mousedown', handleClickOutside)
  63. }
  64. }, [maskClosable])
  65. useEffect(() => {
  66. if (isShow) {
  67. setIsVisible(true)
  68. }
  69. else {
  70. const timer = setTimeout(() => setIsVisible(false), 200)
  71. return () => clearTimeout(timer)
  72. }
  73. }, [isShow])
  74. if (!isVisible)
  75. return null
  76. return createPortal(
  77. <div className={'fixed inset-0 z-[10000000] flex items-center justify-center bg-background-overlay'}
  78. onClick={(e) => {
  79. e.preventDefault()
  80. e.stopPropagation()
  81. }}>
  82. <div ref={dialogRef} className={'relative w-full max-w-[480px] overflow-hidden'}>
  83. <div className='shadows-shadow-lg flex max-w-full flex-col items-start rounded-2xl border-[0.5px] border-solid border-components-panel-border bg-components-panel-bg'>
  84. <div className='flex flex-col items-start gap-2 self-stretch pb-4 pl-6 pr-6 pt-6'>
  85. <div className='title-2xl-semi-bold text-text-primary'>{title}</div>
  86. <div className='system-md-regular w-full text-text-tertiary'>{content}</div>
  87. </div>
  88. <div className='flex items-start justify-end gap-2 self-stretch p-6'>
  89. {showCancel && <Button onClick={onCancel}>{cancelTxt}</Button>}
  90. {showConfirm && <Button variant={'primary'} destructive={type !== 'info'} loading={isLoading} disabled={isDisabled} onClick={onConfirm}>{confirmTxt}</Button>}
  91. </div>
  92. </div>
  93. </div>
  94. </div>, document.body,
  95. )
  96. }
  97. export default React.memo(Confirm)