mirror of
https://github.com/remvze/moodist.git
synced 2025-10-19 13:00:30 -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 { StoreConsumer } from '@/components/store-consumer';
|
||||||
import { Buttons } from '@/components/buttons';
|
import { Buttons } from '@/components/buttons';
|
||||||
import { Categories } from '@/components/categories';
|
import { Categories } from '@/components/categories';
|
||||||
|
import { SnackbarProvider } from '@/contexts/snackbar';
|
||||||
|
|
||||||
import { sounds } from '@/data/sounds';
|
import { sounds } from '@/data/sounds';
|
||||||
|
|
||||||
@ -48,11 +49,13 @@ export function App() {
|
|||||||
}, [favoriteSounds, categories]);
|
}, [favoriteSounds, categories]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<SnackbarProvider>
|
||||||
<StoreConsumer>
|
<StoreConsumer>
|
||||||
<Container>
|
<Container>
|
||||||
<Buttons />
|
<Buttons />
|
||||||
<Categories categories={allCategories} />
|
<Categories categories={allCategories} />
|
||||||
</Container>
|
</Container>
|
||||||
</StoreConsumer>
|
</StoreConsumer>
|
||||||
|
</SnackbarProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { BiPause, BiPlay } from 'react-icons/bi/index';
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
import { useSoundStore } from '@/store';
|
import { useSoundStore } from '@/store';
|
||||||
|
import { useSnackbar } from '@/contexts/snackbar';
|
||||||
import { cn } from '@/helpers/styles';
|
import { cn } from '@/helpers/styles';
|
||||||
|
|
||||||
import styles from './play.module.css';
|
import styles from './play.module.css';
|
||||||
@ -13,8 +14,10 @@ export function PlayButton() {
|
|||||||
const toggle = useSoundStore(state => state.togglePlay);
|
const toggle = useSoundStore(state => state.togglePlay);
|
||||||
const noSelected = useSoundStore(state => state.noSelected());
|
const noSelected = useSoundStore(state => state.noSelected());
|
||||||
|
|
||||||
|
const showSnackbar = useSnackbar();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (noSelected) return pause();
|
if (noSelected) return showSnackbar('Please first select a sound to play.');
|
||||||
|
|
||||||
toggle();
|
toggle();
|
||||||
};
|
};
|
||||||
@ -26,7 +29,6 @@ export function PlayButton() {
|
|||||||
return (
|
return (
|
||||||
<motion.button
|
<motion.button
|
||||||
className={cn(styles.playButton, noSelected && styles.disabled)}
|
className={cn(styles.playButton, noSelected && styles.disabled)}
|
||||||
disabled={noSelected}
|
|
||||||
layout
|
layout
|
||||||
onClick={handleClick}
|
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