b57b24d051
Add all source files for a feature-rich, reusable video player built with React, TypeScript, and Vite. Includes core components, context, hooks, utilities, styles, demo app, and configuration files.
68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
import React, { useState, useRef, useCallback } from 'react'
|
|
import { usePlayerContext } from '../../contexts/PlayerContext'
|
|
import { VolumeUpIcon, VolumeDownIcon, VolumeMuteIcon } from '../../icons'
|
|
import './VolumeControl.css'
|
|
|
|
export const VolumeControl: React.FC = () => {
|
|
const { videoState, setVolume, toggleMute } = usePlayerContext()
|
|
const [showSlider, setShowSlider] = useState(false)
|
|
const timeoutRef = useRef<number>()
|
|
|
|
const handleMouseEnter = useCallback(() => {
|
|
if (timeoutRef.current) {
|
|
window.clearTimeout(timeoutRef.current)
|
|
}
|
|
setShowSlider(true)
|
|
}, [])
|
|
|
|
const handleMouseLeave = useCallback(() => {
|
|
timeoutRef.current = window.setTimeout(() => {
|
|
setShowSlider(false)
|
|
}, 300)
|
|
}, [])
|
|
|
|
const handleSliderChange = useCallback(
|
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const volume = parseFloat(e.target.value)
|
|
setVolume(volume)
|
|
},
|
|
[setVolume]
|
|
)
|
|
|
|
const VolumeIcon = videoState.muted ? VolumeMuteIcon : videoState.volume > 0.5 ? VolumeUpIcon : VolumeDownIcon
|
|
|
|
return (
|
|
<div
|
|
className="volume-control"
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
>
|
|
<button
|
|
className="control-button volume-button"
|
|
onClick={toggleMute}
|
|
aria-label={videoState.muted ? 'Unmute' : 'Mute'}
|
|
title={videoState.muted ? 'Unmute (M)' : 'Mute (M)'}
|
|
>
|
|
<VolumeIcon size={24} color="var(--player-text)" />
|
|
</button>
|
|
|
|
<div className={`volume-slider-container ${showSlider ? 'visible' : ''}`}>
|
|
<input
|
|
type="range"
|
|
min="0"
|
|
max="1"
|
|
step="0.01"
|
|
value={videoState.muted ? 0 : videoState.volume}
|
|
onChange={handleSliderChange}
|
|
className="volume-slider"
|
|
aria-label="Volume"
|
|
/>
|
|
<div
|
|
className="volume-slider-fill"
|
|
style={{ width: `${(videoState.muted ? 0 : videoState.volume) * 100}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|