From 8090599f2bc9ce58cdb36a6a04555afdb7af2bb2 Mon Sep 17 00:00:00 2001 From: MAZE Date: Mon, 30 Oct 2023 21:55:08 +0330 Subject: [PATCH] feat: implement basic snackbar --- src/components/app/app.tsx | 15 ++++--- src/components/buttons/play/play.tsx | 6 ++- src/components/snackbar/index.ts | 1 + src/components/snackbar/snackbar.module.css | 18 ++++++++ src/components/snackbar/snackbar.tsx | 27 ++++++++++++ src/contexts/snackbar.tsx | 48 +++++++++++++++++++++ 6 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 src/components/snackbar/index.ts create mode 100644 src/components/snackbar/snackbar.module.css create mode 100644 src/components/snackbar/snackbar.tsx create mode 100644 src/contexts/snackbar.tsx diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index 132c7b6..0ba5b6d 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -8,6 +8,7 @@ import { Container } from '@/components/container'; import { StoreConsumer } from '@/components/store-consumer'; import { Buttons } from '@/components/buttons'; import { Categories } from '@/components/categories'; +import { SnackbarProvider } from '@/contexts/snackbar'; import { sounds } from '@/data/sounds'; @@ -48,11 +49,13 @@ export function App() { }, [favoriteSounds, categories]); return ( - - - - - - + + + + + + + + ); } diff --git a/src/components/buttons/play/play.tsx b/src/components/buttons/play/play.tsx index 10d4ef2..b6a85c8 100644 --- a/src/components/buttons/play/play.tsx +++ b/src/components/buttons/play/play.tsx @@ -3,6 +3,7 @@ import { BiPause, BiPlay } from 'react-icons/bi/index'; import { motion } from 'framer-motion'; import { useSoundStore } from '@/store'; +import { useSnackbar } from '@/contexts/snackbar'; import { cn } from '@/helpers/styles'; import styles from './play.module.css'; @@ -13,8 +14,10 @@ export function PlayButton() { const toggle = useSoundStore(state => state.togglePlay); const noSelected = useSoundStore(state => state.noSelected()); + const showSnackbar = useSnackbar(); + const handleClick = () => { - if (noSelected) return pause(); + if (noSelected) return showSnackbar('Please first select a sound to play.'); toggle(); }; @@ -26,7 +29,6 @@ export function PlayButton() { return ( diff --git a/src/components/snackbar/index.ts b/src/components/snackbar/index.ts new file mode 100644 index 0000000..0d26e66 --- /dev/null +++ b/src/components/snackbar/index.ts @@ -0,0 +1 @@ +export { Snackbar } from './snackbar'; diff --git a/src/components/snackbar/snackbar.module.css b/src/components/snackbar/snackbar.module.css new file mode 100644 index 0000000..5df37ab --- /dev/null +++ b/src/components/snackbar/snackbar.module.css @@ -0,0 +1,18 @@ +.wrapper { + position: fixed; + z-index: 100; + bottom: 24px; + left: 0; + width: 100%; + + & .snackbar { + width: max-content; + max-width: 90%; + padding: 12px 16px; + border: 1px solid var(--color-neutral-300); + border-radius: 4px; + margin: 0 auto; + background-color: var(--color-neutral-200); + font-size: var(--font-sm); + } +} diff --git a/src/components/snackbar/snackbar.tsx b/src/components/snackbar/snackbar.tsx new file mode 100644 index 0000000..819623e --- /dev/null +++ b/src/components/snackbar/snackbar.tsx @@ -0,0 +1,27 @@ +import { motion } from 'framer-motion'; + +import { mix, fade, slideY } from '@/lib/motion'; + +import styles from './snackbar.module.css'; + +interface SnackbarProps { + message: string; +} + +export function Snackbar({ message }: SnackbarProps) { + const variants = mix(fade(), slideY(20, 0)); + + return ( +
+ + {message} + +
+ ); +} diff --git a/src/contexts/snackbar.tsx b/src/contexts/snackbar.tsx new file mode 100644 index 0000000..886c8c9 --- /dev/null +++ b/src/contexts/snackbar.tsx @@ -0,0 +1,48 @@ +import { + createContext, + useState, + useCallback, + useRef, + useContext, +} from 'react'; +import { AnimatePresence } from 'framer-motion'; + +import { Snackbar } from '@/components/snackbar'; + +export const SnackbarContext = createContext< + (message: string, duration?: number) => void +>(() => {}); + +export const useSnackbar = () => useContext(SnackbarContext); + +interface SnackbarProviderProps { + children: React.ReactNode; +} + +export function SnackbarProvider({ children }: SnackbarProviderProps) { + const [message, setMessage] = useState(''); + const [isVisible, setIsVisible] = useState(false); + const timeout = useRef | null>(null); + + const show = useCallback((message: string, duration = 5000) => { + setMessage(message); + setIsVisible(true); + + if (timeout.current) clearTimeout(timeout.current); + + timeout.current = setTimeout(() => { + setMessage(''); + setIsVisible(false); + }, duration); + }, []); + + return ( + + {children} + + + {isVisible && } + + + ); +}