|
1 | 1 | "use client"; |
2 | 2 |
|
3 | | -import { useMemo } from "react"; |
| 3 | +import { useMemo, type ReactNode } from "react"; |
4 | 4 | import { CiPlay1, FaPause, FiRefreshCw, FiRepeat } from "@/shared/ui/fallback-icons"; |
5 | 5 | import { renderAudioControlIcon, useAudioPlayer } from "@/features/audio-player"; |
6 | 6 | import type { AudioTrackConfig, LanguageCode } from "@/entities/docs"; |
@@ -34,6 +34,34 @@ interface AudioRouteControlsProps { |
34 | 34 | captions?: CaptionsByLanguage; |
35 | 35 | } |
36 | 36 |
|
| 37 | +interface ControlButtonProps { |
| 38 | + label: string; |
| 39 | + onClick: () => void; |
| 40 | + /** Marks the button as on (playing, looping) for styling. */ |
| 41 | + active?: boolean; |
| 42 | + /** Toggle state for assistive technology (loop). */ |
| 43 | + pressed?: boolean; |
| 44 | + testId?: string; |
| 45 | + children: ReactNode; |
| 46 | +} |
| 47 | + |
| 48 | +function ControlButton({ label, onClick, active, pressed, testId, children }: Readonly<ControlButtonProps>) { |
| 49 | + return ( |
| 50 | + <button |
| 51 | + type="button" |
| 52 | + className={styles.audioRouteControlButton} |
| 53 | + onClick={onClick} |
| 54 | + aria-label={label} |
| 55 | + aria-pressed={pressed} |
| 56 | + title={label} |
| 57 | + data-active={active || undefined} |
| 58 | + data-testid={testId} |
| 59 | + > |
| 60 | + {children} |
| 61 | + </button> |
| 62 | + ); |
| 63 | +} |
| 64 | + |
37 | 65 | export function AudioRouteControls({ |
38 | 66 | audioType, |
39 | 67 | pathAudio, |
@@ -77,40 +105,18 @@ export function AudioRouteControls({ |
77 | 105 | return ( |
78 | 106 | <div className={styles.audioRoutePlayer} aria-label={playStatusLabel}> |
79 | 107 | <div className={styles.audioRouteControls}> |
80 | | - <button |
81 | | - type="button" |
82 | | - className={styles.audioRouteControlButton} |
83 | | - onClick={togglePlay} |
84 | | - aria-label={playStatusLabel} |
85 | | - title={playStatusLabel} |
86 | | - data-active={playing || undefined} |
87 | | - data-testid="audio-route-toggle" |
88 | | - > |
| 108 | + <ControlButton label={playStatusLabel} onClick={togglePlay} active={playing} testId="audio-route-toggle"> |
89 | 109 | {renderAudioControlIcon( |
90 | 110 | playing ? controls.pauseIcon : controls.playIcon, |
91 | 111 | playing ? <FaPause aria-hidden /> : <CiPlay1 aria-hidden />, |
92 | 112 | )} |
93 | | - </button> |
94 | | - <button |
95 | | - type="button" |
96 | | - className={styles.audioRouteControlButton} |
97 | | - onClick={restart} |
98 | | - aria-label={controls.restartLabel} |
99 | | - title={controls.restartLabel} |
100 | | - > |
| 113 | + </ControlButton> |
| 114 | + <ControlButton label={controls.restartLabel} onClick={restart}> |
101 | 115 | {renderAudioControlIcon(controls.restartIcon, <FiRefreshCw aria-hidden />)} |
102 | | - </button> |
103 | | - <button |
104 | | - type="button" |
105 | | - className={styles.audioRouteControlButton} |
106 | | - onClick={toggleLoop} |
107 | | - aria-label={loopStatusLabel} |
108 | | - aria-pressed={loopEnabled} |
109 | | - title={loopStatusLabel} |
110 | | - data-active={loopEnabled || undefined} |
111 | | - > |
| 116 | + </ControlButton> |
| 117 | + <ControlButton label={loopStatusLabel} onClick={toggleLoop} active={loopEnabled} pressed={loopEnabled}> |
112 | 118 | {renderAudioControlIcon(loopEnabled ? controls.loopOnIcon : controls.loopOffIcon, <FiRepeat aria-hidden />)} |
113 | | - </button> |
| 119 | + </ControlButton> |
114 | 120 | </div> |
115 | 121 | {isNativeTrack && audioSrc && ( |
116 | 122 | <audio |
|
0 commit comments