Skip to content

Commit e52ddf9

Browse files
committed
Add playable sounds
Signed-off-by: Martin Sulikowski <vLuckyyy.biznes@gmail.com>
1 parent ccfcf04 commit e52ddf9

File tree

6 files changed

+471
-76
lines changed

6 files changed

+471
-76
lines changed

app/notification-generator/NotificationForm.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"use client";
22

3-
import { useState, useCallback } from "react";
3+
import { useState, useCallback, useRef } from "react";
44
import { motion, AnimatePresence } from "framer-motion";
55
import { NotificationConfig, TabType, FieldType } from "./types";
66
import { Tab } from "./form/Tab";
77
import { ChatTab } from "./form/ChatTab";
88
import { ActionBarTab } from "./form/ActionBarTab";
99
import { TitleTab } from "./form/TitleTab";
10-
import { SoundTab } from "./form/SoundTab";
10+
import { SoundTab, SoundTabRef } from "./form/SoundTab";
1111
import { AdvancedTab } from "./form/AdvancedTab";
1212
import { validateField, validateForm } from "./form/validation";
1313

@@ -22,15 +22,14 @@ export function NotificationForm({
2222
}: NotificationFormProps) {
2323
const [activeTab, setActiveTab] = useState<TabType>("chat");
2424
const [errors, setErrors] = useState<Record<string, string>>({});
25-
25+
const soundTabRef = useRef<SoundTabRef>(null);
2626

2727
const handleChange = useCallback((field: FieldType, value: string | boolean) => {
2828
setNotification({
2929
...notification,
3030
[field]: value,
3131
});
3232

33-
3433
if (["fadeIn", "stay", "fadeOut", "volume", "pitch", "sound"].includes(field)) {
3534
const error = validateField(field, value as string);
3635
setErrors((prev) => ({
@@ -45,8 +44,10 @@ export function NotificationForm({
4544
}
4645
}, [notification, setNotification, errors]);
4746

48-
4947
const resetForm = useCallback(() => {
48+
49+
soundTabRef.current?.stopSound();
50+
5051
setNotification({
5152
chat: "",
5253
actionbar: "",
@@ -64,7 +65,6 @@ export function NotificationForm({
6465
setErrors({});
6566
}, [setNotification]);
6667

67-
6868
const renderTabContent = () => {
6969
switch (activeTab) {
7070
case "chat":
@@ -74,7 +74,7 @@ export function NotificationForm({
7474
case "title":
7575
return <TitleTab notification={notification} onChange={handleChange} errors={errors} />;
7676
case "sound":
77-
return <SoundTab notification={notification} onChange={handleChange} errors={errors} />;
77+
return <SoundTab ref={soundTabRef} notification={notification} onChange={handleChange} errors={errors} />;
7878
case "advanced":
7979
return <AdvancedTab notification={notification} onChange={handleChange} />;
8080
default:
@@ -122,7 +122,7 @@ export function NotificationForm({
122122
transition={{ duration: 0.2, delay: 0.1 }}
123123
>
124124
<motion.button
125-
className="rounded-md bg-gray-200 px-4 py-2 text-gray-800 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
125+
className="rounded-md bg-gray-200 px-4 py-2 text-gray-800 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
126126
onClick={resetForm}
127127
whileHover={{ scale: 1.05 }}
128128
whileTap={{ scale: 0.95 }}

app/notification-generator/SoundInfoBox.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,10 @@ import { AlertBox } from "../../components/docs/ui/AlertBox";
22

33
export function SoundInfoBox() {
44
return (
5-
<AlertBox type="tip" title="Choose a Minecraft sound">
6-
You can find and test Minecraft sounds at{" "}
7-
<a
8-
href="https://minecraftsounds.com/"
9-
target="_blank"
10-
rel="noopener noreferrer"
11-
className="text-blue-500 underline"
12-
>
13-
minecraftsounds.com
14-
</a>
15-
. Copy the sound name and paste it below.
5+
<AlertBox type="tip" title="Minecraft Sounds">
6+
<p className="mb-2">
7+
Select a sound from the dropdown menu above. You can preview the sound by clicking the play button.
8+
</p>
169
</AlertBox>
1710
);
1811
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"use client";
2+
3+
import { useState, useEffect, useCallback } from "react";
4+
import { motion } from "framer-motion";
5+
6+
interface SliderFieldProps {
7+
label: string;
8+
name: string;
9+
value: string;
10+
onChange: (name: string, value: string) => void;
11+
min: number;
12+
max: number;
13+
step: number;
14+
error?: string;
15+
}
16+
17+
export const SliderField = ({
18+
label,
19+
name,
20+
value,
21+
onChange,
22+
min,
23+
max,
24+
step,
25+
error,
26+
}: SliderFieldProps) => {
27+
const [sliderValue, setSliderValue] = useState<number>(
28+
value ? parseFloat(value) : (min + max) / 2
29+
);
30+
31+
32+
useEffect(() => {
33+
if (value) {
34+
const parsedValue = parseFloat(value);
35+
36+
const boundedValue = Math.max(min, Math.min(max, parsedValue));
37+
setSliderValue(boundedValue);
38+
}
39+
}, [value, min, max]);
40+
41+
42+
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
43+
const newValue = parseFloat(e.target.value);
44+
setSliderValue(newValue);
45+
onChange(name, newValue.toFixed(2));
46+
}, [name, onChange]);
47+
48+
49+
const gradientPercentage = ((sliderValue - min) / (max - min)) * 100;
50+
51+
return (
52+
<div className="mb-4">
53+
<div className="flex items-center justify-between">
54+
<label className="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">
55+
{label}
56+
</label>
57+
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
58+
{sliderValue.toFixed(2)}
59+
</span>
60+
</div>
61+
<div className="flex items-center">
62+
<input
63+
type="range"
64+
min={min}
65+
max={max}
66+
step={step}
67+
value={sliderValue}
68+
onChange={handleChange}
69+
className="h-2 w-full appearance-none rounded-lg bg-gray-200 dark:bg-gray-700"
70+
style={{
71+
background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${gradientPercentage}%, #e5e7eb ${gradientPercentage}%, #e5e7eb 100%)`,
72+
}}
73+
/>
74+
</div>
75+
{error && (
76+
<motion.p
77+
className="mt-1 text-xs text-red-500 dark:text-red-400"
78+
initial={{ opacity: 0, y: -5 }}
79+
animate={{ opacity: 1, y: 0 }}
80+
transition={{ duration: 0.2 }}
81+
>
82+
{error}
83+
</motion.p>
84+
)}
85+
</div>
86+
);
87+
};

0 commit comments

Comments
 (0)