forked from cerc-io/snowballtools-base
🎨 style: add animation when sidebar open on mobile
This commit is contained in:
parent
1648deb64f
commit
b03200c256
@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { NavLink, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Organization, User } from 'gql-client';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { Option } from '@material-tailwind/react';
|
||||
import { useDisconnect } from 'wagmi';
|
||||
@ -22,12 +23,19 @@ import { Avatar } from 'components/shared/Avatar';
|
||||
import { formatAddress } from 'utils/format';
|
||||
import { getInitials } from 'utils/geInitials';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import { cn } from 'utils/classnames';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
|
||||
export const Sidebar = () => {
|
||||
interface SidebarProps {
|
||||
mobileOpen?: boolean;
|
||||
}
|
||||
|
||||
export const Sidebar = ({ mobileOpen }: SidebarProps) => {
|
||||
const { orgSlug } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const client = useGQLClient();
|
||||
const { disconnect } = useDisconnect();
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
|
||||
const [user, setUser] = useState<User>();
|
||||
|
||||
@ -57,11 +65,24 @@ export const Sidebar = () => {
|
||||
disconnect();
|
||||
navigate('/login');
|
||||
}, [disconnect, navigate]);
|
||||
|
||||
console.log(isDesktop);
|
||||
return (
|
||||
<nav className="h-full w-[320px] lg:flex hidden flex-col">
|
||||
<div className="flex flex-col h-full pt-8 pb-0 px-6 lg:pb-8 gap-9">
|
||||
<Logo orgSlug={orgSlug} />
|
||||
<motion.nav
|
||||
initial={{ x: -320 }}
|
||||
animate={{ x: isDesktop || mobileOpen ? 0 : -320 }}
|
||||
exit={{ x: -320 }}
|
||||
transition={{ type: 'spring', stiffness: 260, damping: 20 }}
|
||||
className={cn('h-full flex-none w-[320px] lg:flex hidden flex-col', {
|
||||
flex: mobileOpen,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cn('flex flex-col h-full pt-8 pb-0 px-6 lg:pb-8 gap-9', {
|
||||
'px-4 pt-5': mobileOpen,
|
||||
})}
|
||||
>
|
||||
{/* Logo */}
|
||||
{!mobileOpen && <Logo orgSlug={orgSlug} />}
|
||||
{/* Switch organization */}
|
||||
<div className="flex flex-1 flex-col gap-4">
|
||||
<AsyncSelect
|
||||
@ -156,6 +177,7 @@ export const Sidebar = () => {
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
{/* Only shows when on mobile */}
|
||||
<div className="shadow-card-sm py-4 pl-4 pr-2 flex lg:hidden items-center border-t border-border-separator/[0.06]">
|
||||
{user?.name && (
|
||||
<div className="flex items-center flex-1 gap-3">
|
||||
@ -169,6 +191,7 @@ export const Sidebar = () => {
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
iconOnly
|
||||
variant="ghost"
|
||||
className="text-elements-low-em"
|
||||
onClick={handleLogOut}
|
||||
@ -176,6 +199,6 @@ export const Sidebar = () => {
|
||||
<LogoutIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.nav>
|
||||
);
|
||||
};
|
||||
|
@ -1,25 +1,35 @@
|
||||
import { Logo } from 'components/Logo';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import {
|
||||
CrossIcon,
|
||||
MenuIcon,
|
||||
NotificationBellIcon,
|
||||
SearchIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import { Sidebar } from 'components/shared/Sidebar';
|
||||
import { OctokitProvider } from 'context/OctokitContext';
|
||||
import React, { ComponentPropsWithoutRef } from 'react';
|
||||
import React, { ComponentPropsWithoutRef, useEffect, useState } from 'react';
|
||||
import { Outlet, useParams } from 'react-router-dom';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { cn } from 'utils/classnames';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
|
||||
export interface DashboardLayoutProps
|
||||
extends ComponentPropsWithoutRef<'section'> {}
|
||||
|
||||
export const DashboardLayout = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: DashboardLayoutProps) => {
|
||||
const { orgSlug } = useParams();
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDesktop) {
|
||||
setIsSidebarOpen(false);
|
||||
}
|
||||
}, [isDesktop]);
|
||||
|
||||
return (
|
||||
<section
|
||||
@ -29,30 +39,79 @@ export const DashboardLayout = ({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Sidebar />
|
||||
{/* Header on mobile */}
|
||||
<div className="flex lg:hidden items-center px-4 py-4 justify-between">
|
||||
<Logo orgSlug={orgSlug} />
|
||||
<div className="flex items-center gap-0.5">
|
||||
<Button iconOnly variant="ghost">
|
||||
<NotificationBellIcon size={18} />
|
||||
</Button>
|
||||
<Button iconOnly variant="ghost">
|
||||
<SearchIcon size={18} />
|
||||
</Button>
|
||||
<Button iconOnly variant="ghost">
|
||||
<MenuIcon size={18} />
|
||||
</Button>
|
||||
<AnimatePresence>
|
||||
{isSidebarOpen ? (
|
||||
<motion.div
|
||||
key="crossIcon"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
transition: { duration: 0.2, delay: 0.3 },
|
||||
}}
|
||||
exit={{ opacity: 0, transition: { duration: 0 } }}
|
||||
>
|
||||
<Button
|
||||
iconOnly
|
||||
variant="ghost"
|
||||
onClick={() => setIsSidebarOpen(false)}
|
||||
>
|
||||
<CrossIcon size={20} />
|
||||
</Button>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="menuIcons"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
transition: { duration: 0.2, delay: 0.2 },
|
||||
}}
|
||||
exit={{ opacity: 0, transition: { duration: 0 } }}
|
||||
>
|
||||
<>
|
||||
<Button iconOnly variant="ghost">
|
||||
<NotificationBellIcon size={18} />
|
||||
</Button>
|
||||
<Button iconOnly variant="ghost">
|
||||
<SearchIcon size={18} />
|
||||
</Button>
|
||||
<Button
|
||||
iconOnly
|
||||
variant="ghost"
|
||||
onClick={() => setIsSidebarOpen(true)}
|
||||
>
|
||||
<MenuIcon size={18} />
|
||||
</Button>
|
||||
</>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 w-full h-full px-1 py-1 md:px-3 md:py-3 overflow-y-hidden">
|
||||
<div className="rounded-3xl bg-base-bg h-full shadow-card overflow-y-auto relative">
|
||||
<OctokitProvider>
|
||||
<Outlet />
|
||||
</OctokitProvider>
|
||||
</div>
|
||||
<div className="flex h-full w-full overflow-hidden">
|
||||
<Sidebar mobileOpen={isSidebarOpen} />
|
||||
<motion.div
|
||||
className={cn(
|
||||
'w-full h-full pr-1 pl-1 py-1 md:pl-0 md:pr-3 md:py-3 overflow-y-hidden min-w-[320px]',
|
||||
{ 'flex-shrink-0': isSidebarOpen || !isDesktop },
|
||||
)}
|
||||
initial={{ x: 0 }} // Initial state, no translation
|
||||
animate={{
|
||||
x: isSidebarOpen ? '10px' : 0, // Translate X based on sidebar state
|
||||
}}
|
||||
transition={{ type: 'spring', stiffness: 260, damping: 20 }}
|
||||
>
|
||||
<div className="rounded-3xl bg-base-bg h-full shadow-card overflow-y-auto relative">
|
||||
<OctokitProvider>
|
||||
<Outlet />
|
||||
</OctokitProvider>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user