'use client' import * as React from 'react' import { Drawer as BaseDrawer } from '@base-ui/react/drawer' import { cn } from '../../internal/utils' import { type ModalContentProps, splitModalProps } from '../../internal/modal' import { buttonVariants } from '../button/button-variants' type DrawerSide = 'top' | 'bottom' | 'left' | 'right' const SIDE_TO_SWIPE = { top: 'up', bottom: 'down', left: 'left', right: 'right', } as const satisfies Record const DrawerSideContext = React.createContext('bottom') const DrawerDepthContext = React.createContext(0) const DrawerSnapContext = React.createContext(false) interface DrawerProps extends Omit, 'swipeDirection'> { /** * The edge the drawer slides in from. * @default 'bottom' */ side?: DrawerSide } const Drawer = Object.assign( function Drawer({ side = 'bottom', ...props }: DrawerProps) { const depth = React.useContext(DrawerDepthContext) + 1 return ( ) }, { createHandle: BaseDrawer.createHandle }, ) type DrawerProviderProps = React.ComponentProps function DrawerProvider(props: DrawerProviderProps) { return } type DrawerIndentBackgroundProps = React.ComponentProps function DrawerIndentBackground({ className, ...props }: DrawerIndentBackgroundProps) { return ( ) } type DrawerIndentProps = React.ComponentProps function DrawerIndent({ className, ...props }: DrawerIndentProps) { return ( ) } type DrawerTriggerProps = React.ComponentProps function DrawerTrigger({ className, ...props }: DrawerTriggerProps) { return } const VIEWPORT_SIDE = { bottom: 'items-end justify-center', top: 'items-start justify-center', left: 'items-stretch justify-start', right: 'items-stretch justify-end', } satisfies Record const POPUP_SIDE = { bottom: cn( 'w-full origin-bottom [height:var(--drawer-height,auto)] max-h-full [--stack-extent:var(--drawer-frontmost-height,var(--drawer-height,0px))]', 'data-nested-drawer-open:[height:var(--drawer-frontmost-height,auto)] data-nested-drawer-open:overflow-hidden', '[transform:translateY(calc(var(--drawer-swipe-movement-y)-var(--stack-offset)))_scale(var(--stack-scale))]', 'data-starting-style:[transform:translateY(calc(100%+0.5rem))] data-ending-style:[transform:translateY(calc(100%+0.5rem))]', ), top: cn( 'w-full origin-top [height:var(--drawer-height,auto)] max-h-full [--stack-extent:var(--drawer-frontmost-height,var(--drawer-height,0px))]', 'data-nested-drawer-open:[height:var(--drawer-frontmost-height,auto)] data-nested-drawer-open:overflow-hidden', '[transform:translateY(calc(var(--drawer-swipe-movement-y)+var(--stack-offset)))_scale(var(--stack-scale))]', 'data-starting-style:[transform:translateY(calc(-100%-0.5rem))] data-ending-style:[transform:translateY(calc(-100%-0.5rem))]', ), left: cn( 'h-full w-96 max-w-full origin-left [--stack-extent:25rem]', '[transform:translateX(calc(var(--drawer-swipe-movement-x)+var(--stack-offset)))_scale(var(--stack-scale))]', 'data-starting-style:[transform:translateX(calc(-100%-0.5rem))] data-ending-style:[transform:translateX(calc(-100%-0.5rem))]', ), right: cn( 'h-full w-96 max-w-full origin-right [--stack-extent:25rem]', '[transform:translateX(calc(var(--drawer-swipe-movement-x)-var(--stack-offset)))_scale(var(--stack-scale))]', 'data-starting-style:[transform:translateX(calc(100%+0.5rem))] data-ending-style:[transform:translateX(calc(100%+0.5rem))]', ), } satisfies Record const POPUP_SNAP_SIDE = { bottom: cn( 'h-[calc(100dvh-1rem)] w-full min-h-0 origin-bottom shadow-[0_-24px_32px_-12px_var(--shadow-color)]', '[--snap-offset:var(--drawer-snap-point-offset,0px)]', '[transform:translateY(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)))]', 'data-starting-style:[transform:translateY(calc(100%+0.5rem))] data-ending-style:[transform:translateY(calc(100%+0.5rem))]', ), top: cn( 'h-[calc(100dvh-1rem)] w-full min-h-0 origin-top', '[--snap-offset:var(--drawer-snap-point-offset,0px)]', '[transform:translateY(calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y)))]', 'data-starting-style:[transform:translateY(calc(-100%-0.5rem))] data-ending-style:[transform:translateY(calc(-100%-0.5rem))]', ), } satisfies Record<'top' | 'bottom', string> const SHADOW_SIDE = { bottom: 'shadow-[0_-24px_32px_-12px_var(--shadow-color)]', top: 'shadow-2xl', left: 'shadow-[24px_0_32px_-12px_var(--shadow-color)]', right: 'shadow-[-24px_0_32px_-12px_var(--shadow-color)]', } satisfies Record const HANDLE_SIDE = { bottom: 'before:absolute before:top-1.5 before:left-1/2 before:h-1 before:w-11.5 before:-translate-x-1/2 before:rounded-full', top: 'before:absolute before:bottom-1.5 before:left-1/2 before:h-1 before:w-11.5 before:-translate-x-1/2 before:rounded-full', left: 'before:absolute before:inset-e-1.5 before:top-1/2 before:h-11.5 before:w-1 before:-translate-y-1/2 before:rounded-full', right: 'before:absolute before:inset-s-1.5 before:top-1/2 before:h-11.5 before:w-1 before:-translate-y-1/2 before:rounded-full', } satisfies Record const FRAME_PAD_SIDE = { bottom: 'pt-4!', top: 'pb-4!', left: 'pe-4!', right: 'ps-4!', } satisfies Record const CONTENT_RECLAIM_SIDE = { bottom: 'overflow-hidden [&>[data-slot=drawer-header]]:-mt-4 [&>[data-slot=drawer-close-button]]:top-0', top: 'overflow-hidden [&>[data-slot=drawer-footer]]:-mb-4', left: 'overflow-hidden [&>:not([data-slot=drawer-close-button])]:-me-4 [&>[data-slot=drawer-close-button]]:inset-e-0', right: 'overflow-hidden [&>:not([data-slot=drawer-close-button])]:-ms-4', } satisfies Record type DrawerContentProps = ModalContentProps< React.ComponentProps, React.ComponentProps, React.ComponentProps, React.ComponentProps > & { /** * Render the × button in the corner. * @default true */ closeButton?: boolean /** * Accessible label for the close button. * @default 'Close' */ closeLabel?: string /** Render the dimmed backdrop. Defaults to `true` at the top level, off when nested. */ backdrop?: boolean /** * Wrap the panel in a translucent glass frame. Turned off, the drawer is a plain solid * card and the handle sits inside it, the way a nested drawer looks. Needs `backdrop`: * without one the panel is always solid. * @default true */ frame?: boolean } function DrawerContent({ className, children, closeButton = true, closeLabel = 'Close', backdrop, frame = true, backdropProps, viewportProps, ...props }: DrawerContentProps) { const side = React.useContext(DrawerSideContext) const depth = React.useContext(DrawerDepthContext) const hasSnap = React.useContext(DrawerSnapContext) const forceBackdrop = backdropProps?.forceRender === true const showBackdrop = forceBackdrop || (backdrop ?? depth <= 1) // The frame is a rim of blurred page around the panel, so it only reads against the // backdrop. Without one - a nested drawer, or `backdrop={false}` - it collapses to a // plain solid card with the handle inside. const showFrame = frame && showBackdrop const snapSide = hasSnap && (side === 'bottom' || side === 'top') ? side : null const { portal, popup } = splitModalProps(props) return ( {showBackdrop && ( )}
[data-slot=drawer-footer]]:pb-6 not-has-[>[data-slot=drawer-header]]:pt-6 [&>[data-slot=drawer-header]+[data-slot=drawer-footer]]:pt-0', showFrame || snapSide ? 'bg-background rounded-[calc(var(--radius-2xl)*5/6)]' : CONTENT_RECLAIM_SIDE[side], snapSide ? 'h-[calc(100dvh-1.5rem-var(--snap-offset,0))]' : 'flex-1', )} > {children} {closeButton && ( )}
) } type DrawerHeaderProps = React.ComponentPropsWithoutRef<'div'> function DrawerHeader({ className, ...props }: DrawerHeaderProps) { return
} type DrawerTitleProps = React.ComponentProps function DrawerTitle({ className, ...props }: DrawerTitleProps) { return ( ) } type DrawerDescriptionProps = React.ComponentProps function DrawerDescription({ className, ...props }: DrawerDescriptionProps) { return ( ) } type DrawerBodyProps = React.ComponentProps function DrawerBody({ className, ...props }: DrawerBodyProps) { return ( ) } type DrawerFooterProps = React.ComponentPropsWithoutRef<'div'> function DrawerFooter({ className, ...props }: DrawerFooterProps) { return
} type DrawerCloseProps = React.ComponentProps function DrawerClose({ className, ...props }: DrawerCloseProps) { return } function CloseIcon(props: React.SVGProps) { return ( ) } export { Drawer, DrawerProvider, DrawerIndent, DrawerIndentBackground, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerBody, DrawerFooter, DrawerClose, } export type { DrawerProps, DrawerProviderProps, DrawerIndentProps, DrawerIndentBackgroundProps, DrawerTriggerProps, DrawerContentProps, DrawerHeaderProps, DrawerTitleProps, DrawerDescriptionProps, DrawerBodyProps, DrawerFooterProps, DrawerCloseProps, }