From cb34b59d864fb80b930c0c9e1c1269bb7e9c2b18 Mon Sep 17 00:00:00 2001 From: MAZE Date: Tue, 10 Oct 2023 23:42:39 +0330 Subject: [PATCH] feat: implement favorite sounds functionality --- src/components/categories/categories.tsx | 29 +++++- src/components/category/category.tsx | 11 ++- src/components/sound/sound.module.css | 95 +++++-------------- src/components/sound/sound.tsx | 20 +++- src/components/sounds/sounds.tsx | 8 +- .../store-consumer/store-consumer.tsx | 2 + src/store/favorite/favorite.actions.ts | 24 +++++ src/store/favorite/favorite.state.ts | 14 +++ src/store/favorite/index.ts | 20 ++++ src/store/sound/index.ts | 2 +- 10 files changed, 144 insertions(+), 81 deletions(-) create mode 100644 src/store/favorite/favorite.actions.ts create mode 100644 src/store/favorite/favorite.state.ts create mode 100644 src/store/favorite/index.ts diff --git a/src/components/categories/categories.tsx b/src/components/categories/categories.tsx index eec5577..6e71935 100644 --- a/src/components/categories/categories.tsx +++ b/src/components/categories/categories.tsx @@ -1,3 +1,9 @@ +import { useEffect, useMemo } from 'react'; +import { useShallow } from 'zustand/react/shallow'; +import { BiSolidHeart } from 'react-icons/bi/index'; + +import { useFavoriteStore } from '@/store/favorite'; + import { Container } from '@/components/container'; import { StoreConsumer } from '../store-consumer'; import { Category } from '@/components/category'; @@ -7,7 +13,18 @@ import { PlayProvider } from '@/contexts/play'; import { sounds } from '@/data/sounds'; export function Categories() { - const { categories } = sounds; + const categories = useMemo(() => sounds.categories, []); + + const favorites = useFavoriteStore(useShallow(state => state.favorites)); + + const favoriteSounds = useMemo(() => { + return categories + .map(category => category.sounds) + .flat() + .filter(sound => favorites.includes(sound.id)); + }, [favorites, categories]); + + useEffect(() => console.log({ favoriteSounds }), [favoriteSounds]); return ( @@ -16,6 +33,16 @@ export function Categories() {
+ {!!favoriteSounds.length && ( + } + id="favorites" + sounds={favoriteSounds} + title="Favorites" + /> + )} + {categories.map(category => ( ))} diff --git a/src/components/category/category.tsx b/src/components/category/category.tsx index 63c2ea2..c7dda06 100644 --- a/src/components/category/category.tsx +++ b/src/components/category/category.tsx @@ -6,6 +6,7 @@ interface CategoryProps { icon: React.ReactNode; title: string; id: string; + functional: boolean; sounds: Array<{ label: string; src: string; @@ -14,7 +15,13 @@ interface CategoryProps { }>; } -export function Category({ icon, id, sounds, title }: CategoryProps) { +export function Category({ + functional = true, + icon, + id, + sounds, + title, +}: CategoryProps) { return (
@@ -24,7 +31,7 @@ export function Category({ icon, id, sounds, title }: CategoryProps) {

{title}

- +
); } diff --git a/src/components/sound/sound.module.css b/src/components/sound/sound.module.css index 9bdc89a..b60ec6c 100644 --- a/src/components/sound/sound.module.css +++ b/src/components/sound/sound.module.css @@ -29,6 +29,28 @@ content: ''; } + & .favoriteButton { + position: absolute; + top: 10px; + right: 10px; + display: flex; + width: 30px; + height: 30px; + align-items: center; + justify-content: center; + border: 1px solid var(--color-neutral-200); + border-radius: 50%; + background-color: black; + background-color: var(--color-neutral-100); + color: var(--color-foreground); + cursor: pointer; + outline: none; + + &.isFavorite { + color: #f43f5e; + } + } + & .icon { position: relative; z-index: 2; @@ -85,77 +107,4 @@ font-weight: 600; line-height: 1.6; } - - & input { - width: 100%; - max-width: 120px; - margin-top: 10px; - - /********** 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 index 7b9ca9d..1ef791d 100644 --- a/src/components/sound/sound.tsx +++ b/src/components/sound/sound.tsx @@ -1,9 +1,11 @@ import { useCallback, useEffect } from 'react'; +import { BiHeart, BiSolidHeart } from 'react-icons/bi/index'; import { Range } from './range'; import { useSound } from '@/hooks/use-sound'; import { useSoundStore } from '@/store'; +import { useFavoriteStore } from '@/store/favorite'; import { usePlay } from '@/contexts/play'; import { cn } from '@/helpers/styles'; @@ -15,11 +17,13 @@ interface SoundProps { icon: React.ReactNode; hidden: boolean; id: string; + functional: boolean; selectHidden: (key: string) => void; unselectHidden: (key: string) => void; } export function Sound({ + functional, hidden, icon, id, @@ -36,15 +40,18 @@ export function Sound({ const volume = useSoundStore(state => state.sounds[id].volume); const isSelected = useSoundStore(state => state.sounds[id].isSelected); + const isFavorite = useFavoriteStore(state => state.favorites.includes(id)); + const toggleFavorite = useFavoriteStore(state => state.toggleFavorite); + const sound = useSound(src, { loop: true, volume }); useEffect(() => { - if (isSelected && isPlaying) { + if (isSelected && isPlaying && functional) { sound?.play(); } else { sound?.pause(); } - }, [isSelected, sound, isPlaying]); + }, [isSelected, sound, isPlaying, functional]); useEffect(() => { if (hidden && isSelected) selectHidden(label); @@ -77,6 +84,15 @@ export function Sound({ onClick={toggle} onKeyDown={toggle} > +
{icon}

{label}

diff --git a/src/components/sounds/sounds.tsx b/src/components/sounds/sounds.tsx index 26cae23..f3430ce 100644 --- a/src/components/sounds/sounds.tsx +++ b/src/components/sounds/sounds.tsx @@ -8,6 +8,7 @@ import styles from './sounds.module.css'; interface SoundsProps { id: string; + functional: boolean; sounds: Array<{ label: string; src: string; @@ -16,7 +17,7 @@ interface SoundsProps { }>; } -export function Sounds({ id, sounds }: SoundsProps) { +export function Sounds({ functional, id, sounds }: SoundsProps) { const [showAll, setShowAll] = useLocalStorage(`${id}-show-more`, false); const [hiddenSelections, setHiddenSelections] = useState<{ @@ -50,14 +51,17 @@ export function Sounds({ id, sounds }: SoundsProps) {