Responsive Header

A responsive header that can be closed by dragging on mobile devices and functions as a standard dialog or drawer on desktop.A responsive header that can be closed by dragging on mobile devices and functions as a standard dialog or drawer on desktop.

Installation

npm install framer-motion vaul

please add this property in your layout.js or app.js

1
'use client';
2
3
import { MenuIcon } from 'lucide-react';
4
import React, {
5
createContext,
6
useContext,
7
useState,
8
useEffect,
9
ReactNode,
10
} from 'react';
11
import { Drawer as VaulHeader } from 'vaul';
12
13
interface DrawerContextProps {
14
open: boolean;
15
setOpen: (open: boolean) => void;
16
}
17
18
const DrawerContext = createContext<DrawerContextProps | undefined>(undefined);
19
20
const useSidebarDrawer = () => {
21
const context = useContext(DrawerContext);
22
if (!context) {
23
throw new Error('useDrawer must be used within a DrawerProvider');
24
}
25
return context;
26
};
27
28
interface DrawerSidebarProps {
29
children: ReactNode;
30
open?: boolean;
31
setOpen?: (open: boolean) => void;
32
drawerBtn?: any | null;
33
}
34
35
export function HeaderDrawer({
36
children,
37
open: controlledOpen,
38
setOpen: controlledSetOpen,
39
drawerBtn,
40
}: DrawerSidebarProps) {
41
const [internalOpen, setInternalOpen] = useState(false);
42
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
43
const setOpen =
44
controlledSetOpen !== undefined ? controlledSetOpen : setInternalOpen;
45
46
const [isDesktop, setIsDesktop] = useState(false);
47
48
useEffect(() => {
49
const mediaQuery = window.matchMedia('(min-width: 768px)');
50
const handleMediaChange = (event: MediaQueryListEvent) => {
51
setIsDesktop(event.matches);
52
};
53
54
setIsDesktop(mediaQuery.matches);
55
mediaQuery.addEventListener('change', handleMediaChange);
56
57
return () => {
58
mediaQuery.removeEventListener('change', handleMediaChange);
59
};
60
}, []);
61
console.log(drawerBtn);
62
63
return (
64
<DrawerContext.Provider value={{ open, setOpen }}>
65
<>
66
<VaulHeader.Root
67
open={open}
68
direction='top'
69
onOpenChange={setOpen}
70
dismissible={isDesktop ? false : true}
71
>
72
{drawerBtn && (
73
<VaulHeader.Trigger asChild>{drawerBtn()}</VaulHeader.Trigger>
74
)}
75
<VaulHeader.Portal>
76
<VaulHeader.Overlay className='fixed inset-0 dark:bg-black/40 bg-white/50 backdrop-blur-sm z-50 ' />
77
<VaulHeader.Content className='dark:bg-gray-900 bg-white border-b z-50 w-full h-fit py-3 fixed top-0 left-0'>
78
{children}
79
</VaulHeader.Content>
80
</VaulHeader.Portal>
81
</VaulHeader.Root>
82
</>
83
</DrawerContext.Provider>
84
);
85
}
86
87
export function DrawerContent({ children }: { children: ReactNode }) {
88
return <>{children}</>;
89
}

you can use state or default button to control the dialog

const [sidebarOpen, setSidebarOpen] = useState(false)
return (
<>
<HeaderDrawer
open={headerOpen}
setOpen={setHeaderOpen}
drawerBtn={()=> { return <button><MenuIcon/></button>
}}>
<DrawerContent>
</DrawerContent>
</HeaderDrawer>
)

Props

PropTypeDescription
openbooleanThe content to be displayed within the AuroraBackground component.
setOpenbooleanthis is an function to close and open the drawer
drawerBtnfunctionthis is an function for default button, when you don't to use state then you can use drawerBtn