diff --git a/.eslintrc.json b/.eslintrc.json
index 78ca909..2991f48 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -41,6 +41,7 @@
"prettier/prettier": "error",
"sort-keys-fix/sort-keys-fix": ["warn", "asc"],
"sort-destructure-keys/sort-destructure-keys": "warn",
+ "jsx-a11y/no-static-element-interactions": "off",
"react/jsx-sort-props": [
"warn",
{
diff --git a/src/components/category/category.tsx b/src/components/category/category.tsx
index 7fce723..2fa91c6 100644
--- a/src/components/category/category.tsx
+++ b/src/components/category/category.tsx
@@ -1,5 +1,6 @@
import { useMemo } from 'react';
+import { Sounds } from '@/components/sounds';
import { cn } from '@/helpers/styles';
import styles from './category.module.css';
@@ -12,7 +13,7 @@ interface CategoryProps {
sounds: Array<{ label: string; src: string }>;
}
-export function Category({ color, icon, title }: CategoryProps) {
+export function Category({ color, icon, sounds, title }: CategoryProps) {
const colorStyle = useMemo(() => {
const colorToStyle: { [color: string]: string } = {
green: styles.green,
@@ -30,6 +31,8 @@ export function Category({ color, icon, title }: CategoryProps) {
{title}
+
+
);
}
diff --git a/src/components/sound/index.ts b/src/components/sound/index.ts
new file mode 100644
index 0000000..41a202a
--- /dev/null
+++ b/src/components/sound/index.ts
@@ -0,0 +1 @@
+export { Sound } from './sound';
diff --git a/src/components/sound/sound.module.css b/src/components/sound/sound.module.css
new file mode 100644
index 0000000..96e8014
--- /dev/null
+++ b/src/components/sound/sound.module.css
@@ -0,0 +1,119 @@
+.sound {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 30px 20px;
+ border: 1px solid var(--color-neutral-200);
+ border-radius: 10px;
+ background: linear-gradient(var(--color-neutral-100), transparent);
+ text-align: center;
+
+ &:not(.selected)::before {
+ position: absolute;
+ top: -1px;
+ left: 0;
+ width: 100%;
+ height: 1px;
+ background: linear-gradient(
+ 90deg,
+ transparent,
+ var(--color-neutral-300),
+ transparent
+ );
+ content: '';
+ }
+
+ &.selected {
+ &.green {
+ border: 2px solid #4ade80;
+ box-shadow: 0 10px 20px #4ade8033;
+ }
+
+ &.indigo {
+ border: 2px solid #818cf8;
+ box-shadow: 0 10px 20px #818cf833;
+ }
+ }
+
+ & h3 {
+ font-family: var(--font-heading);
+ font-size: 15px;
+ font-weight: 600;
+ line-height: 1.6;
+ }
+
+ & input {
+ width: 100%;
+ max-width: 120px;
+ margin-top: 12px;
+
+ /********** Range Input Styles **********/
+
+ /* Range Reset */
+ appearance: none;
+ background: transparent;
+ cursor: pointer;
+
+ /* Removes default focus */
+ &:focus {
+ outline: none;
+ }
+
+ &:disabled {
+ cursor: default;
+ opacity: 0.5;
+ pointer-events: none;
+ }
+
+ /***** Chrome, Safari, Opera and Edge Chromium styles *****/
+
+ &::-webkit-slider-runnable-track {
+ height: 0.5rem;
+ border-radius: 0.5rem;
+ background-color: #27272a;
+ }
+
+ &::-webkit-slider-thumb {
+ width: 14px;
+ height: 14px;
+ border: 1px solid #52525b;
+ border-radius: 50%;
+ margin-top: -3px;
+ appearance: none;
+ background-color: #3f3f46;
+ }
+
+ &:not(:disabled):focus::-webkit-slider-thumb {
+ border: 1px solid #053a5f;
+ outline: 3px solid #053a5f;
+ outline-offset: 0.125rem;
+ }
+
+ /******** Firefox styles ********/
+
+ &::-moz-range-track {
+ height: 0.5rem;
+ border-radius: 0.5rem;
+ background-color: #27272a;
+ }
+
+ &::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ border: none;
+ border: 1px solid #52525b;
+ border-radius: 0;
+ border-radius: 50%;
+ margin-top: -3px;
+ background-color: #3f3f46;
+ }
+
+ &:not(:disabled):focus::-moz-range-thumb {
+ border: 1px solid #053a5f;
+ outline: 3px solid #053a5f;
+ outline-offset: 0.125rem;
+ }
+ }
+}
diff --git a/src/components/sound/sound.tsx b/src/components/sound/sound.tsx
new file mode 100644
index 0000000..4cb9234
--- /dev/null
+++ b/src/components/sound/sound.tsx
@@ -0,0 +1,47 @@
+import { useState, useMemo, useEffect } from 'react';
+
+import { cn } from '@/helpers/styles';
+
+import styles from './sound.module.css';
+
+interface SoundProps {
+ color: string;
+ sound: { label: string; src: string };
+}
+
+export function Sound({ color, sound }: SoundProps) {
+ const [isSelected, setIsSelected] = useState(false);
+ const [volume, setVolume] = useState(0.5);
+
+ const colorStyle = useMemo(() => {
+ const colorToStyle: { [color: string]: string } = {
+ green: styles.green,
+ indigo: styles.indigo,
+ };
+
+ return colorToStyle[color];
+ }, [color]);
+
+ useEffect(() => {
+ if (!isSelected) setVolume(0.5);
+ }, [isSelected]);
+
+ return (
+ setIsSelected(prev => !prev)}
+ onKeyDown={() => setIsSelected(prev => !prev)}
+ >
+
{sound.label}
+ isSelected && setVolume(Number(e.target.value) / 100)}
+ onClick={e => e.stopPropagation()}
+ />
+
+ );
+}
diff --git a/src/components/sounds/index.ts b/src/components/sounds/index.ts
new file mode 100644
index 0000000..a2cb9e0
--- /dev/null
+++ b/src/components/sounds/index.ts
@@ -0,0 +1 @@
+export { Sounds } from './sounds';
diff --git a/src/components/sounds/sounds.module.css b/src/components/sounds/sounds.module.css
new file mode 100644
index 0000000..0732eea
--- /dev/null
+++ b/src/components/sounds/sounds.module.css
@@ -0,0 +1,6 @@
+.sounds {
+ display: grid;
+ margin-top: 20px;
+ gap: 20px;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+}
diff --git a/src/components/sounds/sounds.tsx b/src/components/sounds/sounds.tsx
new file mode 100644
index 0000000..7307a4a
--- /dev/null
+++ b/src/components/sounds/sounds.tsx
@@ -0,0 +1,18 @@
+import { Sound } from '@/components/sound';
+
+import styles from './sounds.module.css';
+
+interface SoundsProps {
+ color: string;
+ sounds: Array<{ label: string; src: string }>;
+}
+
+export function Sounds({ color, sounds }: SoundsProps) {
+ return (
+
+ {sounds.map(sound => (
+
+ ))}
+
+ );
+}