/ src / components / shared / bottom-sheet.tsx
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  }