bottom-sheet.tsx
1 'use client' 2 3 import type { ReactNode } from 'react' 4 import { XIcon } from 'lucide-react' 5 import { Dialog as DialogPrimitive } from 'radix-ui' 6 7 interface Props { 8 open: boolean 9 onClose: () => void 10 children: ReactNode 11 wide?: boolean 12 title?: string 13 description?: string 14 } 15 16 export function BottomSheet({ open, onClose, children, wide, title, description }: Props) { 17 return ( 18 <DialogPrimitive.Root open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onClose() }}> 19 <DialogPrimitive.Portal> 20 <DialogPrimitive.Overlay 21 className="fixed inset-0 z-100 bg-black/72 backdrop-blur-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" 22 /> 23 <DialogPrimitive.Content 24 className={`fixed inset-x-0 bottom-0 z-100 mx-auto flex max-h-[92vh] w-full flex-col bg-raised shadow-[0_24px_80px_rgba(0,0,0,0.6),0_0_1px_rgba(255,255,255,0.05)] outline-none 25 rounded-t-[24px] border border-white/[0.06] 26 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom 27 sm:inset-x-auto sm:top-[50%] sm:bottom-auto sm:left-[50%] sm:w-[calc(100%-2rem)] sm:translate-x-[-50%] sm:translate-y-[-50%] sm:rounded-[24px] 28 sm:data-[state=closed]:zoom-out-95 sm:data-[state=open]:zoom-in-95 29 ${wide ? 'sm:max-w-[760px]' : 'sm:max-w-[560px]'}`} 30 style={{ animationDuration: '220ms' }} 31 > 32 <div className="relative shrink-0 px-4 pt-4 pr-14 sm:px-5 sm:pt-6 sm:pr-16"> 33 <div className="mx-auto h-1 w-10 rounded-full bg-white/[0.08] sm:hidden" /> 34 <DialogPrimitive.Title className="sr-only"> 35 {title || 'Dialog'} 36 </DialogPrimitive.Title> 37 {description ? ( 38 <DialogPrimitive.Description className="sr-only"> 39 {description} 40 </DialogPrimitive.Description> 41 ) : null} 42 <DialogPrimitive.Close 43 className="absolute right-4 top-3.5 inline-flex h-9 w-9 cursor-pointer items-center justify-center rounded-[12px] border border-white/[0.06] bg-white/[0.03] text-text-3 transition-all hover:bg-white/[0.06] hover:text-text-2 focus:outline-none focus:ring-2 focus:ring-accent-bright/30 sm:right-5 sm:top-5" 44 > 45 <XIcon className="size-4" /> 46 <span className="sr-only">Close</span> 47 </DialogPrimitive.Close> 48 </div> 49 <div className="flex-1 overflow-y-auto px-5 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-3 sm:px-8 sm:pb-8 sm:pt-5"> 50 {children} 51 </div> 52 </DialogPrimitive.Content> 53 </DialogPrimitive.Portal> 54 </DialogPrimitive.Root> 55 ) 56 }