mirror of
https://github.com/remvze/moodist.git
synced 2025-12-17 17:04:15 +00:00
feat: add toolbar and portal
This commit is contained in:
parent
6dfa998ffe
commit
ede480186c
12 changed files with 98 additions and 49 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,8 @@
|
||||||
&.tight {
|
&.tight {
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.wide {
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,6 +38,7 @@ export function Modal({
|
||||||
}, [show, lockBody]);
|
}, [show, lockBody]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Portal>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{show && (
|
{show && (
|
||||||
<>
|
<>
|
||||||
|
|
@ -66,5 +69,6 @@ export function Modal({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
src/components/portal/index.ts
Normal file
1
src/components/portal/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { Portal } from './portal';
|
||||||
14
src/components/portal/portal.tsx
Normal file
14
src/components/portal/portal.tsx
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
1
src/components/toolbar/index.ts
Normal file
1
src/components/toolbar/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { Toolbar } from './toolbar';
|
||||||
13
src/components/toolbar/toolbar.module.css
Normal file
13
src/components/toolbar/toolbar.module.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/components/toolbar/toolbar.tsx
Normal file
16
src/components/toolbar/toolbar.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue