feat: add toolbar and portal

This commit is contained in:
MAZE 2024-04-08 20:55:03 +03:30
parent 6dfa998ffe
commit ede480186c
12 changed files with 98 additions and 49 deletions

View file

@ -9,9 +9,8 @@ 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 { ScrollToTop } from '@/components/scroll-to-top';
import { SharedModal } from '@/components/modals/shared'; import { SharedModal } from '@/components/modals/shared';
import { Menu } from '@/components/menu/menu'; import { Toolbar } from '@/components/toolbar';
import { SnackbarProvider } from '@/contexts/snackbar'; import { SnackbarProvider } from '@/contexts/snackbar';
import { sounds } from '@/data/sounds'; import { sounds } from '@/data/sounds';
@ -77,8 +76,7 @@ export function App() {
<Categories categories={allCategories} /> <Categories categories={allCategories} />
</Container> </Container>
<ScrollToTop /> <Toolbar />
<Menu />
<SharedModal /> <SharedModal />
</StoreConsumer> </StoreConsumer>
</SnackbarProvider> </SnackbarProvider>

View file

@ -6,4 +6,8 @@
&.tight { &.tight {
max-width: 450px; max-width: 450px;
} }
&.wide {
max-width: 720px;
}
} }

View file

@ -6,11 +6,24 @@ interface ContainerProps {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
tight?: boolean; tight?: boolean;
wide?: boolean;
} }
export function Container({ children, className, tight }: ContainerProps) { export function Container({
children,
className,
tight,
wide,
}: ContainerProps) {
return ( return (
<div className={cn(styles.container, className, tight && styles.tight)}> <div
className={cn(
styles.container,
className,
tight && styles.tight,
wide && styles.wide,
)}
>
{children} {children}
</div> </div>
); );

View file

@ -1,13 +1,4 @@
.wrapper { .wrapper {
position: fixed;
right: calc(50vw - 400px);
bottom: 20px;
z-index: 15;
@media (width <= 850px) {
right: 5vw;
}
& .menuButton { & .menuButton {
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -2,6 +2,8 @@ import { useEffect } from 'react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { IoClose } from 'react-icons/io5/index'; import { IoClose } from 'react-icons/io5/index';
import { Portal } from '@/components/portal';
import { fade, mix, slideY } from '@/lib/motion'; import { fade, mix, slideY } from '@/lib/motion';
import { cn } from '@/helpers/styles'; import { cn } from '@/helpers/styles';
@ -36,35 +38,37 @@ export function Modal({
}, [show, lockBody]); }, [show, lockBody]);
return ( return (
<AnimatePresence> <Portal>
{show && ( <AnimatePresence>
<> {show && (
<motion.div <>
animate="show"
className={styles.overlay}
exit="hidden"
initial="hidden"
variants={variants.overlay}
onClick={onClose}
onKeyDown={onClose}
/>
<div className={styles.modal}>
<motion.div <motion.div
animate="show" animate="show"
className={cn(styles.content, wide && styles.wide)} className={styles.overlay}
exit="hidden" exit="hidden"
initial="hidden" initial="hidden"
variants={variants.modal} variants={variants.overlay}
> onClick={onClose}
<button className={styles.close} onClick={onClose}> onKeyDown={onClose}
<IoClose /> />
</button> <div className={styles.modal}>
<motion.div
animate="show"
className={cn(styles.content, wide && styles.wide)}
exit="hidden"
initial="hidden"
variants={variants.modal}
>
<button className={styles.close} onClick={onClose}>
<IoClose />
</button>
{children} {children}
</motion.div> </motion.div>
</div> </div>
</> </>
)} )}
</AnimatePresence> </AnimatePresence>
</Portal>
); );
} }

View file

@ -0,0 +1 @@
export { Portal } from './portal';

View file

@ -0,0 +1,14 @@
import { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
interface PortalProps {
children: React.ReactNode;
}
export function Portal({ children }: PortalProps) {
const [isClientSide, setIsClientSide] = useState(false);
useEffect(() => setIsClientSide(true), []);
return isClientSide ? createPortal(children, document.body) : null;
}

View file

@ -1,8 +1,4 @@
.button { .button {
position: fixed;
bottom: 20px;
left: calc(50vw - 400px);
z-index: 10;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -16,10 +12,6 @@
border-radius: 50%; border-radius: 50%;
transition: 0.2s; transition: 0.2s;
@media (width <= 850px) {
left: 5vw;
}
&:hover { &:hover {
background-color: var(--color-neutral-200); background-color: var(--color-neutral-200);
} }

View file

@ -31,7 +31,7 @@ export function ScrollToTop() {
return ( return (
<AnimatePresence> <AnimatePresence>
{isVisible && ( {isVisible ? (
<motion.button <motion.button
animate="show" animate="show"
aria-label="Scroll to top" aria-label="Scroll to top"
@ -43,6 +43,8 @@ export function ScrollToTop() {
> >
<BiUpArrowAlt /> <BiUpArrowAlt />
</motion.button> </motion.button>
) : (
<div />
)} )}
</AnimatePresence> </AnimatePresence>
); );

View file

@ -0,0 +1 @@
export { Toolbar } from './toolbar';

View file

@ -0,0 +1,13 @@
.wrapper {
position: fixed;
bottom: 20px;
left: 0;
z-index: 15;
width: 100%;
.container {
display: flex;
align-items: center;
justify-content: space-between;
}
}

View file

@ -0,0 +1,16 @@
import { Container } from '@/components/container';
import { Menu } from '@/components/menu';
import { ScrollToTop } from '@/components/scroll-to-top';
import styles from './toolbar.module.css';
export function Toolbar() {
return (
<div className={styles.wrapper}>
<Container className={styles.container} wide>
<ScrollToTop />
<Menu />
</Container>
</div>
);
}