sheet.tsx raw

   1  import * as React from 'react'
   2  import * as SheetPrimitive from '@radix-ui/react-dialog'
   3  import { cva, type VariantProps } from 'class-variance-authority'
   4  import { X } from 'lucide-react'
   5  
   6  import { randomString } from '@/lib/random'
   7  import { cn } from '@/lib/utils'
   8  import modalManager from '@/services/modal-manager.service'
   9  
  10  const Sheet = ({ children, open, onOpenChange, ...props }: SheetPrimitive.DialogProps) => {
  11    const [innerOpen, setInnerOpen] = React.useState(open ?? false)
  12    const id = React.useMemo(() => `sheet-${randomString()}`, [])
  13  
  14    React.useEffect(() => {
  15      if (open) {
  16        modalManager.register(id, () => {
  17          onOpenChange?.(false)
  18        })
  19      } else {
  20        modalManager.unregister(id)
  21      }
  22    }, [open])
  23  
  24    React.useEffect(() => {
  25      if (open !== undefined) {
  26        return
  27      }
  28  
  29      if (innerOpen) {
  30        modalManager.register(id, () => {
  31          setInnerOpen(false)
  32        })
  33      } else {
  34        modalManager.unregister(id)
  35      }
  36    }, [innerOpen])
  37  
  38    return (
  39      <SheetPrimitive.Root
  40        open={open ?? innerOpen}
  41        onOpenChange={onOpenChange ?? setInnerOpen}
  42        {...props}
  43      >
  44        {children}
  45      </SheetPrimitive.Root>
  46    )
  47  }
  48  
  49  const SheetTrigger = SheetPrimitive.Trigger
  50  
  51  const SheetClose = SheetPrimitive.Close
  52  
  53  const SheetPortal = SheetPrimitive.Portal
  54  
  55  const SheetOverlay = React.forwardRef<
  56    React.ElementRef<typeof SheetPrimitive.Overlay>,
  57    React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
  58  >(({ className, ...props }, ref) => (
  59    <SheetPrimitive.Overlay
  60      className={cn(
  61        'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
  62        className
  63      )}
  64      {...props}
  65      ref={ref}
  66    />
  67  ))
  68  SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
  69  
  70  const sheetVariants = cva(
  71    'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
  72    {
  73      variants: {
  74        side: {
  75          top: 'inset-x-0 top-0 border-b rounded-b-xl data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
  76          bottom:
  77            'inset-x-0 bottom-0 border-t rounded-t-xl data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
  78          left: 'inset-y-0 left-0 h-full w-3/4 border-r rounded-r-xl data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
  79          right:
  80            'inset-y-0 right-0 h-full w-3/4 border-l rounded-l-xl data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm'
  81        }
  82      },
  83      defaultVariants: {
  84        side: 'right'
  85      }
  86    }
  87  )
  88  
  89  interface SheetContentProps
  90    extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
  91      VariantProps<typeof sheetVariants> {}
  92  
  93  const SheetContent = React.forwardRef<
  94    React.ElementRef<typeof SheetPrimitive.Content>,
  95    SheetContentProps & { hideClose?: boolean }
  96  >(({ side = 'right', className, children, hideClose = false, ...props }, ref) => (
  97    <SheetPortal>
  98      <SheetOverlay />
  99      <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
 100        {!hideClose && (
 101          <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
 102            <X className="h-4 w-4" />
 103            <span className="sr-only">Close</span>
 104          </SheetPrimitive.Close>
 105        )}
 106        {children}
 107      </SheetPrimitive.Content>
 108    </SheetPortal>
 109  ))
 110  SheetContent.displayName = SheetPrimitive.Content.displayName
 111  
 112  const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
 113    <div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
 114  )
 115  SheetHeader.displayName = 'SheetHeader'
 116  
 117  const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
 118    <div
 119      className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
 120      {...props}
 121    />
 122  )
 123  SheetFooter.displayName = 'SheetFooter'
 124  
 125  const SheetTitle = React.forwardRef<
 126    React.ElementRef<typeof SheetPrimitive.Title>,
 127    React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
 128  >(({ className, ...props }, ref) => (
 129    <SheetPrimitive.Title
 130      ref={ref}
 131      className={cn('text-lg font-semibold text-foreground', className)}
 132      {...props}
 133    />
 134  ))
 135  SheetTitle.displayName = SheetPrimitive.Title.displayName
 136  
 137  const SheetDescription = React.forwardRef<
 138    React.ElementRef<typeof SheetPrimitive.Description>,
 139    React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
 140  >(({ className, ...props }, ref) => (
 141    <SheetPrimitive.Description
 142      ref={ref}
 143      className={cn('text-sm text-muted-foreground', className)}
 144      {...props}
 145    />
 146  ))
 147  SheetDescription.displayName = SheetPrimitive.Description.displayName
 148  
 149  export {
 150    Sheet,
 151    SheetPortal,
 152    SheetOverlay,
 153    SheetTrigger,
 154    SheetClose,
 155    SheetContent,
 156    SheetHeader,
 157    SheetFooter,
 158    SheetTitle,
 159    SheetDescription
 160  }
 161