11'use client' ;
22
3- import {
4- Listbox ,
5- ListboxButton ,
6- ListboxOption ,
7- ListboxOptions ,
8- } from '@headlessui/react' ;
3+ import { Input , Popover , PopoverButton , PopoverPanel } from '@headlessui/react' ;
94import { ChevronUpDownIcon } from '@/components/icons/Icons' ;
105import { useConfig } from '@/contexts/ConfigContext' ;
11-
12- const speedOptions = [
13- { value : 0.5 , label : '0.5x' } ,
14- { value : 0.75 , label : '0.75x' } ,
15- { value : 1 , label : '1x' } ,
16- { value : 1.25 , label : '1.25x' } ,
17- { value : 1.5 , label : '1.5x' } ,
18- { value : 1.75 , label : '1.75x' } ,
19- { value : 2 , label : '2x' } ,
20- { value : 2.5 , label : '2.5x' } ,
21- { value : 3 , label : '3x' } ,
22- ] ;
6+ import { useCallback , useEffect , useState } from 'react' ;
237
248export const SpeedControl = ( { setSpeedAndRestart } : {
259 setSpeedAndRestart : ( speed : number ) => void ;
2610} ) => {
2711 const { voiceSpeed } = useConfig ( ) ;
12+ const [ localSpeed , setLocalSpeed ] = useState ( voiceSpeed ) ;
13+
14+ // Sync local speed with global state
15+ useEffect ( ( ) => {
16+ setLocalSpeed ( voiceSpeed ) ;
17+ } , [ voiceSpeed ] ) ;
18+
19+ // Handler for slider change (updates local state only)
20+ const handleSpeedChange = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
21+ setLocalSpeed ( parseFloat ( event . target . value ) ) ;
22+ } , [ ] ) ;
2823
29- // Use voiceSpeed as the source of truth
30- const currentSpeed = voiceSpeed ;
24+ // Handler for slider release
25+ const handleSpeedChangeComplete = useCallback ( ( ) => {
26+ if ( localSpeed !== voiceSpeed ) {
27+ setSpeedAndRestart ( localSpeed ) ;
28+ }
29+ } , [ localSpeed , voiceSpeed , setSpeedAndRestart ] ) ;
3130
3231 return (
33- < div className = "relative" >
34- < Listbox value = { currentSpeed } onChange = { setSpeedAndRestart } >
35- < ListboxButton className = "flex items-center space-x-0.5 sm:space-x-1 bg-transparent text-foreground text-xs sm:text-sm focus:outline-none cursor-pointer hover:bg-offbase rounded pl-1.5 sm:pl-2 pr-0.5 sm:pr-1 py-0.5 sm:py-1" >
36- < span > { currentSpeed } x</ span >
37- < ChevronUpDownIcon className = "h-2.5 w-2.5 sm:h-3 sm:w-3" />
38- </ ListboxButton >
39- < ListboxOptions anchor = 'top start' className = "absolute z-50 w-20 sm:w-24 overflow-auto rounded-lg bg-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" >
40- { speedOptions . map ( ( option ) => (
41- < ListboxOption
42- key = { option . value }
43- value = { option . value }
44- className = { ( { active, selected } ) =>
45- `relative cursor-pointer select-none py-0.5 px-1.5 sm:py-2 sm:px-3 ${ active ? 'bg-offbase' : '' } ${ selected ? 'font-medium' : '' } `
46- }
47- >
48- < span className = 'text-xs sm:text-sm' > { option . label } </ span >
49- </ ListboxOption >
50- ) ) }
51- </ ListboxOptions >
52- </ Listbox >
53- </ div >
32+ < Popover className = "relative" >
33+ < PopoverButton className = "flex items-center space-x-0.5 sm:space-x-1 bg-transparent text-foreground text-xs sm:text-sm focus:outline-none cursor-pointer hover:bg-offbase rounded pl-1.5 sm:pl-2 pr-0.5 sm:pr-1 py-0.5 sm:py-1" >
34+ < span > { Number . isInteger ( localSpeed ) ? localSpeed . toString ( ) : localSpeed . toFixed ( 1 ) } x</ span >
35+ < ChevronUpDownIcon className = "h-2.5 w-2.5 sm:h-3 sm:w-3" />
36+ </ PopoverButton >
37+ < PopoverPanel anchor = "top" className = "absolute z-50 bg-base p-3 rounded-md shadow-lg border border-offbase" >
38+ < div className = "flex flex-col space-y-2" >
39+ < div className = "flex justify-between" >
40+ < span className = "text-xs" > 0.5x</ span >
41+ < span className = "text-xs font-bold" > { Number . isInteger ( localSpeed ) ? localSpeed . toString ( ) : localSpeed . toFixed ( 1 ) } x</ span >
42+ < span className = "text-xs" > 3x</ span >
43+ </ div >
44+ < Input
45+ type = "range"
46+ min = "0.5"
47+ max = "3"
48+ step = "0.1"
49+ value = { localSpeed }
50+ onChange = { handleSpeedChange }
51+ onMouseUp = { handleSpeedChangeComplete }
52+ onKeyUp = { handleSpeedChangeComplete }
53+ onTouchEnd = { handleSpeedChangeComplete }
54+ className = "w-full bg-offbase rounded-lg appearance-none cursor-pointer accent-accent [&::-webkit-slider-runnable-track]:bg-offbase [&::-webkit-slider-runnable-track]:rounded-lg [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-moz-range-track]:bg-offbase [&::-moz-range-track]:rounded-lg [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent"
55+ />
56+ </ div >
57+ </ PopoverPanel >
58+ </ Popover >
5459 ) ;
5560}
0 commit comments