⚡️ feat: add search dialog on mobile
This commit is contained in:
parent
5a86abd133
commit
1ddc3b81c7
@ -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)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user