import React, { useEffect, useRef } from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; import { createRocket } from "../_util/create-rocket"; import { StyledProps } from "../_type"; import { Justify } from "../justify"; import { Button } from "../button"; import { FadeTransition, SlideTransition } from "../transition"; import { useOutsideClick } from "../_util/use-outside-click"; import { H3 } from "../heading"; import { useConfig } from "../_util/config-context"; import { useVisibleTransition } from "../_util/use-visible-transition"; import { callBoth } from "../_util/call-both"; import { noop } from "../_util/noop"; import { uuid } from "../_util/uuid"; import { zStack } from "../_util/z-stack"; import { BackDrop } from "../backdrop"; import { useOverlayFocus } from "../_util/use-overlay-focus"; import { AttachContainer, getOverlayRoot } from "../_util/get-overlay-root"; import { useTranslation } from "../i18n"; import { mergeRefs } from "../util"; export interface DrawerProps extends StyledProps { /** * Drawer 是否可见 */ visible: boolean; /** * 点击关闭图标或抽屉外区域的回调 */ onClose: () => void; /** * 抽屉关闭动画结束时回调 */ onExited?: () => void; /** * 抽屉方向 * @default "right" */ placement?: "right" | "left"; /** * 抽屉大小 * * - `"m"` - 360px * - `"l"` - 800px * * @default "m" */ size?: "m" | "l"; /** * Drawer 中的内容 */ children?: React.ReactNode; /** * Drawer 底部内容 */ footer?: React.ReactNode; /** * 头部标题 */ title?: React.ReactNode; /** * 头部副标题/说明文字 */ subtitle?: React.ReactNode; /** * **\[Deprecated\]** 请使用 `subtitle` 属性 * * @deprecated */ subTitle?: React.ReactNode; /** * 头部右侧渲染内容 */ extra?: React.ReactNode; /** * 点击面板外是否收起面板 * @default true */ outerClickClosable?: boolean; /** * 是否禁用头部关闭图标 * @default false */ disableCloseIcon?: boolean; /** * 是否禁用展开/收起动效 * @default false */ disableAnimation?: boolean; /** * 关闭时是否销毁 Modal 中元素 * @default true * @since 2.3.0 */ destroyOnClose?: boolean; /** * 是否显示遮罩 * @default false * @since 2.5.3 */ showMask?: boolean; /** * 遮罩样式 * @since 2.5.3 */ maskStyle?: React.CSSProperties; /** * 挂载组件的节点 * @default document.body * @since 2.6.0 */ popupContainer?: AttachContainer; } export const Drawer = React.forwardRef(function Drawer( { className, style = {}, children, visible, onClose = noop, onExited = noop, size, placement, footer, title, subTitle, subtitle = subTitle, extra, disableCloseIcon, disableAnimation, outerClickClosable = true, destroyOnClose = true, showMask = false, maskStyle = {}, popupContainer, }: DrawerProps, fRef: React.Ref ) { const { classPrefix, popupContainer: globalPopupContainer } = useConfig(); const t = useTranslation(); const ref = useRef(null); const focusRef = useRef(null); const idRef = useRef(uuid()); const firstRender = useRef(!!visible); const { listen, ignoreProps } = useOutsideClick(ref); listen( () => outerClickClosable && visible && zStack.top === idRef.current && onClose() ); if (visible && !firstRender.current) { firstRender.current = true; } const width = size === "l" ? 800 : 320; const from = placement === "left" ? 0 - width : width; const showHeader = title || subtitle || extra || !disableCloseIcon; const { contentIn, shouldContentEnter, shouldContentRender, onContentExit, } = useVisibleTransition(visible); useEffect(() => { if (contentIn) { const id = idRef.current; zStack.push(id); return () => { zStack.remove(id); }; } return noop; }, [contentIn]); useOverlayFocus(visible, focusRef); if ( !shouldContentRender && (destroyOnClose || (!destroyOnClose && !firstRender.current)) ) { return null; } return ReactDOM.createPortal(
{showMask && ( )}
{showHeader && (
} right={ <> {extra} {!disableCloseIcon && (
)} {children} {footer && {footer}}
, getOverlayRoot(popupContainer || globalPopupContainer) ); }); Drawer.displayName = "Drawer"; function DrawerTitle({ title, subtitle, classPrefix, }: Pick & { classPrefix: string }) { const sub = typeof subtitle === "string" ? ( {subtitle} ) : ( subtitle ); if (typeof title === "string") { return (

{title} {sub}

); } return ( <> {title} {sub} ); } DrawerTitle.displayName = "DrawerTitle"; const DrawerBody = createRocket( "DrawerBody", "div.@{prefix}-drawer__body", "div.@{prefix}-drawer__body-inner" ); const DrawerFooter = createRocket( "DrawerFooter", "div.@{prefix}-drawer__footer" );