index.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import { useState, useEffect } from 'react'
  2. import { View, Text } from '@tarojs/components'
  3. import TDesignIcon from '../icon'
  4. import './index.css'
  5. interface ToastProps {
  6. visible?: boolean
  7. message?: string
  8. icon?: string
  9. theme?: 'loading' | 'success' | 'error' | 'warning'
  10. duration?: number
  11. placement?: 'top' | 'middle' | 'bottom'
  12. direction?: 'row' | 'column'
  13. showOverlay?: boolean
  14. preventScrollThrough?: boolean
  15. onClose?: () => void
  16. }
  17. export default function TDesignToast({
  18. visible = false,
  19. message = '',
  20. icon,
  21. theme,
  22. duration = 2000,
  23. placement = 'middle',
  24. direction = 'row',
  25. showOverlay = false,
  26. onClose
  27. }: ToastProps) {
  28. const [realVisible, setRealVisible] = useState(visible)
  29. const [transitionClass, setTransitionClass] = useState('')
  30. useEffect(() => {
  31. if (visible) {
  32. setRealVisible(true)
  33. setTransitionClass('tdesign-toast--enter')
  34. // 自动关闭
  35. if (duration > 0) {
  36. const timer = setTimeout(() => {
  37. handleClose()
  38. }, duration)
  39. return () => clearTimeout(timer)
  40. }
  41. } else {
  42. handleClose()
  43. }
  44. }, [visible, duration])
  45. const handleClose = () => {
  46. setTransitionClass('tdesign-toast--leave')
  47. setTimeout(() => {
  48. setRealVisible(false)
  49. onClose?.()
  50. }, 300)
  51. }
  52. const getIconName = () => {
  53. if (icon) return icon
  54. switch (theme) {
  55. case 'loading':
  56. return 'loading'
  57. case 'success':
  58. return 'check-circle'
  59. case 'error':
  60. return 'error'
  61. case 'warning':
  62. return 'warning'
  63. default:
  64. return undefined
  65. }
  66. }
  67. const getPlacementStyle = () => {
  68. switch (placement) {
  69. case 'top':
  70. return { top: '25%' }
  71. case 'bottom':
  72. return { top: '75%' }
  73. case 'middle':
  74. default:
  75. return { top: '45%' }
  76. }
  77. }
  78. const iconName = getIconName()
  79. const isLoading = theme === 'loading'
  80. if (!realVisible) {
  81. return null
  82. }
  83. return (
  84. <>
  85. {/* 遮罩层 */}
  86. {showOverlay && (
  87. <View
  88. className="tdesign-toast__overlay"
  89. style={{
  90. position: 'fixed',
  91. top: 0,
  92. left: 0,
  93. right: 0,
  94. bottom: 0,
  95. backgroundColor: 'rgba(0, 0, 0, 0.6)',
  96. zIndex: 11000
  97. }}
  98. onClick={handleClose}
  99. />
  100. )}
  101. {/* Toast 内容 */}
  102. <View
  103. className={`
  104. tdesign-toast
  105. tdesign-toast--${direction}
  106. ${theme ? `tdesign-toast--${theme}` : ''}
  107. ${message ? 'tdesign-toast--with-text' : ''}
  108. ${transitionClass}
  109. `}
  110. style={{
  111. position: 'fixed',
  112. left: '50%',
  113. transform: 'translate(-50%, -50%)',
  114. zIndex: 12001,
  115. ...getPlacementStyle()
  116. }}
  117. aria-role="alert"
  118. >
  119. <View className={`tdesign-toast__content tdesign-toast__content--${direction}`}>
  120. {isLoading && (
  121. <TDesignIcon
  122. name="loading"
  123. size={direction === 'row' ? '48rpx' : '64rpx'}
  124. color="#fff"
  125. className="tdesign-toast__loading"
  126. />
  127. )}
  128. {!isLoading && iconName && (
  129. <TDesignIcon
  130. name={iconName}
  131. size={direction === 'row' ? '48rpx' : '64rpx'}
  132. color="#fff"
  133. className={`tdesign-toast__icon tdesign-toast__icon--${direction}`}
  134. />
  135. )}
  136. {message && (
  137. <Text className={`tdesign-toast__text tdesign-toast__text--${direction}`}>
  138. {message}
  139. </Text>
  140. )}
  141. </View>
  142. </View>
  143. </>
  144. )
  145. }