drawer.tsx raw

   1  import * as React from 'react'
   2  import { Drawer as DrawerPrimitive } from 'vaul'
   3  
   4  import { randomString } from '@/lib/random'
   5  import { cn } from '@/lib/utils'
   6  import modalManager from '@/services/modal-manager.service'
   7  
   8  const Drawer = ({
   9    shouldScaleBackground = true,
  10    open,
  11    onOpenChange,
  12    ...props
  13  }: React.ComponentProps<typeof DrawerPrimitive.Root>) => {
  14    const [innerOpen, setInnerOpen] = React.useState(open ?? false)
  15    const id = React.useMemo(() => `drawer-${randomString()}`, [])
  16  
  17    React.useEffect(() => {
  18      if (open) {
  19        modalManager.register(id, () => {
  20          onOpenChange?.(false)
  21        })
  22      } else {
  23        modalManager.unregister(id)
  24      }
  25    }, [open])
  26  
  27    React.useEffect(() => {
  28      if (open !== undefined) {
  29        return
  30      }
  31  
  32      if (innerOpen) {
  33        modalManager.register(id, () => {
  34          setInnerOpen(false)
  35        })
  36      } else {
  37        modalManager.unregister(id)
  38      }
  39    }, [innerOpen])
  40  
  41    return (
  42      <DrawerPrimitive.Root
  43        shouldScaleBackground={shouldScaleBackground}
  44        open={open ?? innerOpen}
  45        onOpenChange={onOpenChange ?? setInnerOpen}
  46        {...props}
  47      />
  48    )
  49  }
  50  Drawer.displayName = 'Drawer'
  51  
  52  const DrawerTrigger = DrawerPrimitive.Trigger
  53  
  54  const DrawerPortal = DrawerPrimitive.Portal
  55  
  56  const DrawerClose = DrawerPrimitive.Close
  57  
  58  const DrawerOverlay = React.forwardRef<
  59    React.ElementRef<typeof DrawerPrimitive.Overlay>,
  60    React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
  61  >(({ className, ...props }, ref) => (
  62    <DrawerPrimitive.Overlay
  63      ref={ref}
  64      className={cn('fixed inset-0 z-50 bg-black/80', className)}
  65      {...props}
  66    />
  67  ))
  68  DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
  69  
  70  const DrawerContent = React.forwardRef<
  71    React.ElementRef<typeof DrawerPrimitive.Content>,
  72    React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & { hideOverlay?: boolean }
  73  >(({ className, children, hideOverlay = false, ...props }, ref) => (
  74    <DrawerPortal>
  75      {!hideOverlay && <DrawerOverlay />}
  76      <DrawerPrimitive.Content
  77        ref={ref}
  78        className={cn(
  79          'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-2xl sm:border bg-background',
  80          className
  81        )}
  82        style={{
  83          paddingBottom: 'env(safe-area-inset-bottom)'
  84        }}
  85        onOpenAutoFocus={(e) => e.preventDefault()}
  86        {...props}
  87      >
  88        <div className="mx-auto mt-4 pb-2 mb-2 h-2 w-[100px] rounded-full bg-muted" />
  89        {children}
  90      </DrawerPrimitive.Content>
  91    </DrawerPortal>
  92  ))
  93  DrawerContent.displayName = 'DrawerContent'
  94  
  95  const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
  96    <div className={cn('grid gap-1.5 p-4 text-center sm:text-left', className)} {...props} />
  97  )
  98  DrawerHeader.displayName = 'DrawerHeader'
  99  
 100  const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
 101    <div className={cn('mt-auto flex flex-col gap-2 p-4', className)} {...props} />
 102  )
 103  DrawerFooter.displayName = 'DrawerFooter'
 104  
 105  const DrawerTitle = React.forwardRef<
 106    React.ElementRef<typeof DrawerPrimitive.Title>,
 107    React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
 108  >(({ className, ...props }, ref) => (
 109    <DrawerPrimitive.Title
 110      ref={ref}
 111      className={cn('text-lg font-semibold leading-none tracking-tight', className)}
 112      {...props}
 113    />
 114  ))
 115  DrawerTitle.displayName = DrawerPrimitive.Title.displayName
 116  
 117  const DrawerDescription = React.forwardRef<
 118    React.ElementRef<typeof DrawerPrimitive.Description>,
 119    React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
 120  >(({ className, ...props }, ref) => (
 121    <DrawerPrimitive.Description
 122      ref={ref}
 123      className={cn('text-sm text-muted-foreground', className)}
 124      {...props}
 125    />
 126  ))
 127  DrawerDescription.displayName = DrawerPrimitive.Description.displayName
 128  
 129  export {
 130    Drawer,
 131    DrawerClose,
 132    DrawerContent,
 133    DrawerDescription,
 134    DrawerFooter,
 135    DrawerHeader,
 136    DrawerOverlay,
 137    DrawerPortal,
 138    DrawerTitle,
 139    DrawerTrigger
 140  }
 141