expo-popcore-app/components/ui/Toast.tsx

270 lines
7.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { ReactNode } from 'react'
import Text from './Text'
import Block from './Block'
import { Text as RNText } from 'react-native'
import { ActivityIndicator } from 'react-native'
// 定义insets接口
interface Insets {
safeAreaInsetsBottom: number
safeAreaInsetsTop: number
safeAreaInsetsLeft: number
safeAreaInsetsRight: number
}
// 定义全局变量接口
declare global {
interface Window {
toast?: {
show: (component: ReactNode, config?: any) => void
hide: () => void
}
loading?: {
show: (component: ReactNode, config?: any) => void
hide: () => void
}
modal?: {
show: (component: ReactNode, config?: any) => void
hide: () => void
}
actionSheet?: {
show: (component: ReactNode, config?: any) => void
hide: () => void
}
}
}
// 参数接口定义
interface ShowParams {
renderContent?: () => ReactNode
title?: string
duration?: number
hideBackdrop?: boolean
}
interface ShowActionSheetParams {
itemList?: string[]
renderContent?: () => ReactNode
}
const isString = (variable: any): variable is string =>
Object.prototype.toString.call(variable) === '[object String]'
const Toast = (function () {
// 初始化默认insets
let insets: Insets = {
safeAreaInsetsBottom: 0,
safeAreaInsetsTop: 0,
safeAreaInsetsLeft: 0,
safeAreaInsetsRight: 0,
}
// // 使用回调函数获取insets
// StaticSafeAreaInsets.getSafeAreaInsets((staticInsets) => {
// insets = staticInsets
// })
let toastTimer: number | null = null
const show = (params?: ShowParams | string): void => {
// 兼容字符串参数
const options: ShowParams = typeof params === 'string' ? { title: params } : (params || {})
const { renderContent, title, duration = 4000, hideBackdrop = true } = options
hide()
const renderBody = (): ReactNode => {
if (renderContent) {
return renderContent()
}
return title && <RNText style={{ fontSize: 18, fontWeight: '600', color: '#ffffff' }}>{title}</RNText>
}
; (global as any).toast?.show(
<Block className="z-[9999] flex items-center justify-center" style={{ position: 'fixed', top: '30%', left: 0, right: 0 }}>
<Block style={{
backgroundColor: 'rgba(0, 0, 0, 0.9)',
borderRadius: 12,
padding: 20,
marginHorizontal: 40,
minHeight: 60,
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 8,
}}>
{renderBody()}
</Block>
</Block>,
{
style: {},
swipeDirection: null,
hideBackdrop,
onBackdropPress: () => { },
entering: false,
exiting: false,
},
)
if (duration > 0) {
toastTimer = setTimeout(() => {
hide()
}, duration)
}
}
const hide = (): void => {
; (global as any).toast?.hide()
if (toastTimer) {
clearTimeout(toastTimer)
toastTimer = null
}
}
// loading
let loadingTimer: number | null = null
const showLoading = (params?: ShowParams): void => {
const { renderContent, title, duration = 1500, hideBackdrop = false } = params || {}
hideLoading()
const renderBody = (): ReactNode => {
if (renderContent) {
return renderContent()
}
return (
<>
<ActivityIndicator size="small" />
{!!title && (
<Block className="mt-[12px] items-center px-[10px]">
<Text className="text-[14px] text-white">{title}</Text>
</Block>
)}
</>
)
}
; (global as any).loading?.show(
<Block className="z-[9999] flex items-center justify-center">
<Block className="mt-[-40px] min-h-[100px] min-w-[100px] items-center justify-center overflow-hidden rounded-[8px] bg-black/85 py-[20px]">
{renderBody()}
</Block>
</Block>,
{
style: {},
swipeDirection: null,
hideBackdrop,
backdropColor: 'rgba(0,0,0,0.1)',
onBackdropPress: () => { },
entering: false,
exiting: false,
},
)
if (duration > 0) {
loadingTimer = setTimeout(() => {
hideLoading()
}, duration)
}
}
const hideLoading = (): void => {
; (global as any).loading?.hide()
if (loadingTimer) {
clearTimeout(loadingTimer)
loadingTimer = null
}
}
// modal
const showModal = (ele: ReactNode, config: any = {}): void => {
; (global as any).modal?.show(ele, {
swipeDirection: null,
...config,
})
}
const hideModal = (): void => {
; (global as any).modal?.hide()
}
// actionsheet
// 兼容半屏组件
const showActionSheet = ({ itemList, renderContent }: ShowActionSheetParams): Promise<number> => {
return new Promise((resolve, reject) => {
const handleSelect = (item: string): void => {
hideActionSheet()
const itemIndex = itemList?.findIndex((i) => i === item) || -1
resolve(itemIndex)
}
const handleCancel = (): void => {
hideActionSheet()
reject()
}
const renderBody = (): ReactNode => {
if (Array.isArray(itemList)) {
return (
<Block className="bg-white2 mb-[-1000px] gap-[1px] overflow-hidden rounded-t-[12px]">
{itemList.map((item, index) => (
<Block
onClick={() => handleSelect(item)}
key={index}
className="items-center justify-center bg-white py-[16px]"
>
<Text className="text-[16px] text-black">{item}</Text>
</Block>
))}
<Block className="bg-white2 mt-[8px] w-full" />
<Block
onClick={handleCancel}
style={{ paddingBottom: insets.safeAreaInsetsBottom + 20 + 1000 }}
className="flex items-center justify-center bg-white py-[16px]"
>
<Text className="text-[16px] text-black"></Text>
</Block>
</Block>
)
}
if (!renderContent) return null
return (
<Block className="">
{/* <ModalHeader /> */}
{/* 安卓闪动向上2px */}
<Block className="mt-[-2px]">{renderContent()}</Block>
<Block
className="mb-[-1000px] w-full bg-white"
style={{ height: insets.safeAreaInsetsBottom + 20 + 1000, position: 'relative' }}
/>
</Block>
)
}
; (global as any).actionSheet?.show(<Block className="">{renderBody()}</Block>, {
style: { justifyContent: 'flex-end' },
})
})
}
const hideActionSheet = (): void => {
; (global as any).actionSheet?.hide()
}
return {
showActionSheet,
hideActionSheet,
showModal,
hideModal,
showLoading,
hideLoading,
show,
hide,
}
})()
export default Toast