@manas-dev/sound-lab 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @manas-dev/sound-lab might be problematic. Click here for more details.

package/src/index.tsx ADDED
@@ -0,0 +1,211 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Music, X, Sparkles, Keyboard, Gamepad2, Box, Headphones, Zap, Droplets } from 'lucide-react';
3
+
4
+ export interface SoundProfile {
5
+ id: string;
6
+ name: string;
7
+ icon: any; // Lucide icon component
8
+ }
9
+
10
+ const DEFAULT_SOUNDS: SoundProfile[] = [
11
+ { id: 'glass', name: 'Glass', icon: Sparkles },
12
+ { id: 'thock', name: 'Thock', icon: Keyboard },
13
+ { id: 'console', name: 'Console', icon: Gamepad2 },
14
+ { id: 'minecraft', name: 'Minecraft', icon: Box },
15
+ { id: 'retro', name: 'Retro', icon: Gamepad2 },
16
+ { id: 'lofi', name: 'Lofi', icon: Headphones },
17
+ { id: 'mechanical', name: 'Clicky', icon: Keyboard },
18
+ { id: 'scifi', name: 'Sci-Fi', icon: Zap },
19
+ { id: 'bubble', name: 'Bubble', icon: Droplets },
20
+ ];
21
+
22
+ export interface SoundLabProps {
23
+ isSoundEnabled: boolean;
24
+ setIsSoundEnabled: (enabled: boolean) => void;
25
+ volume: number;
26
+ setVolume: (volume: number) => void;
27
+ soundProfile: string;
28
+ setSoundProfile: (profileId: string) => void;
29
+ isDark: boolean;
30
+ playSound?: (type: 'click' | 'hover' | 'switch') => void;
31
+ primaryColor?: string; // Hex color for active states
32
+ className?: string;
33
+ sounds?: SoundProfile[];
34
+ }
35
+
36
+ export const SoundLab: React.FC<SoundLabProps> = ({
37
+ isSoundEnabled,
38
+ setIsSoundEnabled,
39
+ volume,
40
+ setVolume,
41
+ soundProfile,
42
+ setSoundProfile,
43
+ isDark,
44
+ playSound = () => { },
45
+ primaryColor = '#3b82f6', // Default blue-500
46
+ className = '',
47
+ sounds = DEFAULT_SOUNDS
48
+ }) => {
49
+ const [isOpen, setIsOpen] = useState(false);
50
+ const [toastMessage, setToastMessage] = useState<string | null>(null);
51
+ const [showToast, setShowToast] = useState(false);
52
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
53
+
54
+ const showNotification = (message: string) => {
55
+ setToastMessage(message);
56
+ setShowToast(true);
57
+ setTimeout(() => setShowToast(false), 2000);
58
+ };
59
+
60
+ const handleMouseEnter = () => {
61
+ if (timeoutRef.current) {
62
+ clearTimeout(timeoutRef.current);
63
+ timeoutRef.current = null;
64
+ }
65
+ };
66
+
67
+ const handleMouseLeave = () => {
68
+ timeoutRef.current = setTimeout(() => {
69
+ setIsOpen(false);
70
+ }, 1000);
71
+ };
72
+
73
+ useEffect(() => {
74
+ return () => {
75
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
76
+ };
77
+ }, []);
78
+
79
+ // Helper for Hex transparency
80
+ const hexToRgba = (hex: string, alpha: number) => {
81
+ const r = parseInt(hex.slice(1, 3), 16);
82
+ const g = parseInt(hex.slice(3, 5), 16);
83
+ const b = parseInt(hex.slice(5, 7), 16);
84
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
85
+ };
86
+
87
+ return (
88
+ <div className={`relative ${className}`}>
89
+ {/* Toast Notification */}
90
+ {showToast && (
91
+ <div className={`fixed top-20 right-4 px-4 py-2 rounded-xl shadow-lg backdrop-blur-md border animate-in slide-in-from-top-2 fade-in duration-300 z-[60] whitespace-nowrap
92
+ ${isDark ? 'bg-zinc-800/90 border-zinc-700 text-white' : 'bg-white/90 border-slate-200 text-slate-800'}`}>
93
+ <span className="text-xs font-bold">{toastMessage}</span>
94
+ </div>
95
+ )}
96
+
97
+ {/* Trigger Button */}
98
+ <button
99
+ onClick={() => { setIsOpen(!isOpen); playSound('click'); }}
100
+ onMouseEnter={() => playSound('hover')}
101
+ className={`p-2 md:p-3 rounded-full transition-all duration-300
102
+ ${isOpen || isSoundEnabled
103
+ ? (isDark ? `bg-zinc-800` : `bg-white`)
104
+ : (isDark ? 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800/50' : 'text-slate-400 hover:text-slate-600 hover:bg-slate-50')
105
+ }`}
106
+ style={isOpen || isSoundEnabled ? { color: primaryColor } : {}}
107
+ aria-label="Open sound menu"
108
+ >
109
+ {isSoundEnabled ? <Music size={20} className="md:w-6 md:h-6 animate-pulse" /> : <Music size={20} className="md:w-6 md:h-6" />}
110
+ </button>
111
+
112
+ {/* Sound Lab Popup */}
113
+ {isOpen && (
114
+ <div
115
+ onMouseEnter={handleMouseEnter}
116
+ onMouseLeave={handleMouseLeave}
117
+ className={`absolute top-full right-0 mt-4 p-4 rounded-3xl border w-[280px] backdrop-blur-xl shadow-2xl animate-in slide-in-from-top-5 fade-in z-50
118
+ ${isDark ? 'bg-zinc-900/95 border-zinc-800' : 'bg-white/95 border-white/60'}`}>
119
+
120
+ <div className="flex justify-between items-center mb-4">
121
+ <div className="flex items-center gap-2">
122
+ <Music size={14} className={isDark ? 'text-zinc-400' : 'text-slate-400'} />
123
+ <span className={`text-xs font-bold uppercase tracking-wider ${isDark ? 'text-zinc-500' : 'text-slate-500'}`}>Sound Lab</span>
124
+ </div>
125
+ <button
126
+ onClick={() => { setIsOpen(false); playSound('click'); }}
127
+ className={`p-1 rounded-full ${isDark ? 'hover:bg-zinc-800' : 'hover:bg-slate-100'}`}
128
+ aria-label="Close sound menu"
129
+ >
130
+ <X size={14} className={isDark ? 'text-zinc-400' : 'text-slate-400'} />
131
+ </button>
132
+ </div>
133
+
134
+ {/* Master Toggle */}
135
+ <div className={`flex items-center justify-between p-3 rounded-xl mb-4 transition-colors
136
+ ${isDark ? 'bg-zinc-800/50' : 'bg-slate-50'}`}>
137
+ <span className={`text-xs font-medium ${isDark ? 'text-zinc-300' : 'text-slate-600'}`}>Master Audio</span>
138
+ <button
139
+ onClick={() => {
140
+ setIsSoundEnabled(!isSoundEnabled);
141
+ if (!isSoundEnabled) { // If turning ON, play a sound
142
+ setTimeout(() => playSound('switch'), 50);
143
+ showNotification('Sound Enabled');
144
+ } else {
145
+ showNotification('Sound Disabled');
146
+ }
147
+ }}
148
+ className={`relative w-10 h-6 rounded-full transition-colors duration-300 ${isDark ? 'bg-zinc-700' : 'bg-slate-300'}`}
149
+ style={{ backgroundColor: isSoundEnabled ? primaryColor : undefined }}
150
+ aria-label={isSoundEnabled ? "Disable sound" : "Enable sound"}
151
+ >
152
+ <span className={`absolute top-1 left-1 w-4 h-4 rounded-full bg-white transition-transform duration-300 ${isSoundEnabled ? 'translate-x-4' : 'translate-x-0'}`}></span>
153
+ </button>
154
+ </div>
155
+
156
+ {/* Volume Slider */}
157
+ <div className={`p-3 rounded-xl mb-4 transition-colors ${isDark ? 'bg-zinc-800/50' : 'bg-slate-50'}`}>
158
+ <div className="flex justify-between items-center mb-2">
159
+ <span className={`text-xs font-medium ${isDark ? 'text-zinc-300' : 'text-slate-600'}`}>Volume</span>
160
+ <span className="text-xs font-bold" style={{ color: primaryColor }}>{Math.round(volume * 100)}%</span>
161
+ </div>
162
+ <input
163
+ type="range"
164
+ min="0"
165
+ max="1"
166
+ step="0.05"
167
+ value={volume}
168
+ onChange={(e) => {
169
+ setVolume(parseFloat(e.target.value));
170
+ if (!isSoundEnabled) setIsSoundEnabled(true);
171
+ }}
172
+ className={`w-full h-1.5 rounded-full appearance-none cursor-pointer ${isDark ? 'bg-zinc-700' : 'bg-slate-300'} accent-current`}
173
+ style={{ accentColor: primaryColor }}
174
+ aria-label="Volume control"
175
+ />
176
+ </div>
177
+
178
+ <div className="grid grid-cols-3 gap-2">
179
+ {sounds.map((profile) => (
180
+ <button
181
+ key={profile.id}
182
+ onClick={() => {
183
+ setSoundProfile(profile.id);
184
+ if (!isSoundEnabled) setIsSoundEnabled(true);
185
+ setTimeout(() => playSound('click'), 50);
186
+ showNotification(`Profile: ${profile.name}`);
187
+ }}
188
+ className={`flex flex-col items-center gap-1 p-2 rounded-xl border transition-all duration-300
189
+ ${isDark ? 'bg-transparent border-transparent hover:bg-zinc-800' : 'bg-transparent border-transparent hover:bg-slate-100'}`}
190
+ style={soundProfile === profile.id ? {
191
+ backgroundColor: hexToRgba(primaryColor, 0.1),
192
+ borderColor: hexToRgba(primaryColor, 0.5)
193
+ } : {}}
194
+ >
195
+ <profile.icon size={16}
196
+ className={isDark ? 'text-zinc-500' : 'text-slate-400'}
197
+ style={soundProfile === profile.id ? { color: primaryColor } : {}}
198
+ />
199
+ <span className={`text-[10px] font-medium ${isDark ? 'text-zinc-500' : 'text-slate-500'}`}
200
+ style={soundProfile === profile.id ? { color: isDark ? 'white' : primaryColor } : {}}
201
+ >
202
+ {profile.name}
203
+ </span>
204
+ </button>
205
+ ))}
206
+ </div>
207
+ </div>
208
+ )}
209
+ </div>
210
+ );
211
+ };
@@ -0,0 +1,9 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [],
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [],
8
+ }
9
+
package/tsconfig.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "strict": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "module": "esnext",
17
+ "moduleResolution": "node",
18
+ "resolveJsonModule": true,
19
+ "isolatedModules": true,
20
+ "jsx": "react-jsx",
21
+ "declaration": true,
22
+ "rootDir": "src",
23
+ "outDir": "dist"
24
+ },
25
+ "include": [
26
+ "src"
27
+ ],
28
+ "exclude": [
29
+ "node_modules",
30
+ "dist"
31
+ ]
32
+ }