270 lines
7.1 KiB
TypeScript
270 lines
7.1 KiB
TypeScript
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
|