117 lines
3.2 KiB
TypeScript
117 lines
3.2 KiB
TypeScript
import React, { useState } from 'react'
|
|
|
|
interface BluePlayButtonProps {
|
|
text?: string
|
|
lang?: string
|
|
disabled?: boolean
|
|
className?: string
|
|
size?: 'sm' | 'md' | 'lg'
|
|
title?: string
|
|
onPlayStart?: () => void
|
|
onPlayEnd?: () => void
|
|
}
|
|
|
|
export const BluePlayButton: React.FC<BluePlayButtonProps> = ({
|
|
text,
|
|
lang = 'en-US',
|
|
disabled = false,
|
|
className = '',
|
|
size = 'md',
|
|
title,
|
|
onPlayStart,
|
|
onPlayEnd
|
|
}) => {
|
|
const [isPlaying, setIsPlaying] = useState(false)
|
|
|
|
const sizeClasses = {
|
|
sm: 'w-8 h-8',
|
|
md: 'w-10 h-10',
|
|
lg: 'w-12 h-12'
|
|
}
|
|
|
|
const iconSizes = {
|
|
sm: 'w-4 h-4',
|
|
md: 'w-5 h-5',
|
|
lg: 'w-6 h-6'
|
|
}
|
|
|
|
// 內建 TTS 邏輯
|
|
const handleToggle = () => {
|
|
// 停止播放邏輯
|
|
if (isPlaying) {
|
|
speechSynthesis.cancel()
|
|
setIsPlaying(false)
|
|
if (onPlayEnd) onPlayEnd()
|
|
return
|
|
}
|
|
|
|
// 開始播放邏輯
|
|
if (onPlayStart) {
|
|
// 自定義播放場景(如錄音回放)
|
|
setIsPlaying(true)
|
|
onPlayStart()
|
|
setTimeout(() => {
|
|
setIsPlaying(false)
|
|
if (onPlayEnd) onPlayEnd()
|
|
}, 3000)
|
|
} else if (text) {
|
|
// 標準 TTS 播放
|
|
const utterance = new SpeechSynthesisUtterance(text)
|
|
utterance.lang = lang
|
|
utterance.rate = 0.8
|
|
|
|
utterance.onstart = () => {
|
|
setIsPlaying(true)
|
|
}
|
|
|
|
utterance.onend = () => {
|
|
setIsPlaying(false)
|
|
if (onPlayEnd) onPlayEnd()
|
|
}
|
|
|
|
utterance.onerror = () => {
|
|
setIsPlaying(false)
|
|
if (onPlayEnd) onPlayEnd()
|
|
}
|
|
|
|
speechSynthesis.speak(utterance)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<button
|
|
onClick={handleToggle}
|
|
disabled={disabled}
|
|
title={title || (isPlaying ? "點擊停止播放" : "點擊播放發音")}
|
|
className={`group relative ${sizeClasses[size]} rounded-full shadow-lg transform transition-all duration-200
|
|
${isPlaying
|
|
? 'bg-gradient-to-br from-green-500 to-green-600 shadow-green-200 scale-105'
|
|
: 'bg-gradient-to-br from-blue-500 to-blue-600 hover:shadow-xl hover:scale-105 shadow-blue-200'
|
|
} ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'} ${className}
|
|
`}
|
|
>
|
|
{/* 播放中波紋效果 */}
|
|
{isPlaying && (
|
|
<div className="absolute inset-0 bg-blue-400 rounded-full animate-ping opacity-40"></div>
|
|
)}
|
|
|
|
{/* 按鈕圖標 */}
|
|
<div className="relative z-10 flex items-center justify-center w-full h-full">
|
|
{isPlaying ? (
|
|
<svg className={`${iconSizes[size]} text-white`} fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>
|
|
</svg>
|
|
) : (
|
|
<svg className={`${iconSizes[size]} text-white group-hover:scale-110 transition-transform`} fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M8 5v14l11-7z"/>
|
|
</svg>
|
|
)}
|
|
</div>
|
|
|
|
{/* 懸停提示光環 */}
|
|
{!isPlaying && !disabled && (
|
|
<div className="absolute inset-0 rounded-full bg-white opacity-0 group-hover:opacity-20 transition-opacity duration-200"></div>
|
|
)}
|
|
</button>
|
|
)
|
|
} |