️ feat: add search dialog on mobile

This commit is contained in:
Wahyu Kurniawan 2024-03-06 10:31:11 +07:00
parent 5a86abd133
commit 1ddc3b81c7
No known key found for this signature in database
GPG Key ID: 040A1549143A8E33

View File

@ -13,6 +13,7 @@ import { Outlet, useParams } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { cn } from 'utils/classnames'; import { cn } from 'utils/classnames';
import { useMediaQuery } from 'usehooks-ts'; import { useMediaQuery } from 'usehooks-ts';
import { ProjectSearchBarDialog } from 'components/projects/ProjectSearchBar';
export interface DashboardLayoutProps export interface DashboardLayoutProps
extends ComponentPropsWithoutRef<'section'> {} extends ComponentPropsWithoutRef<'section'> {}
@ -23,7 +24,9 @@ export const DashboardLayout = ({
}: DashboardLayoutProps) => { }: DashboardLayoutProps) => {
const { orgSlug } = useParams(); const { orgSlug } = useParams();
const isDesktop = useMediaQuery('(min-width: 1024px)'); const isDesktop = useMediaQuery('(min-width: 1024px)');
const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [isSearchOpen, setIsSearchOpen] = useState(false);
useEffect(() => { useEffect(() => {
if (isDesktop) { if (isDesktop) {
@ -32,86 +35,96 @@ export const DashboardLayout = ({
}, [isDesktop]); }, [isDesktop]);
return ( return (
<section <>
{...props} <section
className={cn( {...props}
'flex flex-col lg:flex-row h-screen bg-snowball-50', className={cn(
className, 'flex flex-col lg:flex-row h-screen bg-snowball-50',
)} className,
> )}
{/* Header on mobile */} >
<div className="flex lg:hidden items-center px-4 py-4 justify-between"> {/* Header on mobile */}
<Logo orgSlug={orgSlug} /> <div className="flex lg:hidden items-center px-4 py-4 justify-between">
<div className="flex items-center gap-0.5"> <Logo orgSlug={orgSlug} />
<AnimatePresence> <div className="flex items-center gap-0.5">
{isSidebarOpen ? ( <AnimatePresence>
<motion.div {isSidebarOpen ? (
key="crossIcon" <motion.div
initial={{ opacity: 0 }} key="crossIcon"
animate={{ initial={{ opacity: 0 }}
opacity: 1, animate={{
transition: { duration: 0.2, delay: 0.3 }, opacity: 1,
}} transition: { duration: 0.2, delay: 0.3 },
exit={{ opacity: 0, transition: { duration: 0 } }} }}
> 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 <Button
iconOnly iconOnly
variant="ghost" variant="ghost"
onClick={() => setIsSidebarOpen(true)} onClick={() => setIsSidebarOpen(false)}
> >
<MenuIcon size={18} /> <CrossIcon size={20} />
</Button> </Button>
</> </motion.div>
</motion.div> ) : (
)} <motion.div
</AnimatePresence> key="menuIcons"
</div> initial={{ opacity: 0 }}
</div> animate={{
<div className="flex h-full w-full overflow-hidden"> opacity: 1,
<Sidebar mobileOpen={isSidebarOpen} /> transition: { duration: 0.2, delay: 0.2 },
<motion.div }}
className={cn( exit={{ opacity: 0, transition: { duration: 0 } }}
'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 }, <>
)} <Button iconOnly variant="ghost">
initial={{ x: 0 }} // Initial state, no translation <NotificationBellIcon size={18} />
animate={{ </Button>
x: isSidebarOpen ? '10px' : 0, // Translate X based on sidebar state <Button
}} iconOnly
transition={{ type: 'spring', stiffness: 260, damping: 20 }} variant="ghost"
> onClick={() => setIsSearchOpen(true)}
<div className="rounded-3xl bg-base-bg h-full shadow-card overflow-y-auto relative"> >
<OctokitProvider> <SearchIcon size={18} />
<Outlet /> </Button>
</OctokitProvider> <Button
iconOnly
variant="ghost"
onClick={() => setIsSidebarOpen(true)}
>
<MenuIcon size={18} />
</Button>
</>
</motion.div>
)}
</AnimatePresence>
</div> </div>
</motion.div> </div>
</div> <div className="flex h-full w-full overflow-hidden">
</section> <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>
</section>
<ProjectSearchBarDialog
open={isSearchOpen}
onClose={() => setIsSearchOpen(false)}
/>
</>
); );
}; };