import { useRef } from 'react'; import { Drawer as BaseDrawer } from '@base-ui/react/drawer'; import { IconClose } from '@tencentcloud/uikit-base-component-react'; import cs from 'classnames'; import styles from './Drawer.module.scss'; import type { DrawerProps } from './types'; /** * Bottom-sheet drawer built on `@base-ui/react/drawer`. * * Drawer extends Dialog, so `` must always be present. * Default is a full-viewport overlay portalled to `document.body`. Pass * `position="absolute"` to portal into a local anchor instead, so the * nearest positioned ancestor contains the overlay (avoids z-index fights * inside another modal). */ export function Drawer({ open, defaultOpen = false, position = 'fixed', swipeDirection = 'down', modal = true, showHeader = true, title = '', showClose = true, closeAriaLabel = 'Close', showOverlay = true, size, className, overlayClassName, contentClassName, style, children, header, footer, onOpenChange, onClose, }: DrawerProps) { const resolvedPosition = position ?? 'fixed'; const resolvedSwipeDirection = swipeDirection ?? 'down'; const isAnchored = resolvedPosition === 'absolute'; const anchorRef = useRef(null); const handleOpenChange = (nextOpen: boolean) => { onOpenChange?.(nextOpen); if (!nextOpen) { onClose?.(); } }; const isHorizontal = resolvedSwipeDirection === 'left' || resolvedSwipeDirection === 'right'; const sizeStyle = size ? { [isHorizontal ? 'width' : 'height']: size } : undefined; const positionClass = styles[`base-drawer--${resolvedPosition}`]; const swipeClass = styles[`base-drawer--swipe-${resolvedSwipeDirection}`]; const surface = ( <> {showOverlay && ( )} {showHeader && (
{header ?? ( <> {title} {showClose && ( )} )}
)} {/* Scrollable body opts out of swipe-to-dismiss so list pans don't fight the drawer gesture. Header + overlay still dismiss. */} {children} {footer &&
{footer}
}
); const drawer = ( {surface} ); if (!isAnchored) { return drawer; } return (
{drawer}
); } export default Drawer;