moodist/src/components/sound/sound.tsx
2024-06-15 12:36:47 +04:30

112 lines
3 KiB
TypeScript

import { useCallback, useEffect, forwardRef } from 'react';
import { ImSpinner9 } from 'react-icons/im/index';
import { Range } from './range';
import { Favorite } from './favorite';
import { useSound } from '@/hooks/use-sound';
import { useSoundStore, useLoadingStore } from '@/stores';
import { cn } from '@/helpers/styles';
import styles from './sound.module.css';
import type { Sound as SoundType } from '@/data/types';
import { useKeyboardButton } from '@/hooks/use-keyboard-button';
interface SoundProps extends SoundType {
functional: boolean;
hidden: boolean;
selectHidden: (key: string) => void;
unselectHidden: (key: string) => void;
}
export const Sound = forwardRef<HTMLDivElement, SoundProps>(function Sound(
{ functional, hidden, icon, id, label, selectHidden, src, unselectHidden },
ref,
) {
const isPlaying = useSoundStore(state => state.isPlaying);
const play = useSoundStore(state => state.play);
const selectSound = useSoundStore(state => state.select);
const unselectSound = useSoundStore(state => state.unselect);
const setVolume = useSoundStore(state => state.setVolume);
const volume = useSoundStore(state => state.sounds[id].volume);
const isSelected = useSoundStore(state => state.sounds[id].isSelected);
const locked = useSoundStore(state => state.locked);
const isLoading = useLoadingStore(state => state.loaders[src]);
const sound = useSound(src, { loop: true, volume });
useEffect(() => {
if (locked) return;
if (isSelected && isPlaying && functional) {
sound?.play();
} else {
sound?.pause();
}
}, [isSelected, sound, isPlaying, functional, locked]);
useEffect(() => {
if (hidden && isSelected) selectHidden(label);
else if (hidden && !isSelected) unselectHidden(label);
}, [label, isSelected, hidden, selectHidden, unselectHidden]);
const select = useCallback(() => {
if (locked) return;
selectSound(id);
play();
}, [selectSound, play, id, locked]);
const unselect = useCallback(() => {
if (locked) return;
unselectSound(id);
setVolume(id, 0.5);
}, [unselectSound, setVolume, id, locked]);
const toggle = useCallback(() => {
if (locked) return;
if (isSelected) unselect();
else select();
}, [isSelected, select, unselect, locked]);
const handleClick = useCallback(() => {
toggle();
}, [toggle]);
const handleKeyDown = useKeyboardButton(() => {
toggle();
});
return (
<div
aria-label={`${label} sound`}
ref={ref}
role="button"
tabIndex={0}
className={cn(
styles.sound,
isSelected && styles.selected,
hidden && styles.hidden,
)}
onClick={handleClick}
onKeyDown={handleKeyDown}
>
<Favorite id={id} label={label} />
<div className={styles.icon}>
{isLoading ? (
<span className={styles.spinner}>
<ImSpinner9 />
</span>
) : (
icon
)}
</div>
<div className={styles.label} id={id}>
{label}
</div>
<Range id={id} label={label} />
</div>
);
});