feat: intégration VU-mètre circulaire autour du bouton PTT
NOUVEAU DESIGN : - VU-mètre transformé en anneau SVG autour du bouton PTT - Cercle progressif (0-360°) selon le niveau audio - Couleurs dynamiques selon le niveau : - Vert (0-75%) : audio normal - Orange (75-90%) : niveau élevé - Rouge (90-100%) : saturation/danger avec effet glow - Bleu : mode talking (micro actif) AVANTAGES : - Beaucoup plus discret et élégant - Intégré visuellement au bouton principal - Pas d'encombrement UI supplémentaire - Contextuellement pertinent (niveau autour du contrôle) TECHNIQUE : - SVG avec stroke-dasharray pour l'arc progressif - Transition fluide 0.1s sur le niveau - Drop-shadow dynamique selon couleur - Responsive (adapté mobile/paysage) - z-index stratifié (anneau derrière, bouton devant) SUPPRESSION : - Ancien composant AudioIndicator.jsx retiré de App.jsx - Garde plus d'espace pour UserList et GroupSelector 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+2
-5
@@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
|
||||
import useLiveKit from './hooks/useLiveKit';
|
||||
import PTTButton from './components/PTTButton';
|
||||
import UserList from './components/UserList';
|
||||
import AudioIndicator from './components/AudioIndicator';
|
||||
import GroupSelector from './components/GroupSelector';
|
||||
import './App.css';
|
||||
|
||||
@@ -241,14 +240,12 @@ function App() {
|
||||
{/* Liste des participants */}
|
||||
<UserList participants={participants} />
|
||||
|
||||
{/* Indicateur audio */}
|
||||
<AudioIndicator level={audioLevel} isTalking={isTalking} />
|
||||
|
||||
{/* Bouton PTT principal */}
|
||||
{/* Bouton PTT principal avec VU-mètre intégré */}
|
||||
<PTTButton
|
||||
isTalking={isTalking}
|
||||
onPressStart={startTalking}
|
||||
onPressEnd={stopTalking}
|
||||
audioLevel={audioLevel}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,24 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Wrapper bouton + anneau VU-mètre */
|
||||
.ptt-button-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Anneau VU-mètre (SVG) */
|
||||
.audio-ring {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.ptt-button {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
@@ -27,6 +45,7 @@
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 1; /* Au-dessus de l'anneau */
|
||||
|
||||
/* Mobile touch optimizations */
|
||||
touch-action: none;
|
||||
@@ -189,6 +208,11 @@
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.audio-ring {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.ptt-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
@@ -210,6 +234,11 @@
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.audio-ring {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.ptt-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
@@ -7,8 +7,9 @@ import './PTTButton.css';
|
||||
* Modes :
|
||||
* - PTT classique : maintenir pour parler
|
||||
* - Mode continu (lock) : glisser vers le haut pendant qu'on parle
|
||||
* Inclut VU-mètre intégré (anneau autour du bouton)
|
||||
*/
|
||||
export default function PTTButton({ isTalking, onPressStart, onPressEnd }) {
|
||||
export default function PTTButton({ isTalking, onPressStart, onPressEnd, audioLevel = 0 }) {
|
||||
const buttonRef = useRef(null);
|
||||
const isPressingRef = useRef(false);
|
||||
const [isLockMode, setIsLockMode] = useState(false);
|
||||
@@ -241,6 +242,25 @@ export default function PTTButton({ isTalking, onPressStart, onPressEnd }) {
|
||||
}
|
||||
};
|
||||
|
||||
// Calculer le niveau audio normalisé (0-100)
|
||||
const normalizedLevel = Math.min(100, Math.max(0, audioLevel));
|
||||
|
||||
// Convertir le niveau en angle pour le cercle SVG (0-360°)
|
||||
const levelAngle = (normalizedLevel / 100) * 360;
|
||||
|
||||
// Calculer le dasharray pour l'arc SVG
|
||||
const radius = 130; // Rayon du cercle VU-mètre
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const dashOffset = circumference - (levelAngle / 360) * circumference;
|
||||
|
||||
// Déterminer la couleur selon le niveau
|
||||
const getAudioColor = () => {
|
||||
if (normalizedLevel > 90) return '#ef4444'; // Danger (rouge)
|
||||
if (normalizedLevel > 75) return '#f59e0b'; // Warning (orange)
|
||||
if (isTalking) return '#3b82f6'; // Talking (bleu)
|
||||
return '#10b981'; // Normal (vert)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ptt-container">
|
||||
{/* Zone de drag vers le haut (indicateur visuel) */}
|
||||
@@ -253,6 +273,44 @@ export default function PTTButton({ isTalking, onPressStart, onPressEnd }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Conteneur bouton + VU-mètre */}
|
||||
<div className="ptt-button-wrapper">
|
||||
{/* VU-mètre circulaire (SVG) */}
|
||||
<svg
|
||||
className="audio-ring"
|
||||
width="280"
|
||||
height="280"
|
||||
viewBox="0 0 280 280"
|
||||
>
|
||||
{/* Cercle de fond (gris) */}
|
||||
<circle
|
||||
cx="140"
|
||||
cy="140"
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke="rgba(255, 255, 255, 0.1)"
|
||||
strokeWidth="8"
|
||||
/>
|
||||
|
||||
{/* Cercle de niveau audio (coloré) */}
|
||||
<circle
|
||||
cx="140"
|
||||
cy="140"
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={getAudioColor()}
|
||||
strokeWidth="8"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={dashOffset}
|
||||
transform="rotate(-90 140 140)"
|
||||
style={{
|
||||
transition: 'stroke-dashoffset 0.1s ease, stroke 0.2s ease',
|
||||
filter: normalizedLevel > 0 ? `drop-shadow(0 0 8px ${getAudioColor()})` : 'none'
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* Bouton PTT principal */}
|
||||
<button
|
||||
ref={buttonRef}
|
||||
@@ -295,6 +353,7 @@ export default function PTTButton({ isTalking, onPressStart, onPressEnd }) {
|
||||
: 'Maintenir pour parler'}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="ptt-hint">
|
||||
{isLockMode
|
||||
|
||||
Reference in New Issue
Block a user