Responsive Sidebar
A responsive sidebar component for React with smooth Framer Motion animations. Functions as a collapsible navigation drawer on mobile and a persistent sidebar on desktop.
A responsive sidebar component for React with smooth Framer Motion animations. Functions as a collapsible navigation drawer on mobile and a persistent sidebar on desktop.
npm install vaul
'use client';import React, {createContext,useContext,useState,useEffect,ReactNode,} from 'react';import { X } from 'lucide-react';import { Drawer as VaulSidebar } from 'vaul';import { cn } from '@/lib/utils';interface DrawerContextProps {open: boolean;setOpen: (open: boolean) => void;}const DrawerContext = createContext<DrawerContextProps | undefined>(undefined);const useSidebarDrawer = () => {const context = useContext(DrawerContext);if (!context) {throw new Error('useDrawer must be used within a DrawerProvider');}return context;};interface DrawerSidebarProps {children: ReactNode;open?: boolean;setOpen?: (open: boolean) => void;direction?: 'left' | 'right';outsideClose?: boolean;className?: string;triggerClassName?: string;DefaultTrigger?: () => React.ReactNode; // Changed to a function that returns ReactNode}export function SidebarDrawer({children,open: controlledOpen,setOpen: controlledSetOpen,direction = 'left',outsideClose = true,className,triggerClassName,DefaultTrigger, // Now a function prop}: DrawerSidebarProps) {const [internalOpen, setInternalOpen] = useState(false);const open = controlledOpen !== undefined ? controlledOpen : internalOpen;const setOpen =controlledSetOpen !== undefined ? controlledSetOpen : setInternalOpen;const [isDesktop, setIsDesktop] = useState(false);useEffect(() => {const mediaQuery = window.matchMedia('(min-width: 768px)');const handleMediaChange = (event: MediaQueryListEvent) => {setIsDesktop(event.matches);};setIsDesktop(mediaQuery.matches);mediaQuery.addEventListener('change', handleMediaChange);return () => {mediaQuery.removeEventListener('change', handleMediaChange);};}, []);return (<DrawerContext.Provider value={{ open, setOpen }}><>{DefaultTrigger && (<div onClick={() => setOpen(true)}>{DefaultTrigger()}</div>)}<VaulSidebar.Rootopen={open}direction={direction === 'right' ? 'right' : 'left'}onOpenChange={setOpen}dismissible={isDesktop ? false : true}><VaulSidebar.Portal><VaulSidebar.OverlayclassName='fixed inset-0 dark:bg-black/40 bg-white/50 backdrop-blur-sm z-50 'onClick={() => setOpen(false)}/><VaulSidebar.ContentclassName={cn(` border-l z-50 ${outsideClose? 'sm:w-[450px] w-[90%] h-[100%] dark:bg-zinc-950 bg-zinc-100': `w-full h-[100%] `} fixed bottom-0 ${direction === 'right' ? 'right-0' : 'left-0'}`,className)}><divclassName={`${outsideClose? 'w-full h-full': 'dark:bg-gray-900 relative bg-white border-r sm:w-[450px] w-[90%] h-full'} `}>{isDesktop ? (<><buttonclassName='flex justify-end w-full absolute right-2 top-2'onClick={() => setOpen(false)}><X /></button></>) : (<><divclassName={`absolute top-[40%] ${direction === 'right' ? 'left-2' : 'right-2'} mx-auto h-16 w-[0.30rem] flex-shrink-0 rounded-full bg-gray-600 my-4`}/></>)}{children}</div></VaulSidebar.Content></VaulSidebar.Portal></VaulSidebar.Root></></DrawerContext.Provider>);}export function DrawerContent({ children }: { children: ReactNode }) {return <>{children}</>;}
you can use state or default button to control the dialog
<SidebarDrawerDefaultTrigger={() => {return (<buttonwhileTap={{ scale: 0.8 }}className='absolute right-2 bottom-2 p-4 dark:bg-black bg-white rounded-lg shadow-black'><Edit /></button>);}}direction={'right'}outsideClose={true}><DrawerContent></DrawerContent></SidebarDrawer>
Prop | Type | Description |
---|---|---|
open | boolean | The content to be displayed within the AuroraBackground component. |
setOpen | boolean | this is an function to close and open the drawer |
drawerBtn | function | this is an function for default button, when you don't to use state then you can use drawerBtn |