Responsive Header

A responsive header component for React that supports drag-to-close gestures on mobile devices and functions as a dialog or drawer on desktop. Built with accessibility, responsiveness, and smooth animations in mind.

Installation

npm install 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