Initial commit: expo-popcore-app

This commit is contained in:
imeepos
2025-12-25 16:25:55 +08:00
commit 02d0c807cd
160 changed files with 39560 additions and 0 deletions

253
components/ui/Toast.tsx Normal file
View File

@@ -0,0 +1,253 @@
import React, { ReactNode } from 'react'
import Text from './Text'
import Block from './Block'
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): void => {
const { renderContent, title, duration = 2000, hideBackdrop = true } = params || {}
hide()
const renderBody = (): ReactNode => {
if (renderContent) {
return renderContent()
}
return title && <Text className="text-[16px] text-white">{title}</Text>
}
; (global as any).toast?.show(
<Block className="z-[9999] flex items-center justify-center">
<Block className="relative mx-[20px] mt-[-40px] rounded-[2px] bg-black/85 px-[12px] py-[8px]">
{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