mirror of
https://github.com/remvze/moodist.git
synced 2025-10-17 12:00:38 -04:00
feat: implement basic snackbar
This commit is contained in:
parent
7810d21225
commit
8090599f2b
@ -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 (
|
||||
<StoreConsumer>
|
||||
<Container>
|
||||
<Buttons />
|
||||
<Categories categories={allCategories} />
|
||||
</Container>
|
||||
</StoreConsumer>
|
||||
<SnackbarProvider>
|
||||
<StoreConsumer>
|
||||
<Container>
|
||||
<Buttons />
|
||||
<Categories categories={allCategories} />
|
||||
</Container>
|
||||
</StoreConsumer>
|
||||
</SnackbarProvider>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
||||
<motion.button
|
||||
className={cn(styles.playButton, noSelected && styles.disabled)}
|
||||
disabled={noSelected}
|
||||
layout
|
||||
onClick={handleClick}
|
||||
>
|
||||
|
1
src/components/snackbar/index.ts
Normal file
1
src/components/snackbar/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { Snackbar } from './snackbar';
|
18
src/components/snackbar/snackbar.module.css
Normal file
18
src/components/snackbar/snackbar.module.css
Normal file
@ -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);
|
||||
}
|
||||
}
|
27
src/components/snackbar/snackbar.tsx
Normal file
27
src/components/snackbar/snackbar.tsx
Normal file
@ -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 (
|
||||
<div className={styles.wrapper}>
|
||||
<motion.div
|
||||
animate="show"
|
||||
className={styles.snackbar}
|
||||
exit="hidden"
|
||||
initial="hidden"
|
||||
variants={variants}
|
||||
>
|
||||
{message}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
48
src/contexts/snackbar.tsx
Normal file
48
src/contexts/snackbar.tsx
Normal file
@ -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<ReturnType<typeof setTimeout> | 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 (
|
||||
<SnackbarContext.Provider value={show}>
|
||||
{children}
|
||||
|
||||
<AnimatePresence>
|
||||
{isVisible && <Snackbar message={message} />}
|
||||
</AnimatePresence>
|
||||
</SnackbarContext.Provider>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user