Files
Psychotoxical-psysonic/src/components/playerBar/PlayerVolume.tsx
T
Frank Stellmacher 48c7b8b780 fix(playerbar): add Mute / Unmute tooltip on the speaker icon (#752)
The speaker button in the player bar toggled volume to 0 (or back to
the pre-mute level) but exposed no `data-tooltip`, so hovering it
showed nothing — users couldn't tell what the icon did. Add a tooltip
that switches between "Mute" and "Unmute" with the current state, and
mirror the same string on `aria-label` for screen readers. New
`player.mute` / `player.unmute` keys land across all 9 locales.
2026-05-17 14:26:19 +02:00

73 lines
2.5 KiB
TypeScript

import React from 'react';
import { Volume2, VolumeX } from 'lucide-react';
import type { TFunction } from 'i18next';
interface Props {
volume: number;
setVolume: (v: number) => void;
premuteVolumeRef: React.MutableRefObject<number>;
showVolPct: boolean;
setShowVolPct: (v: boolean) => void;
handleVolume: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleVolumeWheel: (e: React.WheelEvent<HTMLElement>) => void;
volumeStyle: React.CSSProperties;
inputId: string;
/** 'menu' adds the --menu modifier to the outer section (used inside the
* overflow menu so the layout adapts). Defaults to no modifier. */
sectionModifier?: 'menu';
/** 'menu-only' adds the --menu-only modifier to the slider wrap, widening
* it when the menu is in volume-only mode. */
wrapModifier?: 'menu-only';
t: TFunction;
}
export function PlayerVolume({
volume, setVolume, premuteVolumeRef, showVolPct, setShowVolPct,
handleVolume, handleVolumeWheel, volumeStyle, inputId,
sectionModifier, wrapModifier, t,
}: Props) {
const sectionClass = `player-volume-section${sectionModifier ? ` player-volume-section--${sectionModifier}` : ''}`;
const wrapClass = `player-volume-slider-wrap${wrapModifier ? ` player-volume-slider-wrap--${wrapModifier}` : ''}`;
return (
<div className={sectionClass}>
<button
className="player-btn player-btn-sm"
onClick={() => {
if (volume === 0) {
setVolume(premuteVolumeRef.current);
} else {
premuteVolumeRef.current = volume;
setVolume(0);
}
}}
aria-label={volume === 0 ? t('player.unmute') : t('player.mute')}
data-tooltip={volume === 0 ? t('player.unmute') : t('player.mute')}
style={{ color: 'var(--text-muted)', flexShrink: 0 }}
>
{volume === 0 ? <VolumeX size={16} /> : <Volume2 size={16} />}
</button>
<div className={wrapClass} onWheel={handleVolumeWheel}>
{showVolPct && (
<span className="player-volume-pct" style={{ left: `${volume * 100}%` }}>
{Math.round(volume * 100)}%
</span>
)}
<input
type="range"
id={inputId}
min={0}
max={1}
step={0.01}
value={volume}
onChange={handleVolume}
style={volumeStyle}
aria-label={t('player.volume')}
className="player-volume-slider"
onMouseEnter={() => setShowVolPct(true)}
onMouseLeave={() => setShowVolPct(false)}
/>
</div>
</div>
);
}