UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

760 lines (757 loc) 30.2 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { useReducedMotion } from '../../hooks/useReducedMotion.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { motion, AnimatePresence } from 'framer-motion'; import { AlertCircle, HelpCircle, Settings, X, CheckCircle, Pause, SkipBack, SkipForward, Volume1, MessageCircle, Mic, MicOff } from 'lucide-react'; import { useState, useEffect, useCallback } from 'react'; // Mock voice control hook - in real implementation this would use actual Web Speech API const useVoiceGlassControl = () => { const [isEnabled, setIsEnabled] = useState(false); const [isListening, setIsListening] = useState(false); const [isSupported, setIsSupported] = useState(false); const [transcript, setTranscript] = useState(""); const [interimTranscript, setInterimTranscript] = useState(""); const [lastCommand, setLastCommand] = useState(null); const [error, setError] = useState(null); const [wakeWordDetected, setWakeWordDetected] = useState(false); const [availableVoices, setAvailableVoices] = useState([]); // Mock implementation useEffect(() => { setIsSupported(typeof window !== "undefined" && ("webkitSpeechRecognition" in window || "SpeechRecognition" in window) && "speechSynthesis" in window); if (typeof window !== "undefined" && "speechSynthesis" in window) { const loadVoices = () => { const voices = speechSynthesis.getVoices(); setAvailableVoices(voices); }; loadVoices(); speechSynthesis.onvoiceschanged = loadVoices; } }, []); const enable = useCallback(() => { if (!isSupported) { setError("Voice control not supported in this browser"); return; } setIsEnabled(true); setError(null); }, [isSupported]); const disable = useCallback(() => { setIsEnabled(false); setIsListening(false); setWakeWordDetected(false); }, []); const toggle = useCallback(() => { if (isEnabled) { disable(); } else { enable(); } }, [isEnabled, enable, disable]); const speak = useCallback((text, voice) => { if (!isSupported) return; const utterance = new SpeechSynthesisUtterance(text); if (voice) { utterance.voice = voice; } speechSynthesis.speak(utterance); }, [isSupported]); const clearError = useCallback(() => { setError(null); }, []); const getAvailableVoices = useCallback(() => { return availableVoices; }, [availableVoices]); return { state: { isEnabled, isListening, isSupported, transcript, interimTranscript, lastCommand, error, wakeWordDetected, lastFeedback: lastCommand?.feedback }, actions: { enable, disable, toggle, speak, clearError, getAvailableVoices } }; }; // Mock voice commands helper const GlassVoiceCommands = () => { const commands = [`"Hey Genesis" - wake word to activate voice control`, `"Show navigation" - open main navigation menu`, `"Hide navigation" - close main navigation menu`, `"Go to home" - navigate to home page`, `"Go to settings" - navigate to settings page`, `"Scroll up" - scroll page up`, `"Scroll down" - scroll page down`, `"Play music" - start playing media`, `"Pause music" - pause current media`, `"Next track" - skip to next track`, `"Previous track" - go to previous track`, `"Increase volume" - turn up volume`, `"Decrease volume" - turn down volume`, `"Show help" - display voice commands help`, `"Hide help" - close help overlay`, `"Toggle theme" - switch between light and dark mode`, `"Show notifications" - open notifications panel`, `"Hide notifications" - close notifications panel`, `"Search for [term]" - search for specific content`, `"Open [app name]" - launch specific application`, `"Close [window]" - close specific window or panel`]; return commands; }; function VoiceGlassControl({ className, position = "top-left", autoEnable = false, showTranscript = true, onVoiceCommand, onToggleControls, wakeWord = "Hey Genesis", enableFeedback = true, showHelp = true, maxTranscriptLength = 100 }) { const prefersReducedMotion = useReducedMotion(); const { state, actions } = useVoiceGlassControl(); const [showSettings, setShowSettings] = useState(false); const [showHelpPanel, setShowHelpPanel] = useState(false); const [selectedVoice, setSelectedVoice] = useState(null); const [feedbackEnabled, setFeedbackEnabled] = useState(enableFeedback); const [currentVolume, setCurrentVolume] = useState(75); const [isPlaying, setIsPlaying] = useState(false); // Auto-enable on mount if requested useEffect(() => { if (autoEnable && state.isSupported && !state.isEnabled) { actions.enable(); } }, [autoEnable, state.isSupported, state.isEnabled, actions]); // Load available voices when speech synthesis is ready useEffect(() => { const loadVoices = () => { const voices = actions.getAvailableVoices(); if (voices.length > 0 && !selectedVoice) { // Prefer English voices const englishVoice = voices.find(voice => voice.lang.startsWith("en") && voice.localService) || voices.find(voice => voice.lang.startsWith("en")) || voices[0]; setSelectedVoice(englishVoice); } }; loadVoices(); if (typeof window !== "undefined" && window.speechSynthesis) { window.speechSynthesis.onvoiceschanged = loadVoices; } return () => { if (typeof window !== "undefined" && window.speechSynthesis) { window.speechSynthesis.onvoiceschanged = null; } }; }, [actions, selectedVoice]); // Handle voice command callback useEffect(() => { if (state.lastCommand && onVoiceCommand) { onVoiceCommand(state.lastCommand.originalText, state.lastCommand); } }, [state.lastCommand, onVoiceCommand]); // Handle controls visibility useEffect(() => { if (state.lastCommand?.type === "TOGGLE_CONTROLS" && onToggleControls) { onToggleControls(state.lastCommand.parameters?.show || false); } }, [state.lastCommand, onToggleControls]); // Mock voice command processing const processVoiceCommand = useCallback(transcript => { const command = transcript.toLowerCase().trim(); // Navigation commands if (command.includes("show navigation") || command.includes("open menu")) { setLastCommand({ type: "NAVIGATION", action: "show", feedback: "Navigation menu opened", parameters: { target: "navigation" } }); } else if (command.includes("hide navigation") || command.includes("close menu")) { setLastCommand({ type: "NAVIGATION", action: "hide", feedback: "Navigation menu closed", parameters: { target: "navigation" } }); } else if (command.includes("go to home")) { setLastCommand({ type: "NAVIGATION", action: "navigate", feedback: "Navigating to home", parameters: { target: "home" } }); } else if (command.includes("go to settings")) { setLastCommand({ type: "NAVIGATION", action: "navigate", feedback: "Opening settings", parameters: { target: "settings" } }); } // Media commands else if (command.includes("play music") || command.includes("play")) { setIsPlaying(true); setLastCommand({ type: "MEDIA", action: "play", feedback: "Playing music", parameters: { target: "music" } }); } else if (command.includes("pause music") || command.includes("pause")) { setIsPlaying(false); setLastCommand({ type: "MEDIA", action: "pause", feedback: "Music paused", parameters: { target: "music" } }); } else if (command.includes("next track") || command.includes("next")) { setLastCommand({ type: "MEDIA", action: "next", feedback: "Next track", parameters: { target: "music" } }); } else if (command.includes("previous track") || command.includes("previous")) { setLastCommand({ type: "MEDIA", action: "previous", feedback: "Previous track", parameters: { target: "music" } }); } // Volume commands else if (command.includes("increase volume") || command.includes("volume up")) { const newVolume = Math.min(100, currentVolume + 10); setCurrentVolume(newVolume); setLastCommand({ type: "VOLUME", action: "increase", feedback: `Volume set to ${newVolume}%`, parameters: { volume: newVolume } }); } else if (command.includes("decrease volume") || command.includes("volume down")) { const newVolume = Math.max(0, currentVolume - 10); setCurrentVolume(newVolume); setLastCommand({ type: "VOLUME", action: "decrease", feedback: `Volume set to ${newVolume}%`, parameters: { volume: newVolume } }); } // UI commands else if (command.includes("toggle theme")) { setLastCommand({ type: "UI", action: "toggle_theme", feedback: "Theme toggled", parameters: { target: "theme" } }); } else if (command.includes("show help")) { setShowHelpPanel(true); setLastCommand({ type: "UI", action: "show_help", feedback: "Help panel opened", parameters: { target: "help" } }); } else if (command.includes("hide help")) { setShowHelpPanel(false); setLastCommand({ type: "UI", action: "hide_help", feedback: "Help panel closed", parameters: { target: "help" } }); } else if (command.includes("show notifications")) { setLastCommand({ type: "UI", action: "show_notifications", feedback: "Notifications panel opened", parameters: { target: "notifications" } }); } else if (command.includes("hide notifications")) { setLastCommand({ type: "UI", action: "hide_notifications", feedback: "Notifications panel closed", parameters: { target: "notifications" } }); } // Help command else if (command.includes("what can i say") || command.includes("help")) { setShowHelpPanel(true); setLastCommand({ type: "HELP", action: "show_commands", feedback: "Showing available voice commands", parameters: { target: "commands" } }); } // Unknown command else { setLastCommand({ type: "UNKNOWN", action: "unknown", feedback: `I didn't understand: "${transcript}"`, parameters: { originalText: transcript } }); } }, [currentVolume]); // Mock function to set last command (would be handled by the hook in real implementation) const setLastCommand = command => { // In real implementation, this would update the hook's state console.log("Voice command processed:", command); if (feedbackEnabled && command.feedback) { actions.speak(command.feedback, selectedVoice || undefined); } }; const positionClasses = { "bottom-left": "bottom-4 left-4", "bottom-right": "bottom-4 right-4", "top-left": "top-4 left-4", "top-right": "top-4 right-4" }; const getStateIcon = () => { if (!state.isSupported) return jsx(AlertCircle, { className: 'h-5 w-5 text-primary' }); if (state.isListening) return jsx(Mic, { className: 'h-5 w-5 text-primary' }); if (state.wakeWordDetected) return jsx(Mic, { className: 'h-5 w-5 text-primary animate-pulse' }); if (state.error) return jsx(AlertCircle, { className: 'h-5 w-5 text-primary' }); if (state.isEnabled) return jsx(MicOff, { className: 'h-5 w-5 glass-text-secondary' }); return jsx(MicOff, { className: 'h-5 w-5 glass-text-secondary' }); }; const getStateColor = () => { if (!state.isSupported) return "border-red-400 bg-red-400/10"; if (state.isListening) return "border-blue-400 bg-blue-400/10"; if (state.wakeWordDetected) return "border-green-400 bg-green-400/10"; if (state.error) return "border-red-400 bg-red-400/10"; return "border-gray-400 bg-gray-400/10"; }; const getStateDescription = () => { if (!state.isSupported) return "Voice control not supported"; if (state.isListening) return `Listening for "${wakeWord}"...`; if (state.wakeWordDetected) return "Wake word detected! Speak your command..."; if (state.error) return state.error; if (state.isEnabled) return "Voice control active - say wake word to begin"; return "Voice control inactive"; }; const handleTestCommand = () => { const testCommands = ["show navigation", "play music", "increase volume", "toggle theme", "show help"]; const randomCommand = testCommands[Math.floor(Math.random() * testCommands.length)]; processVoiceCommand(randomCommand); }; if (!state.isSupported) { return jsx("div", { className: cn("fixed z-50", positionClasses[position], className), children: jsx(motion.div, { className: "glass-glass-glass-backdrop-blur-lg glass-border glass-border-red/20 glass-surface-red/10 glass-p-3 glass-radius-lg glass-contrast-guard", whileHover: { scale: 1.05 }, children: jsxs("div", { className: 'glass-flex glass-items-center glass-gap-2 text-primary', children: [jsx(AlertCircle, { className: 'h-4 w-4' }), jsx("span", { className: "glass-text-sm", children: "Voice control not supported" })] }) }) }); } return jsx("div", { className: cn("fixed z-50", positionClasses[position], className), children: jsxs("div", { className: "glass-flex glass-flex-col glass-gap-2", children: [jsxs(motion.div, { className: cn("glass-glass-backdrop-blur-lg border p-3 rounded-lg transition-all duration-300 glass-contrast-guard", getStateColor()), whileHover: { scale: 1.05 }, children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-3", children: [jsx("button", { onClick: actions.toggle, className: 'glass-flex glass-items-center glass-justify-center w-10 h-10 glass-radius-full glass-surface-subtle/10 hover:glass-surface-subtle/20 transition-colors glass-focus glass-touch-target glass-contrast-guard', children: getStateIcon() }), jsxs("div", { className: 'glass-flex-1 min-glass-w-0', children: [jsx("div", { className: 'glass-text-sm font-medium text-primary', children: "Voice Control" }), jsx("div", { className: 'glass-text-xs text-primary/70 truncate', children: getStateDescription() })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-1", children: [showHelp && jsx("button", { onClick: () => setShowHelpPanel(true), className: 'glass-p-1.5 hover:glass-surface-subtle/10 glass-radius transition-colors glass-focus glass-touch-target glass-contrast-guard', title: "Help", children: jsx(HelpCircle, { className: 'h-4 w-4 text-primary/70' }) }), jsx("button", { onClick: () => setShowSettings(true), className: 'glass-p-1.5 hover:glass-surface-subtle/10 glass-radius transition-colors glass-focus glass-touch-target glass-contrast-guard', title: "Settings", children: jsx(Settings, { className: 'h-4 w-4 text-primary/70' }) })] })] }), state.wakeWordDetected && jsx(motion.div, { initial: { opacity: 0, y: -10 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, exit: { opacity: 0, y: -10 }, className: 'mt-2 glass-p-2 glass-surface-green/20 glass-radius glass-text-xs text-primary text-center', children: "\uD83C\uDFA4 Wake word detected - speak your command now!" }), state.error && jsx(motion.div, { initial: { opacity: 0, y: -10 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, className: 'mt-2 glass-p-2 glass-surface-red/20 glass-radius glass-text-xs text-primary', children: jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsx("span", { children: state.error }), jsx("button", { onClick: actions.clearError, className: 'glass-p-0.5 hover:glass-surface-red/20 glass-radius glass-focus glass-touch-target glass-contrast-guard', children: jsx(X, { className: 'h-3 w-3' }) })] }) }), showTranscript && (state.transcript || state.interimTranscript) && jsx(motion.div, { initial: { opacity: 0, y: -10 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, className: 'mt-2 glass-p-2 glass-surface-subtle/10 glass-radius glass-text-xs', children: jsxs("div", { className: 'text-primary font-medium', children: [state.transcript, jsx("span", { className: 'text-primary/50 italic', children: state.interimTranscript })] }) }), state.lastFeedback && jsx(motion.div, { initial: { opacity: 0, y: -10 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, className: 'mt-2 glass-p-2 glass-surface-blue/20 glass-radius glass-text-xs text-primary', children: jsxs("div", { className: "glass-flex glass-items-start glass-gap-2", children: [jsx(CheckCircle, { className: 'h-3 w-3 glass-mt-0-5 glass-flex-shrink-0' }), jsx("span", { children: state.lastFeedback })] }) }), isPlaying && jsxs(motion.div, { initial: { opacity: 0, y: -10 }, animate: prefersReducedMotion ? {} : { opacity: 1, y: 0 }, className: 'mt-2 glass-flex glass-items-center glass-gap-2 glass-p-2 glass-surface-subtle/10 glass-radius', children: [jsx("button", { onClick: () => setIsPlaying(false), className: 'glass-p-1 hover:glass-surface-subtle/20 glass-radius glass-focus glass-touch-target glass-contrast-guard', title: "Pause", children: jsx(Pause, { className: 'h-3 w-3 text-primary' }) }), jsx("button", { onClick: () => processVoiceCommand("previous track"), className: 'glass-p-1 hover:glass-surface-subtle/20 glass-radius glass-focus glass-touch-target glass-contrast-guard', title: "Previous", children: jsx(SkipBack, { className: 'h-3 w-3 text-primary' }) }), jsx("button", { onClick: () => processVoiceCommand("next track"), className: 'glass-p-1 hover:glass-surface-subtle/20 glass-radius glass-focus glass-touch-target glass-contrast-guard', title: "Next", children: jsx(SkipForward, { className: 'h-3 w-3 text-primary' }) }), jsxs("div", { className: "glass-flex-1 glass-flex glass-items-center glass-gap-2", children: [jsx(Volume1, { className: 'h-3 w-3 text-primary/70' }), jsx("div", { className: 'glass-flex-1 glass-surface-subtle/20 glass-radius-full h-1', children: jsx("div", { className: 'glass-surface-blue h-1 glass-radius-full transition-all', style: { width: `${currentVolume}%` } }) }), jsxs("span", { className: 'glass-text-xs text-primary/70', children: [currentVolume, "%"] })] })] })] }), jsx(AnimatePresence, { children: showSettings && jsx(motion.div, { initial: { opacity: 0, scale: 0.95, y: 20 }, animate: prefersReducedMotion ? {} : { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.95, y: 20 }, children: jsxs("div", { className: 'glass-glass-glass-backdrop-blur-lg glass-border glass-border-white/20 glass-surface-subtle/10 glass-p-4 glass-radius-lg w-80 glass-contrast-guard', children: [jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mb-3', children: [jsx("h3", { className: 'font-medium text-primary', children: "Voice Settings" }), jsx("button", { onClick: () => setShowSettings(false), className: 'glass-p-1 hover:glass-surface-subtle/10 glass-radius transition-colors glass-focus glass-touch-target glass-contrast-guard', children: jsx(X, { className: 'h-4 w-4 text-primary/70' }) })] }), jsxs("div", { className: 'space-y-4', children: [jsxs("div", { children: [jsx("label", { className: 'block glass-text-sm font-medium text-primary mb-2', children: "Wake Word" }), jsx("input", { type: "text", value: wakeWord, readOnly: true, className: 'glass-w-full glass-p-2 glass-surface-subtle/10 glass-border glass-border-white/20 glass-radius text-primary glass-text-sm glass-focus glass-touch-target glass-contrast-guard' }), jsx("div", { className: 'glass-text-xs text-primary/60 mt-1', children: "Say this to activate voice control" })] }), jsxs("div", { children: [jsx("label", { className: 'block glass-text-sm font-medium text-primary mb-2', children: "Voice" }), jsx("select", { value: selectedVoice?.name || "", onChange: e => { const voice = actions.getAvailableVoices().find(v => v.name === e.target.value); setSelectedVoice(voice || null); }, className: 'glass-w-full glass-p-2 glass-surface-subtle/10 glass-border glass-border-white/20 glass-radius text-primary glass-text-sm glass-focus glass-touch-target glass-contrast-guard', children: actions.getAvailableVoices().map(voice => jsxs("option", { value: voice.name, className: "glass-surface-primary", children: [voice.name, " (", voice.lang, ")"] }, voice.name)) })] }), jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsxs("div", { children: [jsx("div", { className: 'glass-text-sm font-medium text-primary', children: "Voice Feedback" }), jsx("div", { className: 'glass-text-xs text-primary/60', children: "Speak command confirmations" })] }), jsx("button", { onClick: () => setFeedbackEnabled(!feedbackEnabled), className: cn("w-10 h-6 rounded-full transition-colors relative glass-focus glass-touch-target glass-contrast-guard", feedbackEnabled ? "bg-blue-500" : "bg-white/20"), children: jsx("div", { className: `absolute top-0.5 w-5 h-5 bg-white rounded-full transition-transform ${feedbackEnabled ? "transform translate-x-4" : "translate-x-0.5"}` }) })] }), jsxs("div", { children: [jsx("label", { className: 'block glass-text-sm font-medium text-primary mb-2', children: "Test Commands" }), jsx("button", { onClick: handleTestCommand, className: 'glass-w-full glass-p-2 glass-surface-blue/20 hover:glass-surface-blue/30 glass-border glass-border-blue/30 glass-radius text-primary glass-text-sm transition-colors glass-focus glass-touch-target glass-contrast-guard', children: "Try Random Command" }), jsx("button", { onClick: () => actions.speak("Voice control is working correctly", selectedVoice || undefined), className: 'glass-w-full glass-p-2 glass-surface-green/20 hover:glass-surface-green/30 glass-border glass-border-green/30 glass-radius text-primary glass-text-sm transition-colors mt-2 glass-focus glass-touch-target glass-contrast-guard', children: "Test Voice Output" })] }), jsxs("div", { className: 'pt-3 glass-border-t glass-border-white/10 space-y-1 glass-text-xs text-primary/60', children: [jsxs("div", { children: ["Status: ", state.isEnabled ? "Enabled" : "Disabled"] }), jsxs("div", { children: ["Listening: ", state.isListening ? "Active" : "Inactive"] }), jsxs("div", { children: ["Available voices: ", actions.getAvailableVoices().length] }), jsxs("div", { children: ["Volume: ", currentVolume, "%"] })] })] })] }) }) }), jsx(AnimatePresence, { children: showHelpPanel && jsx(motion.div, { initial: { opacity: 0, scale: 0.95, y: 20 }, animate: prefersReducedMotion ? {} : { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.95, y: 20 }, children: jsxs("div", { className: 'glass-glass-glass-backdrop-blur-lg glass-border glass-border-white/20 glass-surface-subtle/10 glass-p-4 glass-radius-lg w-96 max-h-80 overflow-y-auto glass-contrast-guard', children: [jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mb-3', children: [jsx("h3", { className: 'font-medium text-primary', children: "Voice Commands" }), jsx("button", { onClick: () => setShowHelpPanel(false), className: 'glass-p-1 hover:glass-surface-subtle/10 glass-radius transition-colors glass-focus glass-touch-target glass-contrast-guard', children: jsx(X, { className: 'h-4 w-4 text-primary/70' }) })] }), jsxs("div", { className: 'space-y-3', children: [jsxs("div", { className: 'glass-text-sm text-primary/80', children: ["Start commands with", " ", jsxs("span", { className: 'font-mono glass-surface-subtle/20 glass-px-1 glass-radius', children: ["\"", wakeWord, "\""] }), ":"] }), jsx("div", { className: 'space-y-2', children: GlassVoiceCommands().slice(0, 10).map((command, index) => jsx("div", { className: "glass-p-2 glass-surface-subtle/5 glass-radius glass-text-sm", children: jsxs("div", { className: 'text-primary font-mono', children: ["\"", command, "\""] }) }, index)) }), jsx("div", { className: 'pt-3 glass-border-t glass-border-white/10', children: jsxs("div", { className: 'glass-text-xs text-primary/60', children: [jsxs("div", { className: 'glass-flex glass-items-center glass-gap-2 mb-1', children: [jsx(MessageCircle, { className: 'h-3 w-3' }), jsx("span", { children: "Tips:" })] }), jsxs("ul", { className: 'list-disc list-inside space-y-1 ml-5', children: [jsx("li", { children: "Speak clearly and at normal volume" }), jsx("li", { children: "Wait for the wake word confirmation" }), jsx("li", { children: "Use natural language variations" }), jsx("li", { children: "Check your microphone permissions" })] })] }) })] })] }) }) })] }) }); } export { GlassVoiceCommands, VoiceGlassControl as default }; //# sourceMappingURL=VoiceGlassControl.js.map