fix(ui): clear button hover highlight when mouse leaves

Views set focus state on onMouseEnter but had no matching onMouseLeave, so the last-hovered button stayed highlighted until the mouse entered a different one or keyboard navigation moved focus.

Add onMouseLeave handlers across VersionsView rows and action buttons, SettingsView / ThemesView / SkinsView menu items, and the Import Fork / Import Instance modals. For the views with nullable focusIndex (number | null) the leave handler sets null; for VersionsView and the modals (plain number) it uses -1 as a no-focus sentinel that never matches a valid index.

For VersionsView specifically:
- Row-level onMouseLeave clears both focusRow and focusCol to -1; per-button onMouseLeave inside a row resets focusCol to 0 so the row stays highlighted while the mouse is over it but outside any action button.
- Guard the Enter key handler with focusRow >= 0 so a cleared focus state doesn't crash editions[-1] lookups.
This commit is contained in:
itsRevela
2026-04-17 14:58:57 -05:00
parent 349ab4baae
commit f68059afec
6 changed files with 40 additions and 2 deletions

View File

@@ -151,6 +151,7 @@ export default function CustomTUModal({
<div className="flex gap-4 mt-8 w-full">
<button
onMouseEnter={() => setFocusIndex(3)}
onMouseLeave={() => setFocusIndex(-1)}
onClick={() => {
playSfx("close_click.wav");
onClose();
@@ -166,6 +167,7 @@ export default function CustomTUModal({
</button>
<button
onMouseEnter={() => setFocusIndex(4)}
onMouseLeave={() => setFocusIndex(-1)}
onClick={() => {
playSfx("save_click.wav");
handleImport();

View File

@@ -142,6 +142,7 @@ export default function ImportInstanceModal({
<div className="flex flex-col gap-3 mt-8 w-full items-center">
<button
onMouseEnter={() => setFocusIndex(3)}
onMouseLeave={() => setFocusIndex(-1)}
onClick={handleImport}
className={`w-full h-12 flex items-center justify-center text-xl mc-text-shadow transition-all outline-none border-none bg-transparent ${focusIndex === 3 ? "text-[#FFFF55]" : "text-white"}`}
style={{
@@ -156,6 +157,7 @@ export default function ImportInstanceModal({
<div className="flex gap-4 w-full">
<button
onMouseEnter={() => setFocusIndex(4)}
onMouseLeave={() => setFocusIndex(-1)}
onClick={() => {
playSfx("close_click.wav");
onClose();

View File

@@ -550,6 +550,7 @@ const SettingsView = memo(function SettingsView() {
data-index={index}
tabIndex={0}
onMouseEnter={() => setFocusIndex(index)}
onMouseLeave={() => setFocusIndex(null)}
className="relative w-[360px] h-10 flex items-center justify-center cursor-pointer transition-all outline-none border-none hover:text-[#FFFF55] shrink-0"
style={getSliderStyle(index)}
>
@@ -582,6 +583,7 @@ const SettingsView = memo(function SettingsView() {
key={item.id}
data-index={index}
onMouseEnter={() => setFocusIndex(index)}
onMouseLeave={() => setFocusIndex(null)}
onClick={item.onClick}
className={`w-[360px] h-10 flex items-center justify-center px-4 relative z-30 transition-colors outline-none border-none shrink-0 ${isRed
? focusIndex === index
@@ -612,6 +614,7 @@ const SettingsView = memo(function SettingsView() {
<button
data-index={backIndex}
onMouseEnter={() => setFocusIndex(backIndex)}
onMouseLeave={() => setFocusIndex(null)}
onClick={backItem.onClick}
className={`w-72 h-10 flex items-center justify-center transition-colors text-xl mc-text-shadow outline-none border-none hover:text-[#FFFF55] ${focusIndex === backIndex ? "text-[#FFFF55]" : "text-white"
}`}

View File

@@ -291,6 +291,7 @@ const SkinsView = memo(function SkinsView() {
<button
data-index="0"
onMouseEnter={() => setFocusIndex(0)}
onMouseLeave={() => setFocusIndex(null)}
onClick={handleImportClick}
className={`w-40 h-10 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none hover:text-[#FFFF55] ${focusIndex === 0 ? 'text-[#FFFF55]' : 'text-white'}`}
style={{ backgroundImage: focusIndex === 0 ? "url('/images/button_highlighted.png')" : "url('/images/Button_Background.png')", backgroundSize: '100% 100%', imageRendering: 'pixelated' }}
@@ -301,6 +302,7 @@ const SkinsView = memo(function SkinsView() {
<button
data-index="1"
onMouseEnter={() => !isActiveDefault && setFocusIndex(1)}
onMouseLeave={() => setFocusIndex(null)}
onClick={handleDeleteActive}
className={`w-40 h-10 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none ${isActiveDefault ? 'text-gray-400 opacity-80 cursor-not-allowed' : (focusIndex === 1 ? 'text-[#FFFF55]' : 'text-white')}`}
style={{
@@ -315,6 +317,7 @@ const SkinsView = memo(function SkinsView() {
<button
data-index="2"
onMouseEnter={() => setFocusIndex(2)}
onMouseLeave={() => setFocusIndex(null)}
onClick={() => handleToggleModel()}
className={`w-40 h-10 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none hover:text-[#FFFF55] ${focusIndex === 2 ? 'text-[#FFFF55]' : 'text-white'}`}
style={{ backgroundImage: focusIndex === 2 ? "url('/images/button_highlighted.png')" : "url('/images/Button_Background.png')", backgroundSize: '100% 100%', imageRendering: 'pixelated' }}
@@ -328,6 +331,7 @@ const SkinsView = memo(function SkinsView() {
<button
data-index="3"
onMouseEnter={() => setFocusIndex(3)}
onMouseLeave={() => setFocusIndex(null)}
onClick={() => { playClickSound(); TauriService.openSkinsFolder().catch(() => { }); }}
className={`mc-sq-btn w-10 h-10 flex items-center justify-center outline-none border-none transition-all`}
style={{ backgroundImage: focusIndex === 3 ? "url('/images/Button_Square_Highlighted.png')" : "url('/images/Button_Square.png')", backgroundSize: '100% 100%', imageRendering: 'pixelated' }}
@@ -345,7 +349,7 @@ const SkinsView = memo(function SkinsView() {
const isActive = activeSkinId ? activeSkinId === skin.id : skinUrl === skin.url;
const isFocused = focusIndex === idx;
return (
<div key={skin.id} data-index={idx} tabIndex={0} onMouseEnter={() => setFocusIndex(idx)} className="flex flex-col items-center gap-1 w-32 outline-none">
<div key={skin.id} data-index={idx} tabIndex={0} onMouseEnter={() => setFocusIndex(idx)} onMouseLeave={() => setFocusIndex(null)} className="flex flex-col items-center gap-1 w-32 outline-none">
<div className="h-4">
{isActive && <span className="text-[#FFFF55] text-xs mc-text-shadow uppercase tracking-widest">Active</span>}
</div>
@@ -371,6 +375,7 @@ const SkinsView = memo(function SkinsView() {
<button
data-index={BACK_BUTTON_INDEX}
onMouseEnter={() => setFocusIndex(BACK_BUTTON_INDEX)}
onMouseLeave={() => setFocusIndex(null)}
onClick={() => { playBackSound(); setActiveView('main'); }}
className={`w-72 h-14 flex items-center justify-center transition-colors text-2xl mc-text-shadow mt-2 outline-none border-none hover:text-[#FFFF55] ${focusIndex === BACK_BUTTON_INDEX ? 'text-[#FFFF55]' : 'text-white'}`}
style={{ backgroundImage: focusIndex === BACK_BUTTON_INDEX ? "url('/images/button_highlighted.png')" : "url('/images/Button_Background.png')", backgroundSize: '100% 100%', imageRendering: 'pixelated' }}

View File

@@ -111,6 +111,7 @@ const ThemesView = memo(function ThemesView() {
<button
data-index="0"
onMouseEnter={() => setFocusIndex(0)}
onMouseLeave={() => setFocusIndex(null)}
onClick={() => {
playClickSound();
const currentIndex = totalPalettes.indexOf(currentTheme);
@@ -128,6 +129,7 @@ const ThemesView = memo(function ThemesView() {
<button
data-index="1"
onMouseEnter={() => setFocusIndex(1)}
onMouseLeave={() => setFocusIndex(null)}
onClick={handleImport}
className={`w-72 h-12 flex items-center justify-center px-4 relative transition-colors outline-none border-none hover:text-[#FFFF55] ${focusIndex === 1 ? "text-[#FFFF55]" : "text-white"}`}
style={getItemStyle(1)}
@@ -141,6 +143,7 @@ const ThemesView = memo(function ThemesView() {
<button
data-index="2"
onMouseEnter={() => setFocusIndex(2)}
onMouseLeave={() => setFocusIndex(null)}
onClick={() => {
playBackSound();
setActiveView("main");

View File

@@ -66,7 +66,7 @@ const VersionsView = memo(function VersionsView() {
setFocusCol((prev) => (prev > 0 ? prev - 1 : prev));
}
} else if (e.key === "Enter") {
if (focusRow < editions.length) {
if (focusRow >= 0 && focusRow < editions.length) {
const edition = editions[focusRow];
const isInstalled = installedVersions.includes(edition.id);
const isCustom = edition.id.startsWith("custom_") || edition.id.startsWith("instance_");
@@ -192,6 +192,10 @@ const VersionsView = memo(function VersionsView() {
setFocusCol(0);
}
}}
onMouseLeave={() => {
setFocusRow(-1);
setFocusCol(-1);
}}
onClick={() => {
if (!isPlaceholder && isInstalled) {
playClickSound();
@@ -273,6 +277,7 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(i);
setFocusCol(-1);
}}
onMouseLeave={() => setFocusCol(0)}
onClick={(e) => {
e.stopPropagation();
playClickSound();
@@ -328,6 +333,7 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(i);
setFocusCol(1);
}}
onMouseLeave={() => setFocusCol(0)}
onClick={(e) => {
e.stopPropagation();
if (!downloadingId) {
@@ -375,6 +381,7 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(i);
setFocusCol(2);
}}
onMouseLeave={() => setFocusCol(0)}
onClick={(e) => {
e.stopPropagation();
playClickSound();
@@ -407,6 +414,7 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(i);
setFocusCol(3);
}}
onMouseLeave={() => setFocusCol(0)}
onClick={(e) => {
e.stopPropagation();
playBackSound();
@@ -454,6 +462,7 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(i);
setFocusCol(isInstalled ? 4 : 2);
}}
onMouseLeave={() => setFocusCol(0)}
onClick={(e) => {
e.stopPropagation();
if (hasCustomImage) {
@@ -520,6 +529,7 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(i);
setFocusCol(isInstalled ? 5 : 3);
}}
onMouseLeave={() => setFocusCol(0)}
onClick={(e) => {
e.stopPropagation();
playClickSound();
@@ -559,6 +569,7 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(i);
setFocusCol(isInstalled ? 6 : 4);
}}
onMouseLeave={() => setFocusCol(0)}
onClick={(e) => {
e.stopPropagation();
playBackSound();
@@ -608,6 +619,10 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(editions.length);
setFocusCol(0);
}}
onMouseLeave={() => {
setFocusRow(-1);
setFocusCol(-1);
}}
onClick={() => {
playClickSound();
setIsImportModalOpen(true);
@@ -632,6 +647,10 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(editions.length + 1);
setFocusCol(0);
}}
onMouseLeave={() => {
setFocusRow(-1);
setFocusCol(-1);
}}
onClick={() => {
playClickSound();
setIsInstanceModalOpen(true);
@@ -656,6 +675,10 @@ const VersionsView = memo(function VersionsView() {
setFocusRow(editions.length + 2);
setFocusCol(0);
}}
onMouseLeave={() => {
setFocusRow(-1);
setFocusCol(-1);
}}
onClick={() => {
playBackSound();
setActiveView("main");