Files
desa-darmasaba/src/app/darmasaba/_com/NewsReaderalanding.tsx
nico 4821934224 fix(music-player): fix floating icon position shift on hover
Problem:
- Icon bergeser ke bawah saat hover
- transform: 'scale(1.1)' mengganti transform: 'translateY(-80%)'
- CSS transform property di-replace, bukan di-mix

Solution:
- Gabungkan kedua transform dalam satu string
- Hover: 'translateY(-80%) scale(1.1)' - maintain posisi + scale
- Leave: 'translateY(-80%)' - kembali ke posisi semula

Changes:
- onMouseEnter: transform = 'translateY(-80%) scale(1.1)'
- onMouseLeave: transform = 'translateY(-80%)'
- Added 'ease' timing function for smoother transition

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-05 14:53:38 +08:00

114 lines
3.3 KiB
TypeScript

'use client';
import { Button } from '@mantine/core';
import { IconDisabled, IconDisabledOff } from '@tabler/icons-react';
import { useEffect, useRef, useState } from 'react';
const NewsReaderLanding = () => {
const [isPointerMode, setIsPointerMode] = useState(false);
const utteranceRef = useRef<SpeechSynthesisUtterance | null>(null);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const lastTextRef = useRef<string>('');
const speakText = (text: string) => {
if (!window.speechSynthesis || !text.trim()) return;
// Jangan baca ulang kalau teksnya sama
if (lastTextRef.current === text) return;
lastTextRef.current = text;
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'id-ID';
utterance.rate = 1;
utterance.pitch = 1;
utteranceRef.current = utterance;
window.speechSynthesis.speak(utterance);
};
useEffect(() => {
const root = document.getElementById('page-root');
if (!root) return;
root.style.cursor = isPointerMode ? 'pointer' : 'auto';
if (!isPointerMode) return;
const handleMouseOver = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (
!target ||
!target.innerText ||
target.tagName === 'BUTTON' ||
target.tagName === 'SVG' ||
target.closest('button')
)
return;
// Hapus timeout sebelumnya biar gak spam
if (timeoutRef.current) clearTimeout(timeoutRef.current);
// Delay dikit biar smooth (hindari brebet)
timeoutRef.current = setTimeout(() => {
speakText(target.innerText);
}, 250); // 250ms delay kasih waktu pindah cursor
};
const handleMouseOut = () => {
// Delay kecil sebelum cancel supaya gak motong kasar
if (timeoutRef.current) clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
window.speechSynthesis.cancel();
lastTextRef.current = '';
}, 150);
};
root.addEventListener('mouseover', handleMouseOver);
root.addEventListener('mouseout', handleMouseOut);
return () => {
root.removeEventListener('mouseover', handleMouseOver);
root.removeEventListener('mouseout', handleMouseOut);
root.style.cursor = 'auto';
if (timeoutRef.current) clearTimeout(timeoutRef.current);
window.speechSynthesis.cancel();
lastTextRef.current = '';
};
}, [isPointerMode]);
const handleToggle = () => {
setIsPointerMode((prev) => {
if (prev) {
window.speechSynthesis.cancel();
lastTextRef.current = '';
}
return !prev;
});
};
return (
<Button
onClick={handleToggle}
color="#0B4F78"
variant="filled"
size="md"
mt="md"
style={{
position: 'fixed',
top: '50%', // Menempatkan titik atas ikon di tengah layar
left: '0px',
transform: 'translateY(80%)', // Menggeser ikon ke atas sebesar setengah tingginya sendiri agar benar-benar di tengah
borderBottomRightRadius: '20px',
borderTopRightRadius: '20px',
cursor: 'pointer',
transition: 'transform 0.2s',
zIndex: 1
}}
>
{isPointerMode ? <IconDisabledOff /> : <IconDisabled />}
</Button>
);
};
export default NewsReaderLanding;