|
|
@@ -0,0 +1,158 @@
|
|
|
+import { useState, useEffect } from 'react'
|
|
|
+import { View, Text } from '@tarojs/components'
|
|
|
+import TDesignIcon from '../icon'
|
|
|
+
|
|
|
+interface ToastProps {
|
|
|
+ visible?: boolean
|
|
|
+ message?: string
|
|
|
+ icon?: string
|
|
|
+ theme?: 'loading' | 'success' | 'error' | 'warning'
|
|
|
+ duration?: number
|
|
|
+ placement?: 'top' | 'middle' | 'bottom'
|
|
|
+ direction?: 'row' | 'column'
|
|
|
+ showOverlay?: boolean
|
|
|
+ preventScrollThrough?: boolean
|
|
|
+ onClose?: () => void
|
|
|
+}
|
|
|
+
|
|
|
+export default function TDesignToast({
|
|
|
+ visible = false,
|
|
|
+ message = '',
|
|
|
+ icon,
|
|
|
+ theme,
|
|
|
+ duration = 2000,
|
|
|
+ placement = 'middle',
|
|
|
+ direction = 'row',
|
|
|
+ showOverlay = false,
|
|
|
+ onClose
|
|
|
+}: ToastProps) {
|
|
|
+ const [realVisible, setRealVisible] = useState(visible)
|
|
|
+ const [transitionClass, setTransitionClass] = useState('')
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (visible) {
|
|
|
+ setRealVisible(true)
|
|
|
+ setTransitionClass('tdesign-toast--enter')
|
|
|
+
|
|
|
+ // 自动关闭
|
|
|
+ if (duration > 0) {
|
|
|
+ const timer = setTimeout(() => {
|
|
|
+ handleClose()
|
|
|
+ }, duration)
|
|
|
+ return () => clearTimeout(timer)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ handleClose()
|
|
|
+ }
|
|
|
+ }, [visible, duration])
|
|
|
+
|
|
|
+ const handleClose = () => {
|
|
|
+ setTransitionClass('tdesign-toast--leave')
|
|
|
+ setTimeout(() => {
|
|
|
+ setRealVisible(false)
|
|
|
+ onClose?.()
|
|
|
+ }, 300)
|
|
|
+ }
|
|
|
+
|
|
|
+ const getIconName = () => {
|
|
|
+ if (icon) return icon
|
|
|
+
|
|
|
+ switch (theme) {
|
|
|
+ case 'loading':
|
|
|
+ return 'loading'
|
|
|
+ case 'success':
|
|
|
+ return 'check-circle'
|
|
|
+ case 'error':
|
|
|
+ return 'error'
|
|
|
+ case 'warning':
|
|
|
+ return 'warning'
|
|
|
+ default:
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const getPlacementStyle = () => {
|
|
|
+ switch (placement) {
|
|
|
+ case 'top':
|
|
|
+ return { top: '25%' }
|
|
|
+ case 'bottom':
|
|
|
+ return { top: '75%' }
|
|
|
+ case 'middle':
|
|
|
+ default:
|
|
|
+ return { top: '45%' }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const iconName = getIconName()
|
|
|
+ const isLoading = theme === 'loading'
|
|
|
+
|
|
|
+ if (!realVisible) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ {/* 遮罩层 */}
|
|
|
+ {showOverlay && (
|
|
|
+ <View
|
|
|
+ className="tdesign-toast__overlay"
|
|
|
+ style={{
|
|
|
+ position: 'fixed',
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ right: 0,
|
|
|
+ bottom: 0,
|
|
|
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
|
|
|
+ zIndex: 11000
|
|
|
+ }}
|
|
|
+ onClick={handleClose}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* Toast 内容 */}
|
|
|
+ <View
|
|
|
+ className={`
|
|
|
+ tdesign-toast
|
|
|
+ tdesign-toast--${direction}
|
|
|
+ ${theme ? `tdesign-toast--${theme}` : ''}
|
|
|
+ ${message ? 'tdesign-toast--with-text' : ''}
|
|
|
+ ${transitionClass}
|
|
|
+ `}
|
|
|
+ style={{
|
|
|
+ position: 'fixed',
|
|
|
+ left: '50%',
|
|
|
+ transform: 'translate(-50%, -50%)',
|
|
|
+ zIndex: 12001,
|
|
|
+ ...getPlacementStyle()
|
|
|
+ }}
|
|
|
+ aria-role="alert"
|
|
|
+ >
|
|
|
+ <View className={`tdesign-toast__content tdesign-toast__content--${direction}`}>
|
|
|
+ {isLoading && (
|
|
|
+ <TDesignIcon
|
|
|
+ name="loading"
|
|
|
+ size={direction === 'row' ? '48rpx' : '64rpx'}
|
|
|
+ color="#fff"
|
|
|
+ className="tdesign-toast__loading"
|
|
|
+ />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {!isLoading && iconName && (
|
|
|
+ <TDesignIcon
|
|
|
+ name={iconName}
|
|
|
+ size={direction === 'row' ? '48rpx' : '64rpx'}
|
|
|
+ color="#fff"
|
|
|
+ className={`tdesign-toast__icon tdesign-toast__icon--${direction}`}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {message && (
|
|
|
+ <Text className={`tdesign-toast__text tdesign-toast__text--${direction}`}>
|
|
|
+ {message}
|
|
|
+ </Text>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </>
|
|
|
+ )
|
|
|
+}
|