emilkowalski / vaul

A drawer component for React.
https://vaul.emilkowal.ski
MIT License
6.45k stars 215 forks source link

feat: export useDrawerContext #467

Open jasongerbes opened 1 month ago

jasongerbes commented 1 month ago

Vaul doesn't currently export the useDrawerContext hook, which would be helpful for getting the direction of the <Drawer.Root> to style the <Drawer.Content> appropriately.

For context, I'm updating my instance of the shadcn/ui Drawer component to support all directions (similar to the Sheet component).

This PR exports useDrawerContext and DrawerContext, and includes some format fixes for the new JSDocs.

jasongerbes commented 1 month ago

Hey @emilkowalski, you're right that DrawerContext can't currently be used externally.

My use case requires knowing the value of the direction prop to position the Drawer.Handle appropriately:

I'm currently using a custom DrawerContext to share the direction prop with children of the Drawer.Root.

Here's the full implementation for context. It builds upon the shadcn/ui Drawer component.

components/ui/drawer.tsx ```ts 'use client'; import * as React from 'react'; import { Cross2Icon } from '@radix-ui/react-icons'; import { cva } from 'class-variance-authority'; import { Drawer as DrawerPrimitive } from 'vaul'; import { cn } from '@/lib/utils'; type DrawerContextValue = Pick; const DrawerContext = React.createContext({}); const useDrawerContext = () => { const context = React.useContext(DrawerContext); if (!context) { throw new Error('useDrawerContext must be used within a Drawer.Root'); } return context; }; export type DrawerProps = React.ComponentProps; const Drawer = ({ shouldScaleBackground = true, direction = 'bottom', ...props }: DrawerProps) => ( ); Drawer.displayName = 'Drawer'; const DrawerTrigger = DrawerPrimitive.Trigger; const DrawerPortal = DrawerPrimitive.Portal; const DrawerClose = DrawerPrimitive.Close; const DrawerOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; const drawContentVariants = cva( 'fixed z-50 flex flex-col bg-background outline-none', { variants: { direction: { top: 'inset-x-0 top-0 mb-24 h-auto max-h-[90%] rounded-b-[10px]', bottom: 'inset-x-0 bottom-0 mt-24 h-auto max-h-[90%] rounded-t-[10px]', left: 'inset-y-0 left-0 mr-24 w-full max-w-[85%] rounded-r-[10px] lg:max-w-3xl', right: 'inset-y-0 right-0 ml-24 w-full max-w-[85%] rounded-l-[10px] lg:max-w-3xl', }, }, defaultVariants: { direction: 'bottom', }, }, ); const DrawerContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => { const { direction } = useDrawerContext(); const isHorizontal = direction === 'left' || direction === 'right'; return ( {direction === 'bottom' && }
{children}
{direction === 'top' && } {isHorizontal && ( Close )}
); }); DrawerContent.displayName = 'DrawerContent'; const DrawerHandle = () => (
); const DrawerHeader = ({ className, ...props }: React.HTMLAttributes) => (
); DrawerHeader.displayName = 'DrawerHeader'; const DrawerBody = ({ className, ...props }: React.HTMLAttributes) => (
); DrawerBody.displayName = 'DrawerBody'; const DrawerFooter = ({ className, ...props }: React.HTMLAttributes) => (
); DrawerFooter.displayName = 'DrawerFooter'; const DrawerTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DrawerTitle.displayName = DrawerPrimitive.Title.displayName; const DrawerDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DrawerDescription.displayName = DrawerPrimitive.Description.displayName; export { Drawer, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerBody, DrawerFooter, DrawerTitle, DrawerDescription, }; ```

This workaround is sufficient, so feel free to close this PR if there are specific issues with exporting DrawerContext.

marcusforsberg commented 1 month ago

I also have a need to access useDrawerContext to customize the behaviour of the Overlay. I have non-modal Drawer, which currently force hides the Overlay entirely. I still need the overlay to be visible on certain snap points. If I had access to the context, I could implement my own Overlay logic.

AhmedBaset commented 1 week ago

Is there anything blocking merging this? I have the exact use case as the author