video-flow-b/components/ui/h5-progress-toast.tsx

117 lines
3.8 KiB
TypeScript
Raw 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.

'use client'
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
interface ProgressState {
open: boolean
title: string
progress: number
}
interface ProgressToastContextValue {
/** 显示或更新进度提示 */
show: (params: { title?: string; progress?: number }) => void
/** 仅更新进度或标题 */
update: (params: { title?: string; progress?: number }) => void
/** 隐藏提示 */
hide: () => void
/** 当前状态(只读) */
state: ProgressState
}
const ProgressToastContext = createContext<ProgressToastContextValue | null>(null)
export const H5ProgressToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, setState] = useState<ProgressState>({ open: false, title: 'processing...', progress: 0 })
const hide = useCallback(() => setState(prev => ({ ...prev, open: false })), [])
const show = useCallback((params: { title?: string; progress?: number }) => {
setState(prev => ({
open: true,
title: params.title ?? prev.title,
progress: typeof params.progress === 'number' ? params.progress : prev.progress,
}))
}, [])
const update = useCallback((params: { title?: string; progress?: number }) => {
setState(prev => ({
open: prev.open || true,
title: params.title ?? prev.title,
progress: typeof params.progress === 'number' ? params.progress : prev.progress,
}))
}, [])
// 进度到100自动隐藏
useEffect(() => {
if (!state.open) return
if (state.progress >= 100) {
const timer = setTimeout(() => hide(), 600)
return () => clearTimeout(timer)
}
}, [state.open, state.progress, hide])
const value = useMemo<ProgressToastContextValue>(() => ({ show, update, hide, state }), [show, update, hide, state])
return (
<ProgressToastContext.Provider value={value}>
{children}
<H5ProgressToastUI open={state.open} title={state.title} progress={state.progress} />
</ProgressToastContext.Provider>
)
}
export function useH5ProgressToast() {
const ctx = useContext(ProgressToastContext)
if (!ctx) throw new Error('useH5ProgressToast must be used within H5ProgressToastProvider')
return ctx
}
interface UIProps {
open: boolean
title: string
progress: number
}
/**
* H5样式的顶部居中进度提示贴合截图风格。
* 无遮罩进度为0-100。
*/
const H5ProgressToastUI: React.FC<UIProps> = ({ open, title, progress }) => {
if (!open) return null
const pct = Math.max(0, Math.min(100, Math.round(progress)))
return (
<div
data-alt="progress-toast"
className="fixed right-4 top-16 sm:top-20 z-[100]"
>
<div
data-alt="toast-card"
className="px-4 py-3 rounded-2xl bg-[#1f1b2e]/95 shadow-2xl border border-white/10 min-w-[240px] max-w-[86vw]"
>
<div className="flex items-center gap-3">
<span className="w-3 h-3 rounded-full bg-purple-500 shadow-[0_0_12px_rgba(168,85,247,0.8)]" data-alt="dot" />
<div className="flex-1 min-w-0">
<div className="text-white text-sm font-semibold truncate" data-alt="title-text">{title}</div>
<div className="mt-2 h-2 rounded-full bg-purple-500/30 overflow-hidden" data-alt="progress-bar">
<div
className="h-full bg-gradient-to-r from-purple-400 to-purple-600 rounded-full transition-[width] duration-300 ease-out"
style={{ width: `${pct}%` }}
data-alt="progress-inner"
/>
</div>
<div className="mt-1 flex items-center justify-between text-xs text-white/70">
<span data-alt="percent">{pct}%</span>
<span data-alt="hint">{pct >= 100 ? 'completed' : ''}</span>
</div>
</div>
</div>
</div>
</div>
)
}
export default H5ProgressToastProvider